Skip to content

Commit

Permalink
Merge pull request #26 from opengento/program
Browse files Browse the repository at this point in the history
Program
  • Loading branch information
Timothee31 authored Feb 4, 2025
2 parents 966c580 + 2dd575c commit 0067ec7
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 111 deletions.
4 changes: 4 additions & 0 deletions public/locales/fr/speakers.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
16 changes: 10 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use client';
"use client";

import ContentMedia from "@/components/ContentMedia/ContentMedia";
import Place from "@/components/Place/Place";
Expand All @@ -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";
Expand All @@ -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();
Expand All @@ -25,6 +26,9 @@ export default function Home() {
return (
<div className="relative -top-[104px] left-0">
<Hero />
<div id="program">
<Program />
</div>
<div id="speakers">
<Speakers data={speakers} />
</div>
Expand All @@ -49,12 +53,12 @@ export default function Home() {
className="mt-6"
>
Pour toute autre question, consultez notre{" "}
<Link href={t('common:faqUrl')} className="underline">
{t('common:faqLabel')}
<Link href={t("common:faqUrl")} className="underline">
{t("common:faqLabel")}
</Link>{" "}
ou écrivez-nous via le{" "}
<Link href={t('common:contactUrl')} className="underline">
{t('common:contactLabel')}
<Link href={t("common:contactUrl")} className="underline">
{t("common:contactLabel")}
</Link>
.
</Typography>
Expand Down
97 changes: 60 additions & 37 deletions src/components/Person/Person.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<div className="text-black flex flex-col justify-center cursor-pointer">
<div className="flex gap-2 mb-2">
<Image
src={person.companyLogo}
alt={person.company}
width={20}
height={20}
className="h-6"
/>
<span className="text-sm">{person.company}</span>
</div>
<p className="font-semibold text-lg mb-1">{person.name}</p>
const PersonCard = ({
person,
appearance = "speaker",
}: {
person: PersonProps;
appearance?: "speaker" | "program";
}) => (
<div className="text-black flex flex-col justify-center">
{appearance === "speaker" && (
<div className="flex gap-2 mb-2">
<Image
src={person.companyLogo}
alt={person.company}
width={20}
height={20}
className="h-6"
/>
<span className="text-sm">{person.company}</span>
</div>
)}
<p className="font-semibold text-lg">{person.name}</p>
<p className="text-gray-600 italic">{person.role}</p>
</div>
);

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 ? (
<div className="flex flex-row gap-2 rounded-full">
<div className="flex flex-row gap-2">
<Image
src={person.photo}
alt={person.name}
width={135}
height={135}
className={classNames(
"min-w-[135px] rounded-full relative aspect-square object-cover overflow-hidden",
bgClass
)}
width={appearance === "program" ? 96 : 135}
height={appearance === "program" ? 96 : 135}
className={imageClass}
/>
<PersonCard person={person} />
<PersonCard person={person} appearance={appearance} />
</div>
) : (
<>
<div className="flex flex-col relative rounded-full">
<div className="flex flex-row gap-6">
<div className="flex flex-col relative">
<Image
src={person.photo}
alt={person.name}
width={165}
height={165}
className={classNames(
"min-w-[165px] rounded-full relative aspect-square object-cover overflow-hidden",
bgClass
)}
width={appearance === "program" ? 96 : 165}
height={appearance === "program" ? 96 : 165}
className={imageClass}
/>
<div className="text-white font-semibold opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-primary w-fit px-4 py-2 rounded-full absolute bottom-0 left-1/2 -translate-x-1/2">
<div className="flex flex-row items-center gap-1">
<GrFormView size={20} className="text-white" />
<span className="text-sm">Biographie</span>
{appearance === "speaker" && (
<div className="text-white font-semibold lg:opacity-0 lg:group-hover:opacity-100 lg:transition-opacity lg:duration-300 lg:hover:cursor-pointer bg-primary w-fit px-4 py-2 rounded-full absolute bottom-0 left-1/2 -translate-x-1/2">
<div className="flex flex-row items-center gap-1">
<GrFormView size={20} className="text-white" />
<span className="text-sm">Biographie</span>
</div>
</div>
</div>
)}
</div>
<PersonCard person={person} />
</>
<PersonCard person={person} appearance={appearance} />
</div>
)}
</>
);
Expand Down
21 changes: 21 additions & 0 deletions src/components/Program/Program.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<BackgroundImage
imagePath="/images/background/sponsors.png"
className="py-12"
>
<Container size="large">
<div className="program">
<ProgramList />
</div>
</Container>
</BackgroundImage>
);
};

export default Program;
81 changes: 81 additions & 0 deletions src/components/Program/ProgramList.tsx
Original file line number Diff line number Diff line change
@@ -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<SwiperClass>(null);
const sessions = useDataProvider().useSessions();

const handlePrev = () => {
if (swiperRef.current) {
swiperRef.current.slidePrev();
}
};

const handleNext = () => {
if (swiperRef.current) {
swiperRef.current.slideNext();
}
};
return (
<section className="program-list">
<div className="mb-8">
<TopBanner
title="Extrait du Programme"
onNextClick={handleNext}
onPrevClick={handlePrev}
>
<>
<div className="md:hidden">
<ButtonLink variant="secondary-invert" href={"/program"}>
Voir tout
</ButtonLink>
</div>
<div className="hidden md:block">
<ButtonLink variant="secondary-invert" href={"/program"}>
Découvrir le Programme
</ButtonLink>
</div>
</>
</TopBanner>
</div>
<ClientOnly>
<Swiper
onSwiper={(swiper) => {
swiperRef.current = swiper;
}}
modules={[Navigation]}
spaceBetween={30}
breakpoints={{
0: {
slidesPerView: 1,
spaceBetween: 16,
},
768: {
slidesPerView: 2,
},
1024: {
slidesPerView: 3,
},
}}
className="relative"
>
{sessions.map((session) => (
<SwiperSlide key={session.id} className="!h-auto">
<ProgramTile session={session} />
</SwiperSlide>
))}
</Swiper>
</ClientOnly>
</section>
);
};

export default ProgramList;
36 changes: 36 additions & 0 deletions src/components/Program/ProgramTile.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="program-tile bg-white rounded-xl flex flex-col justify-between gap-6 p-4 md:p-6 h-full group">
<div className="flex flex-col gap-6">
<Session session={session} />
<div className="program-tile-speakers flex flex-col gap-2">
{speakers.map((speaker) => (
<Person person={speaker} appearance="program" key={speaker.id} />
))}
</div>
</div>
<div className="flex justify-center">
<div className="flex flex-row items-center gap-1 bg-primary px-4 py-2 rounded-full lg:opacity-0 lg:group-hover:opacity-100 lg:transition-opacity lg:duration-300 lg:hover:cursor-pointer">
<PiCalendarPlus size={20} className="text-white" />
<span className="text-sm text-white font-semibold">
Ajouter à mon agenda
</span>
</div>
</div>
</div>
);
};

export default ProgramTile;
Loading

0 comments on commit 0067ec7

Please sign in to comment.