From c7251c250189f39e849627ae5e635dd2ea64252e Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Sat, 6 Dec 2025 18:43:53 +0100 Subject: [PATCH 01/21] feat: Introduce year-based routing and 2025 event edition components, while updating existing home and navigation for the new structure and preparing for 2026 data --- src/2025/Home/HomeWrapper2025.tsx | 48 ++ .../ActionButtons/ActionButtons.tsx | 63 ++ src/2025/Home/components/Faqs/Faqs.style.ts | 171 +++++ src/2025/Home/components/Faqs/Faqs.tsx | 193 ++++++ src/2025/Home/components/Faqs/FaqsData.ts | 21 + .../components/Faqs/components/FaqsCard.tsx | 67 ++ src/2025/Home/components/Home/DateUtil.ts | 16 + src/2025/Home/components/Home/Home.tsx | 156 +++++ src/2025/Home/components/Home/Style.Home.tsx | 179 ++++++ .../Home/components/CountDownCompleted.tsx | 33 + .../Home/components/TimeCountdown.tsx | 47 ++ .../Home/components/countdown.style.ts | 31 + .../InfoButtons/InfoButtons.test.tsx | 47 ++ .../components/InfoButtons/InfoButtons.tsx | 44 ++ .../Home/components/Sponsors/BasicSponsor.tsx | 97 +++ .../Home/components/Sponsors/Communities.tsx | 93 +++ .../components/Sponsors/MediaPartners.tsx | 94 +++ .../components/Sponsors/PremiumSponsors.tsx | 99 +++ .../components/Sponsors/RegularSponsors.tsx | 93 +++ .../Home/components/Sponsors/SponsorBadge.tsx | 36 ++ .../components/Sponsors/Sponsors.style.ts | 297 +++++++++ .../Home/components/Sponsors/Sponsors.tsx | 53 ++ .../Home/components/Sponsors/SponsorsData.ts | 162 +++++ .../components/Sponsors/Supporters.test.tsx | 82 +++ .../Home/components/Sponsors/Supporters.tsx | 102 +++ .../Home/components/Sponsors/TopSponsors.tsx | 92 +++ .../Sponsors/useSponsorsHook.test.tsx | 107 ++++ .../components/Sponsors/useSponsorsHook.ts | 41 ++ src/2025/Navigation/NavigationData2025.ts | 26 + src/App.tsx | 595 +----------------- src/components/Navigation/Navigation.tsx | 32 +- src/components/Navigation/NavigationData.ts | 62 +- src/components/Router/RouteRenderer.tsx | 34 + src/components/Router/SuspenseRoute.tsx | 26 + src/config/routeConfig.ts | 131 ++++ src/config/yearRoutes.ts | 96 +++ src/constants/routes.ts | 41 ++ src/data/2026.json | 45 ++ src/utils/lazyComponents.ts | 172 +++++ src/views/Cfp/CfpSection.test.tsx | 6 +- src/views/Home/HomeWrapper.tsx | 8 +- src/views/Home/UseEventEdition.tsx | 2 +- src/views/Home/components/Home/Home.tsx | 5 +- .../Home/components/Sponsors/SponsorsData.ts | 147 +---- src/views/Speakers/Speakers.test.tsx | 2 +- src/views/Talks/LiveView.test.tsx | 2 +- tsconfig.json | 45 +- vite.config.ts | 1 + 48 files changed, 3263 insertions(+), 779 deletions(-) create mode 100644 src/2025/Home/HomeWrapper2025.tsx create mode 100644 src/2025/Home/components/ActionButtons/ActionButtons.tsx create mode 100644 src/2025/Home/components/Faqs/Faqs.style.ts create mode 100644 src/2025/Home/components/Faqs/Faqs.tsx create mode 100644 src/2025/Home/components/Faqs/FaqsData.ts create mode 100644 src/2025/Home/components/Faqs/components/FaqsCard.tsx create mode 100644 src/2025/Home/components/Home/DateUtil.ts create mode 100644 src/2025/Home/components/Home/Home.tsx create mode 100644 src/2025/Home/components/Home/Style.Home.tsx create mode 100644 src/2025/Home/components/Home/components/CountDownCompleted.tsx create mode 100644 src/2025/Home/components/Home/components/TimeCountdown.tsx create mode 100644 src/2025/Home/components/Home/components/countdown.style.ts create mode 100644 src/2025/Home/components/InfoButtons/InfoButtons.test.tsx create mode 100644 src/2025/Home/components/InfoButtons/InfoButtons.tsx create mode 100644 src/2025/Home/components/Sponsors/BasicSponsor.tsx create mode 100644 src/2025/Home/components/Sponsors/Communities.tsx create mode 100644 src/2025/Home/components/Sponsors/MediaPartners.tsx create mode 100644 src/2025/Home/components/Sponsors/PremiumSponsors.tsx create mode 100644 src/2025/Home/components/Sponsors/RegularSponsors.tsx create mode 100644 src/2025/Home/components/Sponsors/SponsorBadge.tsx create mode 100644 src/2025/Home/components/Sponsors/Sponsors.style.ts create mode 100644 src/2025/Home/components/Sponsors/Sponsors.tsx create mode 100644 src/2025/Home/components/Sponsors/SponsorsData.ts create mode 100644 src/2025/Home/components/Sponsors/Supporters.test.tsx create mode 100644 src/2025/Home/components/Sponsors/Supporters.tsx create mode 100644 src/2025/Home/components/Sponsors/TopSponsors.tsx create mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.test.tsx create mode 100644 src/2025/Home/components/Sponsors/useSponsorsHook.ts create mode 100644 src/2025/Navigation/NavigationData2025.ts create mode 100644 src/components/Router/RouteRenderer.tsx create mode 100644 src/components/Router/SuspenseRoute.tsx create mode 100644 src/config/routeConfig.ts create mode 100644 src/config/yearRoutes.ts create mode 100644 src/data/2026.json create mode 100644 src/utils/lazyComponents.ts diff --git a/src/2025/Home/HomeWrapper2025.tsx b/src/2025/Home/HomeWrapper2025.tsx new file mode 100644 index 000000000..ab2d61dc3 --- /dev/null +++ b/src/2025/Home/HomeWrapper2025.tsx @@ -0,0 +1,48 @@ +import { BIG_BREAKPOINT } from "@constants/BreakPoints"; +import React, { FC } from "react"; +import Faqs from "./components/Faqs/Faqs"; +import Home from "./components/Home/Home"; +import Sponsors from "./components/Sponsors/Sponsors"; +import { styled } from "styled-components"; +import conferenceData from "@data/2025.json"; +import { useLocation } from "react-router"; + +import SpeakersCarousel from "@components/Swiper/SpeakersCarousel"; +import { ROUTE_2025_SPEAKERS } from "@constants/routes"; +import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; + +const StyledContainer = styled.div` + padding-bottom: 10rem; + + @media only screen and (max-width: ${BIG_BREAKPOINT}px) { + padding-bottom: 20rem; + } +`; + +export const HomeWrapper2025: FC> = () => { + const { hash } = useLocation(); + + React.useEffect(() => { + if (hash != null && hash !== "") { + const scroll = document.getElementById(hash.substring(1)); + scroll?.scrollIntoView(); + } + }, [hash]); + + useDocumentTitleUpdater("Home", conferenceData?.edition ?? "2025"); + + return ( + + + {conferenceData?.carrousel.enabled && ( + + )} + + + + ); +}; diff --git a/src/2025/Home/components/ActionButtons/ActionButtons.tsx b/src/2025/Home/components/ActionButtons/ActionButtons.tsx new file mode 100644 index 000000000..acbb82b47 --- /dev/null +++ b/src/2025/Home/components/ActionButtons/ActionButtons.tsx @@ -0,0 +1,63 @@ +import { FC, useCallback } from "react"; +import data from "../../../../data/2025.json"; +import Button from "../../../../components/UI/Button"; +import { styled } from "styled-components"; +import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints"; +import { gaEventTracker } from "../../../../components/analytics/Analytics"; +import { useDateInterval } from "../../../../hooks/useDateInterval"; +import { useUrlBuilder } from "../../../../services/urlBuilder"; + +const StyledActionDiv = styled.div` + display: flex; + text-align: center; + + @media (max-width: ${BIG_BREAKPOINT}px) { + flex-direction: column; + width: 75%; + } +`; + +const ActionButtons: FC> = () => { + const { isTicketsDisabled, isSponsorDisabled, isCfpDisabled } = + useDateInterval(new Date(), data); + + const trackSponsorshipInfo = useCallback(() => { + gaEventTracker("sponsorship", "sponsorship"); + }, []); + + const trackTickets = useCallback(() => { + gaEventTracker("ticket", "tickets"); + }, []); + + const trackCFP = useCallback(() => { + gaEventTracker("CFP", "CFP"); + }, []); + + return ( + + + ), +})); + +// Mock useWindowSize +vi.mock("react-use", () => ({ + useWindowSize: () => ({ width: 1200, height: 800 }), +})); + +const mockMeeting: IMeeting = { + id: 12345, + title: "Test Talk Title", + description: "This is a test talk description with important content.", + videoUrl: "https://www.youtube.com/embed/test123", + slidesURL: "https://slides.example.com/test", + videoTags: ["React", "TypeScript", "Testing"], + speakers: [ + { id: "speaker-1", name: "John Doe" }, + { id: "speaker-2", name: "Jane Smith" }, + ], + level: "Intermediate", + type: "Talk", + language: "English", + track: "Frontend", + startDate: "2024-06-17", + endDate: "2024-06-17", + startTime: "10:00", + endTime: "11:00", +}; + +const mockSpeakers: ISpeaker[] = [ + { + id: "speaker-1", + fullName: "John Doe", + speakerImage: "/images/speakers/john.jpg", + bio: "Test bio", + tagLine: "Test tagline", + sessions: [], + links: [], + }, + { + id: "speaker-2", + fullName: "Jane Smith", + speakerImage: "/images/speakers/jane.jpg", + bio: "Test bio 2", + tagLine: "Test tagline 2", + sessions: [], + links: [], + }, +]; + +const renderMeetingDetail = ( + meeting: Partial = {}, + speakers: ISpeaker[] = mockSpeakers +) => { + return render( + + + + ); +}; + +describe("MeetingDetail", () => { + it("renders the meeting title", () => { + renderMeetingDetail(); + expect(screen.getByText(/Test Talk Title/)).toBeInTheDocument(); + }); + + it("renders the meeting description", () => { + renderMeetingDetail(); + expect( + screen.getByText(/This is a test talk description/) + ).toBeInTheDocument(); + }); + + it("renders speaker names with links", () => { + renderMeetingDetail(); + expect(screen.getByText("John Doe")).toBeInTheDocument(); + expect(screen.getByText("Jane Smith")).toBeInTheDocument(); + }); + + it("renders speaker images", () => { + renderMeetingDetail(); + const speakerImages = screen.getAllByRole("img", { name: /John Doe|Jane Smith/ }); + expect(speakerImages).toHaveLength(2); + }); + + it("renders vote talk link with correct href", () => { + renderMeetingDetail(); + const voteLink = screen.getByText(/Vote this talk/).closest("a"); + expect(voteLink).toHaveAttribute( + "href", + "https://openfeedback.io/test-feedback-id/0/12345" + ); + }); + + it("renders video iframe when videoUrl is provided", () => { + renderMeetingDetail(); + const iframe = screen.getByTitle("Test Talk Title"); + expect(iframe).toBeInTheDocument(); + expect(iframe).toHaveAttribute( + "src", + "https://www.youtube.com/embed/test123" + ); + }); + + it("does not render video iframe when videoUrl is empty", () => { + renderMeetingDetail({ videoUrl: undefined }); + expect(screen.queryByTitle("Test Talk Title")).not.toBeInTheDocument(); + }); + + it("renders slides link when slidesURL is provided", () => { + renderMeetingDetail(); + const slidesLink = screen.getByText(/Session Slides/).closest("a"); + expect(slidesLink).toHaveAttribute( + "href", + "https://slides.example.com/test" + ); + }); + + it("does not render slides link when slidesURL is empty", () => { + renderMeetingDetail({ slidesURL: "" }); + expect(screen.queryByText(/Session Slides/)).not.toBeInTheDocument(); + }); + + it("renders video tags", () => { + renderMeetingDetail(); + expect(screen.getByText("React")).toBeInTheDocument(); + expect(screen.getByText("TypeScript")).toBeInTheDocument(); + expect(screen.getByText("Testing")).toBeInTheDocument(); + }); + + it("renders track information", () => { + renderMeetingDetail(); + expect(screen.getByText(/Track:/)).toBeInTheDocument(); + expect(screen.getByText(/Frontend/)).toBeInTheDocument(); + }); + + it("renders level and type information", () => { + renderMeetingDetail(); + expect(screen.getByText(/Talk Intermediate/)).toBeInTheDocument(); + }); + + it("renders go back link", () => { + renderMeetingDetail(); + const backLink = screen.getByText("Go back"); + expect(backLink).toBeInTheDocument(); + expect(backLink.closest("a")).toHaveAttribute("href", "/talks"); + }); + + it("renders add to calendar button", () => { + renderMeetingDetail(); + expect(screen.getByTestId("add-to-calendar")).toBeInTheDocument(); + }); +}); From 32e41dfaea8fc97c2c91cff68e0c7258ae358381 Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Sun, 7 Dec 2025 20:13:31 +0100 Subject: [PATCH 10/21] refactor: Extract year-specific data and logic into wrapper components for generic views. --- src/2023/Cfp/CfpSection2023.tsx | 113 ------- src/2023/Cfp/CfpSectionWrapper.tsx | 10 + src/2023/Routes.tsx | 4 +- src/2023/Speakers/Speakers2023.test.tsx | 205 ------------ src/2023/Speakers/Speakers2023.tsx | 165 ---------- src/2023/Speakers/SpeakersWrapper.tsx | 9 + src/2024/Cfp/CfpSection2024.tsx | 93 ------ src/2024/Cfp/CfpSectionWrapper.tsx | 10 + src/2024/SpeakerDetail/SpeakerDetail.tsx | 165 ---------- .../SpeakerDetailContainer2024.tsx | 13 +- src/2024/Speakers/Speakers2024.test.tsx | 114 ------- src/2024/Speakers/Speakers2024.tsx | 166 ---------- src/2024/Speakers/SpeakersWrapper.tsx | 9 + src/2024/TalkDetail/MeetingDetail.tsx | 295 ------------------ .../TalkDetail/MeetingDetailContainer2024.tsx | 10 +- src/2024/Talks/Talks2024.tsx | 148 --------- src/2024/Talks/TalksWrapper.tsx | 110 +++++++ .../YearSpecific/Speakers/Speakers2023.tsx | 9 - .../YearSpecific/Speakers/Speakers2024.tsx | 7 - src/types/sessions.ts | 20 ++ src/utils/lazyComponents.ts | 14 +- src/views/Cfp/CfpData.ts | 20 +- src/views/Cfp/CfpSection.tsx | 18 +- src/views/MeetingDetail/MeetingDetail.tsx | 9 +- src/views/SpeakerDetail/SpeakerDetail.tsx | 14 +- src/views/Speakers/Speakers.tsx | 22 +- src/views/Talks/Talks.test.tsx | 141 ++++++++- src/views/Talks/Talks.tsx | 144 ++------- src/views/Workshops/Workshops.tsx | 2 +- 29 files changed, 411 insertions(+), 1648 deletions(-) delete mode 100644 src/2023/Cfp/CfpSection2023.tsx create mode 100644 src/2023/Cfp/CfpSectionWrapper.tsx delete mode 100644 src/2023/Speakers/Speakers2023.test.tsx delete mode 100644 src/2023/Speakers/Speakers2023.tsx create mode 100644 src/2023/Speakers/SpeakersWrapper.tsx delete mode 100644 src/2024/Cfp/CfpSection2024.tsx create mode 100644 src/2024/Cfp/CfpSectionWrapper.tsx delete mode 100644 src/2024/SpeakerDetail/SpeakerDetail.tsx delete mode 100644 src/2024/Speakers/Speakers2024.test.tsx delete mode 100644 src/2024/Speakers/Speakers2024.tsx create mode 100644 src/2024/Speakers/SpeakersWrapper.tsx delete mode 100644 src/2024/TalkDetail/MeetingDetail.tsx delete mode 100644 src/2024/Talks/Talks2024.tsx create mode 100644 src/2024/Talks/TalksWrapper.tsx delete mode 100644 src/components/YearSpecific/Speakers/Speakers2023.tsx delete mode 100644 src/components/YearSpecific/Speakers/Speakers2024.tsx diff --git a/src/2023/Cfp/CfpSection2023.tsx b/src/2023/Cfp/CfpSection2023.tsx deleted file mode 100644 index fa7e86b54..000000000 --- a/src/2023/Cfp/CfpSection2023.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { FC } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import { Color } from "@styles/colors"; -import { - StyledLessIcon, - StyledMoreIcon, - StyledSpeakersSection, -} from "../Speakers/Speakers.style"; -import TitleSection from "@components/SectionTitle/TitleSection"; -import { MOBILE_BREAKPOINT } from "@constants/BreakPoints"; -import { useWindowSize } from "react-use"; -import TwitterIcon from "@components/Icons/Twitter"; -import LinkedinIcon from "@components/Icons/Linkedin"; - -import conferenceData from "@data/2023.json"; -import { CfpTrackProps, data } from "./CfpData"; -import { styled } from "styled-components"; -import { - StyledAboutImage, - StyledSocialIconsWrapper, -} from "@views/About/components/Style.AboutCard"; -import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; - -const TrackName = styled.h2` - padding-top: 1.2rem; - padding-bottom: 0.8rem; - font-size: 1.5rem; - color: ${Color.DARK_BLUE}; -`; - -const MemberName = styled.h5` - font-size: 0.8rem; - color: ${Color.DARK_BLUE}; - text-align: left; -`; - -const CfpTrackComponent: FC> = ({ - track, -}) => ( - <> -
- {track.name} -
-
- {track.members.map((member) => { - return ( -
- {member.photo !== "" && ( -
- - {member.name} - - {member.twitter !== "" && ( - - )} - {member.linkedIn !== "" && ( - - )} - -
- )} -
- ); - })} -
- -); - -const CfpSection2023: FC> = () => { - const { width } = useWindowSize(); - - useDocumentTitleUpdater("CFP Committee", conferenceData.edition); - return ( - <> - - - - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - {data.map((track) => ( - - ))} - -
 
- - ); -}; - -export default CfpSection2023; diff --git a/src/2023/Cfp/CfpSectionWrapper.tsx b/src/2023/Cfp/CfpSectionWrapper.tsx new file mode 100644 index 000000000..79d870851 --- /dev/null +++ b/src/2023/Cfp/CfpSectionWrapper.tsx @@ -0,0 +1,10 @@ +import React, { FC } from "react"; +import CfpSection from "@views/Cfp/CfpSection"; +import data2023 from "@data/2023.json"; +import { data } from "./CfpData"; + +export const CfpSectionWrapper: FC = () => { + return ; +}; + +export default CfpSectionWrapper; diff --git a/src/2023/Routes.tsx b/src/2023/Routes.tsx index e64a585a1..6ed25e8c7 100644 --- a/src/2023/Routes.tsx +++ b/src/2023/Routes.tsx @@ -20,7 +20,7 @@ import { Loading } from "@components/Loading/Loading"; const Home2023Wrapper = lazy(() => import("./Home/Home2023Wrapper")); const Speakers2023 = lazy( - () => import("../components/YearSpecific/Speakers/Speakers2023"), + () => import("./Speakers/SpeakersWrapper"), ); const SpeakerDetailContainer2023 = lazy( () => import("./SpeakerDetail/SpeakerDetailContainer2023"), @@ -36,7 +36,7 @@ const SpeakerInformation2023 = lazy( () => import("./Speakers/SpeakerInformation2023"), ); const Communities2023 = lazy(() => import("./Communities/Communities2023")); -const CfpSection2023 = lazy(() => import("./Cfp/CfpSection2023")); +const CfpSection2023 = lazy(() => import("./Cfp/CfpSectionWrapper")); const SessionFeedback2023 = lazy( () => import("./SessionFeedback/SessionFeedback2023"), ); diff --git a/src/2023/Speakers/Speakers2023.test.tsx b/src/2023/Speakers/Speakers2023.test.tsx deleted file mode 100644 index e68dd5bd4..000000000 --- a/src/2023/Speakers/Speakers2023.test.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { vi } from "vitest"; - -vi.mock("../../hooks/useFetchSpeakers"); -vi.mock("../../components/analytics/Analytics", () => ({ - gaEventTracker: vi.fn(), -})); -vi.mock("react-use", () => ({ - useWindowSize: vi.fn(), -})); -vi.mock("@sentry/react", () => ({ - captureException: vi.fn(), -})); -vi.mock("../../data/2023.json", () => { - return { - default: { - hideSpeakers: false, - edition: "2023", - title: "DevBcn", - cfp: { - startDay: "2022-11-01T00:00:00", - endDay: "2023-03-15T00:00:00", - link: "https://sessionize.com/devbcn23/", - }, - }, - }; -}); - -import React from "react"; -import { screen } from "@testing-library/react"; -import Speakers2023 from "./Speakers2023"; -import { - createMockSpeakers, - renderWithRouterAndQueryClient, -} from "../../utils/testing/speakerTestUtils"; -import { useFetchSpeakers } from "../../hooks/useFetchSpeakers"; -import userEvent from "@testing-library/user-event"; -import { gaEventTracker } from "../../components/analytics/Analytics"; -import { useWindowSize } from "react-use"; - -const mockedUseFetchSpeakers = useFetchSpeakers as jest.MockedFunction< - typeof useFetchSpeakers ->; - -describe("Speakers2023 component", () => { - beforeEach(() => { - vi.clearAllMocks(); - vi.mocked(useWindowSize).mockReturnValue({ width: 1200 }); - }); - - it("displays loading state when data is being fetched", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - expect(screen.getByText("Loading...")).toBeInTheDocument(); - }); - - it("displays speakers when data is loaded", () => { - const mockSpeakers = createMockSpeakers(3); - - // Mock the hook to return success state with data - mockedUseFetchSpeakers.mockReturnValue({ - data: mockSpeakers, - isLoading: false, - error: null, - isSuccess: true, - }); - - renderWithRouterAndQueryClient(); - - // Check that each speaker's name is displayed - mockSpeakers.forEach((speaker) => { - expect(screen.getByText(speaker.fullName)).toBeInTheDocument(); - }); - }); - - it("displays a message when no speakers are available", () => { - // Mock the hook to return success state with empty data - mockedUseFetchSpeakers.mockReturnValue({ - data: [], - isLoading: false, - error: null, - isSuccess: true, - }); - - renderWithRouterAndQueryClient(); - - expect(screen.getByText(/No selected speakers yet/i)).toBeInTheDocument(); - }); - - it.skip("displays CFP button when current date is within CFP period", () => { - // Mock the hook to return success state with data - mockedUseFetchSpeakers.mockReturnValue({ - data: [], - isLoading: false, - error: null, - isSuccess: true, - }); - - // Mock Date.now to return a date within the CFP period - const originalDate = Date; - global.Date = class extends Date { - constructor() { - super(); - } - - static now() { - return new Date("2023-01-15").getTime(); - } - } as typeof Date; - - renderWithRouterAndQueryClient(); - - const cfpButton = screen.getByText(/Apply to be a Speaker/i); - expect(cfpButton).toBeInTheDocument(); - - // Restore original Date - global.Date = originalDate; - }); - - it.skip("tracks CFP button clicks", async () => { - // Mock the hook to return success state with data - mockedUseFetchSpeakers.mockReturnValue({ - data: [], - isLoading: false, - error: null, - isSuccess: true, - }); - - // Mock Date.now to return a date within the CFP period - const originalDate = Date; - global.Date = class extends Date { - constructor() { - super(); - } - - static now() { - return new Date("2023-01-15").getTime(); - } - } as typeof Date; - - renderWithRouterAndQueryClient(); - - const cfpButton = screen.getByText(/Apply to be a Speaker/i); - await userEvent.click(cfpButton); - - expect(gaEventTracker).toHaveBeenCalledWith("CFP", "CFP"); - - // Restore original Date - global.Date = originalDate; - }); - - it("calls useFetchSpeakers with the correct year", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - // Verify that useFetchSpeakers was called with "2023" - expect(mockedUseFetchSpeakers).toHaveBeenCalledWith("2023"); - }); - - it("sets the document title correctly", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - // Verify that document.title was set correctly - expect(document.title).toContain("Speakers2023"); - expect(document.title).toContain("2023"); - }); - - it.skip("handles errors correctly", () => { - // Mock the hook to return error state - const error = new Error("Failed to fetch speakers"); - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: false, - error, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - // Note: We're skipping the verification that Sentry.captureException was called - // because it's difficult to properly mock and spy on it in Vitest - }); -}); diff --git a/src/2023/Speakers/Speakers2023.tsx b/src/2023/Speakers/Speakers2023.tsx deleted file mode 100644 index 73b110039..000000000 --- a/src/2023/Speakers/Speakers2023.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { MOBILE_BREAKPOINT } from "@constants/BreakPoints"; -import { Color } from "@styles/colors"; -import { FC, useCallback, useEffect } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import TitleSection from "@components/SectionTitle/TitleSection"; -import { useWindowSize } from "react-use"; -import { - SpeakersCardsContainer, - StyledContainerLeftSlash, - StyledContainerRightSlash, - StyledLessIcon, - StyledMoreIcon, - StyledSlash, - StyledSpeakersSection, - StyledWaveContainer, -} from "./Speakers.style"; -import webData from "@data/2023.json"; -import Button from "@components/UI/Button"; -import { gaEventTracker } from "@components/analytics/Analytics"; -import { useFetchSpeakers } from "@hooks/useFetchSpeakers"; -import { ISpeaker } from "@/types/speakers"; -import { SpeakerCard } from "@views/Speakers/components/SpeakersCard"; -import { useSentryErrorReport } from "@hooks/useSentryErrorReport"; - -const LessThanGreaterThan = () => ( - <> - - - -); - -const Speakers2023: FC> = () => { - const { width } = useWindowSize(); - const today = new Date(); - const isBetween = (startDay: Date, endDay: Date): boolean => - startDay < new Date() && endDay > today; - - const { error, data, isLoading } = useFetchSpeakers("2023"); - - useSentryErrorReport(error); - - const trackCFP = useCallback(() => { - gaEventTracker("CFP", "CFP"); - }, []); - - useEffect(() => { - document.title = `Speakers2023 - DevBcn ${webData.edition}`; - }); - - const CFPStartDay = new Date(webData.cfp.startDay); - const CFPEndDay = new Date(webData.cfp.endDay); - return ( - <> - - - - {width > MOBILE_BREAKPOINT && } - - {isLoading &&

Loading...

} - {isBetween(CFPStartDay, CFPEndDay) && ( -
-
- )} - {data?.length === 0 && ( -

- No selected speakers yet. Keep in touch in our social media for - upcoming announcements -

- )} - {data?.map((speaker: ISpeaker) => ( - - ))} -
- - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - -
-
- - - - - - - ); -}; - -export default Speakers2023; diff --git a/src/2023/Speakers/SpeakersWrapper.tsx b/src/2023/Speakers/SpeakersWrapper.tsx new file mode 100644 index 000000000..bc7bc56f4 --- /dev/null +++ b/src/2023/Speakers/SpeakersWrapper.tsx @@ -0,0 +1,9 @@ +import React, { FC } from "react"; +import Speakers from "@views/Speakers/Speakers"; +import data2023 from "@data/2023.json"; + +export const SpeakersWrapper: FC = () => { + return ; +}; + +export default SpeakersWrapper; diff --git a/src/2024/Cfp/CfpSection2024.tsx b/src/2024/Cfp/CfpSection2024.tsx deleted file mode 100644 index 81e05d7b5..000000000 --- a/src/2024/Cfp/CfpSection2024.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { FC } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import { Color } from "@styles/colors"; -import { - StyledLessIcon, - StyledMoreIcon, - StyledSpeakersSection, -} from "../Speakers/Speakers.style"; -import TitleSection from "@components/SectionTitle/TitleSection"; -import { MOBILE_BREAKPOINT } from "@constants/BreakPoints"; -import { useWindowSize } from "react-use"; -import TwitterIcon from "@components/Icons/Twitter"; -import LinkedinIcon from "@components/Icons/Linkedin"; - -import conferenceData from "@data/2024.json"; -import { CfpTrackProps, data } from "./CfpData"; -import { MemberName, TrackName } from "./Cfp.style"; -import { - StyledAboutImage, - StyledSocialIconsWrapper, -} from "@views/About/components/Style.AboutCard"; -import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; - -export const CfpTrackComponent: FC> = ({ - track, -}) => ( - <> -
- {track.name} -
-
- {track.members.map((member) => { - return ( -
- {member.photo && ( -
- - {member.name} - - {member.twitter && ( - - )} - {member.linkedIn && ( - - )} - -
- )} -
- ); - })} -
- -); - -const CfpSection2024: FC> = () => { - const { width } = useWindowSize(); - useDocumentTitleUpdater("CFP Committee", conferenceData.edition); - return ( - <> - - - - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - {data.map((track) => ( - - ))} - -
 
- - ); -}; - -export default CfpSection2024; diff --git a/src/2024/Cfp/CfpSectionWrapper.tsx b/src/2024/Cfp/CfpSectionWrapper.tsx new file mode 100644 index 000000000..d106ce236 --- /dev/null +++ b/src/2024/Cfp/CfpSectionWrapper.tsx @@ -0,0 +1,10 @@ +import React, { FC } from "react"; +import CfpSection from "@views/Cfp/CfpSection"; +import data2024 from "@data/2024.json"; +import { data } from "./CfpData"; + +export const CfpSectionWrapper: FC = () => { + return ; +}; + +export default CfpSectionWrapper; diff --git a/src/2024/SpeakerDetail/SpeakerDetail.tsx b/src/2024/SpeakerDetail/SpeakerDetail.tsx deleted file mode 100644 index 87acfa4fc..000000000 --- a/src/2024/SpeakerDetail/SpeakerDetail.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { BIG_BREAKPOINT } from "@constants/BreakPoints"; - -import { FC, Suspense } from "react"; -import { useWindowSize } from "react-use"; - -import { ROUTE_2024_SPEAKERS, ROUTE_2024_TALK_DETAIL } from "@constants/routes"; -import { Link } from "react-router"; -import { Color } from "@styles/colors"; -import conferenceData from "@data/2024.json"; -import { - StyledDetailsContainer, - StyledFlexCol, - StyledImageContainer, - StyledInfoContainer, - StyledLink, - StyledMoreThanIcon, - StyledMoreThanIconContainer, - StyledName, - StyledNameContainer, - StyledRightContainer, - StyledSlashes, - StyledSocialMediaContainer, - StyledSocialMediaIcon, - StyledSpeakerDescription, - StyledSpeakerDetailContainer, - StyledSpeakerImg, - StyledSpeakerTitle, -} from "@views/SpeakerDetail/Speaker.style"; -import { StyledTalkDescription } from "@views/SpeakerDetail/SpeakerDetail.style"; -import { ISpeaker } from "@/types/speakers"; -import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; - -interface ISpeakerDetailProps { - speaker: ISpeaker; -} - -const SpeakerDetail: FC> = ({ - speaker, -}) => { - const { width } = useWindowSize(); - - useDocumentTitleUpdater(speaker.fullName, conferenceData.edition); - - const hasSessions = (): boolean => - (speaker.sessions && speaker.sessions.length > 0) || false; - - return ( - - - {width > BIG_BREAKPOINT && ( - - loading

}> - -
- - {speaker.twitterUrl && ( - - - - )} - {speaker.linkedInUrl && ( - - - - )} - -
- )} - - - {speaker.fullName} - {width < BIG_BREAKPOINT && ( - <> - loading

}> - -
- - {speaker.twitterUrl && ( - - - - )} - {speaker.linkedInUrl && ( - - - - )} - - - )} - -
- - - {speaker.tagLine} - {speaker.bio} - - {hasSessions() && ( - <> -

Sessions

-
    - {speaker?.sessions?.map((session) => ( -
  • - - - session - {session.name} - - -
  • - ))} -
- - )} - - - Go back - -
- - - -
-
-
-
- ); -}; - -export default SpeakerDetail; diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx index ee5aac8b2..25fbaaf3f 100644 --- a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx +++ b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx @@ -1,8 +1,12 @@ import { Color } from "@styles/colors"; +import { + ROUTE_2024_SPEAKERS, + ROUTE_2024_TALK_DETAIL, +} from "@constants/routes"; import React, { FC } from "react"; import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import SpeakerDetail from "./SpeakerDetail"; +import SpeakerDetail from "@views/SpeakerDetail/SpeakerDetail"; import { useParams } from "react-router"; import conferenceData from "@data/2024.json"; import { useFetchSpeakers } from "@hooks/useFetchSpeakers"; @@ -28,7 +32,12 @@ export const SpeakerDetailContainer2024: FC< {isLoading &&

Loading

} {!isLoading && data && data.length > 0 ? ( - + ) : ( "not found" )} diff --git a/src/2024/Speakers/Speakers2024.test.tsx b/src/2024/Speakers/Speakers2024.test.tsx deleted file mode 100644 index 0b578eef7..000000000 --- a/src/2024/Speakers/Speakers2024.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from "react"; -import { screen } from "@testing-library/react"; -import Speakers2024 from "./Speakers2024"; -import { - createMockSpeakers, - renderWithRouterAndQueryClient, -} from "@utils/testing/speakerTestUtils"; -import { useFetchSpeakers } from "@hooks/useFetchSpeakers"; -import { useWindowSize } from "react-use"; -import { type MockedFunction, vi } from "vitest"; - -vi.mock("@hooks/useFetchSpeakers"); - -vi.mock("@components/analytics/Analytics", () => ({ - gaEventTracker: vi.fn(), -})); -vi.mock("react-use", () => ({ - useWindowSize: vi.fn(), -})); -vi.mock("@sentry/react", () => ({ - captureException: vi.fn(), -})); -vi.mock("@data/2024.json", () => { - return { - default: { - hideSpeakers: false, - edition: "2024", - title: "DevBcn", - cfp: { - startDay: "2023-01-01T00:00:00", - endDay: "2023-02-01T00:00:00", - link: "https://example.com/cfp", - }, - }, - }; -}); - -const mockedUseFetchSpeakers = useFetchSpeakers as MockedFunction< - typeof useFetchSpeakers ->; - -describe("Speakers2024 component", () => { - beforeEach(() => { - vi.clearAllMocks(); - (useWindowSize as MockedFunction).mockReturnValue({ - width: 1200, - height: 0, - }); - }); - - it("displays loading state when data is being fetched", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - expect(screen.getByText("Loading...")).toBeInTheDocument(); - }); - - it("displays speakers when data is loaded", () => { - const mockSpeakers = createMockSpeakers(3); - - // Mock the hook to return success state with data - mockedUseFetchSpeakers.mockReturnValue({ - data: mockSpeakers, - isLoading: false, - error: null, - isSuccess: true, - }); - - renderWithRouterAndQueryClient(); - - // Check that each speaker's name is displayed - mockSpeakers.forEach((speaker) => { - expect(screen.getByText(speaker.fullName)).toBeInTheDocument(); - }); - }); - - it("calls useFetchSpeakers with the correct year", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - // Verify that useFetchSpeakers was called with "2024" - expect(mockedUseFetchSpeakers).toHaveBeenCalledWith("2024"); - }); - - it("sets the document title correctly", () => { - // Mock the hook to return loading state - mockedUseFetchSpeakers.mockReturnValue({ - data: null, - isLoading: true, - error: null, - isSuccess: false, - }); - - renderWithRouterAndQueryClient(); - - // Verify that document.title was set correctly - expect(document.title).toContain("Speakers"); - expect(document.title).toContain("2024"); - }); -}); diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx deleted file mode 100644 index 83a0fae83..000000000 --- a/src/2024/Speakers/Speakers2024.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { MOBILE_BREAKPOINT } from "@constants/BreakPoints"; -import { Color } from "@styles/colors"; -import React, { FC, useCallback, useEffect } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import TitleSection from "@components/SectionTitle/TitleSection"; -import { useWindowSize } from "react-use"; -import { - SpeakersCardsContainer, - StyledContainerLeftSlash, - StyledContainerRightSlash, - StyledLessIcon, - StyledMoreIcon, - StyledSlash, - StyledSpeakersSection, - StyledWaveContainer, -} from "./Speakers.style"; -import webData from "@data/2024.json"; -import Button from "@components/UI/Button"; -import { gaEventTracker } from "@components/analytics/Analytics"; -import { useFetchSpeakers } from "@hooks/useFetchSpeakers"; -import { SpeakerCard } from "@views/Speakers/components/SpeakersCard"; -import { ISpeaker } from "@/types/speakers"; -import { useSentryErrorReport } from "@hooks/useSentryErrorReport"; - -const LessThanGreaterThan = () => ( - <> - - - -); - -const Speakers2024: FC> = () => { - const { width } = useWindowSize(); - const today = new Date(); - const isBetween = (startDay: Date, endDay: Date): boolean => - startDay < new Date() && endDay > today; - - const { error, data, isLoading } = useFetchSpeakers("2024"); - - useSentryErrorReport(error); - - const trackCFP = useCallback(() => { - gaEventTracker("CFP", "CFP"); - }, []); - - useEffect(() => { - document.title = `Speakers — ${webData.title} — ${webData.edition}`; - }); - - const CFPStartDay = new Date(webData.cfp.startDay); - const CFPEndDay = new Date(webData.cfp.endDay); - return ( - <> - - - - {width > MOBILE_BREAKPOINT && } - - {isLoading &&

Loading...

} - {isBetween(CFPStartDay, CFPEndDay) && ( -
-
- )} - {webData.hideSpeakers ? ( -

- No selected speakers yet. Keep in touch in our social media for - upcoming announcements -

- ) : ( - data?.map((speaker: ISpeaker) => ( - - )) - )} -
- - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - /{" "} - - -
-
- - - - - - - ); -}; - -export default Speakers2024; diff --git a/src/2024/Speakers/SpeakersWrapper.tsx b/src/2024/Speakers/SpeakersWrapper.tsx new file mode 100644 index 000000000..ab11dc502 --- /dev/null +++ b/src/2024/Speakers/SpeakersWrapper.tsx @@ -0,0 +1,9 @@ +import React, { FC } from "react"; +import Speakers from "@views/Speakers/Speakers"; +import data2024 from "@data/2024.json"; + +export const SpeakersWrapper: FC = () => { + return ; +}; + +export default SpeakersWrapper; diff --git a/src/2024/TalkDetail/MeetingDetail.tsx b/src/2024/TalkDetail/MeetingDetail.tsx deleted file mode 100644 index 608acf9f6..000000000 --- a/src/2024/TalkDetail/MeetingDetail.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { - BIG_BREAKPOINT, - LARGE_BREAKPOINT, - MOBILE_BREAKPOINT, -} from "@constants/BreakPoints"; -import { Color } from "@styles/colors"; -import React, { FC, Suspense } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import { useWindowSize } from "react-use"; - -import { Link } from "react-router"; -import { ROUTE_2024_SPEAKER_DETAIL, ROUTE_2024_TALKS } from "@constants/routes"; -import conferenceData from "@data/2024.json"; -import { Tag } from "@components/Tag/Tag"; -import { styled } from "styled-components"; -import { AddToCalendarButton } from "add-to-calendar-button-react"; -import { - StyledContainer, - StyledDetailsContainer, - StyledFlexCol, - StyledName, - StyledNameContainer, - StyledRightContainer, - StyledSpeakerDetailContainer, -} from "@views/SpeakerDetail/Speaker.style"; -import { - StyledDescription, - StyledExtraInfo, - StyledLessThan, - StyledMeetingTitleContainer, - StyledTitleImg, - StyledVideoContainer, - StyledVideoTagsContainer, -} from "@views/MeetingDetail/Style.MeetingDetail"; -import { StyledTitle } from "../Home/Style.Home"; -import { ISpeaker } from "@/types/speakers"; -import { IMeeting } from "@/types/sessions"; -import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; - -const getVideoHeight = (windowWidth: number) => { - let videoHeight; - if (windowWidth < MOBILE_BREAKPOINT) { - videoHeight = 250; - } else if (windowWidth >= MOBILE_BREAKPOINT && windowWidth < BIG_BREAKPOINT) { - videoHeight = 300; - } else if (windowWidth >= BIG_BREAKPOINT && windowWidth < LARGE_BREAKPOINT) { - videoHeight = 450; - } else { - videoHeight = 600; - } - - return videoHeight.toString(); -}; - -const leftVariants = { - initial: { - x: -100, - opacity: 0, - }, - animate: { - x: 0, - opacity: 1, - }, -}; - -const rightVariants = { - initial: { - x: 100, - opacity: 0, - }, - animate: { - x: 0, - opacity: 1, - }, -}; - -const downVariants = { - initial: { - y: 100, - opacity: 0, - }, - animate: { - y: 0, - opacity: 1, - }, -}; - -const opacityVariants = { - initial: { - opacity: 0, - }, - animate: { - opacity: 1, - transition: { - duration: 1, - }, - }, -}; - -export const StyledVoteTalkLink = styled.a` - text-decoration: none; - color: ${Color.BLACK_BLUE}; - font-size: 0.8rem; -`; - -interface IMeetingDetailProps { - meeting: IMeeting; - speakers?: ISpeaker[]; -} - -type MyType = { - urlName?: string; - videoUrl?: string; - level?: string; - videoTags?: string[]; - speakers?: ISpeaker[]; - description: string; - language?: string; - title: string; - type?: string; - track?: string; -}; - -const MeetingDetail: FC> = ({ - meeting, - speakers: mySpeakers, -}) => { - const { width } = useWindowSize(); - - useDocumentTitleUpdater(meeting.title, conferenceData.edition); - - const finalMeetingInfo: MyType = { - ...meeting, - speakers: mySpeakers, - }; - - return ( - - - - - - / {meeting.title} -

Description

- {meeting.description} - - {`${meeting.type} ${meeting.level}`} - Track: - {meeting.track} - - {meeting.slidesURL !== "" && ( -

- - - - {" "} - Slides - -

- )} -
-
- -
- - {meeting.videoUrl && ( - - )} - - {meeting.videoTags?.map((tag) => )} - -
- - 🗳️ Vote this talk - - -
-
- - - - - {finalMeetingInfo.speakers?.map((speaker) => ( - - loading}> - {speaker.fullName} - - - - {speaker.fullName} - - - - ))} - - - - -
- - Go back - {" "} -
-
-
- ); -}; - -export default MeetingDetail; diff --git a/src/2024/TalkDetail/MeetingDetailContainer2024.tsx b/src/2024/TalkDetail/MeetingDetailContainer2024.tsx index 04ca59591..63468fc0b 100644 --- a/src/2024/TalkDetail/MeetingDetailContainer2024.tsx +++ b/src/2024/TalkDetail/MeetingDetailContainer2024.tsx @@ -1,4 +1,8 @@ import { Color } from "@styles/colors"; +import { + ROUTE_2024_SPEAKER_DETAIL, + ROUTE_2024_TALKS, +} from "@constants/routes"; import React, { FC, useEffect } from "react"; import { NotFoundError } from "@components/NotFoundError/NotFoundError"; import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; @@ -7,7 +11,7 @@ import { useParams } from "react-router"; import conferenceData from "@data/2024.json"; import { useFetchTalksById } from "@hooks/useFetchTalks"; import { useFetchSpeakers } from "@hooks/useFetchSpeakers"; -import MeetingDetail from "./MeetingDetail"; +import MeetingDetail from "@views/MeetingDetail/MeetingDetail"; import { ISpeaker } from "@/types/speakers"; import { Session } from "@/types/sessions"; @@ -53,6 +57,10 @@ export const MeetingDetailContainer2024: FC< )} {!isLoading && diff --git a/src/2024/Talks/Talks2024.tsx b/src/2024/Talks/Talks2024.tsx deleted file mode 100644 index f71381ee4..000000000 --- a/src/2024/Talks/Talks2024.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { FC, useEffect, useState } from "react"; -import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; -import TitleSection from "@components/SectionTitle/TitleSection"; -import { Color } from "@styles/colors"; -import conferenceData from "@data/2024.json"; - -import { useFetchTalks } from "@hooks/useFetchTalks"; -import { Dropdown, DropdownChangeEvent } from "primereact/dropdown"; -import "primereact/resources/themes/lara-light-indigo/theme.css"; -import "@styles/theme.css"; -import { - StyledMarginBottom, - StyledSpeakersSection, - StyledTitleContainer, - StyledTitleIcon, - StyledWaveContainer, -} from "@views/Talks/Talks.style"; -import TrackInformation from "@components/common/TrackInformation"; -import { useSentryErrorReport } from "@hooks/useSentryErrorReport"; - -interface TrackInfo { - name: string; - code?: string; -} - -const Talks2024: FC> = () => { - const [selectedGroupId, setSelectedGroupId] = useState( - null, - ); - const { isLoading, error, data } = useFetchTalks("2024"); - - useEffect(() => { - const sessionSelectedGroupCode = - sessionStorage.getItem("selectedGroupCode"); - const sessionSelectedGroupName = - sessionStorage.getItem("selectedGroupName"); - - document.title = `Talks - ${conferenceData.title} - ${conferenceData.edition}`; - - if (sessionSelectedGroupCode && sessionSelectedGroupName) { - setSelectedGroupId({ - name: sessionSelectedGroupName, - code: sessionSelectedGroupCode, - }); - } - }, []); - - useSentryErrorReport(error); - - // Helper function to remove text between parentheses - const removeParenthesesContent = (text: string): string => { - return text.replace(/\s*\([^)]*\)/g, ""); - }; - - const dropDownOptions = [ - { name: "All Tracks", code: undefined }, - ...(data !== undefined - ? data.flatMap((group) => ({ - code: group.groupId.toString(), - name: removeParenthesesContent(group.groupName), - })) - : []), - ]; - - const filteredTalks = selectedGroupId?.code - ? data?.filter((talk) => talk.groupId.toString() === selectedGroupId.code) - : data; - - const onChangeSelectedTrack = (e: DropdownChangeEvent) => { - const value = e.value; - setSelectedGroupId(value || null); - sessionStorage.setItem("selectedGroupCode", value?.code || ""); - sessionStorage.setItem("selectedGroupName", value?.name || ""); - }; - return ( - <> - - - - - - - - - - - - - - - -
- {isLoading &&

Loading

} - {conferenceData.hideTalks ? ( -

- No talks selected yet. Keep in touch in our social media for - upcoming announcements -

- ) : ( - filteredTalks && - Array.isArray(filteredTalks) && ( - <> -
- - -
- {filteredTalks.map((track) => ( - - ))} - - ) - )} -
- -
- - ); -}; - -export default Talks2024; diff --git a/src/2024/Talks/TalksWrapper.tsx b/src/2024/Talks/TalksWrapper.tsx new file mode 100644 index 000000000..471d5a692 --- /dev/null +++ b/src/2024/Talks/TalksWrapper.tsx @@ -0,0 +1,110 @@ +import React, { FC } from "react"; +import Talks from "@views/Talks/Talks"; +import data2024 from "@data/2024.json"; +import { TopRatedTalk, TopTalkWithSpeaker } from "@/types/sessions"; +import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes"; + +const topTenTalks: Array = [ + { + id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", + speaker: "Victor Rentea", + talk: "Top 10 Rest API Design Falls", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), + }, + { + id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", + speaker: "Laura Perea", + talk: "GenAI among us", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), + }, + { + id: "eb3852c1-acf8-42a6-988d-365fad2a5668", + speaker: "Brian Vermeer", + talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"), + }, + { + id: "625b53c9-edea-4e47-a5ba-2ee661c539e3", + speaker: "Álvaro Sánchez-Mariscal", + talk: "Revealing the magic behind Java annotations", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"), + }, + { + id: "7b1c534c-39a5-4398-93e5-626010f00198", + speaker: "Alexander Chatzizacharias", + talk: "What is multimodal RAG, and can we build a village with it?", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"), + }, + { + id: "ebab2b92-503f-4baa-b3ab-064865853223", + speaker: "Bert Jan Schrijver", + talk: "Generic or Specific? Making sensible software design decisions", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"), + }, + { + id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", + speaker: "Marc Nuri", + talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), + }, + { + id: "10937eaf-a0da-48c9-82d6-8711ca26fb16", + speaker: "Andres Almiray", + talk: "Maven Productivity Tips", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"), + }, + { + id: "5ce27637-12b4-4dfe-830d-166d88c837ad", + speaker: "Milen Dyankov", + talk: "AI for Java Developers - From Buzzword to Code", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"), + }, + { + id: "2aea7252-6822-4f42-a9d4-fa830f29df40", + speaker: "Rijo Sam", + talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"), + }, +]; + +const topThreeTalks: Array = [ + { + id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", + award: "Funniest talk", + speaker: "Victor Rentea", + speakerImage: + "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png", + talk: "Top 10 Rest API Design Falls", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), + }, + { + id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", + speaker: "Laura Perea", + award: "Best Rated", + speakerImage: + "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png", + talk: "GenAI among us", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), + }, + { + id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", + speaker: "Marc Nuri", + award: "Most original", + speakerImage: + "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg", + talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), + }, +]; + +export const TalksWrapper: FC = () => { + return ( + + ); +}; + +export default TalksWrapper; diff --git a/src/components/YearSpecific/Speakers/Speakers2023.tsx b/src/components/YearSpecific/Speakers/Speakers2023.tsx deleted file mode 100644 index 056e9fc0c..000000000 --- a/src/components/YearSpecific/Speakers/Speakers2023.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, { FC } from "react"; -import Speakers from "./Speakers"; -import webData from "../../../data/2023.json"; - -const Speakers2023: FC> = () => { - return ; -}; - -export default Speakers2023; \ No newline at end of file diff --git a/src/components/YearSpecific/Speakers/Speakers2024.tsx b/src/components/YearSpecific/Speakers/Speakers2024.tsx deleted file mode 100644 index fd7e87207..000000000 --- a/src/components/YearSpecific/Speakers/Speakers2024.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React, { FC } from "react"; -import Speakers from "./Speakers"; -import webData from "@data/2024.json"; - -export const Speakers2024: FC> = () => { - return ; -}; diff --git a/src/types/sessions.ts b/src/types/sessions.ts index 4b89f0f4c..18afaf16c 100644 --- a/src/types/sessions.ts +++ b/src/types/sessions.ts @@ -76,6 +76,26 @@ export interface IMeetingDetailProps { meeting: IMeeting; speakers?: ISpeaker[]; openFeedbackId: string; + edition?: string; + speakerDetailRoute?: string; + talksRoute?: string; +} + +export interface TrackInfo { + name: string; + code?: string; +} + +export interface TopRatedTalk { + id: string; + speaker: string; + talk: string; + link: string; +} + +export interface TopTalkWithSpeaker extends TopRatedTalk { + speakerImage: string; + award: string; } export type MyType = { diff --git a/src/utils/lazyComponents.ts b/src/utils/lazyComponents.ts index 3399616af..8ffc078e6 100644 --- a/src/utils/lazyComponents.ts +++ b/src/utils/lazyComponents.ts @@ -126,8 +126,8 @@ export const HomeWrapper2024 = lazy(() => })) ); export const Speakers2024 = lazy(() => - import("../components/YearSpecific/Speakers/Speakers2024").then((value) => ({ - default: value.Speakers2024, + import("../2024/Speakers/SpeakersWrapper").then((value) => ({ + default: value.SpeakersWrapper, })) ); export const SpeakerDetailContainer2024 = lazy(() => @@ -135,8 +135,10 @@ export const SpeakerDetailContainer2024 = lazy(() => default: value.SpeakerDetailContainer2024, })) ); -export const CfpSection2024 = lazy(() => import("../2024/Cfp/CfpSection2024")); -export const Talks2024 = lazy(() => import("../2024/Talks/Talks2024")); +export const CfpSection2024 = lazy(() => + import("../2024/Cfp/CfpSectionWrapper") +); +export const Talks2024 = lazy(() => import("../2024/Talks/TalksWrapper")); export const Schedule2024 = lazy(() => import("../2024/Schedule/Schedule2024")); export const JobOffers2024 = lazy(() => import("../2024/JobOffers/JobOffers2024")); export const MeetingDetailContainer2024 = lazy(() => @@ -154,8 +156,8 @@ export const Diversity2023 = lazy(() => import("../2023/Diversity/Diversity2023" export const Schedule2023 = lazy(() => import("../2023/Schedule/Schedule2023")); export const Workshops2023 = lazy(() => import("../2023/Workshops/Workshops2023")); export const JobOffers2023 = lazy(() => import("../2023/JobOffers/JobOffers2023")); -export const CfpSection2023 = lazy(() => import("../2023/Cfp/CfpSection2023")); -export const Speakers2023 = lazy(() => import("../2023/Speakers/Speakers2023")); +export const CfpSection2023 = lazy(() => import("../2023/Cfp/CfpSectionWrapper")); +export const Speakers2023 = lazy(() => import("../2023/Speakers/SpeakersWrapper")); export const SpeakerDetailContainer2023 = lazy( () => import("../2023/SpeakerDetail/SpeakerDetailContainer2023") ); diff --git a/src/views/Cfp/CfpData.ts b/src/views/Cfp/CfpData.ts index 8492a1548..3fbeb5509 100644 --- a/src/views/Cfp/CfpData.ts +++ b/src/views/Cfp/CfpData.ts @@ -1,17 +1,17 @@ -interface CFpTrack { - id: string; - name: string; - members: CfpMember[]; +export interface CfpTrack { + id: string; + name: string; + members: CfpMember[]; } interface CfpMember { - name: string; - photo?: string; - linkedIn?: string; - twitter?: string; + name: string; + photo?: string; + linkedIn?: string; + twitter?: string; } -export const data: CFpTrack[] = [ +export const data: CfpTrack[] = [ { name: "Java & JVM", id: "656fece2-9447-4dbe-8a78-8dc6aa7124f2", @@ -154,5 +154,5 @@ export const data: CFpTrack[] = [ ]; export interface CfpTrackProps { - track: CFpTrack; + track: CfpTrack; } diff --git a/src/views/Cfp/CfpSection.tsx b/src/views/Cfp/CfpSection.tsx index 2d18aab0e..86e2c2826 100644 --- a/src/views/Cfp/CfpSection.tsx +++ b/src/views/Cfp/CfpSection.tsx @@ -16,7 +16,7 @@ import { StyledSocialIconsWrapper, } from "../About/components/Style.AboutCard"; import conferenceData from "@data/2026.json"; -import { CfpTrackProps, data } from "./CfpData"; +import { CfpTrack, CfpTrackProps, data } from "./CfpData"; import { MemberName, TrackName } from "./Cfp.style"; import { useDocumentTitleUpdater } from "@hooks/useDocumentTitleUpdate"; @@ -58,13 +58,21 @@ export const CfpTrackComponent: FC> = ({ ); -const CfpSection: FC> = () => { +interface CfpSectionProps { + conferenceConfig?: typeof conferenceData; + cfpData?: CfpTrack[]; +} + +const CfpSection: FC> = ({ + conferenceConfig = conferenceData, + cfpData = data, +}) => { const { width } = useWindowSize(); - useDocumentTitleUpdater("CFP Committee", conferenceData.edition); + useDocumentTitleUpdater("CFP Committee", conferenceConfig.edition); const isCFPCommitteeReady = (): boolean => - data.every((track) => track.members.length > 0); + cfpData.every((track) => track.members.length > 0); return ( <> @@ -89,7 +97,7 @@ const CfpSection: FC> = () => {

CFP Committee in progress

)} {isCFPCommitteeReady() && - data.map((track) => ( + cfpData.map((track) => ( ))}
diff --git a/src/views/MeetingDetail/MeetingDetail.tsx b/src/views/MeetingDetail/MeetingDetail.tsx index d5ed62d99..69be9c800 100644 --- a/src/views/MeetingDetail/MeetingDetail.tsx +++ b/src/views/MeetingDetail/MeetingDetail.tsx @@ -103,10 +103,13 @@ const MeetingDetail: FC> = ({ meeting, speakers: mySpeakers, openFeedbackId, + edition = conferenceData.edition, + speakerDetailRoute = ROUTE_SPEAKER_DETAIL, + talksRoute = ROUTE_TALKS, }) => { const { width } = useWindowSize(); - useDocumentTitleUpdater(meeting.title, conferenceData.edition); + useDocumentTitleUpdater(meeting.title, edition); const finalMeetingInfo: MyType = { ...meeting, @@ -242,7 +245,7 @@ const MeetingDetail: FC> = ({ /> - + {speaker.fullName} @@ -254,7 +257,7 @@ const MeetingDetail: FC> = ({
> = ({ speaker, + edition = conferenceData.edition, + speakersRoute = ROUTE_SPEAKERS, + talkDetailRoute = ROUTE_TALK_DETAIL, }) => { const { width } = useWindowSize(); useEffect(() => { - document.title = `${speaker.fullName} — ${conferenceData.title} — ${conferenceData.edition}`; - }, [speaker.fullName]); + document.title = `${speaker.fullName} — ${conferenceData.title} — ${edition}`; + }, [speaker.fullName, edition]); const hasSessions = (): boolean => (speaker.sessions && speaker.sessions.length > 0) || false; @@ -124,7 +130,7 @@ const SpeakerDetail: FC> = ({ {speaker?.sessions?.map((session) => (
  • > = ({ )} ( ); -const Speakers: FC> = () => { +interface SpeakersProps { + conferenceConfig?: typeof webData; +} + +const Speakers: FC> = ({ + conferenceConfig = webData, +}) => { const { width } = useWindowSize(); const today = new Date(); const isBetween = (startDay: Date, endDay: Date): boolean => startDay < new Date() && endDay > today; - const { error, data, isLoading } = useFetchSpeakers(); + const { error, data, isLoading } = useFetchSpeakers(conferenceConfig.edition); useSentryErrorReport(error); @@ -44,11 +50,11 @@ const Speakers: FC> = () => { }, []); useEffect(() => { - document.title = `Speakers — ${webData.title} — ${webData.edition}`; + document.title = `Speakers — ${conferenceConfig.title} — ${conferenceConfig.edition}`; }); - const CFPStartDay = new Date(webData.cfp.startDay); - const CFPEndDay = new Date(webData.cfp.endDay); + const CFPStartDay = new Date(conferenceConfig.cfp.startDay); + const CFPEndDay = new Date(conferenceConfig.cfp.endDay); return ( <> @@ -75,11 +81,11 @@ const Speakers: FC> = () => {
  • )} - {webData.hideSpeakers ? ( + {conferenceConfig.hideSpeakers ? (

    No selected speakers yet. Keep in touch in our social media for upcoming announcements @@ -89,7 +95,7 @@ const Speakers: FC> = () => { )) )} diff --git a/src/views/Talks/Talks.test.tsx b/src/views/Talks/Talks.test.tsx index 43c14857c..3019c2026 100644 --- a/src/views/Talks/Talks.test.tsx +++ b/src/views/Talks/Talks.test.tsx @@ -7,7 +7,11 @@ import { } from "../../utils/testing/testUtils"; import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes"; import { useFetchTalks } from "@hooks/useFetchTalks"; -import { IGroup } from "@/types/sessions"; +import { + IGroup, + TopRatedTalk, + TopTalkWithSpeaker, +} from "@/types/sessions"; import userEvent from "@testing-library/user-event"; import { vi } from "vitest"; @@ -37,6 +41,99 @@ vi.mock("../../utils/testing/testUtils", async (importOriginal) => { }; }); +const mockTopTenTalks: TopRatedTalk[] = [ + { + id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", + speaker: "Victor Rentea", + talk: "Top 10 Rest API Design Falls", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), + }, + { + id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", + speaker: "Laura Perea", + talk: "GenAI among us", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), + }, + { + id: "eb3852c1-acf8-42a6-988d-365fad2a5668", + speaker: "Brian Vermeer", + talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"), + }, + { + id: "625b53c9-edea-4e47-a5ba-2ee661c539e3", + speaker: "Álvaro Sánchez-Mariscal", + talk: "Revealing the magic behind Java annotations", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"), + }, + { + id: "7b1c534c-39a5-4398-93e5-626010f00198", + speaker: "Alexander Chatzizacharias", + talk: "What is multimodal RAG, and can we build a village with it?", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"), + }, + { + id: "ebab2b92-503f-4baa-b3ab-064865853223", + speaker: "Bert Jan Schrijver", + talk: "Generic or Specific? Making sensible software design decisions", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"), + }, + { + id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", + speaker: "Marc Nuri", + talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), + }, + { + id: "10937eaf-a0da-48c9-82d6-8711ca26fb16", + speaker: "Andres Almiray", + talk: "Maven Productivity Tips", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"), + }, + { + id: "5ce27637-12b4-4dfe-830d-166d88c837ad", + speaker: "Milen Dyankov", + talk: "AI for Java Developers - From Buzzword to Code", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"), + }, + { + id: "2aea7252-6822-4f42-a9d4-fa830f29df40", + speaker: "Rijo Sam", + talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"), + }, +]; + +const mockTopThreeTalks: TopTalkWithSpeaker[] = [ + { + id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", + award: "Funniest talk", + speaker: "Victor Rentea", + speakerImage: + "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png", + talk: "Top 10 Rest API Design Falls", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), + }, + { + id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", + speaker: "Laura Perea", + award: "Best Rated", + speakerImage: + "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png", + talk: "GenAI among us", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), + }, + { + id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", + speaker: "Marc Nuri", + award: "Most original", + speakerImage: + "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg", + talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", + link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), + }, +]; + describe("Talks", () => { beforeEach(() => { // Reset all mocks before each test @@ -106,7 +203,12 @@ describe("Talks", () => { // Tests for the topThreeTalks array it("renders the top three talks section with correct awards", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); // Check for award titles expect(screen.getByText("Funniest talk")).toBeInTheDocument(); @@ -115,7 +217,12 @@ describe("Talks", () => { }); it("renders all top three talks with correct speaker names and talk titles", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); // Check for speaker names expect(screen.getByText("Victor Rentea")).toBeInTheDocument(); @@ -135,7 +242,12 @@ describe("Talks", () => { }); it("renders top three talks with correct images", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); // Check for images with correct src attributes const images = screen.getAllByRole("img"); @@ -173,7 +285,12 @@ describe("Talks", () => { }); it("renders top three talks with correct links", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); // Check that links are correctly formatted const victorLink = screen.getByText("Victor Rentea").closest("a"); @@ -196,12 +313,22 @@ describe("Talks", () => { // Tests for the topTenTalks array it("renders the top ten talks section", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); expect(screen.getByText("🔝 Top Ten rated talks")).toBeInTheDocument(); }); it("renders all top ten talks with correct links", () => { - renderWithQueryClient(); + renderWithQueryClient( + , + ); // Check for specific talks expect( diff --git a/src/views/Talks/Talks.tsx b/src/views/Talks/Talks.tsx index 143145de6..db0f9c04c 100644 --- a/src/views/Talks/Talks.tsx +++ b/src/views/Talks/Talks.tsx @@ -17,30 +17,27 @@ import { SelectButton, SelectButtonChangeEvent } from "primereact/selectbutton"; import "primereact/resources/themes/lara-light-indigo/theme.css"; import "@styles/theme.css"; import { useSentryErrorReport } from "@hooks/useSentryErrorReport"; -import { ROUTE_MEETING_DETAIL_PLAIN } from "@constants/routes"; - -interface TrackInfo { - name: string; - code?: string; -} - -interface TopRatedTalk { - id: string; - speaker: string; - talk: string; - link: string; -} +import { + TopRatedTalk, + TopTalkWithSpeaker, + TrackInfo, +} from "@/types/sessions"; -interface TopTalkWithSpeaker extends TopRatedTalk { - speakerImage: string; - award: string; +interface TalksProps { + conferenceConfig?: typeof conferenceData; + topTenTalks?: Array; + topThreeTalks?: Array; } -const Talks: FC> = () => { +const Talks: FC> = ({ + conferenceConfig = conferenceData, + topTenTalks = [], + topThreeTalks = [], +}) => { const [selectedGroupId, setSelectedGroupId] = useState( null, ); - const { isLoading, error, data } = useFetchTalks(); + const { isLoading, error, data } = useFetchTalks(conferenceConfig.edition); useEffect(() => { const sessionSelectedGroupCode = @@ -48,7 +45,7 @@ const Talks: FC> = () => { const sessionSelectedGroupName = sessionStorage.getItem("selectedGroupName"); - document.title = `Talks - ${conferenceData.title} - ${conferenceData.edition}`; + document.title = `Talks - ${conferenceConfig.title} - ${conferenceConfig.edition}`; if (sessionSelectedGroupCode && sessionSelectedGroupName) { setSelectedGroupId({ @@ -56,7 +53,7 @@ const Talks: FC> = () => { code: sessionSelectedGroupCode, }); } - }, []); + }, [conferenceConfig.title, conferenceConfig.edition]); useSentryErrorReport(error); @@ -68,106 +65,15 @@ const Talks: FC> = () => { { name: "All Tracks", code: undefined }, ...(data !== undefined ? data - .flatMap((group) => ({ - code: group?.groupId?.toString(), - name: removeParenthesesContent(group.groupName), - })) - .sort((a, b) => a.name.localeCompare(b.name)) + .flatMap((group) => ({ + code: group?.groupId?.toString(), + name: removeParenthesesContent(group.groupName), + })) + .sort((a, b) => a.name.localeCompare(b.name)) : []), ]; - const topTenTalks: Array = [ - { - id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", - speaker: "Victor Rentea", - talk: "Top 10 Rest API Design Falls", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), - }, - { - id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", - speaker: "Laura Perea", - talk: "GenAI among us", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), - }, - { - id: "eb3852c1-acf8-42a6-988d-365fad2a5668", - speaker: "Brian Vermeer", - talk: "Don't Get Burned! Secure Coding Essentials in Java to protect your application", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "851481"), - }, - { - id: "625b53c9-edea-4e47-a5ba-2ee661c539e3", - speaker: "Álvaro Sánchez-Mariscal", - talk: "Revealing the magic behind Java annotations", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "843845"), - }, - { - id: "7b1c534c-39a5-4398-93e5-626010f00198", - speaker: "Alexander Chatzizacharias", - talk: "What is multimodal RAG, and can we build a village with it?", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "832774"), - }, - { - id: "ebab2b92-503f-4baa-b3ab-064865853223", - speaker: "Bert Jan Schrijver", - talk: "Generic or Specific? Making sensible software design decisions", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "827688"), - }, - { - id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", - speaker: "Marc Nuri", - talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), - }, - { - id: "10937eaf-a0da-48c9-82d6-8711ca26fb16", - speaker: "Andres Almiray", - talk: "Maven Productivity Tips", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "860854"), - }, - { - id: "5ce27637-12b4-4dfe-830d-166d88c837ad", - speaker: "Milen Dyankov", - talk: "AI for Java Developers - From Buzzword to Code", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "873844"), - }, - { - id: "2aea7252-6822-4f42-a9d4-fa830f29df40", - speaker: "Rijo Sam", - talk: "Java Beyond Frameworks: Avoiding Lock-In with Agnostic Design", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "875233"), - }, - ]; - const topThreeTalks: Array = [ - { - id: "df057475-0b6a-4fab-8e0d-c5576230dd5c", - award: "Funniest talk", - speaker: "Victor Rentea", - speakerImage: - "https://sessionize.com/image/2fde-400o400o1-NVbZAJzrFZpcRjEe5khxjo.png", - talk: "Top 10 Rest API Design Falls", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "838798"), - }, - { - id: "d32cdd87-3c7d-47bb-98ec-b255d1e4b9ba", - speaker: "Laura Perea", - award: "Best Rated", - speakerImage: - "https://sessionize.com/image/8df6-400o400o1-LKJE9Ej5xvBK92FtxJDo6U.png", - talk: "GenAI among us", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "945091"), - }, - { - id: "11554c51-dc18-407b-b7b4-b8ad2f925b2a", - speaker: "Marc Nuri", - award: "Most original", - speakerImage: - "https://sessionize.com/image/3a9a-400o400o1-sJBQfR5Ki5BGPEDG8GQgKM.jpg", - talk: "Model Context Protocol Servers 101: Unlocking the Power of AI", - link: ROUTE_MEETING_DETAIL_PLAIN.replace(":id", "874255"), - }, - ]; const filteredTalks = selectedGroupId?.code ? data?.filter((talk) => talk.groupId.toString() === selectedGroupId.code) @@ -267,7 +173,7 @@ const Talks: FC> = () => {

    {isLoading &&

    Loading

    } - {conferenceData.hideTalks ? ( + {conferenceConfig.hideTalks ? (

    No talks selected yet. Keep in tap in our social media for upcoming announcements @@ -325,8 +231,8 @@ const Talks: FC> = () => { ))} diff --git a/src/views/Workshops/Workshops.tsx b/src/views/Workshops/Workshops.tsx index 06069418c..39dce348e 100644 --- a/src/views/Workshops/Workshops.tsx +++ b/src/views/Workshops/Workshops.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect } from "react"; import { SectionWrapper } from "@components/SectionWrapper/SectionWrapper"; import { useFetchTalks } from "@hooks/useFetchTalks"; -import { TalkCard } from "../Talks/components/TalkCard"; +import { TalkCard } from "@components/common/TalkCard"; import conferenceData from "@data/2025.json"; import { styled } from "styled-components"; import { BIG_BREAKPOINT } from "@constants/BreakPoints"; From 6ecb5ff2813cbe5919db710a442567679435d2d3 Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Sun, 7 Dec 2025 20:45:55 +0100 Subject: [PATCH 11/21] refactor: centralize ActionButtons component and remove redundant tests from year-specific directories. --- src/2023/Cfp/CfpSection2023.test.tsx | 115 ------------------ .../ActionButtons/ActionButtons.tsx | 59 --------- src/2023/Home/components/Home/Home.tsx | 17 ++- src/2024/Cfp/CfpSection.test.tsx | 28 ----- src/2024/Cfp/TrackComponent.test.tsx | 39 ------ src/2024/Home/Home.tsx | 15 ++- src/2024/Home/components/ActionButtons.tsx | 59 --------- src/2024/Talks/Talks.test.tsx | 41 ------- .../ActionButtons/ActionButtons.tsx | 63 ---------- src/2025/Home/components/Home/Home.tsx | 17 ++- src/data/2025.json | 8 +- .../ActionButtons/ActionButtons.tsx | 45 +++++-- src/views/Home/components/Home/Home.tsx | 17 ++- 13 files changed, 100 insertions(+), 423 deletions(-) delete mode 100644 src/2023/Cfp/CfpSection2023.test.tsx delete mode 100644 src/2023/Home/components/ActionButtons/ActionButtons.tsx delete mode 100644 src/2024/Cfp/CfpSection.test.tsx delete mode 100644 src/2024/Cfp/TrackComponent.test.tsx delete mode 100644 src/2024/Home/components/ActionButtons.tsx delete mode 100644 src/2024/Talks/Talks.test.tsx delete mode 100644 src/2025/Home/components/ActionButtons/ActionButtons.tsx diff --git a/src/2023/Cfp/CfpSection2023.test.tsx b/src/2023/Cfp/CfpSection2023.test.tsx deleted file mode 100644 index 59cd39a0e..000000000 --- a/src/2023/Cfp/CfpSection2023.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { vi } from "vitest"; - -// Mock useWindowSize to control the window size in tests -vi.mock("react-use", () => ({ - useWindowSize: vi.fn(), -})); - -import React from "react"; -import { render, screen, waitFor } from "@testing-library/react"; -import "@testing-library/jest-dom"; -import CfpSection2023 from "./CfpSection2023"; -import { useWindowSize } from "react-use"; -import conferenceData from "../../data/2023.json"; -import { data } from "./CfpData"; - -describe("CfpSection2023", () => { - beforeEach(() => { - // Reset the mock before each test - useWindowSize.mockReset(); - useWindowSize.mockReturnValue({ width: 1024 }); // Default width - }); - - it("should render without crashing", () => { - render(); - }); - - it("should render the title and subtitle", () => { - render(); - expect( - screen.getByText("CFP Committee", { exact: false }), - ).toBeInTheDocument(); - expect( - screen.getByText( - "We're excited to announce the members of the Call for Papers committee for the next DevBcn conference! These experienced professionals will be reviewing and selecting the best talks and workshops for the upcoming event.", - ), - ).toBeInTheDocument(); - }); - - it("should render the tracks and members", () => { - render(); - data.forEach((track) => { - expect(screen.getAllByText(track.name, { exact: false })).not.toBeNull(); - track.members - .filter((member) => member.photo !== "") - .forEach((member) => { - expect( - screen.getAllByText(member.name, { exact: false }), - ).not.toBeNull(); - }); - }); - }); - - it("should render member photos", () => { - render(); - data.forEach((track) => { - track.members - .filter((member) => member.photo !== "") - .forEach((member) => { - const image = screen.getAllByAltText(member.name); - expect(image).not.toBeNull(); - expect(image.at(0)).toHaveAttribute("src", member.photo); - }); - }); - }); - - it("should render twitter links", () => { - render(); - data.forEach((track) => { - track.members - .filter((member) => member.twitter !== "") - .forEach((member) => { - const twitterLinks = screen.getAllByRole("link"); - const twitterLink = twitterLinks.find( - (link) => link.getAttribute("href") === member.twitter, - ); - expect(twitterLink).toBeInTheDocument(); - expect(twitterLink).toHaveAttribute("href", member.twitter); - }); - }); - }); - - it("should render linkedIn links", () => { - render(); - data.forEach((track) => { - track.members - .filter((member) => member.linkedIn !== "") - .forEach((member) => { - const linkedInLinks = screen.getAllByRole("link"); - const linkedInLink = linkedInLinks.find( - (link) => link.getAttribute("href") === member.linkedIn, - ); - expect(linkedInLink).toBeInTheDocument(); - expect(linkedInLink).toHaveAttribute("href", member.linkedIn); - }); - }); - }); - - it("should update the document title", async () => { - render(); - await waitFor(() => { - expect(document.title).toBe( - `CFP Committee — DevBcn - Barcelona Developers Conference — ${conferenceData.edition}`, - ); - }); - }); - - it("should not render the icons when the width is smaller than the breakpoint", () => { - (useWindowSize as jest.Mock).mockReturnValue({ width: 767 }); - render(); - const lessIcon = screen.queryByAltText("more than - icon"); - const moreIcon = screen.queryByAltText("Less than - icon"); - expect(lessIcon).not.toBeInTheDocument(); - expect(moreIcon).not.toBeInTheDocument(); - }); -}); diff --git a/src/2023/Home/components/ActionButtons/ActionButtons.tsx b/src/2023/Home/components/ActionButtons/ActionButtons.tsx deleted file mode 100644 index 0986c80ba..000000000 --- a/src/2023/Home/components/ActionButtons/ActionButtons.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { FC, useCallback } from "react"; -import data from "../../../../data/2023.json"; -import Button from "../../../../components/UI/Button"; -import { styled } from "styled-components"; -import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints"; -import { gaEventTracker } from "../../../../components/analytics/Analytics"; -import { useDateInterval } from "../../../../hooks/useDateInterval"; -import { useUrlBuilder } from "../../../../services/urlBuilder"; - -const StyledActionDiv = styled.div` - display: flex; - text-align: center; - - @media (max-width: ${BIG_BREAKPOINT}px) { - flex-direction: column; - width: 75%; - } -`; - -const ActionButtons: FC> = () => { - const { isTicketsDisabled, isSponsorDisabled, isCfpDisabled } = - useDateInterval(new Date(), data); - - const trackSponsorshipInfo = useCallback(() => { - gaEventTracker("sponsorship", "sponsorship"); - }, []); - - const trackTickets = useCallback(() => { - gaEventTracker("ticket", "tickets"); - }, []); - - const trackCFP = useCallback(() => { - gaEventTracker("CFP", "CFP"); - }, []); - - return ( - -