mirror of
https://github.com/modelec/modelec.club.git
synced 2026-01-18 16:37:30 +01:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
4
.github/codeql/codeql-analysis.yml
vendored
Normal file
4
.github/codeql/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
name: "Default setup"
|
||||
queries:
|
||||
- javascript-security-and-quality.qls
|
||||
- typescript-security-and-quality.qls
|
||||
4
.github/codeql/codeql-config.yml
vendored
Normal file
4
.github/codeql/codeql-config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
name: "Default setup"
|
||||
queries:
|
||||
- javascript-security-and-quality.qls
|
||||
- typescript-security-and-quality.qls
|
||||
39
.github/workflows/deploy-test.yml
vendored
Normal file
39
.github/workflows/deploy-test.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Deploy to server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: recette
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies and build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Copy build directory to server
|
||||
uses: Dylan700/sftp-upload-action@v1.2.3
|
||||
with:
|
||||
server: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USERNAME }}
|
||||
password: ${{ secrets.SERVER_PASSWORD }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
uploads: |
|
||||
./dist => /var/www/modelec.club
|
||||
dry-run: true
|
||||
delete: true
|
||||
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@@ -35,3 +35,4 @@ jobs:
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
uploads: |
|
||||
./dist => /var/www/modelec.club
|
||||
delete: true
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.10.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.8.1",
|
||||
"vite": "^5.4.9"
|
||||
}
|
||||
@@ -3113,11 +3113,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.10.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.8.1",
|
||||
"vite": "^5.4.9"
|
||||
}
|
||||
|
||||
@@ -1,92 +1,116 @@
|
||||
import React from "react";
|
||||
|
||||
import { NavArrowLeft, NavArrowRight } from "iconoir-react";
|
||||
|
||||
import "./carousel.css";
|
||||
import React from 'react';
|
||||
import { NavArrowLeft, NavArrowRight } from 'iconoir-react';
|
||||
import './carousel.css';
|
||||
|
||||
interface CarouselProps {
|
||||
carousel: CarouselImageProps[];
|
||||
carousel: CarouselImageProps[];
|
||||
}
|
||||
|
||||
interface CarouselImageProps {
|
||||
image: string;
|
||||
title: string;
|
||||
text: string;
|
||||
link?: string;
|
||||
alt?: string;
|
||||
image: string;
|
||||
title: string;
|
||||
text: string;
|
||||
link?: string;
|
||||
alt?: string;
|
||||
}
|
||||
|
||||
export const Carousel: React.FC<CarouselProps> = ({ carousel }) => {
|
||||
const [currentIndex, setCurrentImage] = React.useState(0);
|
||||
const [imagesSize, setImagesSize] = React.useState<number[]>([]);
|
||||
const carouselSlider = React.useRef<HTMLDivElement>(null);
|
||||
const [currentIndex, setCurrentImage] = React.useState(0);
|
||||
const [imagesSize, setImagesSize] = React.useState<number[]>([]);
|
||||
const carouselSlider = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const nextImage = () => {
|
||||
setCurrentImage((currentIndex + 1) % carousel.length);
|
||||
const nextImage = () => {
|
||||
setCurrentImage((prevIndex) => (prevIndex + 1) % (carousel.length * 2));
|
||||
};
|
||||
|
||||
const previousImage = () => {
|
||||
setCurrentImage(
|
||||
(prevIndex) =>
|
||||
(prevIndex - 1 + carousel.length * 2) % (carousel.length * 2)
|
||||
);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
nextImage();
|
||||
}, 3000);
|
||||
|
||||
if (carouselSlider.current) {
|
||||
const translate = imagesSize.reduce(
|
||||
(acc, size, index) => (index < currentIndex ? acc + size : acc),
|
||||
0
|
||||
);
|
||||
const gap = 25; // TODO: Find a way to get this value (HARDCODED)
|
||||
carouselSlider.current.style.transform = `translateX(-${translate + gap * currentIndex}px)`;
|
||||
}
|
||||
|
||||
const previousImage = () => {
|
||||
setCurrentImage((currentIndex - 1 + carousel.length) % carousel.length);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setTimeout(() => {
|
||||
nextImage();
|
||||
}, 3000);
|
||||
|
||||
if (currentIndex >= carousel.length) {
|
||||
setTimeout(() => {
|
||||
setCurrentImage(0);
|
||||
if (carouselSlider.current) {
|
||||
const translate = imagesSize.reduce((acc, size, index) => index < currentIndex ? acc + size : acc, 0);
|
||||
const gap = 25; // TODO: Find a way to get this value (HARDCODED)
|
||||
carouselSlider.current.style.translate = `-${translate+gap*currentIndex}px`;
|
||||
carouselSlider.current.style.transition = 'none';
|
||||
carouselSlider.current.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [currentIndex, imagesSize, carouselSlider]);
|
||||
|
||||
function handleImageLoad(i: number, event: React.SyntheticEvent<HTMLImageElement>) {
|
||||
setImagesSize((old) => {
|
||||
const newSizes = [...old];
|
||||
newSizes[i] = (event.target as HTMLImageElement).clientWidth;
|
||||
return newSizes;
|
||||
});
|
||||
}, 300);
|
||||
} else {
|
||||
if (carouselSlider.current) {
|
||||
carouselSlider.current.style.transition = 'transform 0.3s ease';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"carousel"}>
|
||||
<div className={"carousel-wrapper"}>
|
||||
<div className={"carousel-slider"} ref={carouselSlider}>
|
||||
{
|
||||
carousel.map((image, index) => (
|
||||
<div key={index} className={'slide'}>
|
||||
<img
|
||||
key={index}
|
||||
className={`slide-image ${index === currentIndex ? "active" : ""}`}
|
||||
src={image.image}
|
||||
alt={image.alt ?? "Image Carousel"}
|
||||
onLoad={(e) => handleImageLoad(index, e)}
|
||||
/>
|
||||
<div className={'slide-content'}>
|
||||
<h3 className={'slide-title'}>{image.title}</h3>
|
||||
<p className={'slide-text'}>{image.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="carousel-bar">
|
||||
<button className={"carousel-button button_left"} onClick={previousImage}><NavArrowLeft /></button>
|
||||
{
|
||||
carousel.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`carousel-dot ${index === currentIndex ? "dot_active" : ""}`}
|
||||
onClick={() => setCurrentImage(index)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<button className={"carousel-button button_right"} onClick={nextImage}><NavArrowRight /></button>
|
||||
return () => clearInterval(interval);
|
||||
}, [currentIndex, imagesSize, carouselSlider]);
|
||||
|
||||
function handleImageLoad(
|
||||
i: number,
|
||||
event: React.SyntheticEvent<HTMLImageElement>
|
||||
) {
|
||||
setImagesSize((old) => {
|
||||
const newSizes = [...old];
|
||||
newSizes[i] = (event.target as HTMLImageElement).clientWidth;
|
||||
return newSizes;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'carousel'}>
|
||||
<div className={'carousel-wrapper'}>
|
||||
<div className={'carousel-slider'} ref={carouselSlider}>
|
||||
{[...carousel, ...carousel].map((image, index) => (
|
||||
<div key={index} className={'slide'}>
|
||||
<img
|
||||
key={index}
|
||||
className={`slide-image ${index === currentIndex ? 'active' : ''}`}
|
||||
src={image.image}
|
||||
alt={image.alt ?? 'Image Carousel'}
|
||||
onLoad={(e) => handleImageLoad(index, e)}
|
||||
/>
|
||||
<div className={'slide-content'}>
|
||||
<h3 className={'slide-title'}>{image.title}</h3>
|
||||
<p className={'slide-text'}>{image.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
</div>
|
||||
<div className="carousel-bar">
|
||||
<button
|
||||
className={'carousel-button button_left'}
|
||||
onClick={previousImage}
|
||||
>
|
||||
<NavArrowLeft />
|
||||
</button>
|
||||
{carousel.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`carousel-dot ${index === currentIndex % carousel.length ? 'dot_active' : ''}`}
|
||||
onClick={() => setCurrentImage(index)}
|
||||
/>
|
||||
))}
|
||||
<button className={'carousel-button button_right'} onClick={nextImage}>
|
||||
<NavArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user