diff --git a/public/locales/fr/speakers.json b/public/locales/fr/speakers.json
index 85549ff..9cc0e82 100644
--- a/public/locales/fr/speakers.json
+++ b/public/locales/fr/speakers.json
@@ -68,6 +68,7 @@
},
"sessions": [
{
+ "id": 1,
"title": "Comment PSh est devenu le partenaire de Adobe.",
"description": "Une plongée inspirante dans les coulisses d'un partenariat gagnant-gagnant, avec des enseignements applicables à d'autres collaborations stratégiques.",
"lang": "fr",
@@ -79,6 +80,7 @@
"speakers": [1]
},
{
+ "id": 2,
"title": "200 Domains on a Single Magento Instance with a $300 Hosting Bill",
"description": "A fascinating story about a unique customer business model with a Magento store where each product segment has its dedicated domain and technology behind it.",
"lang": "en",
@@ -90,6 +92,7 @@
"speakers": [2]
},
{
+ "id": 3,
"title": "Comment étendre Magento/Adobe Commerce sans le surcharger - App Builder.",
"description": "Et si vous personnalisiez Magento sans jamais toucher à son code ? Découvrez une approche innovante basée sur les addons, en exploitant Adobe App Builder et Runtime I/O pour transformer l’expérience Magento.",
"lang": "fr",
@@ -101,6 +104,7 @@
"speakers": [3]
},
{
+ "id": 4,
"title": "Le frontend Magento : vers une nouvelle dynamique communautaire.",
"description": "Réinventer le frontend Magento en l'alignant sur les meilleures pratiques modernes : une opportunité d’optimiser la stack, d’attirer de nouveaux talents et de dynamiser la communauté grâce à une collaboration renforcée entre backend et frontend.",
"lang": "fr",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 8fe55b6..6774851 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,4 +1,4 @@
-'use client';
+"use client";
import ContentMedia from "@/components/ContentMedia/ContentMedia";
import Place from "@/components/Place/Place";
@@ -7,6 +7,7 @@ import SponsorList from "@/components/SponsorList/SponsorList";
import Container from "@/layouts/Container";
import Hero from "@/components/Hero/Hero";
import Speakers from "@/components/Speakers/Speakers";
+import Program from "@/components/Program/Program";
import useDataProvider from "@/hooks/useDataProvider";
import { SpeakersProps } from "@/components/Speakers/SpeakersProps";
import { SponsorProps } from "@/components/SponsorList/Sponsor/Sponsor.types";
@@ -16,7 +17,7 @@ import Link from "next/link";
import { useTranslation } from "react-i18next";
export default function Home() {
- const { t } = useTranslation(['common']);
+ const { t } = useTranslation(["common"]);
const dataProvider = useDataProvider();
const speakers: SpeakersProps = dataProvider.useSpeakers();
const sponsors: SponsorProps[] = dataProvider.useSponsors();
@@ -25,6 +26,9 @@ export default function Home() {
return (
+
@@ -49,12 +53,12 @@ export default function Home() {
className="mt-6"
>
Pour toute autre question, consultez notre{" "}
-
- {t('common:faqLabel')}
+
+ {t("common:faqLabel")}
{" "}
ou écrivez-nous via le{" "}
-
- {t('common:contactLabel')}
+
+ {t("common:contactLabel")}
.
diff --git a/src/components/Person/Person.tsx b/src/components/Person/Person.tsx
index baf44f9..3730f5e 100644
--- a/src/components/Person/Person.tsx
+++ b/src/components/Person/Person.tsx
@@ -5,65 +5,88 @@ import { GrFormView } from "react-icons/gr";
import { PersonProps } from "@/components/Person/PersonProps";
import classNames from "classnames";
-const PersonCard = ({ person }: { person: PersonProps }) => (
-
-
-
- {person.company}
-
-
{person.name}
+const PersonCard = ({
+ person,
+ appearance = "speaker",
+}: {
+ person: PersonProps;
+ appearance?: "speaker" | "program";
+}) => (
+
+ {appearance === "speaker" && (
+
+
+ {person.company}
+
+ )}
+
{person.name}
{person.role}
);
-const Person = ({ person }: { person: PersonProps }) => {
+const Person = ({
+ person,
+ appearance = "speaker",
+}: {
+ person: PersonProps;
+ appearance?: "speaker" | "program";
+}) => {
const { width } = useWindowSize();
const bgClass = `bg-cover bg-photo-${person.photoBg}`;
+ const imageClass = classNames(
+ "relative aspect-square object-cover overflow-hidden",
+ {
+ "w-20 h-24": appearance === "program",
+ "min-w-[135px]": appearance === "speaker" && width < 768,
+ "min-w-[165px]": appearance === "speaker" && width >= 768,
+ "rounded-full": appearance === "speaker",
+ "[clip-path:polygon(50%_0%,100%_25%,100%_75%,50%_100%,0%_75%,0%_25%)]":
+ appearance === "program",
+ },
+ bgClass
+ );
+
return (
<>
{width < 768 ? (
-
+
) : (
- <>
-
+
+
-
-
-
-
Biographie
+ {appearance === "speaker" && (
+
-
+ )}
-
- >
+
+
)}
>
);
diff --git a/src/components/Program/Program.tsx b/src/components/Program/Program.tsx
new file mode 100644
index 0000000..f9d6d55
--- /dev/null
+++ b/src/components/Program/Program.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import Container from "@/layouts/Container";
+import BackgroundImage from "../BackgroundImage/BackgroundImage";
+import ProgramList from "./ProgramList";
+
+const Program = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default Program;
diff --git a/src/components/Program/ProgramList.tsx b/src/components/Program/ProgramList.tsx
new file mode 100644
index 0000000..2f5921f
--- /dev/null
+++ b/src/components/Program/ProgramList.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import TopBanner from "../TopBanner/TopBanner";
+import ButtonLink from "../ButtonLink/ButtonLink";
+import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
+import { Navigation } from "swiper/modules";
+import "swiper/css";
+import "swiper/css/navigation";
+import useDataProvider from "@/hooks/useDataProvider";
+import ClientOnly from "@/helpers/ClientOnly";
+import ProgramTile from "./ProgramTile";
+
+const ProgramList = () => {
+ const swiperRef = React.useRef
(null);
+ const sessions = useDataProvider().useSessions();
+
+ const handlePrev = () => {
+ if (swiperRef.current) {
+ swiperRef.current.slidePrev();
+ }
+ };
+
+ const handleNext = () => {
+ if (swiperRef.current) {
+ swiperRef.current.slideNext();
+ }
+ };
+ return (
+
+
+
+ <>
+
+
+ Voir tout
+
+
+
+
+ Découvrir le Programme
+
+
+ >
+
+
+
+ {
+ swiperRef.current = swiper;
+ }}
+ modules={[Navigation]}
+ spaceBetween={30}
+ breakpoints={{
+ 0: {
+ slidesPerView: 1,
+ spaceBetween: 16,
+ },
+ 768: {
+ slidesPerView: 2,
+ },
+ 1024: {
+ slidesPerView: 3,
+ },
+ }}
+ className="relative"
+ >
+ {sessions.map((session) => (
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default ProgramList;
diff --git a/src/components/Program/ProgramTile.tsx b/src/components/Program/ProgramTile.tsx
new file mode 100644
index 0000000..9190a38
--- /dev/null
+++ b/src/components/Program/ProgramTile.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+import { SessionProps } from "../Speakers/Session/SessionProps";
+import Session from "../Speakers/Session/Session";
+import { PiCalendarPlus } from "react-icons/pi";
+import Person from "../Person/Person";
+import useDataProvider from "@/hooks/useDataProvider";
+
+const ProgramTile = ({ session }: { session: SessionProps }) => {
+ const dataProvider = useDataProvider();
+ const speakers = dataProvider
+ .usePersonList("speakers", "data.speakers")
+ .filter((speaker) => session.speakers.includes(speaker.id));
+
+ return (
+
+
+
+
+ {speakers.map((speaker) => (
+
+ ))}
+
+
+
+
+
+
+ Ajouter à mon agenda
+
+
+
+
+ );
+};
+
+export default ProgramTile;
diff --git a/src/components/Speakers/Session/Session.tsx b/src/components/Speakers/Session/Session.tsx
index 3cd4e05..c78dff5 100644
--- a/src/components/Speakers/Session/Session.tsx
+++ b/src/components/Speakers/Session/Session.tsx
@@ -1,4 +1,4 @@
-'use client';
+"use client";
import React from "react";
import Typography from "@/components/Typography/Typography";
@@ -9,10 +9,10 @@ import ButtonLink from "@/components/ButtonLink/ButtonLink";
import { useTranslation } from "react-i18next";
const Session = ({ session }: { session: SessionProps }) => {
- const { t } = useTranslation(['speakers']);
+ const { t } = useTranslation(["speakers"]);
return (
-
+
@@ -35,10 +35,7 @@ const Session = ({ session }: { session: SessionProps }) => {
{session.tags.map((tag, tagIndex) => (
-
+
{
))}
-
- {!!session.eventUrl && (
+ {!!session.eventUrl && (
+
}
+ icon={
}
>
-
+
Ajouter à mon agenda
- )}
-
+
+ )}
);
};
diff --git a/src/components/Speakers/Session/SessionProps.ts b/src/components/Speakers/Session/SessionProps.ts
index 03fa028..c158b59 100644
--- a/src/components/Speakers/Session/SessionProps.ts
+++ b/src/components/Speakers/Session/SessionProps.ts
@@ -1,4 +1,5 @@
export type SessionProps = {
+ id: number;
title: string;
description: string;
lang: string;
diff --git a/src/components/Speakers/Speaker/PersonPopIn.tsx b/src/components/Speakers/Speaker/PersonPopIn.tsx
index 93bd97c..99fea35 100644
--- a/src/components/Speakers/Speaker/PersonPopIn.tsx
+++ b/src/components/Speakers/Speaker/PersonPopIn.tsx
@@ -1,4 +1,4 @@
-'use client';
+"use client";
import { PersonProps } from "@/components/Person/PersonProps";
import PersonPopIn from "@/components/Person/PersonPopIn";
@@ -14,7 +14,7 @@ interface SpeakerPopInProps {
const SpeakerPopIn = ({
isOpen,
onClose,
- selectedSpeaker
+ selectedSpeaker,
}: SpeakerPopInProps) => {
const dataProvider = useDataProvider();
const sessions = dataProvider.useSessions(selectedSpeaker.id);
@@ -26,8 +26,10 @@ const SpeakerPopIn = ({
selectedPerson={selectedSpeaker}
>
{sessions.map((session, index) => (
-
- ))};
+
+
+
+ ))}
);
};
diff --git a/src/components/Speakers/SpeakersList.tsx b/src/components/Speakers/SpeakersList.tsx
index facf1ad..223d3b2 100644
--- a/src/components/Speakers/SpeakersList.tsx
+++ b/src/components/Speakers/SpeakersList.tsx
@@ -1,4 +1,4 @@
-'use client';
+"use client";
import React from "react";
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
@@ -19,8 +19,8 @@ const SpeakersList = ({ speakers }: { speakers: PersonProps[] }) => {
const swiperRef = React.useRef
(null);
const [selectedSpeaker, setSelectedSpeaker] =
React.useState(null);
- const { t } = useTranslation(['speakers']);
- const actions = t('actions', { returnObjects: true });
+ const { t } = useTranslation(["speakers"]);
+ const actions = t("actions", { returnObjects: true });
const handleSpeakerClick = (speaker: PersonProps) => {
setSelectedSpeaker(speaker);
@@ -51,7 +51,7 @@ const SpeakersList = ({ speakers }: { speakers: PersonProps[] }) => {
onPrevClick={handlePrev}
onNextClick={handleNext}
>
- {'seeAllUrl' in actions && typeof actions.seeAllUrl === 'string' && (
+ {"seeAllUrl" in actions && typeof actions.seeAllUrl === "string" && (
<>
@@ -76,7 +76,7 @@ const SpeakersList = ({ speakers }: { speakers: PersonProps[] }) => {
spaceBetween={30}
breakpoints={{
0: {
- slidesPerView: 1.2,
+ slidesPerView: 1.1,
spaceBetween: 16,
},
768: {
diff --git a/src/components/TopBanner/TopBanner.tsx b/src/components/TopBanner/TopBanner.tsx
index 2d86124..2217f88 100644
--- a/src/components/TopBanner/TopBanner.tsx
+++ b/src/components/TopBanner/TopBanner.tsx
@@ -29,7 +29,7 @@ const TopBanner = ({
{children}
- {onPrevClick && onNextClick && width > 768 && (
+ {onPrevClick && onNextClick && width > 1024 && (
@@ -48,11 +48,11 @@ const TopBanner = ({
);
return (
-
+
{backgroundImage ? (
{bannerContent}
diff --git a/src/hooks/useDataProvider.tsx b/src/hooks/useDataProvider.tsx
index 02ae176..566bea6 100644
--- a/src/hooks/useDataProvider.tsx
+++ b/src/hooks/useDataProvider.tsx
@@ -1,85 +1,82 @@
-'use client';
+"use client";
import { useTranslation } from "react-i18next";
import { SpeakersProps } from "@/components/Speakers/SpeakersProps";
import { SponsorProps } from "@/components/SponsorList/Sponsor/Sponsor.types";
import { PlaceProps } from "@/components/Place/PlaceProps";
import { PersonProps } from "@/components/Person/PersonProps";
-import {SessionProps} from "@/components/Speakers/Session/SessionProps";
+import { SessionProps } from "@/components/Speakers/Session/SessionProps";
-const useData = (ns: string, key: string = 'data') => {
+const useData = (ns: string, key: string = "data") => {
return useTranslation([ns]).t(key, { returnObjects: true });
-}
+};
const isSpeakersProps = (content: object): content is SpeakersProps => {
- return content !== null && typeof content === 'object';
-}
+ return content !== null && typeof content === "object";
+};
const isSessionProps = (content: object): content is SessionProps[] => {
- return content !== null && typeof content === 'object';
-}
+ return content !== null && typeof content === "object";
+};
const isSponsors = (content: object): content is SponsorProps[] => {
- return content !== null && typeof content === 'object';
-}
+ return content !== null && typeof content === "object";
+};
const isPlaceProps = (content: object): content is PlaceProps => {
- return content !== null && typeof content === 'object';
-}
+ return content !== null && typeof content === "object";
+};
const isPersonList = (content: object): content is PersonProps[] => {
- return content !== null && typeof content === 'object';
-}
+ return content !== null && typeof content === "object";
+};
-const useSpeakers = (): SpeakersProps =>
-{
- const content = useData('speakers');
+const useSpeakers = (): SpeakersProps => {
+ const content = useData("speakers");
if (!isSpeakersProps(content)) {
- throw new Error('Content is not a valid Speakers Type');
+ throw new Error("Content is not a valid Speakers Type");
}
return content;
-}
+};
-const useSessions = (speakerId: number): SessionProps[] =>
-{
- const content = useData('speakers', 'data.sessions');
+const useSessions = (speakerId?: number): SessionProps[] => {
+ const content = useData("speakers", "data.sessions");
if (!isSessionProps(content)) {
- throw new Error('Content is not a valid Speakers Type');
+ throw new Error("Content is not a valid Speakers Type");
}
+ if (!speakerId) return content;
+
return content.filter((session) => session.speakers.includes(speakerId));
-}
+};
-const useSponsors = (): SponsorProps[] =>
-{
- const content = useData('sponsors', 'partners');
+const useSponsors = (): SponsorProps[] => {
+ const content = useData("sponsors", "partners");
if (!isSponsors(content)) {
- throw new Error('Content is not a valid array of Sponsor Type');
+ throw new Error("Content is not a valid array of Sponsor Type");
}
return content;
-}
+};
-const usePlace = (): PlaceProps =>
-{
- const content = useData('place');
+const usePlace = (): PlaceProps => {
+ const content = useData("place");
if (!isPlaceProps(content)) {
- throw new Error('Content is not a valid Place Type');
+ throw new Error("Content is not a valid Place Type");
}
return content;
-}
+};
-const usePersonList = (ns: string, key: string): PersonProps[] =>
-{
+const usePersonList = (ns: string, key: string): PersonProps[] => {
const content = useData(ns, key);
if (!isPersonList(content)) {
- throw new Error('Content is not a valid array of Sponsor Type');
+ throw new Error("Content is not a valid array of Sponsor Type");
}
return content;
-}
+};
const useDataProvider = () => {
return {
@@ -88,7 +85,7 @@ const useDataProvider = () => {
useSessions: useSessions,
usePlace: usePlace,
usePersonList: usePersonList,
- }
+ };
};
export default useDataProvider;