From c7251c250189f39e849627ae5e635dd2ea64252e Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 18:43:53 +0100
Subject: [PATCH 01/21] feat: Introduce year-based routing and 2025 event
edition components, while updating existing home and navigation for the new
structure and preparing for 2026 data
---
src/2025/Home/HomeWrapper2025.tsx | 48 ++
.../ActionButtons/ActionButtons.tsx | 63 ++
src/2025/Home/components/Faqs/Faqs.style.ts | 171 +++++
src/2025/Home/components/Faqs/Faqs.tsx | 193 ++++++
src/2025/Home/components/Faqs/FaqsData.ts | 21 +
.../components/Faqs/components/FaqsCard.tsx | 67 ++
src/2025/Home/components/Home/DateUtil.ts | 16 +
src/2025/Home/components/Home/Home.tsx | 156 +++++
src/2025/Home/components/Home/Style.Home.tsx | 179 ++++++
.../Home/components/CountDownCompleted.tsx | 33 +
.../Home/components/TimeCountdown.tsx | 47 ++
.../Home/components/countdown.style.ts | 31 +
.../InfoButtons/InfoButtons.test.tsx | 47 ++
.../components/InfoButtons/InfoButtons.tsx | 44 ++
.../Home/components/Sponsors/BasicSponsor.tsx | 97 +++
.../Home/components/Sponsors/Communities.tsx | 93 +++
.../components/Sponsors/MediaPartners.tsx | 94 +++
.../components/Sponsors/PremiumSponsors.tsx | 99 +++
.../components/Sponsors/RegularSponsors.tsx | 93 +++
.../Home/components/Sponsors/SponsorBadge.tsx | 36 ++
.../components/Sponsors/Sponsors.style.ts | 297 +++++++++
.../Home/components/Sponsors/Sponsors.tsx | 53 ++
.../Home/components/Sponsors/SponsorsData.ts | 162 +++++
.../components/Sponsors/Supporters.test.tsx | 82 +++
.../Home/components/Sponsors/Supporters.tsx | 102 +++
.../Home/components/Sponsors/TopSponsors.tsx | 92 +++
.../Sponsors/useSponsorsHook.test.tsx | 107 ++++
.../components/Sponsors/useSponsorsHook.ts | 41 ++
src/2025/Navigation/NavigationData2025.ts | 26 +
src/App.tsx | 595 +-----------------
src/components/Navigation/Navigation.tsx | 32 +-
src/components/Navigation/NavigationData.ts | 62 +-
src/components/Router/RouteRenderer.tsx | 34 +
src/components/Router/SuspenseRoute.tsx | 26 +
src/config/routeConfig.ts | 131 ++++
src/config/yearRoutes.ts | 96 +++
src/constants/routes.ts | 41 ++
src/data/2026.json | 45 ++
src/utils/lazyComponents.ts | 172 +++++
src/views/Cfp/CfpSection.test.tsx | 6 +-
src/views/Home/HomeWrapper.tsx | 8 +-
src/views/Home/UseEventEdition.tsx | 2 +-
src/views/Home/components/Home/Home.tsx | 5 +-
.../Home/components/Sponsors/SponsorsData.ts | 147 +----
src/views/Speakers/Speakers.test.tsx | 2 +-
src/views/Talks/LiveView.test.tsx | 2 +-
tsconfig.json | 45 +-
vite.config.ts | 1 +
48 files changed, 3263 insertions(+), 779 deletions(-)
create mode 100644 src/2025/Home/HomeWrapper2025.tsx
create mode 100644 src/2025/Home/components/ActionButtons/ActionButtons.tsx
create mode 100644 src/2025/Home/components/Faqs/Faqs.style.ts
create mode 100644 src/2025/Home/components/Faqs/Faqs.tsx
create mode 100644 src/2025/Home/components/Faqs/FaqsData.ts
create mode 100644 src/2025/Home/components/Faqs/components/FaqsCard.tsx
create mode 100644 src/2025/Home/components/Home/DateUtil.ts
create mode 100644 src/2025/Home/components/Home/Home.tsx
create mode 100644 src/2025/Home/components/Home/Style.Home.tsx
create mode 100644 src/2025/Home/components/Home/components/CountDownCompleted.tsx
create mode 100644 src/2025/Home/components/Home/components/TimeCountdown.tsx
create mode 100644 src/2025/Home/components/Home/components/countdown.style.ts
create mode 100644 src/2025/Home/components/InfoButtons/InfoButtons.test.tsx
create mode 100644 src/2025/Home/components/InfoButtons/InfoButtons.tsx
create mode 100644 src/2025/Home/components/Sponsors/BasicSponsor.tsx
create mode 100644 src/2025/Home/components/Sponsors/Communities.tsx
create mode 100644 src/2025/Home/components/Sponsors/MediaPartners.tsx
create mode 100644 src/2025/Home/components/Sponsors/PremiumSponsors.tsx
create mode 100644 src/2025/Home/components/Sponsors/RegularSponsors.tsx
create mode 100644 src/2025/Home/components/Sponsors/SponsorBadge.tsx
create mode 100644 src/2025/Home/components/Sponsors/Sponsors.style.ts
create mode 100644 src/2025/Home/components/Sponsors/Sponsors.tsx
create mode 100644 src/2025/Home/components/Sponsors/SponsorsData.ts
create mode 100644 src/2025/Home/components/Sponsors/Supporters.test.tsx
create mode 100644 src/2025/Home/components/Sponsors/Supporters.tsx
create mode 100644 src/2025/Home/components/Sponsors/TopSponsors.tsx
create mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
create mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.ts
create mode 100644 src/2025/Navigation/NavigationData2025.ts
create mode 100644 src/components/Router/RouteRenderer.tsx
create mode 100644 src/components/Router/SuspenseRoute.tsx
create mode 100644 src/config/routeConfig.ts
create mode 100644 src/config/yearRoutes.ts
create mode 100644 src/data/2026.json
create mode 100644 src/utils/lazyComponents.ts
diff --git a/src/2025/Home/HomeWrapper2025.tsx b/src/2025/Home/HomeWrapper2025.tsx
new file mode 100644
index 000000000..ab2d61dc3
--- /dev/null
+++ b/src/2025/Home/HomeWrapper2025.tsx
@@ -0,0 +1,48 @@
+import { BIG_BREAKPOINT } from "@constants/BreakPoints";
+import React, { FC } from "react";
+import Faqs from "./components/Faqs/Faqs";
+import Home from "./components/Home/Home";
+import Sponsors from "./components/Sponsors/Sponsors";
+import { styled } from "styled-components";
+import conferenceData from "@data/2025.json";
+import { useLocation } from "react-router";
+
+import SpeakersCarousel from "@components/Swiper/SpeakersCarousel";
+import { ROUTE_2025_SPEAKERS } from "@constants/routes";
+import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
+
+const StyledContainer = styled.div`
+ padding-bottom: 10rem;
+
+ @media only screen and (max-width: ${BIG_BREAKPOINT}px) {
+ padding-bottom: 20rem;
+ }
+`;
+
+export const HomeWrapper2025: FC> = () => {
+ const { hash } = useLocation();
+
+ React.useEffect(() => {
+ if (hash != null && hash !== "") {
+ const scroll = document.getElementById(hash.substring(1));
+ scroll?.scrollIntoView();
+ }
+ }, [hash]);
+
+ useDocumentTitleUpdater("Home", conferenceData?.edition ?? "2025");
+
+ return (
+
+
+ {conferenceData?.carrousel.enabled && (
+
+ )}
+
+
+
+ );
+};
diff --git a/src/2025/Home/components/ActionButtons/ActionButtons.tsx b/src/2025/Home/components/ActionButtons/ActionButtons.tsx
new file mode 100644
index 000000000..acbb82b47
--- /dev/null
+++ b/src/2025/Home/components/ActionButtons/ActionButtons.tsx
@@ -0,0 +1,63 @@
+import { FC, useCallback } from "react";
+import data from "../../../../data/2025.json";
+import Button from "../../../../components/UI/Button";
+import { styled } from "styled-components";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import { gaEventTracker } from "../../../../components/analytics/Analytics";
+import { useDateInterval } from "../../../../hooks/useDateInterval";
+import { useUrlBuilder } from "../../../../services/urlBuilder";
+
+const StyledActionDiv = styled.div`
+ display: flex;
+ text-align: center;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ flex-direction: column;
+ width: 75%;
+ }
+`;
+
+const ActionButtons: FC> = () => {
+ const { isTicketsDisabled, isSponsorDisabled, isCfpDisabled } =
+ useDateInterval(new Date(), data);
+
+ const trackSponsorshipInfo = useCallback(() => {
+ gaEventTracker("sponsorship", "sponsorship");
+ }, []);
+
+ const trackTickets = useCallback(() => {
+ gaEventTracker("ticket", "tickets");
+ }, []);
+
+ const trackCFP = useCallback(() => {
+ gaEventTracker("CFP", "CFP");
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+};
+export default ActionButtons;
diff --git a/src/2025/Home/components/Faqs/Faqs.style.ts b/src/2025/Home/components/Faqs/Faqs.style.ts
new file mode 100644
index 000000000..ac43aa68c
--- /dev/null
+++ b/src/2025/Home/components/Faqs/Faqs.style.ts
@@ -0,0 +1,171 @@
+import { keyframes, styled } from "styled-components";
+import { Color } from "@styles/colors";
+import { motion } from "motion/react";
+import { BIG_BREAKPOINT } from "@constants/BreakPoints";
+
+const revealAnimation = keyframes`
+ from {
+ opacity: 0;
+ translate: 0 100px;
+ }
+ 50% {
+ opacity: .5;
+ }
+ to {
+ opacity: 1;
+ translate: 0 0;
+ }`;
+
+export type FaqCardType = {
+ faq: {
+ question: string;
+ answer: string;
+ };
+ index: number;
+};
+export const StyledFaqSection = styled(motion.section)`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 3rem 2rem;
+ position: relative;
+ @media (min-width: 650px) {
+ padding: 3rem 5rem;
+ }
+`;
+export const StyledWaveContainer = styled.div`
+ background: ${Color.DARK_BLUE};
+ overflow-y: hidden;
+ height: 5rem;
+ width: 100%;
+`;
+export const StyleMoreIcon = styled.img`
+ position: absolute;
+ right: 0;
+ top: 5rem;
+ height: 5rem;
+ @media (min-width: 800px) {
+ height: 10rem;
+ }
+`;
+export const StyleLessIcon = styled.img`
+ position: absolute;
+ left: -1rem;
+ top: 50%;
+ height: 5rem;
+ @media (min-width: 800px) {
+ height: 10rem;
+ }
+`;
+export const StyledImage = styled.img`
+ margin: 3px;
+ padding: 5px;
+ aspect-ratio: 1.5;
+ border: 1px solid ${Color.YELLOW};
+ border-radius: 100% 0 100% 0 / 15% 89% 11% 85%;
+ animation: linear ${revealAnimation} both;
+ animation-timeline: view();
+ animation-range: entry 5% cover 30%;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ width: 100%;
+ }
+`;
+export const StyledH2 = styled.h2`
+ color: white;
+ margin-bottom: 10px;
+`;
+export const StyledP = styled.p`
+ color: white;
+ margin-bottom: 10px;
+`;
+
+export const StyledFaqCard = styled.div`
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ width: 90%;
+ margin-bottom: 3rem;
+ @media (min-width: 800px) {
+ align-items: flex-start;
+ max-width: 900px;
+ margin-bottom: 4rem;
+ }
+`;
+export const StyledFaqImageContainer = styled.div.withConfig({
+ shouldForwardProp: (prop) => !["padding"].includes(prop),
+})<{ padding: string }>`
+ position: relative;
+ @media (min-width: 800px) {
+ height: auto;
+
+ padding: ${({ padding }) => {
+ return padding;
+ }};
+ }
+`;
+export const StyledFaqImage = styled(motion.img)`
+ border: 1px solid ${Color.YELLOW};
+ display: block;
+ height: 242px;
+ margin: 3px;
+ padding: 5px;
+ width: 360px;
+ border-radius: 92% 8% 90% 10% / 9% 90% 10% 91%;
+ animation: linear ${revealAnimation} both;
+ animation-timeline: view();
+ animation-range: entry 5% cover 30%;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ display: none;
+ }
+`;
+export const StyledFaqInfo = styled(motion.div)<{ align: string }>`
+ display: flex;
+ flex-direction: column;
+ color: ${Color.WHITE};
+ @media (min-width: 800px) {
+ width: 60%;
+ align-items: ${({ align }) => {
+ return align;
+ }};
+ }
+`;
+export const StyledFaqTitle = styled.h2`
+ padding-top: 1rem;
+ color: ${Color.YELLOW};
+ font-size: 1.3em;
+ @media (min-width: 800px) {
+ text-align: left;
+ padding-top: unset;
+ }
+`;
+export const StyledFaqText = styled.p`
+ padding: 0.5rem 2rem;
+ text-align: left;
+ @media (min-width: 800px) {
+ hyphens: auto;
+ word-wrap: break-word;
+ }
+
+ ul {
+ margin: 0.5rem 2rem;
+ }
+`;
+export const StyledSummaryLink = styled.a`
+ color: ${Color.LIGHT_BLUE};
+ text-decoration: none !important;
+ transition: all 0.25s ease-in-out;
+
+ &:hover {
+ font-weight: bold;
+ text-decoration: none;
+ color: ${Color.YELLOW};
+ }
+
+ &:visited {
+ color: ${Color.MAGENTA};
+ text-decoration: none;
+ }
+`;
diff --git a/src/2025/Home/components/Faqs/Faqs.tsx b/src/2025/Home/components/Faqs/Faqs.tsx
new file mode 100644
index 000000000..de49f8f55
--- /dev/null
+++ b/src/2025/Home/components/Faqs/Faqs.tsx
@@ -0,0 +1,193 @@
+import { Color } from "@styles/colors";
+import { FC, Suspense } from "react";
+import FaqCard from "./components/FaqsCard";
+import { useWindowSize } from "react-use";
+import { MOBILE_BREAKPOINT } from "@constants/BreakPoints";
+import { StyledLoadingImage } from "@components/Loading/Loading";
+// @ts-expect-error some quirky import
+import { motion } from "motion/react";
+import { faqsData } from "./FaqsData";
+import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
+import {
+ StyledFaqSection,
+ StyledH2,
+ StyledImage,
+ StyledP,
+ StyledSummaryLink,
+ StyledWaveContainer,
+ StyleLessIcon,
+ StyleMoreIcon,
+} from "./Faqs.style";
+
+const Faqs: FC> = () => {
+ const { width } = useWindowSize();
+
+ return (
+ <>
+
+
+ {faqsData.map((faq, index) => (
+
+ ))}
+
+
+
+ Check last DevBcn edition
+
+ }>
+
+
+
+
+ }>
+
+
+
+
+
+
+ DevBcn is the rebranding of the biggest Java & JVM conference in
+ Spain, now including more technologies and tracks.
+
+
+
+
+ Check for videos/photos andsummary of the DevBcn —{" "}
+
+ 2024 edition —
+
+
+ {" "}
+ 2023 edition
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {width > MOBILE_BREAKPOINT && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Faqs;
diff --git a/src/2025/Home/components/Faqs/FaqsData.ts b/src/2025/Home/components/Faqs/FaqsData.ts
new file mode 100644
index 000000000..6cdb6f82b
--- /dev/null
+++ b/src/2025/Home/components/Faqs/FaqsData.ts
@@ -0,0 +1,21 @@
+export interface FaqType {
+ answer: string;
+ id: string;
+ question: string;
+}
+
+export const faqsData: FaqType[] = [
+ {
+ id: "2274b606-a043-4899-839a-f236d88bbe98",
+ question: "What is the Barcelona Developers Conference?",
+ answer:
+ "Two days to share knowledge and experiences, meet enthusiasts and geeks and learn about new technologies related to Backend and frontend development, Agile, DevOps, Cloud, and many others.",
+ },
+ {
+ id: "13eedebd-59a8-48c5-8897-6270a617ae75",
+ question: "Why should i participate?",
+ answer:
+ "This conference is the perfect stage to discover how others are using your favourite technology. " +
+ "There is something interesting for any kind of tech passionate: on the backend Java & JVM, Python, Rust, Go, to Frontend with JavaScript, TypeScript and Web assembly; Also, Cloud, Kubernetes, and DevOps, Agile, Big Data, Machine Learning and more",
+ },
+];
diff --git a/src/2025/Home/components/Faqs/components/FaqsCard.tsx b/src/2025/Home/components/Faqs/components/FaqsCard.tsx
new file mode 100644
index 000000000..4b5d95840
--- /dev/null
+++ b/src/2025/Home/components/Faqs/components/FaqsCard.tsx
@@ -0,0 +1,67 @@
+import React, { FC, Suspense } from "react";
+import { StyledLoadingImage } from "@components/Loading/Loading";
+// @ts-expect-error some quirky import
+import { motion } from "motion/react";
+import {
+ FaqCardType,
+ StyledFaqCard,
+ StyledFaqImage,
+ StyledFaqImageContainer,
+ StyledFaqInfo,
+ StyledFaqText,
+ StyledFaqTitle,
+} from "../Faqs.style";
+
+const FaqCard: FC> = ({ faq, index }) => {
+ const isOdd = index % 2 === 0;
+
+ return (
+
+
+ }>
+
+
+
+
+
+ {faq.question}
+
+
+ {faq.answer}
+
+
+
+ );
+};
+
+export default FaqCard;
diff --git a/src/2025/Home/components/Home/DateUtil.ts b/src/2025/Home/components/Home/DateUtil.ts
new file mode 100644
index 000000000..f647c913c
--- /dev/null
+++ b/src/2025/Home/components/Home/DateUtil.ts
@@ -0,0 +1,16 @@
+import { format } from "date-fns";
+
+export function formatDateRange(startDate: Date, endDate: Date): string {
+ const sameMonthAndYear =
+ startDate.getMonth() === endDate.getMonth() &&
+ startDate.getFullYear() === endDate.getFullYear();
+
+ if (sameMonthAndYear) {
+ return `${format(startDate, "MMMM do")} - ${format(endDate, "do, yyyy")}`;
+ } else {
+ return `${format(startDate, "MMMM do, yyyy")} - ${format(
+ endDate,
+ "MMMM do, yyyy",
+ )}`;
+ }
+}
diff --git a/src/2025/Home/components/Home/Home.tsx b/src/2025/Home/components/Home/Home.tsx
new file mode 100644
index 000000000..58f42eabf
--- /dev/null
+++ b/src/2025/Home/components/Home/Home.tsx
@@ -0,0 +1,156 @@
+import Countdown from "react-countdown";
+import React, { FC } from "react";
+import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
+import { BIGGER_BREAKPOINT } from "@constants/BreakPoints";
+import TimeCountDown from "./components/TimeCountdown";
+import { useWindowSize } from "react-use";
+// @ts-expect-error some quirky import
+import { motion } from "motion/react";
+import {
+ StyledBlueSlash,
+ StyledBottomSlash,
+ StyledDevBcnLogo,
+ StyledGreenSlash,
+ StyledHomeImage,
+ StyledLessThan,
+ StyledLogoDiv,
+ StyledSubtitle,
+ StyledTitle,
+ StyledTitleContainer,
+ StyledTopSlash,
+ StyleHomeContainer,
+} from "./Style.Home";
+import ActionButtons from "../ActionButtons/ActionButtons";
+import { Color } from "@styles/colors";
+import InfoButtons from "../InfoButtons/InfoButtons";
+import { formatDateRange } from "./DateUtil";
+import { Link } from "react-router";
+import edition from "@data/2025.json";
+import CountDownCompleted from "./components/CountDownCompleted";
+
+const Home: FC> = () => {
+ const { width } = useWindowSize();
+
+ return (
+
+
+
+
+
+
+
+
+ The Barcelona Developers Conference {edition?.edition}
+
+
+ {edition?.trackNumber} tracks with the following topics:
+ {edition?.tracks}
+
+
+
+ Past events: 2024 edition |{" "}
+ 2023 edition
+
+
+
+
+
+
+ {edition?.startDay &&
+ edition.endDay &&
+ formatDateRange(
+ new Date(edition.startDay),
+ new Date(edition.endDay),
+ )}
+
+
+ La Farga, Hospitalet, Barcelona
+
+
+
+
+
+ {edition?.actionButtons && }
+ {edition?.showInfoButtons && }
+
+ {width > BIGGER_BREAKPOINT && (
+
+ )}
+ {width > BIGGER_BREAKPOINT && (
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /{" "}
+
+
+ )}
+
+ {width > BIGGER_BREAKPOINT && (
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / /{" "}
+
+ )}
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/src/2025/Home/components/Home/Style.Home.tsx b/src/2025/Home/components/Home/Style.Home.tsx
new file mode 100644
index 000000000..5b2ff9212
--- /dev/null
+++ b/src/2025/Home/components/Home/Style.Home.tsx
@@ -0,0 +1,179 @@
+import { styled } from "styled-components";
+import { Color } from "@styles/colors";
+import { motion } from "motion/react";
+import { BIG_BREAKPOINT, BIGGER_BREAKPOINT } from "@constants/BreakPoints";
+
+export const StyledHomeImage = styled.div`
+ padding: 70px 0 40px;
+ background: linear-gradient(-45deg, ${Color.LIGHT_BLUE}, ${Color.MAGENTA}, ${Color.DARK_BLUE}, ${Color.GREEN});
+ background-size: 400% 400%;
+ animation: gradient 15s ease infinite;
+}
+
+@keyframes gradient {
+ 0% {
+ background-position: 0 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0 50%;
+ }
+`;
+export const StyleHomeContainer = styled.div`
+ position: relative;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+`;
+
+export const StyledTitleContainer = styled(motion.div)`
+ background-color: ${(props) => props.color ?? Color.DARK_BLUE};
+ border-radius: 10px;
+ width: 70%;
+ margin-bottom: 1rem;
+ padding: 10px 5px;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ width: 80%;
+ }
+`;
+
+export const StyledTitle = styled(motion.h1)`
+ padding: 0.5rem 1rem;
+ color: ${Color.WHITE};
+ font-family: "Square 721 Regular", sans-serif;
+`;
+
+export const StyledSubtitle = styled(motion.h2)`
+ color: ${(props: { color: string }) => props.color ?? Color.WHITE};
+ font-family: "DejaVu Sans ExtraLight", sans-serif;
+ font-size: 1.25rem;
+ text-shadow: 1px 1px 1px black;
+ padding: 0.25rem;
+
+ a {
+ text-decoration: none;
+ color: ${Color.LIGHT_BLUE};
+ }
+`;
+
+export const StyledLessThan = styled(motion.img)`
+ height: 7rem;
+ position: absolute;
+ top: 50%;
+ left: 5rem;
+ animation: StyledLessThanAnimation 6s infinite linear;
+
+ @keyframes StyledLessThanAnimation {
+ 0% {
+ transform: rotate(0deg) translate(-20px) rotate(0deg);
+ }
+ 80% {
+ transform: rotate(360deg) translate(-20px) rotate(-360deg);
+ }
+ 90% {
+ transform: translate(-5px);
+ }
+ 100% {
+ transform: translate(-20px);
+ }
+ }
+`;
+
+export const StyledBottomSlash = styled(motion.div)`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 40%;
+ height: 2rem;
+`;
+
+export const StyledTopSlash = styled(motion.div)`
+ position: absolute;
+ bottom: 25%;
+ right: 0;
+ height: 2rem;
+ width: 25%;
+`;
+
+export const StyledGreenSlash = styled(motion.p)`
+ font-family: "Square 721 Regular", sans-serif;
+ color: ${Color.DARK_BLUE};
+ font-size: 2rem;
+ overflow-y: hidden;
+ height: 100%;
+`;
+
+export const StyledBlueSlash = styled(motion.p)`
+ font-family: "Square 721 Regular", sans-serif;
+ color: ${Color.BLUE};
+ font-size: 2rem;
+ overflow-y: hidden;
+ height: 100%;
+`;
+export const StyledDevBcnLogo = styled.img`
+ margin: 20px;
+ height: 20rem;
+ aspect-ratio: 800/327;
+ transition: height 0.2s ease-in-out;
+ @media (max-width: ${BIGGER_BREAKPOINT}px) {
+ height: 15rem;
+ }
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ height: 8rem;
+ }
+`;
+export const StyledKcdLogo = styled.img`
+ margin-top: 4em;
+ margin-left: 2em;
+ height: 13rem;
+ transition: height 0.2s ease-in-out;
+ aspect-ratio: 800/327;
+ @media (max-width: ${BIGGER_BREAKPOINT}px) {
+ height: 12rem;
+ margin: 0;
+ }
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ margin-top: 0;
+ margin-left: 2.5em;
+ margin-right: 2.5em;
+ padding: 1em;
+ height: 10rem;
+ }
+`;
+export const StyledPlusSign = styled.span`
+ color: white;
+ font-size: 5em;
+ display: block;
+ padding-top: 1.5em;
+ text-shadow: 3px 3px #000;
+ transition: height 0.2s ease-in-out;
+ @media (max-width: ${BIGGER_BREAKPOINT}px) {
+ margin: 0;
+ padding: 0;
+ font-size: 3em;
+ }
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ font-size: 1.5rem;
+ padding: 0;
+ margin: 0;
+ }
+`;
+export const StyledLogoDiv = styled(motion.div)`
+ padding-top: 4rem;
+ padding-bottom: 2rem;
+ display: flex;
+
+ @media (max-width: ${BIGGER_BREAKPOINT}px) {
+ flex-direction: column;
+ }
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ flex-direction: column;
+ }
+`;
diff --git a/src/2025/Home/components/Home/components/CountDownCompleted.tsx b/src/2025/Home/components/Home/components/CountDownCompleted.tsx
new file mode 100644
index 000000000..c354bef50
--- /dev/null
+++ b/src/2025/Home/components/Home/components/CountDownCompleted.tsx
@@ -0,0 +1,33 @@
+import React, {FC} from "react";
+import {
+ StyledTimerContainer,
+ StyledTimerLetters,
+ StyleLine,
+ TimeCountDownContainer
+} from "./countdown.style";
+
+const CountDownCompleted: FC = () => {
+ return
+
+ 0
+ DAYS
+
+
+
+ 0
+ HOURS
+
+
+
+ 0
+ MINUTES
+
+
+
+ 0
+ SECONDS
+
+ ;
+};
+
+export default CountDownCompleted;
\ No newline at end of file
diff --git a/src/2025/Home/components/Home/components/TimeCountdown.tsx b/src/2025/Home/components/Home/components/TimeCountdown.tsx
new file mode 100644
index 000000000..ab8e41409
--- /dev/null
+++ b/src/2025/Home/components/Home/components/TimeCountdown.tsx
@@ -0,0 +1,47 @@
+import React, {FC} from 'react';
+import {
+ StyledTimerContainer,
+ StyledTimerLetters,
+ StyleLine,
+ TimeCountDownContainer
+} from "./countdown.style";
+
+type TimeCountDownProps = {
+ days: number;
+ hours: number;
+ minutes: number;
+ seconds: number;
+};
+
+const TimeCountDown: FC> = ({
+ days,
+ hours,
+ minutes,
+ seconds,
+ }) => {
+ return (
+
+
+ {days}
+ DAYS
+
+
+
+ {hours}
+ HOURS
+
+
+
+ {minutes}
+ MINUTES
+
+
+
+ {seconds}
+ SECONDS
+
+
+ );
+};
+
+export default TimeCountDown;
diff --git a/src/2025/Home/components/Home/components/countdown.style.ts b/src/2025/Home/components/Home/components/countdown.style.ts
new file mode 100644
index 000000000..6b9963d59
--- /dev/null
+++ b/src/2025/Home/components/Home/components/countdown.style.ts
@@ -0,0 +1,31 @@
+import { styled } from "styled-components";
+import { Color } from "../../../../../styles/colors";
+
+export const TimeCountDownContainer = styled.div`
+ display: flex;
+ align-items: center;
+ padding-top: 1.2rem;
+`;
+export const StyledTimerContainer = styled.div`
+ align-items: center;
+ background-color: rgba(50, 50, 50, 0.5);
+ border-radius: 3rem;
+ border: 1.5px solid ${Color.DARK_BLUE};
+ box-shadow: 1px 1px 1px ${Color.LIGHT_BLUE};
+ color: white;
+ display: flex;
+ flex-direction: column;
+ font-family: "Square 721 Regular", sans-serif;
+ font-size: 1.5em;
+ height: 5rem;
+ justify-content: center;
+ width: 5rem;
+`;
+export const StyleLine = styled.div`
+ width: 0.75rem;
+ background: ${Color.DARK_BLUE};
+ height: 1.5px;
+`;
+export const StyledTimerLetters = styled.p`
+ font-size: 0.75rem;
+`;
diff --git a/src/2025/Home/components/InfoButtons/InfoButtons.test.tsx b/src/2025/Home/components/InfoButtons/InfoButtons.test.tsx
new file mode 100644
index 000000000..5e37f7c7e
--- /dev/null
+++ b/src/2025/Home/components/InfoButtons/InfoButtons.test.tsx
@@ -0,0 +1,47 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+import ActionButtons from "./InfoButtons";
+import { gaEventTracker } from "../../../../components/analytics/Analytics";
+import { vi } from "vitest";
+
+vi.mock("../../../../components/analytics/Analytics", () => ({
+ gaEventTracker: vi.fn(),
+}));
+
+describe("ActionButtons", () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test("renders two action buttons", () => {
+ render();
+
+ const attendeeButton = screen.getByText("🙋🏻 Attendee information");
+ const speakerButton = screen.getByText("🎙️ Speaker information");
+
+ expect(attendeeButton).toBeInTheDocument();
+ expect(speakerButton).toBeInTheDocument();
+ });
+
+ test("calls gaEventTracker with correct parameters on attendee button click", () => {
+ render();
+ const attendeeButton = screen.getByText("🙋🏻 Attendee information");
+
+ fireEvent.click(attendeeButton);
+
+ expect(gaEventTracker).toHaveBeenCalledWith(
+ "attendee-info",
+ "attendee-infos",
+ );
+ });
+
+ test("calls gaEventTracker with correct parameters on speaker button click", () => {
+ render();
+ const speakerButton = screen.getByText("🎙️ Speaker information");
+
+ fireEvent.click(speakerButton);
+
+ expect(gaEventTracker).toHaveBeenCalledWith("speaker-info", "speaker-info");
+ });
+
+ // Add more test cases as needed
+});
diff --git a/src/2025/Home/components/InfoButtons/InfoButtons.tsx b/src/2025/Home/components/InfoButtons/InfoButtons.tsx
new file mode 100644
index 000000000..a5aa7c588
--- /dev/null
+++ b/src/2025/Home/components/InfoButtons/InfoButtons.tsx
@@ -0,0 +1,44 @@
+import { FC, useCallback } from "react";
+import Button from "../../../../components/UI/Button";
+import { styled } from "styled-components";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import { gaEventTracker } from "../../../../components/analytics/Analytics";
+
+const StyledActionDiv = styled.div`
+ display: flex;
+ text-align: center;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ flex-direction: column;
+ width: 75%;
+ }
+`;
+
+const InfoButtons: FC> = () => {
+ const trackAttendee = useCallback(() => {
+ gaEventTracker("attendee-info", "attendee-infos");
+ }, []);
+
+ const trackSpeaker = useCallback(() => {
+ gaEventTracker("speaker-info", "speaker-info");
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+};
+export default InfoButtons;
diff --git a/src/2025/Home/components/Sponsors/BasicSponsor.tsx b/src/2025/Home/components/Sponsors/BasicSponsor.tsx
new file mode 100644
index 000000000..99c0b10c3
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/BasicSponsor.tsx
@@ -0,0 +1,97 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconNano,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const BasicSponsor: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/Communities.tsx b/src/2025/Home/components/Sponsors/Communities.tsx
new file mode 100644
index 000000000..8536f8034
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/Communities.tsx
@@ -0,0 +1,93 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconMicro,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const Communities: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+
+
+
+ = BIG_BREAKPOINT
+ ? Color.WHITE
+ : Color.DARK_BLUE
+ }
+ id="Slashes"
+ >
+ COMMUNITIES
+
+ {slashes}
+
+ {width >= BIG_BREAKPOINT && (
+
+ {slashes}
+
+ )}
+
+
+
+
+ {sponsors.map((sponsor) => (
+
+
+
+ ))}
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/MediaPartners.tsx b/src/2025/Home/components/Sponsors/MediaPartners.tsx
new file mode 100644
index 000000000..5cc9cfe9b
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/MediaPartners.tsx
@@ -0,0 +1,94 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconMicro,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const MediaPartners: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+
+
+
+ = BIG_BREAKPOINT
+ ? Color.WHITE
+ : Color.DARK_BLUE
+ }
+ id="Slashes"
+ >
+ MEDIA PARTNERS
+
+ {slashes}
+
+ {width >= BIG_BREAKPOINT && (
+
+ {slashes}
+
+ )}
+
+
+
+
+ {sponsors.map((sponsor) => (
+
+
+
+ ))}
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/PremiumSponsors.tsx b/src/2025/Home/components/Sponsors/PremiumSponsors.tsx
new file mode 100644
index 000000000..67fef734a
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/PremiumSponsors.tsx
@@ -0,0 +1,99 @@
+import {
+ PremiumSponsorImage,
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const PremiumSponsors: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/RegularSponsors.tsx b/src/2025/Home/components/Sponsors/RegularSponsors.tsx
new file mode 100644
index 000000000..1dec1c786
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/RegularSponsors.tsx
@@ -0,0 +1,93 @@
+import {
+ RegularSponsorImage,
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const RegularSponsors: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/SponsorBadge.tsx b/src/2025/Home/components/Sponsors/SponsorBadge.tsx
new file mode 100644
index 000000000..86a8fe55c
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/SponsorBadge.tsx
@@ -0,0 +1,36 @@
+import { AnimatePresence } from "framer-motion";
+import { FC } from "react";
+import {
+ leftVariants,
+ rightVariants,
+ StyledSponsorBadgeLeft,
+} from "./Sponsors.style";
+
+interface ISponsorBadgeProps {
+ position: "left" | "right";
+ color: string;
+ isVisible: boolean;
+}
+
+const SponsorBadge: FC> = ({
+ position,
+ color,
+ isVisible,
+}) => {
+ return (
+
+ {isVisible && (
+
+ )}
+
+ );
+};
+
+export default SponsorBadge;
diff --git a/src/2025/Home/components/Sponsors/Sponsors.style.ts b/src/2025/Home/components/Sponsors/Sponsors.style.ts
new file mode 100644
index 000000000..1a18abd4f
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/Sponsors.style.ts
@@ -0,0 +1,297 @@
+import { styled } from "styled-components";
+import { BIG_BREAKPOINT, LARGE_BREAKPOINT } from "@constants/BreakPoints";
+import { motion } from "motion/react";
+
+const SponsorMargin = 11;
+const sponsorMarginDesktop = 11;
+export const StyledSponsorsContainer = styled.div`
+ position: relative;
+ padding-top: 4rem;
+`;
+export const StyledTitleContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 1rem;
+`;
+export const StyledTitleImg = styled.img`
+ height: 4rem;
+ @media (min-width: 800px) {
+ height: 10rem;
+ }
+`;
+export const StyledSponsorItemContainer = styled.div`
+ position: relative;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ font-size: 1.75rem;
+ padding: 5.5rem 0 0.5rem;
+ z-index: 1;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ margin-bottom: 5rem;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ margin-bottom: 3rem;
+ }
+`;
+export const StyledSponsorTitleContainer = styled.div`
+ width: 100%;
+ display: flex;
+ position: absolute;
+ top: 1rem;
+ z-index: 2;
+ background: none;
+`;
+export const StyledSponsorTitleMargin = styled.div`
+ width: 10%;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ width: ${sponsorMarginDesktop}%;
+ }
+`;
+export const StyledSponsorTitleSlashesContainer = styled.div<{ color: string }>`
+ display: flex;
+ flex-wrap: nowrap;
+ width: 90%;
+ font-family: "Square 721 Regular", sans-serif;
+ color: ${({ color }) => color};
+ height: 2.75rem;
+ line-height: 2.75rem;
+ white-space: nowrap;
+
+ overflow: hidden;
+
+ z-index: 2;
+
+ transition: all 0.2s linear;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ width: 41%;
+ }
+`;
+export const StyledSlashes = styled.div`
+ white-space: nowrap;
+ overflow: hidden;
+ clip-path: polygon(0 0, 100% 0, 97% 100%, 0% 100%);
+`;
+export const StyledSponsorLogosContainer = styled.div`
+ display: flex;
+ width: 100%;
+ top: 4.75rem;
+ z-index: 2;
+ background: none;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ bottom: 15rem;
+ padding-right: 1rem;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ }
+`;
+export const StyledLogos = styled.div.withConfig({
+ shouldForwardProp: (prop) => !["position"].includes(prop),
+})<{ position?: "left" | "right" }>`
+ display: flex;
+ width: 100%;
+
+ padding-left: ${({ position }) =>
+ position === "right" ? 0 : SponsorMargin}%;
+ padding-right: ${({ position }) =>
+ position === "right" ? SponsorMargin : 0}%;
+
+ flex-wrap: wrap;
+ justify-content: center;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ justify-content: center;
+ padding-left: ${({ position }) =>
+ position === "right" ? 0 : sponsorMarginDesktop}%;
+ padding-right: ${({ position }) =>
+ position === "right" ? sponsorMarginDesktop : 0}%;
+ top: 5rem;
+ flex-wrap: wrap;
+ width: ${({ position }) => (position === "right" ? 90 : 100)}%;
+ }
+
+ @media (min-width: ${LARGE_BREAKPOINT}px) {
+ justify-content: ${({ position }) =>
+ position === "right" ? "flex-end" : "flex-start"};
+ padding-left: ${({ position }) =>
+ position === "right" ? 0 : sponsorMarginDesktop}%;
+ padding-right: ${({ position }) =>
+ position === "right" ? sponsorMarginDesktop : 0}%;
+ top: 5rem;
+ }
+`;
+export const StyledFlexGrow = styled.div`
+ flex: 1;
+ display: none;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ display: flex;
+ }
+`;
+export const StyledSeparator = styled.div`
+ width: 7rem;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ width: 4rem;
+ }
+`;
+export const PremiumSponsorImage = styled.img`
+ height: 6rem;
+ transition: height ease-in 0.25s;
+ max-width: 100%;
+ margin: 1rem 2rem;
+
+ &:hover {
+ filter: drop-shadow(1px 1px 1px #fff) !important;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 7.5rem;
+ }
+`;
+export const RegularSponsorImage = styled.img`
+ height: 3.25rem;
+ margin-right: 0.5rem;
+ margin-bottom: 1.5rem;
+ transition: height ease-in 0.25s;
+ border-radius: 1rem;
+
+ &:hover {
+ filter: drop-shadow(1px 1px 1px #fff) !important;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 3.25rem;
+ margin-right: 2rem;
+ margin-bottom: 0.75rem;
+ }
+
+ @media (min-width: ${LARGE_BREAKPOINT}px) {
+ height: 3.25rem;
+ margin-right: 2rem;
+ margin-bottom: 27px;
+ }
+`;
+export const StyledSponsorIconNano = styled.img`
+ height: 3.5rem;
+ margin-bottom: 1rem;
+
+ margin-left: 0.75rem;
+ transition: height ease-in 0.25s;
+
+ &:hover {
+ height: 4rem;
+ filter: drop-shadow(1px 1px 1px #fff) !important;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 3.5rem;
+ margin-left: 1rem;
+ }
+
+ @media (min-width: ${LARGE_BREAKPOINT}px) {
+ height: 3.5rem;
+ margin-left: 2.5rem;
+ }
+`;
+export const StyledSponsorIconMicro = styled.img`
+ height: 3.5rem;
+ margin-bottom: 1rem;
+ transition: height ease-in 0.25s;
+ margin-left: 0.75rem;
+
+ &:hover {
+ filter: drop-shadow(1px 1px 1px #fff) !important;
+ }
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 3.5rem;
+ margin-left: 1rem;
+ }
+
+ @media (min-width: ${LARGE_BREAKPOINT}px) {
+ height: 3.5rem;
+ margin-left: 2.5rem;
+ }
+`;
+export const StyledSponsorBadgeLeft = styled(motion.div)<{
+ color: string;
+ position: "left" | "right";
+}>`
+ display: none;
+ position: absolute;
+ width: ${({ position }) => (position === "left" ? "60%" : "62%")};
+ clip-path: ${({ position }) => {
+ if (position === "left") {
+ return "polygon(0 0, 100% 0, 92% 100%, 0% 100%)";
+ } else {
+ return "polygon(6% 0, 100% 0, 100% 100%, 0 100%)";
+ }
+ }};
+ top: 0;
+ bottom: 0;
+ background-color: ${({ color }) => color};
+
+ left: ${({ position }) => {
+ if (position === "left") {
+ return "0";
+ } else {
+ return "unset";
+ }
+ }};
+
+ right: ${({ position }) => {
+ if (position === "right") {
+ return "0";
+ } else {
+ return "unset";
+ }
+ }};
+ z-index: 1;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ display: flex;
+ }
+`;
+
+export const leftVariants = {
+ initial: {
+ x: -700,
+ },
+ animate: {
+ x: 0,
+ transition: {
+ duration: 0.2,
+ },
+ },
+ exit: {
+ x: -700,
+ transition: {
+ duration: 0.2,
+ },
+ },
+};
+
+export const rightVariants = {
+ initial: {
+ x: 1000,
+ },
+ animate: {
+ x: 0,
+ transition: {
+ duration: 0.2,
+ },
+ },
+ exit: {
+ x: 1000,
+ transition: {
+ duration: 0.2,
+ },
+ },
+};
diff --git a/src/2025/Home/components/Sponsors/Sponsors.tsx b/src/2025/Home/components/Sponsors/Sponsors.tsx
new file mode 100644
index 000000000..acc048f8f
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/Sponsors.tsx
@@ -0,0 +1,53 @@
+import { Color } from "@styles/colors";
+import React, { FC } from "react";
+
+import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
+import TitleSection from "@components/SectionTitle/TitleSection";
+import {
+ StyledSponsorsContainer,
+ StyledTitleContainer,
+ StyledTitleImg,
+} from "./Sponsors.style";
+import { TopSponsors } from "./TopSponsors";
+import { RegularSponsors } from "./RegularSponsors";
+import { PremiumSponsors } from "./PremiumSponsors";
+import { BasicSponsor } from "./BasicSponsor";
+import { Communities } from "./Communities";
+import { MediaPartners } from "./MediaPartners";
+import { Supporters } from "./Supporters";
+import { sponsors } from "./SponsorsData";
+
+const Sponsors: FC> = () => (
+
+
+
+);
+
+export default Sponsors;
diff --git a/src/2025/Home/components/Sponsors/SponsorsData.ts b/src/2025/Home/components/Sponsors/SponsorsData.ts
new file mode 100644
index 000000000..3c867d42c
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/SponsorsData.ts
@@ -0,0 +1,162 @@
+export interface Sponsors {
+ top: Sponsor[] | null;
+ premium: Sponsor[] | null;
+ regular: Sponsor[] | null;
+ communities: Sponsor[] | null;
+ basic: Sponsor[] | null;
+ media_partners: Sponsor[] | null;
+ supporters: Sponsor[] | null;
+}
+
+export interface Sponsor {
+ name: string;
+ website: string;
+ image: string;
+}
+
+export const sponsors: Sponsors = {
+ top: [],
+ premium: [
+ {
+ name: "Dynatrace",
+ image: "images/sponsors/dynatrace.png",
+ website: "https://www.dynatrace.com/",
+ },
+ ],
+ regular: [
+ {
+ name: "Caixabank Tech",
+ website: "https://www.caixabanktech.com/es/pagina-de-inicio/",
+ image: "/images/sponsors/caixabank-tech.png",
+ },
+ {
+ name: "Sopra Steria",
+ image: "/images/sponsors/sopra.png",
+ website: "https://www.soprasteria.es/",
+ },
+ {
+ name: "Elastic",
+ image: "/images/sponsors/logo-elastic-horizontal-color.png",
+ website: "https://www.elastic.co/",
+ },
+ {
+ name: "Manychat",
+ website: "https://careers.manychat.com/",
+ image: "/images/sponsors/logo-manychat.webp",
+ },
+ {
+ name: "Snowflake",
+ website: "https://www.snowflake.com/en/developers/",
+ image: "/images/sponsors/logo-snowflake.png",
+ },
+ {
+ name: "Clever Cloud",
+ image: "/images/sponsors/clever-cloud.png",
+ website: "https://www.clever-cloud.com/",
+ },
+ {
+ name: "Vonage",
+ website: "https://vonage.dev/DevBcn",
+ image: "/images/sponsors/vonage.jpg",
+ },
+ {
+ name: "NUBANK",
+ image: "/images/sponsors/datomic.svg",
+ website: "https://nubank.com.br/",
+ },
+ ],
+ basic: [
+ {
+ name: "Seidor",
+ website: "https://www.opentrends.net/en",
+ image: "/images/sponsors/seidor.png",
+ },
+ {
+ name: "Grupo Castilla",
+ image: "/images/sponsors/grupo-castilla.png",
+ website:
+ "https://www.grupocastilla.es/servicios-rrhh/consultoria-tecnologica/",
+ },
+ {
+ name: "FOR GOOD AI",
+ website: "https://zencoder.ai/",
+ image: "/images/sponsors/zencoder.png",
+ },
+ {
+ name: "ORTUS SOLUTIONS",
+ website: "https://boxlang.io/",
+ image: "/images/sponsors/boxlang.png",
+ },
+ {
+ name: "Preply",
+ website: "https://preply.com/en/careers",
+ image: "/images/sponsors/preply.svg",
+ },
+ {
+ name: "Dow Jones",
+ image: "/images/sponsors/dow-jones.png",
+ website: "https://www.dowjones.com/",
+ },
+ {
+ name: "Azul",
+ image: "images/sponsors/azul.png",
+ website: "https://www.azul.com",
+ },
+ {
+ name: "Glovo",
+ website:
+ "https://jobs.glovoapp.com/departments/engineering-2/?d=engineering&l=barcelona-hq",
+ image: "images/sponsors/glovo.png",
+ },
+ ],
+ communities: [
+ {
+ name: "Step4ward",
+ image: "images/sponsors/step4ward.png",
+ website: "https://bit.ly/step4wardhome",
+ },
+ {
+ name: "Migracode Barcelona",
+ image: "images/sponsors/migracode.jpg",
+ website: "https://www.migracode.org/",
+ },
+ {
+ name: "CodeWomen+",
+ image: "/images/sponsors/codewomen.png",
+ website: "https://codewomen.plus/",
+ },
+ ],
+ media_partners: [
+ {
+ name: "Digital Expert Online",
+ website: "https://digital-expert.online/en/",
+ image: "/images/sponsors/logo-digital-expert.svg",
+ },
+ {
+ name: "Kube events",
+ image: "/images/sponsors/kube-events.png",
+ website: "https://kube.events/",
+ },
+ {
+ name: "Kube careers",
+ image: "/images/sponsors/kube-career.png",
+ website: "https://kube.careers/",
+ },
+ {
+ name: "CIO Insights",
+ website: "https://www.cioinsights.com/",
+ image: "/images/sponsors/cio-insights.png",
+ },
+ {
+ name: "Codely",
+ image: "images/sponsors/codely.png",
+ website: "https://codely.com/",
+ },
+ {
+ name: "Foojay",
+ image: "images/sponsors/foojay.jpg",
+ website: "https://foojay.io/",
+ },
+ ],
+ supporters: [],
+};
diff --git a/src/2025/Home/components/Sponsors/Supporters.test.tsx b/src/2025/Home/components/Sponsors/Supporters.test.tsx
new file mode 100644
index 000000000..60cf81eb0
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/Supporters.test.tsx
@@ -0,0 +1,82 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+import { Supporters } from "./Supporters";
+import React from "react";
+import { useWindowSize } from "react-use";
+import { BrowserRouter, Route, Routes } from "react-router";
+import { Sponsor } from "./SponsorsData";
+import { vi } from "vitest";
+
+vi.mock("react-use", () => ({
+ useWindowSize: vi.fn(),
+}));
+
+describe("Supporters", () => {
+ beforeEach(() => {
+ (useWindowSize as jest.Mock).mockReturnValue({ width: 1024 }); // Mock window width for testing
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ const supporters: Sponsor[] = [
+ {
+ name: "test",
+ website: "https://www.acme.com",
+ image: "https://www.acme.com/logo.png",
+ },
+ ];
+
+ // disabled until supporters included
+ it("renders component with supporters", () => {
+ render(
+ Loading...}>
+
+ } />
+
+ ,
+ { wrapper: BrowserRouter },
+ );
+
+ expect(screen.getByTestId("supporters")).toBeInTheDocument();
+ expect(screen.getByText("SUPPORTERS")).toBeInTheDocument();
+ expect(screen.getAllByRole("link")).toHaveLength(1);
+ });
+
+ it("applies hover styles on mouse enter", () => {
+ render(
+ Loading...}>
+
+ } />
+
+ ,
+ { wrapper: BrowserRouter },
+ );
+ const supportersElement = screen.getByTestId("supporters");
+
+ fireEvent.mouseEnter(supportersElement);
+
+ expect(supportersElement).toHaveClass("SponsorItem");
+ expect(screen.getByText("SUPPORTERS")).toHaveStyle(
+ "color: rgb(255, 252, 249)",
+ );
+ });
+
+ it("removes hover styles on mouse leave", () => {
+ render(
+ Loading...}>
+
+ } />
+
+ ,
+ { wrapper: BrowserRouter },
+ );
+ const supporterElement = screen.getByTestId("supporters");
+
+ fireEvent.mouseEnter(supporterElement);
+ fireEvent.mouseLeave(supporterElement);
+
+ expect(supporterElement).not.toHaveClass("hovered");
+ expect(screen.getByText("SUPPORTERS")).toHaveStyle("color: rgb(0, 36, 84)");
+ });
+});
diff --git a/src/2025/Home/components/Sponsors/Supporters.tsx b/src/2025/Home/components/Sponsors/Supporters.tsx
new file mode 100644
index 000000000..a7d09ee2e
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/Supporters.tsx
@@ -0,0 +1,102 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconMicro,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const Supporters: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+
+
+ = BIG_BREAKPOINT
+ ? Color.WHITE
+ : Color.DARK_BLUE
+ }
+ id="Slashes"
+ >
+ {slashes}
+
+
+ {width < BIG_BREAKPOINT && "SUPPORTERS"}
+
+ {width >= BIG_BREAKPOINT && (
+ = BIG_BREAKPOINT
+ ? Color.WHITE
+ : Color.DARK_BLUE
+ }
+ >
+ {slashes}
+ SUPPORTERS
+
+ )}
+
+
+
+
+
+
+ {sponsors.map((sponsor) => (
+
+
+
+ ))}
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/TopSponsors.tsx b/src/2025/Home/components/Sponsors/TopSponsors.tsx
new file mode 100644
index 000000000..ef3b3c657
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/TopSponsors.tsx
@@ -0,0 +1,92 @@
+import {
+ PremiumSponsorImage,
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+interface Props {
+ sponsors: Array | null;
+}
+
+export const TopSponsors: FC> = ({
+ sponsors,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+
+ return (
+ <>
+ {sponsors !== null && sponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx b/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
new file mode 100644
index 000000000..57b6f4e55
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
@@ -0,0 +1,107 @@
+import { act, renderHook } from "@testing-library/react";
+import { useSponsorsHook } from "./useSponsorsHook";
+import React, { FC } from "react";
+import { vi } from "vitest";
+import { buildSlashes } from "../../../../services/buildSlashes";
+
+// Mock the buildSlashes function
+vi.mock("../../../../services/buildSlashes", () => ({
+ buildSlashes: vi.fn((count: number) => "//".repeat(count)),
+}));
+
+const wrapper: FC>> = ({
+ children,
+}) => {
+ return {children}
;
+};
+
+describe("useSponsorsHook", () => {
+ beforeEach(() => {
+ // Clear mock calls between tests
+ vi.clearAllMocks();
+ });
+
+ it("should initialize with default values", () => {
+ const { result } = renderHook(
+ () => useSponsorsHook({ numberOfSlashGroups: 2 }),
+ { wrapper },
+ );
+
+ expect(result.current.slashes).toBe("////");
+ expect(result.current.isHovered).toBe(false);
+ expect(typeof result.current.width).toBe("number");
+ });
+
+ it("should update slashes when window size changes", () => {
+ const { rerender } = renderHook(
+ () => useSponsorsHook({ numberOfSlashGroups: 2 }),
+ { wrapper },
+ );
+
+ // Initial render should call buildSlashes once
+ expect(buildSlashes).toHaveBeenCalledTimes(2);
+ expect(buildSlashes).toHaveBeenCalledWith(2);
+
+ // Trigger a rerender (simulating window resize)
+ rerender();
+
+ // buildSlashes should be called again
+ expect(buildSlashes).toHaveBeenCalledTimes(3);
+ });
+
+ it("should update hover state correctly", () => {
+ const { result } = renderHook(
+ () => useSponsorsHook({ numberOfSlashGroups: 2 }),
+ { wrapper },
+ );
+
+ // Initial state should be not hovered
+ expect(result.current.isHovered).toBe(false);
+
+ // Simulate hover
+ act(() => {
+ result.current.handleHover();
+ });
+ expect(result.current.isHovered).toBe(true);
+
+ // Simulate unhover
+ act(() => {
+ result.current.handleUnHover();
+ });
+ expect(result.current.isHovered).toBe(false);
+ });
+
+ it("should update slashes when numberOfSlashGroups changes", () => {
+ const { result, rerender } = renderHook(
+ ({ numberOfSlashGroups }) => useSponsorsHook({ numberOfSlashGroups }),
+ { initialProps: { numberOfSlashGroups: 2 } },
+ );
+
+ // Initial render with 2 groups
+ expect(buildSlashes).toHaveBeenCalledWith(2);
+ expect(result.current.slashes).toBe("////");
+
+ // Update to 3 groups
+ rerender({ numberOfSlashGroups: 3 });
+
+ expect(buildSlashes).toHaveBeenCalledWith(3);
+ expect(result.current.slashes).toBe("//////");
+ });
+
+ it("should memoize hover handlers", () => {
+ const { result, rerender } = renderHook(() =>
+ useSponsorsHook({ numberOfSlashGroups: 2 }),
+ );
+
+ // Store initial handlers
+ const initialHandleHover = result.current.handleHover;
+ const initialHandleUnHover = result.current.handleUnHover;
+
+ // Rerender
+ rerender();
+
+ // Handlers should remain the same (memoized)
+ expect(result.current.handleHover).toBe(initialHandleHover);
+ expect(result.current.handleUnHover).toBe(initialHandleUnHover);
+ });
+});
diff --git a/src/2025/Home/components/Sponsors/useSponsorsHook.ts b/src/2025/Home/components/Sponsors/useSponsorsHook.ts
new file mode 100644
index 000000000..8e421f39a
--- /dev/null
+++ b/src/2025/Home/components/Sponsors/useSponsorsHook.ts
@@ -0,0 +1,41 @@
+import {useCallback, useEffect, useState} from "react";
+import {useWindowSize} from "react-use";
+
+import {buildSlashes} from "../../../../services/buildSlashes";
+
+/**
+ * Configuration for the sponsors hook
+ */
+interface SponsorHookConfig {
+ /** Number of slash groups to display in the sponsor section */
+ numberOfSlashGroups: number;
+}
+
+/**
+ * Custom hook to manage sponsor section behavior including:
+ * - Responsive slashes generation
+ * - Hover state management
+ * - Window size tracking
+ */
+export const useSponsorsHook = ({
+ numberOfSlashGroups = 2,
+}: SponsorHookConfig) => {
+ const { width } = useWindowSize();
+ const [slashes, setSlashes] = useState(buildSlashes(numberOfSlashGroups));
+ const [isHovered, setIsHovered] = useState(false);
+
+ useEffect(() => {
+ setSlashes(buildSlashes(numberOfSlashGroups));
+ }, [width, numberOfSlashGroups]);
+
+ const handleHover = useCallback(() => setIsHovered(true), []);
+ const handleUnHover = useCallback(() => setIsHovered(false), []);
+
+ return {
+ width,
+ slashes,
+ isHovered,
+ handleHover,
+ handleUnHover,
+ };
+};
diff --git a/src/2025/Navigation/NavigationData2025.ts b/src/2025/Navigation/NavigationData2025.ts
new file mode 100644
index 000000000..9a4a38eef
--- /dev/null
+++ b/src/2025/Navigation/NavigationData2025.ts
@@ -0,0 +1,26 @@
+import {
+ ROUTE_2025_CFP,
+ ROUTE_2025_DIVERSITY,
+ ROUTE_2025_HOME,
+ ROUTE_2025_JOB_OFFERS,
+ ROUTE_2025_SCHEDULE,
+ ROUTE_2025_SPEAKERS,
+ ROUTE_2025_TALKS,
+ ROUTE_2025_WORKSHOPS,
+} from "../../constants/routes";
+import { NavigationItem } from "../../components/Navigation/NavigationData";
+
+export const navigationItems2025: NavigationItem[] = [
+ { id: "Home", link: ROUTE_2025_HOME },
+ { id: "Sponsors", link: "/2025#sponsors" },
+ { id: "SCHEDULE", link: ROUTE_2025_SCHEDULE },
+ { id: "Talks", link: ROUTE_2025_TALKS },
+ { id: "Workshops", link: ROUTE_2025_WORKSHOPS },
+ { id: "JOB OFFERS", link: ROUTE_2025_JOB_OFFERS },
+ { id: "Speakers", link: ROUTE_2025_SPEAKERS },
+];
+
+export const subMenuItems2025: NavigationItem[] = [
+ { id: "DIVERSITY", link: ROUTE_2025_DIVERSITY },
+ { id: "Cfp Committee", link: ROUTE_2025_CFP },
+];
diff --git a/src/App.tsx b/src/App.tsx
index d1498455b..427845ab9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,158 +1,16 @@
import { Link, Route, Routes } from "react-router";
-import {
- ROUTE_2023_ATTENDEE,
- ROUTE_2023_CFP,
- ROUTE_2023_COMMUNITIES,
- ROUTE_2023_DIVERSITY,
- ROUTE_2023_HOME,
- ROUTE_2023_JOB_OFFERS,
- ROUTE_2023_SCHEDULE,
- ROUTE_2023_SESSION_FEEDBACK,
- ROUTE_2023_SPEAKER_DETAIL_PLAIN,
- ROUTE_2023_SPEAKER_INFO,
- ROUTE_2023_SPEAKERS,
- ROUTE_2023_TALK_DETAIL_PLAIN,
- ROUTE_2023_TALKS,
- ROUTE_2023_WORKSHOPS,
- ROUTE_2024_ATTENDEE,
- ROUTE_2024_CFP,
- ROUTE_2024_COMMUNITIES,
- ROUTE_2024_DIVERSITY,
- ROUTE_2024_HOME,
- ROUTE_2024_JOB_OFFERS,
- ROUTE_2024_SCHEDULE,
- ROUTE_2024_SESSION_FEEDBACK,
- ROUTE_2024_SPEAKER_DETAIL_PLAIN,
- ROUTE_2024_SPEAKER_INFO,
- ROUTE_2024_SPEAKERS,
- ROUTE_2024_TALK_DETAIL_PLAIN,
- ROUTE_2024_TALKS,
- ROUTE_2024_WORKSHOPS,
- ROUTE_ABOUT_US,
- ROUTE_ACCOMMODATION,
- ROUTE_CFP,
- ROUTE_CODE_OF_CONDUCT,
- ROUTE_CONDITIONS,
- ROUTE_COOKIES,
- ROUTE_DIVERSITY,
- ROUTE_HOME,
- ROUTE_JOB_OFFERS,
- ROUTE_KCD,
- ROUTE_MEETING_DETAIL_PLAIN,
- ROUTE_SCHEDULE,
- ROUTE_SPEAKER_DETAIL_PLAIN,
- ROUTE_SPEAKER_INFO,
- ROUTE_SPEAKERS,
- ROUTE_SPONSORSHIP,
- ROUTE_TALKS,
- ROUTE_TRAVEL,
- ROUTE_WORKSHOPS,
-} from "@constants/routes";
+import { ROUTE_COOKIES } from "@constants/routes";
import { Footer } from "@components/Footer/Footer";
-import { HomeWrapper } from "@views/Home/HomeWrapper";
import { Navigation } from "@components/Navigation/Navigation";
import { ScrollToTop } from "@components/ScrollToTop/ScrollToTop";
-import { SpeakerDetailContainer } from "@views/SpeakerDetail/SpeakerDetailContainer";
import { styled } from "styled-components";
-import React, { FC, lazy } from "react";
+import React, { FC, Suspense } from "react";
import { CookieConsent } from "react-cookie-consent";
import { Color } from "@styles/colors";
import { Loading } from "@components/Loading/Loading";
import { QueryClient, QueryClientProvider } from "react-query";
-import { SessionFeedback2023 } from "./2023/SessionFeedback/SessionFeedback2023";
-import { AttendeeInformation2023 } from "./2023/Attendee/AttendeeInformation2023";
-import { Communities2023 } from "./2023/Communities/Communities2023";
-import { SpeakerInformation2023 } from "./2023/Speakers/SpeakerInformation2023";
-import QrCodeSection from "@views/QrCode/QrCodeSection";
-
-//
-const Talks = lazy(() => import("./views/Talks/Talks"));
-const Conditions = lazy(() => import("./views/Conditions/Conditions"));
-const Cookies = lazy(() => import("./views/Cookies/Cookies"));
-const Speakers = lazy(() => import("./views/Speakers/Speakers"));
-const SpeakerInformation = lazy(() =>
- import("./views/Speakers/SpeakerInformation").then((value) => ({
- default: value.SpeakerInformation,
- })),
-);
-const About = lazy(() => import("./views/About/About"));
-const Travel = lazy(() => import("./views/Travel/Travel"));
-const NotFoundError = lazy(() =>
- import("./components/NotFoundError/NotFoundError").then((value) => ({
- default: value.NotFoundError,
- })),
-);
-const Kcd = lazy(() => import("./views/kcd/Kcd"));
-const Sponsorship = lazy(() => import("./views/sponsorship/Sponsorship"));
-const CfpSection = lazy(() => import("./views/Cfp/CfpSection"));
-const CodeOfConduct = lazy(() => import("./views/CodeOfConduct/CodeOfConduct"));
-const Accommodation = lazy(() =>
- import("./views/Travel/Accommodation").then((value) => ({
- default: value.Accommodation,
- })),
-);
-const Schedule = lazy(() => import("./views/Schedule/Schedule"));
-const Diversity = lazy(() =>
- import("./views/Diversity/Diversity").then((value) => ({
- default: value.Diversity,
- })),
-);
-const LiveView = lazy(() =>
- import("./views/Talks/LiveView").then((value) => ({
- default: value.LiveView,
- })),
-);
-
-const TalkDetailContainer = lazy(
- () => import("./views/MeetingDetail/TalkDetailContainer"),
-);
-
-const JobOffersList = lazy(() => import("@components/JobOffers/JobOffersList"));
-//
-//
-const HomeWrapper2024 = lazy(() =>
- import("./2024/HomeWrapper2024").then((value) => ({
- default: value.HomeWrapper2024,
- })),
-);
-const Speakers2024 = lazy(() =>
- import("./components/YearSpecific/Speakers/Speakers2024").then((value) => ({
- default: value.Speakers2024,
- })),
-);
-const SpeakerDetailContainer2024 = lazy(() =>
- import("./2024/SpeakerDetail/SpeakerDetailContainer2024").then((value) => ({
- default: value.SpeakerDetailContainer2024,
- })),
-);
-const CfpSection2024 = lazy(() => import("./2024/Cfp/CfpSection2024"));
-const Talks2024 = lazy(() => import("./2024/Talks/Talks2024"));
-const Workshops = lazy(() => import("./views/Workshops/Workshops"));
-const Schedule2024 = lazy(() => import("./2024/Schedule/Schedule2024"));
-const JobOffers2024 = lazy(() => import("./2024/JobOffers/JobOffers2024"));
-const MeetingDetailContainer2024 = lazy(() =>
- import("./2024/TalkDetail/MeetingDetailContainer2024").then((value) => ({
- default: value.MeetingDetailContainer2024,
- })),
-);
-//
-//
-const Home2023Wrapper = lazy(() => import("./2023/Home/Home2023Wrapper"));
-const Diversity2023 = lazy(() => import("./2023/Diversity/Diversity2023"));
-const Schedule2023 = lazy(() => import("./2023/Schedule/Schedule2023"));
-const Workshops2023 = lazy(() => import("./2023/Workshops/Workshops2023"));
-const JobOffers2023 = lazy(() => import("./2023/JobOffers/JobOffers2023"));
-const CfpSection2023 = lazy(() => import("./2023/Cfp/CfpSection2023"));
-const Speakers2023 = lazy(() => import("./2023/Speakers/Speakers2023"));
-const SpeakerDetailContainer2023 = lazy(
- () => import("./2023/SpeakerDetail/SpeakerDetailContainer2023"),
-);
-const Talks2023 = lazy(() => import("./2023/Talks/Talks2023"));
-const TalkDetailContainer2023 = lazy(
- () => import("./2023/TalkDetail/TalkDetailContainer2023"),
-);
-//
+import { getAllRoutes } from "@config/routeConfig";
const StyledAppWrapper = styled.div`
position: relative;
@@ -188,443 +46,28 @@ const RenderCookie = () => (
const App: FC> = () => {
const queryClient = new QueryClient();
+ const routes = getAllRoutes();
+
return (
- {/* */}
- } />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/*}>
-
- } />*/}
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {
- }>
-
-
- }
- />
- }
- {
- }>
-
-
- }
- />
- }
- }>
-
-
- }
- />
- */
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/*}>
-
- } />*/}
- {/*}>
-
- } />*/}
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/* */}
- {/* }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/* */}
- {/* }>
-
-
- }
- />
+ {routes.map((route, index) => {
+ const Component = route.component;
+ return (
+ }>
+
+
+ }
+ />
+ );
+ })}
{!isDevBcnCookieSet && }
diff --git a/src/components/Navigation/Navigation.tsx b/src/components/Navigation/Navigation.tsx
index ac574b5da..bc540602a 100644
--- a/src/components/Navigation/Navigation.tsx
+++ b/src/components/Navigation/Navigation.tsx
@@ -1,15 +1,19 @@
// @ts-expect-error some quirky import
import { AnimatePresence } from "framer-motion";
-import { FC, useEffect, useState } from "react";
+import React, { FC, useEffect, useState } from "react";
import { BIG_BREAKPOINT, MOBILE_BREAKPOINT } from "@constants/BreakPoints";
import { useLocation, useNavigate } from "react-router";
import Breadcrumbs from "./Breadcrumbs";
import { ROUTE_HOME, ROUTE_HOME_ALTERNATE } from "@constants/routes";
-import { navigationItems2025, subMenuItems2025 } from "./NavigationData";
+import { navigationItems2026, subMenuItems2026 } from "./NavigationData";
import {
navigationItems2023,
subMenuItems2023,
} from "../../2023/Navigation/NavigationData2023";
+import {
+ navigationItems2025 as navigationItems2025Archived,
+ subMenuItems2025 as subMenuItems2025Archived,
+} from "../../2025/Navigation/NavigationData2025";
import { useWindowSize } from "react-use";
import {
StyledClipPath,
@@ -30,12 +34,11 @@ import {
navigationItems2024,
subMenuItems2024,
} from "../../2024/Navigation/NavigationData";
-
export const Navigation: FC> = () => {
const { width } = useWindowSize();
const [isOpened, setIsOpened] = useState(false);
- const [navItems, setNavItems] = useState(navigationItems2025);
- const [subNavItems, setSubNavItems] = useState(subMenuItems2025);
+ const [navItems, setNavItems] = useState(navigationItems2026);
+ const [subNavItems, setSubNavItems] = useState(subMenuItems2026);
const { pathname } = useLocation();
const navigate = useNavigate();
const handleLogoClick = () => {
@@ -47,7 +50,7 @@ export const Navigation: FC> = () => {
const isHomePage = () => {
return pathname === ROUTE_HOME || pathname === ROUTE_HOME_ALTERNATE;
};
-
+ // ...
useEffect(() => {
const navMapping = {
"/2024": {
@@ -58,9 +61,17 @@ export const Navigation: FC> = () => {
navItems: navigationItems2023,
subNavItems: subMenuItems2023,
},
+ "/2025": {
+ navItems: navigationItems2025Archived,
+ subNavItems: subMenuItems2025Archived,
+ },
+ "/2026": {
+ navItems: navigationItems2026,
+ subNavItems: subMenuItems2026,
+ },
default: {
- navItems: navigationItems2025,
- subNavItems: subMenuItems2025,
+ navItems: navigationItems2026,
+ subNavItems: subMenuItems2026,
},
};
@@ -80,7 +91,10 @@ export const Navigation: FC> = () => {
if (pathname.startsWith("/2024")) {
return "https://tickets.devbcn.com/event/devbcn-2024";
}
- return "https://tickets.devbcn.com/event/devbcn-2025";
+ if (pathname.startsWith("/2025")) {
+ return "https://tickets.devbcn.com/event/devbcn-2025";
+ }
+ return "https://tickets.devbcn.com/event/devbcn-2026";
};
return (
diff --git a/src/components/Navigation/NavigationData.ts b/src/components/Navigation/NavigationData.ts
index 377f86748..ed9c23a80 100644
--- a/src/components/Navigation/NavigationData.ts
+++ b/src/components/Navigation/NavigationData.ts
@@ -1,16 +1,14 @@
import {
+ ROUTE_2026_CODE_OF_CONDUCT,
+ ROUTE_2026_HOME,
+ ROUTE_2026_JOB_OFFERS,
+ ROUTE_2026_SCHEDULE,
+ ROUTE_2026_SPEAKERS,
+ ROUTE_2026_SPONSORSHIP,
+ ROUTE_2026_TALKS,
+ ROUTE_2026_TRAVEL,
+ ROUTE_2026_WORKSHOPS,
ROUTE_ABOUT_US,
- ROUTE_CFP,
- ROUTE_CODE_OF_CONDUCT,
- ROUTE_DIVERSITY,
- ROUTE_HOME,
- ROUTE_JOB_OFFERS,
- ROUTE_SCHEDULE,
- ROUTE_SPEAKERS,
- ROUTE_SPONSORSHIP,
- ROUTE_TALKS,
- ROUTE_TRAVEL,
- ROUTE_WORKSHOPS,
} from "@constants/routes";
export interface NavigationItem {
@@ -18,26 +16,32 @@ export interface NavigationItem {
link: string;
}
-export const navigationItems2025: NavigationItem[] = [
- { id: "Home", link: ROUTE_HOME },
- { id: "Code of Conduct", link: ROUTE_CODE_OF_CONDUCT },
- { id: "Sponsors", link: "/#sponsors" },
- { id: "SCHEDULE", link: ROUTE_SCHEDULE },
- { id: "Talks", link: ROUTE_TALKS },
- { id: "Workshops", link: ROUTE_WORKSHOPS },
- { id: "JOB OFFERS", link: ROUTE_JOB_OFFERS },
+export const navigationItems2026: NavigationItem[] = [
+ { id: "Home", link: ROUTE_2026_HOME },
+ { id: "Code of Conduct", link: ROUTE_2026_CODE_OF_CONDUCT },
+ { id: "Sponsors", link: "/2026/#sponsors" },
+ { id: "SCHEDULE", link: ROUTE_2026_SCHEDULE },
+ { id: "Talks", link: ROUTE_2026_TALKS },
+ { id: "Workshops", link: ROUTE_2026_WORKSHOPS },
+ { id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
//{ id: "Communities", link: ROUTE_COMMUNITIES },
- { id: "Speakers", link: ROUTE_SPEAKERS },
+ { id: "Speakers", link: ROUTE_2026_SPEAKERS },
{ id: "About Us", link: ROUTE_ABOUT_US },
- { id: "Travel", link: ROUTE_TRAVEL },
- { id: "Sponsorship", link: ROUTE_SPONSORSHIP },
+ { id: "Travel", link: ROUTE_2026_TRAVEL },
+ { id: "Sponsorship", link: ROUTE_2026_SPONSORSHIP },
];
-export const subMenuItems2025: NavigationItem[] = [
- { id: "DIVERSITY", link: ROUTE_DIVERSITY },
- { id: "Cfp Committee", link: ROUTE_CFP },
- //{ id: "Accommodation", link: ROUTE_ACCOMMODATION },
- //{ id: "Attendee information", link: ROUTE_ATTENDEE },
- //{ id: "Speaker information", link: ROUTE_SPEAKER_INFO },
- //{ id: "Session feedback", link: ROUTE_SESSION_FEEDBACK },
+export const subMenuItems2026: NavigationItem[] = [
+ { id: "Home", link: ROUTE_2026_HOME },
+ { id: "Code of Conduct", link: ROUTE_2026_CODE_OF_CONDUCT },
+ { id: "Sponsors", link: "/2026/#sponsors" },
+ { id: "SCHEDULE", link: ROUTE_2026_SCHEDULE },
+ { id: "Talks", link: ROUTE_2026_TALKS },
+ { id: "Workshops", link: ROUTE_2026_WORKSHOPS },
+ { id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
+ //{ id: "Communities", link: ROUTE_COMMUNITIES },
+ { id: "Speakers", link: ROUTE_2026_SPEAKERS },
+ { id: "About Us", link: ROUTE_ABOUT_US },
+ { id: "Travel", link: ROUTE_2026_TRAVEL },
+ { id: "Sponsorship", link: ROUTE_2026_SPONSORSHIP },
];
diff --git a/src/components/Router/RouteRenderer.tsx b/src/components/Router/RouteRenderer.tsx
new file mode 100644
index 000000000..ff86b5120
--- /dev/null
+++ b/src/components/Router/RouteRenderer.tsx
@@ -0,0 +1,34 @@
+import React, { FC, Suspense } from "react";
+import { Route } from "react-router";
+import { RouteConfig } from "../../config/routeConfig";
+import { Loading } from "@components/Loading/Loading";
+
+interface RouteRendererProps {
+ routes: RouteConfig[];
+ fallback?: React.ReactElement;
+}
+
+/**
+ * Dynamically renders routes from configuration
+ * Automatically wraps each route with Suspense
+ */
+export const RouteRenderer: FC = ({ routes, fallback = }) => {
+ return (
+ <>
+ {routes.map((route, index) => {
+ const Component = route.component;
+ return (
+
+
+
+ }
+ />
+ );
+ })}
+ >
+ );
+};
diff --git a/src/components/Router/SuspenseRoute.tsx b/src/components/Router/SuspenseRoute.tsx
new file mode 100644
index 000000000..2860132ea
--- /dev/null
+++ b/src/components/Router/SuspenseRoute.tsx
@@ -0,0 +1,26 @@
+import React, { FC, ReactElement, Suspense } from "react";
+import { Route, RouteProps } from "react-router";
+import { Loading } from "@components/Loading/Loading";
+
+interface SuspenseRouteProps {
+ path: string;
+ element: ReactElement;
+ fallback?: ReactElement;
+}
+
+/**
+ * Wrapper component that combines Route with Suspense
+ * Eliminates the need to manually wrap every route with React.Suspense
+ */
+export const SuspenseRoute: FC = ({
+ path,
+ element,
+ fallback = ,
+}) => {
+ return (
+ {element}}
+ />
+ );
+};
diff --git a/src/config/routeConfig.ts b/src/config/routeConfig.ts
new file mode 100644
index 000000000..1cada9e0e
--- /dev/null
+++ b/src/config/routeConfig.ts
@@ -0,0 +1,131 @@
+import * as routes from "@constants/routes";
+import { yearRouteConfigs } from "./yearRoutes";
+import * as LazyComponents from "../utils/lazyComponents";
+
+/**
+ * Interface for a single route configuration
+ */
+export interface RouteConfig {
+ path: string;
+ component: React.LazyExoticComponent> | React.ComponentType;
+ exact?: boolean;
+}
+
+/**
+ * Generate routes for a specific year
+ */
+export const generateYearRoutes = (year: string): RouteConfig[] => {
+ const yearConfig = yearRouteConfigs[year];
+ if (!yearConfig) return [];
+
+ const routesList: RouteConfig[] = [
+ { path: `/${year}`, component: yearConfig.home },
+ ];
+
+ // Add optional routes if they exist
+ if (yearConfig.schedule) {
+ routesList.push({ path: `/${year}/schedule`, component: yearConfig.schedule });
+ }
+ if (yearConfig.speakers) {
+ routesList.push({ path: `/${year}/speakers`, component: yearConfig.speakers });
+ }
+ if (yearConfig.talks) {
+ routesList.push({ path: `/${year}/talks`, component: yearConfig.talks });
+ }
+ if (yearConfig.workshops) {
+ routesList.push({ path: `/${year}/workshops`, component: yearConfig.workshops });
+ }
+ if (yearConfig.jobOffers) {
+ routesList.push({ path: `/${year}/jobOffers`, component: yearConfig.jobOffers });
+ }
+ if (yearConfig.diversity) {
+ routesList.push({ path: `/${year}/diversity`, component: yearConfig.diversity });
+ }
+ if (yearConfig.cfp) {
+ routesList.push({ path: `/${year}/cfp`, component: yearConfig.cfp });
+ }
+ if (yearConfig.speakerInfo) {
+ routesList.push({
+ path: `/${year}/speaker-information`,
+ component: yearConfig.speakerInfo,
+ });
+ }
+ if (yearConfig.speakerDetail) {
+ routesList.push({
+ path: `/${year}/speaker/:id`,
+ component: yearConfig.speakerDetail,
+ });
+ }
+ if (yearConfig.talkDetail) {
+ routesList.push({ path: `/${year}/talk/:id`, component: yearConfig.talkDetail });
+ }
+ if (yearConfig.sessionFeedback) {
+ routesList.push({
+ path: `/${year}/session-feedback`,
+ component: yearConfig.sessionFeedback,
+ });
+ }
+ if (yearConfig.attendee) {
+ routesList.push({ path: `/${year}/attendee`, component: yearConfig.attendee });
+ }
+ if (yearConfig.communities) {
+ routesList.push({ path: `/${year}/communities`, component: yearConfig.communities });
+ }
+
+ return routesList;
+};
+
+/**
+ * Shared/common routes that don't belong to a specific year
+ */
+export const sharedRoutes: RouteConfig[] = [
+ { path: "/loading", component: LazyComponents.Loading },
+ { path: routes.ROUTE_TALKS, component: LazyComponents.Talks },
+ { path: routes.ROUTE_WORKSHOPS, component: LazyComponents.Workshops },
+ { path: routes.ROUTE_CODE_OF_CONDUCT, component: LazyComponents.CodeOfConduct },
+ { path: routes.ROUTE_CONDITIONS, component: LazyComponents.Conditions },
+ { path: "live-view", component: LazyComponents.LiveView },
+ { path: routes.ROUTE_COOKIES, component: LazyComponents.Cookies },
+ { path: routes.ROUTE_DIVERSITY, component: LazyComponents.Diversity },
+ { path: routes.ROUTE_JOB_OFFERS, component: LazyComponents.JobOffersList },
+ { path: routes.ROUTE_SCHEDULE, component: LazyComponents.Schedule },
+ { path: routes.ROUTE_SPEAKERS, component: LazyComponents.Speakers },
+ { path: routes.ROUTE_SPEAKER_INFO, component: LazyComponents.SpeakerInformation },
+ { path: routes.ROUTE_ABOUT_US, component: LazyComponents.About },
+ { path: routes.ROUTE_CFP, component: LazyComponents.CfpSection },
+ { path: routes.ROUTE_TRAVEL, component: LazyComponents.Travel },
+ { path: routes.ROUTE_KCD, component: LazyComponents.Kcd },
+ { path: routes.ROUTE_MEETING_DETAIL_PLAIN, component: LazyComponents.TalkDetailContainer },
+ { path: routes.ROUTE_SPEAKER_DETAIL_PLAIN, component: LazyComponents.SpeakerDetailContainer },
+ { path: routes.ROUTE_SPONSORSHIP, component: LazyComponents.Sponsorship },
+ { path: "/:year", component: LazyComponents.HomeWrapper },
+ { path: routes.ROUTE_ACCOMMODATION, component: LazyComponents.Accommodation },
+ { path: routes.ROUTE_HOME, component: LazyComponents.HomeWrapper2026 },
+ // 404 catch-all - must be last
+ { path: "*", component: LazyComponents.NotFoundError },
+];
+
+/**
+ * Get all route configurations
+ */
+export const getAllRoutes = (): RouteConfig[] => {
+ const allRoutes: RouteConfig[] = [];
+
+ // Add shared routes first (except 404)
+ const sharedWithout404 = sharedRoutes.filter((r) => r.path !== "*");
+ allRoutes.push(...sharedWithout404);
+
+ // Add year-specific routes
+ const years = ["2026", "2025", "2024", "2023"];
+ years.forEach((year) => {
+ allRoutes.push(...generateYearRoutes(year));
+ });
+
+ // Add 404 route last
+ const notFound = sharedRoutes.find((r) => r.path === "*");
+ if (notFound) {
+ allRoutes.push(notFound);
+ }
+
+ return allRoutes;
+};
diff --git a/src/config/yearRoutes.ts b/src/config/yearRoutes.ts
new file mode 100644
index 000000000..e6f63af5b
--- /dev/null
+++ b/src/config/yearRoutes.ts
@@ -0,0 +1,96 @@
+import { LazyExoticComponent, ComponentType } from "react";
+import * as LazyComponents from "../utils/lazyComponents";
+
+/**
+ * Route configuration for a specific year
+ * Maps route names to their lazy-loaded components
+ */
+export interface YearRouteComponents {
+ home: LazyExoticComponent>;
+ schedule?: LazyExoticComponent>;
+ speakers?: LazyExoticComponent>;
+ talks?: LazyExoticComponent>;
+ workshops?: LazyExoticComponent>;
+ jobOffers?: LazyExoticComponent>;
+ diversity?: LazyExoticComponent>;
+ cfp?: LazyExoticComponent>;
+ speakerInfo?: LazyExoticComponent>;
+ speakerDetail?: LazyExoticComponent>;
+ talkDetail?: LazyExoticComponent>;
+ sessionFeedback?: LazyExoticComponent>;
+ attendee?: LazyExoticComponent>;
+ communities?: LazyExoticComponent>;
+}
+
+/**
+ * Year-specific route configurations
+ * Each year maps to its specific components
+ */
+export const yearRouteConfigs: Record = {
+ "2026": {
+ home: LazyComponents.HomeWrapper2026,
+ schedule: LazyComponents.Schedule2026,
+ speakers: LazyComponents.Speakers2026,
+ talks: LazyComponents.Talks2026,
+ workshops: LazyComponents.Workshops2026,
+ jobOffers: LazyComponents.JobOffersList2026,
+ diversity: LazyComponents.Diversity2026,
+ cfp: LazyComponents.CfpSection2026,
+ speakerInfo: LazyComponents.SpeakerInformation2026,
+ speakerDetail: LazyComponents.SpeakerDetailContainer2026,
+ talkDetail: LazyComponents.TalkDetailContainer2026,
+ },
+ "2025": {
+ home: LazyComponents.HomeWrapper2025,
+ schedule: LazyComponents.Schedule2025,
+ speakers: LazyComponents.Speakers2025,
+ talks: LazyComponents.Talks2025,
+ workshops: LazyComponents.Workshops2025,
+ jobOffers: LazyComponents.JobOffers2025,
+ diversity: LazyComponents.Diversity2025,
+ cfp: LazyComponents.CfpSection2025,
+ speakerInfo: LazyComponents.SpeakerInformation2025,
+ speakerDetail: LazyComponents.SpeakerDetailContainer2025,
+ talkDetail: LazyComponents.TalkDetailContainer2025,
+ sessionFeedback: LazyComponents.SessionFeedback2025,
+ attendee: LazyComponents.AttendeeInformation2025,
+ communities: LazyComponents.Communities2025,
+ },
+ "2024": {
+ home: LazyComponents.HomeWrapper2024,
+ schedule: LazyComponents.Schedule2024,
+ speakers: LazyComponents.Speakers2024,
+ talks: LazyComponents.Talks2024,
+ workshops: LazyComponents.Workshops,
+ jobOffers: LazyComponents.JobOffers2024,
+ diversity: LazyComponents.Diversity,
+ cfp: LazyComponents.CfpSection2024,
+ speakerInfo: LazyComponents.SpeakerInformation2023, // Reusing 2023
+ speakerDetail: LazyComponents.SpeakerDetailContainer2024,
+ talkDetail: LazyComponents.MeetingDetailContainer2024,
+ sessionFeedback: LazyComponents.SessionFeedback2023,
+ attendee: LazyComponents.AttendeeInformation2023,
+ communities: LazyComponents.Communities2023,
+ },
+ "2023": {
+ home: LazyComponents.Home2023Wrapper,
+ diversity: LazyComponents.Diversity2023,
+ schedule: LazyComponents.Schedule2023,
+ workshops: LazyComponents.Workshops2023,
+ jobOffers: LazyComponents.JobOffers2023,
+ cfp: LazyComponents.CfpSection2023,
+ speakers: LazyComponents.Speakers2023,
+ speakerDetail: LazyComponents.SpeakerDetailContainer2023,
+ talks: LazyComponents.Talks2023,
+ talkDetail: LazyComponents.TalkDetailContainer2023,
+ sessionFeedback: LazyComponents.SessionFeedback2023,
+ attendee: LazyComponents.AttendeeInformation2023,
+ communities: LazyComponents.Communities2023,
+ speakerInfo: LazyComponents.SpeakerInformation2023,
+ },
+};
+
+/**
+ * List of years with route configurations
+ */
+export const CONFIGURED_YEARS = Object.keys(yearRouteConfigs);
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index 722f1fee9..b189cce12 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -26,6 +26,47 @@ export const ROUTE_MEETING_DETAIL_PLAIN = "/talk/:id";
export const ROUTE_TRAVEL = "/travel";
export const ROUTE_ACCOMMODATION = "/accommodation";
+// 2026
+
+export const ROUTE_2026_ATTENDEE = "/2026/attendee";
+export const ROUTE_2026_CFP = "/2026/cfp";
+export const ROUTE_2026_COMMUNITIES = "/2026/communities";
+export const ROUTE_2026_DIVERSITY = "/2026/diversity";
+export const ROUTE_2026_HOME = "/2026";
+export const ROUTE_2026_JOB_OFFERS = "/2026/jobOffers";
+export const ROUTE_2026_SCHEDULE = "/2026/schedule";
+export const ROUTE_2026_SESSION_FEEDBACK = "/2026/session-feedback";
+export const ROUTE_2026_SPEAKERS = "/2026/speakers";
+export const ROUTE_2026_SPEAKER_DETAIL = "/2026/speaker";
+export const ROUTE_2026_SPEAKER_DETAIL_PLAIN = "/2026/speaker/:id";
+export const ROUTE_2026_SPEAKER_INFO = "/2026/speaker-information";
+export const ROUTE_2026_TALKS = "/2026/talks";
+export const ROUTE_2026_TALK_DETAIL = "/2026/talk";
+export const ROUTE_2026_TALK_DETAIL_PLAIN = "/2026/talk/:id";
+export const ROUTE_2026_CODE_OF_CONDUCT = "/2026/codeOfConduct";
+export const ROUTE_2026_SPONSORSHIP = "/2026/sponsorship";
+export const ROUTE_2026_TRAVEL = "/2026/travel";
+export const ROUTE_2026_WORKSHOPS = "/2026/workshops";
+
+// 2025
+
+export const ROUTE_2025_ATTENDEE = "/2025/attendee";
+export const ROUTE_2025_CFP = "/2025/cfp";
+export const ROUTE_2025_COMMUNITIES = "/2025/communities";
+export const ROUTE_2025_DIVERSITY = "/2025/diversity";
+export const ROUTE_2025_HOME = "/2025";
+export const ROUTE_2025_JOB_OFFERS = "/2025/jobOffers";
+export const ROUTE_2025_SCHEDULE = "/2025/schedule";
+export const ROUTE_2025_SESSION_FEEDBACK = "/2025/session-feedback";
+export const ROUTE_2025_SPEAKERS = "/2025/speakers";
+export const ROUTE_2025_SPEAKER_DETAIL = "/2025/speaker";
+export const ROUTE_2025_SPEAKER_DETAIL_PLAIN = "/2025/speaker/:id";
+export const ROUTE_2025_SPEAKER_INFO = "/2025/speaker-information";
+export const ROUTE_2025_TALKS = "/2025/talks";
+export const ROUTE_2025_TALK_DETAIL = "/2025/talk";
+export const ROUTE_2025_TALK_DETAIL_PLAIN = "/2025/talk/:id";
+export const ROUTE_2025_WORKSHOPS = "/2025/workshops";
+
// 2023
export const ROUTE_2023_ATTENDEE = "/2023/attendee";
diff --git a/src/data/2026.json b/src/data/2026.json
new file mode 100644
index 000000000..7dd1630c4
--- /dev/null
+++ b/src/data/2026.json
@@ -0,0 +1,45 @@
+{
+ "actionButtons": true,
+ "carrousel": {
+ "enabled": false
+ },
+ "cfp": {
+ "startDay": "2026-01-01T00:00:00+01:00",
+ "endDay": "2026-03-01T00:00:00+01:00",
+ "link": "https://sessionize.com/devbcn26/"
+ },
+ "diversity": false,
+ "edition": "2026",
+ "email": "info@devbcn.com",
+ "endDay": "2026-06-17T19:00:00+01:00",
+ "facebook": "https://facebook.com/devbcn",
+ "flickr": "https://flickr.com/devbcn",
+ "github": "https://github.com/devbcn",
+ "hideSpeakers": true,
+ "hideTalks": true,
+ "jobOffers": {
+ "enabled": false
+ },
+ "linkedin": "https://www.linkedin.com/company/devbcn",
+ "openFeedbackId": "devbcn26",
+ "schedule": {
+ "enabled": false
+ },
+ "sessionizeUrl": "",
+ "showCountdown": true,
+ "showInfoButtons": false,
+ "sponsors": {
+ "startDate": "2025-12-01T09:00:00+01:00",
+ "endDate": "2026-06-18T09:00:00+01:00"
+ },
+ "startDay": "2026-06-16T08:00:00+01:00",
+ "tickets": {
+ "startDay": "2026-02-01T00:00:00+01:00",
+ "endDay": "2026-06-17T00:00:00+01:00"
+ },
+ "title": "DevBcn - Barcelona Developers Conference ",
+ "trackNumber": 5,
+ "tracks": "Java & JVM | Cloud, DevOps, VMs, Kubernetes | Frontend, JavaScript, TypeScript, Angular, WASM | Leadership, Agile, Diversity | Big Data, Machine Learning, AI, Python",
+ "twitter": "https://twitter.com/dev_bcn",
+ "youtube": "https://www.youtube.com/dev_bcn"
+}
\ No newline at end of file
diff --git a/src/utils/lazyComponents.ts b/src/utils/lazyComponents.ts
new file mode 100644
index 000000000..3399616af
--- /dev/null
+++ b/src/utils/lazyComponents.ts
@@ -0,0 +1,172 @@
+import { lazy } from "react";
+
+// ============================================
+// SHARED COMPONENTS (No year prefix)
+// ============================================
+
+export const Loading = lazy(() =>
+ import("@components/Loading/Loading").then((value) => ({
+ default: value.Loading,
+ }))
+);
+
+export const NotFoundError = lazy(() =>
+ import("../components/NotFoundError/NotFoundError").then((value) => ({
+ default: value.NotFoundError,
+ }))
+);
+
+export const HomeWrapper = lazy(() => import("../views/Home/HomeWrapper").then((value) => ({
+ default: value.HomeWrapper,
+})));
+
+export const About = lazy(() => import("../views/About/About"));
+export const Accommodation = lazy(() =>
+ import("../views/Travel/Accommodation").then((value) => ({
+ default: value.Accommodation,
+ }))
+);
+export const CfpSection = lazy(() => import("../views/Cfp/CfpSection"));
+export const CodeOfConduct = lazy(() => import("../views/CodeOfConduct/CodeOfConduct"));
+export const Conditions = lazy(() => import("../views/Conditions/Conditions"));
+export const Cookies = lazy(() => import("../views/Cookies/Cookies"));
+export const Diversity = lazy(() =>
+ import("../views/Diversity/Diversity").then((value) => ({
+ default: value.Diversity,
+ }))
+);
+export const JobOffersList = lazy(() => import("@components/JobOffers/JobOffersList"));
+export const Kcd = lazy(() => import("../views/kcd/Kcd"));
+export const LiveView = lazy(() =>
+ import("../views/Talks/LiveView").then((value) => ({
+ default: value.LiveView,
+ }))
+);
+export const Schedule = lazy(() => import("../views/Schedule/Schedule"));
+export const Speakers = lazy(() => import("../views/Speakers/Speakers"));
+export const SpeakerDetailContainer = lazy(() =>
+ import("../views/SpeakerDetail/SpeakerDetailContainer").then((module) => ({
+ default: module.SpeakerDetailContainer,
+ }))
+);
+export const SpeakerInformation = lazy(() =>
+ import("../views/Speakers/SpeakerInformation").then((value) => ({
+ default: value.SpeakerInformation,
+ }))
+);
+export const Sponsorship = lazy(() => import("../views/sponsorship/Sponsorship"));
+export const TalkDetailContainer = lazy(
+ () => import("../views/MeetingDetail/TalkDetailContainer")
+);
+export const Talks = lazy(() => import("../views/Talks/Talks"));
+export const Travel = lazy(() => import("../views/Travel/Travel"));
+export const Workshops = lazy(() => import("../views/Workshops/Workshops"));
+
+// ============================================
+// 2026 COMPONENTS
+// ============================================
+
+// 2026 uses mostly shared components, just re-export them with year suffix for consistency
+export const HomeWrapper2026 = HomeWrapper;
+export const Schedule2026 = Schedule;
+export const Speakers2026 = Speakers;
+export const Talks2026 = Talks;
+export const Workshops2026 = Workshops;
+export const JobOffersList2026 = JobOffersList;
+export const Diversity2026 = Diversity;
+export const CfpSection2026 = CfpSection;
+export const SpeakerInformation2026 = SpeakerInformation;
+export const SpeakerDetailContainer2026 = SpeakerDetailContainer;
+export const TalkDetailContainer2026 = TalkDetailContainer;
+
+// ============================================
+// 2025 COMPONENTS
+// ============================================
+
+export const HomeWrapper2025 = lazy(() =>
+ import("../2025/Home/HomeWrapper2025").then((value) => ({
+ default: value.HomeWrapper2025,
+ }))
+);
+export const Schedule2025 = lazy(() => import("../views/Schedule/Schedule"));
+export const Speakers2025 = lazy(() => import("../views/Speakers/Speakers"));
+export const Talks2025 = lazy(() => import("../views/Talks/Talks"));
+export const SpeakerInformation2025 = lazy(() =>
+ import("../views/Speakers/SpeakerInformation").then((module) => ({
+ default: module.SpeakerInformation,
+ }))
+);
+export const SpeakerDetailContainer2025 = lazy(() =>
+ import("../views/SpeakerDetail/SpeakerDetailContainer").then((module) => ({
+ default: module.SpeakerDetailContainer,
+ }))
+);
+export const TalkDetailContainer2025 = lazy(() =>
+ import("../views/MeetingDetail/TalkDetailContainer")
+);
+export const Workshops2025 = lazy(() => import("../views/Workshops/Workshops"));
+export const JobOffers2025 = lazy(() => import("@components/JobOffers/JobOffersList"));
+export const Diversity2025 = lazy(() =>
+ import("../views/Diversity/Diversity").then((module) => ({
+ default: module.Diversity,
+ }))
+);
+export const CfpSection2025 = lazy(() => import("../views/Cfp/CfpSection"));
+export const SessionFeedback2025 = lazy(() => import("../2023/SessionFeedback/SessionFeedback2023"));
+export const AttendeeInformation2025 = lazy(() => import("../2023/Attendee/AttendeeInformation2023"));
+export const Communities2025 = lazy(() => import("../2023/Communities/Communities2023"));
+
+// ============================================
+// 2024 COMPONENTS
+// ============================================
+
+export const HomeWrapper2024 = lazy(() =>
+ import("../2024/HomeWrapper2024").then((value) => ({
+ default: value.HomeWrapper2024,
+ }))
+);
+export const Speakers2024 = lazy(() =>
+ import("../components/YearSpecific/Speakers/Speakers2024").then((value) => ({
+ default: value.Speakers2024,
+ }))
+);
+export const SpeakerDetailContainer2024 = lazy(() =>
+ import("../2024/SpeakerDetail/SpeakerDetailContainer2024").then((value) => ({
+ default: value.SpeakerDetailContainer2024,
+ }))
+);
+export const CfpSection2024 = lazy(() => import("../2024/Cfp/CfpSection2024"));
+export const Talks2024 = lazy(() => import("../2024/Talks/Talks2024"));
+export const Schedule2024 = lazy(() => import("../2024/Schedule/Schedule2024"));
+export const JobOffers2024 = lazy(() => import("../2024/JobOffers/JobOffers2024"));
+export const MeetingDetailContainer2024 = lazy(() =>
+ import("../2024/TalkDetail/MeetingDetailContainer2024").then((value) => ({
+ default: value.MeetingDetailContainer2024,
+ }))
+);
+
+// ============================================
+// 2023 COMPONENTS
+// ============================================
+
+export const Home2023Wrapper = lazy(() => import("../2023/Home/Home2023Wrapper"));
+export const Diversity2023 = lazy(() => import("../2023/Diversity/Diversity2023"));
+export const Schedule2023 = lazy(() => import("../2023/Schedule/Schedule2023"));
+export const Workshops2023 = lazy(() => import("../2023/Workshops/Workshops2023"));
+export const JobOffers2023 = lazy(() => import("../2023/JobOffers/JobOffers2023"));
+export const CfpSection2023 = lazy(() => import("../2023/Cfp/CfpSection2023"));
+export const Speakers2023 = lazy(() => import("../2023/Speakers/Speakers2023"));
+export const SpeakerDetailContainer2023 = lazy(
+ () => import("../2023/SpeakerDetail/SpeakerDetailContainer2023")
+);
+export const Talks2023 = lazy(() => import("../2023/Talks/Talks2023"));
+export const TalkDetailContainer2023 = lazy(
+ () => import("../2023/TalkDetail/TalkDetailContainer2023")
+);
+export const SessionFeedback2023 = lazy(() => import("../2023/SessionFeedback/SessionFeedback2023"));
+export const AttendeeInformation2023 = lazy(() => import("../2023/Attendee/AttendeeInformation2023"));
+export const Communities2023 = lazy(() => import("../2023/Communities/Communities2023"));
+export const SpeakerInformation2023 = lazy(() => import("../2023/Speakers/SpeakerInformation2023"));
+
+// QR Code (appears to be 2024-specific)
+export const QrCodeSection = lazy(() => import("../views/QrCode/QrCodeSection"));
diff --git a/src/views/Cfp/CfpSection.test.tsx b/src/views/Cfp/CfpSection.test.tsx
index 4cee0f4b8..a75b93746 100644
--- a/src/views/Cfp/CfpSection.test.tsx
+++ b/src/views/Cfp/CfpSection.test.tsx
@@ -1,9 +1,9 @@
import React from "react";
-import {render, screen} from "@testing-library/react";
+import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import CfpSection from "./CfpSection";
-import conferenceData from "../../data/2025.json";
-import {data} from "./CfpData";
+import conferenceData from "../../data/2026.json";
+import { data } from "./CfpData";
describe("CfpSection", () => {
it("sets document title on mount", () => {
diff --git a/src/views/Home/HomeWrapper.tsx b/src/views/Home/HomeWrapper.tsx
index 76163ef55..85dbdfd4f 100644
--- a/src/views/Home/HomeWrapper.tsx
+++ b/src/views/Home/HomeWrapper.tsx
@@ -4,11 +4,11 @@ import Faqs from "./components/Faqs/Faqs";
import Home from "./components/Home/Home";
import Sponsors from "./components/Sponsors/Sponsors";
import { styled } from "styled-components";
-import conferenceData from "@data/2025.json";
+import conferenceData from "@data/2026.json";
import { useLocation } from "react-router";
import SpeakersCarousel from "@components/Swiper/SpeakersCarousel";
-import { ROUTE_SPEAKERS } from "@constants/routes";
+import { ROUTE_2026_SPEAKERS } from "@constants/routes";
import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
const StyledContainer = styled.div`
@@ -29,7 +29,7 @@ export const HomeWrapper: FC> = () => {
}
}, [hash]);
- useDocumentTitleUpdater("Home", conferenceData?.edition ?? "2025");
+ useDocumentTitleUpdater("Home", conferenceData?.edition ?? "2026");
return (
@@ -37,7 +37,7 @@ export const HomeWrapper: FC> = () => {
{conferenceData?.carrousel.enabled && (
)}
diff --git a/src/views/Home/UseEventEdition.tsx b/src/views/Home/UseEventEdition.tsx
index d030b7733..d6054addb 100644
--- a/src/views/Home/UseEventEdition.tsx
+++ b/src/views/Home/UseEventEdition.tsx
@@ -7,7 +7,7 @@ export function useEventEdition(setEdition: (data: Edition) => void) {
useEffect(() => {
// Fallback to the current year if no year is provided in the URL
- const editionYear = year ?? "2025";
+ const editionYear = year ?? "2026";
import(`../../data/${editionYear}.json`)
.then((data) => {
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index 58f42eabf..f339ebfbf 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -25,7 +25,7 @@ import { Color } from "@styles/colors";
import InfoButtons from "../InfoButtons/InfoButtons";
import { formatDateRange } from "./DateUtil";
import { Link } from "react-router";
-import edition from "@data/2025.json";
+import edition from "@data/2026.json";
import CountDownCompleted from "./components/CountDownCompleted";
const Home: FC> = () => {
@@ -68,7 +68,8 @@ const Home: FC> = () => {
transition={{ duration: 0.5, delay: 1 }}
>
- Past events: 2024 edition |{" "}
+ Past events: 2025 edition |{" "}
+ 2024 edition| {" "}
2023 edition
diff --git a/src/views/Home/components/Sponsors/SponsorsData.ts b/src/views/Home/components/Sponsors/SponsorsData.ts
index 3c867d42c..73965a28c 100644
--- a/src/views/Home/components/Sponsors/SponsorsData.ts
+++ b/src/views/Home/components/Sponsors/SponsorsData.ts
@@ -16,147 +16,10 @@ export interface Sponsor {
export const sponsors: Sponsors = {
top: [],
- premium: [
- {
- name: "Dynatrace",
- image: "images/sponsors/dynatrace.png",
- website: "https://www.dynatrace.com/",
- },
- ],
- regular: [
- {
- name: "Caixabank Tech",
- website: "https://www.caixabanktech.com/es/pagina-de-inicio/",
- image: "/images/sponsors/caixabank-tech.png",
- },
- {
- name: "Sopra Steria",
- image: "/images/sponsors/sopra.png",
- website: "https://www.soprasteria.es/",
- },
- {
- name: "Elastic",
- image: "/images/sponsors/logo-elastic-horizontal-color.png",
- website: "https://www.elastic.co/",
- },
- {
- name: "Manychat",
- website: "https://careers.manychat.com/",
- image: "/images/sponsors/logo-manychat.webp",
- },
- {
- name: "Snowflake",
- website: "https://www.snowflake.com/en/developers/",
- image: "/images/sponsors/logo-snowflake.png",
- },
- {
- name: "Clever Cloud",
- image: "/images/sponsors/clever-cloud.png",
- website: "https://www.clever-cloud.com/",
- },
- {
- name: "Vonage",
- website: "https://vonage.dev/DevBcn",
- image: "/images/sponsors/vonage.jpg",
- },
- {
- name: "NUBANK",
- image: "/images/sponsors/datomic.svg",
- website: "https://nubank.com.br/",
- },
- ],
- basic: [
- {
- name: "Seidor",
- website: "https://www.opentrends.net/en",
- image: "/images/sponsors/seidor.png",
- },
- {
- name: "Grupo Castilla",
- image: "/images/sponsors/grupo-castilla.png",
- website:
- "https://www.grupocastilla.es/servicios-rrhh/consultoria-tecnologica/",
- },
- {
- name: "FOR GOOD AI",
- website: "https://zencoder.ai/",
- image: "/images/sponsors/zencoder.png",
- },
- {
- name: "ORTUS SOLUTIONS",
- website: "https://boxlang.io/",
- image: "/images/sponsors/boxlang.png",
- },
- {
- name: "Preply",
- website: "https://preply.com/en/careers",
- image: "/images/sponsors/preply.svg",
- },
- {
- name: "Dow Jones",
- image: "/images/sponsors/dow-jones.png",
- website: "https://www.dowjones.com/",
- },
- {
- name: "Azul",
- image: "images/sponsors/azul.png",
- website: "https://www.azul.com",
- },
- {
- name: "Glovo",
- website:
- "https://jobs.glovoapp.com/departments/engineering-2/?d=engineering&l=barcelona-hq",
- image: "images/sponsors/glovo.png",
- },
- ],
- communities: [
- {
- name: "Step4ward",
- image: "images/sponsors/step4ward.png",
- website: "https://bit.ly/step4wardhome",
- },
- {
- name: "Migracode Barcelona",
- image: "images/sponsors/migracode.jpg",
- website: "https://www.migracode.org/",
- },
- {
- name: "CodeWomen+",
- image: "/images/sponsors/codewomen.png",
- website: "https://codewomen.plus/",
- },
- ],
- media_partners: [
- {
- name: "Digital Expert Online",
- website: "https://digital-expert.online/en/",
- image: "/images/sponsors/logo-digital-expert.svg",
- },
- {
- name: "Kube events",
- image: "/images/sponsors/kube-events.png",
- website: "https://kube.events/",
- },
- {
- name: "Kube careers",
- image: "/images/sponsors/kube-career.png",
- website: "https://kube.careers/",
- },
- {
- name: "CIO Insights",
- website: "https://www.cioinsights.com/",
- image: "/images/sponsors/cio-insights.png",
- },
- {
- name: "Codely",
- image: "images/sponsors/codely.png",
- website: "https://codely.com/",
- },
- {
- name: "Foojay",
- image: "images/sponsors/foojay.jpg",
- website: "https://foojay.io/",
- },
- ],
+ premium: [],
+ regular: [],
+ basic: [],
+ communities: [],
+ media_partners: [],
supporters: [],
};
diff --git a/src/views/Speakers/Speakers.test.tsx b/src/views/Speakers/Speakers.test.tsx
index 79034ca90..d0d17e81d 100644
--- a/src/views/Speakers/Speakers.test.tsx
+++ b/src/views/Speakers/Speakers.test.tsx
@@ -31,7 +31,7 @@ vi.mock("@sentry/react", () => ({
}));
// Mock the 2025.json data
-vi.mock("../../data/2025.json", () => {
+vi.mock("../../data/2026.json", () => {
const mockData = {
hideSpeakers: false,
edition: "2024",
diff --git a/src/views/Talks/LiveView.test.tsx b/src/views/Talks/LiveView.test.tsx
index 64eea81de..ac5af3065 100644
--- a/src/views/Talks/LiveView.test.tsx
+++ b/src/views/Talks/LiveView.test.tsx
@@ -3,7 +3,7 @@ import * as useFetchTalksModule from "@hooks/useFetchTalks";
import { useFetchLiveView } from "@hooks/useFetchTalks";
import { Loading } from "@components/Loading/Loading";
import { UngroupedSession } from "./liveView.types";
-import conference from "@data/2025.json";
+import conference from "@data/2026.json";
import { TalkCard } from "./components/TalkCard";
import { StyledAgenda, StyledMain } from "./Talks.style";
import { talkCardAdapter } from "./TalkCardAdapter";
diff --git a/tsconfig.json b/tsconfig.json
index 3d4931c2a..918e07a97 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,17 +23,40 @@
"noEmitOnError": false,
"baseUrl": "src",
"paths": {
- "@components/*": ["components/*"],
- "@constants/*": ["constants/*"],
- "@services/*": ["services/*"],
- "@hooks/*": ["hooks/*"],
- "@styles/*": ["styles/*"],
- "@views/*": ["views/*"],
- "@utils/*": ["utils/*"],
- "@data/*": ["data/*"],
- "@/types/*": ["types/*"]
+ "@components/*": [
+ "components/*"
+ ],
+ "@config/*": [
+ "config/*"
+ ],
+ "@constants/*": [
+ "constants/*"
+ ],
+ "@services/*": [
+ "services/*"
+ ],
+ "@hooks/*": [
+ "hooks/*"
+ ],
+ "@styles/*": [
+ "styles/*"
+ ],
+ "@views/*": [
+ "views/*"
+ ],
+ "@utils/*": [
+ "utils/*"
+ ],
+ "@data/*": [
+ "data/*"
+ ],
+ "@/types/*": [
+ "types/*"
+ ]
},
- "types": ["@testing-library/jest-dom"]
+ "types": [
+ "@testing-library/jest-dom"
+ ]
},
"include": [
"src"
@@ -44,4 +67,4 @@
"**/*.spec.ts",
"**/*.spec.tsx"
]
-}
+}
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
index 8a758e29b..0c0cb7a0a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -46,6 +46,7 @@ export default defineConfig(({ mode }) => {
alias: {
// Path aliases matching those in tsconfig.json
"@components": path.resolve(__dirname, "./src/components"),
+ "@config": path.resolve(__dirname, "./src/config"),
"@constants": path.resolve(__dirname, "./src/constants"),
"@services": path.resolve(__dirname, "./src/services"),
"@hooks": path.resolve(__dirname, "./src/hooks"),
From 8a06c4fac00d7f627a4938d77a62bd36f18fb942 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 19:07:22 +0100
Subject: [PATCH 02/21] chore: Add VS Code launch configuration and disable
several navigation links.
---
src/components/Navigation/NavigationData.ts | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/components/Navigation/NavigationData.ts b/src/components/Navigation/NavigationData.ts
index ed9c23a80..415cb76fc 100644
--- a/src/components/Navigation/NavigationData.ts
+++ b/src/components/Navigation/NavigationData.ts
@@ -20,12 +20,12 @@ export const navigationItems2026: NavigationItem[] = [
{ id: "Home", link: ROUTE_2026_HOME },
{ id: "Code of Conduct", link: ROUTE_2026_CODE_OF_CONDUCT },
{ id: "Sponsors", link: "/2026/#sponsors" },
- { id: "SCHEDULE", link: ROUTE_2026_SCHEDULE },
- { id: "Talks", link: ROUTE_2026_TALKS },
- { id: "Workshops", link: ROUTE_2026_WORKSHOPS },
- { id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
+ //{ id: "SCHEDULE", link: ROUTE_2026_SCHEDULE },
+ //{ id: "Talks", link: ROUTE_2026_TALKS },
+ //{ id: "Workshops", link: ROUTE_2026_WORKSHOPS },
+ //{ id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
//{ id: "Communities", link: ROUTE_COMMUNITIES },
- { id: "Speakers", link: ROUTE_2026_SPEAKERS },
+ //{ id: "Speakers", link: ROUTE_2026_SPEAKERS },
{ id: "About Us", link: ROUTE_ABOUT_US },
{ id: "Travel", link: ROUTE_2026_TRAVEL },
{ id: "Sponsorship", link: ROUTE_2026_SPONSORSHIP },
@@ -36,11 +36,11 @@ export const subMenuItems2026: NavigationItem[] = [
{ id: "Code of Conduct", link: ROUTE_2026_CODE_OF_CONDUCT },
{ id: "Sponsors", link: "/2026/#sponsors" },
{ id: "SCHEDULE", link: ROUTE_2026_SCHEDULE },
- { id: "Talks", link: ROUTE_2026_TALKS },
- { id: "Workshops", link: ROUTE_2026_WORKSHOPS },
- { id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
+ //{ id: "Talks", link: ROUTE_2026_TALKS },
+ //{ id: "Workshops", link: ROUTE_2026_WORKSHOPS },
+ //{ id: "JOB OFFERS", link: ROUTE_2026_JOB_OFFERS },
//{ id: "Communities", link: ROUTE_COMMUNITIES },
- { id: "Speakers", link: ROUTE_2026_SPEAKERS },
+ //{ id: "Speakers", link: ROUTE_2026_SPEAKERS },
{ id: "About Us", link: ROUTE_ABOUT_US },
{ id: "Travel", link: ROUTE_2026_TRAVEL },
{ id: "Sponsorship", link: ROUTE_2026_SPONSORSHIP },
From 7d00244aad3ff0ab82f3b665cc5f56d762989a36 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 19:26:44 +0100
Subject: [PATCH 03/21] Update src/2025/Home/components/Faqs/Faqs.tsx
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---
src/2025/Home/components/Faqs/Faqs.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/2025/Home/components/Faqs/Faqs.tsx b/src/2025/Home/components/Faqs/Faqs.tsx
index de49f8f55..f05291985 100644
--- a/src/2025/Home/components/Faqs/Faqs.tsx
+++ b/src/2025/Home/components/Faqs/Faqs.tsx
@@ -160,7 +160,7 @@ const Faqs: FC> = () => {
transition={{ duration: 0.8, delay: 2.1 }}
>
From 46795e76559a0b6b1a0142f107c341742fad3002 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 19:27:44 +0100
Subject: [PATCH 04/21] Update src/views/Home/components/Home/Home.tsx
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---
src/views/Home/components/Home/Home.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index f339ebfbf..39d95d7af 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -69,7 +69,7 @@ const Home: FC> = () => {
>
Past events: 2025 edition |{" "}
- 2024 edition| {" "}
+ 2024 edition |{" "}
2023 edition
From a14f602e4b8426f05fdfc1d8b959ca679705d9df Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 19:59:29 +0100
Subject: [PATCH 05/21] refactor: Consolidate sponsor display components into a
single `SponsorTier` component and add VS Code launch configuration.
---
.../components/Sponsors/Sponsors.style.ts | 96 ------
.../Home/components/Sponsors/Sponsors.tsx | 90 ++++--
src/2024/Sponsors/SponsorBadge.tsx | 36 ---
src/2024/Sponsors/Sponsors.tsx | 86 +++--
.../Home/components/Sponsors/BasicSponsor.tsx | 97 ------
.../Home/components/Sponsors/Communities.tsx | 93 ------
.../components/Sponsors/MediaPartners.tsx | 94 ------
.../components/Sponsors/PremiumSponsors.tsx | 99 ------
.../components/Sponsors/RegularSponsors.tsx | 93 ------
.../Home/components/Sponsors/SponsorBadge.tsx | 36 ---
.../components/Sponsors/Sponsors.style.ts | 297 ------------------
.../Home/components/Sponsors/Sponsors.tsx | 75 ++++-
.../components/Sponsors/Supporters.test.tsx | 82 -----
.../Home/components/Sponsors/Supporters.tsx | 102 ------
.../Home/components/Sponsors/TopSponsors.tsx | 92 ------
.../components/Sponsors/useSponsorsHook.ts | 41 ---
.../Home/components/Sponsors/BasicSponsor.tsx | 97 ------
.../Home/components/Sponsors/Communities.tsx | 93 ------
.../components/Sponsors/MediaPartners.tsx | 94 ------
.../components/Sponsors/PremiumSponsors.tsx | 99 ------
.../components/Sponsors/RegularSponsors.tsx | 93 ------
.../Home/components/Sponsors/SponsorTier.tsx | 142 +++++++++
.../Home/components/Sponsors/Sponsors.tsx | 73 ++++-
.../Home/components/Sponsors/Supporters.tsx | 102 ------
.../Home/components/Sponsors/TopSponsors.tsx | 92 ------
25 files changed, 380 insertions(+), 2014 deletions(-)
delete mode 100644 src/2023/Home/components/Sponsors/Sponsors.style.ts
delete mode 100644 src/2024/Sponsors/SponsorBadge.tsx
delete mode 100644 src/2025/Home/components/Sponsors/BasicSponsor.tsx
delete mode 100644 src/2025/Home/components/Sponsors/Communities.tsx
delete mode 100644 src/2025/Home/components/Sponsors/MediaPartners.tsx
delete mode 100644 src/2025/Home/components/Sponsors/PremiumSponsors.tsx
delete mode 100644 src/2025/Home/components/Sponsors/RegularSponsors.tsx
delete mode 100644 src/2025/Home/components/Sponsors/SponsorBadge.tsx
delete mode 100644 src/2025/Home/components/Sponsors/Sponsors.style.ts
delete mode 100644 src/2025/Home/components/Sponsors/Supporters.test.tsx
delete mode 100644 src/2025/Home/components/Sponsors/Supporters.tsx
delete mode 100644 src/2025/Home/components/Sponsors/TopSponsors.tsx
delete mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.ts
delete mode 100644 src/views/Home/components/Sponsors/BasicSponsor.tsx
delete mode 100644 src/views/Home/components/Sponsors/Communities.tsx
delete mode 100644 src/views/Home/components/Sponsors/MediaPartners.tsx
delete mode 100644 src/views/Home/components/Sponsors/PremiumSponsors.tsx
delete mode 100644 src/views/Home/components/Sponsors/RegularSponsors.tsx
create mode 100644 src/views/Home/components/Sponsors/SponsorTier.tsx
delete mode 100644 src/views/Home/components/Sponsors/Supporters.tsx
delete mode 100644 src/views/Home/components/Sponsors/TopSponsors.tsx
diff --git a/src/2023/Home/components/Sponsors/Sponsors.style.ts b/src/2023/Home/components/Sponsors/Sponsors.style.ts
deleted file mode 100644
index f69fbde81..000000000
--- a/src/2023/Home/components/Sponsors/Sponsors.style.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { styled } from "styled-components";
-import { BIG_BREAKPOINT } from "@constants/BreakPoints";
-// @ts-expect-error some quirky import message
-import { motion } from "motion/react";
-
-export const StyledSponsorsContainer = styled.div`
- position: relative;
- padding-top: 4rem;
-`;
-export const StyledTitleContainer = styled.div`
- display: flex;
- justify-content: space-between;
- margin-bottom: 0.5rem;
-`;
-export const StyledTitleImg = styled.img`
- height: 4rem;
- @media (min-width: 800px) {
- height: 10rem;
- }
-`;
-
-export const StyledSponsorBadgeLeft = styled(motion.div)<{
- color: string;
- position: "left" | "right";
-}>`
- display: none;
- position: absolute;
- width: ${({ position }) => (position === "left" ? "60%" : "62%")};
- clip-path: ${({ position }) => {
- if (position === "left") {
- return "polygon(0 0, 100% 0, 92% 100%, 0% 100%)";
- } else {
- return "polygon(6% 0, 100% 0, 100% 100%, 0 100%)";
- }
- }};
- top: 0;
- bottom: 0;
- background-color: ${({ color }) => color};
-
- left: ${({ position }) => {
- if (position === "left") {
- return "0";
- } else {
- return "unset";
- }
- }};
-
- right: ${({ position }) => {
- if (position === "right") {
- return "0";
- } else {
- return "unset";
- }
- }};
- z-index: 1;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- display: flex;
- }
-`;
-
-export const leftVariants = {
- initial: {
- x: -700,
- },
- animate: {
- x: 0,
- transition: {
- duration: 0.2,
- },
- },
- exit: {
- x: -700,
- transition: {
- duration: 0.2,
- },
- },
-};
-
-export const rightVariants = {
- initial: {
- x: 1000,
- },
- animate: {
- x: 0,
- transition: {
- duration: 0.2,
- },
- },
- exit: {
- x: 1000,
- transition: {
- duration: 0.2,
- },
- },
-};
diff --git a/src/2023/Home/components/Sponsors/Sponsors.tsx b/src/2023/Home/components/Sponsors/Sponsors.tsx
index 3b0e4a657..c04c9974e 100644
--- a/src/2023/Home/components/Sponsors/Sponsors.tsx
+++ b/src/2023/Home/components/Sponsors/Sponsors.tsx
@@ -7,31 +7,10 @@ import {
StyledSponsorsContainer,
StyledTitleContainer,
StyledTitleImg,
-} from "./Sponsors.style";
-
-import { BasicSponsor } from "@views/Home/components/Sponsors/BasicSponsor";
-import { MediaPartners } from "@views/Home/components/Sponsors/MediaPartners";
-import { RegularSponsors } from "@views/Home/components/Sponsors/RegularSponsors";
-import { PremiumSponsors } from "@views/Home/components/Sponsors/PremiumSponsors";
-import { TopSponsors } from "@views/Home/components/Sponsors/TopSponsors";
-import { Communities } from "@views/Home/components/Sponsors/Communities";
-import { Supporters } from "@views/Home/components/Sponsors/Supporters";
-
+} from "@views/Home/components/Sponsors/Sponsors.style";
+import { SponsorTier } from "@views/Home/components/Sponsors/SponsorTier";
import { sponsors } from "./SponsorsData";
-export const buildSlashes = (module: number) => {
- const slashesElement = document.getElementById("Slashes");
-
- const slashesWidth = slashesElement?.offsetWidth ?? 0;
-
- let slashes = "";
- for (let index = 0; index < slashesWidth; index++) {
- if (index % module === 0) slashes += "/ ";
- }
-
- return slashes;
-};
-
const Sponsors: FC> = () => (
);
export default Sponsors;
+
diff --git a/src/2024/Sponsors/SponsorBadge.tsx b/src/2024/Sponsors/SponsorBadge.tsx
deleted file mode 100644
index 9f3f521f7..000000000
--- a/src/2024/Sponsors/SponsorBadge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import {AnimatePresence} from "framer-motion";
-import {FC} from "react";
-import {
- leftVariants,
- rightVariants,
- StyledSponsorBadgeLeft,
-} from "./Sponsors.style";
-
-interface ISponsorBadgeProps {
- position: "left" | "right";
- color: string;
- isVisible: boolean;
-}
-
-const SponsorBadge: FC> = ({
- position,
- color,
- isVisible,
- }) => {
- return (
-
- {isVisible && (
-
- )}
-
- );
-};
-
-export default SponsorBadge;
diff --git a/src/2024/Sponsors/Sponsors.tsx b/src/2024/Sponsors/Sponsors.tsx
index 8a2215e2a..964ba955c 100644
--- a/src/2024/Sponsors/Sponsors.tsx
+++ b/src/2024/Sponsors/Sponsors.tsx
@@ -8,28 +8,9 @@ import {
} from "./Sponsors.style";
import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
import TitleSection from "@components/SectionTitle/TitleSection";
-import { BasicSponsor } from "@views/Home/components/Sponsors/BasicSponsor";
-import { MediaPartners } from "@views/Home/components/Sponsors/MediaPartners";
-import { RegularSponsors } from "@views/Home/components/Sponsors/RegularSponsors";
-import { PremiumSponsors } from "@views/Home/components/Sponsors/PremiumSponsors";
-import { TopSponsors } from "@views/Home/components/Sponsors/TopSponsors";
-import { Communities } from "@views/Home/components/Sponsors/Communities";
-import { Supporters } from "@views/Home/components/Sponsors/Supporters";
+import { SponsorTier } from "@views/Home/components/Sponsors/SponsorTier";
import { sponsors } from "./SponsorsData";
-export const buildSlashes = (module: number) => {
- const slashesElement = document.getElementById("Slashes");
-
- const slashesWidth = slashesElement?.offsetWidth ?? 0;
-
- let slashes = "";
- for (let index = 0; index < slashesWidth; index++) {
- if (index % module === 0) slashes += "/ ";
- }
-
- return slashes;
-};
-
const Sponsors: FC> = () => (
);
export default Sponsors;
+
diff --git a/src/2025/Home/components/Sponsors/BasicSponsor.tsx b/src/2025/Home/components/Sponsors/BasicSponsor.tsx
deleted file mode 100644
index 99c0b10c3..000000000
--- a/src/2025/Home/components/Sponsors/BasicSponsor.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconNano,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const BasicSponsor: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/Communities.tsx b/src/2025/Home/components/Sponsors/Communities.tsx
deleted file mode 100644
index 8536f8034..000000000
--- a/src/2025/Home/components/Sponsors/Communities.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const Communities: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- COMMUNITIES
-
- {slashes}
-
- {width >= BIG_BREAKPOINT && (
-
- {slashes}
-
- )}
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/MediaPartners.tsx b/src/2025/Home/components/Sponsors/MediaPartners.tsx
deleted file mode 100644
index 5cc9cfe9b..000000000
--- a/src/2025/Home/components/Sponsors/MediaPartners.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const MediaPartners: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- MEDIA PARTNERS
-
- {slashes}
-
- {width >= BIG_BREAKPOINT && (
-
- {slashes}
-
- )}
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/PremiumSponsors.tsx b/src/2025/Home/components/Sponsors/PremiumSponsors.tsx
deleted file mode 100644
index 67fef734a..000000000
--- a/src/2025/Home/components/Sponsors/PremiumSponsors.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import {
- PremiumSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const PremiumSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/RegularSponsors.tsx b/src/2025/Home/components/Sponsors/RegularSponsors.tsx
deleted file mode 100644
index 1dec1c786..000000000
--- a/src/2025/Home/components/Sponsors/RegularSponsors.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- RegularSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const RegularSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/SponsorBadge.tsx b/src/2025/Home/components/Sponsors/SponsorBadge.tsx
deleted file mode 100644
index 86a8fe55c..000000000
--- a/src/2025/Home/components/Sponsors/SponsorBadge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { AnimatePresence } from "framer-motion";
-import { FC } from "react";
-import {
- leftVariants,
- rightVariants,
- StyledSponsorBadgeLeft,
-} from "./Sponsors.style";
-
-interface ISponsorBadgeProps {
- position: "left" | "right";
- color: string;
- isVisible: boolean;
-}
-
-const SponsorBadge: FC> = ({
- position,
- color,
- isVisible,
-}) => {
- return (
-
- {isVisible && (
-
- )}
-
- );
-};
-
-export default SponsorBadge;
diff --git a/src/2025/Home/components/Sponsors/Sponsors.style.ts b/src/2025/Home/components/Sponsors/Sponsors.style.ts
deleted file mode 100644
index 1a18abd4f..000000000
--- a/src/2025/Home/components/Sponsors/Sponsors.style.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-import { styled } from "styled-components";
-import { BIG_BREAKPOINT, LARGE_BREAKPOINT } from "@constants/BreakPoints";
-import { motion } from "motion/react";
-
-const SponsorMargin = 11;
-const sponsorMarginDesktop = 11;
-export const StyledSponsorsContainer = styled.div`
- position: relative;
- padding-top: 4rem;
-`;
-export const StyledTitleContainer = styled.div`
- display: flex;
- justify-content: space-between;
- margin-bottom: 1rem;
-`;
-export const StyledTitleImg = styled.img`
- height: 4rem;
- @media (min-width: 800px) {
- height: 10rem;
- }
-`;
-export const StyledSponsorItemContainer = styled.div`
- position: relative;
- width: 100%;
- display: flex;
- flex-direction: column;
- font-size: 1.75rem;
- padding: 5.5rem 0 0.5rem;
- z-index: 1;
-
- @media (max-width: ${BIG_BREAKPOINT}px) {
- margin-bottom: 5rem;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- margin-bottom: 3rem;
- }
-`;
-export const StyledSponsorTitleContainer = styled.div`
- width: 100%;
- display: flex;
- position: absolute;
- top: 1rem;
- z-index: 2;
- background: none;
-`;
-export const StyledSponsorTitleMargin = styled.div`
- width: 10%;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- width: ${sponsorMarginDesktop}%;
- }
-`;
-export const StyledSponsorTitleSlashesContainer = styled.div<{ color: string }>`
- display: flex;
- flex-wrap: nowrap;
- width: 90%;
- font-family: "Square 721 Regular", sans-serif;
- color: ${({ color }) => color};
- height: 2.75rem;
- line-height: 2.75rem;
- white-space: nowrap;
-
- overflow: hidden;
-
- z-index: 2;
-
- transition: all 0.2s linear;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- width: 41%;
- }
-`;
-export const StyledSlashes = styled.div`
- white-space: nowrap;
- overflow: hidden;
- clip-path: polygon(0 0, 100% 0, 97% 100%, 0% 100%);
-`;
-export const StyledSponsorLogosContainer = styled.div`
- display: flex;
- width: 100%;
- top: 4.75rem;
- z-index: 2;
- background: none;
-
- @media (max-width: ${BIG_BREAKPOINT}px) {
- bottom: 15rem;
- padding-right: 1rem;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- }
-`;
-export const StyledLogos = styled.div.withConfig({
- shouldForwardProp: (prop) => !["position"].includes(prop),
-})<{ position?: "left" | "right" }>`
- display: flex;
- width: 100%;
-
- padding-left: ${({ position }) =>
- position === "right" ? 0 : SponsorMargin}%;
- padding-right: ${({ position }) =>
- position === "right" ? SponsorMargin : 0}%;
-
- flex-wrap: wrap;
- justify-content: center;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- justify-content: center;
- padding-left: ${({ position }) =>
- position === "right" ? 0 : sponsorMarginDesktop}%;
- padding-right: ${({ position }) =>
- position === "right" ? sponsorMarginDesktop : 0}%;
- top: 5rem;
- flex-wrap: wrap;
- width: ${({ position }) => (position === "right" ? 90 : 100)}%;
- }
-
- @media (min-width: ${LARGE_BREAKPOINT}px) {
- justify-content: ${({ position }) =>
- position === "right" ? "flex-end" : "flex-start"};
- padding-left: ${({ position }) =>
- position === "right" ? 0 : sponsorMarginDesktop}%;
- padding-right: ${({ position }) =>
- position === "right" ? sponsorMarginDesktop : 0}%;
- top: 5rem;
- }
-`;
-export const StyledFlexGrow = styled.div`
- flex: 1;
- display: none;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- display: flex;
- }
-`;
-export const StyledSeparator = styled.div`
- width: 7rem;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- width: 4rem;
- }
-`;
-export const PremiumSponsorImage = styled.img`
- height: 6rem;
- transition: height ease-in 0.25s;
- max-width: 100%;
- margin: 1rem 2rem;
-
- &:hover {
- filter: drop-shadow(1px 1px 1px #fff) !important;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 7.5rem;
- }
-`;
-export const RegularSponsorImage = styled.img`
- height: 3.25rem;
- margin-right: 0.5rem;
- margin-bottom: 1.5rem;
- transition: height ease-in 0.25s;
- border-radius: 1rem;
-
- &:hover {
- filter: drop-shadow(1px 1px 1px #fff) !important;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 3.25rem;
- margin-right: 2rem;
- margin-bottom: 0.75rem;
- }
-
- @media (min-width: ${LARGE_BREAKPOINT}px) {
- height: 3.25rem;
- margin-right: 2rem;
- margin-bottom: 27px;
- }
-`;
-export const StyledSponsorIconNano = styled.img`
- height: 3.5rem;
- margin-bottom: 1rem;
-
- margin-left: 0.75rem;
- transition: height ease-in 0.25s;
-
- &:hover {
- height: 4rem;
- filter: drop-shadow(1px 1px 1px #fff) !important;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 3.5rem;
- margin-left: 1rem;
- }
-
- @media (min-width: ${LARGE_BREAKPOINT}px) {
- height: 3.5rem;
- margin-left: 2.5rem;
- }
-`;
-export const StyledSponsorIconMicro = styled.img`
- height: 3.5rem;
- margin-bottom: 1rem;
- transition: height ease-in 0.25s;
- margin-left: 0.75rem;
-
- &:hover {
- filter: drop-shadow(1px 1px 1px #fff) !important;
- }
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 3.5rem;
- margin-left: 1rem;
- }
-
- @media (min-width: ${LARGE_BREAKPOINT}px) {
- height: 3.5rem;
- margin-left: 2.5rem;
- }
-`;
-export const StyledSponsorBadgeLeft = styled(motion.div)<{
- color: string;
- position: "left" | "right";
-}>`
- display: none;
- position: absolute;
- width: ${({ position }) => (position === "left" ? "60%" : "62%")};
- clip-path: ${({ position }) => {
- if (position === "left") {
- return "polygon(0 0, 100% 0, 92% 100%, 0% 100%)";
- } else {
- return "polygon(6% 0, 100% 0, 100% 100%, 0 100%)";
- }
- }};
- top: 0;
- bottom: 0;
- background-color: ${({ color }) => color};
-
- left: ${({ position }) => {
- if (position === "left") {
- return "0";
- } else {
- return "unset";
- }
- }};
-
- right: ${({ position }) => {
- if (position === "right") {
- return "0";
- } else {
- return "unset";
- }
- }};
- z-index: 1;
-
- @media (min-width: ${BIG_BREAKPOINT}px) {
- display: flex;
- }
-`;
-
-export const leftVariants = {
- initial: {
- x: -700,
- },
- animate: {
- x: 0,
- transition: {
- duration: 0.2,
- },
- },
- exit: {
- x: -700,
- transition: {
- duration: 0.2,
- },
- },
-};
-
-export const rightVariants = {
- initial: {
- x: 1000,
- },
- animate: {
- x: 0,
- transition: {
- duration: 0.2,
- },
- },
- exit: {
- x: 1000,
- transition: {
- duration: 0.2,
- },
- },
-};
diff --git a/src/2025/Home/components/Sponsors/Sponsors.tsx b/src/2025/Home/components/Sponsors/Sponsors.tsx
index acc048f8f..03fe540e2 100644
--- a/src/2025/Home/components/Sponsors/Sponsors.tsx
+++ b/src/2025/Home/components/Sponsors/Sponsors.tsx
@@ -7,14 +7,8 @@ import {
StyledSponsorsContainer,
StyledTitleContainer,
StyledTitleImg,
-} from "./Sponsors.style";
-import { TopSponsors } from "./TopSponsors";
-import { RegularSponsors } from "./RegularSponsors";
-import { PremiumSponsors } from "./PremiumSponsors";
-import { BasicSponsor } from "./BasicSponsor";
-import { Communities } from "./Communities";
-import { MediaPartners } from "./MediaPartners";
-import { Supporters } from "./Supporters";
+} from "@views/Home/components/Sponsors/Sponsors.style";
+import { SponsorTier } from "@views/Home/components/Sponsors/SponsorTier";
import { sponsors } from "./SponsorsData";
const Sponsors: FC> = () => (
@@ -39,15 +33,66 @@ const Sponsors: FC> = () => (
src="/images/LessThanBlueWhiteIcon.svg"
/>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
export default Sponsors;
+
diff --git a/src/2025/Home/components/Sponsors/Supporters.test.tsx b/src/2025/Home/components/Sponsors/Supporters.test.tsx
deleted file mode 100644
index 60cf81eb0..000000000
--- a/src/2025/Home/components/Sponsors/Supporters.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { fireEvent, render, screen } from "@testing-library/react";
-import { Supporters } from "./Supporters";
-import React from "react";
-import { useWindowSize } from "react-use";
-import { BrowserRouter, Route, Routes } from "react-router";
-import { Sponsor } from "./SponsorsData";
-import { vi } from "vitest";
-
-vi.mock("react-use", () => ({
- useWindowSize: vi.fn(),
-}));
-
-describe("Supporters", () => {
- beforeEach(() => {
- (useWindowSize as jest.Mock).mockReturnValue({ width: 1024 }); // Mock window width for testing
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- const supporters: Sponsor[] = [
- {
- name: "test",
- website: "https://www.acme.com",
- image: "https://www.acme.com/logo.png",
- },
- ];
-
- // disabled until supporters included
- it("renders component with supporters", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
-
- expect(screen.getByTestId("supporters")).toBeInTheDocument();
- expect(screen.getByText("SUPPORTERS")).toBeInTheDocument();
- expect(screen.getAllByRole("link")).toHaveLength(1);
- });
-
- it("applies hover styles on mouse enter", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const supportersElement = screen.getByTestId("supporters");
-
- fireEvent.mouseEnter(supportersElement);
-
- expect(supportersElement).toHaveClass("SponsorItem");
- expect(screen.getByText("SUPPORTERS")).toHaveStyle(
- "color: rgb(255, 252, 249)",
- );
- });
-
- it("removes hover styles on mouse leave", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const supporterElement = screen.getByTestId("supporters");
-
- fireEvent.mouseEnter(supporterElement);
- fireEvent.mouseLeave(supporterElement);
-
- expect(supporterElement).not.toHaveClass("hovered");
- expect(screen.getByText("SUPPORTERS")).toHaveStyle("color: rgb(0, 36, 84)");
- });
-});
diff --git a/src/2025/Home/components/Sponsors/Supporters.tsx b/src/2025/Home/components/Sponsors/Supporters.tsx
deleted file mode 100644
index a7d09ee2e..000000000
--- a/src/2025/Home/components/Sponsors/Supporters.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const Supporters: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- {slashes}
-
-
- {width < BIG_BREAKPOINT && "SUPPORTERS"}
-
- {width >= BIG_BREAKPOINT && (
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- >
- {slashes}
- SUPPORTERS
-
- )}
-
-
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/TopSponsors.tsx b/src/2025/Home/components/Sponsors/TopSponsors.tsx
deleted file mode 100644
index ef3b3c657..000000000
--- a/src/2025/Home/components/Sponsors/TopSponsors.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import {
- PremiumSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const TopSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/2025/Home/components/Sponsors/useSponsorsHook.ts b/src/2025/Home/components/Sponsors/useSponsorsHook.ts
deleted file mode 100644
index 8e421f39a..000000000
--- a/src/2025/Home/components/Sponsors/useSponsorsHook.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import {useCallback, useEffect, useState} from "react";
-import {useWindowSize} from "react-use";
-
-import {buildSlashes} from "../../../../services/buildSlashes";
-
-/**
- * Configuration for the sponsors hook
- */
-interface SponsorHookConfig {
- /** Number of slash groups to display in the sponsor section */
- numberOfSlashGroups: number;
-}
-
-/**
- * Custom hook to manage sponsor section behavior including:
- * - Responsive slashes generation
- * - Hover state management
- * - Window size tracking
- */
-export const useSponsorsHook = ({
- numberOfSlashGroups = 2,
-}: SponsorHookConfig) => {
- const { width } = useWindowSize();
- const [slashes, setSlashes] = useState(buildSlashes(numberOfSlashGroups));
- const [isHovered, setIsHovered] = useState(false);
-
- useEffect(() => {
- setSlashes(buildSlashes(numberOfSlashGroups));
- }, [width, numberOfSlashGroups]);
-
- const handleHover = useCallback(() => setIsHovered(true), []);
- const handleUnHover = useCallback(() => setIsHovered(false), []);
-
- return {
- width,
- slashes,
- isHovered,
- handleHover,
- handleUnHover,
- };
-};
diff --git a/src/views/Home/components/Sponsors/BasicSponsor.tsx b/src/views/Home/components/Sponsors/BasicSponsor.tsx
deleted file mode 100644
index 99c0b10c3..000000000
--- a/src/views/Home/components/Sponsors/BasicSponsor.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconNano,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const BasicSponsor: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/Communities.tsx b/src/views/Home/components/Sponsors/Communities.tsx
deleted file mode 100644
index 8536f8034..000000000
--- a/src/views/Home/components/Sponsors/Communities.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const Communities: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- COMMUNITIES
-
- {slashes}
-
- {width >= BIG_BREAKPOINT && (
-
- {slashes}
-
- )}
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/MediaPartners.tsx b/src/views/Home/components/Sponsors/MediaPartners.tsx
deleted file mode 100644
index 5cc9cfe9b..000000000
--- a/src/views/Home/components/Sponsors/MediaPartners.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const MediaPartners: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- MEDIA PARTNERS
-
- {slashes}
-
- {width >= BIG_BREAKPOINT && (
-
- {slashes}
-
- )}
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/PremiumSponsors.tsx b/src/views/Home/components/Sponsors/PremiumSponsors.tsx
deleted file mode 100644
index 67fef734a..000000000
--- a/src/views/Home/components/Sponsors/PremiumSponsors.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import {
- PremiumSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const PremiumSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/RegularSponsors.tsx b/src/views/Home/components/Sponsors/RegularSponsors.tsx
deleted file mode 100644
index 1dec1c786..000000000
--- a/src/views/Home/components/Sponsors/RegularSponsors.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- RegularSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const RegularSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/SponsorTier.tsx b/src/views/Home/components/Sponsors/SponsorTier.tsx
new file mode 100644
index 000000000..b48282fac
--- /dev/null
+++ b/src/views/Home/components/Sponsors/SponsorTier.tsx
@@ -0,0 +1,142 @@
+import {
+ PremiumSponsorImage,
+ RegularSponsorImage,
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconMicro,
+ StyledSponsorIconNano,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import { Color } from "../../../../styles/colors";
+import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import React, { FC } from "react";
+import { Sponsor } from "./SponsorsData";
+import { useSponsorsHook } from "./useSponsorsHook";
+
+type ImageSize = "premium" | "regular" | "nano" | "micro";
+
+interface SponsorTierProps {
+ sponsors: Sponsor[] | null;
+ title: string;
+ id: string;
+ badgeColor: string;
+ badgePosition: "left" | "right";
+ imageSize: ImageSize;
+ testId?: string;
+}
+
+const ImageComponents: Record = {
+ premium: PremiumSponsorImage,
+ regular: RegularSponsorImage,
+ nano: StyledSponsorIconNano,
+ micro: StyledSponsorIconMicro,
+};
+
+export const SponsorTier: FC> = ({
+ sponsors,
+ title,
+ id,
+ badgeColor,
+ badgePosition,
+ imageSize,
+ testId,
+}) => {
+ const { width, slashes, isHovered, handleHover, handleUnHover } =
+ useSponsorsHook({
+ numberOfSlashGroups: 2,
+ });
+
+ const ImageComponent = ImageComponents[imageSize];
+ const isRightAligned = badgePosition === "right";
+
+ if (!sponsors || sponsors.length === 0) {
+ return null;
+ }
+
+ const titleColor =
+ isHovered && width >= BIG_BREAKPOINT ? Color.WHITE : badgeColor;
+
+ return (
+
+
+
+
+ {!isRightAligned && }
+
+ {isRightAligned ? (
+ <>
+
+ {slashes}
+
+ {width < BIG_BREAKPOINT && title}
+
+ {width >= BIG_BREAKPOINT && (
+
+ {slashes}
+ {title}
+
+ )}
+
+ >
+ ) : (
+ <>
+
+ {title}
+
+ {slashes}
+
+ {width >= BIG_BREAKPOINT && (
+
+ {slashes}
+
+ )}
+ >
+ )}
+
+
+
+ {isRightAligned && }
+
+ {sponsors.map((sponsor) => (
+
+
+
+ ))}
+
+ {!isRightAligned && }
+
+
+ );
+};
+
+export default SponsorTier;
diff --git a/src/views/Home/components/Sponsors/Sponsors.tsx b/src/views/Home/components/Sponsors/Sponsors.tsx
index acc048f8f..3f05e5f8f 100644
--- a/src/views/Home/components/Sponsors/Sponsors.tsx
+++ b/src/views/Home/components/Sponsors/Sponsors.tsx
@@ -8,13 +8,7 @@ import {
StyledTitleContainer,
StyledTitleImg,
} from "./Sponsors.style";
-import { TopSponsors } from "./TopSponsors";
-import { RegularSponsors } from "./RegularSponsors";
-import { PremiumSponsors } from "./PremiumSponsors";
-import { BasicSponsor } from "./BasicSponsor";
-import { Communities } from "./Communities";
-import { MediaPartners } from "./MediaPartners";
-import { Supporters } from "./Supporters";
+import { SponsorTier } from "./SponsorTier";
import { sponsors } from "./SponsorsData";
const Sponsors: FC> = () => (
@@ -39,15 +33,66 @@ const Sponsors: FC> = () => (
src="/images/LessThanBlueWhiteIcon.svg"
/>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
export default Sponsors;
+
diff --git a/src/views/Home/components/Sponsors/Supporters.tsx b/src/views/Home/components/Sponsors/Supporters.tsx
deleted file mode 100644
index a7d09ee2e..000000000
--- a/src/views/Home/components/Sponsors/Supporters.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import {
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorIconMicro,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const Supporters: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
-
-
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- id="Slashes"
- >
- {slashes}
-
-
- {width < BIG_BREAKPOINT && "SUPPORTERS"}
-
- {width >= BIG_BREAKPOINT && (
- = BIG_BREAKPOINT
- ? Color.WHITE
- : Color.DARK_BLUE
- }
- >
- {slashes}
- SUPPORTERS
-
- )}
-
-
-
-
-
-
- {sponsors.map((sponsor) => (
-
-
-
- ))}
-
-
-
- )}
- >
- );
-};
diff --git a/src/views/Home/components/Sponsors/TopSponsors.tsx b/src/views/Home/components/Sponsors/TopSponsors.tsx
deleted file mode 100644
index ef3b3c657..000000000
--- a/src/views/Home/components/Sponsors/TopSponsors.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import {
- PremiumSponsorImage,
- StyledFlexGrow,
- StyledLogos,
- StyledSeparator,
- StyledSlashes,
- StyledSponsorItemContainer,
- StyledSponsorLogosContainer,
- StyledSponsorTitleContainer,
- StyledSponsorTitleMargin,
- StyledSponsorTitleSlashesContainer,
-} from "./Sponsors.style";
-import SponsorBadge from "./SponsorBadge";
-import { Color } from "../../../../styles/colors";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import React, { FC } from "react";
-import { Sponsor } from "./SponsorsData";
-import { useSponsorsHook } from "./useSponsorsHook";
-
-interface Props {
- sponsors: Array | null;
-}
-
-export const TopSponsors: FC> = ({
- sponsors,
-}) => {
- const { width, slashes, isHovered, handleHover, handleUnHover } =
- useSponsorsHook({
- numberOfSlashGroups: 2,
- });
-
- return (
- <>
- {sponsors !== null && sponsors.length > 0 && (
-
- )}
- >
- );
-};
From 461c49a3332cdeb4511ca7ef2948b54456148320 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sat, 6 Dec 2025 20:14:03 +0100
Subject: [PATCH 06/21] feat: Update conference data to 2026, remove
sponsor-related tests, and add VS Code launch configuration.
---
.../Sponsors/useSponsorsHook.test.tsx | 107 ------------------
src/views/Cfp/CfpSection.tsx | 2 +-
.../components/Sponsors/Supporters.test.tsx | 82 --------------
src/views/Speakers/Speakers.test.tsx | 12 +-
src/views/Speakers/Speakers.tsx | 2 +-
5 files changed, 8 insertions(+), 197 deletions(-)
delete mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
delete mode 100644 src/views/Home/components/Sponsors/Supporters.test.tsx
diff --git a/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx b/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
deleted file mode 100644
index 57b6f4e55..000000000
--- a/src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { act, renderHook } from "@testing-library/react";
-import { useSponsorsHook } from "./useSponsorsHook";
-import React, { FC } from "react";
-import { vi } from "vitest";
-import { buildSlashes } from "../../../../services/buildSlashes";
-
-// Mock the buildSlashes function
-vi.mock("../../../../services/buildSlashes", () => ({
- buildSlashes: vi.fn((count: number) => "//".repeat(count)),
-}));
-
-const wrapper: FC>> = ({
- children,
-}) => {
- return {children}
;
-};
-
-describe("useSponsorsHook", () => {
- beforeEach(() => {
- // Clear mock calls between tests
- vi.clearAllMocks();
- });
-
- it("should initialize with default values", () => {
- const { result } = renderHook(
- () => useSponsorsHook({ numberOfSlashGroups: 2 }),
- { wrapper },
- );
-
- expect(result.current.slashes).toBe("////");
- expect(result.current.isHovered).toBe(false);
- expect(typeof result.current.width).toBe("number");
- });
-
- it("should update slashes when window size changes", () => {
- const { rerender } = renderHook(
- () => useSponsorsHook({ numberOfSlashGroups: 2 }),
- { wrapper },
- );
-
- // Initial render should call buildSlashes once
- expect(buildSlashes).toHaveBeenCalledTimes(2);
- expect(buildSlashes).toHaveBeenCalledWith(2);
-
- // Trigger a rerender (simulating window resize)
- rerender();
-
- // buildSlashes should be called again
- expect(buildSlashes).toHaveBeenCalledTimes(3);
- });
-
- it("should update hover state correctly", () => {
- const { result } = renderHook(
- () => useSponsorsHook({ numberOfSlashGroups: 2 }),
- { wrapper },
- );
-
- // Initial state should be not hovered
- expect(result.current.isHovered).toBe(false);
-
- // Simulate hover
- act(() => {
- result.current.handleHover();
- });
- expect(result.current.isHovered).toBe(true);
-
- // Simulate unhover
- act(() => {
- result.current.handleUnHover();
- });
- expect(result.current.isHovered).toBe(false);
- });
-
- it("should update slashes when numberOfSlashGroups changes", () => {
- const { result, rerender } = renderHook(
- ({ numberOfSlashGroups }) => useSponsorsHook({ numberOfSlashGroups }),
- { initialProps: { numberOfSlashGroups: 2 } },
- );
-
- // Initial render with 2 groups
- expect(buildSlashes).toHaveBeenCalledWith(2);
- expect(result.current.slashes).toBe("////");
-
- // Update to 3 groups
- rerender({ numberOfSlashGroups: 3 });
-
- expect(buildSlashes).toHaveBeenCalledWith(3);
- expect(result.current.slashes).toBe("//////");
- });
-
- it("should memoize hover handlers", () => {
- const { result, rerender } = renderHook(() =>
- useSponsorsHook({ numberOfSlashGroups: 2 }),
- );
-
- // Store initial handlers
- const initialHandleHover = result.current.handleHover;
- const initialHandleUnHover = result.current.handleUnHover;
-
- // Rerender
- rerender();
-
- // Handlers should remain the same (memoized)
- expect(result.current.handleHover).toBe(initialHandleHover);
- expect(result.current.handleUnHover).toBe(initialHandleUnHover);
- });
-});
diff --git a/src/views/Cfp/CfpSection.tsx b/src/views/Cfp/CfpSection.tsx
index e455b58e7..2d18aab0e 100644
--- a/src/views/Cfp/CfpSection.tsx
+++ b/src/views/Cfp/CfpSection.tsx
@@ -15,7 +15,7 @@ import {
StyledAboutImage,
StyledSocialIconsWrapper,
} from "../About/components/Style.AboutCard";
-import conferenceData from "@data/2025.json";
+import conferenceData from "@data/2026.json";
import { CfpTrackProps, data } from "./CfpData";
import { MemberName, TrackName } from "./Cfp.style";
import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
diff --git a/src/views/Home/components/Sponsors/Supporters.test.tsx b/src/views/Home/components/Sponsors/Supporters.test.tsx
deleted file mode 100644
index 60cf81eb0..000000000
--- a/src/views/Home/components/Sponsors/Supporters.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { fireEvent, render, screen } from "@testing-library/react";
-import { Supporters } from "./Supporters";
-import React from "react";
-import { useWindowSize } from "react-use";
-import { BrowserRouter, Route, Routes } from "react-router";
-import { Sponsor } from "./SponsorsData";
-import { vi } from "vitest";
-
-vi.mock("react-use", () => ({
- useWindowSize: vi.fn(),
-}));
-
-describe("Supporters", () => {
- beforeEach(() => {
- (useWindowSize as jest.Mock).mockReturnValue({ width: 1024 }); // Mock window width for testing
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- const supporters: Sponsor[] = [
- {
- name: "test",
- website: "https://www.acme.com",
- image: "https://www.acme.com/logo.png",
- },
- ];
-
- // disabled until supporters included
- it("renders component with supporters", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
-
- expect(screen.getByTestId("supporters")).toBeInTheDocument();
- expect(screen.getByText("SUPPORTERS")).toBeInTheDocument();
- expect(screen.getAllByRole("link")).toHaveLength(1);
- });
-
- it("applies hover styles on mouse enter", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const supportersElement = screen.getByTestId("supporters");
-
- fireEvent.mouseEnter(supportersElement);
-
- expect(supportersElement).toHaveClass("SponsorItem");
- expect(screen.getByText("SUPPORTERS")).toHaveStyle(
- "color: rgb(255, 252, 249)",
- );
- });
-
- it("removes hover styles on mouse leave", () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const supporterElement = screen.getByTestId("supporters");
-
- fireEvent.mouseEnter(supporterElement);
- fireEvent.mouseLeave(supporterElement);
-
- expect(supporterElement).not.toHaveClass("hovered");
- expect(screen.getByText("SUPPORTERS")).toHaveStyle("color: rgb(0, 36, 84)");
- });
-});
diff --git a/src/views/Speakers/Speakers.test.tsx b/src/views/Speakers/Speakers.test.tsx
index d0d17e81d..02b9fcd8e 100644
--- a/src/views/Speakers/Speakers.test.tsx
+++ b/src/views/Speakers/Speakers.test.tsx
@@ -30,15 +30,15 @@ vi.mock("@sentry/react", () => ({
captureException: vi.fn(),
}));
-// Mock the 2025.json data
+// Mock the 2026.json data
vi.mock("../../data/2026.json", () => {
const mockData = {
hideSpeakers: false,
- edition: "2024",
+ edition: "2026",
title: "DevBcn",
cfp: {
- startDay: "2023-01-01T00:00:00",
- endDay: "2023-02-01T00:00:00",
+ startDay: "2026-01-01T00:00:00",
+ endDay: "2026-03-01T00:00:00",
link: "https://example.com/cfp",
},
};
@@ -94,7 +94,7 @@ describe("Speakers component", () => {
// Mock Date to return a date within the CFP period
const originalDate = Date;
- const mockDate = new Date("2023-01-15");
+ const mockDate = new Date("2026-01-15");
// This ensures that both new Date() and Date.now() use our mock date
global.Date = class extends Date {
@@ -130,7 +130,7 @@ describe("Speakers component", () => {
// Mock Date to return a date within the CFP period
const originalDate = Date;
- const mockDate = new Date("2023-01-15");
+ const mockDate = new Date("2026-01-15");
// This ensures that both new Date() and Date.now() use our mock date
global.Date = class extends Date {
diff --git a/src/views/Speakers/Speakers.tsx b/src/views/Speakers/Speakers.tsx
index 606fbd0a6..5b4bc074c 100644
--- a/src/views/Speakers/Speakers.tsx
+++ b/src/views/Speakers/Speakers.tsx
@@ -15,7 +15,7 @@ import {
StyledSpeakersSection,
StyledWaveContainer,
} from "./Speakers.style";
-import webData from "@data/2025.json";
+import webData from "@data/2026.json";
import Button from "@components/UI/Button";
import { gaEventTracker } from "@components/analytics/Analytics";
import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
From 91cb12a57f6431ba8310c26eaeb9f8013d7a5151 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 7 Dec 2025 19:32:24 +0100
Subject: [PATCH 07/21] feat: Add VS Code launch configuration and update
`@vitest/coverage-v8` dependency.
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 7ad8421c4..c139dc86e 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,7 @@
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.46.2",
"@vitejs/plugin-react": "^5.1.0",
- "@vitest/coverage-v8": "^3.2.4",
+ "@vitest/coverage-v8": "^4.0.5",
"autoprefixer": "^10.4.21",
"babel-plugin-import": "^1.13.8",
"eslint": "^9.26.0",
@@ -109,4 +109,4 @@
"bundleDependencies": [
"clsx"
]
-}
+}
\ No newline at end of file
From da4f73fddf75d6d31aae4ba842c44ba03c3ec42b Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 7 Dec 2025 19:45:55 +0100
Subject: [PATCH 08/21] refactor: centralize `TalkCard` component to
`@components/common` and update imports.
---
.vscode/launch.json | 14 +
package-lock.json | 453 ++++--------------------
src/2023/Workshops/Workshops2023.tsx | 2 +-
src/2024/Talks/LiveView.tsx | 2 +-
src/components/Talk/TalkCard.tsx | 10 -
src/components/common/TalkCard.test.tsx | 119 +++++++
src/views/Talks/LiveView.test.tsx | 2 +-
src/views/Talks/LiveView.tsx | 2 +-
src/views/Talks/TalkCardAdapter.ts | 2 +-
src/views/Talks/components/TalkCard.tsx | 13 -
10 files changed, 205 insertions(+), 414 deletions(-)
create mode 100644 .vscode/launch.json
delete mode 100644 src/components/Talk/TalkCard.tsx
create mode 100644 src/components/common/TalkCard.test.tsx
delete mode 100644 src/views/Talks/components/TalkCard.tsx
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..fdaedb981
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "command": "npm start",
+ "name": "Run npm start",
+ "request": "launch",
+ "type": "node-terminal"
+ },
+ ]
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 339d81b9a..09fc52a7a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,7 +66,7 @@
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.46.2",
"@vitejs/plugin-react": "^5.1.0",
- "@vitest/coverage-v8": "^3.2.4",
+ "@vitest/coverage-v8": "^4.0.5",
"autoprefixer": "^10.4.21",
"babel-plugin-import": "^1.13.8",
"eslint": "^9.26.0",
@@ -97,20 +97,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@asamuzakjp/css-color": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz",
@@ -2592,112 +2578,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@jest/diff-sequences": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
@@ -3246,17 +3126,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -5509,31 +5378,30 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
- "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.15.tgz",
+ "integrity": "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
- "ast-v8-to-istanbul": "^0.3.3",
- "debug": "^4.4.1",
+ "@vitest/utils": "4.0.15",
+ "ast-v8-to-istanbul": "^0.3.8",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
- "istanbul-reports": "^3.1.7",
- "magic-string": "^0.30.17",
- "magicast": "^0.3.5",
- "std-env": "^3.9.0",
- "test-exclude": "^7.0.1",
- "tinyrainbow": "^2.0.0"
+ "istanbul-reports": "^3.2.0",
+ "magicast": "^0.5.1",
+ "obug": "^2.1.1",
+ "std-env": "^3.10.0",
+ "tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "3.2.4",
- "vitest": "3.2.4"
+ "@vitest/browser": "4.0.15",
+ "vitest": "4.0.15"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -5551,35 +5419,31 @@
"node": ">=18"
}
},
- "node_modules/@vitest/coverage-v8/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": {
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz",
+ "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/coverage-v8/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": {
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz",
+ "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "@vitest/pretty-format": "4.0.15",
+ "tinyrainbow": "^3.0.3"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": {
@@ -5597,47 +5461,6 @@
"node": ">=10"
}
},
- "node_modules/@vitest/coverage-v8/node_modules/magic-string": {
- "version": "0.30.17",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/test-exclude": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
- "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^10.4.1",
- "minimatch": "^9.0.4"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@vitest/expect": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.5.tgz",
@@ -5656,16 +5479,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/expect/node_modules/tinyrainbow": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
- "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@vitest/mocker": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.5.tgz",
@@ -5726,16 +5539,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/pretty-format/node_modules/tinyrainbow": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
- "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@vitest/runner": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.5.tgz",
@@ -5799,16 +5602,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/utils/node_modules/tinyrainbow": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
- "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@xobotyi/scrollbar-width": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz",
@@ -6216,12 +6009,13 @@
"license": "MIT"
},
"node_modules/ast-v8-to-istanbul": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz",
- "integrity": "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==",
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz",
+ "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^9.0.1"
}
@@ -6231,6 +6025,7 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
@@ -6239,7 +6034,8 @@
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/async": {
"version": "3.2.6",
@@ -7287,13 +7083,6 @@
"node": ">= 0.4"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8597,36 +8386,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
@@ -9904,9 +9663,9 @@
}
},
"node_modules/istanbul-lib-report/node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
@@ -9917,9 +9676,9 @@
}
},
"node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -9948,22 +9707,6 @@
"node": ">= 0.4"
}
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
"node_modules/jake": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
@@ -10511,15 +10254,15 @@
}
},
"node_modules/magicast": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
- "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz",
+ "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.25.4",
- "@babel/types": "^7.25.4",
- "source-map-js": "^1.2.0"
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "source-map-js": "^1.2.1"
}
},
"node_modules/make-dir": {
@@ -11022,6 +10765,17 @@
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==",
"license": "MIT"
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -11137,13 +10891,6 @@
"node": ">=6"
}
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -12868,9 +12615,9 @@
}
},
"node_modules/std-env": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
- "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"dev": true,
"license": "MIT"
},
@@ -12903,29 +12650,6 @@
"node": ">=8"
}
},
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/string-width/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -13074,20 +12798,6 @@
"node": ">=8"
}
},
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -13420,9 +13130,9 @@
}
},
"node_modules/tinyrainbow": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
- "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -14228,16 +13938,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vitest/node_modules/tinyrainbow": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
- "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -14797,25 +14497,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/src/2023/Workshops/Workshops2023.tsx b/src/2023/Workshops/Workshops2023.tsx
index 78a0108cf..c43402f56 100644
--- a/src/2023/Workshops/Workshops2023.tsx
+++ b/src/2023/Workshops/Workshops2023.tsx
@@ -13,7 +13,7 @@ import { useFetchTalks } from "@hooks/useFetchTalks";
import conferenceData from "@data/2023.json";
import { styled } from "styled-components";
import { BIG_BREAKPOINT } from "@constants/BreakPoints";
-import { TalkCard } from "@components/Talk/TalkCard";
+import { TalkCard } from "@components/common/TalkCard";
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
const StyledSection = styled.section`
diff --git a/src/2024/Talks/LiveView.tsx b/src/2024/Talks/LiveView.tsx
index 0b93cdbb3..03a84a8ba 100644
--- a/src/2024/Talks/LiveView.tsx
+++ b/src/2024/Talks/LiveView.tsx
@@ -3,7 +3,7 @@ import { useFetchLiveView } from "@hooks/useFetchTalks";
import { Loading } from "@components/Loading/Loading";
import conference from "@data/2024.json";
import { UngroupedSession } from "@views/Talks/liveView.types";
-import { TalkCard } from "@views/Talks/components/TalkCard";
+import { TalkCard } from "@components/common/TalkCard";
import { talkCardAdapter } from "@views/Talks/TalkCardAdapter";
import { StyledMain } from "@views/Talks/Talks.style";
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
diff --git a/src/components/Talk/TalkCard.tsx b/src/components/Talk/TalkCard.tsx
deleted file mode 100644
index 54039f799..000000000
--- a/src/components/Talk/TalkCard.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from "react";
-import { TalkCard as CommonTalkCard, TalkCardProps } from "../common/TalkCard";
-
-export type { TalkCardProps };
-
-export const TalkCard: React.FC> = (
- props,
-) => {
- return ;
-};
diff --git a/src/components/common/TalkCard.test.tsx b/src/components/common/TalkCard.test.tsx
new file mode 100644
index 000000000..6135b4135
--- /dev/null
+++ b/src/components/common/TalkCard.test.tsx
@@ -0,0 +1,119 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { TalkCard, TalkCardProps } from "./TalkCard";
+import { BrowserRouter } from "react-router";
+import { vi } from "vitest";
+
+// Mock framer-motion to avoid animation issues in tests
+vi.mock("framer-motion", () => ({
+ motion: {
+ div: ({
+ children,
+ ...props
+ }: {
+ children: React.ReactNode;
+ [key: string]: unknown;
+ }) => {children}
,
+ },
+}));
+
+const mockTalk: TalkCardProps["talk"] = {
+ id: 123,
+ title: "Test Talk Title",
+ talkImage: 1,
+ speakers: [
+ { id: "speaker-1", name: "John Doe" },
+ { id: "speaker-2", name: "Jane Smith" },
+ ],
+ level: "Intermediate",
+ link: "https://example.com",
+ tags: ["React", "TypeScript"],
+ track: "Frontend",
+ categories: [
+ {
+ id: 1,
+ name: "Session format",
+ categoryItems: [{ id: 101, name: "Talk" }],
+ },
+ {
+ id: 2,
+ name: "Level",
+ categoryItems: [{ id: 201, name: "Intermediate" }],
+ },
+ {
+ id: 3,
+ name: "Track",
+ categoryItems: [{ id: 301, name: "Frontend" }],
+ },
+ ],
+ questionAnswers: [
+ { id: 1, question: "Tags", answer: "React, TypeScript" },
+ ],
+};
+
+const renderTalkCard = (props: Partial = {}) => {
+ const defaultProps: TalkCardProps = {
+ talk: mockTalk,
+ openFeedbackId: "test-feedback-id",
+ year: "2024",
+ showTrack: false,
+ ...props,
+ };
+
+ return render(
+
+
+
+ );
+};
+
+describe("TalkCard", () => {
+ it("renders the talk title", () => {
+ renderTalkCard();
+ expect(screen.getByText("Test Talk Title")).toBeInTheDocument();
+ });
+
+ it("renders speaker names", () => {
+ renderTalkCard();
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ expect(screen.getByText("Jane Smith")).toBeInTheDocument();
+ });
+
+ it("renders vote link with correct href", () => {
+ renderTalkCard();
+ const voteLink = screen.getByText(/Vote this talk/);
+ expect(voteLink.closest("a")).toHaveAttribute(
+ "href",
+ "https://openfeedback.io/test-feedback-id/0/123"
+ );
+ });
+
+ it("shows track info when showTrack is true", () => {
+ renderTalkCard({ showTrack: true });
+ expect(screen.getByText(/Track:/)).toBeInTheDocument();
+ });
+
+ it("hides track info when showTrack is false", () => {
+ renderTalkCard({ showTrack: false });
+ expect(screen.queryByText(/Track:/)).not.toBeInTheDocument();
+ });
+
+ it("links to correct talk detail route for 2024", () => {
+ renderTalkCard({ year: "2024" });
+ const talkLink = screen.getByText("Test Talk Title");
+ expect(talkLink.closest("a")).toHaveAttribute("href", "/2024/talk/123");
+ });
+
+ it("links to correct talk detail route for 2023", () => {
+ renderTalkCard({ year: "2023" });
+ const talkLink = screen.getByText("Test Talk Title");
+ expect(talkLink.closest("a")).toHaveAttribute("href", "/2023/talk/123");
+ });
+
+ it("links to correct speaker detail route", () => {
+ renderTalkCard({ year: "2024" });
+ const speakerLink = screen.getByText("John Doe").closest("a");
+ expect(speakerLink).toHaveAttribute("href", "/2024/speaker/speaker-1");
+ });
+});
diff --git a/src/views/Talks/LiveView.test.tsx b/src/views/Talks/LiveView.test.tsx
index ac5af3065..ba68a5090 100644
--- a/src/views/Talks/LiveView.test.tsx
+++ b/src/views/Talks/LiveView.test.tsx
@@ -4,7 +4,7 @@ import { useFetchLiveView } from "@hooks/useFetchTalks";
import { Loading } from "@components/Loading/Loading";
import { UngroupedSession } from "./liveView.types";
import conference from "@data/2026.json";
-import { TalkCard } from "./components/TalkCard";
+import { TalkCard } from "@components/common/TalkCard";
import { StyledAgenda, StyledMain } from "./Talks.style";
import { talkCardAdapter } from "./TalkCardAdapter";
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
diff --git a/src/views/Talks/LiveView.tsx b/src/views/Talks/LiveView.tsx
index b4da5ab7a..8a5d8fbe5 100644
--- a/src/views/Talks/LiveView.tsx
+++ b/src/views/Talks/LiveView.tsx
@@ -3,7 +3,7 @@ import { useFetchLiveView } from "@hooks/useFetchTalks";
import { Loading } from "@components/Loading/Loading";
import { UngroupedSession } from "./liveView.types";
import conference from "@data/2025.json";
-import { TalkCard } from "./components/TalkCard";
+import { TalkCard } from "@components/common/TalkCard";
import { StyledAgenda, StyledMain } from "./Talks.style";
import { talkCardAdapter } from "./TalkCardAdapter";
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
diff --git a/src/views/Talks/TalkCardAdapter.ts b/src/views/Talks/TalkCardAdapter.ts
index fa3b64ec4..22fba67ca 100644
--- a/src/views/Talks/TalkCardAdapter.ts
+++ b/src/views/Talks/TalkCardAdapter.ts
@@ -1,5 +1,5 @@
import { UngroupedSession } from "./liveView.types";
-import { TalkCardProps } from "./components/TalkCard";
+import { TalkCardProps } from "@components/common/TalkCard";
import {
QuestionAnswers,
diff --git a/src/views/Talks/components/TalkCard.tsx b/src/views/Talks/components/TalkCard.tsx
deleted file mode 100644
index 9b80d8a08..000000000
--- a/src/views/Talks/components/TalkCard.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-import {
- TalkCard as CommonTalkCard,
- TalkCardProps,
-} from "@components/common/TalkCard";
-
-export type { TalkCardProps };
-
-export const TalkCard: React.FC> = (
- props,
-) => {
- return ;
-};
From 4adab9fcf480643090e8f195bbbf08e0b39e5aa7 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 7 Dec 2025 19:48:59 +0100
Subject: [PATCH 09/21] test: add comprehensive unit tests for the
MeetingDetail component
---
.../MeetingDetail/MeetingDetail.test.tsx | 185 ++++++++++++++++++
1 file changed, 185 insertions(+)
create mode 100644 src/views/MeetingDetail/MeetingDetail.test.tsx
diff --git a/src/views/MeetingDetail/MeetingDetail.test.tsx b/src/views/MeetingDetail/MeetingDetail.test.tsx
new file mode 100644
index 000000000..8108ef5f4
--- /dev/null
+++ b/src/views/MeetingDetail/MeetingDetail.test.tsx
@@ -0,0 +1,185 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import MeetingDetail from "./MeetingDetail";
+import { BrowserRouter } from "react-router";
+import { vi } from "vitest";
+import { IMeeting } from "@/types/sessions";
+import { ISpeaker } from "@/types/speakers";
+
+// Mock framer-motion
+vi.mock("framer-motion", () => ({
+ motion: {
+ div: ({
+ children,
+ ...props
+ }: {
+ children: React.ReactNode;
+ [key: string]: unknown;
+ }) => {children}
,
+ img: (props: { [key: string]: unknown }) =>
,
+ },
+}));
+
+// Mock add-to-calendar-button-react
+vi.mock("add-to-calendar-button-react", () => ({
+ AddToCalendarButton: () => (
+
+ ),
+}));
+
+// Mock useWindowSize
+vi.mock("react-use", () => ({
+ useWindowSize: () => ({ width: 1200, height: 800 }),
+}));
+
+const mockMeeting: IMeeting = {
+ id: 12345,
+ title: "Test Talk Title",
+ description: "This is a test talk description with important content.",
+ videoUrl: "https://www.youtube.com/embed/test123",
+ slidesURL: "https://slides.example.com/test",
+ videoTags: ["React", "TypeScript", "Testing"],
+ speakers: [
+ { id: "speaker-1", name: "John Doe" },
+ { id: "speaker-2", name: "Jane Smith" },
+ ],
+ level: "Intermediate",
+ type: "Talk",
+ language: "English",
+ track: "Frontend",
+ startDate: "2024-06-17",
+ endDate: "2024-06-17",
+ startTime: "10:00",
+ endTime: "11:00",
+};
+
+const mockSpeakers: ISpeaker[] = [
+ {
+ id: "speaker-1",
+ fullName: "John Doe",
+ speakerImage: "/images/speakers/john.jpg",
+ bio: "Test bio",
+ tagLine: "Test tagline",
+ sessions: [],
+ links: [],
+ },
+ {
+ id: "speaker-2",
+ fullName: "Jane Smith",
+ speakerImage: "/images/speakers/jane.jpg",
+ bio: "Test bio 2",
+ tagLine: "Test tagline 2",
+ sessions: [],
+ links: [],
+ },
+];
+
+const renderMeetingDetail = (
+ meeting: Partial = {},
+ speakers: ISpeaker[] = mockSpeakers
+) => {
+ return render(
+
+
+
+ );
+};
+
+describe("MeetingDetail", () => {
+ it("renders the meeting title", () => {
+ renderMeetingDetail();
+ expect(screen.getByText(/Test Talk Title/)).toBeInTheDocument();
+ });
+
+ it("renders the meeting description", () => {
+ renderMeetingDetail();
+ expect(
+ screen.getByText(/This is a test talk description/)
+ ).toBeInTheDocument();
+ });
+
+ it("renders speaker names with links", () => {
+ renderMeetingDetail();
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ expect(screen.getByText("Jane Smith")).toBeInTheDocument();
+ });
+
+ it("renders speaker images", () => {
+ renderMeetingDetail();
+ const speakerImages = screen.getAllByRole("img", { name: /John Doe|Jane Smith/ });
+ expect(speakerImages).toHaveLength(2);
+ });
+
+ it("renders vote talk link with correct href", () => {
+ renderMeetingDetail();
+ const voteLink = screen.getByText(/Vote this talk/).closest("a");
+ expect(voteLink).toHaveAttribute(
+ "href",
+ "https://openfeedback.io/test-feedback-id/0/12345"
+ );
+ });
+
+ it("renders video iframe when videoUrl is provided", () => {
+ renderMeetingDetail();
+ const iframe = screen.getByTitle("Test Talk Title");
+ expect(iframe).toBeInTheDocument();
+ expect(iframe).toHaveAttribute(
+ "src",
+ "https://www.youtube.com/embed/test123"
+ );
+ });
+
+ it("does not render video iframe when videoUrl is empty", () => {
+ renderMeetingDetail({ videoUrl: undefined });
+ expect(screen.queryByTitle("Test Talk Title")).not.toBeInTheDocument();
+ });
+
+ it("renders slides link when slidesURL is provided", () => {
+ renderMeetingDetail();
+ const slidesLink = screen.getByText(/Session Slides/).closest("a");
+ expect(slidesLink).toHaveAttribute(
+ "href",
+ "https://slides.example.com/test"
+ );
+ });
+
+ it("does not render slides link when slidesURL is empty", () => {
+ renderMeetingDetail({ slidesURL: "" });
+ expect(screen.queryByText(/Session Slides/)).not.toBeInTheDocument();
+ });
+
+ it("renders video tags", () => {
+ renderMeetingDetail();
+ expect(screen.getByText("React")).toBeInTheDocument();
+ expect(screen.getByText("TypeScript")).toBeInTheDocument();
+ expect(screen.getByText("Testing")).toBeInTheDocument();
+ });
+
+ it("renders track information", () => {
+ renderMeetingDetail();
+ expect(screen.getByText(/Track:/)).toBeInTheDocument();
+ expect(screen.getByText(/Frontend/)).toBeInTheDocument();
+ });
+
+ it("renders level and type information", () => {
+ renderMeetingDetail();
+ expect(screen.getByText(/Talk Intermediate/)).toBeInTheDocument();
+ });
+
+ it("renders go back link", () => {
+ renderMeetingDetail();
+ const backLink = screen.getByText("Go back");
+ expect(backLink).toBeInTheDocument();
+ expect(backLink.closest("a")).toHaveAttribute("href", "/talks");
+ });
+
+ it("renders add to calendar button", () => {
+ renderMeetingDetail();
+ expect(screen.getByTestId("add-to-calendar")).toBeInTheDocument();
+ });
+});
From 32e41dfaea8fc97c2c91cff68e0c7258ae358381 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 7 Dec 2025 20:13:31 +0100
Subject: [PATCH 10/21] refactor: Extract year-specific data and logic into
wrapper components for generic views.
---
src/2023/Cfp/CfpSection2023.tsx | 113 -------
src/2023/Cfp/CfpSectionWrapper.tsx | 10 +
src/2023/Routes.tsx | 4 +-
src/2023/Speakers/Speakers2023.test.tsx | 205 ------------
src/2023/Speakers/Speakers2023.tsx | 165 ----------
src/2023/Speakers/SpeakersWrapper.tsx | 9 +
src/2024/Cfp/CfpSection2024.tsx | 93 ------
src/2024/Cfp/CfpSectionWrapper.tsx | 10 +
src/2024/SpeakerDetail/SpeakerDetail.tsx | 165 ----------
.../SpeakerDetailContainer2024.tsx | 13 +-
src/2024/Speakers/Speakers2024.test.tsx | 114 -------
src/2024/Speakers/Speakers2024.tsx | 166 ----------
src/2024/Speakers/SpeakersWrapper.tsx | 9 +
src/2024/TalkDetail/MeetingDetail.tsx | 295 ------------------
.../TalkDetail/MeetingDetailContainer2024.tsx | 10 +-
src/2024/Talks/Talks2024.tsx | 148 ---------
src/2024/Talks/TalksWrapper.tsx | 110 +++++++
.../YearSpecific/Speakers/Speakers2023.tsx | 9 -
.../YearSpecific/Speakers/Speakers2024.tsx | 7 -
src/types/sessions.ts | 20 ++
src/utils/lazyComponents.ts | 14 +-
src/views/Cfp/CfpData.ts | 20 +-
src/views/Cfp/CfpSection.tsx | 18 +-
src/views/MeetingDetail/MeetingDetail.tsx | 9 +-
src/views/SpeakerDetail/SpeakerDetail.tsx | 14 +-
src/views/Speakers/Speakers.tsx | 22 +-
src/views/Talks/Talks.test.tsx | 141 ++++++++-
src/views/Talks/Talks.tsx | 144 ++-------
src/views/Workshops/Workshops.tsx | 2 +-
29 files changed, 411 insertions(+), 1648 deletions(-)
delete mode 100644 src/2023/Cfp/CfpSection2023.tsx
create mode 100644 src/2023/Cfp/CfpSectionWrapper.tsx
delete mode 100644 src/2023/Speakers/Speakers2023.test.tsx
delete mode 100644 src/2023/Speakers/Speakers2023.tsx
create mode 100644 src/2023/Speakers/SpeakersWrapper.tsx
delete mode 100644 src/2024/Cfp/CfpSection2024.tsx
create mode 100644 src/2024/Cfp/CfpSectionWrapper.tsx
delete mode 100644 src/2024/SpeakerDetail/SpeakerDetail.tsx
delete mode 100644 src/2024/Speakers/Speakers2024.test.tsx
delete mode 100644 src/2024/Speakers/Speakers2024.tsx
create mode 100644 src/2024/Speakers/SpeakersWrapper.tsx
delete mode 100644 src/2024/TalkDetail/MeetingDetail.tsx
delete mode 100644 src/2024/Talks/Talks2024.tsx
create mode 100644 src/2024/Talks/TalksWrapper.tsx
delete mode 100644 src/components/YearSpecific/Speakers/Speakers2023.tsx
delete mode 100644 src/components/YearSpecific/Speakers/Speakers2024.tsx
diff --git a/src/2023/Cfp/CfpSection2023.tsx b/src/2023/Cfp/CfpSection2023.tsx
deleted file mode 100644
index fa7e86b54..000000000
--- a/src/2023/Cfp/CfpSection2023.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import React, { FC } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import { Color } from "@styles/colors";
-import {
- StyledLessIcon,
- StyledMoreIcon,
- StyledSpeakersSection,
-} from "../Speakers/Speakers.style";
-import TitleSection from "@components/SectionTitle/TitleSection";
-import { MOBILE_BREAKPOINT } from "@constants/BreakPoints";
-import { useWindowSize } from "react-use";
-import TwitterIcon from "@components/Icons/Twitter";
-import LinkedinIcon from "@components/Icons/Linkedin";
-
-import conferenceData from "@data/2023.json";
-import { CfpTrackProps, data } from "./CfpData";
-import { styled } from "styled-components";
-import {
- StyledAboutImage,
- StyledSocialIconsWrapper,
-} from "@views/About/components/Style.AboutCard";
-import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
-
-const TrackName = styled.h2`
- padding-top: 1.2rem;
- padding-bottom: 0.8rem;
- font-size: 1.5rem;
- color: ${Color.DARK_BLUE};
-`;
-
-const MemberName = styled.h5`
- font-size: 0.8rem;
- color: ${Color.DARK_BLUE};
- text-align: left;
-`;
-
-const CfpTrackComponent: FC> = ({
- track,
-}) => (
- <>
-
-
- {track.members.map((member) => {
- return (
-
- {member.photo !== "" && (
-
-
- {member.name}
-
- {member.twitter !== "" && (
-
- )}
- {member.linkedIn !== "" && (
-
- )}
-
-
- )}
-
- );
- })}
-
- >
-);
-
-const CfpSection2023: FC> = () => {
- const { width } = useWindowSize();
-
- useDocumentTitleUpdater("CFP Committee", conferenceData.edition);
- return (
- <>
-
-
-
- {width > MOBILE_BREAKPOINT && (
- <>
-
-
- >
- )}
-
- {data.map((track) => (
-
- ))}
-
-
- >
- );
-};
-
-export default CfpSection2023;
diff --git a/src/2023/Cfp/CfpSectionWrapper.tsx b/src/2023/Cfp/CfpSectionWrapper.tsx
new file mode 100644
index 000000000..79d870851
--- /dev/null
+++ b/src/2023/Cfp/CfpSectionWrapper.tsx
@@ -0,0 +1,10 @@
+import React, { FC } from "react";
+import CfpSection from "@views/Cfp/CfpSection";
+import data2023 from "@data/2023.json";
+import { data } from "./CfpData";
+
+export const CfpSectionWrapper: FC = () => {
+ return ;
+};
+
+export default CfpSectionWrapper;
diff --git a/src/2023/Routes.tsx b/src/2023/Routes.tsx
index e64a585a1..6ed25e8c7 100644
--- a/src/2023/Routes.tsx
+++ b/src/2023/Routes.tsx
@@ -20,7 +20,7 @@ import { Loading } from "@components/Loading/Loading";
const Home2023Wrapper = lazy(() => import("./Home/Home2023Wrapper"));
const Speakers2023 = lazy(
- () => import("../components/YearSpecific/Speakers/Speakers2023"),
+ () => import("./Speakers/SpeakersWrapper"),
);
const SpeakerDetailContainer2023 = lazy(
() => import("./SpeakerDetail/SpeakerDetailContainer2023"),
@@ -36,7 +36,7 @@ const SpeakerInformation2023 = lazy(
() => import("./Speakers/SpeakerInformation2023"),
);
const Communities2023 = lazy(() => import("./Communities/Communities2023"));
-const CfpSection2023 = lazy(() => import("./Cfp/CfpSection2023"));
+const CfpSection2023 = lazy(() => import("./Cfp/CfpSectionWrapper"));
const SessionFeedback2023 = lazy(
() => import("./SessionFeedback/SessionFeedback2023"),
);
diff --git a/src/2023/Speakers/Speakers2023.test.tsx b/src/2023/Speakers/Speakers2023.test.tsx
deleted file mode 100644
index e68dd5bd4..000000000
--- a/src/2023/Speakers/Speakers2023.test.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-import { vi } from "vitest";
-
-vi.mock("../../hooks/useFetchSpeakers");
-vi.mock("../../components/analytics/Analytics", () => ({
- gaEventTracker: vi.fn(),
-}));
-vi.mock("react-use", () => ({
- useWindowSize: vi.fn(),
-}));
-vi.mock("@sentry/react", () => ({
- captureException: vi.fn(),
-}));
-vi.mock("../../data/2023.json", () => {
- return {
- default: {
- hideSpeakers: false,
- edition: "2023",
- title: "DevBcn",
- cfp: {
- startDay: "2022-11-01T00:00:00",
- endDay: "2023-03-15T00:00:00",
- link: "https://sessionize.com/devbcn23/",
- },
- },
- };
-});
-
-import React from "react";
-import { screen } from "@testing-library/react";
-import Speakers2023 from "./Speakers2023";
-import {
- createMockSpeakers,
- renderWithRouterAndQueryClient,
-} from "../../utils/testing/speakerTestUtils";
-import { useFetchSpeakers } from "../../hooks/useFetchSpeakers";
-import userEvent from "@testing-library/user-event";
-import { gaEventTracker } from "../../components/analytics/Analytics";
-import { useWindowSize } from "react-use";
-
-const mockedUseFetchSpeakers = useFetchSpeakers as jest.MockedFunction<
- typeof useFetchSpeakers
->;
-
-describe("Speakers2023 component", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- vi.mocked(useWindowSize).mockReturnValue({ width: 1200 });
- });
-
- it("displays loading state when data is being fetched", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- expect(screen.getByText("Loading...")).toBeInTheDocument();
- });
-
- it("displays speakers when data is loaded", () => {
- const mockSpeakers = createMockSpeakers(3);
-
- // Mock the hook to return success state with data
- mockedUseFetchSpeakers.mockReturnValue({
- data: mockSpeakers,
- isLoading: false,
- error: null,
- isSuccess: true,
- });
-
- renderWithRouterAndQueryClient();
-
- // Check that each speaker's name is displayed
- mockSpeakers.forEach((speaker) => {
- expect(screen.getByText(speaker.fullName)).toBeInTheDocument();
- });
- });
-
- it("displays a message when no speakers are available", () => {
- // Mock the hook to return success state with empty data
- mockedUseFetchSpeakers.mockReturnValue({
- data: [],
- isLoading: false,
- error: null,
- isSuccess: true,
- });
-
- renderWithRouterAndQueryClient();
-
- expect(screen.getByText(/No selected speakers yet/i)).toBeInTheDocument();
- });
-
- it.skip("displays CFP button when current date is within CFP period", () => {
- // Mock the hook to return success state with data
- mockedUseFetchSpeakers.mockReturnValue({
- data: [],
- isLoading: false,
- error: null,
- isSuccess: true,
- });
-
- // Mock Date.now to return a date within the CFP period
- const originalDate = Date;
- global.Date = class extends Date {
- constructor() {
- super();
- }
-
- static now() {
- return new Date("2023-01-15").getTime();
- }
- } as typeof Date;
-
- renderWithRouterAndQueryClient();
-
- const cfpButton = screen.getByText(/Apply to be a Speaker/i);
- expect(cfpButton).toBeInTheDocument();
-
- // Restore original Date
- global.Date = originalDate;
- });
-
- it.skip("tracks CFP button clicks", async () => {
- // Mock the hook to return success state with data
- mockedUseFetchSpeakers.mockReturnValue({
- data: [],
- isLoading: false,
- error: null,
- isSuccess: true,
- });
-
- // Mock Date.now to return a date within the CFP period
- const originalDate = Date;
- global.Date = class extends Date {
- constructor() {
- super();
- }
-
- static now() {
- return new Date("2023-01-15").getTime();
- }
- } as typeof Date;
-
- renderWithRouterAndQueryClient();
-
- const cfpButton = screen.getByText(/Apply to be a Speaker/i);
- await userEvent.click(cfpButton);
-
- expect(gaEventTracker).toHaveBeenCalledWith("CFP", "CFP");
-
- // Restore original Date
- global.Date = originalDate;
- });
-
- it("calls useFetchSpeakers with the correct year", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- // Verify that useFetchSpeakers was called with "2023"
- expect(mockedUseFetchSpeakers).toHaveBeenCalledWith("2023");
- });
-
- it("sets the document title correctly", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- // Verify that document.title was set correctly
- expect(document.title).toContain("Speakers2023");
- expect(document.title).toContain("2023");
- });
-
- it.skip("handles errors correctly", () => {
- // Mock the hook to return error state
- const error = new Error("Failed to fetch speakers");
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: false,
- error,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- // Note: We're skipping the verification that Sentry.captureException was called
- // because it's difficult to properly mock and spy on it in Vitest
- });
-});
diff --git a/src/2023/Speakers/Speakers2023.tsx b/src/2023/Speakers/Speakers2023.tsx
deleted file mode 100644
index 73b110039..000000000
--- a/src/2023/Speakers/Speakers2023.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { MOBILE_BREAKPOINT } from "@constants/BreakPoints";
-import { Color } from "@styles/colors";
-import { FC, useCallback, useEffect } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import TitleSection from "@components/SectionTitle/TitleSection";
-import { useWindowSize } from "react-use";
-import {
- SpeakersCardsContainer,
- StyledContainerLeftSlash,
- StyledContainerRightSlash,
- StyledLessIcon,
- StyledMoreIcon,
- StyledSlash,
- StyledSpeakersSection,
- StyledWaveContainer,
-} from "./Speakers.style";
-import webData from "@data/2023.json";
-import Button from "@components/UI/Button";
-import { gaEventTracker } from "@components/analytics/Analytics";
-import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
-import { ISpeaker } from "@/types/speakers";
-import { SpeakerCard } from "@views/Speakers/components/SpeakersCard";
-import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
-
-const LessThanGreaterThan = () => (
- <>
-
-
- >
-);
-
-const Speakers2023: FC> = () => {
- const { width } = useWindowSize();
- const today = new Date();
- const isBetween = (startDay: Date, endDay: Date): boolean =>
- startDay < new Date() && endDay > today;
-
- const { error, data, isLoading } = useFetchSpeakers("2023");
-
- useSentryErrorReport(error);
-
- const trackCFP = useCallback(() => {
- gaEventTracker("CFP", "CFP");
- }, []);
-
- useEffect(() => {
- document.title = `Speakers2023 - DevBcn ${webData.edition}`;
- });
-
- const CFPStartDay = new Date(webData.cfp.startDay);
- const CFPEndDay = new Date(webData.cfp.endDay);
- return (
- <>
-
-
-
- {width > MOBILE_BREAKPOINT && }
-
- {isLoading && Loading...
}
- {isBetween(CFPStartDay, CFPEndDay) && (
-
-
-
- )}
- {data?.length === 0 && (
-
- No selected speakers yet. Keep in touch in our social media for
- upcoming announcements
-
- )}
- {data?.map((speaker: ISpeaker) => (
-
- ))}
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
-
-
- >
- );
-};
-
-export default Speakers2023;
diff --git a/src/2023/Speakers/SpeakersWrapper.tsx b/src/2023/Speakers/SpeakersWrapper.tsx
new file mode 100644
index 000000000..bc7bc56f4
--- /dev/null
+++ b/src/2023/Speakers/SpeakersWrapper.tsx
@@ -0,0 +1,9 @@
+import React, { FC } from "react";
+import Speakers from "@views/Speakers/Speakers";
+import data2023 from "@data/2023.json";
+
+export const SpeakersWrapper: FC = () => {
+ return ;
+};
+
+export default SpeakersWrapper;
diff --git a/src/2024/Cfp/CfpSection2024.tsx b/src/2024/Cfp/CfpSection2024.tsx
deleted file mode 100644
index 81e05d7b5..000000000
--- a/src/2024/Cfp/CfpSection2024.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { FC } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import { Color } from "@styles/colors";
-import {
- StyledLessIcon,
- StyledMoreIcon,
- StyledSpeakersSection,
-} from "../Speakers/Speakers.style";
-import TitleSection from "@components/SectionTitle/TitleSection";
-import { MOBILE_BREAKPOINT } from "@constants/BreakPoints";
-import { useWindowSize } from "react-use";
-import TwitterIcon from "@components/Icons/Twitter";
-import LinkedinIcon from "@components/Icons/Linkedin";
-
-import conferenceData from "@data/2024.json";
-import { CfpTrackProps, data } from "./CfpData";
-import { MemberName, TrackName } from "./Cfp.style";
-import {
- StyledAboutImage,
- StyledSocialIconsWrapper,
-} from "@views/About/components/Style.AboutCard";
-import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
-
-export const CfpTrackComponent: FC> = ({
- track,
-}) => (
- <>
-
-
- {track.members.map((member) => {
- return (
-
- {member.photo && (
-
-
- {member.name}
-
- {member.twitter && (
-
- )}
- {member.linkedIn && (
-
- )}
-
-
- )}
-
- );
- })}
-
- >
-);
-
-const CfpSection2024: FC> = () => {
- const { width } = useWindowSize();
- useDocumentTitleUpdater("CFP Committee", conferenceData.edition);
- return (
- <>
-
-
-
- {width > MOBILE_BREAKPOINT && (
- <>
-
-
- >
- )}
-
- {data.map((track) => (
-
- ))}
-
-
- >
- );
-};
-
-export default CfpSection2024;
diff --git a/src/2024/Cfp/CfpSectionWrapper.tsx b/src/2024/Cfp/CfpSectionWrapper.tsx
new file mode 100644
index 000000000..d106ce236
--- /dev/null
+++ b/src/2024/Cfp/CfpSectionWrapper.tsx
@@ -0,0 +1,10 @@
+import React, { FC } from "react";
+import CfpSection from "@views/Cfp/CfpSection";
+import data2024 from "@data/2024.json";
+import { data } from "./CfpData";
+
+export const CfpSectionWrapper: FC = () => {
+ return ;
+};
+
+export default CfpSectionWrapper;
diff --git a/src/2024/SpeakerDetail/SpeakerDetail.tsx b/src/2024/SpeakerDetail/SpeakerDetail.tsx
deleted file mode 100644
index 87acfa4fc..000000000
--- a/src/2024/SpeakerDetail/SpeakerDetail.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { BIG_BREAKPOINT } from "@constants/BreakPoints";
-
-import { FC, Suspense } from "react";
-import { useWindowSize } from "react-use";
-
-import { ROUTE_2024_SPEAKERS, ROUTE_2024_TALK_DETAIL } from "@constants/routes";
-import { Link } from "react-router";
-import { Color } from "@styles/colors";
-import conferenceData from "@data/2024.json";
-import {
- StyledDetailsContainer,
- StyledFlexCol,
- StyledImageContainer,
- StyledInfoContainer,
- StyledLink,
- StyledMoreThanIcon,
- StyledMoreThanIconContainer,
- StyledName,
- StyledNameContainer,
- StyledRightContainer,
- StyledSlashes,
- StyledSocialMediaContainer,
- StyledSocialMediaIcon,
- StyledSpeakerDescription,
- StyledSpeakerDetailContainer,
- StyledSpeakerImg,
- StyledSpeakerTitle,
-} from "@views/SpeakerDetail/Speaker.style";
-import { StyledTalkDescription } from "@views/SpeakerDetail/SpeakerDetail.style";
-import { ISpeaker } from "@/types/speakers";
-import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
-
-interface ISpeakerDetailProps {
- speaker: ISpeaker;
-}
-
-const SpeakerDetail: FC> = ({
- speaker,
-}) => {
- const { width } = useWindowSize();
-
- useDocumentTitleUpdater(speaker.fullName, conferenceData.edition);
-
- const hasSessions = (): boolean =>
- (speaker.sessions && speaker.sessions.length > 0) || false;
-
- return (
-
-
- {width > BIG_BREAKPOINT && (
-
- loading
}>
-
-
-
- {speaker.twitterUrl && (
-
-
-
- )}
- {speaker.linkedInUrl && (
-
-
-
- )}
-
-
- )}
-
-
- {speaker.fullName}
- {width < BIG_BREAKPOINT && (
- <>
- loading}>
-
-
-
- {speaker.twitterUrl && (
-
-
-
- )}
- {speaker.linkedInUrl && (
-
-
-
- )}
-
- >
- )}
-
-
-
-
- {speaker.tagLine}
- {speaker.bio}
-
- {hasSessions() && (
- <>
- Sessions
-
- {speaker?.sessions?.map((session) => (
- -
-
-
-
- {session.name}
-
-
-
- ))}
-
- >
- )}
-
-
- Go back
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SpeakerDetail;
diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
index ee5aac8b2..25fbaaf3f 100644
--- a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
+++ b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
@@ -1,8 +1,12 @@
import { Color } from "@styles/colors";
+import {
+ ROUTE_2024_SPEAKERS,
+ ROUTE_2024_TALK_DETAIL,
+} from "@constants/routes";
import React, { FC } from "react";
import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import SpeakerDetail from "./SpeakerDetail";
+import SpeakerDetail from "@views/SpeakerDetail/SpeakerDetail";
import { useParams } from "react-router";
import conferenceData from "@data/2024.json";
import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
@@ -28,7 +32,12 @@ export const SpeakerDetailContainer2024: FC<
{isLoading && Loading
}
{!isLoading && data && data.length > 0 ? (
-
+
) : (
"not found"
)}
diff --git a/src/2024/Speakers/Speakers2024.test.tsx b/src/2024/Speakers/Speakers2024.test.tsx
deleted file mode 100644
index 0b578eef7..000000000
--- a/src/2024/Speakers/Speakers2024.test.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React from "react";
-import { screen } from "@testing-library/react";
-import Speakers2024 from "./Speakers2024";
-import {
- createMockSpeakers,
- renderWithRouterAndQueryClient,
-} from "@utils/testing/speakerTestUtils";
-import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
-import { useWindowSize } from "react-use";
-import { type MockedFunction, vi } from "vitest";
-
-vi.mock("@hooks/useFetchSpeakers");
-
-vi.mock("@components/analytics/Analytics", () => ({
- gaEventTracker: vi.fn(),
-}));
-vi.mock("react-use", () => ({
- useWindowSize: vi.fn(),
-}));
-vi.mock("@sentry/react", () => ({
- captureException: vi.fn(),
-}));
-vi.mock("@data/2024.json", () => {
- return {
- default: {
- hideSpeakers: false,
- edition: "2024",
- title: "DevBcn",
- cfp: {
- startDay: "2023-01-01T00:00:00",
- endDay: "2023-02-01T00:00:00",
- link: "https://example.com/cfp",
- },
- },
- };
-});
-
-const mockedUseFetchSpeakers = useFetchSpeakers as MockedFunction<
- typeof useFetchSpeakers
->;
-
-describe("Speakers2024 component", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- (useWindowSize as MockedFunction).mockReturnValue({
- width: 1200,
- height: 0,
- });
- });
-
- it("displays loading state when data is being fetched", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- expect(screen.getByText("Loading...")).toBeInTheDocument();
- });
-
- it("displays speakers when data is loaded", () => {
- const mockSpeakers = createMockSpeakers(3);
-
- // Mock the hook to return success state with data
- mockedUseFetchSpeakers.mockReturnValue({
- data: mockSpeakers,
- isLoading: false,
- error: null,
- isSuccess: true,
- });
-
- renderWithRouterAndQueryClient();
-
- // Check that each speaker's name is displayed
- mockSpeakers.forEach((speaker) => {
- expect(screen.getByText(speaker.fullName)).toBeInTheDocument();
- });
- });
-
- it("calls useFetchSpeakers with the correct year", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- // Verify that useFetchSpeakers was called with "2024"
- expect(mockedUseFetchSpeakers).toHaveBeenCalledWith("2024");
- });
-
- it("sets the document title correctly", () => {
- // Mock the hook to return loading state
- mockedUseFetchSpeakers.mockReturnValue({
- data: null,
- isLoading: true,
- error: null,
- isSuccess: false,
- });
-
- renderWithRouterAndQueryClient();
-
- // Verify that document.title was set correctly
- expect(document.title).toContain("Speakers");
- expect(document.title).toContain("2024");
- });
-});
diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx
deleted file mode 100644
index 83a0fae83..000000000
--- a/src/2024/Speakers/Speakers2024.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { MOBILE_BREAKPOINT } from "@constants/BreakPoints";
-import { Color } from "@styles/colors";
-import React, { FC, useCallback, useEffect } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import TitleSection from "@components/SectionTitle/TitleSection";
-import { useWindowSize } from "react-use";
-import {
- SpeakersCardsContainer,
- StyledContainerLeftSlash,
- StyledContainerRightSlash,
- StyledLessIcon,
- StyledMoreIcon,
- StyledSlash,
- StyledSpeakersSection,
- StyledWaveContainer,
-} from "./Speakers.style";
-import webData from "@data/2024.json";
-import Button from "@components/UI/Button";
-import { gaEventTracker } from "@components/analytics/Analytics";
-import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
-import { SpeakerCard } from "@views/Speakers/components/SpeakersCard";
-import { ISpeaker } from "@/types/speakers";
-import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
-
-const LessThanGreaterThan = () => (
- <>
-
-
- >
-);
-
-const Speakers2024: FC> = () => {
- const { width } = useWindowSize();
- const today = new Date();
- const isBetween = (startDay: Date, endDay: Date): boolean =>
- startDay < new Date() && endDay > today;
-
- const { error, data, isLoading } = useFetchSpeakers("2024");
-
- useSentryErrorReport(error);
-
- const trackCFP = useCallback(() => {
- gaEventTracker("CFP", "CFP");
- }, []);
-
- useEffect(() => {
- document.title = `Speakers — ${webData.title} — ${webData.edition}`;
- });
-
- const CFPStartDay = new Date(webData.cfp.startDay);
- const CFPEndDay = new Date(webData.cfp.endDay);
- return (
- <>
-
-
-
- {width > MOBILE_BREAKPOINT && }
-
- {isLoading && Loading...
}
- {isBetween(CFPStartDay, CFPEndDay) && (
-
-
-
- )}
- {webData.hideSpeakers ? (
-
- No selected speakers yet. Keep in touch in our social media for
- upcoming announcements
-
- ) : (
- data?.map((speaker: ISpeaker) => (
-
- ))
- )}
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- /{" "}
-
-
-
-
-
-
-
- >
- );
-};
-
-export default Speakers2024;
diff --git a/src/2024/Speakers/SpeakersWrapper.tsx b/src/2024/Speakers/SpeakersWrapper.tsx
new file mode 100644
index 000000000..ab11dc502
--- /dev/null
+++ b/src/2024/Speakers/SpeakersWrapper.tsx
@@ -0,0 +1,9 @@
+import React, { FC } from "react";
+import Speakers from "@views/Speakers/Speakers";
+import data2024 from "@data/2024.json";
+
+export const SpeakersWrapper: FC = () => {
+ return ;
+};
+
+export default SpeakersWrapper;
diff --git a/src/2024/TalkDetail/MeetingDetail.tsx b/src/2024/TalkDetail/MeetingDetail.tsx
deleted file mode 100644
index 608acf9f6..000000000
--- a/src/2024/TalkDetail/MeetingDetail.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-import {
- BIG_BREAKPOINT,
- LARGE_BREAKPOINT,
- MOBILE_BREAKPOINT,
-} from "@constants/BreakPoints";
-import { Color } from "@styles/colors";
-import React, { FC, Suspense } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import { useWindowSize } from "react-use";
-
-import { Link } from "react-router";
-import { ROUTE_2024_SPEAKER_DETAIL, ROUTE_2024_TALKS } from "@constants/routes";
-import conferenceData from "@data/2024.json";
-import { Tag } from "@components/Tag/Tag";
-import { styled } from "styled-components";
-import { AddToCalendarButton } from "add-to-calendar-button-react";
-import {
- StyledContainer,
- StyledDetailsContainer,
- StyledFlexCol,
- StyledName,
- StyledNameContainer,
- StyledRightContainer,
- StyledSpeakerDetailContainer,
-} from "@views/SpeakerDetail/Speaker.style";
-import {
- StyledDescription,
- StyledExtraInfo,
- StyledLessThan,
- StyledMeetingTitleContainer,
- StyledTitleImg,
- StyledVideoContainer,
- StyledVideoTagsContainer,
-} from "@views/MeetingDetail/Style.MeetingDetail";
-import { StyledTitle } from "../Home/Style.Home";
-import { ISpeaker } from "@/types/speakers";
-import { IMeeting } from "@/types/sessions";
-import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
-
-const getVideoHeight = (windowWidth: number) => {
- let videoHeight;
- if (windowWidth < MOBILE_BREAKPOINT) {
- videoHeight = 250;
- } else if (windowWidth >= MOBILE_BREAKPOINT && windowWidth < BIG_BREAKPOINT) {
- videoHeight = 300;
- } else if (windowWidth >= BIG_BREAKPOINT && windowWidth < LARGE_BREAKPOINT) {
- videoHeight = 450;
- } else {
- videoHeight = 600;
- }
-
- return videoHeight.toString();
-};
-
-const leftVariants = {
- initial: {
- x: -100,
- opacity: 0,
- },
- animate: {
- x: 0,
- opacity: 1,
- },
-};
-
-const rightVariants = {
- initial: {
- x: 100,
- opacity: 0,
- },
- animate: {
- x: 0,
- opacity: 1,
- },
-};
-
-const downVariants = {
- initial: {
- y: 100,
- opacity: 0,
- },
- animate: {
- y: 0,
- opacity: 1,
- },
-};
-
-const opacityVariants = {
- initial: {
- opacity: 0,
- },
- animate: {
- opacity: 1,
- transition: {
- duration: 1,
- },
- },
-};
-
-export const StyledVoteTalkLink = styled.a`
- text-decoration: none;
- color: ${Color.BLACK_BLUE};
- font-size: 0.8rem;
-`;
-
-interface IMeetingDetailProps {
- meeting: IMeeting;
- speakers?: ISpeaker[];
-}
-
-type MyType = {
- urlName?: string;
- videoUrl?: string;
- level?: string;
- videoTags?: string[];
- speakers?: ISpeaker[];
- description: string;
- language?: string;
- title: string;
- type?: string;
- track?: string;
-};
-
-const MeetingDetail: FC> = ({
- meeting,
- speakers: mySpeakers,
-}) => {
- const { width } = useWindowSize();
-
- useDocumentTitleUpdater(meeting.title, conferenceData.edition);
-
- const finalMeetingInfo: MyType = {
- ...meeting,
- speakers: mySpeakers,
- };
-
- return (
-
-
-
-
-
- / {meeting.title}
- Description
- {meeting.description}
-
- {`${meeting.type} ${meeting.level}`}
- Track:
- {meeting.track}
-
- {meeting.slidesURL !== "" && (
-
-
- {" "}
- Slides
-
-
- )}
-
-
-
-
-
- {meeting.videoUrl && (
-
- )}
-
- {meeting.videoTags?.map((tag) => )}
-
-
-
- 🗳️ Vote this talk
-
-
-
-
-
-
-
-
- {finalMeetingInfo.speakers?.map((speaker) => (
-
- loading}>
-
-
-
-
- {speaker.fullName}
-
-
-
- ))}
-
-
-
-
-
-
- Go back
- {" "}
-
-
-
- );
-};
-
-export default MeetingDetail;
diff --git a/src/2024/TalkDetail/MeetingDetailContainer2024.tsx b/src/2024/TalkDetail/MeetingDetailContainer2024.tsx
index 04ca59591..63468fc0b 100644
--- a/src/2024/TalkDetail/MeetingDetailContainer2024.tsx
+++ b/src/2024/TalkDetail/MeetingDetailContainer2024.tsx
@@ -1,4 +1,8 @@
import { Color } from "@styles/colors";
+import {
+ ROUTE_2024_SPEAKER_DETAIL,
+ ROUTE_2024_TALKS,
+} from "@constants/routes";
import React, { FC, useEffect } from "react";
import { NotFoundError } from "@components/NotFoundError/NotFoundError";
import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
@@ -7,7 +11,7 @@ import { useParams } from "react-router";
import conferenceData from "@data/2024.json";
import { useFetchTalksById } from "@hooks/useFetchTalks";
import { useFetchSpeakers } from "@hooks/useFetchSpeakers";
-import MeetingDetail from "./MeetingDetail";
+import MeetingDetail from "@views/MeetingDetail/MeetingDetail";
import { ISpeaker } from "@/types/speakers";
import { Session } from "@/types/sessions";
@@ -53,6 +57,10 @@ export const MeetingDetailContainer2024: FC<
)}
{!isLoading &&
diff --git a/src/2024/Talks/Talks2024.tsx b/src/2024/Talks/Talks2024.tsx
deleted file mode 100644
index f71381ee4..000000000
--- a/src/2024/Talks/Talks2024.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import React, { FC, useEffect, useState } from "react";
-import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import TitleSection from "@components/SectionTitle/TitleSection";
-import { Color } from "@styles/colors";
-import conferenceData from "@data/2024.json";
-
-import { useFetchTalks } from "@hooks/useFetchTalks";
-import { Dropdown, DropdownChangeEvent } from "primereact/dropdown";
-import "primereact/resources/themes/lara-light-indigo/theme.css";
-import "@styles/theme.css";
-import {
- StyledMarginBottom,
- StyledSpeakersSection,
- StyledTitleContainer,
- StyledTitleIcon,
- StyledWaveContainer,
-} from "@views/Talks/Talks.style";
-import TrackInformation from "@components/common/TrackInformation";
-import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
-
-interface TrackInfo {
- name: string;
- code?: string;
-}
-
-const Talks2024: FC> = () => {
- const [selectedGroupId, setSelectedGroupId] = useState(
- null,
- );
- const { isLoading, error, data } = useFetchTalks("2024");
-
- useEffect(() => {
- const sessionSelectedGroupCode =
- sessionStorage.getItem("selectedGroupCode");
- const sessionSelectedGroupName =
- sessionStorage.getItem("selectedGroupName");
-
- document.title = `Talks - ${conferenceData.title} - ${conferenceData.edition}`;
-
- if (sessionSelectedGroupCode && sessionSelectedGroupName) {
- setSelectedGroupId({
- name: sessionSelectedGroupName,
- code: sessionSelectedGroupCode,
- });
- }
- }, []);
-
- useSentryErrorReport(error);
-
- // Helper function to remove text between parentheses
- const removeParenthesesContent = (text: string): string => {
- return text.replace(/\s*\([^)]*\)/g, "");
- };
-
- const dropDownOptions = [
- { name: "All Tracks", code: undefined },
- ...(data !== undefined
- ? data.flatMap((group) => ({
- code: group.groupId.toString(),
- name: removeParenthesesContent(group.groupName),
- }))
- : []),
- ];
-
- const filteredTalks = selectedGroupId?.code
- ? data?.filter((talk) => talk.groupId.toString() === selectedGroupId.code)
- : data;
-
- const onChangeSelectedTrack = (e: DropdownChangeEvent) => {
- const value = e.value;
- setSelectedGroupId(value || null);
- sessionStorage.setItem("selectedGroupCode", value?.code || "");
- sessionStorage.setItem("selectedGroupName", value?.name || "");
- };
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isLoading && Loading
}
- {conferenceData.hideTalks ? (
-
- No talks selected yet. Keep in touch in our social media for
- upcoming announcements
-
- ) : (
- filteredTalks &&
- Array.isArray(filteredTalks) && (
- <>
-
-
-
-
- {filteredTalks.map((track) => (
-
- ))}
- >
- )
- )}
-
-
-
- >
- );
-};
-
-export default Talks2024;
diff --git a/src/2024/Talks/TalksWrapper.tsx b/src/2024/Talks/TalksWrapper.tsx
new file mode 100644
index 000000000..471d5a692
--- /dev/null
+++ b/src/2024/Talks/TalksWrapper.tsx
@@ -0,0 +1,110 @@
+import React, { FC } from "react";
+import Talks from "@views/Talks/Talks";
+import data2024 from "@data/2024.json";
+import { TopRatedTalk, TopTalkWithSpeaker } from "@/types/sessions";
+import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes";
+
+const topTenTalks: Array = [
+ {
+ id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
+ speaker: "Victor Rentea",
+ talk: "Top 10 Rest API Design Falls",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
+ },
+ {
+ id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
+ speaker: "Laura Perea",
+ talk: "GenAI among us",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
+ },
+ {
+ id: "eb3852c1-acf8-42a6-988d-365fad2a5668",
+ speaker: "Brian Vermeer",
+ talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"),
+ },
+ {
+ id: "625b53c9-edea-4e47-a5ba-2ee661c539e3",
+ speaker: "Álvaro Sánchez-Mariscal",
+ talk: "Revealing the magic behind Java annotations",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"),
+ },
+ {
+ id: "7b1c534c-39a5-4398-93e5-626010f00198",
+ speaker: "Alexander Chatzizacharias",
+ talk: "What is multimodal RAG, and can we build a village with it?",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"),
+ },
+ {
+ id: "ebab2b92-503f-4baa-b3ab-064865853223",
+ speaker: "Bert Jan Schrijver",
+ talk: "Generic or Specific? Making sensible software design decisions",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"),
+ },
+ {
+ id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
+ speaker: "Marc Nuri",
+ talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
+ },
+ {
+ id: "10937eaf-a0da-48c9-82d6-8711ca26fb16",
+ speaker: "Andres Almiray",
+ talk: "Maven Productivity Tips",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"),
+ },
+ {
+ id: "5ce27637-12b4-4dfe-830d-166d88c837ad",
+ speaker: "Milen Dyankov",
+ talk: "AI for Java Developers - From Buzzword to Code",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"),
+ },
+ {
+ id: "2aea7252-6822-4f42-a9d4-fa830f29df40",
+ speaker: "Rijo Sam",
+ talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"),
+ },
+];
+
+const topThreeTalks: Array = [
+ {
+ id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
+ award: "Funniest talk",
+ speaker: "Victor Rentea",
+ speakerImage:
+ "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png",
+ talk: "Top 10 Rest API Design Falls",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
+ },
+ {
+ id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
+ speaker: "Laura Perea",
+ award: "Best Rated",
+ speakerImage:
+ "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png",
+ talk: "GenAI among us",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
+ },
+ {
+ id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
+ speaker: "Marc Nuri",
+ award: "Most original",
+ speakerImage:
+ "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg",
+ talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
+ },
+];
+
+export const TalksWrapper: FC = () => {
+ return (
+
+ );
+};
+
+export default TalksWrapper;
diff --git a/src/components/YearSpecific/Speakers/Speakers2023.tsx b/src/components/YearSpecific/Speakers/Speakers2023.tsx
deleted file mode 100644
index 056e9fc0c..000000000
--- a/src/components/YearSpecific/Speakers/Speakers2023.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React, { FC } from "react";
-import Speakers from "./Speakers";
-import webData from "../../../data/2023.json";
-
-const Speakers2023: FC> = () => {
- return ;
-};
-
-export default Speakers2023;
\ No newline at end of file
diff --git a/src/components/YearSpecific/Speakers/Speakers2024.tsx b/src/components/YearSpecific/Speakers/Speakers2024.tsx
deleted file mode 100644
index fd7e87207..000000000
--- a/src/components/YearSpecific/Speakers/Speakers2024.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React, { FC } from "react";
-import Speakers from "./Speakers";
-import webData from "@data/2024.json";
-
-export const Speakers2024: FC> = () => {
- return ;
-};
diff --git a/src/types/sessions.ts b/src/types/sessions.ts
index 4b89f0f4c..18afaf16c 100644
--- a/src/types/sessions.ts
+++ b/src/types/sessions.ts
@@ -76,6 +76,26 @@ export interface IMeetingDetailProps {
meeting: IMeeting;
speakers?: ISpeaker[];
openFeedbackId: string;
+ edition?: string;
+ speakerDetailRoute?: string;
+ talksRoute?: string;
+}
+
+export interface TrackInfo {
+ name: string;
+ code?: string;
+}
+
+export interface TopRatedTalk {
+ id: string;
+ speaker: string;
+ talk: string;
+ link: string;
+}
+
+export interface TopTalkWithSpeaker extends TopRatedTalk {
+ speakerImage: string;
+ award: string;
}
export type MyType = {
diff --git a/src/utils/lazyComponents.ts b/src/utils/lazyComponents.ts
index 3399616af..8ffc078e6 100644
--- a/src/utils/lazyComponents.ts
+++ b/src/utils/lazyComponents.ts
@@ -126,8 +126,8 @@ export const HomeWrapper2024 = lazy(() =>
}))
);
export const Speakers2024 = lazy(() =>
- import("../components/YearSpecific/Speakers/Speakers2024").then((value) => ({
- default: value.Speakers2024,
+ import("../2024/Speakers/SpeakersWrapper").then((value) => ({
+ default: value.SpeakersWrapper,
}))
);
export const SpeakerDetailContainer2024 = lazy(() =>
@@ -135,8 +135,10 @@ export const SpeakerDetailContainer2024 = lazy(() =>
default: value.SpeakerDetailContainer2024,
}))
);
-export const CfpSection2024 = lazy(() => import("../2024/Cfp/CfpSection2024"));
-export const Talks2024 = lazy(() => import("../2024/Talks/Talks2024"));
+export const CfpSection2024 = lazy(() =>
+ import("../2024/Cfp/CfpSectionWrapper")
+);
+export const Talks2024 = lazy(() => import("../2024/Talks/TalksWrapper"));
export const Schedule2024 = lazy(() => import("../2024/Schedule/Schedule2024"));
export const JobOffers2024 = lazy(() => import("../2024/JobOffers/JobOffers2024"));
export const MeetingDetailContainer2024 = lazy(() =>
@@ -154,8 +156,8 @@ export const Diversity2023 = lazy(() => import("../2023/Diversity/Diversity2023"
export const Schedule2023 = lazy(() => import("../2023/Schedule/Schedule2023"));
export const Workshops2023 = lazy(() => import("../2023/Workshops/Workshops2023"));
export const JobOffers2023 = lazy(() => import("../2023/JobOffers/JobOffers2023"));
-export const CfpSection2023 = lazy(() => import("../2023/Cfp/CfpSection2023"));
-export const Speakers2023 = lazy(() => import("../2023/Speakers/Speakers2023"));
+export const CfpSection2023 = lazy(() => import("../2023/Cfp/CfpSectionWrapper"));
+export const Speakers2023 = lazy(() => import("../2023/Speakers/SpeakersWrapper"));
export const SpeakerDetailContainer2023 = lazy(
() => import("../2023/SpeakerDetail/SpeakerDetailContainer2023")
);
diff --git a/src/views/Cfp/CfpData.ts b/src/views/Cfp/CfpData.ts
index 8492a1548..3fbeb5509 100644
--- a/src/views/Cfp/CfpData.ts
+++ b/src/views/Cfp/CfpData.ts
@@ -1,17 +1,17 @@
-interface CFpTrack {
- id: string;
- name: string;
- members: CfpMember[];
+export interface CfpTrack {
+ id: string;
+ name: string;
+ members: CfpMember[];
}
interface CfpMember {
- name: string;
- photo?: string;
- linkedIn?: string;
- twitter?: string;
+ name: string;
+ photo?: string;
+ linkedIn?: string;
+ twitter?: string;
}
-export const data: CFpTrack[] = [
+export const data: CfpTrack[] = [
{
name: "Java & JVM",
id: "656fece2-9447-4dbe-8a78-8dc6aa7124f2",
@@ -154,5 +154,5 @@ export const data: CFpTrack[] = [
];
export interface CfpTrackProps {
- track: CFpTrack;
+ track: CfpTrack;
}
diff --git a/src/views/Cfp/CfpSection.tsx b/src/views/Cfp/CfpSection.tsx
index 2d18aab0e..86e2c2826 100644
--- a/src/views/Cfp/CfpSection.tsx
+++ b/src/views/Cfp/CfpSection.tsx
@@ -16,7 +16,7 @@ import {
StyledSocialIconsWrapper,
} from "../About/components/Style.AboutCard";
import conferenceData from "@data/2026.json";
-import { CfpTrackProps, data } from "./CfpData";
+import { CfpTrack, CfpTrackProps, data } from "./CfpData";
import { MemberName, TrackName } from "./Cfp.style";
import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate";
@@ -58,13 +58,21 @@ export const CfpTrackComponent: FC> = ({
>
);
-const CfpSection: FC> = () => {
+interface CfpSectionProps {
+ conferenceConfig?: typeof conferenceData;
+ cfpData?: CfpTrack[];
+}
+
+const CfpSection: FC> = ({
+ conferenceConfig = conferenceData,
+ cfpData = data,
+}) => {
const { width } = useWindowSize();
- useDocumentTitleUpdater("CFP Committee", conferenceData.edition);
+ useDocumentTitleUpdater("CFP Committee", conferenceConfig.edition);
const isCFPCommitteeReady = (): boolean =>
- data.every((track) => track.members.length > 0);
+ cfpData.every((track) => track.members.length > 0);
return (
<>
@@ -89,7 +97,7 @@ const CfpSection: FC> = () => {
CFP Committee in progress
)}
{isCFPCommitteeReady() &&
- data.map((track) => (
+ cfpData.map((track) => (
))}
diff --git a/src/views/MeetingDetail/MeetingDetail.tsx b/src/views/MeetingDetail/MeetingDetail.tsx
index d5ed62d99..69be9c800 100644
--- a/src/views/MeetingDetail/MeetingDetail.tsx
+++ b/src/views/MeetingDetail/MeetingDetail.tsx
@@ -103,10 +103,13 @@ const MeetingDetail: FC> = ({
meeting,
speakers: mySpeakers,
openFeedbackId,
+ edition = conferenceData.edition,
+ speakerDetailRoute = ROUTE_SPEAKER_DETAIL,
+ talksRoute = ROUTE_TALKS,
}) => {
const { width } = useWindowSize();
- useDocumentTitleUpdater(meeting.title, conferenceData.edition);
+ useDocumentTitleUpdater(meeting.title, edition);
const finalMeetingInfo: MyType = {
...meeting,
@@ -242,7 +245,7 @@ const MeetingDetail: FC> = ({
/>
-
+
{speaker.fullName}
@@ -254,7 +257,7 @@ const MeetingDetail: FC> = ({
> = ({
speaker,
+ edition = conferenceData.edition,
+ speakersRoute = ROUTE_SPEAKERS,
+ talkDetailRoute = ROUTE_TALK_DETAIL,
}) => {
const { width } = useWindowSize();
useEffect(() => {
- document.title = `${speaker.fullName} — ${conferenceData.title} — ${conferenceData.edition}`;
- }, [speaker.fullName]);
+ document.title = `${speaker.fullName} — ${conferenceData.title} — ${edition}`;
+ }, [speaker.fullName, edition]);
const hasSessions = (): boolean =>
(speaker.sessions && speaker.sessions.length > 0) || false;
@@ -124,7 +130,7 @@ const SpeakerDetail: FC
> = ({
{speaker?.sessions?.map((session) => (
> = ({
)}
(
>
);
-const Speakers: FC> = () => {
+interface SpeakersProps {
+ conferenceConfig?: typeof webData;
+}
+
+const Speakers: FC> = ({
+ conferenceConfig = webData,
+}) => {
const { width } = useWindowSize();
const today = new Date();
const isBetween = (startDay: Date, endDay: Date): boolean =>
startDay < new Date() && endDay > today;
- const { error, data, isLoading } = useFetchSpeakers();
+ const { error, data, isLoading } = useFetchSpeakers(conferenceConfig.edition);
useSentryErrorReport(error);
@@ -44,11 +50,11 @@ const Speakers: FC> = () => {
}, []);
useEffect(() => {
- document.title = `Speakers — ${webData.title} — ${webData.edition}`;
+ document.title = `Speakers — ${conferenceConfig.title} — ${conferenceConfig.edition}`;
});
- const CFPStartDay = new Date(webData.cfp.startDay);
- const CFPEndDay = new Date(webData.cfp.endDay);
+ const CFPStartDay = new Date(conferenceConfig.cfp.startDay);
+ const CFPEndDay = new Date(conferenceConfig.cfp.endDay);
return (
<>
@@ -75,11 +81,11 @@ const Speakers: FC> = () => {
)}
- {webData.hideSpeakers ? (
+ {conferenceConfig.hideSpeakers ? (
No selected speakers yet. Keep in touch in our social media for
upcoming announcements
@@ -89,7 +95,7 @@ const Speakers: FC> = () => {
))
)}
diff --git a/src/views/Talks/Talks.test.tsx b/src/views/Talks/Talks.test.tsx
index 43c14857c..3019c2026 100644
--- a/src/views/Talks/Talks.test.tsx
+++ b/src/views/Talks/Talks.test.tsx
@@ -7,7 +7,11 @@ import {
} from "../../utils/testing/testUtils";
import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes";
import { useFetchTalks } from "@hooks/useFetchTalks";
-import { IGroup } from "@/types/sessions";
+import {
+ IGroup,
+ TopRatedTalk,
+ TopTalkWithSpeaker,
+} from "@/types/sessions";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
@@ -37,6 +41,99 @@ vi.mock("../../utils/testing/testUtils", async (importOriginal) => {
};
});
+const mockTopTenTalks: TopRatedTalk[] = [
+ {
+ id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
+ speaker: "Victor Rentea",
+ talk: "Top 10 Rest API Design Falls",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
+ },
+ {
+ id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
+ speaker: "Laura Perea",
+ talk: "GenAI among us",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
+ },
+ {
+ id: "eb3852c1-acf8-42a6-988d-365fad2a5668",
+ speaker: "Brian Vermeer",
+ talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"),
+ },
+ {
+ id: "625b53c9-edea-4e47-a5ba-2ee661c539e3",
+ speaker: "Álvaro Sánchez-Mariscal",
+ talk: "Revealing the magic behind Java annotations",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"),
+ },
+ {
+ id: "7b1c534c-39a5-4398-93e5-626010f00198",
+ speaker: "Alexander Chatzizacharias",
+ talk: "What is multimodal RAG, and can we build a village with it?",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"),
+ },
+ {
+ id: "ebab2b92-503f-4baa-b3ab-064865853223",
+ speaker: "Bert Jan Schrijver",
+ talk: "Generic or Specific? Making sensible software design decisions",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"),
+ },
+ {
+ id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
+ speaker: "Marc Nuri",
+ talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
+ },
+ {
+ id: "10937eaf-a0da-48c9-82d6-8711ca26fb16",
+ speaker: "Andres Almiray",
+ talk: "Maven Productivity Tips",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"),
+ },
+ {
+ id: "5ce27637-12b4-4dfe-830d-166d88c837ad",
+ speaker: "Milen Dyankov",
+ talk: "AI for Java Developers - From Buzzword to Code",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"),
+ },
+ {
+ id: "2aea7252-6822-4f42-a9d4-fa830f29df40",
+ speaker: "Rijo Sam",
+ talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"),
+ },
+];
+
+const mockTopThreeTalks: TopTalkWithSpeaker[] = [
+ {
+ id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
+ award: "Funniest talk",
+ speaker: "Victor Rentea",
+ speakerImage:
+ "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png",
+ talk: "Top 10 Rest API Design Falls",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
+ },
+ {
+ id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
+ speaker: "Laura Perea",
+ award: "Best Rated",
+ speakerImage:
+ "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png",
+ talk: "GenAI among us",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
+ },
+ {
+ id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
+ speaker: "Marc Nuri",
+ award: "Most original",
+ speakerImage:
+ "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg",
+ talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
+ link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
+ },
+];
+
describe("Talks", () => {
beforeEach(() => {
// Reset all mocks before each test
@@ -106,7 +203,12 @@ describe("Talks", () => {
// Tests for the topThreeTalks array
it("renders the top three talks section with correct awards", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
// Check for award titles
expect(screen.getByText("Funniest talk")).toBeInTheDocument();
@@ -115,7 +217,12 @@ describe("Talks", () => {
});
it("renders all top three talks with correct speaker names and talk titles", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
// Check for speaker names
expect(screen.getByText("Victor Rentea")).toBeInTheDocument();
@@ -135,7 +242,12 @@ describe("Talks", () => {
});
it("renders top three talks with correct images", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
// Check for images with correct src attributes
const images = screen.getAllByRole("img");
@@ -173,7 +285,12 @@ describe("Talks", () => {
});
it("renders top three talks with correct links", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
// Check that links are correctly formatted
const victorLink = screen.getByText("Victor Rentea").closest("a");
@@ -196,12 +313,22 @@ describe("Talks", () => {
// Tests for the topTenTalks array
it("renders the top ten talks section", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
expect(screen.getByText("🔝 Top Ten rated talks")).toBeInTheDocument();
});
it("renders all top ten talks with correct links", () => {
- renderWithQueryClient();
+ renderWithQueryClient(
+ ,
+ );
// Check for specific talks
expect(
diff --git a/src/views/Talks/Talks.tsx b/src/views/Talks/Talks.tsx
index 143145de6..db0f9c04c 100644
--- a/src/views/Talks/Talks.tsx
+++ b/src/views/Talks/Talks.tsx
@@ -17,30 +17,27 @@ import { SelectButton, SelectButtonChangeEvent } from "primereact/selectbutton";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "@styles/theme.css";
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
-import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes";
-
-interface TrackInfo {
- name: string;
- code?: string;
-}
-
-interface TopRatedTalk {
- id: string;
- speaker: string;
- talk: string;
- link: string;
-}
+import {
+ TopRatedTalk,
+ TopTalkWithSpeaker,
+ TrackInfo,
+} from "@/types/sessions";
-interface TopTalkWithSpeaker extends TopRatedTalk {
- speakerImage: string;
- award: string;
+interface TalksProps {
+ conferenceConfig?: typeof conferenceData;
+ topTenTalks?: Array;
+ topThreeTalks?: Array;
}
-const Talks: FC> = () => {
+const Talks: FC> = ({
+ conferenceConfig = conferenceData,
+ topTenTalks = [],
+ topThreeTalks = [],
+}) => {
const [selectedGroupId, setSelectedGroupId] = useState(
null,
);
- const { isLoading, error, data } = useFetchTalks();
+ const { isLoading, error, data } = useFetchTalks(conferenceConfig.edition);
useEffect(() => {
const sessionSelectedGroupCode =
@@ -48,7 +45,7 @@ const Talks: FC> = () => {
const sessionSelectedGroupName =
sessionStorage.getItem("selectedGroupName");
- document.title = `Talks - ${conferenceData.title} - ${conferenceData.edition}`;
+ document.title = `Talks - ${conferenceConfig.title} - ${conferenceConfig.edition}`;
if (sessionSelectedGroupCode && sessionSelectedGroupName) {
setSelectedGroupId({
@@ -56,7 +53,7 @@ const Talks: FC> = () => {
code: sessionSelectedGroupCode,
});
}
- }, []);
+ }, [conferenceConfig.title, conferenceConfig.edition]);
useSentryErrorReport(error);
@@ -68,106 +65,15 @@ const Talks: FC> = () => {
{ name: "All Tracks", code: undefined },
...(data !== undefined
? data
- .flatMap((group) => ({
- code: group?.groupId?.toString(),
- name: removeParenthesesContent(group.groupName),
- }))
- .sort((a, b) => a.name.localeCompare(b.name))
+ .flatMap((group) => ({
+ code: group?.groupId?.toString(),
+ name: removeParenthesesContent(group.groupName),
+ }))
+ .sort((a, b) => a.name.localeCompare(b.name))
: []),
];
- const topTenTalks: Array = [
- {
- id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
- speaker: "Victor Rentea",
- talk: "Top 10 Rest API Design Falls",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
- },
- {
- id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
- speaker: "Laura Perea",
- talk: "GenAI among us",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
- },
- {
- id: "eb3852c1-acf8-42a6-988d-365fad2a5668",
- speaker: "Brian Vermeer",
- talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"),
- },
- {
- id: "625b53c9-edea-4e47-a5ba-2ee661c539e3",
- speaker: "Álvaro Sánchez-Mariscal",
- talk: "Revealing the magic behind Java annotations",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"),
- },
- {
- id: "7b1c534c-39a5-4398-93e5-626010f00198",
- speaker: "Alexander Chatzizacharias",
- talk: "What is multimodal RAG, and can we build a village with it?",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"),
- },
- {
- id: "ebab2b92-503f-4baa-b3ab-064865853223",
- speaker: "Bert Jan Schrijver",
- talk: "Generic or Specific? Making sensible software design decisions",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"),
- },
- {
- id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
- speaker: "Marc Nuri",
- talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
- },
- {
- id: "10937eaf-a0da-48c9-82d6-8711ca26fb16",
- speaker: "Andres Almiray",
- talk: "Maven Productivity Tips",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"),
- },
- {
- id: "5ce27637-12b4-4dfe-830d-166d88c837ad",
- speaker: "Milen Dyankov",
- talk: "AI for Java Developers - From Buzzword to Code",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"),
- },
- {
- id: "2aea7252-6822-4f42-a9d4-fa830f29df40",
- speaker: "Rijo Sam",
- talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"),
- },
- ];
- const topThreeTalks: Array = [
- {
- id: "df057475-0b6a-4fab-8e0d-c5576230dd5c",
- award: "Funniest talk",
- speaker: "Victor Rentea",
- speakerImage:
- "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png",
- talk: "Top 10 Rest API Design Falls",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"),
- },
- {
- id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba",
- speaker: "Laura Perea",
- award: "Best Rated",
- speakerImage:
- "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png",
- talk: "GenAI among us",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"),
- },
- {
- id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a",
- speaker: "Marc Nuri",
- award: "Most original",
- speakerImage:
- "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg",
- talk: "Model Context Protocol Servers 101: Unlocking the Power of AI",
- link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"),
- },
- ];
const filteredTalks = selectedGroupId?.code
? data?.filter((talk) => talk.groupId.toString() === selectedGroupId.code)
@@ -267,7 +173,7 @@ const Talks: FC> = () => {
{isLoading && Loading
}
- {conferenceData.hideTalks ? (
+ {conferenceConfig.hideTalks ? (
No talks selected yet. Keep in tap in our social media for
upcoming announcements
@@ -325,8 +231,8 @@ const Talks: FC> = () => {
))}
>
diff --git a/src/views/Workshops/Workshops.tsx b/src/views/Workshops/Workshops.tsx
index 06069418c..39dce348e 100644
--- a/src/views/Workshops/Workshops.tsx
+++ b/src/views/Workshops/Workshops.tsx
@@ -1,7 +1,7 @@
import React, { FC, useEffect } from "react";
import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
import { useFetchTalks } from "@hooks/useFetchTalks";
-import { TalkCard } from "../Talks/components/TalkCard";
+import { TalkCard } from "@components/common/TalkCard";
import conferenceData from "@data/2025.json";
import { styled } from "styled-components";
import { BIG_BREAKPOINT } from "@constants/BreakPoints";
From 6ecb5ff2813cbe5919db710a442567679435d2d3 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 7 Dec 2025 20:45:55 +0100
Subject: [PATCH 11/21] refactor: centralize ActionButtons component and remove
redundant tests from year-specific directories.
---
src/2023/Cfp/CfpSection2023.test.tsx | 115 ------------------
.../ActionButtons/ActionButtons.tsx | 59 ---------
src/2023/Home/components/Home/Home.tsx | 17 ++-
src/2024/Cfp/CfpSection.test.tsx | 28 -----
src/2024/Cfp/TrackComponent.test.tsx | 39 ------
src/2024/Home/Home.tsx | 15 ++-
src/2024/Home/components/ActionButtons.tsx | 59 ---------
src/2024/Talks/Talks.test.tsx | 41 -------
.../ActionButtons/ActionButtons.tsx | 63 ----------
src/2025/Home/components/Home/Home.tsx | 17 ++-
src/data/2025.json | 8 +-
.../ActionButtons/ActionButtons.tsx | 45 +++++--
src/views/Home/components/Home/Home.tsx | 17 ++-
13 files changed, 100 insertions(+), 423 deletions(-)
delete mode 100644 src/2023/Cfp/CfpSection2023.test.tsx
delete mode 100644 src/2023/Home/components/ActionButtons/ActionButtons.tsx
delete mode 100644 src/2024/Cfp/CfpSection.test.tsx
delete mode 100644 src/2024/Cfp/TrackComponent.test.tsx
delete mode 100644 src/2024/Home/components/ActionButtons.tsx
delete mode 100644 src/2024/Talks/Talks.test.tsx
delete mode 100644 src/2025/Home/components/ActionButtons/ActionButtons.tsx
diff --git a/src/2023/Cfp/CfpSection2023.test.tsx b/src/2023/Cfp/CfpSection2023.test.tsx
deleted file mode 100644
index 59cd39a0e..000000000
--- a/src/2023/Cfp/CfpSection2023.test.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { vi } from "vitest";
-
-// Mock useWindowSize to control the window size in tests
-vi.mock("react-use", () => ({
- useWindowSize: vi.fn(),
-}));
-
-import React from "react";
-import { render, screen, waitFor } from "@testing-library/react";
-import "@testing-library/jest-dom";
-import CfpSection2023 from "./CfpSection2023";
-import { useWindowSize } from "react-use";
-import conferenceData from "../../data/2023.json";
-import { data } from "./CfpData";
-
-describe("CfpSection2023", () => {
- beforeEach(() => {
- // Reset the mock before each test
- useWindowSize.mockReset();
- useWindowSize.mockReturnValue({ width: 1024 }); // Default width
- });
-
- it("should render without crashing", () => {
- render();
- });
-
- it("should render the title and subtitle", () => {
- render();
- expect(
- screen.getByText("CFP Committee", { exact: false }),
- ).toBeInTheDocument();
- expect(
- screen.getByText(
- "We're excited to announce the members of the Call for Papers committee for the next DevBcn conference! These experienced professionals will be reviewing and selecting the best talks and workshops for the upcoming event.",
- ),
- ).toBeInTheDocument();
- });
-
- it("should render the tracks and members", () => {
- render();
- data.forEach((track) => {
- expect(screen.getAllByText(track.name, { exact: false })).not.toBeNull();
- track.members
- .filter((member) => member.photo !== "")
- .forEach((member) => {
- expect(
- screen.getAllByText(member.name, { exact: false }),
- ).not.toBeNull();
- });
- });
- });
-
- it("should render member photos", () => {
- render();
- data.forEach((track) => {
- track.members
- .filter((member) => member.photo !== "")
- .forEach((member) => {
- const image = screen.getAllByAltText(member.name);
- expect(image).not.toBeNull();
- expect(image.at(0)).toHaveAttribute("src", member.photo);
- });
- });
- });
-
- it("should render twitter links", () => {
- render();
- data.forEach((track) => {
- track.members
- .filter((member) => member.twitter !== "")
- .forEach((member) => {
- const twitterLinks = screen.getAllByRole("link");
- const twitterLink = twitterLinks.find(
- (link) => link.getAttribute("href") === member.twitter,
- );
- expect(twitterLink).toBeInTheDocument();
- expect(twitterLink).toHaveAttribute("href", member.twitter);
- });
- });
- });
-
- it("should render linkedIn links", () => {
- render();
- data.forEach((track) => {
- track.members
- .filter((member) => member.linkedIn !== "")
- .forEach((member) => {
- const linkedInLinks = screen.getAllByRole("link");
- const linkedInLink = linkedInLinks.find(
- (link) => link.getAttribute("href") === member.linkedIn,
- );
- expect(linkedInLink).toBeInTheDocument();
- expect(linkedInLink).toHaveAttribute("href", member.linkedIn);
- });
- });
- });
-
- it("should update the document title", async () => {
- render();
- await waitFor(() => {
- expect(document.title).toBe(
- `CFP Committee — DevBcn - Barcelona Developers Conference — ${conferenceData.edition}`,
- );
- });
- });
-
- it("should not render the icons when the width is smaller than the breakpoint", () => {
- (useWindowSize as jest.Mock).mockReturnValue({ width: 767 });
- render();
- const lessIcon = screen.queryByAltText("more than - icon");
- const moreIcon = screen.queryByAltText("Less than - icon");
- expect(lessIcon).not.toBeInTheDocument();
- expect(moreIcon).not.toBeInTheDocument();
- });
-});
diff --git a/src/2023/Home/components/ActionButtons/ActionButtons.tsx b/src/2023/Home/components/ActionButtons/ActionButtons.tsx
deleted file mode 100644
index 0986c80ba..000000000
--- a/src/2023/Home/components/ActionButtons/ActionButtons.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React, { FC, useCallback } from "react";
-import data from "../../../../data/2023.json";
-import Button from "../../../../components/UI/Button";
-import { styled } from "styled-components";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
-import { gaEventTracker } from "../../../../components/analytics/Analytics";
-import { useDateInterval } from "../../../../hooks/useDateInterval";
-import { useUrlBuilder } from "../../../../services/urlBuilder";
-
-const StyledActionDiv = styled.div`
- display: flex;
- text-align: center;
-
- @media (max-width: ${BIG_BREAKPOINT}px) {
- flex-direction: column;
- width: 75%;
- }
-`;
-
-const ActionButtons: FC> = () => {
- const { isTicketsDisabled, isSponsorDisabled, isCfpDisabled } =
- useDateInterval(new Date(), data);
-
- const trackSponsorshipInfo = useCallback(() => {
- gaEventTracker("sponsorship", "sponsorship");
- }, []);
-
- const trackTickets = useCallback(() => {
- gaEventTracker("ticket", "tickets");
- }, []);
-
- const trackCFP = useCallback(() => {
- gaEventTracker("CFP", "CFP");
- }, []);
-
- return (
-
-
-
-
-
- );
-};
-export default ActionButtons;
diff --git a/src/2023/Home/components/Home/Home.tsx b/src/2023/Home/components/Home/Home.tsx
index 613afd16e..b2f49d3d4 100644
--- a/src/2023/Home/components/Home/Home.tsx
+++ b/src/2023/Home/components/Home/Home.tsx
@@ -14,8 +14,9 @@ import {
StyleHomeContainer,
} from "./Style.Home";
import { useWindowSize } from "react-use";
+import { useDateInterval } from "@hooks/useDateInterval";
import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper";
-import ActionButtons from "../ActionButtons/ActionButtons";
+import ActionButtons from "@views/Home/components/ActionButtons/ActionButtons";
import { styled } from "styled-components";
import { Color } from "@styles/colors";
import InfoButtons from "../InfoButtons/InfoButtons";
@@ -33,6 +34,8 @@ const StyledLogo = styled.img`
`;
const Home: FC> = () => {
const { width } = useWindowSize();
+ const { isTicketsDisabled, isSponsorDisabled, isCfpDisabled } =
+ useDateInterval(new Date(), data);
const startDay = data.startDay;
return (
@@ -79,7 +82,17 @@ const Home: FC> = () => {
renderer={TimeCountDown}
/>
)}
- {data.actionButtons && }
+ {data.actionButtons && (
+
+ )}
{data.showInfoButtons && }