mirror of
https://github.com/modelec/modelec.club.git
synced 2026-01-18 16:37:30 +01:00
Merge pull request #91 from modelec/COPA-38
[COPA-38] - lazy loading for images
This commit is contained in:
@@ -71,9 +71,6 @@ describe('<Team />', () => {
|
||||
cy.get(
|
||||
`.team-group:eq(${groupIndex}) .team-member:eq(${memberIndex}) .team-member-role`
|
||||
).should('contain.text', member.role);
|
||||
cy.get(
|
||||
`.team-group:eq(${groupIndex}) .team-member:eq(${memberIndex}) img`
|
||||
).should('have.attr', 'src', member.image);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,27 +152,12 @@ describe('<Timeline />', () => {
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(0)
|
||||
.should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/Bureau/IMG_3881.PNG'
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(1)
|
||||
.should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/Bureau/IMG_3882.PNG'
|
||||
);
|
||||
cy.get('.timeline-project-title').should(
|
||||
'contain.text',
|
||||
'Serge : Notre robot'
|
||||
);
|
||||
cy.get('.timeline-project-banner').should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/serge.png'
|
||||
);
|
||||
cy.get('.timeline-project-paragraph').should(
|
||||
'contain.text',
|
||||
'Voici Serge, notre robot pour la coupe de France de Robotique 2024 !'
|
||||
@@ -184,7 +169,6 @@ describe('<Timeline />', () => {
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(2)
|
||||
.should('have.attr', 'src', 'https://r2.modelec.club/cdf.png');
|
||||
cy.get('.timeline-project-title').should('contain.text', 'PAMI v1');
|
||||
cy.get('.timeline-project-paragraph').should(
|
||||
'contain.text',
|
||||
@@ -192,7 +176,6 @@ describe('<Timeline />', () => {
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(3)
|
||||
.should('have.attr', 'src', 'https://r2.modelec.club/PAMI-V1.png');
|
||||
cy.get('.timeline-project-title').should(
|
||||
'contain.text',
|
||||
'Construction de la table de jeu'
|
||||
@@ -203,27 +186,11 @@ describe('<Timeline />', () => {
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(4)
|
||||
.should('have.attr', 'src', 'https://r2.modelec.club/table2024.jpg');
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(5)
|
||||
.should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/Table/20231102_163959.jpg'
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(6)
|
||||
.should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/Table/20231102_164300.jpg'
|
||||
);
|
||||
cy.get('.timeline-project-card-image')
|
||||
.eq(7)
|
||||
.should(
|
||||
'have.attr',
|
||||
'src',
|
||||
'https://r2.modelec.club/Table/IMG_20231202_160702.jpg'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
38
package-lock.json
generated
38
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-lazy-load-image-component": "^1.6.3",
|
||||
"react-router-dom": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -24,6 +25,7 @@
|
||||
"@simonsmith/cypress-image-snapshot": "^9.1.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-lazy-load-image-component": "^1.6.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
@@ -1864,6 +1866,17 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-lazy-load-image-component": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.4.tgz",
|
||||
"integrity": "sha512-8pFPeDPF4yVG4lU1/ixZidJEEDZmEOYOTYDvmIu2IAabyuv97Q7n/93nMCocHvQ7vD1czKGiW+op55D9m3MkdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sinonjs__fake-timers": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
|
||||
@@ -5603,6 +5616,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@@ -5615,6 +5634,12 @@
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
@@ -6495,6 +6520,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-lazy-load-image-component": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz",
|
||||
"integrity": "sha512-kdQYUDbuISF3T9El0sBLNoWrmPohqlytcG4ognLtHYjY8bZAsJ0/Ez+VaV+0QlVyUY3K6dDXkuQAz3GpvdjBkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x || ^19.x.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-lazy-load-image-component": "^1.6.3",
|
||||
"react-router-dom": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -41,6 +42,7 @@
|
||||
"@simonsmith/cypress-image-snapshot": "^9.1.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-lazy-load-image-component": "^1.6.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
import './banner.css';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface BannerProps {
|
||||
image: { src: string, alt?: string };
|
||||
header: string;
|
||||
children?: React.ReactNode;
|
||||
label?: string;
|
||||
image: { src: string; alt?: string };
|
||||
header: string;
|
||||
children?: React.ReactNode;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const Banner: React.FC<BannerProps> = ({ image, header, children, label }) => {
|
||||
return (
|
||||
<div className={'banner'}>
|
||||
<img className={'banner-image'} src={image.src} alt={image.alt} />
|
||||
<div className={'banner-content'}>
|
||||
<h3 className={'banner-header'}>{header}</h3>
|
||||
<p className={'banner-text'}>{children}</p>
|
||||
{ label && <span className={'banner-label'}>{label}</span> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const Banner: React.FC<BannerProps> = ({
|
||||
image,
|
||||
header,
|
||||
children,
|
||||
label,
|
||||
}) => {
|
||||
return (
|
||||
<div className={'banner'}>
|
||||
<LazyLoadImage
|
||||
className={'banner-image'}
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
/>
|
||||
<div className={'banner-content'}>
|
||||
<h3 className={'banner-header'}>{header}</h3>
|
||||
<p className={'banner-text'}>{children}</p>
|
||||
{label && <span className={'banner-label'}>{label}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NavArrowLeft, NavArrowRight } from 'iconoir-react';
|
||||
import './carousel.css';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface CarouselProps {
|
||||
carousel: CarouselImageProps[];
|
||||
@@ -78,7 +79,7 @@ export const Carousel: React.FC<CarouselProps> = ({ carousel }) => {
|
||||
<div className={'carousel-slider'} ref={carouselSlider}>
|
||||
{[...carousel, ...carousel].map((image, index) => (
|
||||
<div key={index} className={'slide'}>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
key={index}
|
||||
className={`slide-image ${index === currentIndex ? 'active' : ''}`}
|
||||
src={image.image}
|
||||
|
||||
@@ -1,33 +1,47 @@
|
||||
interface PartnerProps {
|
||||
name: string;
|
||||
logo: string;
|
||||
texts: React.ReactNode[];
|
||||
photos: string[];
|
||||
link: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
texts: React.ReactNode[];
|
||||
photos: string[];
|
||||
link: string;
|
||||
}
|
||||
|
||||
import { ArrowUpRight } from 'iconoir-react';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
import './partner.css';
|
||||
|
||||
export const Partner: React.FC<PartnerProps> = ({ name, logo, texts, photos, link }) => (
|
||||
<div className={'partner'}>
|
||||
<div className={'partner-infos'}>
|
||||
<img className={'partner-logo'} src={logo} alt={name} />
|
||||
<div className={'partner-content'}>
|
||||
<h2 className={'partner-name'}>{name}</h2>
|
||||
{texts.map((text, index) => (
|
||||
<p key={index} className={'partner-text'}>{text}</p>
|
||||
))}
|
||||
</div>
|
||||
<a className={'partner-link'} target={'_blank'} href={link}>
|
||||
<ArrowUpRight />
|
||||
Accéder au site
|
||||
</a>
|
||||
</div>
|
||||
<div className={'partner-photos'}>
|
||||
{photos.map((photo, index) => (
|
||||
<img key={index} className={'partner-preview'} src={photo} alt={`Photo ${index + 1} - ${name}`} />
|
||||
))}
|
||||
</div>
|
||||
export const Partner: React.FC<PartnerProps> = ({
|
||||
name,
|
||||
logo,
|
||||
texts,
|
||||
photos,
|
||||
link,
|
||||
}) => (
|
||||
<div className={'partner'}>
|
||||
<div className={'partner-infos'}>
|
||||
<LazyLoadImage className={'partner-logo'} src={logo} alt={name} />
|
||||
<div className={'partner-content'}>
|
||||
<h2 className={'partner-name'}>{name}</h2>
|
||||
{texts.map((text, index) => (
|
||||
<p key={index} className={'partner-text'}>
|
||||
{text}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<a className={'partner-link'} target={'_blank'} href={link}>
|
||||
<ArrowUpRight />
|
||||
Accéder au site
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
<div className={'partner-photos'}>
|
||||
{photos.map((photo, index) => (
|
||||
<img
|
||||
key={index}
|
||||
className={'partner-preview'}
|
||||
src={photo}
|
||||
alt={`Photo ${index + 1} - ${name}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,51 +1,56 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import "./team.css";
|
||||
import './team.css';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface TeamMemberProps {
|
||||
name: string;
|
||||
role: string;
|
||||
image: string;
|
||||
name: string;
|
||||
role: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface TeamGroupProps {
|
||||
title: string;
|
||||
members: TeamMemberProps[];
|
||||
title: string;
|
||||
members: TeamMemberProps[];
|
||||
}
|
||||
|
||||
interface TeamProps {
|
||||
groups: TeamGroupProps[];
|
||||
groups: TeamGroupProps[];
|
||||
}
|
||||
|
||||
const TeamMember: React.FC<TeamMemberProps> = ({ name, role, image }) => {
|
||||
return (
|
||||
<div className='team-member'>
|
||||
<div className='team-member-image-container'>
|
||||
<img className='team-member-image' src={image} alt={name} />
|
||||
</div>
|
||||
<div className='team-member-content'>
|
||||
<h5 className='team-member-name'>{name}</h5>
|
||||
<p className='team-member-role'>{role}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="team-member">
|
||||
<div className="team-member-image-container">
|
||||
<LazyLoadImage className="team-member-image" src={image} alt={name} />
|
||||
</div>
|
||||
<div className="team-member-content">
|
||||
<h5 className="team-member-name">{name}</h5>
|
||||
<p className="team-member-role">{role}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TeamGroup: React.FC<TeamGroupProps> = ({ title, members }) => {
|
||||
return (
|
||||
<div className='team-group'>
|
||||
<h3 className='team-group-title'>{title}</h3>
|
||||
<div className='team-group-list'>
|
||||
{members.map((member, index) => <TeamMember key={index} {...member} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="team-group">
|
||||
<h3 className="team-group-title">{title}</h3>
|
||||
<div className="team-group-list">
|
||||
{members.map((member, index) => (
|
||||
<TeamMember key={index} {...member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Team: React.FC<TeamProps> = ({ groups }) => {
|
||||
return (
|
||||
<div className={"team"}>
|
||||
{groups.map((group, index) => <TeamGroup key={index} {...group} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={'team'}>
|
||||
{groups.map((group, index) => (
|
||||
<TeamGroup key={index} {...group} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import './timeline.css';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface StatCardProps {
|
||||
type: "stat";
|
||||
type: 'stat';
|
||||
data: string;
|
||||
label: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ImageCardProps {
|
||||
type: "image";
|
||||
type: 'image';
|
||||
src: string;
|
||||
alt: string;
|
||||
fit: "contain" | "cover";
|
||||
};
|
||||
fit: 'contain' | 'cover';
|
||||
}
|
||||
|
||||
interface TimelineProjectProps {
|
||||
title: string;
|
||||
date: string;
|
||||
banner?: string;
|
||||
paragraphs: React.ReactNode[];
|
||||
cards?: (StatCardProps|ImageCardProps)[];
|
||||
};
|
||||
cards?: (StatCardProps | ImageCardProps)[];
|
||||
}
|
||||
|
||||
interface TimelineProps {
|
||||
projects: TimelineProjectProps[];
|
||||
};
|
||||
}
|
||||
|
||||
const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (props) => {
|
||||
if (props.type === "stat") {
|
||||
const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (
|
||||
props
|
||||
) => {
|
||||
if (props.type === 'stat') {
|
||||
const { data, label } = props as StatCardProps;
|
||||
return (
|
||||
<div className={'timeline-project-card card_stat'}>
|
||||
@@ -36,46 +39,63 @@ const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (props) =>
|
||||
<p className={'timeline-project-card-subtext'}>{label}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (props.type === "image") {
|
||||
} else if (props.type === 'image') {
|
||||
const { src, alt, fit } = props as ImageCardProps;
|
||||
return (
|
||||
<div className={`timeline-project-card card_image_${fit}`}>
|
||||
<img className={"timeline-project-card-image"} src={src} alt={alt} />
|
||||
<LazyLoadImage
|
||||
className={'timeline-project-card-image'}
|
||||
src={src}
|
||||
alt={alt}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TimelineProject: React.FC<TimelineProjectProps> = ({ title, banner, date, paragraphs, cards }) => {
|
||||
export const TimelineProject: React.FC<TimelineProjectProps> = ({
|
||||
title,
|
||||
banner,
|
||||
date,
|
||||
paragraphs,
|
||||
cards,
|
||||
}) => {
|
||||
return (
|
||||
<div className={'timeline-project'}>
|
||||
<div className={'timeline-project-content'}>
|
||||
<h3 className={'timeline-project-title'}>{title}</h3>
|
||||
<p className={'timeline-project-date'}>{date}</p>
|
||||
</div>
|
||||
{ banner && <img className={'timeline-project-banner'} src={banner} alt={title} /> }
|
||||
{banner && (
|
||||
<LazyLoadImage
|
||||
className={'timeline-project-banner'}
|
||||
src={banner}
|
||||
alt={title}
|
||||
/>
|
||||
)}
|
||||
<div className={'timeline-project-description'}>
|
||||
{ paragraphs.map((paragraph, i) => <p className={'timeline-project-paragraph'} key={i}>{paragraph}</p>) }
|
||||
{paragraphs.map((paragraph, i) => (
|
||||
<p className={'timeline-project-paragraph'} key={i}>
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<div className={'timeline-project-cards'}>
|
||||
{ cards && cards.map((card, i) => <TimelineProjectCard key={i} {...card} />) }
|
||||
{cards &&
|
||||
cards.map((card, i) => <TimelineProjectCard key={i} {...card} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const Timeline: React.FC<TimelineProps> = ({ projects }) => {
|
||||
return (
|
||||
<div className={'timeline'}>
|
||||
<h4 className={'timeline-tag'}>Aujourd'hui</h4>
|
||||
{
|
||||
projects.map((project, i) => {
|
||||
return (
|
||||
<TimelineProject key={i} {...project} />
|
||||
);
|
||||
})
|
||||
}
|
||||
{projects.map((project, i) => {
|
||||
return <TimelineProject key={i} {...project} />;
|
||||
})}
|
||||
<h4 className={'timeline-tag'}>Il y a quelques temps</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import './footer.css';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface FooterLinkProps {
|
||||
text: string;
|
||||
@@ -9,11 +10,15 @@ interface FooterLinkProps {
|
||||
target?: string;
|
||||
}
|
||||
|
||||
const FooterLink: React.FC<FooterLinkProps> = ({ text, link, target="_self" }) => {
|
||||
const FooterLink: React.FC<FooterLinkProps> = ({
|
||||
text,
|
||||
link,
|
||||
target = '_self',
|
||||
}) => {
|
||||
return (
|
||||
<Link to={link} className={`footer-link`} target={target}>
|
||||
{text}
|
||||
</Link>
|
||||
<Link to={link} className={`footer-link`} target={target}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,35 +26,61 @@ export const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer className={'footer'}>
|
||||
<div className={'footer-content'}>
|
||||
<img className={'footer-logo'} src={'https://r2.modelec.club/logo.png'} alt={'Modelec'} />
|
||||
<LazyLoadImage
|
||||
className={'footer-logo'}
|
||||
src={'https://r2.modelec.club/logo.png'}
|
||||
alt={'Modelec'}
|
||||
/>
|
||||
<div className={'footer-part'}>
|
||||
<h3 className={'footer-title'}>Plan du site</h3>
|
||||
<div className={'footer-links'}>
|
||||
{[
|
||||
{ text: 'Accueil', link: '/' },
|
||||
{ text: 'Projets', link: '/projets/' },
|
||||
/*
|
||||
/*
|
||||
{ text: 'Matériels', link: '/materiels/' },
|
||||
{ text: 'Photos', link: '/photos/' },
|
||||
*/
|
||||
{ text: 'Partenaires', link: '/partenaires/' },
|
||||
{ text: 'Nous contacter', link: '/contact/' },
|
||||
].map((link) => <FooterLink key={link.text} {...link} />)}
|
||||
].map((link) => (
|
||||
<FooterLink key={link.text} {...link} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'footer-part'}>
|
||||
<h3 className={'footer-title'}>Nos réseaux</h3>
|
||||
<div className={'footer-links'}>
|
||||
{[
|
||||
{ text: 'Instagram', link: 'https://www.instagram.com/modelec_isen', target: '_blank' },
|
||||
{ text: 'Youtube', link: 'https://www.youtube.com/@Modelec-ISEN', target: '_blank' },
|
||||
{ text: 'Github', link: 'https://www.github.com/modelec', target: '_blank' },
|
||||
{ text: 'Mail', link: 'mailto:contact@modelec.club', target: '_blank' },
|
||||
].map((link) => <FooterLink key={link.text} {...link} />)}
|
||||
{
|
||||
text: 'Instagram',
|
||||
link: 'https://www.instagram.com/modelec_isen',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
text: 'Youtube',
|
||||
link: 'https://www.youtube.com/@Modelec-ISEN',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
text: 'Github',
|
||||
link: 'https://www.github.com/modelec',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
text: 'Mail',
|
||||
link: 'mailto:contact@modelec.club',
|
||||
target: '_blank',
|
||||
},
|
||||
].map((link) => (
|
||||
<FooterLink key={link.text} {...link} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className={'footer-credits'}>© 2024 Modelec ISEN Nantes. Site réalisé par AppenISEN.</span>
|
||||
<span className={'footer-credits'}>
|
||||
© 2024 Modelec ISEN Nantes. Site réalisé par AppenISEN.
|
||||
</span>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Menu } from 'iconoir-react';
|
||||
import './navbar.css';
|
||||
import { useWindowsSize } from '../../hooks/useWindowsSize';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
interface NavbarLinkProps {
|
||||
text: string;
|
||||
@@ -14,37 +15,46 @@ interface NavbarLinkProps {
|
||||
|
||||
const NavbarLink: React.FC<NavbarLinkProps> = ({ text, link, isActive }) => {
|
||||
return (
|
||||
<Link to={link} className={`navbar-link ${isActive ? "link_active" : ""}`}>
|
||||
{text}
|
||||
</Link>
|
||||
<Link to={link} className={`navbar-link ${isActive ? 'link_active' : ''}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const MobileNavbarLink: React.FC<NavbarLinkProps> = ({ text, link, isActive }) => {
|
||||
const MobileNavbarLink: React.FC<NavbarLinkProps> = ({
|
||||
text,
|
||||
link,
|
||||
isActive,
|
||||
}) => {
|
||||
return (
|
||||
<Link to={link} className={`mobileNavbar-link ${isActive ? "link_active" : ""}`}>
|
||||
{text}
|
||||
</Link>
|
||||
<Link
|
||||
to={link}
|
||||
className={`mobileNavbar-link ${isActive ? 'link_active' : ''}`}
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const Navbar = () => {
|
||||
const location = useLocation();
|
||||
const pathname = location.pathname.endsWith('/') ? location.pathname : location.pathname + '/';
|
||||
const pathname = location.pathname.endsWith('/')
|
||||
? location.pathname
|
||||
: location.pathname + '/';
|
||||
|
||||
const links = [
|
||||
{ text: 'Accueil', link: '/' },
|
||||
{ text: 'Projets', link: '/projets/' },
|
||||
/*
|
||||
/*
|
||||
{ text: 'Matériels', link: '/materiels/' },
|
||||
{ text: 'Photos', link: '/photos/' },
|
||||
*/
|
||||
{ text: 'Partenaires', link: '/partenaires/' },
|
||||
{ text: 'Nous contacter', link: '/contact/' },
|
||||
]
|
||||
];
|
||||
|
||||
const activeLink = links.findIndex((link) => link.link === pathname);
|
||||
|
||||
const activeLink = links.findIndex(link => link.link === pathname);
|
||||
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
||||
const { width } = useWindowsSize();
|
||||
|
||||
@@ -58,43 +68,55 @@ export const Navbar = () => {
|
||||
<>
|
||||
<nav className={'navbar'}>
|
||||
<Link to={'/'} className={'navbar-logo'}>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'navbar-logo-img img_large'}
|
||||
src={'https://r2.modelec.club/logo-full.png'} // TODO: Change to SVG logo / import it from assets
|
||||
alt={'Modelec Logo'}
|
||||
/>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'navbar-logo-img img_small'}
|
||||
src={'https://r2.modelec.club/logo.png'} // TODO: Change to SVG logo / import it from assets
|
||||
alt={'Modelec Logo'}
|
||||
/>
|
||||
</Link>
|
||||
<div className={'navbar-links'}>
|
||||
{
|
||||
links.map((link, index) => (
|
||||
<NavbarLink key={index} text={link.text} link={link.link} isActive={activeLink == index} />
|
||||
))
|
||||
}
|
||||
{links.map((link, index) => (
|
||||
<NavbarLink
|
||||
key={index}
|
||||
text={link.text}
|
||||
link={link.link}
|
||||
isActive={activeLink == index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button className={'navbar-open'} onClick={() => setIsMobileMenuOpen(old => !old)}>
|
||||
<button
|
||||
className={'navbar-open'}
|
||||
onClick={() => setIsMobileMenuOpen((old) => !old)}
|
||||
>
|
||||
<Menu />
|
||||
</button>
|
||||
{ isMobileMenuOpen &&
|
||||
{isMobileMenuOpen && (
|
||||
<nav className={'mobileNavbar'}>
|
||||
<h1 className={'mobileNavbar-title'}>Se déplacer sur le site</h1>
|
||||
<div className={'mobileNavbar-links'}>
|
||||
{
|
||||
links.map((link, index) => (
|
||||
<MobileNavbarLink key={index} text={link.text} link={link.link} isActive={activeLink == index} />
|
||||
))
|
||||
}
|
||||
{links.map((link, index) => (
|
||||
<MobileNavbarLink
|
||||
key={index}
|
||||
text={link.text}
|
||||
link={link.link}
|
||||
isActive={activeLink == index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
)}
|
||||
</nav>
|
||||
{ isMobileMenuOpen &&
|
||||
<div className={'mobileNavbar-overlay'} onClick={() => setIsMobileMenuOpen(false)}></div>
|
||||
}
|
||||
{isMobileMenuOpen && (
|
||||
<div
|
||||
className={'mobileNavbar-overlay'}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
></div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
import { Github, Youtube, Instagram, BookmarkBook } from 'iconoir-react';
|
||||
|
||||
@@ -84,7 +85,7 @@ const Home: React.FC = () => {
|
||||
target={'_blank'}
|
||||
href={'https://isen-nantes.fr'}
|
||||
>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'partners-partner-logo'}
|
||||
src={'https://r2.modelec.club/isen.png'}
|
||||
alt={'ISEN Nantes'}
|
||||
@@ -96,7 +97,7 @@ const Home: React.FC = () => {
|
||||
target={'_blank'}
|
||||
href={'https://www.tracopower.com/fr/fra'}
|
||||
>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'partners-partner-logo'}
|
||||
src={'https://r2.modelec.club/tracopower.jpeg'}
|
||||
alt={'Traco Power'}
|
||||
@@ -108,7 +109,7 @@ const Home: React.FC = () => {
|
||||
target={'_blank'}
|
||||
href={'https://mercurycloud.fr'}
|
||||
>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'partners-partner-logo'}
|
||||
src={'https://r2.modelec.club/mercurycloud.png'}
|
||||
alt={'Mercury Cloud'}
|
||||
@@ -120,7 +121,7 @@ const Home: React.FC = () => {
|
||||
target={'_blank'}
|
||||
href={'https://instagram.com/odyssey_bde'}
|
||||
>
|
||||
<img
|
||||
<LazyLoadImage
|
||||
className={'partners-partner-logo'}
|
||||
src={'https://r2.modelec.club/bde.png'}
|
||||
alt={'BDE Odyssey'}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/banner/banner.tsx","./src/components/box/box.tsx","./src/components/carousel/carousel.tsx","./src/components/contact/contact.tsx","./src/components/partner/partner.tsx","./src/components/socialnetwork/socialnetwork.tsx","./src/components/team/team.tsx","./src/components/timeline/timeline.tsx","./src/hooks/scrolltotop.tsx","./src/hooks/usewindowssize.tsx","./src/layouts/footer/footer.tsx","./src/layouts/navbar/navbar.tsx","./src/pages/404/404.tsx","./src/pages/contact/contact.tsx","./src/pages/home/home.tsx","./src/pages/partenaires/partenaires.tsx","./src/pages/projets/projets.tsx"],"version":"5.6.3"}
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/banner/banner.tsx","./src/components/box/box.tsx","./src/components/carousel/carousel.tsx","./src/components/contact/contact.tsx","./src/components/partner/partner.tsx","./src/components/socialnetwork/socialnetwork.tsx","./src/components/team/team.tsx","./src/components/timeline/timeline.tsx","./src/hooks/scrollToTop.tsx","./src/hooks/useWindowsSize.tsx","./src/layouts/footer/footer.tsx","./src/layouts/navbar/navbar.tsx","./src/pages/404/404.tsx","./src/pages/contact/Contact.tsx","./src/pages/home/Home.tsx","./src/pages/partenaires/Partenaires.tsx","./src/pages/projets/Projets.tsx"],"version":"5.7.2"}
|
||||
@@ -1 +1 @@
|
||||
{"root":["./vite.config.ts"],"version":"5.6.3"}
|
||||
{"root":["./vite.config.ts"],"version":"5.7.2"}
|
||||
Reference in New Issue
Block a user