From 405040368e0865d1d52a0c322caa764622ee853a Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:09:37 +0100
Subject: [PATCH 01/29] feat: 2025 edition
---
src/2024/Home/DateUtil.ts | 16 +
src/2024/Home/Home.tsx | 141 +++
src/2024/Home/Style.Home.tsx | 179 ++++
src/2024/Home/components/ActionButtons.tsx | 67 ++
src/2024/Home/components/TimeCountdown.tsx | 103 ++
src/2024/HomeWrapper2024.tsx | 44 +
src/2024/Navigation/NavigationData.ts | 44 +
src/2024/Speakers/SpeakerInformation.test.tsx | 19 +
src/2024/Speakers/SpeakerInformation.tsx | 167 ++++
src/2024/Speakers/Speakers.style.ts | 91 ++
src/2024/Speakers/Speakers2024.tsx | 176 ++++
src/2024/Speakers/UseFetchSpeakers.test.tsx | 144 +++
src/2024/Speakers/UseFetchSpeakers.ts | 33 +
src/2024/SpeakersCarousel/SpeakerSwiper.tsx | 107 +++
.../SpeakersCarousel/SpeakersCarousel.scss | 29 +
.../SpeakersCarousel/SpeakersCarousel.tsx | 106 +++
src/2024/Sponsors/BasicSponsor.tsx | 103 ++
src/2024/Sponsors/Communities.tsx | 98 ++
src/2024/Sponsors/MediaPartners.tsx | 98 ++
src/2024/Sponsors/PremiumSponsors.tsx | 105 +++
src/2024/Sponsors/RegularSponsors.tsx | 101 ++
src/2024/Sponsors/SponsorBadge.tsx | 36 +
src/2024/Sponsors/Sponsors.style.ts | 296 ++++++
src/2024/Sponsors/Sponsors.tsx | 63 ++
src/2024/Sponsors/SponsorsData.ts | 179 ++++
src/2024/Sponsors/Supporters.test.tsx | 72 ++
src/2024/Sponsors/Supporters.tsx | 106 +++
src/2024/Sponsors/TopSponsors.tsx | 97 ++
src/2024/Talks/LiveView.test.tsx | 17 +
src/2024/Talks/LiveView.tsx | 65 ++
src/2024/Talks/Talks.test.tsx | 71 ++
src/2024/Talks/Talks2024.tsx | 145 +++
src/2024/Talks/UseFetchTalks.ts | 134 +++
src/2024/Talks/useFetchTalks.test.tsx | 425 +++++++++
src/App.tsx | 888 ++++++++++--------
src/components/Navigation/Navigation.tsx | 253 ++---
src/components/Navigation/NavigationData.ts | 18 +-
src/components/UI/Button.tsx | 8 +-
src/constants/routes.ts | 19 +
src/data/2023.json | 4 +-
src/data/2024.json | 6 +-
src/data/2025.json | 43 +
src/views/Home/HomeWrapper.tsx | 10 +-
src/views/Home/UseEventEdition.tsx | 6 +-
.../ActionButtons/ActionButtons.tsx | 8 +-
src/views/Home/components/Faqs/Faqs.tsx | 34 +-
src/views/Home/components/Home/Home.tsx | 245 ++---
.../SpeakersCarousel/SpeakerSwiper.tsx | 16 +-
.../Home/components/Sponsors/SponsorsData.ts | 168 +---
49 files changed, 4568 insertions(+), 835 deletions(-)
create mode 100644 src/2024/Home/DateUtil.ts
create mode 100644 src/2024/Home/Home.tsx
create mode 100644 src/2024/Home/Style.Home.tsx
create mode 100644 src/2024/Home/components/ActionButtons.tsx
create mode 100644 src/2024/Home/components/TimeCountdown.tsx
create mode 100644 src/2024/HomeWrapper2024.tsx
create mode 100644 src/2024/Navigation/NavigationData.ts
create mode 100644 src/2024/Speakers/SpeakerInformation.test.tsx
create mode 100644 src/2024/Speakers/SpeakerInformation.tsx
create mode 100644 src/2024/Speakers/Speakers.style.ts
create mode 100644 src/2024/Speakers/Speakers2024.tsx
create mode 100644 src/2024/Speakers/UseFetchSpeakers.test.tsx
create mode 100644 src/2024/Speakers/UseFetchSpeakers.ts
create mode 100644 src/2024/SpeakersCarousel/SpeakerSwiper.tsx
create mode 100644 src/2024/SpeakersCarousel/SpeakersCarousel.scss
create mode 100644 src/2024/SpeakersCarousel/SpeakersCarousel.tsx
create mode 100644 src/2024/Sponsors/BasicSponsor.tsx
create mode 100644 src/2024/Sponsors/Communities.tsx
create mode 100644 src/2024/Sponsors/MediaPartners.tsx
create mode 100644 src/2024/Sponsors/PremiumSponsors.tsx
create mode 100644 src/2024/Sponsors/RegularSponsors.tsx
create mode 100644 src/2024/Sponsors/SponsorBadge.tsx
create mode 100644 src/2024/Sponsors/Sponsors.style.ts
create mode 100644 src/2024/Sponsors/Sponsors.tsx
create mode 100644 src/2024/Sponsors/SponsorsData.ts
create mode 100644 src/2024/Sponsors/Supporters.test.tsx
create mode 100644 src/2024/Sponsors/Supporters.tsx
create mode 100644 src/2024/Sponsors/TopSponsors.tsx
create mode 100644 src/2024/Talks/LiveView.test.tsx
create mode 100644 src/2024/Talks/LiveView.tsx
create mode 100644 src/2024/Talks/Talks.test.tsx
create mode 100644 src/2024/Talks/Talks2024.tsx
create mode 100644 src/2024/Talks/UseFetchTalks.ts
create mode 100644 src/2024/Talks/useFetchTalks.test.tsx
create mode 100644 src/data/2025.json
diff --git a/src/2024/Home/DateUtil.ts b/src/2024/Home/DateUtil.ts
new file mode 100644
index 00000000..75ce2296
--- /dev/null
+++ b/src/2024/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/2024/Home/Home.tsx b/src/2024/Home/Home.tsx
new file mode 100644
index 00000000..5fbf4581
--- /dev/null
+++ b/src/2024/Home/Home.tsx
@@ -0,0 +1,141 @@
+import Countdown from "react-countdown";
+import React, {FC} from "react";
+import LessThanIcon from "../../assets/images/MoreThanBlueWhiteIcon.svg";
+import TimeCountDown from "./components/TimeCountdown";
+import {useWindowSize} from "react-use";
+import {
+ StyledBlueSlash,
+ StyledBottomSlash,
+ StyledDevBcnLogo,
+ StyledGreenSlash,
+ StyledHomeImage,
+ StyledKcdLogo,
+ StyledLessThan,
+ StyledLogoDiv,
+ StyledPlusSign,
+ StyledSubtitle,
+ StyledTitle,
+ StyledTitleContainer,
+ StyledTopSlash,
+ StyleHomeContainer,
+} from "./Style.Home";
+import {formatDateRange} from "./DateUtil";
+import {Link} from "react-router-dom";
+import data from "../../data/2024.json";
+import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import {Color} from "../../styles/colors";
+
+import ActionButtons
+ from "../../views/Home/components/ActionButtons/ActionButtons";
+import InfoButtons from "../../views/Home/components/InfoButtons/InfoButtons";
+import {BIGGER_BREAKPOINT} from "../../constants/BreakPoints";
+
+const Home: FC> = () => {
+ const {width} = useWindowSize();
+
+ return (
+
+
+
+
+
+ +
+
+
+
+
+ The Barcelona Developers Conference {data?.edition}
+
+
+ Former{" "}
+
+ JBCNConf
+ {" "}
+
+
+ Multidisciplinary conference made for Developers and
+ by
+ Developers, to learn and share on the different
+ modern software
+ technologies used across the companies
+
+
+
+ Past events: 2023
+ edition
+
+
+
+
+
+ {data?.startDay &&
+ data.endDay &&
+ formatDateRange(
+ new Date(data.startDay),
+ new Date(data.endDay),
+ )}
+
+
+ La Farga, Hospitalet, Barcelona
+
+
+
+
+ {data?.trackNumber} tracks with the following
+ topics:
+ {data?.tracks}
+
+
+ {data.showCountdown && (
+
+ )}
+ {data?.actionButtons && }
+ {data?.showInfoButtons && }
+
+ {width > BIGGER_BREAKPOINT && (
+
+ )}
+ {width > BIGGER_BREAKPOINT && (
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / /{" "}
+
+
+ )}
+
+ {width > BIGGER_BREAKPOINT && (
+
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / /{" "}
+
+ )}
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/src/2024/Home/Style.Home.tsx b/src/2024/Home/Style.Home.tsx
new file mode 100644
index 00000000..180a00da
--- /dev/null
+++ b/src/2024/Home/Style.Home.tsx
@@ -0,0 +1,179 @@
+import styled from "styled-components";
+import {motion} from "framer-motion";
+import {Color} from "../../styles/colors";
+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.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.h1`
+ padding: 0.5rem 1rem;
+ color: ${Color.WHITE};
+ font-family: "Square 721 Regular", sans-serif;
+`;
+
+export const StyledSubtitle = styled.h2`
+ color: ${(props) => 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.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/2024/Home/components/ActionButtons.tsx b/src/2024/Home/components/ActionButtons.tsx
new file mode 100644
index 00000000..c4489d2c
--- /dev/null
+++ b/src/2024/Home/components/ActionButtons.tsx
@@ -0,0 +1,67 @@
+import {FC, useCallback} from "react";
+import data from "../../../data/2024.json";
+import styled from "styled-components";
+import {BIG_BREAKPOINT} from "../../../constants/BreakPoints";
+import {gaEventTracker} from "../../../components/analytics/Analytics";
+import Button from "../../../components/UI/Button";
+
+const StyledActionDiv = styled.div`
+ display: flex;
+ text-align: center;
+
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ flex-direction: column;
+ width: 75%;
+ }
+`;
+
+const ActionButtons: FC> = () => {
+ const ticketStartDay = new Date(data.tickets.startDay);
+ const ticketEndDay = new Date(data.tickets.endDay);
+ const CFPStartDay = new Date(data.cfp.startDay);
+ const CFPEndDay = new Date(data.cfp.endDay);
+ const sponsorshipStartDay = new Date(data.sponsors.startDate);
+ const sponsorshipEndDay = new Date(data.sponsors.endDate);
+ const today = new Date();
+
+ const isBetween = (startDay: Date, endDay: Date): boolean =>
+ startDay < new Date() && endDay > today;
+
+ const trackSponsorshipInfo = useCallback(() => {
+ gaEventTracker("sponsorship", "sponsorship");
+ }, []);
+
+ const trackTickets = useCallback(() => {
+ gaEventTracker("ticket", "tickets");
+ }, []);
+
+ const trackCFP = useCallback(() => {
+ gaEventTracker("CFP", "CFP");
+ }, []);
+
+ return (
+
+
+ {isBetween(CFPStartDay, CFPEndDay) && (
+
+ )}
+ {isBetween(sponsorshipStartDay, sponsorshipEndDay) && (
+
+ )}
+
+ );
+};
+export default ActionButtons;
diff --git a/src/2024/Home/components/TimeCountdown.tsx b/src/2024/Home/components/TimeCountdown.tsx
new file mode 100644
index 00000000..5bc349af
--- /dev/null
+++ b/src/2024/Home/components/TimeCountdown.tsx
@@ -0,0 +1,103 @@
+import {FC} from 'react';
+import styled from 'styled-components';
+import {Color} from "../../../styles/colors";
+
+const TimeCountDownContainer = styled.div`
+ display: flex;
+ align-items: center;
+ padding-top: 1.5rem;
+`;
+
+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;
+`;
+
+const StyleLine = styled.div`
+ width: 0.75rem;
+ background: ${Color.DARK_BLUE};
+ height: 1.5px;
+`;
+
+const StyledTimerLetters = styled.p`
+ font-size: 0.75rem;
+`;
+
+type TimeCountDownProps = {
+ days: number;
+ hours: number;
+ minutes: number;
+ seconds: number;
+ completed: boolean;
+};
+
+const TimeCountDown: FC> = ({
+ days,
+ hours,
+ minutes,
+ seconds,
+ completed,
+ }) => {
+ if (completed) {
+ return (
+
+
+ 0
+ DAYS
+
+
+
+ 0
+ HOURS
+
+
+
+ 0
+ MINUTES
+
+
+
+ 0
+ SECONDS
+
+
+ );
+ } else {
+ return (
+
+
+ {days}
+ DAYS
+
+
+
+ {hours}
+ HOURS
+
+
+
+ {minutes}
+ MINUTES
+
+
+
+ {seconds}
+ SECONDS
+
+
+ );
+ }
+};
+
+export default TimeCountDown;
diff --git a/src/2024/HomeWrapper2024.tsx b/src/2024/HomeWrapper2024.tsx
new file mode 100644
index 00000000..6ae49e29
--- /dev/null
+++ b/src/2024/HomeWrapper2024.tsx
@@ -0,0 +1,44 @@
+import React, {FC, useState} from "react";
+import styled from "styled-components";
+
+import {useLocation} from "react-router-dom";
+import {BIG_BREAKPOINT} from "../constants/BreakPoints";
+
+import {useEventEdition} from "../views/Home/UseEventEdition";
+import Faqs from "../views/Home/components/Faqs/Faqs";
+
+import Home from "./Home/Home";
+import SpeakersCarousel from "./SpeakersCarousel/SpeakersCarousel";
+import Sponsors from "./Sponsors/Sponsors";
+import {Edition} from "../views/Home/HomeWrapper";
+
+const StyledContainer = styled.div`
+ padding-bottom: 10rem;
+
+ @media only screen and (max-width: ${BIG_BREAKPOINT}px) {
+ padding-bottom: 20rem;
+ }
+`;
+
+export const HomeWrapper2024: FC> = () => {
+ const {hash} = useLocation();
+ const [edition, setEdition] = useState();
+
+ useEventEdition(setEdition);
+ React.useEffect(() => {
+ document.title = `Home - ${edition?.title} - ${edition?.edition}`;
+ if (hash != null && hash !== "") {
+ const scroll = document.getElementById(hash.substring(1));
+ scroll?.scrollIntoView();
+ }
+ }, [hash, edition]);
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/2024/Navigation/NavigationData.ts b/src/2024/Navigation/NavigationData.ts
new file mode 100644
index 00000000..6ec272be
--- /dev/null
+++ b/src/2024/Navigation/NavigationData.ts
@@ -0,0 +1,44 @@
+import {
+ ROUTE_ABOUT_US,
+ ROUTE_ACCOMMODATION,
+ ROUTE_CFP,
+ ROUTE_CODE_OF_CONDUCT,
+ ROUTE_DIVERSITY,
+ ROUTE_HOME,
+ ROUTE_JOB_OFFERS,
+ ROUTE_SCHEDULE,
+ ROUTE_SPEAKERS,
+ ROUTE_SPONSORSHIP,
+ ROUTE_TALKS,
+ ROUTE_TRAVEL,
+} from "../../constants/routes";
+
+export interface NavigationItem {
+ id: string;
+ link: string;
+}
+
+export const navigationItems2024: 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},
+ //{ id: "Communities", link: ROUTE_COMMUNITIES },
+ {id: "Speakers", link: ROUTE_SPEAKERS},
+ {id: "About Us", link: ROUTE_ABOUT_US},
+ {id: "Travel", link: ROUTE_TRAVEL},
+ //{ id: "KCD - Barcelona", link: ROUTE_KCD },
+ {id: "Sponsorship", link: ROUTE_SPONSORSHIP},
+];
+
+export const subMenuItems2024: 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 },
+];
diff --git a/src/2024/Speakers/SpeakerInformation.test.tsx b/src/2024/Speakers/SpeakerInformation.test.tsx
new file mode 100644
index 00000000..0e4d86c0
--- /dev/null
+++ b/src/2024/Speakers/SpeakerInformation.test.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import {render, screen} from "@testing-library/react";
+import {BrowserRouter, Route, Routes} from "react-router-dom";
+import SpeakerInformation from "./SpeakerInformation";
+
+describe("Speakers activities component", () => {
+ it("renders component correctly", () => {
+ render(
+
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter}
+ );
+ const headingElement = screen.getByText("Speakers activities plan");
+ expect(headingElement).toBeInTheDocument();
+ });
+});
diff --git a/src/2024/Speakers/SpeakerInformation.tsx b/src/2024/Speakers/SpeakerInformation.tsx
new file mode 100644
index 00000000..d0973d5c
--- /dev/null
+++ b/src/2024/Speakers/SpeakerInformation.tsx
@@ -0,0 +1,167 @@
+import {FC} from "react";
+import styled from "styled-components";
+import {Color} from "../../styles/colors";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
+import data from "../../data/2024.json";
+import {format} from "date-fns";
+
+const Heading = styled.h1`
+ font-family: "DejaVu Sans Condensed Bold", sans-serif;
+ color: ${Color.DARK_BLUE};
+`;
+
+const Paragraph = styled.p`
+{
+ font-family: "Square 721 Regular", sans-serif;
+ margin: 1rem;
+}
+`;
+
+const List = styled.ul`
+{
+ padding: 0.2rem 0.5rem;
+ margin: 0.5rem 1rem;
+}
+`;
+
+const Image = styled.img`
+{
+ width: 20vw;
+ border: 1px solid ${Color.DARK_BLUE};
+ margin: 1.5rem;
+ padding: 3px;
+ border-radius: 1rem;
+}
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ width: 70vw;
+ border-radius: 0.5rem;
+ }
+`;
+
+const SpeakerInformation: FC> = () => {
+ const startDate = new Date(data.startDay);
+ const speakersActivityDate = startDate.setDate(startDate.getDate() - 1);
+ return (
+
+
+ Speakers activities plan
+
+ Here's the detailed information on the speakers activities
+ for
+ {format(speakersActivityDate, " EEEE, MMMM do, yyyy")}
+
+
+
+
+
+
+
+
+
+ 16:00
+
+ {" "}
+ Hotel Porta Fira
+
+ {" "}
+ - 🗣 Initial gathering
+
+ 🚍 Bus, 45 minutes ( be punctual !)
+
+
+
+
+ 17:00
+
+ {" "}
+ Familia Torres
+
+ {" "}
+ - planned visit
+
+ 🍷 Wine taste
+ 🍽️ Dinner
+
+
+
+ 22:00 Return to Barcelona
+
+ 🚍 45 minutes bus travel
+ 1st stop: Hotel Porta Fira
+ 2nd stop: Hotel Catalonia Plaza for drinks
+
+
+
+
+
+ 22:45{" "}
+
+ Hotel Catalonia Barcelona Plaza
+
+ {" "}
+ - Drinks
+
+ 🥂 Ask for your free drinks bracelet on
+ arrival
+
+
+
+
+
+
+
+
+
+
+ Brought to you by{" "}
+
+ Confluent
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SpeakerInformation;
diff --git a/src/2024/Speakers/Speakers.style.ts b/src/2024/Speakers/Speakers.style.ts
new file mode 100644
index 00000000..5f26dd84
--- /dev/null
+++ b/src/2024/Speakers/Speakers.style.ts
@@ -0,0 +1,91 @@
+import styled from "styled-components";
+import {motion} from "framer-motion";
+import {BIG_BREAKPOINT, TABLET_BREAKPOINT} from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
+
+export const StyledSpeakersSection = styled.section`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 0 2rem;
+ @media (min-width: ${TABLET_BREAKPOINT}px) {
+ padding: 0 5rem;
+ }
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ padding: 0 10rem;
+ }
+`;
+export const StyledLessIcon = styled.img`
+ position: absolute;
+ left: -1rem;
+ top: 2rem;
+ height: 5rem;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 10rem;
+ }
+`;
+export const StyledMoreIcon = styled.img`
+ position: absolute;
+ right: -1rem;
+ top: 2rem;
+ height: 5rem;
+
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 10rem;
+ }
+`;
+export const StyledContainerLeftSlash = styled(motion.div)<{
+ positionPercentage: string;
+}>`
+ position: absolute;
+ top: ${({positionPercentage}) => {
+ return positionPercentage;
+ }};
+ left: 0;
+ height: 2rem;
+ width: 50%;
+ opacity: 0.2;
+`;
+export const StyledContainerRightSlash = styled(motion.div)<{
+ positionPercentage: string;
+}>`
+ position: absolute;
+ top: ${({positionPercentage}) => {
+ return positionPercentage;
+ }};
+ right: 0;
+ height: 2rem;
+ width: 50%;
+ opacity: 0.2;
+`;
+export const StyledSlash = styled(motion.p)<{ color: string }>`
+ font-family: "Square 721 Regular", sans-serif;
+ color: ${({color}) => {
+ return color;
+ }};
+ font-size: 2rem;
+ overflow-y: hidden;
+ height: 100%;
+`;
+export const StyledWaveContainer = styled.div`
+ background: ${Color.WHITE};
+ overflow-y: hidden;
+ height: 8rem;
+ width: 100%;
+ @media (min-width: ${TABLET_BREAKPOINT}px) {
+ height: 16rem;
+ }
+`;
+export const SpeakersCardsContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ padding: 3rem 0;
+ justify-content: center;
+ z-index: 1;
+ @media (min-width: ${TABLET_BREAKPOINT}px) {
+ padding: 5rem 0;
+ }
+`;
diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx
new file mode 100644
index 00000000..7a2e9625
--- /dev/null
+++ b/src/2024/Speakers/Speakers2024.tsx
@@ -0,0 +1,176 @@
+import {MOBILE_BREAKPOINT} from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
+import React, {FC, useCallback, useEffect} from "react";
+import LessThanBlueIcon from "../../assets/images/LessThanBlueIcon.svg";
+import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg";
+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 "./UseFetchSpeakers";
+import * as Sentry from "@sentry/react";
+import {SpeakerCard} from "../../2023/Speakers/components/SpeakersCard";
+import {ISpeaker} from "../../views/Speakers/Speaker.types";
+
+const LessThanGreaterThan = (props: { width: number }) => (
+ <>
+ {props.width > MOBILE_BREAKPOINT && (
+ <>
+
+
+ >
+ )}
+ >
+);
+
+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();
+
+ if (error) {
+ Sentry.captureException(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 (
+ <>
+
+
+
+
+
+ {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/UseFetchSpeakers.test.tsx b/src/2024/Speakers/UseFetchSpeakers.test.tsx
new file mode 100644
index 00000000..8d24b6ec
--- /dev/null
+++ b/src/2024/Speakers/UseFetchSpeakers.test.tsx
@@ -0,0 +1,144 @@
+import React, {FC} from "react";
+import {QueryClient, QueryClientProvider} from "react-query";
+import {renderHook, waitFor} from "@testing-library/react";
+import {speakerAdapter, useFetchSpeakers} from "./UseFetchSpeakers";
+import {beforeAll, beforeEach, describe, expect, it} from "@jest/globals";
+import axios, {AxiosHeaders, AxiosResponse} from "axios";
+import {IResponse} from "../../views/Speakers/Speaker.types";
+
+jest.mock("axios");
+const mockedAxios = axios as jest.Mocked;
+const axiosHeaders = new AxiosHeaders();
+
+const payload: AxiosResponse = {
+ status: 200,
+ statusText: "OK",
+ headers: {},
+ config: {
+ headers: axiosHeaders,
+ },
+ data: [
+ {
+ id: "1",
+ fullName: "John Smith",
+ profilePicture: "https://example.com/john.jpg",
+ tagLine: "Software engineer",
+ bio: "I am a software engineer",
+ sessions: [
+ {
+ id: 4567,
+ name: "sample session",
+ },
+ ],
+ links: [
+ {
+ linkType: "Twitter",
+ url: "https://twitter.com/johnsmith",
+ title: "",
+ },
+ {
+ linkType: "LinkedIn",
+ url: "https://linkedin.com/in/johnsmith",
+ title: "",
+ },
+ ],
+ },
+ {
+ id: "2",
+ fullName: "Jane Doe",
+ profilePicture: "https://example.com/jane.jpg",
+ tagLine: "Data scientist",
+ bio: "I am a data scientist",
+ sessions: [],
+ links: [
+ {
+ linkType: "Twitter",
+ url: "https://twitter.com/janedoe",
+ title: "",
+ },
+ {
+ linkType: "LinkedIn",
+ url: "https://linkedin.com/in/janedoe",
+ title: "",
+ },
+ ],
+ },
+ ],
+};
+
+describe("fetch speaker hook and speaker adapter", () => {
+ beforeAll(() => {
+ jest.mock("axios");
+ });
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("should adapt from a server response", async () => {
+ const queryClient = new QueryClient();
+
+ mockedAxios.get.mockImplementation(() => Promise.resolve(payload));
+ const wrapper: FC>> = ({children}) => {
+ return (
+
+ {children}
+
+ );
+ };
+
+ const {result} = renderHook(() => useFetchSpeakers(), {
+ wrapper,
+ });
+ await waitFor(() => result.current.isSuccess, {});
+ await waitFor(() => !result.current.isLoading, {});
+ expect(mockedAxios.get).toHaveBeenCalled();
+ expect(result.current.isLoading).toEqual(false);
+ expect(result.current.error).toEqual(null);
+ expect(result.current.data).toEqual(speakerAdapter(payload.data));
+ });
+
+ it("should adapt from server response a query with id", async () => {
+ //Given
+ const queryClient = new QueryClient();
+ mockedAxios.get.mockResolvedValueOnce(payload);
+ const expectedPayload: IResponse[] = [
+ {
+ id: "1",
+ bio: "I am a software engineer",
+ fullName: "John Smith",
+ links: [
+ {
+ linkType: "LinkedIn",
+ url: "https://linkedin.com/in/johnsmith",
+ title: "",
+ },
+ {
+ url: "https://twitter.com/johnsmith",
+ title: "",
+ linkType: "Twitter",
+ },
+ ],
+ profilePicture: "https://example.com/john.jpg",
+ tagLine: "Software engineer",
+ sessions: [{id: 4567, name: "sample session"}],
+ },
+ ];
+ const wrapper: FC>> = ({children}) => {
+ return (
+
+ {children}
+
+ );
+ };
+
+ //When
+ const {result} = renderHook(() => useFetchSpeakers("1"), {
+ wrapper,
+ });
+ await waitFor(() => result.current.isSuccess);
+ await waitFor(() => !result.current.isLoading, {});
+ //then
+ expect(mockedAxios.get).toHaveBeenCalled();
+ expect(result.current.data).toEqual(speakerAdapter(expectedPayload));
+ });
+});
diff --git a/src/2024/Speakers/UseFetchSpeakers.ts b/src/2024/Speakers/UseFetchSpeakers.ts
new file mode 100644
index 00000000..451a0f7f
--- /dev/null
+++ b/src/2024/Speakers/UseFetchSpeakers.ts
@@ -0,0 +1,33 @@
+import {useQuery, UseQueryResult} from "react-query";
+import axios from "axios";
+import {IResponse, ISpeaker} from "../../views/Speakers/Speaker.types";
+
+export const useFetchSpeakers = (id?: string): UseQueryResult => {
+ return useQuery("api-speakers", async () => {
+ const serverResponse = await axios.get(
+ "https://sessionize.com/api/v2/teq4asez/view/Speakers",
+ );
+ let returnData;
+ if (id !== undefined) {
+ returnData = serverResponse.data.filter(
+ (speaker: { id: string }) => speaker.id === id,
+ );
+ } else {
+ returnData = serverResponse.data;
+ }
+ return speakerAdapter(returnData);
+ });
+};
+export const speakerAdapter = (response: IResponse[]): ISpeaker[] =>
+ response.map((response) => ({
+ id: response.id,
+ fullName: response.fullName,
+ speakerImage: response.profilePicture,
+ tagLine: response.tagLine,
+ bio: response.bio,
+ sessions: response.sessions,
+ twitterUrl: response.links.filter((link) => link.linkType === "Twitter")[0],
+ linkedInUrl: response.links.filter(
+ (link) => link.linkType === "LinkedIn",
+ )[0],
+ }));
diff --git a/src/2024/SpeakersCarousel/SpeakerSwiper.tsx b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx
new file mode 100644
index 00000000..53350366
--- /dev/null
+++ b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx
@@ -0,0 +1,107 @@
+import React, {FC} from "react";
+import {Autoplay, Parallax} from "swiper";
+import {Swiper, SwiperSlide} from "swiper/react";
+import styled from "styled-components";
+import "swiper/swiper-bundle.min.css";
+import "./SpeakersCarousel.scss";
+import {Link} from "react-router-dom";
+import conferenceData from "../../data/2024.json";
+import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers";
+import * as Sentry from "@sentry/react";
+import {Color} from "../../styles/colors";
+import {ROUTE_SPEAKER_DETAIL} from "../../constants/routes";
+
+const StyledSlideImage = styled.img`
+ display: block;
+ width: 100%;
+ aspect-ratio: 1/1;
+ border-radius: 10px;
+`;
+
+const StyledSlideContain = styled.div`
+ position: absolute;
+ bottom: 0;
+ background: ${Color.MAGENTA};
+ background: linear-gradient(
+ to bottom,
+ rgba(255, 0, 0, 0),
+ ${Color.DARK_BLUE}
+ );
+ padding: 0.5rem 0.25rem;
+ min-width: 100%;
+`;
+
+const StyledSlideText = styled.p`
+ font-size: 0.875rem;
+ color: white;
+`;
+const SpeakerSwiper: FC> = () => {
+ const {isLoading, data, error} = useFetchSpeakers();
+
+ const swiperSpeakers = data?.sort(() => 0.5 - Math.random()).slice(0, 20);
+
+ if (error) {
+ Sentry.captureException(error);
+ }
+
+ return (
+ <>
+ {isLoading && Loading
}
+ {conferenceData.carrousel.enabled && swiperSpeakers && (
+
+ {swiperSpeakers.map((speaker) => (
+
+
+
+
+ {speaker.fullName}
+
+
+
+ ))}
+
+ )}
+ >
+ );
+};
+
+export default SpeakerSwiper;
diff --git a/src/2024/SpeakersCarousel/SpeakersCarousel.scss b/src/2024/SpeakersCarousel/SpeakersCarousel.scss
new file mode 100644
index 00000000..5238a6c3
--- /dev/null
+++ b/src/2024/SpeakersCarousel/SpeakersCarousel.scss
@@ -0,0 +1,29 @@
+.swiper {
+ width: 100%;
+ overflow-x: hidden;
+ margin: 2rem 0;
+}
+
+.swiper-slide {
+ background: transparent;
+ position: relative;
+ /* Center slide text vertically */
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ -webkit-align-items: center;
+ align-items: center;
+}
+
+.link--text {
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/src/2024/SpeakersCarousel/SpeakersCarousel.tsx b/src/2024/SpeakersCarousel/SpeakersCarousel.tsx
new file mode 100644
index 00000000..0ba92bda
--- /dev/null
+++ b/src/2024/SpeakersCarousel/SpeakersCarousel.tsx
@@ -0,0 +1,106 @@
+import {FC} from "react";
+import {Link} from "react-router-dom";
+import LessThanBlueWhiteIcon
+ from "../../assets/images/MoreThanBlueWhiteIcon.svg";
+import {motion} from "framer-motion";
+import styled from "styled-components";
+import SpeakerSwiper from "./SpeakerSwiper";
+import {useWindowSize} from "react-use";
+import {
+ BIGGER_BREAKPOINT,
+ TABLET_BREAKPOINT
+} from "../../constants/BreakPoints";
+import {ROUTE_SPEAKERS} from "../../constants/routes";
+import TitleSection from "../../components/SectionTitle/TitleSection";
+import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import {Color} from "../../styles/colors";
+
+const StyledSpeakersContainer = styled.section`
+ background-color: ${Color.LIGHT_BLUE};
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const StyledTitleWrapper = styled.div`
+ max-width: 1280px;
+`;
+
+const StyledLink = styled.div`
+ display: flex;
+ justify-content: center;
+ padding-bottom: 3rem;
+ @media (min-width: ${TABLET_BREAKPOINT}px) {
+ justify-content: flex-end;
+ padding-right: 10rem;
+ }
+`;
+
+const StyledSubtitle = styled.h2`
+ color: ${Color.DARK_BLUE};
+ padding-right: 0.75rem;
+`;
+
+const StyledLessThanRed = styled.img`
+ height: 1.5rem;
+`;
+
+export const StyledBottomSlash = styled(motion.div)`
+ position: absolute;
+ bottom: -8px;
+ left: 0;
+ width: 40%;
+ height: 2rem;
+`;
+
+const StyledBlueSlash = styled(motion.p)`
+ font-family: "Square 721 Regular", sans-serif;
+ color: ${Color.DARK_BLUE};
+ font-size: 2rem;
+ overflow-y: hidden;
+ height: 100%;
+`;
+
+const SpeakersCarousel: FC> = () => {
+ const {width} = useWindowSize();
+ return (
+
+
+
+
+
+
+
+
+ View all speakers
+
+
+
+
+ {width > BIGGER_BREAKPOINT && (
+
+ / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / /{" "}
+
+ )}
+
+
+
+ );
+};
+
+export default SpeakersCarousel;
diff --git a/src/2024/Sponsors/BasicSponsor.tsx b/src/2024/Sponsors/BasicSponsor.tsx
new file mode 100644
index 00000000..3255d374
--- /dev/null
+++ b/src/2024/Sponsors/BasicSponsor.tsx
@@ -0,0 +1,103 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconNano,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+
+import {buildSlashes} from "./Sponsors";
+import {useWindowSize} from "react-use";
+import {useCallback, useEffect, useState} from "react";
+import {sponsors} from "./SponsorsData";
+import {Color} from "../../styles/colors";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
+
+export const BasicSponsor = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHoverSponsorBasic = useCallback(() => setIsHovered(true), []);
+ const handleUnHoverSponsorBasic = useCallback(() => setIsHovered(false), []);
+
+ let basicSponsors = sponsors.basic;
+ return (
+ <>
+ {basicSponsors !== null && basicSponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/Communities.tsx b/src/2024/Sponsors/Communities.tsx
new file mode 100644
index 00000000..94bf20ca
--- /dev/null
+++ b/src/2024/Sponsors/Communities.tsx
@@ -0,0 +1,98 @@
+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 {buildSlashes} from "./Sponsors";
+import {useWindowSize} from "react-use";
+import {useCallback, useEffect, useState} from "react";
+import {sponsors} from "./SponsorsData";
+
+export const Communities = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const communities = sponsors.communities;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHover = useCallback(() => setIsHovered(true), []);
+ const handleUnHover = useCallback(() => setIsHovered(false), []);
+ return (
+ <>
+ {communities !== null && communities.length > 0 && (
+
+
+
+
+ = BIG_BREAKPOINT
+ ? Color.WHITE
+ : Color.DARK_BLUE
+ }
+ id="Slashes"
+ >
+ COMMUNITIES
+
+ {slashes}
+
+ {width >= BIG_BREAKPOINT && (
+
+ {slashes}
+
+ )}
+
+
+
+
+ {communities.map((sponsor) => (
+
+
+
+ ))}
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/MediaPartners.tsx b/src/2024/Sponsors/MediaPartners.tsx
new file mode 100644
index 00000000..26e6d2f8
--- /dev/null
+++ b/src/2024/Sponsors/MediaPartners.tsx
@@ -0,0 +1,98 @@
+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 {buildSlashes} from "./Sponsors";
+import {useWindowSize} from "react-use";
+import {FC, useCallback, useEffect, useState} from "react";
+import {sponsors} from "./SponsorsData";
+
+export const MediaPartners: FC> = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const mediaPartners = sponsors.media_partners;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHoverMediaPartner = useCallback(() => setIsHovered(true), []);
+ const handleUnHoverMediaPartner = useCallback(() => setIsHovered(false), []);
+ return (
+ <>
+ {mediaPartners !== null && mediaPartners.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/PremiumSponsors.tsx b/src/2024/Sponsors/PremiumSponsors.tsx
new file mode 100644
index 00000000..03868b35
--- /dev/null
+++ b/src/2024/Sponsors/PremiumSponsors.tsx
@@ -0,0 +1,105 @@
+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 {useWindowSize} from "react-use";
+import {FC, useCallback, useEffect, useState} from "react";
+import {buildSlashes} from "./Sponsors";
+import {sponsors} from "./SponsorsData";
+
+export const PremiumSponsors: FC> = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const premiumSponsors = sponsors.premium;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHoverSponsorPremium = useCallback(() => setIsHovered(true), []);
+ const handleUnHoverSponsorPremium = useCallback(
+ () => setIsHovered(false),
+ []
+ );
+ return (
+ <>
+ {premiumSponsors !== null && premiumSponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/RegularSponsors.tsx b/src/2024/Sponsors/RegularSponsors.tsx
new file mode 100644
index 00000000..0fdc9355
--- /dev/null
+++ b/src/2024/Sponsors/RegularSponsors.tsx
@@ -0,0 +1,101 @@
+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 {buildSlashes} from "./Sponsors";
+import {useWindowSize} from "react-use";
+import {useCallback, useEffect, useState} from "react";
+import {sponsors} from "./SponsorsData";
+
+export const RegularSponsors = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const regularSponsors = sponsors.regular;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHoverSponsorRegular = useCallback(() => setIsHovered(true), []);
+ const handleUnHoverSponsorRegular = useCallback(
+ () => setIsHovered(false),
+ []
+ );
+ return (
+ <>
+ {regularSponsors !== null && regularSponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/SponsorBadge.tsx b/src/2024/Sponsors/SponsorBadge.tsx
new file mode 100644
index 00000000..9f3f521f
--- /dev/null
+++ b/src/2024/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/2024/Sponsors/Sponsors.style.ts b/src/2024/Sponsors/Sponsors.style.ts
new file mode 100644
index 00000000..df3a0bad
--- /dev/null
+++ b/src/2024/Sponsors/Sponsors.style.ts
@@ -0,0 +1,296 @@
+import styled from "styled-components";
+import {BIG_BREAKPOINT, LARGE_BREAKPOINT} from "../../constants/BreakPoints";
+import {motion} from "framer-motion";
+
+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<{ 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/2024/Sponsors/Sponsors.tsx b/src/2024/Sponsors/Sponsors.tsx
new file mode 100644
index 00000000..3395b3ff
--- /dev/null
+++ b/src/2024/Sponsors/Sponsors.tsx
@@ -0,0 +1,63 @@
+import {FC} from "react";
+
+import LessThanBlueIcon from "../../assets/images/MoreThanBlueWhiteIcon.svg";
+import LessThanBlueWhiteIcon
+ from "../../assets/images/LessThanBlueWhiteIcon.svg";
+import {Color} from "../../styles/colors";
+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 SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import TitleSection from "../../components/SectionTitle/TitleSection";
+
+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/SponsorsData.ts b/src/2024/Sponsors/SponsorsData.ts
new file mode 100644
index 00000000..7ae865ed
--- /dev/null
+++ b/src/2024/Sponsors/SponsorsData.ts
@@ -0,0 +1,179 @@
+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: [
+ {
+ name: "DATASTAX",
+ image: "images/sponsors/datastax.png",
+ website: "https://www.datastax.com/",
+ },
+ ],
+ premium: [
+ {
+ name: "Allianz",
+ image: "images/sponsors/allianz.png",
+ website: "https://tech.allianz.com/en.html",
+ },
+ {
+ name: "Barcelona JUG",
+ image: "images/sponsors/bcn-jug.png",
+ website: "https://www.meetup.com/barcelonajug/",
+ },
+ {
+ name: "PREM.AI",
+ website: "https://www.premai.io/",
+ image: "images/sponsors/prem-ai.png",
+ },
+ {
+ name: "Revolut",
+ website: "https://www.revolut.com/working-at-revolut/",
+ image: "images/sponsors/revolut.png",
+ },
+ ],
+ regular: [
+ {
+ name: "Sopra Steria",
+ image: "/images/sponsors/sopra.png",
+ website: "https://www.soprasteria.es/",
+ },
+ {
+ name: "Caixabank Tech",
+ website: "https://www.caixabanktech.com/es/pagina-de-inicio/",
+ image: "images/sponsors/caixabank-tech.png",
+ },
+ {
+ name: "Idealista",
+ image: "images/sponsors/idealista.jpg",
+ website: "https://www.idealista.com/info/trabaja-con-nosotros",
+ },
+ {
+ name: "Clever Cloud",
+ image: "images/sponsors/clever-cloud.png",
+ website: "https://www.clever-cloud.com/",
+ },
+ {
+ name: "ALTEN",
+ image: "images/sponsors/alten.png",
+ website: "https://www.alten.es/",
+ },
+ {
+ name: "TIGERA",
+ image: "images/sponsors/tigera.png",
+ website: "https://www.tigera.io/",
+ },
+ {
+ name: "Dynatrace",
+ image: "images/sponsors/dynatrace.png",
+ website: "https://www.dynatrace.com/",
+ },
+ ],
+ basic: [
+ {
+ name: "Seidor",
+ website: "https://www.opentrends.net/en",
+ image: "images/sponsors/seidor.png",
+ },
+ {
+ name: "Perfect Scale",
+ website: "https://www.perfectscale.io/",
+ image: "images/sponsors/perfect-scale.png",
+ },
+ {
+ name: "Auth0",
+ image: "images/sponsors/auth0.png",
+ website: "https://okta.com/careers",
+ },
+ {
+ name: "Barcelona Activa",
+ image: "images/sponsors/barcelona-activa.png",
+ website: "https://www.barcelonactiva.cat/",
+ },
+ {
+ name: "Reevo",
+ image: "images/sponsors/reevo.png",
+ website: "https://www.reevo.it/",
+ },
+ {
+ name: "GFT",
+ website: "https://www.gft.com/es/es/",
+ image: "images/sponsors/GFT.jpg",
+ },
+ {
+ name: "Grupo Castilla",
+ image: "images/sponsors/grupo-castilla.png",
+ website:
+ "https://www.grupocastilla.es/servicios-rrhh/consultoria-tecnologica/",
+ },
+ {
+ name: "Axa",
+ image: "images/sponsors/axa.png",
+ website: "https://www.axapartners.es/es",
+ },
+ ],
+ communities: [
+ {
+ name: "KCD Barcelona",
+ website:
+ "https://community.cncf.io/events/details/cncf-kcd-spain-presents-kcd-barcelona-2024/",
+ image: "/images/KCD-logo-black.png",
+ },
+ {
+ name: "Apache Foundation",
+ image: "images/sponsors/apache-foundation.jpeg",
+ website: "https://www.apache.org/",
+ },
+ {
+ name: "Eclipse Foundation",
+ image: "images/sponsors/eclipse-foundation.png",
+ website: "https://www.eclipse.org/",
+ },
+ {
+ name: "Foojay",
+ image: "images/sponsors/foojay.jpg",
+ website: "https://foojay.io/",
+ },
+ {
+ name: "Migracode Barcelona",
+ image: "images/sponsors/migracode.jpg",
+ website: "https://www.migracode.org/",
+ },
+ {
+ name: "Step4ward",
+ image: "images/sponsors/step4ward.png",
+ website: "https://bit.ly/step4wardhome",
+ },
+ ],
+ media_partners: [
+ {
+ 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//",
+ },
+ ],
+ supporters: [
+ {
+ name: "BarcelonaJS",
+ website: "https://barcelonajs.com/",
+ image: "images/sponsors/barcelona-js.png",
+ },
+ ],
+};
diff --git a/src/2024/Sponsors/Supporters.test.tsx b/src/2024/Sponsors/Supporters.test.tsx
new file mode 100644
index 00000000..827d64ce
--- /dev/null
+++ b/src/2024/Sponsors/Supporters.test.tsx
@@ -0,0 +1,72 @@
+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-dom";
+
+jest.mock("react-use", () => ({
+ useWindowSize: jest.fn(),
+}));
+
+describe("Supporters", () => {
+ beforeEach(() => {
+ (useWindowSize as jest.Mock).mockReturnValue({width: 1024}); // Mock window width for testing
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ // disabled until supporters included
+ it.skip("renders component with supporters", () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter}
+ );
+
+ expect(screen.getByTestId("supporters")).toBeInTheDocument();
+ expect(screen.getByText("SUPPORTERS")).toBeInTheDocument();
+ expect(screen.getAllByRole("link")).toHaveLength(5);
+ });
+
+ it.skip("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.skip("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/2024/Sponsors/Supporters.tsx b/src/2024/Sponsors/Supporters.tsx
new file mode 100644
index 00000000..c7cbf9c4
--- /dev/null
+++ b/src/2024/Sponsors/Supporters.tsx
@@ -0,0 +1,106 @@
+import {
+ StyledFlexGrow,
+ StyledLogos,
+ StyledSeparator,
+ StyledSlashes,
+ StyledSponsorIconMicro,
+ StyledSponsorItemContainer,
+ StyledSponsorLogosContainer,
+ StyledSponsorTitleContainer,
+ StyledSponsorTitleMargin,
+ StyledSponsorTitleSlashesContainer,
+} from "./Sponsors.style";
+import SponsorBadge from "./SponsorBadge";
+import {buildSlashes} from "./Sponsors";
+import {useWindowSize} from "react-use";
+import {useCallback, useEffect, useState} from "react";
+import {sponsors} from "./SponsorsData";
+import {Color} from "../../styles/colors";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
+
+export const Supporters = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const supporters = sponsors.supporters;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHover = useCallback(() => setIsHovered(true), []);
+ const handleUnHover = useCallback(() => setIsHovered(false), []);
+ return (
+ <>
+ {supporters !== null && supporters.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
+
+ )}
+
+
+
+
+
+
+ {supporters.map((sponsor) => (
+
+
+
+ ))}
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Sponsors/TopSponsors.tsx b/src/2024/Sponsors/TopSponsors.tsx
new file mode 100644
index 00000000..7e7b7bef
--- /dev/null
+++ b/src/2024/Sponsors/TopSponsors.tsx
@@ -0,0 +1,97 @@
+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 {FC, useCallback, useEffect, useState} from "react";
+import {useWindowSize} from "react-use";
+import {buildSlashes} from "./Sponsors";
+import {sponsors} from "./SponsorsData";
+
+export const TopSponsors: FC> = () => {
+ const {width} = useWindowSize();
+ const [slashes, setSlashes] = useState("");
+ const [isHovered, setIsHovered] = useState(false);
+ const topSponsors = sponsors.top;
+
+ useEffect(() => {
+ const newSlashes = buildSlashes(2);
+
+ setSlashes(newSlashes);
+ }, [width]);
+
+ const handleHoverSponsorTop = useCallback(() => setIsHovered(true), []);
+ const handleUnHoverSponsorTop = useCallback(() => setIsHovered(false), []);
+
+ return (
+ <>
+ {topSponsors !== null && topSponsors.length > 0 && (
+
+ )}
+ >
+ );
+};
diff --git a/src/2024/Talks/LiveView.test.tsx b/src/2024/Talks/LiveView.test.tsx
new file mode 100644
index 00000000..cc65de35
--- /dev/null
+++ b/src/2024/Talks/LiveView.test.tsx
@@ -0,0 +1,17 @@
+import LiveView from "./LiveView";
+import {QueryClient, QueryClientProvider} from "react-query";
+import {render, screen} from "@testing-library/react";
+import React from "react";
+
+describe("Live view component", () => {
+ it("renders without crashing", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+ ,
+ );
+ const titleElement = screen.getByText(/Live Schedule/);
+ expect(titleElement).toBeInTheDocument();
+ });
+});
diff --git a/src/2024/Talks/LiveView.tsx b/src/2024/Talks/LiveView.tsx
new file mode 100644
index 00000000..e0240b0e
--- /dev/null
+++ b/src/2024/Talks/LiveView.tsx
@@ -0,0 +1,65 @@
+import React, {FC, useCallback, useEffect, useMemo} from "react";
+import {useFetchLiveView} from "./UseFetchTalks";
+import Loading from "../../components/Loading/Loading";
+import conference from "../../data/2024.json";
+import * as Sentry from "@sentry/react";
+import {UngroupedSession} from "../../views/Talks/liveView.types";
+import {TalkCard} from "../../views/Talks/components/TalkCard";
+import {talkCardAdapter} from "../../views/Talks/TalkCardAdapter";
+import {StyledMain} from "../../views/Talks/Talks.style";
+
+const LiveView: FC> = () => {
+ const {isLoading, error, data} = useFetchLiveView();
+ const today = useMemo(() => new Date(), []);
+
+ const isBetween = useCallback(
+ (today: Date, startDate: string, endDate: string): boolean => {
+ return today >= new Date(startDate) && today <= new Date(endDate);
+ },
+ [],
+ );
+
+ const getPredicate = useCallback(
+ () => (session: UngroupedSession) =>
+ isBetween(today, session.startsAt, session.endsAt),
+ [today, isBetween],
+ );
+
+ const filteredTalks = useMemo(() => {
+ return data?.sessions?.filter(getPredicate());
+ }, [data, getPredicate]);
+
+ useEffect(() => {
+ document.title = `Live view - ${conference.title} - ${conference.edition} Edition`;
+ }, []);
+
+ useEffect(() => {
+ if (error) {
+ Sentry.captureException(error);
+ }
+ }, [error]);
+
+ return (
+
+
+
+ {conference.title} - {conference.edition} Edition
+
+
+ {isLoading && }
+ Live Schedule
+ {!isBetween(today, conference.startDay, conference.endDay) && (
+ The live schedule is not ready yet
+ )}
+ {filteredTalks?.map((session) => (
+
+ ))}
+
+ );
+};
+
+export default LiveView;
diff --git a/src/2024/Talks/Talks.test.tsx b/src/2024/Talks/Talks.test.tsx
new file mode 100644
index 00000000..815f19a0
--- /dev/null
+++ b/src/2024/Talks/Talks.test.tsx
@@ -0,0 +1,71 @@
+import React from "react";
+import {render, screen} from "@testing-library/react";
+import Talks2024 from "./Talks2024";
+import {QueryClient, QueryClientProvider} from "react-query";
+
+describe("Talks", () => {
+ it("renders without errors", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ });
+
+ it("renders the correct title", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ const titleElement = screen.getByText(/TALKS/);
+ expect(titleElement).toBeInTheDocument();
+ });
+
+ it("renders the correct subtitle", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ const subtitleElement = screen.getByText(
+ /speakers coming from all corners of the world/i
+ );
+ expect(subtitleElement).toBeInTheDocument();
+ });
+
+ it("renders a filter by track dropdown", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ const dropdownElement = screen.getByText("Loading");
+ expect(dropdownElement).toBeInTheDocument();
+ });
+
+ it("renders a loading message when talks are being fetched", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ expect(screen.getByText("Loading")).toBeInTheDocument();
+ });
+
+ it("renders a message when no talks are selected", () => {
+ const queryClient = new QueryClient();
+ render(
+
+
+
+ );
+ const dropdownElement = screen.getByText("Loading");
+ expect(dropdownElement).toBeInTheDocument();
+ });
+});
diff --git a/src/2024/Talks/Talks2024.tsx b/src/2024/Talks/Talks2024.tsx
new file mode 100644
index 00000000..af1fd79b
--- /dev/null
+++ b/src/2024/Talks/Talks2024.tsx
@@ -0,0 +1,145 @@
+import React, {FC, useEffect, useState} from "react";
+import LessThanDarkBlueIcon from "../../assets/images/LessThanDarkBlueIcon.svg";
+import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg";
+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 "./UseFetchTalks";
+import * as Sentry from "@sentry/react";
+import {Dropdown, DropdownChangeEvent} from "primereact/dropdown";
+import "primereact/resources/primereact.min.css";
+import "primereact/resources/themes/lara-light-indigo/theme.css";
+import "../../styles/theme.css";
+import TrackInformation from "../../views/Talks/components/TrackInformation";
+import {
+ StyledMarginBottom,
+ StyledSpeakersSection,
+ StyledTitleContainer,
+ StyledTitleIcon,
+ StyledWaveContainer
+} from "../../views/Talks/Talks.style";
+
+interface TrackInfo {
+ name: string;
+ code?: string;
+}
+
+const Talks2024: FC> = () => {
+ const [selectedGroupId, setSelectedGroupId] = useState(
+ null,
+ );
+ const {isLoading, error, data} = useFetchTalks();
+
+ 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,
+ });
+ }
+ }, []);
+
+ if (error) {
+ Sentry.captureException(error);
+ }
+
+ const dropDownOptions = [
+ {name: "All Tracks", code: undefined},
+ ...(data !== undefined
+ ? data.flatMap((group) => ({
+ code: group.groupId.toString(),
+ name: 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) && (
+ <>
+
+
+ Filter by Track:
+
+
+
+ {filteredTalks.map((track) => (
+
+ ))}
+ >
+ )
+ )}
+
+
+
+ >
+ );
+};
+
+export default Talks2024;
diff --git a/src/2024/Talks/UseFetchTalks.ts b/src/2024/Talks/UseFetchTalks.ts
new file mode 100644
index 00000000..61f70678
--- /dev/null
+++ b/src/2024/Talks/UseFetchTalks.ts
@@ -0,0 +1,134 @@
+import {useQuery, UseQueryResult} from "react-query";
+import axios from "axios";
+import {
+ CategoryItemEnum,
+ IGroup,
+ QuestionAnswers,
+ Session,
+ SessionCategory
+} from "../../views/Talks/Talk.types";
+import {Liveview} from "../../views/Talks/liveView.types";
+import {IMeeting} from "../../views/MeetingDetail/MeetingDetail.Type";
+
+export const useFetchTalks = (): UseQueryResult =>
+ useQuery("api-talks", async () => {
+ let data = await axios.get(
+ "https://sessionize.com/api/v2/teq4asez/view/Sessions",
+ );
+ return data.data;
+ });
+
+export const useFetchTalksById = (id: string): UseQueryResult =>
+ useQuery("talks", async () => {
+ const serverResponse = await axios.get(
+ "https://sessionize.com/api/v2/teq4asez/view/Sessions",
+ );
+ return serverResponse.data
+ .map((track: IGroup) => track.sessions)
+ .flat(1)
+ .filter((session: { id: string }) => session.id === id);
+ });
+
+export const useFetchLiveView = (): UseQueryResult =>
+ useQuery("api-talks", async () => {
+ let data = await axios.get(
+ "https://sessionize.com/api/v2/ezm48alx/view/Sessions",
+ );
+ return data.data.at(0);
+ });
+
+export const extractSessionTags = (
+ questionAnswers: QuestionAnswers[],
+): string[] | undefined => {
+ let tags = questionAnswers
+ .filter((question) => question.question === "Tags/Topics")
+ .map((question) => question.answer)
+ .at(0);
+ return tags?.split(",");
+};
+
+export const extractSessionSlides = (
+ questionAnswers: QuestionAnswers[],
+): string => {
+ let slides = questionAnswers
+ .filter((question) => question.question === "Slides")
+ .map((question) => question.answer)
+ .at(0);
+ return slides ?? "";
+};
+
+const sessionEmojis: Record = {
+ Session: "🗣",
+ Workshop: "💻",
+ "Lightning talk": "⚡️",
+};
+
+const sessionLevel: Record = {
+ "Introductory and overview": "⭐",
+ Intermediate: "⭐⭐",
+ Advanced: "⭐⭐⭐",
+};
+
+export const extractSessionCategoryInfo = (
+ categories: SessionCategory[],
+ item: CategoryItemEnum = CategoryItemEnum.Level,
+): string | undefined => {
+ const info = categories.find((category) => category.name === item)
+ ?.categoryItems?.[0]?.name;
+
+ if (!info) {
+ return undefined;
+ }
+
+ const emojis =
+ item === CategoryItemEnum.Format ? sessionEmojis : sessionLevel;
+
+ for (const [key, value] of Object.entries(emojis)) {
+ if (info.includes(key)) {
+ return `${info} ${value}`;
+ }
+ }
+
+ if (item === CategoryItemEnum.Language && info === "Spanish") {
+ return `${info} 🇪🇸`;
+ }
+ if (item === CategoryItemEnum.Language && info === "English") {
+ return `${info} 🇬🇧`;
+ }
+
+ return `${info}`;
+};
+
+export const sessionAdapter = (
+ session: Session | undefined,
+): IMeeting | undefined => {
+ if (session === undefined) {
+ return undefined;
+ }
+ return {
+ description: session.description,
+ endDate: session.endsAt.split("T")[0],
+ endTime: session.endsAt.split("T")[1],
+ id: session.id,
+ language: extractSessionCategoryInfo(
+ session.categories,
+ CategoryItemEnum.Language,
+ ),
+ level: extractSessionCategoryInfo(session?.categories),
+ slidesURL: extractSessionSlides(session.questionAnswers),
+ speakers: session.speakers,
+ startDate: session.startsAt.split("T")[0],
+ startTime: session.startsAt.split("T")[1],
+ title: session.title,
+ track: extractSessionCategoryInfo(
+ session.categories,
+ CategoryItemEnum.Track,
+ ),
+ type: extractSessionCategoryInfo(
+ session.categories,
+ CategoryItemEnum.Format,
+ ),
+ videoTags: extractSessionTags(session.questionAnswers),
+ videoUrl: session.recordingUrl,
+ };
+};
diff --git a/src/2024/Talks/useFetchTalks.test.tsx b/src/2024/Talks/useFetchTalks.test.tsx
new file mode 100644
index 00000000..503e11d5
--- /dev/null
+++ b/src/2024/Talks/useFetchTalks.test.tsx
@@ -0,0 +1,425 @@
+import React, {FC} from "react";
+import {QueryClient, QueryClientProvider} from "react-query";
+import {renderHook, waitFor} from "@testing-library/react";
+import axios, {AxiosHeaders, AxiosResponse} from "axios";
+import {faker} from "@faker-js/faker";
+import {beforeAll, beforeEach, describe, expect, it} from "@jest/globals";
+import {
+ extractSessionCategoryInfo,
+ extractSessionSlides,
+ extractSessionTags,
+ sessionAdapter,
+ useFetchLiveView,
+ useFetchTalksById,
+} from "./UseFetchTalks";
+import {IMeeting} from "../../views/MeetingDetail/MeetingDetail.Type";
+import {
+ CategoryItemEnum,
+ QuestionAnswers,
+ Session,
+ SessionCategory
+} from "../../views/Talks/Talk.types";
+import {UngroupedSession} from "../../views/Talks/liveView.types";
+
+
+jest.mock("axios");
+const mockedAxios = axios as jest.Mocked;
+const axiosHeaders = new AxiosHeaders();
+const queryClient = new QueryClient();
+const wrapper: FC>> = ({
+ children,
+ }) => (
+ {children}
+);
+
+describe("sessionAdapter", () => {
+ test("returns empty strings when session is undefined", () => {
+ expect(sessionAdapter(undefined)).toBeUndefined();
+ });
+
+ test("returns the expected output when session is defined", () => {
+ const session: Session = {
+ track: "Java ( core frameworks & libraries )",
+ id: 5000,
+ description: "Session description",
+ startsAt: "2024-06-13T12:00:00",
+ endsAt: "2024-06-13T14:00:00",
+ title: "Session title",
+ speakers: [
+ {
+ id: "6f672350-1c71-4a6e-a382-2b1db6e631fd",
+ name: "Eric Deandrea",
+ },
+ {
+ id: "4452d53b-603f-4185-beab-766a19258c0f",
+ name: "Holly Cummins",
+ },
+ ],
+ recordingUrl: "https://example.com/video.mp4",
+ questionAnswers: [
+ {
+ id: 47395,
+ question: "Tags/Topics",
+ questionType: "Short_Text",
+ answer: "java,openjdk",
+ },
+ {
+ id: 3425,
+ question: "Slides",
+ questionType: "web_address",
+ answer: "https://www.google.com",
+ },
+ ],
+ categories: [
+ {
+ id: 45078,
+ name: CategoryItemEnum.Format,
+ categoryItems: [
+ {
+ id: 149212,
+ name: "Session",
+ },
+ ],
+ },
+ {
+ id: 45079,
+ name: CategoryItemEnum.Track,
+ categoryItems: [
+ {
+ id: 159116,
+ name: "Java ( core frameworks & libraries )",
+ },
+ ],
+ },
+ {
+ id: 45080,
+ name: CategoryItemEnum.Level,
+ categoryItems: [
+ {
+ id: 149217,
+ name: "Introductory and overview",
+ },
+ ],
+ },
+ {
+ id: 45081,
+ name: CategoryItemEnum.Language,
+ categoryItems: [
+ {
+ id: 149221,
+ name: "English",
+ },
+ ],
+ },
+ ],
+ };
+ const expected: IMeeting = {
+ id: 5000,
+ description: "Session description",
+ title: "Session title",
+ speakers: [
+ {
+ id: "6f672350-1c71-4a6e-a382-2b1db6e631fd",
+ name: "Eric Deandrea",
+ },
+ {
+ id: "4452d53b-603f-4185-beab-766a19258c0f",
+ name: "Holly Cummins",
+ },
+ ],
+ videoUrl: "https://example.com/video.mp4",
+ slidesURL: "https://www.google.com",
+ videoTags: ["java", "openjdk"],
+ level: "Introductory and overview ⭐",
+ language: "English 🇬🇧",
+ type: "Session 🗣",
+ track: "Java ( core frameworks & libraries )",
+ startDate: "2024-06-13",
+ startTime: "12:00:00",
+ endDate: "2024-06-13",
+ endTime: "14:00:00",
+ };
+
+ expect(sessionAdapter(session)).toEqual(expected);
+ });
+});
+
+describe("extractSessionTags", () => {
+ test("returns undefined when questionAnswers is empty", () => {
+ expect(extractSessionTags([])).toBeUndefined();
+ });
+
+ test("returns undefined when questionAnswers do not have a Tags/Topics question", () => {
+ const questionAnswers: QuestionAnswers[] = [
+ {
+ id: 45775,
+ question: "Question 1",
+ answer: "Answer 1",
+ questionType: "Short_Text",
+ },
+ {
+ id: 999,
+ question: "Question 2",
+ answer: "Answer 2",
+ questionType: "Short_Text",
+ },
+ ];
+
+ expect(extractSessionTags(questionAnswers)).toBeUndefined();
+ });
+
+ test("returns the expected output when questionAnswers have a Tags/Topics question", () => {
+ const questionAnswers: QuestionAnswers[] = [
+ {
+ id: 1,
+ question: "Question 1",
+ answer: "Answer 1",
+ questionType: "Short_Text",
+ },
+ {
+ id: 2,
+ question: "Tags/Topics",
+ answer: "tag1, tag2, tag3",
+ questionType: "Short_Text",
+ },
+ {
+ id: 3,
+ question: "Question 2",
+ answer: "Answer 2",
+ questionType: "Short_Text",
+ },
+ ];
+
+ expect(extractSessionTags(questionAnswers)).toEqual([
+ "tag1",
+ " tag2",
+ " tag3",
+ ]);
+ });
+});
+
+describe("extractSessionSlides", () => {
+ test("returns empty when questionAnswers is empty", () => {
+ expect(extractSessionSlides([])).toEqual("");
+ });
+
+ test("returns the expected output when questionAnswers have a Slides question", () => {
+ const questionAnswers: QuestionAnswers[] = [
+ {
+ id: 1,
+ question: "Question 1",
+ answer: "Answer 1",
+ questionType: "Short_Text",
+ },
+ {
+ id: 2,
+ question: "Slides",
+ answer: "https://www.google.com",
+ questionType: "Short_Text",
+ },
+ {
+ id: 3,
+ question: "Question 2",
+ answer: "Answer 2",
+ questionType: "Short_Text",
+ },
+ ];
+
+ expect(extractSessionSlides(questionAnswers)).toEqual(
+ "https://www.google.com",
+ );
+ });
+});
+
+describe("extractSessionCategoryInfo", () => {
+ const categories: SessionCategory[] = [
+ {
+ id: 4,
+ name: CategoryItemEnum.Level,
+ categoryItems: [
+ {id: 1, name: "Introductory and overview"},
+ {id: 2, name: "Intermediate"},
+ ],
+ },
+ {
+ id: 8,
+ name: CategoryItemEnum.Language,
+ categoryItems: [
+ {id: 3, name: "English"},
+ {id: 4, name: "Spanish"},
+ ],
+ },
+ ];
+
+ test("returns undefined when categories is empty", () => {
+ expect(
+ extractSessionCategoryInfo([], CategoryItemEnum.Level),
+ ).toBeUndefined();
+ });
+
+ test("returns undefined when the requested item is not present in categories", () => {
+ expect(
+ extractSessionCategoryInfo(categories, CategoryItemEnum.Track),
+ ).toBeUndefined();
+ });
+
+ test("returns the expected output when the requested item is present in categories", () => {
+ expect(
+ extractSessionCategoryInfo(categories, CategoryItemEnum.Level),
+ ).toEqual("Introductory and overview ⭐");
+ });
+
+ test("returns the expected output when the requested item is present in categories with a different name", () => {
+ expect(
+ extractSessionCategoryInfo(categories, CategoryItemEnum.Language),
+ ).toEqual("English 🇬🇧");
+ });
+});
+
+describe("Fetch Talks by id", () => {
+ beforeAll(() => {
+ jest.mock("axios");
+ });
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("fetches and returns talks data for a specific id", async () => {
+ const payload: AxiosResponse = {
+ status: 200,
+ statusText: "OK",
+ headers: {},
+ config: {
+ headers: axiosHeaders,
+ },
+ data: {
+ id: faker.number.int(),
+ title: faker.lorem.text(),
+ description: faker.lorem.lines(1),
+ startsAt: faker.date.past().toString(),
+ endsAt: faker.date.past().toString(),
+ slidesURL: faker.internet.url(),
+ speakers: [
+ {
+ id: faker.string.uuid(),
+ name: faker.person.fullName(),
+ },
+ ],
+ categories: [
+ {
+ id: 123,
+ name: CategoryItemEnum.Level,
+ categoryItems: [
+ {
+ id: faker.number.int(),
+ name: faker.lorem.words(1),
+ },
+ ],
+ },
+ ],
+ questionAnswers: [
+ {
+ id: 123,
+ question: "",
+ questionType: "",
+ answer: "",
+ },
+ ],
+ recordingUrl: "",
+ track: "",
+ },
+ };
+
+ mockedAxios.get.mockImplementation(() => Promise.resolve(payload));
+
+ const wrapper: FC>> = ({
+ children,
+ }) => {
+ return (
+
+ {children}
+
+ );
+ };
+
+ const {result} = renderHook(() => useFetchTalksById("1234"), {
+ wrapper,
+ });
+
+ await waitFor(() => result.current.isSuccess);
+ await waitFor(() => !result.current.isLoading);
+ expect(mockedAxios.get).toHaveBeenNthCalledWith(
+ 1,
+ "https://sessionize.com/api/v2/teq4asez/view/Sessions",
+ );
+ expect(mockedAxios.get).toHaveReturnedTimes(1);
+ //expect(result.current.isLoading).toEqual(false);
+ expect(result.current.error).toEqual(null);
+ //expect(result.current.data).toEqual(sessionAdapter(payload.data));
+ });
+});
+
+describe("Fetch Live session talks", () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ queryClient.clear();
+ });
+
+ it.skip("fetches and returns ungrouped talks data", async () => {
+ const payload: AxiosResponse = {
+ status: 200,
+ statusText: "OK",
+ headers: {},
+ config: {
+ headers: axiosHeaders,
+ },
+ data: {
+ id: faker.string.uuid(),
+ title: faker.lorem.lines(1),
+ description: faker.lorem.lines(2),
+ startsAt: faker.date.past().toLocaleString(),
+ endsAt: faker.date.past().toLocaleString(),
+ isConfirmed: true,
+ isInformed: true,
+ isPlenumSession: false,
+ liveURL: null,
+ isServiceSession: false,
+ status: "Accepted",
+ room: "Main Stage",
+ roomID: faker.number.int(),
+ questionAnswers: [],
+ recordingURL: null,
+ categories: [
+ {
+ id: faker.number.int(),
+ name: "Session format",
+ sort: 0,
+ categoryItems: [],
+ },
+ ],
+ speakers: [
+ {
+ id: faker.string.uuid(),
+ name: faker.person.fullName(),
+ },
+ ],
+ },
+ };
+
+ mockedAxios.get.mockResolvedValue(payload);
+
+ const {result} = renderHook(() => useFetchLiveView(), {
+ wrapper,
+ });
+
+ await waitFor(() => {
+ expect(result.current.isSuccess).toBe(true);
+ });
+
+ expect(mockedAxios.get).toHaveBeenCalledWith(
+ "https://sessionize.com/api/v2/ezm48alx/view/Sessions",
+ );
+ //expect(result.current.data).toStrictEqual(payload.data);
+ expect(result.current.error).toBeNull();
+ });
+});
diff --git a/src/App.tsx b/src/App.tsx
index db31e292..53c7ad58 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,51 +1,67 @@
-import { Link, Route, Routes } from "react-router-dom";
+import {Link, Route, Routes} from "react-router-dom";
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_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_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,
} from "./constants/routes";
import Footer from "./components/Footer/Footer";
-import { HomeWrapper } from "./views/Home/HomeWrapper";
-import MeetingDetailContainer from "./views/MeetingDetail/MeetingDetailContainer";
+import {HomeWrapper} from "./views/Home/HomeWrapper";
+import MeetingDetailContainer
+ from "./views/MeetingDetail/MeetingDetailContainer";
import Navigation from "./components/Navigation/Navigation";
import ScrollToTop from "./components/ScrollToTop/ScrollToTop";
-import SpeakerDetailContainer from "./views/SpeakerDetail/SpeakerDetailContainer";
+import SpeakerDetailContainer
+ from "./views/SpeakerDetail/SpeakerDetailContainer";
import styled from "styled-components";
-import React, { FC } from "react";
-import { CookieConsent } from "react-cookie-consent";
-import { Color } from "./styles/colors";
+import React, {FC} 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 {QueryClient, QueryClientProvider} from "react-query";
import Talks from "./views/Talks/Talks";
import Conditions from "./views/Conditions/Conditions";
import Cookies from "./views/Cookies/Cookies";
@@ -54,9 +70,10 @@ import SpeakerInformation from "./views/Speakers/SpeakerInformation";
import About from "./views/About/About";
import Travel from "./views/Travel/Travel";
import NotFoundError from "./components/NotFoundError/NotFoundError";
-import { Home2023Wrapper } from "./2023/Home/Home2023Wrapper";
+import {Home2023Wrapper} from "./2023/Home/Home2023Wrapper";
import Speakers2023 from "./2023/Speakers/Speakers2023";
-import SpeakerDetailContainer2023 from "./2023/SpeakerDetail/SpeakerDetailContainer2023";
+import SpeakerDetailContainer2023
+ from "./2023/SpeakerDetail/SpeakerDetailContainer2023";
import Talks2023 from "./2023/Talks/Talks2023";
import TalkDetailContainer2023 from "./2023/TalkDetail/TalkDetailContainer2023";
import AttendeeInformation2023 from "./2023/Attendee/AttendeeInformation2023";
@@ -71,361 +88,478 @@ import JobOffers2023 from "./2023/JobOffers/JobOffers2023";
import Sponsorship from "./views/sponsorship/Sponsorship";
import Diversity2023 from "./2023/Diversity/Diversity2023";
import CfpSection from "./views/Cfp/CfpSection";
-import { CodeOfConduct } from "./views/CodeOfConduct/CodeOfConduct";
-import { Accommodation } from "./views/Travel/Accommodation";
+import {CodeOfConduct} from "./views/CodeOfConduct/CodeOfConduct";
+import {Accommodation} from "./views/Travel/Accommodation";
import Schedule from "./views/Schedule/Schedule";
import Diversity from "./views/Diversity/Diversity";
import LiveView from "./views/Talks/LiveView";
import JobOffers from "./views/JobOffers/JobOffers";
+import {HomeWrapper2024} from "./2024/HomeWrapper2024";
+import Speakers2024 from "./2024/Speakers/Speakers2024";
+import Talks2024 from "./2024/Talks/Talks2024";
const StyledAppWrapper = styled.div`
- position: relative;
- min-height: 100vh;
+ position: relative;
+ min-height: 100vh;
`;
const isDevBcnCookieSet = document.cookie
- .split("; ")
- .some((row) => row.startsWith("DevBcnCookie="));
+ .split("; ")
+ .some((row) => row.startsWith("DevBcnCookie="));
const RenderCookie = () => (
- <>
- {!isDevBcnCookieSet && (
-
- This website uses cookies to enhance the user experience.{" "}
-
- Read here
-
-
- )}
- >
+ <>
+ {!isDevBcnCookieSet && (
+
+ This website uses cookies to enhance the user experience.{" "}
+
+ Read here
+
+
+ )}
+ >
);
const App: FC> = () => {
- const queryClient = new QueryClient();
- return (
-
-
-
-
-
- } />
- }>
-
-
- }
- />
- {/* }>
+ const queryClient = new QueryClient();
+ return (
+
+
+
+
+
+ }/>
+ }>
+
+
+ }
+ />
+ {/* }>
} />*/}
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {
- }>
-
-
- }
- />
- }
- {
- }>
-
-
- }
- />
- }
- }>
-
-
- }
- />
- */
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/* }>
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ {
+ }>
+
+
+ }
+ />
+ }
+ {
+ }>
+
+
+ }
+ />
+ }
+ }>
+
+
+ }
+ />
+ */
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ {/* }>
} />*/}
- {/* }>
+ {/* }>
} />*/}
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- {/* 2023 Edition */}
- }>
-
-
- }
- />
- {
- }>
-
-
- }
- />
- }
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
- }>
-
-
- }
- />
-
-
-
-
-
- );
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ {/* 2024 Edition */}
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ {/* 2023 Edition */}
+ }>
+
+
+ }
+ />
+ {
+ }>
+
+
+ }
+ />
+ }
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+
+
+
+
+
+ );
};
export default App;
diff --git a/src/components/Navigation/Navigation.tsx b/src/components/Navigation/Navigation.tsx
index c1b5682b..8c25dda4 100644
--- a/src/components/Navigation/Navigation.tsx
+++ b/src/components/Navigation/Navigation.tsx
@@ -1,140 +1,147 @@
-import { AnimatePresence } from "framer-motion";
-import { FC, useEffect, useState } from "react";
-import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints";
-import { useLocation, useNavigate } from "react-router-dom";
+import {AnimatePresence} from "framer-motion";
+import {FC, useEffect, useState} from "react";
+import {MOBILE_BREAKPOINT} from "../../constants/BreakPoints";
+import {useLocation, useNavigate} from "react-router-dom";
import Breadcrumbs from "./Breadcrumbs";
import CloseIcon from "../../assets/images/CloseIcon.svg";
import NavigationLogo from "../../assets/images/devBcn.png";
-import { ROUTE_HOME, ROUTE_HOME_ALTERNATE } from "../../constants/routes";
+import {ROUTE_HOME, ROUTE_HOME_ALTERNATE} from "../../constants/routes";
import TicketsImage from "../../assets/images/TicketsImage.svg";
-import { navigationItems, subMenuItems } from "./NavigationData";
+import {navigationItems2025, subMenuItems2025} from "./NavigationData";
import {
- navigationItems2023,
- subMenuItems2023,
+ navigationItems2023,
+ subMenuItems2023,
} from "../../2023/Navigation/NavigationData2023";
-import { useWindowSize } from "react-use";
+import {useWindowSize} from "react-use";
import {
- StyledClipPath,
- StyledHeader,
- StyledHeaderLogo,
- StyledHeaderWrapper,
- StyledLink,
- StyledMenuIcon,
- StyledNavigation,
- StyledNavigationContainer,
- StyledNavigationLogo,
- StyledNavLinkHighlightedImage,
- StyledTicketLink,
+ StyledClipPath,
+ StyledHeader,
+ StyledHeaderLogo,
+ StyledHeaderWrapper,
+ StyledLink,
+ StyledMenuIcon,
+ StyledNavigation,
+ StyledNavigationContainer,
+ StyledNavigationLogo,
+ StyledNavLinkHighlightedImage,
+ StyledTicketLink,
} from "./Style.Navigation";
-import { HorizontalMenu } from "./HorizontalMenu";
-import { HamburgerMenu } from "./HamburgerMenu";
+import {HorizontalMenu} from "./HorizontalMenu";
+import {HamburgerMenu} from "./HamburgerMenu";
const Navigation: FC> = () => {
- const { width } = useWindowSize();
- const [isOpened, setIsOpened] = useState(false);
- const [is2023, setIs2023] = useState(false);
- const [navItems, setNavItems] = useState(navigationItems);
- const [subNavItems, setSubNavItems] = useState(subMenuItems);
- const { pathname } = useLocation();
- const navigate = useNavigate();
- const handleLogoClick = () => {
- navigate(ROUTE_HOME);
- };
- const handleSetMenu = () => {
- setIsOpened(!isOpened);
- };
- const isHomePage = () => {
- return pathname === ROUTE_HOME || pathname === ROUTE_HOME_ALTERNATE;
- };
+ const {width} = useWindowSize();
+ const [isOpened, setIsOpened] = useState(false);
+ const [is2023, setIs2023] = useState(false);
+ const [navItems, setNavItems] = useState(navigationItems2025);
+ const [subNavItems, setSubNavItems] = useState(subMenuItems2025);
+ const {pathname} = useLocation();
+ const navigate = useNavigate();
+ const handleLogoClick = () => {
+ navigate(ROUTE_HOME);
+ };
+ const handleSetMenu = () => {
+ setIsOpened(!isOpened);
+ };
+ const isHomePage = () => {
+ return pathname === ROUTE_HOME || pathname === ROUTE_HOME_ALTERNATE;
+ };
- useEffect(() => {
- if (pathname.startsWith("/2023")) {
- setIs2023(true);
- setNavItems(navigationItems2023);
- setSubNavItems(subMenuItems2023);
- } else {
- setNavItems(navigationItems);
- setSubNavItems(subMenuItems);
- }
- }, [pathname, navItems, subNavItems]);
+ useEffect(() => {
+ if (pathname.startsWith("/2024")) {
+ setNavItems(navigationItems2025);
+ setSubNavItems(subMenuItems2025);
+ }
- return (
- <>
-
-
-
-
-
-
+ if (pathname.startsWith("/2023")) {
+ setIs2023(true);
+ setNavItems(navigationItems2023);
+ setSubNavItems(subMenuItems2023);
+ } else {
+ setNavItems(navigationItems2025);
+ setSubNavItems(subMenuItems2025);
+ }
+ }, [pathname, navItems, subNavItems]);
- {!isHomePage() && }
-
-
- {isOpened && (
- MOBILE_BREAKPOINT
- ? { width: "140vw" }
- : { width: "100vw" }
- }
- exit={{ width: 0 }}
- >
-
-
- {
- navigate(ROUTE_HOME);
- handleSetMenu();
- }}
- />
- {navItems.map((item) => (
-
- {item.id}
-
- ))}
- {subNavItems.map((item) => (
-
- {item.id}
-
- ))}
+ return (
+ <>
+
+
+
+
+
+
-
-
-
-
- {width > MOBILE_BREAKPOINT && }
-
- )}
-
- >
- );
+ {!isHomePage() && }
+
+
+ {isOpened && (
+ MOBILE_BREAKPOINT
+ ? {width: "140vw"}
+ : {width: "100vw"}
+ }
+ exit={{width: 0}}
+ >
+
+
+ {
+ navigate(ROUTE_HOME);
+ handleSetMenu();
+ }}
+ />
+ {navItems.map((item) => (
+
+ {item.id}
+
+ ))}
+ {subNavItems.map((item) => (
+
+ {item.id}
+
+ ))}
+
+
+
+
+
+ {width > MOBILE_BREAKPOINT && }
+
+ )}
+
+ >
+ );
};
export default Navigation;
diff --git a/src/components/Navigation/NavigationData.ts b/src/components/Navigation/NavigationData.ts
index e4bf5275..07289c14 100644
--- a/src/components/Navigation/NavigationData.ts
+++ b/src/components/Navigation/NavigationData.ts
@@ -5,12 +5,7 @@ import {
ROUTE_CODE_OF_CONDUCT,
ROUTE_DIVERSITY,
ROUTE_HOME,
- ROUTE_JOB_OFFERS,
- ROUTE_KCD,
- ROUTE_SCHEDULE,
- ROUTE_SPEAKERS,
ROUTE_SPONSORSHIP,
- ROUTE_TALKS,
ROUTE_TRAVEL,
} from "../../constants/routes";
@@ -19,23 +14,22 @@ export interface NavigationItem {
link: string;
}
-export const navigationItems: NavigationItem[] = [
+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: "SCHEDULE", link: ROUTE_SCHEDULE },
+ //{ id: "Talks", link: ROUTE_TALKS },
//{ id: "Workshops", link: ROUTE_WORKSHOPS },
- { id: "JOB OFFERS", link: ROUTE_JOB_OFFERS },
+ //{ id: "JOB OFFERS", link: ROUTE_JOB_OFFERS },
//{ id: "Communities", link: ROUTE_COMMUNITIES },
- { id: "Speakers", link: ROUTE_SPEAKERS },
+ //{ id: "Speakers", link: ROUTE_SPEAKERS },
{ id: "About Us", link: ROUTE_ABOUT_US },
{ id: "Travel", link: ROUTE_TRAVEL },
- { id: "KCD - Barcelona", link: ROUTE_KCD },
{ id: "Sponsorship", link: ROUTE_SPONSORSHIP },
];
-export const subMenuItems: NavigationItem[] = [
+export const subMenuItems2025: NavigationItem[] = [
{ id: "DIVERSITY", link: ROUTE_DIVERSITY },
{ id: "Cfp Committee", link: ROUTE_CFP },
{ id: "Accommodation", link: ROUTE_ACCOMMODATION },
diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx
index 784c64cf..7818e330 100644
--- a/src/components/UI/Button.tsx
+++ b/src/components/UI/Button.tsx
@@ -1,7 +1,7 @@
-import React, { FC, ReactNode } from "react";
+import React, {FC, ReactNode} from "react";
import styled from "styled-components";
-import { Color } from "../../styles/colors";
-import { BIG_BREAKPOINT } from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
interface ButtonProps {
text: string;
@@ -91,7 +91,7 @@ const Button: FC> = ({
{children}
{` ${text}`}
- {disabled && SOLD OUT }
+ {disabled && SOON }
>
);
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index bb8c5b95..722f1fee 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -44,3 +44,22 @@ export const ROUTE_2023_TALKS = "/2023/talks";
export const ROUTE_2023_TALK_DETAIL = "/2023/talk";
export const ROUTE_2023_TALK_DETAIL_PLAIN = "/2023/talk/:id";
export const ROUTE_2023_WORKSHOPS = "/2023/workshops";
+
+// 2024
+
+export const ROUTE_2024_ATTENDEE = "/2024/attendee";
+export const ROUTE_2024_CFP = "/2024/cfp";
+export const ROUTE_2024_COMMUNITIES = "/2024/communities";
+export const ROUTE_2024_DIVERSITY = "/2024/diversity";
+export const ROUTE_2024_HOME = "/2024";
+export const ROUTE_2024_JOB_OFFERS = "/2024/jobOffers";
+export const ROUTE_2024_SCHEDULE = "/2024/schedule";
+export const ROUTE_2024_SESSION_FEEDBACK = "/2024/session-feedback";
+export const ROUTE_2024_SPEAKERS = "/2024/speakers";
+export const ROUTE_2024_SPEAKER_DETAIL = "/2024/speaker";
+export const ROUTE_2024_SPEAKER_DETAIL_PLAIN = "/2024/speaker/:id";
+export const ROUTE_2024_SPEAKER_INFO = "/2024/speaker-information";
+export const ROUTE_2024_TALKS = "/2024/talks";
+export const ROUTE_2024_TALK_DETAIL = "/2024/talk";
+export const ROUTE_2024_TALK_DETAIL_PLAIN = "/2024/talk/:id";
+export const ROUTE_2024_WORKSHOPS = "/2024/workshops";
\ No newline at end of file
diff --git a/src/data/2023.json b/src/data/2023.json
index 9f8efa69..b14898e1 100644
--- a/src/data/2023.json
+++ b/src/data/2023.json
@@ -1,5 +1,5 @@
{
- "actionButtons": true,
+ "actionButtons": false,
"carrousel": {
"enabled": false
},
@@ -24,8 +24,8 @@
"schedule": {
"enabled": true
},
- "showInfoButtons": true,
"showCountdown": false,
+ "showInfoButtons": true,
"sponsors": {
"startDate": "2022-01-01T09:00:00",
"endDate": "2023-07-13T09:00:00"
diff --git a/src/data/2024.json b/src/data/2024.json
index 8353d010..27d3517c 100644
--- a/src/data/2024.json
+++ b/src/data/2024.json
@@ -1,5 +1,5 @@
{
- "actionButtons": true,
+ "actionButtons": false,
"carrousel": {
"enabled": true
},
@@ -24,8 +24,8 @@
"schedule": {
"enabled": true
},
- "showCountdown": true,
- "showInfoButtons": false,
+ "showCountdown": false,
+ "showInfoButtons": true,
"sponsors": {
"startDate": "2023-12-01T09:00:00",
"endDate": "2024-05-13T09:00:00"
diff --git a/src/data/2025.json b/src/data/2025.json
new file mode 100644
index 00000000..0e83bdb0
--- /dev/null
+++ b/src/data/2025.json
@@ -0,0 +1,43 @@
+{
+ "actionButtons": true,
+ "carrousel": {
+ "enabled": false
+ },
+ "cfp": {
+ "startDay": "2025-01-01T00:00:00",
+ "endDay": "2025-04-01T00:00:00",
+ "link": "https://sessionize.com/devbcn-2025/"
+ },
+ "diversity": false,
+ "edition": "2025",
+ "email": "info@devbcn.com",
+ "endDay": "2025-06-14T14:00: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",
+ "schedule": {
+ "enabled": false
+ },
+ "showCountdown": true,
+ "showInfoButtons": false,
+ "sponsors": {
+ "startDate": "2023-12-01T09:00:00",
+ "endDate": "2025-05-13T09:00:00"
+ },
+ "startDay": "2025-06-13T09:00:00",
+ "tickets": {
+ "startDay": "2025-01-01T00:00:00",
+ "endDay": "2025-06-01T00:00: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"
+}
diff --git a/src/views/Home/HomeWrapper.tsx b/src/views/Home/HomeWrapper.tsx
index a951721c..dfcec282 100644
--- a/src/views/Home/HomeWrapper.tsx
+++ b/src/views/Home/HomeWrapper.tsx
@@ -1,13 +1,13 @@
-import { BIG_BREAKPOINT } from "../../constants/BreakPoints";
-import React, { FC, useState } from "react";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
+import React, {FC, useState} from "react";
import Faqs from "./components/Faqs/Faqs";
import Home from "./components/Home/Home";
import SpeakersCarousel from "./components/SpeakersCarousel/SpeakersCarousel";
import Sponsors from "./components/Sponsors/Sponsors";
import styled from "styled-components";
-import { useLocation } from "react-router-dom";
-import { useEventEdition } from "./UseEventEdition";
+import {useLocation} from "react-router-dom";
+import {useEventEdition} from "./UseEventEdition";
const StyledContainer = styled.div`
padding-bottom: 10rem;
@@ -66,7 +66,7 @@ export const HomeWrapper: FC> = () => {
-
+ {edition?.carrousel.enabled && }
);
diff --git a/src/views/Home/UseEventEdition.tsx b/src/views/Home/UseEventEdition.tsx
index 255b8cea..781e257e 100644
--- a/src/views/Home/UseEventEdition.tsx
+++ b/src/views/Home/UseEventEdition.tsx
@@ -1,12 +1,12 @@
-import { useEffect } from "react";
-import { useParams } from "react-router-dom";
+import {useEffect} from "react";
+import {useParams} from "react-router-dom";
export function useEventEdition(setEdition: (data: any) => void) {
let { year } = useParams();
useEffect(() => {
// Fallback to the current year if no year is provided in the URL
- const editionYear = year ?? "2024";
+ const editionYear = year ?? "2025";
import(`../../data/${editionYear}.json`)
.then((data) => {
diff --git a/src/views/Home/components/ActionButtons/ActionButtons.tsx b/src/views/Home/components/ActionButtons/ActionButtons.tsx
index dea0f545..7048d2f3 100644
--- a/src/views/Home/components/ActionButtons/ActionButtons.tsx
+++ b/src/views/Home/components/ActionButtons/ActionButtons.tsx
@@ -1,9 +1,9 @@
-import { FC, useCallback } from "react";
+import {FC, useCallback} from "react";
import data from "../../../../data/2024.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 {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
+import {gaEventTracker} from "../../../../components/analytics/Analytics";
const StyledActionDiv = styled.div`
display: flex;
@@ -44,7 +44,7 @@ const ActionButtons: FC> = () => {
{isBetween(CFPStartDay, CFPEndDay) && (
diff --git a/src/views/Home/components/Faqs/Faqs.tsx b/src/views/Home/components/Faqs/Faqs.tsx
index ef3ec02a..3788ba38 100644
--- a/src/views/Home/components/Faqs/Faqs.tsx
+++ b/src/views/Home/components/Faqs/Faqs.tsx
@@ -1,18 +1,19 @@
-import { Color } from "../../../../styles/colors";
-import { FC, Suspense, useState } from "react";
+import {Color} from "../../../../styles/colors";
+import {FC, Suspense} from "react";
import FaqCard from "./components/FaqsCard";
import LessThanIcon from "../../../../assets/images/LessThanBlueIcon.svg";
import MoreThanIcon from "../../../../assets/images/LessThanBlueWhiteIcon.svg";
-import SectionWrapper from "../../../../components/SectionWrapper/SectionWrapper";
-import { faqsData } from "./FaqsData";
-import { useWindowSize } from "react-use";
-import { MOBILE_BREAKPOINT } from "../../../../constants/BreakPoints";
+import SectionWrapper
+ from "../../../../components/SectionWrapper/SectionWrapper";
+import {faqsData} from "./FaqsData";
+import {useWindowSize} from "react-use";
+import {MOBILE_BREAKPOINT} from "../../../../constants/BreakPoints";
import flickr from "../../../../assets/images/flickr.svg";
import youtube from "../../../../assets/images/youtube.svg";
import image1 from "../../../../assets/images/devbcn-1.jpg";
import image2 from "../../../../assets/images/devbcn2.jpg";
import Logo from "../../../../assets/images/logo.svg";
-import { StyledLoadingImage } from "../../../../components/Loading/Loading";
+import {StyledLoadingImage} from "../../../../components/Loading/Loading";
import {
StyledFaqSection,
StyledH2,
@@ -23,14 +24,9 @@ import {
StyleLessIcon,
StyleMoreIcon,
} from "./Faqs.style";
-import { useEventEdition } from "../../UseEventEdition";
-import { Edition } from "../../HomeWrapper";
const Faqs: FC> = () => {
const { width } = useWindowSize();
- const [edition, setEdition] = useState();
-
- useEventEdition(setEdition);
return (
<>
@@ -53,14 +49,22 @@ const Faqs: FC> = () => {
Spain, now including more technologies and tracks.
- Check for videos/photos and{" "}
+ Check for videos/photos andsummary of the DevBcn —{" "}
+
+
+ 2024 edition —
+
- summary of the DevBcn -{" "}
- {parseInt(edition?.edition as string) - 1} edition
+
+ {" "}2023 edition
> = () => {
- const { width } = useWindowSize();
- const [edition, setEdition] = useState();
+ const {width} = useWindowSize();
+ const [edition, setEdition] = useState();
- useEventEdition(setEdition);
+ useEventEdition(setEdition);
- return (
-
-
-
-
-
- +
-
-
-
-
- The Barcelona Developers Conference {edition?.edition}
-
-
- Former{" "}
-
- JBCNConf
- {" "}
-
-
- Multidisciplinary conference made for Developers and by
- Developers, to learn and share on the different modern software
- technologies used across the companies
-
-
-
- Past events: 2023 edition
-
-
-
-
-
- {edition?.startDay &&
- edition.endDay &&
- formatDateRange(
- new Date(edition.startDay),
- new Date(edition.endDay),
- )}
-
-
- La Farga, Hospitalet, Barcelona
-
-
-
-
- {edition?.trackNumber} tracks with the following topics:
- {edition?.tracks}
-
-
- {data.showCountdown && (
-
- )}
- {edition?.actionButtons && }
- {edition?.showInfoButtons && }
+ return (
+
+
+
+
+
+
+
+
+ The Barcelona Developers
+ Conference {edition?.edition}
+
+
+ Former{" "}
+
+ JBCNConf
+ {" "}
+
+
+ Multidisciplinary conference made for Developers and
+ by
+ Developers, to learn and share on the different
+ modern software
+ technologies used across the companies
+
+
+
+ Past events: 2024
+ edition{" "}| 2023
+ edition
+
+
+
+
+
+ {edition?.startDay &&
+ edition.endDay &&
+ formatDateRange(
+ new Date(edition.startDay),
+ new Date(edition.endDay),
+ )}
+
+
+ La Farga, Hospitalet, Barcelona
+
+
+
+
+ {edition?.trackNumber} tracks with the following
+ topics:
+ {edition?.tracks}
+
+
+ {data.showCountdown && (
+
+ )}
+ {edition?.actionButtons && }
+ {edition?.showInfoButtons && }
- {width > BIGGER_BREAKPOINT && (
-
- )}
- {width > BIGGER_BREAKPOINT && (
-
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / /{" "}
-
-
- )}
-
- {width > BIGGER_BREAKPOINT && (
-
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
- / /{" "}
-
- )}
-
-
-
-
- );
+ {width > BIGGER_BREAKPOINT && (
+
+ )}
+ {width > BIGGER_BREAKPOINT && (
+
+
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / /{" "}
+
+
+ )}
+
+ {width > BIGGER_BREAKPOINT && (
+
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / / / / / / / / / / / / / / / / / / / / / / / /
+ / / / / / / / /
+ / /{" "}
+
+ )}
+
+
+
+
+ );
};
export default Home;
diff --git a/src/views/Home/components/SpeakersCarousel/SpeakerSwiper.tsx b/src/views/Home/components/SpeakersCarousel/SpeakerSwiper.tsx
index 35151b42..016fee9c 100644
--- a/src/views/Home/components/SpeakersCarousel/SpeakerSwiper.tsx
+++ b/src/views/Home/components/SpeakersCarousel/SpeakerSwiper.tsx
@@ -1,14 +1,14 @@
-import React, { FC } from "react";
-import { Autoplay, Parallax } from "swiper";
-import { Swiper, SwiperSlide } from "swiper/react";
+import React, {FC} from "react";
+import {Autoplay, Parallax} from "swiper";
+import {Swiper, SwiperSlide} from "swiper/react";
import styled from "styled-components";
-import { Color } from "../../../../styles/colors";
+import {Color} from "../../../../styles/colors";
import "swiper/swiper-bundle.min.css";
import "./SpeakersCarousel.scss";
-import { Link } from "react-router-dom";
-import conferenceData from "../../../../data/2024.json";
-import { ROUTE_SPEAKER_DETAIL } from "../../../../constants/routes";
-import { useFetchSpeakers } from "../../../Speakers/UseFetchSpeakers";
+import {Link} from "react-router-dom";
+import conferenceData from "../../../../data/2025.json";
+import {ROUTE_SPEAKER_DETAIL} from "../../../../constants/routes";
+import {useFetchSpeakers} from "../../../Speakers/UseFetchSpeakers";
import * as Sentry from "@sentry/react";
const StyledSlideImage = styled.img`
diff --git a/src/views/Home/components/Sponsors/SponsorsData.ts b/src/views/Home/components/Sponsors/SponsorsData.ts
index ae946890..73965a28 100644
--- a/src/views/Home/components/Sponsors/SponsorsData.ts
+++ b/src/views/Home/components/Sponsors/SponsorsData.ts
@@ -15,165 +15,11 @@ export interface Sponsor {
}
export const sponsors: Sponsors = {
- top: [
- {
- name: "DATASTAX",
- image: "images/sponsors/datastax.png",
- website: "https://www.datastax.com/",
- },
- ],
- premium: [
- {
- name: "Allianz",
- image: "images/sponsors/allianz.png",
- website: "https://tech.allianz.com/en.html",
- },
- {
- name: "Barcelona JUG",
- image: "images/sponsors/bcn-jug.png",
- website: "https://www.meetup.com/barcelonajug/",
- },
- {
- name: "PREM.AI",
- website: "https://www.premai.io/",
- image: "images/sponsors/prem-ai.png",
- },
- {
- name: "Revolut",
- website: "https://www.revolut.com/working-at-revolut/",
- image: "images/sponsors/revolut.png",
- },
- ],
- regular: [
- {
- name: "Sopra Steria",
- image: "/images/sponsors/sopra.png",
- website: "https://www.soprasteria.es/",
- },
- {
- name: "Caixabank Tech",
- website: "https://www.caixabanktech.com/es/pagina-de-inicio/",
- image: "images/sponsors/caixabank-tech.png",
- },
- {
- name: "Idealista",
- image: "images/sponsors/idealista.jpg",
- website: "https://www.idealista.com/info/trabaja-con-nosotros",
- },
- {
- name: "Clever Cloud",
- image: "images/sponsors/clever-cloud.png",
- website: "https://www.clever-cloud.com/",
- },
- {
- name: "ALTEN",
- image: "images/sponsors/alten.png",
- website: "https://www.alten.es/",
- },
- {
- name: "TIGERA",
- image: "images/sponsors/tigera.png",
- website: "https://www.tigera.io/",
- },
- {
- name: "Dynatrace",
- image: "images/sponsors/dynatrace.png",
- website: "https://www.dynatrace.com/",
- },
- ],
- basic: [
- {
- name: "Seidor",
- website: "https://www.opentrends.net/en",
- image: "images/sponsors/seidor.png",
- },
- {
- name: "Perfect Scale",
- website: "https://www.perfectscale.io/",
- image: "images/sponsors/perfect-scale.png",
- },
- {
- name: "Auth0",
- image: "images/sponsors/auth0.png",
- website: "https://okta.com/careers",
- },
- {
- name: "Barcelona Activa",
- image: "images/sponsors/barcelona-activa.png",
- website: "https://www.barcelonactiva.cat/",
- },
- {
- name: "Reevo",
- image: "images/sponsors/reevo.png",
- website: "https://www.reevo.it/",
- },
- {
- name: "GFT",
- website: "https://www.gft.com/es/es/",
- image: "images/sponsors/GFT.jpg",
- },
- {
- name: "Grupo Castilla",
- image: "images/sponsors/grupo-castilla.png",
- website:
- "https://www.grupocastilla.es/servicios-rrhh/consultoria-tecnologica/",
- },
- {
- name: "Axa",
- image: "images/sponsors/axa.png",
- website: "https://www.axapartners.es/es",
- },
- ],
- communities: [
- {
- name: "KCD Barcelona",
- website:
- "https://community.cncf.io/events/details/cncf-kcd-spain-presents-kcd-barcelona-2024/",
- image: "/images/KCD-logo-black.png",
- },
- {
- name: "Apache Foundation",
- image: "images/sponsors/apache-foundation.jpeg",
- website: "https://www.apache.org/",
- },
- {
- name: "Eclipse Foundation",
- image: "images/sponsors/eclipse-foundation.png",
- website: "https://www.eclipse.org/",
- },
- {
- name: "Foojay",
- image: "images/sponsors/foojay.jpg",
- website: "https://foojay.io/",
- },
- {
- name: "Migracode Barcelona",
- image: "images/sponsors/migracode.jpg",
- website: "https://www.migracode.org/",
- },
- {
- name: "Step4ward",
- image: "images/sponsors/step4ward.png",
- website: "https://bit.ly/step4wardhome",
- },
- ],
- media_partners: [
- {
- 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//",
- },
- ],
- supporters: [
- {
- name: "BarcelonaJS",
- website: "https://barcelonajs.com/",
- image: "images/sponsors/barcelona-js.png",
- },
- ],
+ top: [],
+ premium: [],
+ regular: [],
+ basic: [],
+ communities: [],
+ media_partners: [],
+ supporters: [],
};
From 73e24ddc3434c4457ce03de7948d847ecb2cc6fa Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:20:19 +0100
Subject: [PATCH 02/29] style: styled components
---
src/components/SectionWrapper/SectionWrapper.tsx | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/components/SectionWrapper/SectionWrapper.tsx b/src/components/SectionWrapper/SectionWrapper.tsx
index 9229b8c2..c2bbc72d 100644
--- a/src/components/SectionWrapper/SectionWrapper.tsx
+++ b/src/components/SectionWrapper/SectionWrapper.tsx
@@ -1,8 +1,10 @@
-import { FC, ReactNode } from "react";
-import { MAX_WIDTH } from "../../constants/BreakPoints";
+import {FC, ReactNode} from "react";
+import {MAX_WIDTH} from "../../constants/BreakPoints";
import styled from "styled-components";
-const StyledSectionWrapper = styled.div<{
+const StyledSectionWrapper = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['paddingBottom'].includes(prop),
+})<{
color: string;
paddingBottom: number;
}>`
@@ -13,12 +15,11 @@ const StyledSectionWrapper = styled.div<{
height: 100%;
justify-content: start;
padding-bottom: ${({ paddingBottom }) => paddingBottom}px;
- /* height: 92vh;
- scroll-snap-align: start;
- scroll-snap-type: y mandatory;*/
`;
-const StyledInnerWrapper = styled.div<{ marginTop: number }>`
+const StyledInnerWrapper = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['marginTop'].includes(prop),
+})<{ marginTop: number }>`
width: 100%;
height: 100%;
position: relative;
From b477933bc85c7ce0967ac8ab68f8ce5365f92a97 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:26:16 +0100
Subject: [PATCH 03/29] style: styled components
---
src/2023/Home/components/Sponsors/Sponsors.style.ts | 10 +++++++---
src/views/Home/components/Sponsors/Sponsors.style.ts | 10 +++++++---
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/2023/Home/components/Sponsors/Sponsors.style.ts b/src/2023/Home/components/Sponsors/Sponsors.style.ts
index 3bcd263a..405495b1 100644
--- a/src/2023/Home/components/Sponsors/Sponsors.style.ts
+++ b/src/2023/Home/components/Sponsors/Sponsors.style.ts
@@ -3,7 +3,7 @@ import {
BIG_BREAKPOINT,
LARGE_BREAKPOINT,
} from "../../../../constants/BreakPoints";
-import { motion } from "framer-motion";
+import {motion} from "framer-motion";
const SponsorMargin = 11;
const sponsorMarginDesktop = 7;
@@ -91,7 +91,9 @@ export const StyledSponsorLogosContainer = styled.div`
@media (min-width: ${BIG_BREAKPOINT}px) {
}
`;
-export const StyledLogos = styled.div<{ position?: "left" | "right" }>`
+export const StyledLogos = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['position'].includes(prop),
+})<{ position?: "left" | "right" }>`
display: flex;
width: 100%;
@@ -232,7 +234,9 @@ export const StyledSponsorIconMicro = styled.img`
}
`;
-export const StyledSponsorBadgeLeft = styled(motion.div)<{
+export const StyledSponsorBadgeLeft = styled(motion.div).withConfig({
+ shouldForwardProp: (prop) => !['position'].includes(prop),
+})<{
color: string;
position: "left" | "right";
}>`
diff --git a/src/views/Home/components/Sponsors/Sponsors.style.ts b/src/views/Home/components/Sponsors/Sponsors.style.ts
index 2f387504..0714c285 100644
--- a/src/views/Home/components/Sponsors/Sponsors.style.ts
+++ b/src/views/Home/components/Sponsors/Sponsors.style.ts
@@ -3,7 +3,7 @@ import {
BIG_BREAKPOINT,
LARGE_BREAKPOINT,
} from "../../../../constants/BreakPoints";
-import { motion } from "framer-motion";
+import {motion} from "framer-motion";
const SponsorMargin = 11;
const sponsorMarginDesktop = 11;
@@ -94,7 +94,9 @@ export const StyledSponsorLogosContainer = styled.div`
@media (min-width: ${BIG_BREAKPOINT}px) {
}
`;
-export const StyledLogos = styled.div<{ position?: "left" | "right" }>`
+export const StyledLogos = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['position'].includes(prop),
+})<{ position?: "left" | "right" }>`
display: flex;
width: 100%;
@@ -222,7 +224,9 @@ export const StyledSponsorIconMicro = styled.img`
}
`;
-export const StyledSponsorBadgeLeft = styled(motion.div)<{
+export const StyledSponsorBadgeLeft = styled(motion.div).withConfig({
+ shouldForwardProp: (prop) => !['position'].includes(prop),
+})<{
color: string;
position: "left" | "right";
}>`
From 8c0e6bd6690d4eb8f6f7880ed18c3fef15c7179c Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:44:08 +0100
Subject: [PATCH 04/29] style: styled components
---
src/components/Footer/Styles.Footer.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/components/Footer/Styles.Footer.tsx b/src/components/Footer/Styles.Footer.tsx
index c794253d..51e59d6f 100644
--- a/src/components/Footer/Styles.Footer.tsx
+++ b/src/components/Footer/Styles.Footer.tsx
@@ -1,6 +1,6 @@
import styled from "styled-components";
-import { Color } from "../../styles/colors";
-import { BIG_BREAKPOINT } from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
export const StyledFooterAbsoluteContainer = styled.div`
position: absolute;
@@ -63,7 +63,9 @@ export const StyledFlexCol = styled.div`
align-items: center;
`;
-export const StyledFlexRow = styled.div<{ justify?: string }>`
+export const StyledFlexRow = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['justify'].includes(prop),
+})<{ justify?: string }>`
width: 100%;
display: flex;
align-items: center;
From 6c231790fdab08b8fda8ca757cab2cf5e39d7068 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:45:43 +0100
Subject: [PATCH 05/29] style: styled components
---
src/2023/Home/components/Faqs/Faqs.style.ts | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/2023/Home/components/Faqs/Faqs.style.ts b/src/2023/Home/components/Faqs/Faqs.style.ts
index 97f63359..b65a545e 100644
--- a/src/2023/Home/components/Faqs/Faqs.style.ts
+++ b/src/2023/Home/components/Faqs/Faqs.style.ts
@@ -1,7 +1,7 @@
import styled from "styled-components";
-import { Color } from "../../../../styles/colors";
-import { motion } from "framer-motion";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import {Color} from "../../../../styles/colors";
+import {motion} from "framer-motion";
+import {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
export type FaqCardType = {
faq: {
@@ -76,7 +76,9 @@ export const StyledFaqCard = styled.div`
margin-bottom: 4rem;
}
`;
-export const StyledFaqImageContainer = styled.div<{ padding: string }>`
+export const StyledFaqImageContainer = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['padding'].includes(prop),
+})<{ padding: string }>`
position: relative;
@media (min-width: 800px) {
height: auto;
From 5c34440e42d1e29639f2394912bafef546af6c08 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:49:26 +0100
Subject: [PATCH 06/29] style: styled components
---
src/2024/Sponsors/Sponsors.style.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/2024/Sponsors/Sponsors.style.ts b/src/2024/Sponsors/Sponsors.style.ts
index df3a0bad..3539fdd2 100644
--- a/src/2024/Sponsors/Sponsors.style.ts
+++ b/src/2024/Sponsors/Sponsors.style.ts
@@ -91,7 +91,9 @@ export const StyledSponsorLogosContainer = styled.div`
@media (min-width: ${BIG_BREAKPOINT}px) {
}
`;
-export const StyledLogos = styled.div<{ position?: "left" | "right" }>`
+export const StyledLogos = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['position'].includes(prop),
+})<{ position?: "left" | "right" }>`
display: flex;
width: 100%;
From c0a37f724cc042e0bf6a47987b481a56d8efc780 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:55:41 +0100
Subject: [PATCH 07/29] style: styled components
---
src/views/Home/components/Faqs/Faqs.style.ts | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/views/Home/components/Faqs/Faqs.style.ts b/src/views/Home/components/Faqs/Faqs.style.ts
index 52b6a346..933139c8 100644
--- a/src/views/Home/components/Faqs/Faqs.style.ts
+++ b/src/views/Home/components/Faqs/Faqs.style.ts
@@ -1,7 +1,7 @@
-import styled, { keyframes } from "styled-components";
-import { Color } from "../../../../styles/colors";
-import { motion } from "framer-motion";
-import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
+import styled, {keyframes} from "styled-components";
+import {Color} from "../../../../styles/colors";
+import {motion} from "framer-motion";
+import {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
const revealAnimation = keyframes`
from {
@@ -93,7 +93,9 @@ export const StyledFaqCard = styled.div`
margin-bottom: 4rem;
}
`;
-export const StyledFaqImageContainer = styled.div<{ padding: string }>`
+export const StyledFaqImageContainer = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['padding'].includes(prop),
+})<{ padding: string }>`
position: relative;
@media (min-width: 800px) {
height: auto;
From d1a7c40d6ceb7aa68b28255c7b6536ae5be309f1 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 18:57:19 +0100
Subject: [PATCH 08/29] style: styled components
---
src/components/Navigation/NavigationData.ts | 9 +++------
src/components/Tag/Style.Tag.tsx | 4 +++-
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/components/Navigation/NavigationData.ts b/src/components/Navigation/NavigationData.ts
index 07289c14..68e54976 100644
--- a/src/components/Navigation/NavigationData.ts
+++ b/src/components/Navigation/NavigationData.ts
@@ -1,9 +1,6 @@
import {
ROUTE_ABOUT_US,
- ROUTE_ACCOMMODATION,
- ROUTE_CFP,
ROUTE_CODE_OF_CONDUCT,
- ROUTE_DIVERSITY,
ROUTE_HOME,
ROUTE_SPONSORSHIP,
ROUTE_TRAVEL,
@@ -30,9 +27,9 @@ export const navigationItems2025: NavigationItem[] = [
];
export const subMenuItems2025: NavigationItem[] = [
- { id: "DIVERSITY", link: ROUTE_DIVERSITY },
- { id: "Cfp Committee", link: ROUTE_CFP },
- { id: "Accommodation", link: ROUTE_ACCOMMODATION },
+ //{ 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 },
diff --git a/src/components/Tag/Style.Tag.tsx b/src/components/Tag/Style.Tag.tsx
index 34b374d4..78870b72 100644
--- a/src/components/Tag/Style.Tag.tsx
+++ b/src/components/Tag/Style.Tag.tsx
@@ -1,6 +1,8 @@
import styled from "styled-components";
-export const StyledTagWrapper = styled.div<{ borderColor: string }>`
+export const StyledTagWrapper = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['borderColor'].includes(prop),
+})<{ borderColor: string }>`
border: ${({ borderColor }) => {
return `1px solid ${borderColor}`;
}};
From 32eea306b8d3c4ddf959d0181948a7aa377eb228 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:11:01 +0100
Subject: [PATCH 09/29] feat: fixing navigation
---
src/2024/Navigation/NavigationData.ts | 34 +++++++--------
src/components/Navigation/Navigation.tsx | 53 ++++++++++++++++--------
2 files changed, 53 insertions(+), 34 deletions(-)
diff --git a/src/2024/Navigation/NavigationData.ts b/src/2024/Navigation/NavigationData.ts
index 6ec272be..0163db2d 100644
--- a/src/2024/Navigation/NavigationData.ts
+++ b/src/2024/Navigation/NavigationData.ts
@@ -1,16 +1,16 @@
import {
- ROUTE_ABOUT_US,
- ROUTE_ACCOMMODATION,
- ROUTE_CFP,
- ROUTE_CODE_OF_CONDUCT,
- ROUTE_DIVERSITY,
- ROUTE_HOME,
- ROUTE_JOB_OFFERS,
- ROUTE_SCHEDULE,
- ROUTE_SPEAKERS,
- ROUTE_SPONSORSHIP,
- ROUTE_TALKS,
- ROUTE_TRAVEL,
+ ROUTE_2024_JOB_OFFERS,
+ ROUTE_2024_SCHEDULE,
+ ROUTE_2024_SPEAKERS,
+ ROUTE_2024_TALKS,
+ ROUTE_ABOUT_US,
+ ROUTE_ACCOMMODATION,
+ ROUTE_CFP,
+ ROUTE_CODE_OF_CONDUCT,
+ ROUTE_DIVERSITY,
+ ROUTE_HOME,
+ ROUTE_SPONSORSHIP,
+ ROUTE_TRAVEL,
} from "../../constants/routes";
export interface NavigationItem {
@@ -21,13 +21,13 @@ export interface NavigationItem {
export const navigationItems2024: 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: "Sponsors", link: "/2024#sponsors"},
+ {id: "SCHEDULE", link: ROUTE_2024_SCHEDULE},
+ {id: "Talks", link: ROUTE_2024_TALKS},
//{ id: "Workshops", link: ROUTE_WORKSHOPS },
- {id: "JOB OFFERS", link: ROUTE_JOB_OFFERS},
+ {id: "JOB OFFERS", link: ROUTE_2024_JOB_OFFERS},
//{ id: "Communities", link: ROUTE_COMMUNITIES },
- {id: "Speakers", link: ROUTE_SPEAKERS},
+ {id: "Speakers", link: ROUTE_2024_SPEAKERS},
{id: "About Us", link: ROUTE_ABOUT_US},
{id: "Travel", link: ROUTE_TRAVEL},
//{ id: "KCD - Barcelona", link: ROUTE_KCD },
diff --git a/src/components/Navigation/Navigation.tsx b/src/components/Navigation/Navigation.tsx
index 8c25dda4..20a1df2c 100644
--- a/src/components/Navigation/Navigation.tsx
+++ b/src/components/Navigation/Navigation.tsx
@@ -28,11 +28,14 @@ import {
} from "./Style.Navigation";
import {HorizontalMenu} from "./HorizontalMenu";
import {HamburgerMenu} from "./HamburgerMenu";
+import {
+ navigationItems2024,
+ subMenuItems2024
+} from "../../2024/Navigation/NavigationData";
const Navigation: FC> = () => {
const {width} = useWindowSize();
const [isOpened, setIsOpened] = useState(false);
- const [is2023, setIs2023] = useState(false);
const [navItems, setNavItems] = useState(navigationItems2025);
const [subNavItems, setSubNavItems] = useState(subMenuItems2025);
const {pathname} = useLocation();
@@ -48,20 +51,40 @@ const Navigation: FC> = () => {
};
useEffect(() => {
- if (pathname.startsWith("/2024")) {
- setNavItems(navigationItems2025);
- setSubNavItems(subMenuItems2025);
- }
+ const navMapping = {
+ "/2024": {
+ navItems: navigationItems2024,
+ subNavItems: subMenuItems2024
+ },
+ "/2023": {
+ navItems: navigationItems2023,
+ subNavItems: subMenuItems2023
+ },
+ default: {
+ navItems: navigationItems2025,
+ subNavItems: subMenuItems2025
+ },
+ };
+
+ const {
+ navItems,
+ subNavItems
+ } = Object.entries(navMapping).find(([key]) => pathname.startsWith(key))?.[1] || navMapping.default;
+ setNavItems(navItems);
+ setSubNavItems(subNavItems);
+ }, [pathname]);
+
+
+ const getTicketURL = (): string => {
if (pathname.startsWith("/2023")) {
- setIs2023(true);
- setNavItems(navigationItems2023);
- setSubNavItems(subMenuItems2023);
- } else {
- setNavItems(navigationItems2025);
- setSubNavItems(subMenuItems2025);
+ return "https://tickets.devbcn.com/event/devbcn-2023";
+ }
+ if (pathname.startsWith("/2024")) {
+ return "https://tickets.devbcn.com/event/devbcn-2024";
}
- }, [pathname, navItems, subNavItems]);
+ return "https://tickets.devbcn.com/event/devbcn-2025";
+ }
return (
<>
@@ -124,11 +147,7 @@ const Navigation: FC> = () => {
))}
From 755c9dd37c274fdf2d6c8ca0c28510341667cb84 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:20:15 +0100
Subject: [PATCH 10/29] feat: talks 2024
---
src/2024/Speakers/Speakers2024.tsx | 18 +++++++++---------
src/views/Speakers/components/SpeakersCard.tsx | 10 +++++-----
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx
index 7a2e9625..f403ef81 100644
--- a/src/2024/Speakers/Speakers2024.tsx
+++ b/src/2024/Speakers/Speakers2024.tsx
@@ -7,22 +7,22 @@ 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,
+ 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 "./UseFetchSpeakers";
import * as Sentry from "@sentry/react";
-import {SpeakerCard} from "../../2023/Speakers/components/SpeakersCard";
import {ISpeaker} from "../../views/Speakers/Speaker.types";
+import {SpeakerCard} from "../../views/Speakers/components/SpeakersCard";
const LessThanGreaterThan = (props: { width: number }) => (
<>
diff --git a/src/views/Speakers/components/SpeakersCard.tsx b/src/views/Speakers/components/SpeakersCard.tsx
index ef181edf..3669b698 100644
--- a/src/views/Speakers/components/SpeakersCard.tsx
+++ b/src/views/Speakers/components/SpeakersCard.tsx
@@ -1,4 +1,4 @@
-import { FC, Suspense } from "react";
+import {FC, Suspense} from "react";
import {
StyledImageAnimation,
StyledSpeakerCard,
@@ -7,9 +7,9 @@ import {
StyledSpeakerText,
StyledSpeakerTitle,
} from "./SpeakerCard.Style";
-import { Link } from "react-router-dom";
-import { ROUTE_SPEAKER_DETAIL } from "../../../constants/routes";
-import { ISpeaker } from "../Speaker.types";
+import {Link} from "react-router-dom";
+import {ROUTE_2024_SPEAKER_DETAIL} from "../../../constants/routes";
+import {ISpeaker} from "../Speaker.types";
import Loading from "../../../assets/images/logo.png";
type SpeakerCardProps = {
@@ -22,7 +22,7 @@ export const SpeakerCard: FC> = ({
return (
From 50df00f53323bd41c6a3cebd79181e0f96d07306 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:33:18 +0100
Subject: [PATCH 11/29] feat: talks 2024
---
src/2024/Talks/Talks2024.tsx | 12 +--
src/2024/Talks/components/TalkCard.tsx | 101 +++++++++++++++++++++++++
2 files changed, 107 insertions(+), 6 deletions(-)
create mode 100644 src/2024/Talks/components/TalkCard.tsx
diff --git a/src/2024/Talks/Talks2024.tsx b/src/2024/Talks/Talks2024.tsx
index af1fd79b..a691c508 100644
--- a/src/2024/Talks/Talks2024.tsx
+++ b/src/2024/Talks/Talks2024.tsx
@@ -12,14 +12,14 @@ import {Dropdown, DropdownChangeEvent} from "primereact/dropdown";
import "primereact/resources/primereact.min.css";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "../../styles/theme.css";
-import TrackInformation from "../../views/Talks/components/TrackInformation";
import {
- StyledMarginBottom,
- StyledSpeakersSection,
- StyledTitleContainer,
- StyledTitleIcon,
- StyledWaveContainer
+ StyledMarginBottom,
+ StyledSpeakersSection,
+ StyledTitleContainer,
+ StyledTitleIcon,
+ StyledWaveContainer
} from "../../views/Talks/Talks.style";
+import TrackInformation from "./components/TrackInformation";
interface TrackInfo {
name: string;
diff --git a/src/2024/Talks/components/TalkCard.tsx b/src/2024/Talks/components/TalkCard.tsx
new file mode 100644
index 00000000..e3b3b1f6
--- /dev/null
+++ b/src/2024/Talks/components/TalkCard.tsx
@@ -0,0 +1,101 @@
+import React, {FC} from "react";
+import {Link} from "react-router-dom";
+import {Tag} from "../../../components/Tag/Tag";
+import {
+ ROUTE_2024_SPEAKER_DETAIL,
+ ROUTE_2024_TALK_DETAIL,
+} from "../../../constants/routes";
+
+import {
+ extractSessionCategoryInfo,
+ extractSessionTags,
+} from "../UseFetchTalks";
+
+import {Color} from "../../../styles/colors";
+import {StyledJobsInfo} from "../../../views/JobOffers/components/JobsCard";
+import {
+ CategoryItemEnum,
+ QuestionAnswers,
+ SessionCategory,
+ SessionSpeaker
+} from "../../../views/Talks/Talk.types";
+import {
+ StyledSessionCard,
+ StyledSessionText,
+ StyledTagsWrapper,
+ StyledTalkSpeaker,
+ StyledTalkTitle
+} from "../../../views/Talks/Talks.style";
+import {StyledVoteTalkLink} from "../../../views/MeetingDetail/MeetingDetail";
+
+export interface TalkCardProps {
+ talk: {
+ id: number;
+ title: string;
+ talkImage?: number;
+ speakers: SessionSpeaker[];
+ level?: string;
+ link?: string;
+ tags?: string[];
+ track: string;
+ categories: SessionCategory[];
+ questionAnswers: QuestionAnswers[];
+ };
+ showTrack?: boolean;
+}
+
+export const TalkCard: FC> = ({
+ showTrack = false,
+ talk,
+ }) => {
+ return (
+
+
+
+ {talk.title}
+
+
+ {talk.speakers.map((speaker: SessionSpeaker) => (
+
+
+ {speaker.name}
+
+
+ ))}
+
+
+ {`${extractSessionCategoryInfo(
+ talk.categories,
+ CategoryItemEnum.Format,
+ )} `}
+ {extractSessionCategoryInfo(talk.categories)}{" "}
+
+ {showTrack && (
+
+ Track:
+ {extractSessionCategoryInfo(
+ talk.categories,
+ CategoryItemEnum.Track,
+ )}
+
+ )}
+
+ {extractSessionTags(talk.questionAnswers)?.map((tag) => {
+ return ;
+ })}
+
+
+
+ 🗳️ Vote this talk
+
+
+
+
+ );
+};
From a7e3c9a51b6bbc5a0c81a634025cd37d8720372c Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:33:36 +0100
Subject: [PATCH 12/29] feat: speaker 2024
---
src/2024/SpeakerDetail/SpeakerDetail.tsx | 172 ++++++++++++++++++
.../SpeakerDetail/SpeakerDetailContainer.tsx | 51 ++++++
.../Talks/components/TrackInformation.tsx | 40 ++++
3 files changed, 263 insertions(+)
create mode 100644 src/2024/SpeakerDetail/SpeakerDetail.tsx
create mode 100644 src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
create mode 100644 src/2024/Talks/components/TrackInformation.tsx
diff --git a/src/2024/SpeakerDetail/SpeakerDetail.tsx b/src/2024/SpeakerDetail/SpeakerDetail.tsx
new file mode 100644
index 00000000..c104479e
--- /dev/null
+++ b/src/2024/SpeakerDetail/SpeakerDetail.tsx
@@ -0,0 +1,172 @@
+import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
+
+import {FC, Suspense, useEffect} from "react";
+import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg";
+import LessThan from "../../assets/images/MoreThanIcon.svg";
+import SlashesWhite from "../../assets/images/SlashesWhite.svg";
+import linkedinIcon from "../../assets/images/linkedinIcon.svg";
+import twitterIcon from "../../assets/images/twitterIcon.svg";
+import {useWindowSize} from "react-use";
+
+import {ROUTE_SPEAKERS, ROUTE_TALK_DETAIL} from "../../constants/routes";
+import {Link} from "react-router-dom";
+import {Color} from "../../styles/colors";
+import conferenceData from "../../data/2024.json";
+import {ISpeaker} from "../../views/Speakers/Speaker.types";
+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";
+
+interface ISpeakerDetailProps {
+ speaker: ISpeaker;
+}
+
+const SpeakerDetail: FC> = ({speaker}) => {
+ const {width} = useWindowSize();
+
+ useEffect(() => {
+ document.title = `${speaker.fullName} — ${conferenceData.title} — ${conferenceData.edition}`;
+ }, [speaker.fullName]);
+
+ 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/SpeakerDetailContainer.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
new file mode 100644
index 00000000..4c869f22
--- /dev/null
+++ b/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
@@ -0,0 +1,51 @@
+import {Color} from "../../styles/colors";
+
+import React, {FC} from "react";
+import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import SpeakerDetail from "./SpeakerDetail";
+import {useParams} from "react-router-dom";
+import {StyledContainer, StyledWaveContainer} from "./Speaker.style";
+import conferenceData from "../../data/2024.json";
+import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers";
+import * as Sentry from "@sentry/react";
+
+const SpeakerDetailContainer: FC> = () => {
+ const {id} = useParams<{ id: string }>();
+
+ const {isLoading, error, data} = useFetchSpeakers(id);
+
+ if (error) {
+ Sentry.captureException(error);
+ }
+ React.useEffect(() => {
+ if (data) {
+ document.title = `${data[0]?.fullName} - DevBcn - ${conferenceData.edition}`;
+ }
+ }, [id, data]);
+ return (
+
+
+ {isLoading && Loading }
+ {!isLoading && data && data.length > 0 ? (
+
+ ) : (
+ "not found"
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default SpeakerDetailContainer;
diff --git a/src/2024/Talks/components/TrackInformation.tsx b/src/2024/Talks/components/TrackInformation.tsx
new file mode 100644
index 00000000..2301a6dd
--- /dev/null
+++ b/src/2024/Talks/components/TrackInformation.tsx
@@ -0,0 +1,40 @@
+import React, {FC, useMemo} from "react";
+import {TalkCard} from "./TalkCard";
+import {
+ StyledSessionSection,
+ StyledTrackInfo
+} from "../../../views/Talks/Talks.style";
+import {IGroup} from "../../../views/Talks/Talk.types";
+
+interface TrackInfoProps {
+ track: IGroup;
+}
+
+const useGenerateAnchorName = (trackName: string) => {
+ const visibleTodos = useMemo(() => {
+ return trackName
+ .split(/\s+/)
+ .map((word) => word.replace(/,$/, "").toLowerCase());
+ }, [trackName]);
+ return visibleTodos[0];
+};
+
+const TrackInformation: FC> = ({
+ track,
+ }) => {
+ const anchorName = useGenerateAnchorName(track.groupName);
+
+ return (
+
+ {track.groupName}
+
+ {Array.isArray(track.sessions) &&
+ track.sessions.map((session) => (
+
+ ))}
+
+
+ );
+};
+
+export default React.memo(TrackInformation);
From 24b7a6f0519c6c96772363e6cc3eda6ba41d8d42 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:39:55 +0100
Subject: [PATCH 13/29] feat: speaker 2024
---
.../SpeakerDetail/SpeakerDetailContainer.tsx | 3 +-
src/2024/TalkDetail/MeetingDetail.tsx | 301 ++++++++++++++++++
.../TalkDetail/MeetingDetailContainer.tsx | 69 ++++
3 files changed, 372 insertions(+), 1 deletion(-)
create mode 100644 src/2024/TalkDetail/MeetingDetail.tsx
create mode 100644 src/2024/TalkDetail/MeetingDetailContainer.tsx
diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
index 4c869f22..e999926f 100644
--- a/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
+++ b/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
@@ -4,10 +4,11 @@ import React, {FC} from "react";
import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
import SpeakerDetail from "./SpeakerDetail";
import {useParams} from "react-router-dom";
-import {StyledContainer, StyledWaveContainer} from "./Speaker.style";
import conferenceData from "../../data/2024.json";
import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers";
import * as Sentry from "@sentry/react";
+import {StyledContainer} from "../../views/SpeakerDetail/Speaker.style";
+import {StyledWaveContainer} from "../../views/Talks/Talks.style";
const SpeakerDetailContainer: FC> = () => {
const {id} = useParams<{ id: string }>();
diff --git a/src/2024/TalkDetail/MeetingDetail.tsx b/src/2024/TalkDetail/MeetingDetail.tsx
new file mode 100644
index 00000000..323662db
--- /dev/null
+++ b/src/2024/TalkDetail/MeetingDetail.tsx
@@ -0,0 +1,301 @@
+import {
+ BIG_BREAKPOINT,
+ LARGE_BREAKPOINT,
+ MOBILE_BREAKPOINT,
+} from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
+import React, {FC, Suspense, useEffect} from "react";
+import LessThanIconWhite from "../../assets/images/LessThanIconWhite.svg";
+import LessThanIcon from "../../assets/images/LessThanBlueIcon.svg";
+import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg";
+import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import {useWindowSize} from "react-use";
+
+import {Link} from "react-router-dom";
+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 {IMeeting} from "../../views/MeetingDetail/MeetingDetail.Type";
+import {ISpeaker} from "../../views/Speakers/Speaker.types";
+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";
+
+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();
+
+ useEffect(() => {
+ document.title = `${meeting.title} — ${conferenceData.title} — ${conferenceData.edition}`;
+ }, [meeting.title]);
+
+ 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/MeetingDetailContainer.tsx b/src/2024/TalkDetail/MeetingDetailContainer.tsx
new file mode 100644
index 00000000..9799e37b
--- /dev/null
+++ b/src/2024/TalkDetail/MeetingDetailContainer.tsx
@@ -0,0 +1,69 @@
+import {Color} from "../../styles/colors";
+import React, {FC, useEffect} from "react";
+import NotFoundError from "../../components/NotFoundError/NotFoundError";
+import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
+import styled from "styled-components";
+import {useParams} from "react-router-dom";
+import conferenceData from "../../data/2024.json";
+import {sessionAdapter, useFetchTalksById} from "../Talks/UseFetchTalks";
+import * as Sentry from "@sentry/react";
+import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers";
+import MeetingDetail from "./MeetingDetail";
+import {Session} from "../../views/Talks/Talk.types";
+import {ISpeaker} from "../../views/Speakers/Speaker.types";
+
+const StyledContainer = styled.div`
+ background-color: ${Color.WHITE};
+`;
+const MeetingDetailContainer: FC> = () => {
+ const {id} = useParams<{ id: string }>();
+ const {isLoading, error, data} = useFetchTalksById(id!);
+ const {data: speakerData} = useFetchSpeakers();
+
+ const getTalkSpeakers = (
+ data: Session[] | undefined,
+ ): string[] | undefined => {
+ const speakers = data?.[0]?.speakers;
+ return speakers?.map((speaker) => speaker.id);
+ };
+
+ const talkSpeakers: string[] | undefined = getTalkSpeakers(data);
+ const sessionSpeakers: ISpeaker[] | undefined = speakerData?.filter(
+ (speaker) => talkSpeakers?.includes(speaker.id),
+ );
+
+ const adaptedMeeting = sessionAdapter(data?.at(0));
+
+ useEffect(() => {
+ document.title = `${data?.at(0)?.title} - DevBcn - ${
+ conferenceData.edition
+ }`;
+ }, [data]);
+
+ if (error) {
+ Sentry.captureException(error);
+ }
+
+ return (
+
+
+ {isLoading && Loading }
+ {!isLoading &&
+ sessionSpeakers !== undefined &&
+ sessionSpeakers.length > 0 &&
+ adaptedMeeting !== undefined && (
+
+ )}
+ {!isLoading &&
+ (!sessionSpeakers ||
+ sessionSpeakers.length === 0 ||
+ !adaptedMeeting) && }
+
+
+ );
+};
+
+export default MeetingDetailContainer;
From 7ecf1f2eb6d6e70d16d6a98cae8a92680837b843 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Tue, 29 Oct 2024 19:48:16 +0100
Subject: [PATCH 14/29] feat: speaker 2024
---
...ner.tsx => SpeakerDetailContainer2024.tsx} | 4 +--
src/App.tsx | 12 +++++----
src/views/MeetingDetail/MeetingDetail.tsx | 25 +++++++++++--------
...tainer.tsx => TalkDetailContainer2024.tsx} | 18 ++++++-------
4 files changed, 32 insertions(+), 27 deletions(-)
rename src/2024/SpeakerDetail/{SpeakerDetailContainer.tsx => SpeakerDetailContainer2024.tsx} (93%)
rename src/views/MeetingDetail/{MeetingDetailContainer.tsx => TalkDetailContainer2024.tsx} (78%)
diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
similarity index 93%
rename from src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
rename to src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
index e999926f..4a1f56d6 100644
--- a/src/2024/SpeakerDetail/SpeakerDetailContainer.tsx
+++ b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx
@@ -10,7 +10,7 @@ import * as Sentry from "@sentry/react";
import {StyledContainer} from "../../views/SpeakerDetail/Speaker.style";
import {StyledWaveContainer} from "../../views/Talks/Talks.style";
-const SpeakerDetailContainer: FC> = () => {
+const SpeakerDetailContainer2024: FC> = () => {
const {id} = useParams<{ id: string }>();
const {isLoading, error, data} = useFetchSpeakers(id);
@@ -49,4 +49,4 @@ const SpeakerDetailContainer: FC> = () => {
);
};
-export default SpeakerDetailContainer;
+export default SpeakerDetailContainer2024;
diff --git a/src/App.tsx b/src/App.tsx
index 53c7ad58..f3ab2904 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -50,8 +50,6 @@ import {
import Footer from "./components/Footer/Footer";
import {HomeWrapper} from "./views/Home/HomeWrapper";
-import MeetingDetailContainer
- from "./views/MeetingDetail/MeetingDetailContainer";
import Navigation from "./components/Navigation/Navigation";
import ScrollToTop from "./components/ScrollToTop/ScrollToTop";
import SpeakerDetailContainer
@@ -97,6 +95,10 @@ import JobOffers from "./views/JobOffers/JobOffers";
import {HomeWrapper2024} from "./2024/HomeWrapper2024";
import Speakers2024 from "./2024/Speakers/Speakers2024";
import Talks2024 from "./2024/Talks/Talks2024";
+import TalkDetailContainer2024
+ from "./views/MeetingDetail/TalkDetailContainer2024";
+import SpeakerDetailContainer2024
+ from "./2024/SpeakerDetail/SpeakerDetailContainer2024";
const StyledAppWrapper = styled.div`
position: relative;
@@ -274,7 +276,7 @@ const App: FC> = () => {
path={ROUTE_MEETING_DETAIL_PLAIN}
element={
}>
-
+
}
/>
@@ -411,7 +413,7 @@ const App: FC> = () => {
path={ROUTE_2024_SPEAKER_DETAIL_PLAIN}
element={
}>
-
+
}
/>
@@ -427,7 +429,7 @@ const App: FC> = () => {
path={ROUTE_2024_TALK_DETAIL_PLAIN}
element={
}>
-
+
}
/>
diff --git a/src/views/MeetingDetail/MeetingDetail.tsx b/src/views/MeetingDetail/MeetingDetail.tsx
index 5d5b26da..51160c71 100644
--- a/src/views/MeetingDetail/MeetingDetail.tsx
+++ b/src/views/MeetingDetail/MeetingDetail.tsx
@@ -3,14 +3,14 @@ import {
LARGE_BREAKPOINT,
MOBILE_BREAKPOINT,
} from "../../constants/BreakPoints";
-import { Color } from "../../styles/colors";
-import React, { FC, Suspense, useEffect } from "react";
-import { IMeeting } from "./MeetingDetail.Type";
+import {Color} from "../../styles/colors";
+import React, {FC, Suspense, useEffect} from "react";
+import {IMeeting} from "./MeetingDetail.Type";
import LessThanIconWhite from "../../assets/images/LessThanIconWhite.svg";
import LessThanIcon from "../../assets/images/LessThanBlueIcon.svg";
import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg";
import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
-import { useWindowSize } from "react-use";
+import {useWindowSize} from "react-use";
import {
StyledContainer,
StyledDescription,
@@ -28,13 +28,16 @@ import {
StyledVideoContainer,
StyledVideoTagsContainer,
} from "./Style.MeetingDetail";
-import { Link } from "react-router-dom";
-import { ROUTE_SPEAKER_DETAIL, ROUTE_TALKS } from "../../constants/routes";
+import {Link} from "react-router-dom";
+import {
+ ROUTE_2024_SPEAKER_DETAIL,
+ ROUTE_2024_TALKS
+} from "../../constants/routes";
import conferenceData from "../../data/2024.json";
-import { Tag } from "../../components/Tag/Tag";
-import { ISpeaker } from "../Speakers/Speaker.types";
+import {Tag} from "../../components/Tag/Tag";
+import {ISpeaker} from "../Speakers/Speaker.types";
import styled from "styled-components";
-import { AddToCalendarButton } from "add-to-calendar-button-react";
+import {AddToCalendarButton} from "add-to-calendar-button-react";
const getVideoHeight = (windowWidth: number) => {
let videoHeight;
@@ -253,7 +256,7 @@ const MeetingDetail: FC> = ({
/>
-
+
{speaker.fullName}
@@ -265,7 +268,7 @@ const MeetingDetail: FC> = ({
> = () => {
+const TalkDetailContainer2024: FC
> = () => {
const { id } = useParams<{ id: string }>();
const { isLoading, error, data } = useFetchTalksById(id!);
const { data: speakerData } = useFetchSpeakers();
@@ -66,4 +66,4 @@ const MeetingDetailContainer: FC> = () => {
);
};
-export default MeetingDetailContainer;
+export default TalkDetailContainer2024;
From 9186b2e91ea6f74d2a3c36d18cb2470d1302ba83 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Wed, 30 Oct 2024 08:32:26 +0100
Subject: [PATCH 15/29] feat: index.html
---
public/index.html | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/public/index.html b/public/index.html
index 5b2af503..e762a435 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,7 +3,8 @@
- DevBcn 2024 - Barcelona Developers Conference in Spain — June 13-14
+ DevBcn 2025 - Barcelona Developers Conference in Spain — July
+ 13-14
@@ -16,7 +17,7 @@
@@ -51,7 +52,7 @@
/>
-
+
@@ -116,8 +117,8 @@
"@context": "https://schema.org",
"@type": "Event",
"name": "DevBcn — Barcelona Developers Conference",
- "startDate": "2024-06-13",
- "endDate": "2024-06-14",
+ "startDate": "2025-06-13",
+ "endDate": "2025-06-14",
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
"eventStatus": "https://schema.org/EventScheduled",
"location": {
@@ -141,8 +142,8 @@
"@type": "Event",
"name": "Kubernetes Community Days - Spain",
"description": "One track dedicated to Kubernetes and Cloud computing, curated by the CNCF Foundation",
- "startDate": "2024-06-13",
- "endDate": "2024-06-14",
+ "startDate": "2025-06-13",
+ "endDate": "2025-06-14",
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
"eventStatus": "https://schema.org/EventScheduled",
"location": {
@@ -163,21 +164,21 @@
"offers": [
{
"@type": "Offer",
- "url": "https://tickets.devbcn.com/event/devbcn-2024",
+ "url": "https://tickets.devbcn.com/event/devbcn-2025",
"price": "250.00",
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
- "validFrom": "2024-02-01",
- "validThrough": "2024-01-31"
+ "validFrom": "2025-02-01",
+ "validThrough": "2025-01-31"
},
{
"@type": "Offer",
- "url": "https://tickets.devbcn.com/event/devbcn-2024",
+ "url": "https://tickets.devbcn.com/event/devbcn-2025",
"price": "300.00",
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
- "validFrom": "2024-02-01",
- "validThrough": "2024-02-29"
+ "validFrom": "2025-02-01",
+ "validThrough": "2025-02-29"
}
],
"organizer": {
From 2c87501b542211d1519523e431ef232f5d7c4389 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Fri, 1 Nov 2024 18:13:33 +0100
Subject: [PATCH 16/29] chore: pull request changes
---
public/index.html | 28 +-
src/App.tsx | 2 +-
src/data/2025.json | 6 +-
src/views/Home/HomeWrapper.tsx | 5 +-
src/views/Home/components/Home/Home.tsx | 4 +-
src/views/sponsorship/Sponsorship.tsx | 578 ++++++++++++------------
6 files changed, 307 insertions(+), 316 deletions(-)
diff --git a/public/index.html b/public/index.html
index e762a435..ccbe2e38 100644
--- a/public/index.html
+++ b/public/index.html
@@ -52,7 +52,7 @@
/>
-
+
@@ -117,8 +117,8 @@
"@context": "https://schema.org",
"@type": "Event",
"name": "DevBcn — Barcelona Developers Conference",
- "startDate": "2025-06-13",
- "endDate": "2025-06-14",
+ "startDate": "2025-07-09",
+ "endDate": "2025-07-10",
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
"eventStatus": "https://schema.org/EventScheduled",
"location": {
@@ -137,28 +137,6 @@
"https://www.devbcn.com/images/devbcn24.png",
"https://www.devbcn.com/images/2023/FaqsImage0.png"
],
- "subEvent": {
- "@context": "https://schema.org",
- "@type": "Event",
- "name": "Kubernetes Community Days - Spain",
- "description": "One track dedicated to Kubernetes and Cloud computing, curated by the CNCF Foundation",
- "startDate": "2025-06-13",
- "endDate": "2025-06-14",
- "eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
- "eventStatus": "https://schema.org/EventScheduled",
- "location": {
- "@type": "Place",
- "name": "La Farga",
- "address": {
- "@type": "PostalAddress",
- "streetAddress": "carrer Barcelona, 2",
- "addressLocality": "L'Hospitalet de Llobregat",
- "postalCode": "08901",
- "addressRegion": "Ba",
- "addressCountry": "SP"
- }
- }
- },
"performers": [],
"description": "Multidisciplinary conference made for Developers and by Developers, to learn and share on the different modern software technologies used across the companies",
"offers": [
diff --git a/src/App.tsx b/src/App.tsx
index f3ab2904..7ad3cf95 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -49,7 +49,7 @@ import {
} from "./constants/routes";
import Footer from "./components/Footer/Footer";
-import {HomeWrapper} from "./views/Home/HomeWrapper";
+import HomeWrapper from "./views/Home/HomeWrapper";
import Navigation from "./components/Navigation/Navigation";
import ScrollToTop from "./components/ScrollToTop/ScrollToTop";
import SpeakerDetailContainer
diff --git a/src/data/2025.json b/src/data/2025.json
index 0e83bdb0..d9578b4d 100644
--- a/src/data/2025.json
+++ b/src/data/2025.json
@@ -11,7 +11,7 @@
"diversity": false,
"edition": "2025",
"email": "info@devbcn.com",
- "endDay": "2025-06-14T14:00:00",
+ "endDay": "2025-07-10T14:00:00",
"facebook": "https://facebook.com/devbcn",
"flickr": "https://flickr.com/devbcn",
"github": "https://github.com/devbcn",
@@ -30,13 +30,13 @@
"startDate": "2023-12-01T09:00:00",
"endDate": "2025-05-13T09:00:00"
},
- "startDay": "2025-06-13T09:00:00",
+ "startDay": "2025-07-09T09:00:00",
"tickets": {
"startDay": "2025-01-01T00:00:00",
"endDay": "2025-06-01T00:00:00"
},
"title": "DevBcn - Barcelona Developers Conference ",
- "trackNumber": 5,
+ "trackNumber": 4,
"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"
diff --git a/src/views/Home/HomeWrapper.tsx b/src/views/Home/HomeWrapper.tsx
index dfcec282..8e5ebacc 100644
--- a/src/views/Home/HomeWrapper.tsx
+++ b/src/views/Home/HomeWrapper.tsx
@@ -32,6 +32,7 @@ export interface Edition {
github: string;
linkedin: string;
showInfoButtons: boolean;
+ showCountdown: boolean;
tickets: Cfp;
cfp: Cfp;
carrousel: Carrousel;
@@ -49,7 +50,7 @@ export interface Cfp {
endDay: Date;
}
-export const HomeWrapper: FC> = () => {
+const HomeWrapper: FC> = () => {
const { hash } = useLocation();
const [edition, setEdition] = useState();
@@ -71,3 +72,5 @@ export const HomeWrapper: FC> = () => {
);
};
+
+export default HomeWrapper;
\ No newline at end of file
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index cab03f31..44f5ba38 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -93,10 +93,10 @@ const Home: FC> = () => {
{edition?.tracks}
- {data.showCountdown && (
+ {data.showCountdown &&
- )}
+ }
{edition?.actionButtons && }
{edition?.showInfoButtons && }
diff --git a/src/views/sponsorship/Sponsorship.tsx b/src/views/sponsorship/Sponsorship.tsx
index 1dbca318..4ab8647a 100644
--- a/src/views/sponsorship/Sponsorship.tsx
+++ b/src/views/sponsorship/Sponsorship.tsx
@@ -1,325 +1,335 @@
-import { FC, useEffect } from "react";
+import {FC, useEffect} from "react";
import TitleSection from "../../components/SectionTitle/TitleSection";
import SectionWrapper from "../../components/SectionWrapper/SectionWrapper";
-import { BIG_BREAKPOINT, MOBILE_BREAKPOINT } from "../../constants/BreakPoints";
-import { Color } from "../../styles/colors";
+import {BIG_BREAKPOINT, MOBILE_BREAKPOINT} from "../../constants/BreakPoints";
+import {Color} from "../../styles/colors";
import LessThanBlue from "../../assets/images/MoreThanBlueWhiteIcon.svg";
-import LessThanTransparentIcon from "../../assets/images/LessThanTransparentIcon.svg";
+import LessThanTransparentIcon
+ from "../../assets/images/LessThanTransparentIcon.svg";
import MoreThanBlue from "../../assets/images/LessThanBlueWhiteIcon.svg";
-import MoreThanTransparentIcon from "../../assets/images/MoreThanTransparentIcon.svg";
+import MoreThanTransparentIcon
+ from "../../assets/images/MoreThanTransparentIcon.svg";
import styled from "styled-components";
-import { useWindowSize } from "react-use";
+import {useWindowSize} from "react-use";
import {
- StyledLessIcon,
- StyledMoreIcon,
- StyledSpeakersSection,
+ StyledLessIcon,
+ StyledMoreIcon,
+ StyledSpeakersSection,
} from "../Speakers/Speakers.style";
-import { StyledMarginBottom } from "../Talks/Talks.style";
-import data from "../../data/2024.json";
-import { format } from "date-fns";
+import {StyledMarginBottom} from "../Talks/Talks.style";
+import data from "../../data/2025.json";
+import {format} from "date-fns";
import Flicking from "@egjs/react-flicking";
-import { AutoPlay } from "@egjs/flicking-plugins";
+import {AutoPlay} from "@egjs/flicking-plugins";
import "@egjs/react-flicking/dist/flicking.css";
const StyledWaveContainer = styled.div`
- background: ${Color.DARK_BLUE};
- overflow-y: hidden;
- height: 3rem;
- width: 100%;
+ background: ${Color.DARK_BLUE};
+ overflow-y: hidden;
+ height: 3rem;
+ width: 100%;
`;
export const StyledSectionsSeparator = styled.div`
- background: ${Color.WHITE};
- height: 3rem;
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 5rem;
- }
+ background: ${Color.WHITE};
+ height: 3rem;
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 5rem;
+ }
`;
const StyledSponsorshipText = styled.div`
- text-align: start;
- color: ${Color.BLACK_BLUE};
- max-width: 95vw;
+ text-align: start;
+ color: ${Color.BLACK_BLUE};
+ max-width: 95vw;
- p {
- margin: 5px 20px;
- text-align: justify;
- }
+ p {
+ margin: 5px 20px;
+ text-align: justify;
+ }
- ul {
- margin: 5px 20px;
+ ul {
+ margin: 5px 20px;
- li {
- margin: 5px 0;
+ li {
+ margin: 5px 0;
+ }
}
- }
- h4 {
- margin: 20px 0;
- }
+ h4 {
+ margin: 20px 0;
+ }
- a:visited {
- color: ${Color.DARK_BLUE};
- font-weight: normal;
- }
+ a:visited {
+ color: ${Color.DARK_BLUE};
+ font-weight: normal;
+ }
- @media only screen and (max-width: ${BIG_BREAKPOINT}px) {
- iframe {
- width: 90vw;
+ @media only screen and (max-width: ${BIG_BREAKPOINT}px) {
+ iframe {
+ width: 90vw;
+ }
}
- }
`;
const StyleLessIcon = styled.img`
- position: absolute;
- left: -1rem;
- top: 12rem;
- height: 5rem;
- @media (min-width: ${BIG_BREAKPOINT}px) {
- height: 10rem;
- }
+ position: absolute;
+ left: -1rem;
+ top: 12rem;
+ height: 5rem;
+ @media (min-width: ${BIG_BREAKPOINT}px) {
+ height: 10rem;
+ }
`;
const StyleMoreIcon = styled.img`
- position: absolute;
- right: -1rem;
- top: 2rem;
- height: 5rem;
- @media (min-width: 800px) {
- height: 10rem;
- }
+ position: absolute;
+ right: -1rem;
+ top: 2rem;
+ height: 5rem;
+ @media (min-width: 800px) {
+ height: 10rem;
+ }
`;
const Sponsorship: FC> = () => {
- const { width } = useWindowSize();
- const plugins = [
- new AutoPlay({ duration: 2000, direction: "NEXT", stopOnHover: false }),
- ];
-
- useEffect(() => {
- document.title = `Sponsorship — ${data.title} — ${data.edition}`;
- });
+ const {width} = useWindowSize();
+ const plugins = [
+ new AutoPlay({duration: 2000, direction: "NEXT", stopOnHover: false}),
+ ];
- return (
- <>
-
-
-
- {width > MOBILE_BREAKPOINT && (
- <>
-
-
- >
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ useEffect(() => {
+ document.title = `Sponsorship — ${data.title} — ${data.edition}`;
+ });
-
-
-
-
-
+ return (
+ <>
+
+
+
+ {width > MOBILE_BREAKPOINT && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- {width > MOBILE_BREAKPOINT && (
- <>
-
-
- >
- )}
-
-
- Mark Your Calendars!
-
- DevBcn 2024 is set for June 13th — 14th at the
- iconic La Farga, Hospitalet de Llobregat. This year, we're diving
- deep into the realms of Java, JVM, Cloud, DevOps, Frontend
- technologies, Leadership strategies, and groundbreaking
- advancements in Big Data and AI. Furthermore, we’ve partnered with
- the{" "}
-
-
- CNCF Foundation
-
- {" "}
- to include the{" "}
-
-
+
- Kubernetes Community days (KCD)
-
- {" "}
- in the next edition, a full track dedicated and curated for
- Kubernetes and the Cloud.
-
- A New Era of Tech Innovation
-
- Dive into tracks covering Java, JVM, Cloud, DevOps, Frontend
- technologies, Leadership, Big Data, AI, and more. DevBcn 2024 is
- the perfect stage to connect with tech professionals, thought
- leaders, and innovators.
-
- Tailored Sponsorship Opportunities
-
- While we're keeping the details of our sponsorship packages
- exclusive, we promise they're more engaging and impactful than
- ever. Curious? Access our{" "}
-
-
- detailed brochure
- {" "}
- {" "}
- at and discover the myriad of ways you can shine at DevBcn 2024.
-
- Why Partner with DevBcn 2024?
-
-
-
- Expand Your Reach: Engage with a diverse,
- tech-savvy audience. Our latest edition held more than 1000
- attendees.
-
-
- Elevate Your Brand: Showcase your products
- and innovations in a dynamic environment.
-
-
- Network with the Best: Connect with industry
- leaders and potential collaborators. Nearly 30 companies have
- pledged their trust in DevBcn.
-
-
- Showcase Thought Leadership: Share your
- expertise and insights with a global audience.
-
-
-
- Join us on this exciting journey
-
- To discuss how we can align our sponsorship opportunities with
- your brand's vision, contact us at{" "}
- sponsors@devbcn.com
-
-
- Let’s make DevBcn 2024 an unforgettable experience together! Stay
- updated and spread the excitement using{" "}
-
- #devbcn24 .
-
-
-
- We eagerly await the opportunity to collaborate with you once more
- for an extraordinary event!
-
- Take a look at our 2023’s edition summary
- VIDEO
- Explore DevBcn 2023 Talks Online!
-
-
- 🎥 DevBcn 2023 - recorded sessions
-
-
-
-
-
-
- >
- );
+
+
+
+
+
+ {width > MOBILE_BREAKPOINT && (
+ <>
+
+
+ >
+ )}
+
+
+ Mark Your Calendars!
+
+ DevBcn {data?.edition} is set for July 9th —
+ 10th at the
+ iconic La Farga, Hospitalet de Llobregat. This year,
+ we're diving
+ deep into the realms of Java, JVM, Cloud, DevOps,
+ Frontend
+ technologies, Leadership strategies, and
+ groundbreaking
+ advancements in Big Data and AI.
+
+ A New Era of Tech Innovation
+
+ Dive into tracks covering Java, JVM, Cloud, DevOps,
+ Frontend
+ technologies, Leadership, Big Data, AI, and more.
+ DevBcn {data?.edition} is
+ the perfect stage to connect with tech
+ professionals, thought
+ leaders, and innovators.
+
+ Tailored Sponsorship Opportunities
+
+ While we're keeping the details of our sponsorship
+ packages
+ exclusive, we promise they're more engaging and
+ impactful than
+ ever. Curious? Access our{" "}
+
+
+ detailed brochure
+ {" "}
+ {" "}
+ at and discover the myriad of ways you can shine at
+ DevBcn {data?.edition}.
+
+ Why Partner with DevBcn?
+
+
+
+ Expand Your Reach: Engage
+ with a diverse,
+ tech-savvy audience. Our latest edition held
+ more than 1000
+ attendees.
+
+
+ Elevate Your
+ Brand: Showcase your products
+ and innovations in a dynamic environment.
+
+
+ Network with the
+ Best: Connect with industry
+ leaders and potential collaborators. Nearly
+ 30 companies have
+ pledged their trust in DevBcn.
+
+
+ Showcase Thought
+ Leadership: Share your
+ expertise and insights with a global
+ audience.
+
+
+
+ Join us on this exciting journey
+
+ To discuss how we can align our sponsorship
+ opportunities with
+ your brand's vision, contact us at{" "}
+ sponsors@devbcn.com
+
+
+ Let’s make DevBcn {data?.edition} an unforgettable
+ experience together! Stay
+ updated and spread the excitement using{" "}
+
+ #devbcn25.
+
+
+
+ We eagerly await the opportunity to collaborate with
+ you once more
+ for an extraordinary event!
+
+ Take a look at our latest edition summary
+ VIDEO
+ Explore DevBcn Talks Online!
+
+
+
+
+
+ >
+ );
};
export default Sponsorship;
From 4ee801b7e129cab8b9df8b232088b392e513bb75 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Fri, 1 Nov 2024 18:19:03 +0100
Subject: [PATCH 17/29] chore: buttons on the front page
---
src/data/2025.json | 4 ++--
src/views/Home/components/ActionButtons/ActionButtons.tsx | 6 ++----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/data/2025.json b/src/data/2025.json
index d9578b4d..114b4736 100644
--- a/src/data/2025.json
+++ b/src/data/2025.json
@@ -27,8 +27,8 @@
"showCountdown": true,
"showInfoButtons": false,
"sponsors": {
- "startDate": "2023-12-01T09:00:00",
- "endDate": "2025-05-13T09:00:00"
+ "startDate": "2024-01-01T09:00:00",
+ "endDate": "2025-06-09T09:00:00"
},
"startDay": "2025-07-09T09:00:00",
"tickets": {
diff --git a/src/views/Home/components/ActionButtons/ActionButtons.tsx b/src/views/Home/components/ActionButtons/ActionButtons.tsx
index 7048d2f3..28e718e5 100644
--- a/src/views/Home/components/ActionButtons/ActionButtons.tsx
+++ b/src/views/Home/components/ActionButtons/ActionButtons.tsx
@@ -47,20 +47,18 @@ const ActionButtons: FC> = () => {
link="https://tickets.devbcn.com/event/devbcn-2025"
disabled={!isBetween(ticketStartDay, ticketEndDay)}
/>
- {isBetween(CFPStartDay, CFPEndDay) && (
- )}
- {isBetween(sponsorshipStartDay, sponsorshipEndDay) && (
- )}
);
};
From 8fd34b3a25605f4a8c0609fd142dc4fe14f64a32 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 14:41:45 +0100
Subject: [PATCH 18/29] chore: countdown
---
src/2024/HomeWrapper2024.tsx | 3 +-
src/types/types.ts | 32 +++++++++++++++++
src/views/Home/HomeWrapper.tsx | 34 +------------------
src/views/Home/components/Home/Home.tsx | 6 ++--
.../Home/components/TimeCountdown.tsx | 6 ++--
5 files changed, 41 insertions(+), 40 deletions(-)
create mode 100644 src/types/types.ts
diff --git a/src/2024/HomeWrapper2024.tsx b/src/2024/HomeWrapper2024.tsx
index 6ae49e29..4cb89898 100644
--- a/src/2024/HomeWrapper2024.tsx
+++ b/src/2024/HomeWrapper2024.tsx
@@ -10,7 +10,8 @@ import Faqs from "../views/Home/components/Faqs/Faqs";
import Home from "./Home/Home";
import SpeakersCarousel from "./SpeakersCarousel/SpeakersCarousel";
import Sponsors from "./Sponsors/Sponsors";
-import {Edition} from "../views/Home/HomeWrapper";
+
+import {Edition} from "../types/types";
const StyledContainer = styled.div`
padding-bottom: 10rem;
diff --git a/src/types/types.ts b/src/types/types.ts
new file mode 100644
index 00000000..20fd32fa
--- /dev/null
+++ b/src/types/types.ts
@@ -0,0 +1,32 @@
+export interface Edition {
+ actionButtons: boolean;
+ carrousel: Carrousel;
+ cfp: Cfp;
+ diversity: boolean;
+ edition: string;
+ email: string;
+ endDay: Date;
+ facebook: string;
+ github: string;
+ jobOffers: Carrousel;
+ linkedin: string;
+ schedule: Carrousel;
+ showCountdown: boolean;
+ showInfoButtons: boolean;
+ startDay: Date;
+ tickets: Cfp;
+ title: string;
+ trackNumber: string;
+ tracks: string;
+ twitter: string;
+ youtube: string;
+}
+
+export interface Carrousel {
+ enabled: boolean;
+}
+
+export interface Cfp {
+ startDay: Date;
+ endDay: Date;
+}
\ No newline at end of file
diff --git a/src/views/Home/HomeWrapper.tsx b/src/views/Home/HomeWrapper.tsx
index 8e5ebacc..9b5cc875 100644
--- a/src/views/Home/HomeWrapper.tsx
+++ b/src/views/Home/HomeWrapper.tsx
@@ -8,6 +8,7 @@ import styled from "styled-components";
import {useLocation} from "react-router-dom";
import {useEventEdition} from "./UseEventEdition";
+import {Edition} from "../../types/types";
const StyledContainer = styled.div`
padding-bottom: 10rem;
@@ -17,39 +18,6 @@ const StyledContainer = styled.div`
}
`;
-export interface Edition {
- actionButtons: boolean;
- edition: string;
- startDay: Date;
- endDay: Date;
- trackNumber: string;
- title: string;
- tracks: string;
- email: string;
- twitter: string;
- youtube: string;
- facebook: string;
- github: string;
- linkedin: string;
- showInfoButtons: boolean;
- showCountdown: boolean;
- tickets: Cfp;
- cfp: Cfp;
- carrousel: Carrousel;
- schedule: Carrousel;
- jobOffers: Carrousel;
- diversity: boolean;
-}
-
-export interface Carrousel {
- enabled: boolean;
-}
-
-export interface Cfp {
- startDay: Date;
- endDay: Date;
-}
-
const HomeWrapper: FC> = () => {
const { hash } = useLocation();
const [edition, setEdition] = useState();
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index 44f5ba38..620bc425 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -25,9 +25,9 @@ import {Color} from "../../../../styles/colors";
import InfoButtons from "../InfoButtons/InfoButtons";
import {formatDateRange} from "./DateUtil";
import {useEventEdition} from "../../UseEventEdition";
-import {Edition} from "../../HomeWrapper";
import {Link} from "react-router-dom";
import data from "../../../../data/2025.json";
+import {Edition} from "../../../../types/types";
const Home: FC> = () => {
const {width} = useWindowSize();
@@ -94,8 +94,8 @@ const Home: FC> = () => {
{data.showCountdown &&
-
+ <>>
}
{edition?.actionButtons && }
{edition?.showInfoButtons && }
diff --git a/src/views/Home/components/Home/components/TimeCountdown.tsx b/src/views/Home/components/Home/components/TimeCountdown.tsx
index 8d230ebc..fd75911a 100644
--- a/src/views/Home/components/Home/components/TimeCountdown.tsx
+++ b/src/views/Home/components/Home/components/TimeCountdown.tsx
@@ -1,11 +1,11 @@
-import { FC } from 'react';
+import {FC} from 'react';
import styled from 'styled-components';
-import { Color, } from '../../../../../styles/colors';
+import {Color,} from '../../../../../styles/colors';
const TimeCountDownContainer = styled.div`
display: flex;
align-items: center;
- padding-top: 1.5rem;
+ padding-top: 1.2rem;
`;
const StyledTimerContainer = styled.div`
From 659f33c2c8d143bb965cba6390af180f09c0f57c Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 15:13:42 +0100
Subject: [PATCH 19/29] test: update tests to edition 2025
---
src/App.test.tsx | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 3d3a1152..eedccb8a 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,5 +1,5 @@
-import { render, screen } from "@testing-library/react";
-import { BrowserRouter, Route, Routes } from "react-router-dom";
+import {render, screen} from "@testing-library/react";
+import {BrowserRouter, Route, Routes} from "react-router-dom";
import App from "./App";
import React from "react";
import userEvent from "@testing-library/user-event";
@@ -15,14 +15,14 @@ describe("navigation pages", () => {
{ wrapper: BrowserRouter },
);
expect(
- await screen.findByText(/The Barcelona Developers Conference 2024/i),
+ await screen.findByText(/The Barcelona Developers Conference 2025/i),
).toBeInTheDocument();
expect(
- await screen.findByText(/June 13th - 14th, 2024/i),
+ await screen.findByText(/July 9th - 10th, 2025/i),
).toBeInTheDocument();
expect(
- await screen.findByText(/5 tracks with the following topics:/i),
+ await screen.findByText(/4 tracks with the following topics:/i),
).toBeInTheDocument();
});
@@ -36,7 +36,7 @@ describe("navigation pages", () => {
{ wrapper: BrowserRouter },
);
expect(
- await screen.findByText(/The Barcelona Developers Conference 2024/i),
+ await screen.findByText(/The Barcelona Developers Conference 2025/i),
).toBeInTheDocument();
const user = userEvent.setup();
await user.click(screen.getByText("Travel"));
@@ -45,7 +45,7 @@ describe("navigation pages", () => {
).toBeVisible();
});
- test("it render the SPEAKERS page", async () => {
+ test.skip("it render the SPEAKERS page", async () => {
render(
Loading...}>
@@ -61,7 +61,7 @@ describe("navigation pages", () => {
).toBeInTheDocument();
});
- test("it render the TALKS page", async () => {
+ test.skip("it render the TALKS page", async () => {
render(
Loading...}>
@@ -91,7 +91,7 @@ describe("navigation pages", () => {
).toBeInTheDocument();
});
- test("it render the CFP page", async () => {
+ test.skip("it render the CFP page", async () => {
render(
Loading...}>
From 4c173f7f066fa1c4fdf664ccc965c07341974e05 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 15:19:00 +0100
Subject: [PATCH 20/29] chore: spelling
---
wordlist.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/wordlist.txt b/wordlist.txt
index cdd85b4b..acea5581 100644
--- a/wordlist.txt
+++ b/wordlist.txt
@@ -7,6 +7,7 @@ DevBcn
ESLint
Facebook
Farga
+KCD
LkR
PLo
StyledSelectTrack
From 38c93ce8d060180455393d82c9e9432269779a0c Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 15:28:15 +0100
Subject: [PATCH 21/29] chore: spelling
---
src/App.test.tsx | 248 ++++++++++++++++++++++++-----------------------
1 file changed, 126 insertions(+), 122 deletions(-)
diff --git a/src/App.test.tsx b/src/App.test.tsx
index eedccb8a..bca3dde9 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -5,134 +5,138 @@ import React from "react";
import userEvent from "@testing-library/user-event";
describe("navigation pages", () => {
- test("it render the HOME page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- expect(
- await screen.findByText(/The Barcelona Developers Conference 2025/i),
- ).toBeInTheDocument();
+ test("it render the HOME page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ expect(
+ await screen.findByText(/The Barcelona Developers Conference 2025/i),
+ ).toBeInTheDocument();
- expect(
- await screen.findByText(/July 9th - 10th, 2025/i),
- ).toBeInTheDocument();
- expect(
- await screen.findByText(/4 tracks with the following topics:/i),
- ).toBeInTheDocument();
- });
+ expect(
+ await screen.findByText(/July 9th - 10th, 2025/i),
+ ).toBeInTheDocument();
+ expect(
+ await screen.findByText(/4 tracks with the following topics:/i),
+ ).toBeInTheDocument();
+ });
- test("it render the TRAVEL page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- expect(
- await screen.findByText(/The Barcelona Developers Conference 2025/i),
- ).toBeInTheDocument();
- const user = userEvent.setup();
- await user.click(screen.getByText("Travel"));
- expect(
- await screen.findByText("La Farga Centre d'Activitats"),
- ).toBeVisible();
- });
+ test("it render the TRAVEL page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ expect(
+ await screen.findByText(/The Barcelona Developers Conference 2025/i),
+ ).toBeInTheDocument();
+ const user = userEvent.setup();
+ await user.click(screen.getByText("Travel"));
+ expect(
+ await screen.findByText("La Farga Centre d'Activitats"),
+ ).toBeVisible();
+ });
- test.skip("it render the SPEAKERS page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("Speakers"));
- expect(
- await screen.findByText(/Speakers coming from all corners of the world/i),
- ).toBeInTheDocument();
- });
+ //Reason: not enabled yet
+ test.skip("it render the SPEAKERS page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("Speakers"));
+ expect(
+ await screen.findByText(/Speakers coming from all corners of the world/i),
+ ).toBeInTheDocument();
+ });
- test.skip("it render the TALKS page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("Talks"));
- expect(await screen.findByText("/ Talks")).toBeInTheDocument();
- });
+ //Reason: not enabled yet
+ test.skip("it render the TALKS page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("Talks"));
+ expect(await screen.findByText("/ Talks")).toBeInTheDocument();
+ });
- test.skip("it render the JOB OFFERS page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("JOB OFFERS"));
- expect(
- await screen.findByText("Have a look at some opportunities"),
- ).toBeInTheDocument();
- });
+ //Reason: not enabled yet
+ test.skip("it render the JOB OFFERS page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("JOB OFFERS"));
+ expect(
+ await screen.findByText("Have a look at some opportunities"),
+ ).toBeInTheDocument();
+ });
- test.skip("it render the CFP page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("Cfp Committee"));
- expect(await screen.findByText("Java & JVM")).toBeInTheDocument();
- });
+ //Reason: not enabled yet
+ test.skip("it render the CFP page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("Cfp Committee"));
+ expect(await screen.findByText("Java & JVM")).toBeInTheDocument();
+ });
- test("it renders the ABOUT US page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("About Us"));
- expect(await screen.findByText(/Jonathan Vila/i)).toBeInTheDocument();
- expect(await screen.findByText(/Nacho Cougil/i)).toBeInTheDocument();
- });
+ test("it renders the ABOUT US page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("About Us"));
+ expect(await screen.findByText(/Jonathan Vila/i)).toBeInTheDocument();
+ expect(await screen.findByText(/Nacho Cougil/i)).toBeInTheDocument();
+ });
- test("it renders the CODE OF CONDUCT page", async () => {
- render(
- Loading...}>
-
- } />
-
- ,
- { wrapper: BrowserRouter },
- );
- const user = userEvent.setup();
- await user.click(screen.getByText("Code of Conduct"));
- expect(
- await screen.findByText(/The DevBcn is the yearly event/i),
- ).toBeInTheDocument();
- });
+ test("it renders the CODE OF CONDUCT page", async () => {
+ render(
+ Loading...}>
+
+ }/>
+
+ ,
+ {wrapper: BrowserRouter},
+ );
+ const user = userEvent.setup();
+ await user.click(screen.getByText("Code of Conduct"));
+ expect(
+ await screen.findByText(/The DevBcn is the yearly event/i),
+ ).toBeInTheDocument();
+ });
});
From 8b422c8c06b235e377f1c0486ea956c76e46fc4b Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 15:55:02 +0100
Subject: [PATCH 22/29] chore: sonar changes for countdown component
---
src/views/Home/components/Home/Home.tsx | 6 +-
.../Home/components/CountDownCompleted.tsx | 33 +++++
.../Home/components/TimeCountdown.tsx | 130 +++++-------------
.../Home/components/countdown.style.ts | 31 +++++
4 files changed, 105 insertions(+), 95 deletions(-)
create mode 100644 src/views/Home/components/Home/components/CountDownCompleted.tsx
create mode 100644 src/views/Home/components/Home/components/countdown.style.ts
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index 620bc425..712c3483 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -28,6 +28,7 @@ import {useEventEdition} from "../../UseEventEdition";
import {Link} from "react-router-dom";
import data from "../../../../data/2025.json";
import {Edition} from "../../../../types/types";
+import CountDownCompleted from "./components/CountDownCompleted";
const Home: FC> = () => {
const {width} = useWindowSize();
@@ -94,8 +95,9 @@ const Home: FC> = () => {
{data.showCountdown &&
- <>>
+
}
{edition?.actionButtons && }
{edition?.showInfoButtons && }
diff --git a/src/views/Home/components/Home/components/CountDownCompleted.tsx b/src/views/Home/components/Home/components/CountDownCompleted.tsx
new file mode 100644
index 00000000..c354bef5
--- /dev/null
+++ b/src/views/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/views/Home/components/Home/components/TimeCountdown.tsx b/src/views/Home/components/Home/components/TimeCountdown.tsx
index fd75911a..ab8e4140 100644
--- a/src/views/Home/components/Home/components/TimeCountdown.tsx
+++ b/src/views/Home/components/Home/components/TimeCountdown.tsx
@@ -1,103 +1,47 @@
-import {FC} from 'react';
-import styled from 'styled-components';
-import {Color,} from '../../../../../styles/colors';
-
-const TimeCountDownContainer = styled.div`
- display: flex;
- align-items: center;
- padding-top: 1.2rem;
-`;
-
-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;
-`;
-
-const StyleLine = styled.div`
- width: 0.75rem;
- background: ${Color.DARK_BLUE};
- height: 1.5px;
-`;
-
-const StyledTimerLetters = styled.p`
- font-size: 0.75rem;
-`;
+import React, {FC} from 'react';
+import {
+ StyledTimerContainer,
+ StyledTimerLetters,
+ StyleLine,
+ TimeCountDownContainer
+} from "./countdown.style";
type TimeCountDownProps = {
- days: number;
- hours: number;
- minutes: number;
- seconds: number;
- completed: boolean;
+ days: number;
+ hours: number;
+ minutes: number;
+ seconds: number;
};
const TimeCountDown: FC> = ({
- days,
- hours,
- minutes,
- seconds,
- completed,
-}) => {
- if (completed) {
- return (
-
-
- 0
- DAYS
-
-
-
- 0
- HOURS
-
-
-
- 0
- MINUTES
-
-
-
- 0
- SECONDS
-
-
- );
- } else {
+ days,
+ hours,
+ minutes,
+ seconds,
+ }) => {
return (
-
-
- {days}
- DAYS
-
-
-
- {hours}
- HOURS
-
-
-
- {minutes}
- MINUTES
-
-
-
- {seconds}
- SECONDS
-
-
+
+
+ {days}
+ DAYS
+
+
+
+ {hours}
+ HOURS
+
+
+
+ {minutes}
+ MINUTES
+
+
+
+ {seconds}
+ SECONDS
+
+
);
- }
};
export default TimeCountDown;
diff --git a/src/views/Home/components/Home/components/countdown.style.ts b/src/views/Home/components/Home/components/countdown.style.ts
new file mode 100644
index 00000000..76043c07
--- /dev/null
+++ b/src/views/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;
+`;
\ No newline at end of file
From 338a91699da011021ef8ef0a907967bd76feb02a Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Sun, 3 Nov 2024 18:03:15 +0100
Subject: [PATCH 23/29] feat: display countdown
---
src/views/Home/components/Home/Home.tsx | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/src/views/Home/components/Home/Home.tsx b/src/views/Home/components/Home/Home.tsx
index 712c3483..bb9ca02a 100644
--- a/src/views/Home/components/Home/Home.tsx
+++ b/src/views/Home/components/Home/Home.tsx
@@ -1,5 +1,5 @@
import Countdown from "react-countdown";
-import React, {FC, useState} from "react";
+import React, {FC} from "react";
import LessThanIcon from "../../../../assets/images/MoreThanBlueWhiteIcon.svg";
import SectionWrapper
from "../../../../components/SectionWrapper/SectionWrapper";
@@ -24,17 +24,12 @@ import ActionButtons from "../ActionButtons/ActionButtons";
import {Color} from "../../../../styles/colors";
import InfoButtons from "../InfoButtons/InfoButtons";
import {formatDateRange} from "./DateUtil";
-import {useEventEdition} from "../../UseEventEdition";
import {Link} from "react-router-dom";
-import data from "../../../../data/2025.json";
-import {Edition} from "../../../../types/types";
+import edition from "../../../../data/2025.json";
import CountDownCompleted from "./components/CountDownCompleted";
const Home: FC> = () => {
const {width} = useWindowSize();
- const [edition, setEdition] = useState();
-
- useEventEdition(setEdition);
return (
@@ -94,11 +89,9 @@ const Home: FC> = () => {
{edition?.tracks}
- {data.showCountdown &&
-
- }
+
{edition?.actionButtons && }
{edition?.showInfoButtons && }
From aaf40c4641f13fb4b0e92573eabb0f0d96bba029 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Mon, 4 Nov 2024 12:08:51 +0100
Subject: [PATCH 24/29] feat: 2025 brochure
---
src/views/sponsorship/Sponsorship.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/sponsorship/Sponsorship.tsx b/src/views/sponsorship/Sponsorship.tsx
index 4ab8647a..7190e4bb 100644
--- a/src/views/sponsorship/Sponsorship.tsx
+++ b/src/views/sponsorship/Sponsorship.tsx
@@ -235,7 +235,7 @@ const Sponsorship: FC> = () => {
ever. Curious? Access our{" "}
From ba8e0ef4778750f77385f639999c5c2f2dd0d571 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Mon, 4 Nov 2024 22:31:39 +0100
Subject: [PATCH 25/29] feat: add subtext for action buttons
---
src/components/UI/Button.tsx | 4 +++-
src/views/Home/components/ActionButtons/ActionButtons.tsx | 2 ++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx
index 7818e330..1003f7d0 100644
--- a/src/components/UI/Button.tsx
+++ b/src/components/UI/Button.tsx
@@ -5,6 +5,7 @@ import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
interface ButtonProps {
text: string;
+ subtext?: string;
link: string;
onClick: () => void;
disabled?: boolean;
@@ -65,6 +66,7 @@ const StyledActionButton = styled.div`
const Button: FC> = ({
text,
+ subtext = "SOON",
link,
onClick,
disabled,
@@ -91,7 +93,7 @@ const Button: FC> = ({
{children}
{` ${text}`}
- {disabled && SOON }
+ {disabled && {subtext} }
>
);
diff --git a/src/views/Home/components/ActionButtons/ActionButtons.tsx b/src/views/Home/components/ActionButtons/ActionButtons.tsx
index 28e718e5..59ef09d3 100644
--- a/src/views/Home/components/ActionButtons/ActionButtons.tsx
+++ b/src/views/Home/components/ActionButtons/ActionButtons.tsx
@@ -44,12 +44,14 @@ const ActionButtons: FC> = () => {
From 22a7e287dfb4a9e3133a6e4eae41b2474ebb18d2 Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Mon, 4 Nov 2024 22:58:32 +0100
Subject: [PATCH 26/29] feat: add subtext for action buttons
---
src/components/UI/Button.tsx | 154 ++++++++++++++++++-----------------
1 file changed, 78 insertions(+), 76 deletions(-)
diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx
index 1003f7d0..7ac56717 100644
--- a/src/components/UI/Button.tsx
+++ b/src/components/UI/Button.tsx
@@ -4,98 +4,100 @@ import {Color} from "../../styles/colors";
import {BIG_BREAKPOINT} from "../../constants/BreakPoints";
interface ButtonProps {
- text: string;
+ text: string;
subtext?: string;
- link: string;
- onClick: () => void;
- disabled?: boolean;
- target?: string;
- children?: ReactNode;
+ link: string;
+ onClick: () => void;
+ disabled?: boolean;
+ target?: string;
+ children?: ReactNode;
}
const doNothingHandler = (
- event: React.MouseEvent,
+ event: React.MouseEvent,
) => {
- event.preventDefault();
+ event.preventDefault();
};
const StyledActionButton = styled.div`
- background-color: ${Color.LIGHT_BLUE};
- text-align: center;
- font-size: 1.3em;
- min-width: 200px;
- margin: 20px 5px;
- border-radius: 5px;
- box-shadow: 1px 1px 1px #000;
- padding: 10px 15px;
- transform: perspective(1px) translateZ(0);
- transition-duration: 0.5s;
- vertical-align: middle;
+ background-color: ${Color.LIGHT_BLUE};
+ text-align: center;
+ font-size: 1.3em;
+ min-width: 200px;
+ margin: 20px 5px;
+ border-radius: 5px;
+ box-shadow: 1px 1px 1px #000;
+ padding: 10px 15px;
+ transform: perspective(1px) translateZ(0);
+ transition-duration: 0.5s;
+ vertical-align: middle;
+ cursor: pointer;
- &:hover,
- &:focus,
- &:active {
- background-color: ${Color.DARK_BLUE};
- transition-timing-function: cubic-bezier(0.47, 2.02, 0.31, -0.36);
- }
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: ${Color.DARK_BLUE};
+ transition-timing-function: cubic-bezier(0.47, 2.02, 0.31, -0.36);
+ }
- & span {
- font-size: 0.9rem;
- }
+ & span {
+ font-size: 0.9rem;
+ }
- & a {
- text-decoration: none;
- text-shadow: 1px 1px 1px #000;
- color: white;
- display: block;
- text-transform: uppercase;
- vertical-align: middle;
- }
+ & a {
+ text-decoration: none;
+ text-shadow: 1px 1px 1px #000;
+ color: white;
+ display: block;
+ text-transform: uppercase;
+ vertical-align: middle;
+ }
- & small {
- font-weight: bold;
- font-size: 0.7em;
- color: ${Color.MAGENTA};
- text-shadow: none;
- }
+ & small {
+ font-weight: bold;
+ font-size: 0.7em;
+ color: ${Color.MAGENTA};
+ text-shadow: none;
+ }
- @media (max-width: ${BIG_BREAKPOINT}px) {
- margin: 5px 1px;
- }
+ @media (max-width: ${BIG_BREAKPOINT}px) {
+ margin: 5px 1px;
+ }
`;
const Button: FC> = ({
- text,
+ text,
subtext = "SOON",
- link,
- onClick,
- disabled,
- target = "_blank",
- children,
-}) => {
- return (
-
- <>
-
- {children}
- {` ${text}`}
-
- {disabled && {subtext} }
- >
-
- );
+ link,
+ onClick,
+ disabled,
+ target = "_blank",
+ children,
+ }) => {
+ return (
+
+ <>
+
+ {children}
+ {` ${text}`}
+
+ {disabled && {subtext} }
+ >
+
+ );
};
export default Button;
From b324110a4809fd4474c29ecf276e22961b0a315e Mon Sep 17 00:00:00 2001
From: Anyul Rivas
Date: Mon, 4 Nov 2024 23:38:33 +0100
Subject: [PATCH 27/29] feat: sponsorship value image
---
public/images/devBcn-sponsorship.png | Bin 0 -> 41677 bytes
src/views/Home/components/Home/Home.tsx | 3 +++
2 files changed, 3 insertions(+)
create mode 100644 public/images/devBcn-sponsorship.png
diff --git a/public/images/devBcn-sponsorship.png b/public/images/devBcn-sponsorship.png
new file mode 100644
index 0000000000000000000000000000000000000000..43f84a2713488ae84f623e34daaf6896c97f8d8d
GIT binary patch
literal 41677
zcmdR#^+*?31R3QLQ1+nbO}gH3JA&&(%s$N_jrH#
z-hbf^ioS$l#v^8_-cg$BO1M~*SRfDx_qDRTHVA}55Bxn7j1GLxNO_h6{=jrm
ze&-GXVH5rPjReZbA_svA0$v+mRIdg31>+pAjOfuQ|xT
zGE=J684x(L5>sG?hE_L@N+Khp4&m^SJw_!&4&6}ZPFR{x$zG;w@-~)5Mha^9+-agT
zn>n97o1L9r^7|a3!Ycp&JZRC>EcK=T_YXyv^jP!^|9iyaXn_d+_wccbM2`I5V~|mf
z`2TsbJS{~5=zq^;I2(kZLH_sTM5S;R*#Dj!Db6R4^uJe@-$;c1|N7G7{ncH)p|#W~
z(lV3fLv6`PnvE)fY3IsihQ&Ukp2T*Bkkoo4dV(h-g~{^r{>u2sNab}|19i+UDi||}
z8AJ*y2PK1)(Og2xb*u-Ys%rz(6}Hk~TAK;mgT
zHE?l5-Tp}z0aI*Fs9C5iTV3h+UJXcCW3s&BgEa@}0(pfS8anyb*Mud>POl$bnp>7f
zeu#&!Dn|-~KDu1jF24CS13pEM+ac)Uo7SM8%8OXkywblrY!#k&As{Zz#`3(neU@<^
z@URHd=wk8L4ReXNJ#Lmrp&((U$?`zjegSz}$sX#lWmzVQU%oA$JTdLpNp|*kt(={!
zSlC!=M%okg{RIVi1z7oy6H
zeSB5)Bi$8;gKD$HU?x~)R>ME{}e(jF6Yl6
z_ia+S?lUBn%Be6R-j}bz#^q+_i6-mtFu4t1>E+|y^=WBQCfYpGL4m#QaH~%hq{l5D
z3A{T#RX7k^(tD>yzQ{c~H%BOrV1%+3`Y&?db3M88?8(abYzB~{{>_BHw@JK%atKZ7
z^~aBE*&@NZZfySOr$FVk%U{;<;GFQ{pWSH}(0z6FKlINH;+LG(oF&i#+XVn`h#`rG
zv0rMc<2rbn>GF8ITjQOfVbr%X728J9K`U*8cWh>k#qwKl@w?M$BaM$s?sqz2irxw3
zx_(q`PYn1NJ#I)epBG3UAA7|RhVNK^!4Hs?U*KGbOc2%wJrI$w>3hJlWGUY_wJ+v9!yJ(#tTj%dIxSl?pNv6si
zlbz1wRQY%P*LKa$$B%MJf6O#m8Yy_GL(9v{R}Vhor>jN#&^@>u4RhX;8L?W)dHG*Y
zU6_aX2@{j16ny)J#if45Qd5a6{
zeDEoEK(hlPy@LH`n8JB@?Lw(Eeo0B--GdYn#>KAHTNACY;9x@ASB8-At~oQwB$VgU{jn!(Xpo?XR!C(DoX}
z|M$>gTLbUo{>!MSH+>_4aqC^H*v*M47%j%p>e-K{e`kWoc`M0MJERzA#zuU{#Fib6
zFZ`m-}_Ss8>EL@=jt~GPL(kVmqj$4)#T336@BmpWDoy?uwze6S~9YNKfoQsc<*`Q
zcX};w&NMe~WO<1nxEg`mmhibE+WpjD>slPBs*Jxjjh~eT~RU^dG3|hraUcu
z=w@*1tsfrkcxS~x`nl_k-MZ5YChLNNa?0Z3lu`QzdU*&_tO}Q`+B0e~`g}OO5u^tO
z4X^7ph~zL#?%Opo`3dWr
z>Fb-Ofx_%McKKap{Q9+KtA-n!q;e$_jI6BKD#d79YIG5Ga=sou8kIdHF6lQO=sL`M<@1q4}9-1J;CF?WxPR_EX`ys?IXdFOiC^k
z)a&Qm0gES7JNCK!8HONA9WJz={B_?#Z{AXuO86)8v77|q=|BfN$P$O~u#B_U%d^{^
z2h|$W;bUYn2O3=Eo|3AdZFM%08tEjKR!;>_Q*DB=!p)`O!C>&0Bxj4=GgAV;mlVf8
zBuJ=!_labLo4MynV{u;U=Gf<9L4w
zKCAz($8hFC{IujrSX@k!piF)uE+Ap$j)GB4>??tvqs@a8q{Dy&MNQT|5$BGj0`>CH
zIWQ|h&GRRQfBxG*j)V$Jif>CwDh@;A8hFY0@{oxItwntYGMFPcoGsVU0TZmrC!0x$zVrmXbwmN>9PVm~ATVEd&!~O@!cJVA3)}NEj#6>0B
zve2|3b1+bvT(|tGb8>`9k
z+lD*6b8l|C$l4{0^BXi*6unqqO^RD-qZ>M6Zkz=|#_Jgs@%Q1+nU@!%&;Ac@k{oEAms=}zHnn0kqWj~rSCn#fqN#Ml422sX9|P}~VIQEZsS3=9U%$(3=m%VnVd?g
z6$vq^HUvu(YtV{NSaeRBD-GI|*Gw&~Q25D`xL)_L*U_~X0}&aYoW
zW)7B^J%9fyC>P@tX^S)%(%>m7u1ebyf9SkH?T@(J^FJ(kpN$lWtV|SIuFDJ_o|U@o
z$wW$=@1(idB+Ffy-8ND5m!&Nt?CpVGsjy9bekS_2qVFbXy@h?5woXm26_mOmdW|yV
zh3~VU_v@d5`w=98upC|Izp~OZcwjM<%%JHoA)h$jt(B3;(yw{O?tJnBW!Q6a5c9zx
z9*2am?Hr1zAJFVPouT|OTi0C`hJbl5@PLLfa@8waq34@(@(2IL({3^Dp=|`U)WhXw
zXAUahQ~m&juLE@9*3`7OXX{|y`}Tw|oTvBB8&k)SC2x8*h}kbH)cggGoFdNA
zDsv(a3wI4F;#_mk+VhC@?>;Um0iCJ7xP&&DTH)W_>2FTNI`LGCrmnn-aJqmyj254t
zJJU5pIk|#Zx|4>~QZzAmGLqyoHT;V39r&M48hN{SXm{6#u~F-EAysgak@v9?9aRG1
z;!`NxZ>T~{1I1upzF=J(5QJ{$QUHp`h+3Q?6HH%MN5$!exX5_bYFe8cPxbD8V!7Gl
zwp9jV?L?;;3v>rSS{{hG;Ppma_3&%p?p~~4F*1UOvjORQ#8}y;+$-X>q2iQ@=$t2g
zbu5)NU-awk$$7jKl(P_ZN{`Q$twGqG3!UKk5w+rnAuN8oiRQzd7v
zD_vZXi{|a?tfO0Z#3^YH{TZkGeiZ-idHb{mb2utUErZlj@$%^8EW%lxW^HTF%+9V1
zRy>YO7*39ghb;mIv;3^;bl@Z_uMlu?sr~7Wi0iuf`L7m^4z#YXUw<&`HhRnJZ_BNt=P(K#
zB+C|S{3+$9VZKY&S>UY9?ElS{iFCvduTbei=H0uO$SCcjbscnim5Yfk!iF6w<+@~4
zZHo-ou@2K-KdNzhSH_Fwt*keD`|n1MQbHlh#oB!)>hgxZf_GO(r6(@m+<&vOdV_$w
z?hT||F9nh+*6H!bd42#+134YZM64d9Pq1*nWprEc^i(L^+{#_{ZeD+!X{SPHP{1{|
z5ag#9`=&1;sj9%Pbx?$)o;x2O&eg$7
zV%)Z?iImkwf{nT46s!ek{VK(_I&V1^jA>3by*n+BpXymyIKrynFYiJdy>Qh)oKo|
zZrFxELt5wkA5XJ_o+OXrj{WcN8G76(bgIS$-QF5W^t4Is-Xos*`?c+Yl5Dsbg*wT9
z{+nZz9tIq>Rv)W-Sc>8*?+G!v(c+@fXo5cFq)1Mi^l3
zKwY~BlUA2o%Z$7q?X(%nm9n5
z_8&MWQOt_?qEJC@ZdiGoBT9)QPu1H+$pw||$S+@R0+%X(V)qi%4j#Uxi~$Eam@69_
z+`%#!-EuV>_r%Rbnzl^?q+E_uNqY4;0`VvQ8|lA|d6zIuVZd6ftkMz$rxIE%V-7c8
z_$rh7_Bx05x%bGO4CyaB@SmibA4$jbYN!iQW0U#aD1SkDJw+(Zz(o5o2Nh>;Qc|-_
zXXW>-@WvL23{w&am7JC`_9z>r_AWxIZjFlM>^ugVq>?wW>$>a4&;@BfpVAQ*IpXaz
zd87D25s!_nA|cBSg%XY@A$srkalw7Au#W2gh9N$mMCRh|Z!20fh&R$!I^qNS(%tp7
z3=FV@yp>|fNs+^3(e-WoaIkU1jk$oOhT=FQj%0Qg>uKE1qVp#9U5_3Fj3d=yve1EJ
zvA9+EG}G#z$S4|wBJ=MVp9gJp*jxCb3Be8i4#CFp;cl=e)l>IbO#q;hf^mH9O`DA85pentZCe6WOISda+6cD!>RY<}LUaf5xwgzh#k90|#BN7PP
zB!|Zev5M`;!Lh*yTJae|k+#-tJgO<=B?Aw05{4x(bw9aBDO9WKJ&`tebbBnnM*M)S
zji*HFll3g=h1+v>Pu}P}Twmg!tk1=t{Q1^$IP!DEKeg5vQ@6rvQj!8_Axl#a^_`E+
z8>fOMBatnskwF?iPOIXE;$nJ~>J`i#s&sg8WVEzcUr}-ryHWUmUKpzjp{r)LSw#e@
z=QT7e)at86+7?di=HER+n8bO(DEFS|`^RBLMQT7@O-|JKw@Qky1w4Y6^?Z;Em1D?X{u$5Bd6$28Z?4vdkt
z!7jB_jE5&qR5IioSwOK
z$Gla%^WiYDIp3_gCqsdd(Y*;VAwb|AKhMQ7qZ}JVYl};^($Uf_9{=DvQ~OkU&A*zz
z)EKX?>u_@8PM1vE4nZI{yO=VQ`IrHjt(?Kp!rY&>XIkEd9C=~E<9v_df
z+1ar}v|}KE*VF`DjvW+Z)K@3t4_%FXRgLd@Uf!Drf~<
z{eWBV9a6!J3YD(lzsRH#I!^>D7$_(=UhM8-4@SHA&RFAjsJtwM#+`<&fQZLeo~}e%
z!{(UXZq@{HohT@nyv5|T%
z!;3i_?*Pxwh&>c|FRE!tnmPtkU$55_#}CjTppK;+iGbSds+T|BpJQApQjy
z(=_E$c#L=Bo-AR1EMVOh|4r`YX%59q?Zw=tbqbNBOStGWI>WOlEA3486aZSWzpW3xJK5L-UR
zQ}J%pK`sty<^PisxQOBo6r)x!P=7PY#t9S~l_<%J(c@eMFBaV@_wVKO_t1GZEV@6m
z9eNepNLXRoNKq>h?+tXK+k-7oZ+v)1g*Oz2NmA
zl7qv_gfEFk$kMUCe{twJnA2X9oBC0)$9w~>7)c6c&z=ex=x;8YhFHEH*rA0XxGKZ!
zNXR?zIeRP^goW7c->($6Bto5v392nZ6)j_9bO}VSN|`38*7o;R^g<6g4-EV~W$#&9
zS{kmy7N%}9GtO*{W1e~`Dh=$x-bDhrhznSJ6FU%+uFVSst^h@%xp~I&F2?dSSA=L~
z!^~*=5|9~spF4>#_dNPcHIx7~voqaB^z^Wg>gFCppVBZ`+SW8ihzco*3zAd^%so}W
zY|OdVw3&?q8VJz;a?1&ErKDov-Q3v*evrj*A}cG~#lvFf;p<_49C0Nh
zIn(jX@#E2#ox`yT5R2wEHt;WB+#Z%IRud>JM5dR!cz9^Qi>fFD&sH97MIf;I#E(1TQVubmV_+t?RWDc
zkqMDJ0;JfS@(OZVGQ2k15d3p8A#e+mePXjeS|TzH~E+O
z*Ly4sF(Y5h6XD6|8Nn3pGK+
zAS?3+Cn~>`ub2qwS}9>Bar03wB9sfJmr1IcK9@D#6muxWltYCU5n}mKRC03Cr~RT&
zQh_GDmSGK3s|eAv1otvq8#)zy-2$W0r|@VR4w{?i^I+1)$|JM0za%C_C#o3lLGBwz
zCMCr>5f8&5HqYjO!I*(VkZPck8k}|%AXSJJZ>iric%O>->1-(Q)d!T9G%i<_2>c;cX
zkMS-(P~v50^g{E^^q1?_MtqoJNpJFO%!p#KE(eNBbKU&QGwEhu2EkJmyLX#Ja&-Gq
zQeL!jCIP%2z7kglVjrd2D1Tl;Q7L*TS;eh!goXC(VielivWEEh^
zbp)F5YFRh=OcOt#c0?Pl1ul@D^jzeCy3kpL(du!g6kv`SmmM8uf?$kv42j%bS5rCZ
zz(B0_7E3X5*MM^ac(7%H95r>TWZd>}A;^G0bhSgf5DzO(L3^S7x?+!=QwFr^lT(6Y
z-j&cw7T90to8vr+$%n8QQUk8*k7o!va=u<~xhpqU_vd_OmPQ)1CL~VNU5gNq
zDo{$hBInB^%Apgo3T>A;
zj8ZCGxN7#5c5>91`n
z*PRnIWpeZsT}lt|S-0h5D+8}cg*cal7$)rNc|KYfPq;{NcO-V`hmdZ^jNs{Wz)9ND
zZG_~9v{Ioen`?kc77kg8Ya4XB=I7Bsnwpx?%?@R?b$S+q3A8Tmo~ULn^>b(UGV<9m
za-C1_SJpn_gMeBtYhscJnF;Tjnj<6(M>01ISnHl^Kt(F*-tol6L2!RvL}4E^96rNKX4B8!qr7
zwi~(UEf#h$^_+e7?n660Oj$n0AStX|R|#`iDm^LtLi9<<
zD?ynpJ~w9-&ST&I$Z|a2=t|{K3X*m{Op*Nxq>g_^q2Kt@?jUq5b=?;!=_o7b
zx2C2;edZQJpY*HN0by5Uvtc&3L9jDjLWDf$o8R7!9Qfc#Eaf;CQ;K~$YIgxdFv>+Z
zcbA8uSBL&5clXPt#l$vchLb#h(v*|J##`T4N9H;384PLF$F7W9MmDfwG5$d+Q;PR_$O+ej!pOpZ=HA)e@=Racv}^&19Rz4-MG^wkPW_qhmlXnJ;Ns$0qh*D;^vcLQio^VpHgXs+HKwqLw!&Q(*ViH}EaX(G9*5MR
zWkDwp2(B2@Q=rX5h#nMVvIE&b0SWs_jwIWZHjc8PLJGBmc^wa0hRvjLwD8IWL=D(B
zSA0yUB{T5bbI$&*km^ok{o=u4W3Q?D46
zfilsCp%pXHJ*|D=BPLEECrt_dBFl8ZkBZ|&%@`43CcQjjaPljB*r%8TqwhiU4^_@K
zurpTI_tCwDm_Y(n)MaHt-61`I$PYDNIoh)NR?8o2emq#L8pXnDCy}q4$={v>bY`Xd
zE6EcN@8FbTwDyko-H@$Rp4Z^=3eQr~b~+ADVi(VEa#wJF9sOk$r{%KGMr!0zL^-S&IH=EN~?dGq!|;7W({{}jr-hJ>G%YJFZ^DoRcTb`l(hJe-_Wx;*^YpMlBgf4h_WeLGPJ%urDB$Uxi+Z4w0^J=HnN-!R2O1q7`OImHy!E``XA<#W0!$R9l1
z5pHA|L@JF3J1-!q@-Z8{HV;ZPhwe;?fL<1pjM{qU5%0{2fB>H_1LW92NG7FIwqq@F
zMnyJT!P`E7y?~A`Q@Jlcp^x2$6%Z6*S{f`9*ftmfUjS5C{-?dupahj-SqoW;XWLT_b^O5=Kzw(D#eV0lKA-uEv1sju!tkpnyiptVj9CVF{4
zSeaJd=!Jd87@;h%nRK;JDuxrhug$<5U@Uwa$8qac|NQWrxkf>7O1NZn!|R6422Ft|
zzeFYD`e}Z6w|si}t?6X(Oz1xT^VZ=uc-Z=FDP85
zQ=C4xW#7J)*AcSz0`gIoRl!=J@krZPWh71NSW@1=iCwoMPV!rL%mSG>Le4H97Sh4V
z_wDRtV;yC6$BbQG^;=(%+;;x-Iba3DW74Vt2fn_GDFY9vlf*b@rm$<;{q^Bl>h)rU>lnrXQ*9r_}DKOl&~o%f<9
zF#f*dxcy)AWZ+&~wem!8x$e)<&F`N(Tq6efHShzYsOX|gcPef+x0f~b%0j|o^D5gZ
zBd%$>^q1>zj(;=rTArS%<8iOC0&bJ_}UuZ$2(6tuLEH$AarlP8j_Et9mCL`t?Hm4C3Q1y%?j;RDkK0F
zjw^f4VlJ8B#%t26{dyH&*k7Cy$*}{-M&l}6%Y;2PkO2%N@pC;S{$H4`9R$QOu2&BN
zSrTLs3kwW`R*mg!hDd<+9n;r$_*5K*9D?Dg>KS$bqAgfHV&(Vj5UwxR1U|yy8AYzBaeJ0q(jpY?XveQye2B
zRX}_I+oxa{
zgEDk}^Trc`fv?m(rxNvL+-+lnv|E-dLUye75Q!6>N)O92{q8&P>&cO
z1`@2^rN`}rugOt=8$JT5oBVd1-X2Jw48vGe8D;VmR9sp<6VDZ++uG#@_8Z
zL8`{cp|3VsZV7+ylZZ|v{7^^B(F*|@*4jC@kR>9hessUoJU3ShlutaRg57=_n8cfo
z{nN^cWsPXusowuAIl=_7zI&n>iig>sgR*@xOO~0KlWys9P0~}l=i#fV>9M;LKWW;5
z=PjfpJ`m=mbtom3QM>T2TrWy2Ig_QI{srEZ;K`YbqKcv5|DB}lu1@shs$h$hS2V8p
zV9_}Xeew}yyt3!}loJ8m=|Gpu*Br$8TvA8H=%x>Zk>ZDyyr*T|JIhT(#8?!fo+tcZ
zI@uSmER)}q$p^19TbwRt&={Od)!VLb2752(gm+0hQ;YZ#q*cFw3e|uO;doeK)PjO4
zpZr?7Mn))sUeCf_6x9V~j2inJ*F>&;WPyl$VI#jk%Wza_>7HT%B=
zh5*zG*17_f{|KlLpqyM$LI6PNeD*x(U(g76lB?Km0@CV<$ZP2hQXsfpPEdpOmy!b8
z$rp}yl@lIhbJFEB)9lmPEfBMfn~IBNz_O8`0=&-DWoenMg>yJCJ)_#OA9K1XMf%Uu
zQt9eveqo_z(OlfZgS`R!OO}ci*In&bk_O-Kj{oEac|YARl#0Ymzr?IISPNfc0_yZK
zfMh0W#2rzALDPiVbw>k%U0-FLri%&+up9z!s1E0EEr6lDuonYq+f`QhxGbJ57=R&a
zwh0#a!QARJ@Ut_O%Ewi1Y6)5t2lklx5&id73R(fI>bY&v%ye{-19x7H&g*+CzMPB$
zBrq1(D=i=hP*G)f;blSse)h_-M2V)J7TVDHKi9keI{oCF_eIg~)^rSZs+V}wamdB6
z%nFpbdPkrnI)X6~G3Rn`m1?&0b%w_3jp+O1DKyu`ddRQQsHSm&Sxlr~@w5G}`3dHxOAlE$JxRUlTh7y7G=AhCHy&JHsw-R{
zKH-!%5>KU~?)6zboL@_k^ICvR{67>qZq&K22Z(&yMeb9Z{)5!Hl>PXK{3!mC5)N>M
zFJ#zKq}f_XKK4-?8L?-@ME7o0VVvw@)bl*v)7%@{zV@*_dY90
z)}1SV(}#g@Le@};IuPLU$b*-?sTGwGUMFWBGRiiA%MKJGKR{n-W$qP@P$ZlU61;4;
zu`w`=ivHW|ngSw+R28em!HAMfqE>HQd3!@Vr!8WJu4U{c6Rv+**w3%1K=w<_$A1oF
zfSOeV9SxbHd}XMB4hL?@7l8c)4E(=jZ?W}8jwx;G>pd$P2Pdbdv2pmN)MLoQ17U2S
z1xVEULT+09X=mx>$@lYp4`1WfFD!d(Xc-+KWDtPkgKE=ijR$^LKDc@7D;de1Nq1?N
zNo%W+s^_)@n=H=De3n2V442W&69PxFwvr&xr@a0*yb?1wNeAkHi^^sLgN}gLFVc^IN9iho
zQxTK8C2x>g9tXJtn^@R=i>;hZ;6Jqz0iNMduDQ8PNnkEDH^}=?I9&qI6%GJ@sXyaA
znr-qj9$|S5;Tc!R?U1TC?D1aecX;-pA>*==T`(1h
zwg8!j4D3VzMyb$bvGT@QUlxbACu9UpWM?U
zi&v!=VcK-`4+4RR7GcQJ?PCHf35_O|(RwYy$LF4ynh5{oDOzpvhM@mzzn#O|d<8K5
z7DToSJM;N>JVF0S;!tRxRwJ!AqixTpp^)GVzFd`;{YE-(tx{gUAOux0TU)ya%MPMw
zUr&cZfZ0lxs5_d?NWVbD(Q^zp9iZGy?
zU5IylrthheWacf^F}fWc_W%qkH?(0nKDYgDIch!P-;~4s={SiojbCLNN!jFf{)uK?
z3I72BTcRmu?2lD&TCGtYW{H3oQ%|NCme!Q4N;z})3gvc0>E?C_I98*3651dLIpYiulf|C0dk&eHyy^73m<*FJZNc2qVnA3YthBpz32!=?;E-JyV#VYHncoJ#JGjhDdrZ1S@8|_4i>Oh>_O=E2kt}
z{iXP`?{>-5n3S}^2my@9{(CcEaVX$A#cdW4daV-dR4wJ5i8i9}u&ZEgsGOD-#SDl$
z6n8f&E7+~*11UpMjF6<^4a3viBAzv{=NcOofmABsc^wSQ>Vw02#2N6)T%DqdHWj)V
zIBZb5NSZ>OFkx;06)Rv;bwtzSRt=Kf5*PT?1C;f=$z7L~QIUhTuU_O`K$Mh1kUD?A
z?A>X9jo?=8Z-4IGZFak)baPXeDV*#Dk`hB-5p_C=X!kKo-Xc@|T~!SnfBVUiBwmnV
z@jt|s5=I50z*!xn0OV>V%z`q?u=p|y
zCTk1}!wL&IO|tosG&IN!*Tp(kbmdsXWT6p-g)|#{uW`NEutM@&{rO5`uVPU%nlVUg5&f`(e!Bw&^XM+DVKgzT-EBK&{&tk
zJXZE90QWDA!h-752)$si+5?CD#GZ;_?#Jm;olulxOMy+FkM8*Jgsw<$=su88M0lH0
zpyQK?wF^5|{?f?O#=5-p0MZEjFk`0-jabZS_*_D2YGgufR)=xEf$jd*(GdAxetN>P
zqSl6@*xH4)Erw7)rD4-sjgMAa{xG?-#{k`&=dObT&)a^2M$#1+=g3lwS58)=;YiB?
zXNGNvwDzc;b|IIGBFZThbd@mTy(01U$1CQUj(sKnx{b!J!tDZ)9yj1%iM{T-am?X&
z!<*69j*E+nFZo)M&%|F6hTF2URfO>}6xZ#8yKKyq?*&0FPiq4IWU5>GMKv-CNcjaJ
z;GIvdk0y5h=5yv`6Nl$^nu3J%F{q^|VmJXx)yc^(7%d;A
z_3rW8{a=?JH|A2q$;!!%&`pja?cIAXT9YM0b4Hv*9{HNFozNen2ceL{7&w429gD@U
zO}^9U8S9?9_-w5ppcmLlH196=UBi-1C+Fu?qcf|z=S37
z;++5JXp3n2J+3Wti9T(sna9H@A@@}*BNSpmjSWV+5S0LCED4_cbbMvRK+gcWMANro
z2alcE35<#VVx=SK>1-P>OE`tN_>Tq0$&(lZA8xsY1J1sEqG)AjJL56`9#w1G2ZxbxwGD5LtGqGsF2-le7O
zQ8k=*BRA{pw1W<_0p_v9XqA}RKyUpoF(Xmuv;6sGPNb-|QEq}A#!}mkT
zNOpXLF;hh^+P)t7Qrk;m2X%mrZ>nuD-XIxoDzvFdSk2+5G(nXrI53n
z#9xWNH!IZ($wl%YUAR%le4XYX(2!z_bDx8YbDhJhs$MRoOW_g?NPPfL;)cBJ8=1md
zY(>x0zxEO+_-~0D@M+@3E*=j=zA_YS=3hk_FB)I`+cjUL;Y9N9S1}^HOAzF{(dNny
zb+E-KP}<0QWaBn3I-DV@SE!-%LV{Pyuhp+`K|+*m!I$QdE$Vy3sIgR?7F=bg<5|U@
zT<2`tc|3_*8?Qq<(@+AQ*(oXEv?49mJJY>nUV>PqMEob4L(6r32nvAp0FL%ddyb`M
zM2-%ON#((>VL`nCc}xsy
z&)6k?mbF#_hdf%<4UUfEJD;Gn&haoz064>1Td!ecwB!aH9W)JyF({SdsQbHnd;gKh
zzjOHf7tY`qT#(clfb{LmP05g~RMotfu8~AhB!7=B4L&1U(?|t4W6+@8#vPJ$1i@pQ
z*Urp{5*}!F_Vypoo9)uCXqh3*8c#uhyPeCfD+|=KcdZ9LP5vfZZw2xL!wYy^lNAl`U53M%FYK^OH$~Y
z#cGD`Ek)D&s~aC<(6CK=dTtS`+kgBB=yLH?i>ceLa{r;y;W&Fi9ruVeU+oVS07H>_
z-spYpKRZ4#oWp}-#*-DIOrHFqXj@xlTBwZo%Ys`B-wOr~1qB5X09Mn~(_?+X{;HCB
zgOBwe027hxy}naNzH{-sWZ7Rz{zh#mRE;Uf#AYVOqO3vM+?7J%&2=NGTnw2s%!mdM
zA^zt$njKI(VmTa&zee?_K*!dyvwo~hX^Wt_5bc#9GWoGkQMc>c5%Af4(9exo6?GG-
zQ51!aa}lhHjrQS9ATrH6Z(*@_g$3Fu`pga&`>PnTETjR=w1LN>XhT%~Qu`BLl=ph7
z={CSA57y)%vdRUCgEABR@@a~28ClJYoE%dvz3<`Wy0`H62r+YywlnYVVC~0?({)FQ
z>{`cd^Y(VzKiAfjt%x^B&i)jZHI|OWF=V|LkNt~g7^X-Q2-muDLIX}
z1~_5l#HddM@<+cuK2QaBZ&v&g_SwSP_~w^;WyOc-Axf*Ih1u-;5{-mJ7Z`}um~Wd7oNfwr%2iC7&*v(+
z03m+P(Fgz=0QgzcRmyilaTd!_4(%+vsgW&YnLz|3Y
z6uV`hjBkielah&jig1|n9^ENLc-Odg&_kC0!f%1f{P)yVCsUQs6OVd1CmD*NiiXi$
z7{Jg0N8@^?{(Gt{CjRibV$*}xbQ?4^-Jw*YX;LC!8$^ySGDa5v3S&P)>*nTPW4XrJ
zMYgPtZ%TV;7Aa9{st>vs7MwCTx`=!p!)WqOY7!|lD%
z5{HQIBOn*SJxU-R@->BOu?pPS@rUNM8ltoqdzoVP#Y(+a8IcXh>BeREFcU(o(C6k&
zxU$NCxR%dfQ_%?-=?e)RKcOg{WEjQ#$r?_JBR0_(1SQNiPqqbuo5d0HWIe0+ko2^1R#(1UgcV~4V8od
zSq{vLY3A-N#{2RCF?C3D4SPjw8QjbLLu>V7mL146*^7~ZY-8tAl$O&@*IcmzBKBYwc2;1yrVnRVI%-bRPb~^YSqdjp61&%4UCm+e$gWq9H(&xP0CDM7%SqT=$KS
zQ1vI5drS^c*;`h=$l7YtzRl
zDq4`&P5DoK*;R%01tGegO0Nk4T(1OU)Z_Q?HIlgqd%CgY6C
zR-ZSEcUoyDTEKV#d)p#0SwC@f^v@d5KV%G??#~*_znjNjX8o2B0h4j}Gk7lQo%l*v
z@v)XyTnhW&aCjX_M%g8WuTO3dz)c$Lx8FctIscM#+GC8U%?gE_#b)PrvL`C5Q93gu
z<_Yimsw=$P#eY-e?D5&c)`d*4OBD}9k>1kmS`WIqUJ1~p>VYVy2Z}ioo`}a|kDUVS
zKh8fY|8RnL#lu(yl#vc%FzER(d)w&ApUVg=T6s@T^q2MpPe2=gM~+luQ?PsY((B2S
znyip0FJbr%EfO%t$FYUPp_7Rz
zseUMSC)(&Bm4G#b*~2p^?r9NdXW}FX`_Bru&=+c#)tEvUs}ubYO!#ne&P6TiOOTpM
zN-p(X%JQ!r1Rom%c#}LmE9%|(xYo||?y#rZj
z>gAwX5FzWOoszD$RxE9c!M6A`H4;uvadQv~gl(@FbKgpPvji7jOu+`4NW+lZGp>tp*jJxQocjKGOq
zM`Fn(AVjc)j&SS#4Dzpm=XpK5m~WvvPcQHj+r;pR{JlB{vFR(%9NB+-~@Gy)ee8;m~ns~v0vGnQ?T3U
zU+Bm6{?I#df8uB=JBkb0Iok3Cdy0al{mlMx<;Absjo?Stsq~O4
z_|i5RM!AYcN(@+%Il=m%&j*U3S!swW<0t6?f_aKJ%ITOrINK|ull~bXY&H7I)%N;h
zA$>&r)a?GHO-8~c0)%TTd5#29(^MNG%Oa)a^%`8~t(O!xkTj-S3n;wZO+d9t5Xhzh
zRIj4J)IhxGaaeQl7{ad*`xS(*^5cP$ePa1Zy~D!z+KuR`B
zquae_k1FF%VAz7ZE@JC=E*@Puw{LU3up6q_2|Q9EAS)CZ4Xp|#ozLXNieUtlDkH9L
zgZ3Jw@x@Iu6x($xmX*Lr%!_>RdnCVi=i>q`kLRq^pY*ZF$h~hEe#iP`uc$f&eqTp^
z1jMR+sNoABQ-Z4GA46jVzgk1Dt!C_P7aNfG_K7`h6?w;VM6kZ(kbvU0dqWdDEgJZ!
z-Iv~PM+V&wec3z}Jxq6A{9`R%c_+ykdkvq~kTJFBc_+ZGjOtnW>^I!kgaST(_Tr8P
zS3A7Z<7(_LCmTB(C=w`99~h}nD00^={tbT#$c+-b7+ezd)z7_5g6dRv$6Ul@^7--L
zN+S&?7Mxz2?($yWMoTSTuRcAyq)g>0$u9G;5Jny;6Q
zUhX4P1^t_cRt8N`fSrCZ62RH{t=Pi-9!MYKbJ|AZGD{v=;DVFmD&z||VtlSr$A%&N
zqp%>?#fO4-J?-1}7d**-=u^wy%}$>r;fV+clo=SE>pr48m{)>w%X+1O|EdpIDl5J!
zZ|?4)=-4pfZ>Ne;C-(G7JVTEbY(Gk8-cTPE=-y-qjct7W?KuNuc_Rr#&X1*&;$BbC
z&1xTicGC0#Le=8*sB8M({tJe_3nvRZXZyDbc-uh2rCK{jZWaPitDJ}b0UZhcp%c_n
zE#|UP?c|Rj7$I0cK4?9g(YWp8v4B5Aj{;&9^fh0
zPGSj==?0(m@-X4EI|3|id=xD>3?T52?>wpyYQ`-dxSpj1UPA$j!tyQ*uAEv8(xk;>J)
zC232@H^xKfCQ91MKTIWb_AUfm?T?WmLp7NbZ@hEjgTsrfp@HHBa`V_>;`%FdUka_P
z4LC?UVORg$Wznh+6_NF<<~$_!`K`tKKjcgoCw|5B!g8nWf?u;g9iF%YeQIPT~6
z-2c%H8`SaU*Cd#n8=ra{FT
z`P0PEs*E7wP3Ya77hg-8K+n<=JeWtjr50Em=sjw(U!`)#QI8(=?DAN^YCbw1fmOs_
zAYQ(DcrW+qz5K{%v@P}fCUTUZOOqmm<;ma$R2?A{I8WixIu0~f?ZXuFB>Me>FwuAl
z3c`6o%khyed5pI;{{1XKOno#~|awdY-i!yHV>HVfCwR*TdPSiJq=(c16cV+bGFl
z+U)^a3vOgW4HsRzrRz38@Lca%L&E4S33wgw2~Eu^Y>Un}L*G1QbzHA&DY$1o{N(#q
zXhC!Jfg|eUk-#lUXS&DT{k;^|yP=5vDnq0<{G%%wf22QgdFDU7dxfadq8LN`
zYsUb!K$;#Sh@pB1v-b_rvzU;cA8Ri_noSreWX)Aa$%@0EfvNOl^p%H6z}r|VT-t3hS=y>1u~%;1B-CLb4ULs7ys=Q
z)zn1}D}4I=x&Fd}mG1Q_J@bRlxC_O}M!}tXV`GylL`=)8koGlAv*l_WO(JBH*V%=F
z%if(l=cr1r9g{Q?D}%Y=>Emj`4k8d;vDw
zTd%*L&=>AlZf%377Hj-PoI_RRz0h#bBryG6C2xyO?Hk#ZO47|QudS+!WYw&E-|isR
z=;`P6gqH#s>+X~Xw}RZfAE
zy#|3-uguk%Z>?>T05j)^WATlY5G*2i%A#p0^PL2$u4^cM|db?VjZf^U1*TK#b
z{Pd#9A17!*w$XlOD3M5yGjw3^#z}+o!uug!uF(M`4T7D$vY_bAkc@)}=T{kvfQ60x
zPFUxTq+p7M_=Tg8s3G_^hJPSKO`o(4|a;bT@{Aor3PKohjnw*-VAdu7gFfwEyW<$R7smO7-~Y
z+V_R)cGq;)=a2O=453i~!!kz2@&|?8Y5DMs1i$;1*Fg?}OXL7Z<~u9vo14;7%a(m0
zYng>?VFff=RS%|;LeI=dwEI-D`~&-tOSIMA10;^T{^WN!mg^CY15agBsx^Xc{Vw!7
z(NApl!Q@+bE~DngFv^Qqtw}P0TjiYU#OcPil9IXx$^aU*@(fi{pkqhYtn7Vv@ckm3
z7ad?RKh>X3g)xKA19u)2#m7`!b1@QnJw`yXYG0nH8oMFQv9i7sHV!)
zl;azjNJBdt6DQe(z$xXGgQzd=;EZVH?eAENFxhN;bCYTB^{Ks?nx>I4)}MYYzUN!7
zNgr>%DBsV+S5nwz2QS_6Un8eUK=C9Jd@e_8gJK%ExK8t!``S*ozb$l&Y*`i_ES3l7
zb{CfiXG@Oagb}^Kfa1B|>4U7n+xxz{Gm{^ur&0uSgpe%2X3m9s|Bx;kM5Tm?&6P2g
zh?ZA?9HnilsSb^~`p)Y3FWHUB(!=}ZASWsH`A^R3^^tWraY4`!&<0W^{MDM5_CKd^
z)Jyetypn>qfR%i*{Y7e!`JRZxT$xMA)^iO9L0{j?rhdKl8=tF_Q%C2Tgs~%G+`twpfS#TjYX;kGpCCxj#8U5%)%K?z-0jE58`RU`^QwWOyj?9(
zNLs}ob{W>!?jqg{Kak|;0`ry77~vcX`D4jKwmJ@VSIxEf_;pmyQN-}+zrP#X@_G!X
zB`-eKp9IW_q}?c5zm>_(|u`Yd(^z2q^7E7@V+(i+vYAm(RSpvJ!<8ryq+xc
zh52=)!0+5&l%s;KTVy-+pIlTL{nKa=(OZFz;s~d2APQwE(*}UqYsNxA~A7ZQf3J=s$h_ez=Yu_umBEt_FfJLmm#>+3QWKIb?pWp3kSJUE(ak*aV@-5G
z6E?A?ZU#EVbF@_h1fz**VGO43S(_I!@+{7-e^*WJJFI9?YmT7rCS
z&BQ{kT-@>Q_yLEWkBu!axTPajK;Orsc`Phe#+NUdCtR%AV-N?wfBz+rZ7;_;X5y@&
z9rbzFh*DEj-`RGdxBpv@X~^`$`w3dt?sqglvLqfOg$zS%7dUN~uK60JoJ>IQ$b1#79vj|ipo4vm5Mq>9PQQR}
z`mMSc#6CYAqwbk}vzFa1+;(TpYFe8FH)kUs3R{0oaGd6iboVX)SeRO700iqM9Eb)0
z3THm{K1W$){pkrh7VQ_s2FE*)R*O7@Wm?ouwBox|0F3_LqWy(ep660qi)SnJfB>x?
zGXyDeL1BAPkD!vGlBmy_oF`7c{_y@GJC-UPwLfsdS}7|n4O0wXRtEswEapnPA}zq8
z2Pf!gRhe<%m*LelXdjlDG_X<6zmp%LZU2Ht>H8HH-w<|N{y3qhRIA*Wi1KcATDO9j$jr
zQPt3R0DkS~P-Wpe6&ZXnZK@$(?D@4dc}d1{qYtcOvaIxz-}P6M3YH>-h+MH2Q
zBjuXrQpX9(`p7O5W}L7OT!5Ji^YT=+oT`PH#g%5^%QUl1{#e+Iw@Q#
zIy=c?=dlX{JbuNF!)MDkmljG1J})JuQoXC67wM88uzbL&(`mKoz)++1SKOCfl`N;B02OSOR{6MA5!I3Tcj|xo8;C;|&
zi)v%)V?Dy$=STT9JM-^{2T4P{2#}H-8Kn<(C71TEQ63*S?ff3H?4#|IKEFDG
zvmV4racOL7&b8V)GIEHrr%}vFR?C~SAZM#C`y319G(pl@9YADX2ljaz
zwGXGhUKU(ZUWD&9+4<^?uezhsh?`x>vAcpqanSL%5*zytGT-0o7yfBGW8$J9+&%U0
zHq5enS0{%TCK55*2v*-w4^BiAVr{>si~bR-&8vUq|Cqyl-)_dS!WY0(N6D=vE40{l
z;&ujwx}ljmY4dVy^X{W+eL!P9(rb7y>fv&FGUKiGHqQ%&LsB7vP8;Q~960xQ1O
z!zJLoxrrUzV+unyJbg>mWL{F$i-M&F-
z&|EUNyRZ~FX2@0&NBV&jDXRk=gbiC(Dd%=|ZpzmTNM8d2Y=)M>CbZlsbAqE{2p@^a
zTQEt+@wDz0NFs4Vk)7l
zUoMIXMjxobX2uUTGl=5*4_Pad@>V&vSQz5s627#BuCZWc+@yUa_3{!_)|y4u0L*`e!XZ9K(p;8LAwQ_@7lQRISSXiiuQ>8s{v&A}sp$O+3bOCgaq@YT?Iv
zrA|E6&`AdHc4U61@7|ji{aasc17>k?=6JZ`mf)4LzhdJw?EI%HN~=nT;!)o2Vc9$E
zg3O2|LAev_>*d?O{$LpD5F>sKCc!(a7n3aWB?QC5$9r3{k}XT8Ks6>Tm3ruPTvmB>^^hxnw`Jn}KB)@Z8=uav)$
z#of2R;@!y)U3<-JalQhPufd5zVAT+93yrRm7HtW1N3WjmtPLm=<8y7>@oq
z&`A*wu$2Qy5PZc9YIU>en0Y%B%l_B{W_+7`#b%^(um};@R6PEG0UnJ(qX>MK-|{oh
z3k-638~Uoiud_+E;-9{+Z$P0)M*JT&WHP0`;{0?@r}dgV<&HxL8*uuqF#sj7`+5lasp-@g0S*LEz3dtBO=U1o#pK%Luy>
zqxJi38nzg)g8tAVmMCMDA^BwG5avSbIX$|+LoY-?_iDz<3IoNqwqL}5a)=F0l}%g=
zjwwuCY3hAc35zTgG>=U3vrfqbUcKZE7*>Z%1MRavYcJFw)qJB`Xbr!ug0E5Kvwg{%
zVP{~60<2a!{eTz<*2sNqY}nOWp2Ae^WnX<)3)b#HSWDPEgch*j*tuDuCCU+ZdU=_?
zE75AWk$`6YaHKybOph{#nf-EU>%3aTo1ls%4wQjl!o<;TJSmlrs>uMxP>)$X8ojqdOJoKy--%1Ai
zm}O$jFRdzFO{y?i4N^v;+%kXrb?8pprpof-RF`O7d+^?wPP+kVU~zZ8Q`AmeYkUez
zwGv*f9H)NsOHJsEj4iz&XiF|q6E{T)E
z$#OtFYV7aMSG%i+)z`^|Mls}JyLvaX*FJ^2zm}_h?Dj&Q0vFg_d+Ke5@JvMI*tTRE
zbPn0oCxiNMRnWs~XTEdTkUB9aajcgfpI)}jzl`nmR-(?k*uG&flx?$I-OYezr<9tN
z(JdFcS)$EQk$N${)Y_F{aQ}*rD^H+##Y&{yu`?Kc;`V%4Sc*@N9yevNTKha
zI1n077a%EV)%3@2a5_0ZKPA6jWHaWG2QW-fD*^Ls>P{LcSf1oQhV<18Zg@GAA2uW
zgM(%py4Z3RV%CXwqO{KyOq|xwqi-@wyEZ3`-XJyjqQ4txgBDB4<_cgLRIJ4CsuST`
z_^5ZsVQ)``2;4{`8ns?RW+Bw>4w@mL155*{5FVd5iv}
zTxtF7{j8Ibm_ypUmHI}tcp(@k>`cfY!-caLY>21*qlY#AGr4KCo+gL=-f)yFplHjEJHJIr@%`!S&eE=IKS^h3fWRst&E~&hHjs=;!*1dctu&o<2Mk|>SG4fIg#losg>qE%r7Jr&oEkIk%&bM=AeM40acm)zVO8QRc
z2kHB~3c2bC5EMl{ZEdB`sdv5oalUTd`(GaY+Kikn3FB&3T+7sjW1x*nl+43#vP7u=
z1mAG7n_VH}hHTBNG%}8sNZQ!jl$tXIrlpU3QS7$LA{AgGO06n69f~>x{0(Ua%_YPHY
zE#c1R6u)9y&_Rwc$a+O-zPCq#kOi*L;z-vv4O*fawZ3L^$puXm(VxA^Q!-KN$1F(M
z$8h|(@02&sUeKmAz@Ta)@erh_==P{>ah2MN9Pa-j2R?EYan^y
z`4ozP@@Mp&ZQH@LTJ{D0&XtV|mE!o#4LL>Cx(6$?T0<{LPS`%p#>jK%2WCa;sqU&$
zeK-D}6ej)1bf$z_Y^lsuR+aV2;+54&Jp)k{)F-HnTyy@bdiQysrxNXC`U_0d@rUz3
z&&{#rU=|nKM
z%Anb6!bg+OPqN1F%_fO*6-)0G)>JW0n8k_w8x3}q0Gy$Ww?UU?Yrh|b+30=A9SM8y
z#KF>#?Q@R)c;;+pltve`DQR*UiVVklx2xFF#%DY_mP^SY;P&+dOa9;0-yoQ?3z>Tq
zN7&9@GWihep+;{aA}`SYn*>qF)T0X-^suV~UGOCA}
z>{j065{Y1gKbo-6O&?_WM)jOX)~{Fpa{2vj*YoWd_f@A=8Mu&xu#-TB@&~JF6V88s
zwW6)`(u7l!$GY#5X=yFex}saN#NPI==b>N(;CU%D~Sl2~bBD)$>`n3+p-Y8nO`anl}d5_$@H%Dr0z^
z;YRB56MS*T1w6?sOmEv~Qij(Tg2E~vN^7$a;QNb_g?WNApoH~Krm;7Rbvr%pH){<|
zM60>6=2$$6T>@fy9G7oz)15GMuXmS7jz74|`*%*2Fa(4&O_o1A5)Ypb{KWuwJ4eC5
z_Ay%{Oj7=k8USSi_h8P?3b4E;<&InES~uU39iRNp8-Rt$sG-og?<-=qMKJVue|mQ8
z&wcr}zSg$w{e^RkPin!%rZ(Pa(xO5|iG*(#Ajp!VK+E>Z|Nmzi#r
zH!&vST3$#}hMu1sE2Q}m!>T)GJawn;Ilw~D+0Q5sX?4lDAsNmJXis^*WT>sedhhyX
zV)A>nQ(XAzxb_7(4#dXeKp?6rTa!6gkSeC;C6c4l)ZPNw=or)KFK7VVv)!NTt8=Ie
zUw*>%=muvbInfXT(1dc<0ClizeR0J4*aMtdd)eymbO=V3K*$zg4*xluv>!cK;Z}DG
zA%(}uq<0|%PB0Ntf-TtP+$Hql9|S_l91grQisgPR%!EqSyJ8i(*FgLWHZ%pWflm55
zhz-Ctec`qy(0qv^tqQkTzSH4pAA3D3tTlRm)}vE>_@Z=E_Ho`^NpR=uJFRoj}R_5$mRCipBBq*9{Lr>a
z`l7dgmAJo!S8gXq$cPUH=HM{2+40r`L>6YNk!2s=aq~BzLwoPj73qr7Z%>m_Ma7Zl
z2?3~Kwt^R#!UUO}uT?9xt-Edjbsa*faA(M?Aya}k1L?osKR({Xg76U*@Q@>@m4Bu#
zP(dGKVuWRGK|St?igsa*cbZr>{!5>BRfgwU4GsgU
z<$818T3{akJ;0u>{D{-UlBwboA2fnsI`k*@J%1~br_mWv-s7F_PW?0M3U(}@3LFX@;GuSN$RAHkA>1OoWWhm*1iz(7Cf614fJ&=bPP#^3O9AbV)jdk+tWZddSV
zkd1d2GG$!|NPh>Xk>!TKp=8#2Zk9n)e6b+_
z3G9NnMjOQJUVV-w?&Mru5O)ZzGi$&z(3@;6=LXS`mQqtk%NRVQ6663((*n6DK`eX!
z-R8<5pKWAJHg=Ca_$+jbAeji~Covl{RxrZD@^5ctfhNhJGDFAR8raD1!8WfS<>R|s
z!JNG4@{bu@hYoe;{pj~MY~A|hfBvQu%@{B7@!9L2lk2B1ihjK^Ujw!ZN;Ws5lQWM$
zUN3fRcao4!<3R(6F(|1U+K(#|`)|b?&naP#OQ2T%&eU~JXT56u82}m;J^|WnB(#>!
zV>|LFSJ3mm=KW@)kERDYs+H7WlW+b2ekP5zAO1aG&%D>RFXQFXAcdIbyLc?vvVyRI
zT@ZqTcZ>MQkBlQXs&sl6!{Kc(&voLu-^r8SX=?UJTIXW{h0E)0@qB|2~xVJfW0@Gu(ae;fufI(6>6sY9sXMS%2
z!C5JA0nM4w6VkGbGL|hw$#*ALIQ@GXfoTST0CvPab#(=-pp$$LRg%>53x
zV})oU&JNKo&DLM9ZVrV#(LSzc27@Vmf%EnsPaBm5$=Pb`^-cXgJ{NhKF
zjt;j{3i#)VZ-`Q?3ExX?W>z}z$>aybvBy(Lu((Gzn%GU4f$ZVV73L~k!0OSY(WN8m
zS@Xf9Rn%9C#9lZC=`+c-L@So0g0|Jq~hy;6D}oP9H!hD
z1#C|4005|xZ%MKpA{NgTZH$tWK1XJ)zyq`g*04j!Z;NHa@ka5GEgZJ{r>~F-GQnBL
zWs}X%5TlbK>$y7E4Vi&^1|HRNu^#nkb`l$MvV2tE^QQaHp#+5#@ZJ!6IZdPBHX3j>
z%>{X8;JPW<`4Rf~`@CK4!oi&+rHg6B7t2$C&kw1fhAh&q?0eNZy{`caH#zdbMpG?R
zfT;h6H0le8?|t{~Vw>$`2XKt|kbOv&9ah)&rMSHm2N%+7u=(jY-~<-QjKfsFue``_aO1kq*Jl|Dx(`oUy
zzh0~fGQ^i<9Bx0PmRwi>8Pa$=zuJ=9sOl~c_q;CZ>pz-zOh1@;^6|jpYCMIW!>cW<
zU@zLAjHC299}_ha>ogq?149X*wtcZ@zcPNEn>u9j(bN_(un#m8KxXx@_&tf;lD`GE
zmB+3%dq|)|>iA&u$L%$BN`}{XEsgKX^^d+1>s|g?UUwGtzhZ>>9N@J!E>nwt1BYkd
zGaE0a$x>F!8_&x#z`Q4ISEN7|4mO+g$K0gP#I`ZdO;=QH?&TM!7X%r%wNKJE9zJOw
zPXQ``S+@x{!2TL1Q-h@^@5Cz?q;+)+qtJ4LCrQb)m`y1E;sAM?wB7|-j*(|kAHX^!
z$dDRi7-y{@9@}WQqeD;a;vJ^?CWDNhnuNe6E9>5*o(6XJ7~1;Q&)Bg32J}iGpcH7ozXC=YuT#Opu)&DHe)_49-=D~&hm3*8&7-i)R)vT
zo2+;#I*f=>aDl(U_88*F7C;*U;piRmlJP8dh>nobWLf;D?{X#^^F0{}0+CELiXw88@mbo%6&-_dW`X`G!nklERoVMa{QP7IfQb@cR{
zxBi?mgq6#J0P>V`xZE?UY{gY9gT4bO07Bp}R@YZ2UcGy@I{qDsec^yGI~#WE+e+2k9r_C}W{mX}3y<5PJ}Tcd%LU
z!4?VdZJUUxRkReNM@ABJj08rlYGB{8@P-1+KoxRVPG-DtEqfF_@=tXxrZv|E@D8B>
zsTp0IN6+J?^mn5aSZe3h*$G1i=G(>WMp{3NF@mIE$%Nw0;F=Ov#NVJLu1dSDs)
za2d}yJDiO7a1#EB0th*tam5Eo8^44E)w`etjg2x*AAU!=)O(79g5w*&6n*pt@hJ-A
z5l~`b`3S-6$Bm^pIeMTLE`q9CW&l7{$1_IKAjO~r
zQMuKWjJG)!ga{hK=jqb|;YLCa@X(+uICfVxbu9~cJ1RWx9xdbN6
zM1<3YwasmV^)ZtEo0Y+q8h|A#G`UCv#q;rrv$b0QD#Vgrc;7p573g@_9y@GfqsPQ>
zZcaYR#Avj7*m|4MfCd%0TKOnQ(b{fN@6`MJemdebI0IY4>@oW{H070LckEKdQ6Phk
zxno`csmuH7E9nAMiPjnbt2aizM+Z{5>c1U}ycfi}Z@Oa!7iDiT(C9gFUPPT*#eSAA
z7OKNS4@GKC=D!U5ox5x^o5lJWSkuuUck!Uf;}M)=#pR`MJ+vy7?cehAluQ{-dsN)#
zy|{1F`f7gLA0}q2KxPY!emy6?`wnKme_{?7;C%@gTwek*(GXt`Q_q-hybG
zWa6jOo@$;^uy+zA+wa{Uop3#x#i$Y#+^u)wM0Ak;wHKR1k_8Ow^l2z=f{@>0s^i?7
zv~E2$CG2a*zi{ff7c|&6AHt|Wz#HNxE9g!J8U#EC=$?eoUQx!Zve%;0rlJCZ8Ffq!aAt!r
z`=p!Hdlq8xqsN(g0fI{%^rYZzxV!FuH>4$D*a@GK`$
zRGUs@moXe|%Pd(U@Q^i*Oauv~;g^ocRKK#qstQTWR+nrM6MUpj-Ws$+e>1?S7Z?Pf
zyq=ZHS-hy~E{~@1f1r7eW6VtYfE2655wlfMk>m-DeiK-8Q}Jm_lH{F)dg4t`K;%h`
zi2MlSt?Ag+1Mroc(k~cjnPmXgtpXwjP6xm$r~F>GK#hZuqV_i{s5T+{IR<=wA>am^
zoy@XQ_H@Ru@1T$hsf6q_y)hrA>wWaszc#*9&&z=VpK&ZiPbC=<*5D5KB5?hF7zv3o
z_5&co+-?Cs>d(p?v7gYbZ3^}zE&(s61a?hNw_8bBtiv;YBmOqW+Xxii%`)^jCLbF$nyL>ys!6S)M6#|8Ks#)7=6A6E7JUIt(V#
ziwmwzgT;XiYuxM$wuY+;e?N8brNJeePu*9!&md}k=ab+y%%f})$Dj{_V4Dx}4)v^I
zy-53}NvW3lX9!53y9H<}`oo9sA>FwevBxAWLlHRmWU#0OVyH~tEBga+R*`lz&1vlWy^s_~90W!H>;X?+m@3|K&G6OnABIL8Yp?v4&R~I_RHTv`vVSpMSbqu8!3ok)Ms$?RwS{DD6jO`
z2RqBwA!}PsvpEo^P)&Eo?%trVzW*LGNUGsi>&z+P?aJooao`awGYkHd`qc=1STKJH
zL{NRtpd8r&Yc?vRS$;a-^yks2s^p+^Ede%w;*L4i>$F#ML#4`J^;^JAY0vf3D3_kN
zF+RMN3&