From 491207b704ab321d29da229156e15c8d4c0a8146 Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Thu, 13 Mar 2025 18:06:51 +0100 Subject: [PATCH 1/9] test: test useFetchSpeaker --- src/2024/Speakers/UseFetchSpeakers.test.tsx | 144 ---- src/2024/Talks/useFetchTalks.test.tsx | 684 +++++++++---------- src/services/speakerAdapter.test.ts | 318 +++++++++ src/views/Speakers/UseFetchSpeakers.test.tsx | 49 +- src/views/Talks/useFetchTalks.test.tsx | 89 +-- 5 files changed, 647 insertions(+), 637 deletions(-) delete mode 100644 src/2024/Speakers/UseFetchSpeakers.test.tsx create mode 100644 src/services/speakerAdapter.test.ts diff --git a/src/2024/Speakers/UseFetchSpeakers.test.tsx b/src/2024/Speakers/UseFetchSpeakers.test.tsx deleted file mode 100644 index 2adad88f..00000000 --- a/src/2024/Speakers/UseFetchSpeakers.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, {FC} from "react"; -import {QueryClient, QueryClientProvider} from "react-query"; -import {renderHook, waitFor} from "@testing-library/react"; -import {useFetchSpeakers} from "./UseFetchSpeakers"; -import axios, {AxiosHeaders, AxiosResponse} from "axios"; -import {speakerAdapter} from "../../services/speakerAdapter"; -import {IResponse} from "../../types/speakers"; - -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/Talks/useFetchTalks.test.tsx b/src/2024/Talks/useFetchTalks.test.tsx index 24d426a2..3fdaf483 100644 --- a/src/2024/Talks/useFetchTalks.test.tsx +++ b/src/2024/Talks/useFetchTalks.test.tsx @@ -1,423 +1,351 @@ -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 {useFetchLiveView, useFetchTalksById,} from "./UseFetchTalks"; -import {UngroupedSession} from "../../views/Talks/liveView.types"; +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 { useFetchTalksById } from "./UseFetchTalks"; import { - CategoryItemEnum, - IMeeting, - QuestionAnswers, - Session, - SessionCategory + CategoryItemEnum, + IMeeting, + QuestionAnswers, + Session, + SessionCategory, } from "../../types/sessions"; import { - extractSessionCategoryInfo, - extractSessionSlides, - extractSessionTags, - sessionAdapter + extractSessionCategoryInfo, + extractSessionSlides, + extractSessionTags, + sessionAdapter, } from "../../services/sessionsAdapter"; - 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 empty strings when session is undefined", () => { + expect(sessionAdapter(undefined)).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", - }, + 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: 999, - question: "Question 2", - answer: "Answer 2", - questionType: "Short_Text", + id: 149212, + name: "Session", }, - ]; - - expect(extractSessionTags(questionAnswers)).toBeUndefined(); - }); - - test("returns the expected output when questionAnswers have a Tags/Topics question", () => { - const questionAnswers: QuestionAnswers[] = [ + ], + }, + { + id: 45079, + name: CategoryItemEnum.Track, + categoryItems: [ { - id: 1, - question: "Question 1", - answer: "Answer 1", - questionType: "Short_Text", + id: 159116, + name: "Java ( core frameworks & libraries )", }, + ], + }, + { + id: 45080, + name: CategoryItemEnum.Level, + categoryItems: [ { - id: 2, - question: "Tags/Topics", - answer: "tag1, tag2, tag3", - questionType: "Short_Text", + id: 149217, + name: "Introductory and overview", }, + ], + }, + { + id: 45081, + name: CategoryItemEnum.Language, + categoryItems: [ { - id: 3, - question: "Question 2", - answer: "Answer 2", - questionType: "Short_Text", + 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(extractSessionTags(questionAnswers)).toEqual([ - "tag1", - " tag2", - " tag3", - ]); - }); + expect(sessionAdapter(session)).toEqual(expected); + }); }); -describe("extractSessionSlides", () => { - test("returns empty when questionAnswers is empty", () => { - expect(extractSessionSlides([])).toEqual(""); - }); +describe("extractSessionTags", () => { + test("returns undefined when questionAnswers is empty", () => { + expect(extractSessionTags([])).toBeUndefined(); + }); - 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", - }, - ]; + 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(extractSessionSlides(questionAnswers)).toEqual( - "https://www.google.com", - ); - }); -}); + expect(extractSessionTags(questionAnswers)).toBeUndefined(); + }); -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 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", + }, ]; - test("returns undefined when categories is empty", () => { - expect( - extractSessionCategoryInfo([], CategoryItemEnum.Level), - ).toBeUndefined(); - }); + expect(extractSessionTags(questionAnswers)).toEqual([ + "tag1", + " tag2", + " tag3", + ]); + }); +}); - test("returns undefined when the requested item is not present in categories", () => { - expect( - extractSessionCategoryInfo(categories, CategoryItemEnum.Track), - ).toBeUndefined(); - }); +describe("extractSessionSlides", () => { + test("returns empty when questionAnswers is empty", () => { + expect(extractSessionSlides([])).toEqual(""); + }); - 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 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", + }, + ]; - test("returns the expected output when the requested item is present in categories with a different name", () => { - expect( - extractSessionCategoryInfo(categories, CategoryItemEnum.Language), - ).toEqual("English πŸ‡¬πŸ‡§"); - }); + expect(extractSessionSlides(questionAnswers)).toEqual( + "https://www.google.com", + ); + }); }); -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: "", - }, - }; +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" }, + ], + }, + ]; - mockedAxios.get.mockImplementation(() => Promise.resolve(payload)); + test("returns undefined when categories is empty", () => { + expect( + extractSessionCategoryInfo([], CategoryItemEnum.Level), + ).toBeUndefined(); + }); - const wrapper: FC>> = ({ - children, - }) => { - return ( - - {children} - - ); - }; + test("returns undefined when the requested item is not present in categories", () => { + expect( + extractSessionCategoryInfo(categories, CategoryItemEnum.Track), + ).toBeUndefined(); + }); - const {result} = renderHook(() => useFetchTalksById("1234"), { - wrapper, - }); + test("returns the expected output when the requested item is present in categories", () => { + expect( + extractSessionCategoryInfo(categories, CategoryItemEnum.Level), + ).toEqual("Introductory and overview ⭐"); + }); - 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)); - }); + 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 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(), - }, - ], - }, - }; +describe("Fetch Talks by id", () => { + beforeAll(() => { + jest.mock("axios"); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); - mockedAxios.get.mockResolvedValue(payload); + 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: "", + }, + }; - const {result} = renderHook(() => useFetchLiveView(), { - wrapper, - }); + mockedAxios.get.mockImplementation(() => Promise.resolve(payload)); - await waitFor(() => { - expect(result.current.isSuccess).toBe(true); - }); + const wrapper: FC>> = ({ + children, + }) => { + return ( + + {children} + + ); + }; - expect(mockedAxios.get).toHaveBeenCalledWith( - "https://sessionize.com/api/v2/ezm48alx/view/Sessions", - ); - //expect(result.current.data).toStrictEqual(payload.data); - expect(result.current.error).toBeNull(); + 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)); + }); }); diff --git a/src/services/speakerAdapter.test.ts b/src/services/speakerAdapter.test.ts new file mode 100644 index 00000000..35635b11 --- /dev/null +++ b/src/services/speakerAdapter.test.ts @@ -0,0 +1,318 @@ +import { IResponse, ISpeaker } from "../types/speakers"; +import { speakerAdapter } from "./speakerAdapter"; + +describe("speakerAdapter", () => { + it("should correctly adapt a single response to a speaker", () => { + const response: IResponse[] = [ + { + id: "1", + fullName: "John Doe", + profilePicture: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + links: [ + { + title: "", + linkType: "Twitter", + url: "https://twitter.com/johndoe", + }, + { + title: "", + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + }, + ], + }, + ]; + + const expectedSpeaker: ISpeaker[] = [ + { + id: "1", + fullName: "John Doe", + speakerImage: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + twitterUrl: { + linkType: "Twitter", + url: "https://twitter.com/johndoe", + title: "", + }, + linkedInUrl: { + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + title: "", + }, + }, + ]; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeaker); + }); + + it("should correctly adapt multiple responses to speakers", () => { + const response: IResponse[] = [ + { + id: "1", + fullName: "John Doe", + profilePicture: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + links: [ + { + linkType: "Twitter", + url: "https://twitter.com/johndoe", + title: "", + }, + { + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + title: "", + }, + ], + }, + { + id: "2", + fullName: "Jane Smith", + profilePicture: "jane.jpg", + tagLine: "AI Expert", + bio: "Specialized in AI", + sessions: [{ id: 3, name: "Session 3" }], + links: [ + { + linkType: "Twitter", + url: "https://twitter.com/janesmith", + title: "", + }, + { + linkType: "LinkedIn", + url: "https://linkedin.com/in/janesmith", + title: "", + }, + ], + }, + ]; + + const expectedSpeakers: ISpeaker[] = [ + { + id: "1", + fullName: "John Doe", + speakerImage: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + twitterUrl: { + linkType: "Twitter", + url: "https://twitter.com/johndoe", + title: "", + }, + linkedInUrl: { + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + title: "", + }, + }, + { + id: "2", + fullName: "Jane Smith", + speakerImage: "jane.jpg", + tagLine: "AI Expert", + bio: "Specialized in AI", + sessions: [{ id: 3, name: "Session 3" }], + twitterUrl: { + linkType: "Twitter", + url: "https://twitter.com/janesmith", + title: "", + }, + linkedInUrl: { + linkType: "LinkedIn", + url: "https://linkedin.com/in/janesmith", + title: "", + }, + }, + ]; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeakers); + }); + + it("should handle missing Twitter URL", () => { + const response: IResponse[] = [ + { + id: "1", + fullName: "John Doe", + profilePicture: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + links: [ + { + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + title: "", + }, + ], + }, + ]; + + const expectedSpeaker: ISpeaker[] = [ + { + id: "1", + fullName: "John Doe", + speakerImage: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + twitterUrl: undefined, + linkedInUrl: { + linkType: "LinkedIn", + url: "https://linkedin.com/in/johndoe", + title: "", + }, + }, + ]; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeaker); + }); + + it("should handle missing LinkedIn URL", () => { + const response: IResponse[] = [ + { + id: "1", + fullName: "John Doe", + profilePicture: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + links: [ + { + linkType: "Twitter", + url: "https://twitter.com/johndoe", + title: "", + }, + ], + }, + ]; + + const expectedSpeaker: ISpeaker[] = [ + { + id: "1", + fullName: "John Doe", + speakerImage: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + twitterUrl: { + linkType: "Twitter", + url: "https://twitter.com/johndoe", + title: "", + }, + linkedInUrl: undefined, + }, + ]; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeaker); + }); + + it("should handle empty links array", () => { + const response: IResponse[] = [ + { + id: "1", + fullName: "John Doe", + profilePicture: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + links: [], + }, + ]; + + const expectedSpeaker: ISpeaker[] = [ + { + id: "1", + fullName: "John Doe", + speakerImage: "john.jpg", + tagLine: "Tech Enthusiast", + bio: "A passionate developer", + sessions: [ + { id: 1, name: "Session 1" }, + { + id: 2, + name: "Session 2", + }, + ], + twitterUrl: undefined, + linkedInUrl: undefined, + }, + ]; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeaker); + }); + + it("should handle an empty response array", () => { + const response: IResponse[] = []; + const expectedSpeaker: ISpeaker[] = []; + + const result = speakerAdapter(response); + expect(result).toEqual(expectedSpeaker); + }); +}); diff --git a/src/views/Speakers/UseFetchSpeakers.test.tsx b/src/views/Speakers/UseFetchSpeakers.test.tsx index 7e8e1ed7..aae36a7f 100644 --- a/src/views/Speakers/UseFetchSpeakers.test.tsx +++ b/src/views/Speakers/UseFetchSpeakers.test.tsx @@ -1,10 +1,9 @@ -import React, {FC} from "react"; -import {QueryClient, QueryClientProvider} from "react-query"; -import {renderHook, waitFor} from "@testing-library/react"; -import {useFetchSpeakers} from "./UseFetchSpeakers"; -import axios, {AxiosHeaders, AxiosResponse} from "axios"; -import {speakerAdapter} from "../../services/speakerAdapter"; -import {IResponse} from "../../types/speakers"; +import React, { FC } from "react"; +import { QueryClient, QueryClientProvider } from "react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import { useFetchSpeakers } from "./UseFetchSpeakers"; +import axios, { AxiosHeaders, AxiosResponse } from "axios"; +import { IResponse } from "../../types/speakers"; jest.mock("axios"); const mockedAxios = axios as jest.Mocked; @@ -74,11 +73,13 @@ describe("fetch speaker hook and speaker adapter", () => { jest.clearAllMocks(); }); - it.skip("should adapt from a server response", async () => { + it("should adapt from a server response", async () => { const queryClient = new QueryClient(); mockedAxios.get.mockImplementation(() => Promise.resolve(payload)); - const wrapper: FC>> = ({ children }) => { + const wrapper: FC>> = ({ + children, + }) => { return ( {children} @@ -94,36 +95,15 @@ describe("fetch speaker hook and speaker adapter", () => { 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.skip("should adapt from server response a query with id", async () => { + 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 }) => { + const wrapper: FC>> = ({ + children, + }) => { return ( {children} @@ -139,6 +119,5 @@ describe("fetch speaker hook and speaker adapter", () => { await waitFor(() => !result.current.isLoading, {}); //then expect(mockedAxios.get).toHaveBeenCalled(); - expect(result.current.data).toEqual(speakerAdapter(expectedPayload)); }); }); diff --git a/src/views/Talks/useFetchTalks.test.tsx b/src/views/Talks/useFetchTalks.test.tsx index 94449a72..f0bbe1a3 100644 --- a/src/views/Talks/useFetchTalks.test.tsx +++ b/src/views/Talks/useFetchTalks.test.tsx @@ -1,33 +1,27 @@ -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 {useFetchLiveView, useFetchTalksById,} from "./UseFetchTalks"; -import {UngroupedSession} from "./liveView.types"; +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 { useFetchTalksById } from "./UseFetchTalks"; import { extractSessionCategoryInfo, extractSessionSlides, extractSessionTags, - sessionAdapter + sessionAdapter, } from "../../services/sessionsAdapter"; import { CategoryItemEnum, IMeeting, QuestionAnswers, Session, - SessionCategory + SessionCategory, } from "../../types/sessions"; 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", () => { @@ -347,7 +341,7 @@ describe("Fetch Talks by id", () => { await waitFor(() => !result.current.isLoading); expect(mockedAxios.get).toHaveBeenNthCalledWith( 1, - "https://sessionize.com/api/v2/xhudniix/view/Sessions", + "https://sessionize.com/api/v2/xhudniix/view/Sessions", ); expect(mockedAxios.get).toHaveReturnedTimes(1); //expect(result.current.isLoading).toEqual(false); @@ -355,68 +349,3 @@ describe("Fetch Talks by id", () => { //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(); - }); -}); From 9b48abbdf2d7cf9ea4659bf3e3641019d1220590 Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Thu, 13 Mar 2025 18:07:08 +0100 Subject: [PATCH 2/9] feat: parameterize url --- src/data/2023.json | 1 + src/data/2024.json | 1 + src/data/2025.json | 1 + 3 files changed, 3 insertions(+) diff --git a/src/data/2023.json b/src/data/2023.json index b14898e1..725b45b3 100644 --- a/src/data/2023.json +++ b/src/data/2023.json @@ -24,6 +24,7 @@ "schedule": { "enabled": true }, + "sessionizeUrl": "https://sessionize.com/api/v2/ttsitynd", "showCountdown": false, "showInfoButtons": true, "sponsors": { diff --git a/src/data/2024.json b/src/data/2024.json index 27d3517c..7efeeab5 100644 --- a/src/data/2024.json +++ b/src/data/2024.json @@ -24,6 +24,7 @@ "schedule": { "enabled": true }, + "sessionizeUrl": "https://sessionize.com/api/v2/teq4asez", "showCountdown": false, "showInfoButtons": true, "sponsors": { diff --git a/src/data/2025.json b/src/data/2025.json index 4e3d1f17..02e7382d 100644 --- a/src/data/2025.json +++ b/src/data/2025.json @@ -24,6 +24,7 @@ "schedule": { "enabled": false }, + "sessionizeUrl": "https://sessionize.com/api/v2/xhudniix", "showCountdown": true, "showInfoButtons": false, "sponsors": { From d066d30fff8e60e23f9eeadc7a500661ff37406b Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Thu, 13 Mar 2025 18:07:36 +0100 Subject: [PATCH 3/9] refactor: remove duplication --- .../SpeakersCarousel/SpeakerSwiper.tsx | 8 +- .../SpeakerDetailContainer2023.tsx | 7 +- src/2023/Speakers/Speakers2023.tsx | 32 +- src/2023/Speakers/UseFetchSpeakers.ts | 22 -- .../TalkDetail/TalkDetailContainer2023.tsx | 18 +- .../SpeakerDetailContainer2024.tsx | 81 ++--- src/2024/Speakers/Speakers2024.tsx | 292 +++++++++--------- src/2024/Speakers/UseFetchSpeakers.ts | 22 -- src/2024/SpeakersCarousel/SpeakerSwiper.tsx | 184 +++++------ .../TalkDetail/MeetingDetailContainer.tsx | 102 +++--- src/views/Speakers/Speakers.tsx | 32 +- 11 files changed, 388 insertions(+), 412 deletions(-) delete mode 100644 src/2023/Speakers/UseFetchSpeakers.ts delete mode 100644 src/2024/Speakers/UseFetchSpeakers.ts diff --git a/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx b/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx index 428ac669..4ef82cc4 100644 --- a/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx +++ b/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx @@ -7,9 +7,11 @@ import "swiper/swiper-bundle.min.css"; import "./SpeakersCarousel.scss"; import { Link } from "react-router"; import { ROUTE_SPEAKER_DETAIL } from "../../../../constants/routes"; -import { useFetchSpeakers } from "../../../Speakers/UseFetchSpeakers"; import * as Sentry from "@sentry/react"; +import conferenceData from "../../../../data/2023.json"; +import { useFetchSpeakers } from "../../../../views/Speakers/UseFetchSpeakers"; + const StyledSlideImage = styled.img` display: block; width: 100%; @@ -35,7 +37,9 @@ const StyledSlideText = styled.p` color: white; `; const SpeakerSwiper: FC> = () => { - const { isLoading, data, error } = useFetchSpeakers(); + const { isLoading, data, error } = useFetchSpeakers( + conferenceData.sessionizeUrl, + ); const swiperSpeakers = data?.sort(() => 0.5 - Math.random()).slice(0, 20); diff --git a/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx b/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx index cdf001c8..90c4dd65 100644 --- a/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx +++ b/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx @@ -6,13 +6,16 @@ import SpeakerDetail2023 from "./SpeakerDetail2023"; import { useParams } from "react-router"; import { StyledContainer, StyledWaveContainer } from "./Speaker.style"; import conferenceData from "../../data/2023.json"; -import { useFetchSpeakers } from "../Speakers/UseFetchSpeakers"; import * as Sentry from "@sentry/react"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const SpeakerDetailContainer2023: FC> = () => { const { id } = useParams<{ id: string }>(); - const { isLoading, error, data } = useFetchSpeakers(id); + const { isLoading, error, data } = useFetchSpeakers( + conferenceData.sessionizeUrl, + id, + ); if (error) { Sentry.captureException(error); diff --git a/src/2023/Speakers/Speakers2023.tsx b/src/2023/Speakers/Speakers2023.tsx index 5fd28c26..4ae3b68b 100644 --- a/src/2023/Speakers/Speakers2023.tsx +++ b/src/2023/Speakers/Speakers2023.tsx @@ -1,12 +1,12 @@ -import {MOBILE_BREAKPOINT} from "../../constants/BreakPoints"; -import {Color} from "../../styles/colors"; -import {FC, useCallback, useEffect} from "react"; +import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; +import { Color } from "../../styles/colors"; +import { 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 {SpeakerCard} from "./components/SpeakersCard"; +import { SpeakerCard } from "./components/SpeakersCard"; import TitleSection from "../../components/SectionTitle/TitleSection"; -import {useWindowSize} from "react-use"; +import { useWindowSize } from "react-use"; import { SpeakersCardsContainer, StyledContainerLeftSlash, @@ -19,10 +19,10 @@ import { } from "./Speakers.style"; import webData from "../../data/2023.json"; import Button from "../../components/UI/Button"; -import {gaEventTracker} from "../../components/analytics/Analytics"; -import {useFetchSpeakers} from "./UseFetchSpeakers"; +import { gaEventTracker } from "../../components/analytics/Analytics"; import * as Sentry from "@sentry/react"; -import {ISpeaker} from "../../types/speakers"; +import { ISpeaker } from "../../types/speakers"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const LessThanGreaterThan = (props: { width: number }) => ( <> @@ -41,7 +41,9 @@ const Speakers2023: FC> = () => { const isBetween = (startDay: Date, endDay: Date): boolean => startDay < new Date() && endDay > today; - const { error, data, isLoading } = useFetchSpeakers(); + const { error, data, isLoading } = useFetchSpeakers( + `${webData.sessionizeUrl}/view/Speakers`, + ); if (error) { Sentry.captureException(error); @@ -105,7 +107,8 @@ const Speakers2023: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -117,7 +120,8 @@ const Speakers2023: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -129,7 +133,8 @@ const Speakers2023: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -141,7 +146,8 @@ const Speakers2023: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} diff --git a/src/2023/Speakers/UseFetchSpeakers.ts b/src/2023/Speakers/UseFetchSpeakers.ts deleted file mode 100644 index d0727ee7..00000000 --- a/src/2023/Speakers/UseFetchSpeakers.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {useQuery, UseQueryResult} from "react-query"; -import axios from "axios"; -import {speakerAdapter} from "../../services/speakerAdapter"; -import {ISpeaker} from "../../types/speakers"; - -export const useFetchSpeakers = (id?: string): UseQueryResult => { - return useQuery("api-speakers", async () => { - const serverResponse = await axios.get( - "https://sessionize.com/api/v2/ttsitynd/view/Speakers" - ); - let returnData; - if (id !== undefined) { - returnData = serverResponse.data.filter( - (speaker: { id: string }) => speaker.id === id - ); - } else { - returnData = serverResponse.data; - } - return speakerAdapter(returnData); - }); -}; - diff --git a/src/2023/TalkDetail/TalkDetailContainer2023.tsx b/src/2023/TalkDetail/TalkDetailContainer2023.tsx index f1f096ea..2d29cc21 100644 --- a/src/2023/TalkDetail/TalkDetailContainer2023.tsx +++ b/src/2023/TalkDetail/TalkDetailContainer2023.tsx @@ -1,17 +1,17 @@ -import {Color} from "../../styles/colors"; -import React, {FC, useEffect} from "react"; +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"; +import { useParams } from "react-router"; import conferenceData from "../../data/2023.json"; -import {useFetchTalksById} from "../Talks/UseFetchTalks"; +import { useFetchTalksById } from "../Talks/UseFetchTalks"; import * as Sentry from "@sentry/react"; -import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers"; -import {Session} from "../../types/sessions"; +import { Session } from "../../types/sessions"; import TalkDetail from "./TalkDetail"; -import {ISpeaker} from "../../types/speakers"; -import {sessionAdapter} from "../../services/sessionsAdapter"; +import { ISpeaker } from "../../types/speakers"; +import { sessionAdapter } from "../../services/sessionsAdapter"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const StyledContainer = styled.div` background-color: ${Color.WHITE}; @@ -19,7 +19,7 @@ const StyledContainer = styled.div` const TalkDetailContainer2023: FC> = () => { const { id } = useParams<{ id: string }>(); const { isLoading, error, data } = useFetchTalksById(id!); - const { data: speakerData } = useFetchSpeakers(); + const { data: speakerData } = useFetchSpeakers(conferenceData.sessionizeUrl); const getTalkSpeakers = ( data: Session[] | undefined, diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx index 8d7be2b8..7c6177ef 100644 --- a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx +++ b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx @@ -1,52 +1,55 @@ -import {Color} from "../../styles/colors"; +import { Color } from "../../styles/colors"; -import React, {FC} from "react"; +import React, { FC } from "react"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import SpeakerDetail from "./SpeakerDetail"; -import {useParams} from "react-router"; +import { useParams } from "react-router"; 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"; +import { StyledContainer } from "../../views/SpeakerDetail/Speaker.style"; +import { StyledWaveContainer } from "../../views/Talks/Talks.style"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const SpeakerDetailContainer2024: FC> = () => { - const {id} = useParams<{ id: string }>(); + const { id } = useParams<{ id: string }>(); - const {isLoading, error, data} = useFetchSpeakers(id); + const { isLoading, error, data } = useFetchSpeakers( + conferenceData.sessionizeUrl, + id, + ); - if (error) { - Sentry.captureException(error); + if (error) { + Sentry.captureException(error); + } + React.useEffect(() => { + if (data) { + document.title = `${data[0]?.fullName} - DevBcn - ${conferenceData.edition}`; } - React.useEffect(() => { - if (data) { - document.title = `${data[0]?.fullName} - DevBcn - ${conferenceData.edition}`; - } - }, [id, data]); - return ( - - - {isLoading &&

Loading

} - {!isLoading && data && data.length > 0 ? ( - - ) : ( - "not found" - )} -
- - - - - -
- ); + }, [id, data]); + return ( + + + {isLoading &&

Loading

} + {!isLoading && data && data.length > 0 ? ( + + ) : ( + "not found" + )} +
+ + + + + +
+ ); }; export default SpeakerDetailContainer2024; diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx index c5f4e973..751fb53e 100644 --- a/src/2024/Speakers/Speakers2024.tsx +++ b/src/2024/Speakers/Speakers2024.tsx @@ -1,176 +1,172 @@ -import {MOBILE_BREAKPOINT} from "../../constants/BreakPoints"; -import {Color} from "../../styles/colors"; -import React, {FC, useCallback, useEffect} from "react"; +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 { 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 { gaEventTracker } from "../../components/analytics/Analytics"; import * as Sentry from "@sentry/react"; -import {SpeakerCard} from "../../views/Speakers/components/SpeakersCard"; -import {ISpeaker} from "../../types/speakers"; +import { SpeakerCard } from "../../views/Speakers/components/SpeakersCard"; +import { ISpeaker } from "../../types/speakers"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const LessThanGreaterThan = (props: { width: number }) => ( - <> - {props.width > MOBILE_BREAKPOINT && ( - <> - - - - )} - + <> + {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 { 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( + `${webData.sessionizeUrl}/view/Speakers`, + ); - if (error) { - Sentry.captureException(error); - } + if (error) { + Sentry.captureException(error); + } - const trackCFP = useCallback(() => { - gaEventTracker("CFP", "CFP"); - }, []); + const trackCFP = useCallback(() => { + gaEventTracker("CFP", "CFP"); + }, []); - useEffect(() => { - document.title = `Speakers β€” ${webData.title} β€” ${webData.edition}`; - }); + 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) => ( - - )) - )} -
- - - / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / /{" "} - - + color={Color.WHITE} + /> + + + {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.ts b/src/2024/Speakers/UseFetchSpeakers.ts deleted file mode 100644 index 40c738be..00000000 --- a/src/2024/Speakers/UseFetchSpeakers.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {useQuery, UseQueryResult} from "react-query"; -import axios from "axios"; -import {speakerAdapter} from "../../services/speakerAdapter"; -import {ISpeaker} from "../../types/speakers"; - -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); - }); -}; - diff --git a/src/2024/SpeakersCarousel/SpeakerSwiper.tsx b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx index 7231db56..e0ad9550 100644 --- a/src/2024/SpeakersCarousel/SpeakerSwiper.tsx +++ b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx @@ -1,129 +1,131 @@ -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 "swiper/swiper-bundle.min.css"; import "./SpeakersCarousel.scss"; -import {Link} from "react-router"; +import { Link } from "react-router"; 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"; +import { Color } from "../../styles/colors"; +import { ROUTE_SPEAKER_DETAIL } from "../../constants/routes"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const StyledSlideImage = styled.img` - display: block; - width: 100%; - aspect-ratio: 1/1; - border-radius: 10px; + 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%; + 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; + font-size: 0.875rem; + color: white; `; const SpeakerSwiper: FC> = () => { - const {isLoading, data, error} = useFetchSpeakers(); + const { isLoading, data, error } = useFetchSpeakers( + conferenceData.sessionizeUrl, + ); - // Securely shuffle the speakers using Fisher-Yates algorithm with crypto API + // Securely shuffle the speakers using Fisher-Yates algorithm with crypto API const swiperSpeakers = React.useMemo(() => { if (!data) return null; - + // Create a copy of the data to avoid mutating the original const speakersCopy = [...data]; - + // Fisher-Yates shuffle with crypto.getRandomValues for secure randomization for (let i = speakersCopy.length - 1; i > 0; i--) { // Generate a secure random value using crypto API const randomBuffer = new Uint32Array(1); window.crypto.getRandomValues(randomBuffer); - + // Use the random value to get an index between 0 and i (inclusive) const j = randomBuffer[0] % (i + 1); - + // Swap elements at i and j [speakersCopy[i], speakersCopy[j]] = [speakersCopy[j], speakersCopy[i]]; } - + // Return the first 20 speakers from the shuffled array return speakersCopy.slice(0, 20); }, [data]); - if (error) { - Sentry.captureException(error); - } + if (error) { + Sentry.captureException(error); + } - return ( - <> - {isLoading &&

Loading

} - {conferenceData.carrousel.enabled && swiperSpeakers && ( - - {swiperSpeakers.map((speaker) => ( - - - - - {speaker.fullName} - - - - ))} - - )} - - ); + return ( + <> + {isLoading &&

Loading

} + {conferenceData.carrousel.enabled && swiperSpeakers && ( + + {swiperSpeakers.map((speaker) => ( + + + + + {speaker.fullName} + + + + ))} + + )} + + ); }; export default SpeakerSwiper; diff --git a/src/2024/TalkDetail/MeetingDetailContainer.tsx b/src/2024/TalkDetail/MeetingDetailContainer.tsx index f48834d9..0e049ca2 100644 --- a/src/2024/TalkDetail/MeetingDetailContainer.tsx +++ b/src/2024/TalkDetail/MeetingDetailContainer.tsx @@ -1,71 +1,71 @@ -import {Color} from "../../styles/colors"; -import React, {FC, useEffect} from "react"; +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"; +import { useParams } from "react-router"; import conferenceData from "../../data/2024.json"; -import {useFetchTalksById} from "../Talks/UseFetchTalks"; +import { useFetchTalksById } from "../Talks/UseFetchTalks"; import * as Sentry from "@sentry/react"; -import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers"; import MeetingDetail from "./MeetingDetail"; -import {ISpeaker} from "../../types/speakers"; -import {Session} from "../../types/sessions"; -import {sessionAdapter} from "../../services/sessionsAdapter"; +import { ISpeaker } from "../../types/speakers"; +import { Session } from "../../types/sessions"; +import { sessionAdapter } from "../../services/sessionsAdapter"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; const StyledContainer = styled.div` - background-color: ${Color.WHITE}; + background-color: ${Color.WHITE}; `; const MeetingDetailContainer: FC> = () => { - const {id} = useParams<{ id: string }>(); - const {isLoading, error, data} = useFetchTalksById(id!); - const {data: speakerData} = useFetchSpeakers(); + const { id } = useParams<{ id: string }>(); + const { isLoading, error, data } = useFetchTalksById(id!); + const { data: speakerData } = useFetchSpeakers(conferenceData.sessionizeUrl); - const getTalkSpeakers = ( - data: Session[] | undefined, - ): string[] | undefined => { - const speakers = data?.[0]?.speakers; - return speakers?.map((speaker) => speaker.id); - }; + 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 talkSpeakers: string[] | undefined = getTalkSpeakers(data); + const sessionSpeakers: ISpeaker[] | undefined = speakerData?.filter( + (speaker) => talkSpeakers?.includes(speaker.id), + ); - const adaptedMeeting = sessionAdapter(data?.at(0)); + const adaptedMeeting = sessionAdapter(data?.at(0)); - useEffect(() => { - document.title = `${data?.at(0)?.title} - DevBcn - ${ - conferenceData.edition - }`; - }, [data]); + useEffect(() => { + document.title = `${data?.at(0)?.title} - DevBcn - ${ + conferenceData.edition + }`; + }, [data]); - if (error) { - Sentry.captureException(error); - } + if (error) { + Sentry.captureException(error); + } - return ( - - - {isLoading &&

Loading

} - {!isLoading && - sessionSpeakers !== undefined && - sessionSpeakers.length > 0 && - adaptedMeeting !== undefined && ( - - )} - {!isLoading && - (!sessionSpeakers || - sessionSpeakers.length === 0 || - !adaptedMeeting) && } -
-
- ); + return ( + + + {isLoading &&

Loading

} + {!isLoading && + sessionSpeakers !== undefined && + sessionSpeakers.length > 0 && + adaptedMeeting !== undefined && ( + + )} + {!isLoading && + (!sessionSpeakers || + sessionSpeakers.length === 0 || + !adaptedMeeting) && } +
+
+ ); }; export default MeetingDetailContainer; diff --git a/src/views/Speakers/Speakers.tsx b/src/views/Speakers/Speakers.tsx index f2525fff..59fafd9c 100644 --- a/src/views/Speakers/Speakers.tsx +++ b/src/views/Speakers/Speakers.tsx @@ -1,12 +1,12 @@ -import {MOBILE_BREAKPOINT} from "../../constants/BreakPoints"; -import {Color} from "../../styles/colors"; -import {FC, useCallback, useEffect} from "react"; +import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; +import { Color } from "../../styles/colors"; +import { 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 {SpeakerCard} from "./components/SpeakersCard"; +import { SpeakerCard } from "./components/SpeakersCard"; import TitleSection from "../../components/SectionTitle/TitleSection"; -import {useWindowSize} from "react-use"; +import { useWindowSize } from "react-use"; import { SpeakersCardsContainer, StyledContainerLeftSlash, @@ -19,10 +19,10 @@ import { } 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 { gaEventTracker } from "../../components/analytics/Analytics"; +import { useFetchSpeakers } from "./UseFetchSpeakers"; import * as Sentry from "@sentry/react"; -import {ISpeaker} from "../../types/speakers"; +import { ISpeaker } from "../../types/speakers"; const LessThanGreaterThan = (props: { width: number }) => ( <> @@ -41,7 +41,9 @@ const Speakers: FC> = () => { const isBetween = (startDay: Date, endDay: Date): boolean => startDay < new Date() && endDay > today; - const { error, data, isLoading } = useFetchSpeakers(); + const { error, data, isLoading } = useFetchSpeakers( + `${webData.sessionizeUrl}/view/Speakers`, + ); if (error) { Sentry.captureException(error); @@ -106,7 +108,8 @@ const Speakers: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -118,7 +121,8 @@ const Speakers: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -130,7 +134,8 @@ const Speakers: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} @@ -142,7 +147,8 @@ const Speakers: FC> = () => { > / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /{" "} + / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / + /{" "} From d20db04f63e41ca0bd8facff7bc5de21b837d83a Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Thu, 13 Mar 2025 18:08:00 +0100 Subject: [PATCH 4/9] feat: parameterize url --- src/views/Speakers/UseFetchSpeakers.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/views/Speakers/UseFetchSpeakers.ts b/src/views/Speakers/UseFetchSpeakers.ts index 8ff0fecb..82d0a0bf 100644 --- a/src/views/Speakers/UseFetchSpeakers.ts +++ b/src/views/Speakers/UseFetchSpeakers.ts @@ -1,13 +1,14 @@ import { useQuery, UseQueryResult } from "react-query"; import axios from "axios"; import { speakerAdapter } from "../../services/speakerAdapter"; -import { ISpeaker } from "../../types/speakers"; +import { ISpeaker } from "../../types/speakers"; -export const useFetchSpeakers = (id?: string): UseQueryResult => { - return useQuery("api-speakers", async () => { - const serverResponse = await axios.get( - "https://sessionize.com/api/v2/xhudniix/view/Speakers", - ); +export const useFetchSpeakers = ( + url: string = "https://sessionize.com/api/v2/xhudniix/view/Speakers", + id?: string, +): UseQueryResult => { + return useQuery(["api-speakers", url], async () => { + const serverResponse = await axios.get(url); let returnData; if (id !== undefined) { returnData = serverResponse.data.filter( From 0e07f73ce8f23bd58cd1286fbc4fe4ab87333ff6 Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Sat, 15 Mar 2025 05:48:14 +0100 Subject: [PATCH 5/9] test: mock API calls --- src/App.test.tsx | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/App.test.tsx b/src/App.test.tsx index 9602e8d6..26a257b6 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -3,8 +3,77 @@ import { BrowserRouter, Route, Routes } from "react-router"; import App from "./App"; import React from "react"; import userEvent from "@testing-library/user-event"; +import { IResponse } from "./types/speakers"; +import axios, { AxiosHeaders, AxiosResponse } from "axios"; + +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("navigation pages", () => { + beforeAll(() => { + jest.mock("axios"); + mockedAxios.get.mockImplementation(() => Promise.resolve(payload)); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); + test("it render the HOME page", async () => { render( Loading...}> From 96899542dc6566eea75811f1287dae154f24c55a Mon Sep 17 00:00:00 2001 From: Anyul Rivas Date: Tue, 18 Mar 2025 15:18:19 +0100 Subject: [PATCH 6/9] fix: fix 2024 diversity route --- src/2023/Cfp/CfpSection2023.test.tsx | 2 +- src/2023/Cfp/CfpSection2023.tsx | 6 +- src/2023/Communities/Communities2023.tsx | 5 +- src/2023/Diversity/Diversity2023.tsx | 59 +- src/2023/Home/Home2023Wrapper.tsx | 4 +- .../SpeakersCarousel/SpeakerSwiper.tsx | 29 +- src/2023/JobOffers/JobOffers2023.tsx | 5 +- src/2023/Schedule/Schedule2023.tsx | 5 +- .../SessionFeedback/SessionFeedback2023.tsx | 5 +- .../SpeakerDetailContainer2023.tsx | 15 +- src/2023/Speakers/Speakers2023.tsx | 13 +- src/2023/TalkDetail/TalkDetail.tsx | 11 +- .../TalkDetail/TalkDetailContainer2023.tsx | 16 +- src/2023/Talks/Talks2023.tsx | 13 +- src/2023/Workshops/Workshops2023.tsx | 23 +- src/2024/Cfp/CfpSection.test.tsx | 2 +- src/2024/Cfp/CfpSection2024.tsx | 55 +- src/2024/HomeWrapper2024.tsx | 60 +- src/2024/SpeakerDetail/SpeakerDetail.tsx | 292 +++++---- .../SpeakerDetailContainer2024.tsx | 14 +- src/2024/Speakers/Speakers2024.tsx | 13 +- src/2024/SpeakersCarousel/SpeakerSwiper.tsx | 10 +- src/2024/TalkDetail/MeetingDetail.tsx | 498 +++++++-------- .../TalkDetail/MeetingDetailContainer.tsx | 16 +- src/2024/Talks/LiveView.tsx | 111 ++-- src/2024/Talks/Talks2024.tsx | 181 +++--- .../NotFoundError/NotFoundError.tsx | 11 +- src/components/Swiper/SpeakerSwiper.tsx | 182 +++--- src/services/useDocumentTitleUpdate.ts | 7 + src/services/useSentryErrorReport.ts | 7 + src/views/About/About.tsx | 5 +- src/views/Attendee/AttendeeInformation.tsx | 7 +- src/views/Cfp/CfpSection.test.tsx | 2 +- src/views/Cfp/CfpSection.tsx | 64 +- src/views/CodeOfConduct/CodeOfConduct.tsx | 7 +- src/views/Communities/Communities.tsx | 6 +- src/views/Conditions/Conditions.tsx | 50 +- src/views/Diversity/Diversity.tsx | 7 +- src/views/Home/HomeWrapper.tsx | 4 +- src/views/JobOffers/JobOffers.tsx | 5 +- src/views/MeetingDetail/MeetingDetail.tsx | 11 +- .../MeetingDetail/TalkDetailContainer2024.tsx | 30 +- src/views/Schedule/Schedule.tsx | 5 +- src/views/SessionFeedback/SessionFeedback.tsx | 5 +- src/views/SpeakerDetail/SpeakerDetail.tsx | 9 +- .../SpeakerDetail/SpeakerDetailContainer.tsx | 66 +- src/views/Speakers/Speakers.tsx | 17 +- src/views/Speakers/UseFetchSpeakers.ts | 4 +- src/views/Talks/LiveView.tsx | 15 +- src/views/Talks/Talks.tsx | 10 +- src/views/Travel/Travel.tsx | 54 +- src/views/Workshops/Workshops.tsx | 34 +- src/views/kcd/Kcd.tsx | 13 +- src/views/sponsorship/Sponsorship.tsx | 600 +++++++++--------- 54 files changed, 1327 insertions(+), 1373 deletions(-) create mode 100644 src/services/useDocumentTitleUpdate.ts create mode 100644 src/services/useSentryErrorReport.ts diff --git a/src/2023/Cfp/CfpSection2023.test.tsx b/src/2023/Cfp/CfpSection2023.test.tsx index e8bacfd8..67e989be 100644 --- a/src/2023/Cfp/CfpSection2023.test.tsx +++ b/src/2023/Cfp/CfpSection2023.test.tsx @@ -98,7 +98,7 @@ describe("CfpSection2023", () => { render(); await waitFor(() => { expect(document.title).toBe( - `CFP Committee - DevBcn - ${conferenceData.edition}`, + `CFP Committee β€” DevBcn - Barcelona Developers Conference β€” ${conferenceData.edition}`, ); }); }); diff --git a/src/2023/Cfp/CfpSection2023.tsx b/src/2023/Cfp/CfpSection2023.tsx index c03e02a4..94558ccb 100644 --- a/src/2023/Cfp/CfpSection2023.tsx +++ b/src/2023/Cfp/CfpSection2023.tsx @@ -21,6 +21,7 @@ import { StyledAboutImage, StyledSocialIconsWrapper, } from "../../views/About/components/Style.AboutCard"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const TrackName = styled.h2` padding-top: 1.2rem; @@ -75,9 +76,8 @@ const CfpTrackComponent: FC> = ({ const CfpSection2023: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `CFP Committee - DevBcn - ${conferenceData.edition}`; - }, []); + + useDocumentTitleUpdater("CFP Committee", conferenceData.edition); return ( <> diff --git a/src/2023/Communities/Communities2023.tsx b/src/2023/Communities/Communities2023.tsx index 301429a7..53a34919 100644 --- a/src/2023/Communities/Communities2023.tsx +++ b/src/2023/Communities/Communities2023.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import TwitterIcon from "../../components/Icons/Twitter"; import { Color } from "../../styles/colors"; import WebsiteIcon from "../../components/Icons/website"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const Heading = styled.h1` padding-top: 10rem; @@ -29,9 +30,7 @@ const FoSS = styled.div` `; const Communities2023: FC> = () => { - React.useEffect(() => { - document.title = "Communities"; - }); + useDocumentTitleUpdater("Communities", "2023"); return ( <> FOSS & Diversity Communities diff --git a/src/2023/Diversity/Diversity2023.tsx b/src/2023/Diversity/Diversity2023.tsx index 12e72bb1..06f8a9d3 100644 --- a/src/2023/Diversity/Diversity2023.tsx +++ b/src/2023/Diversity/Diversity2023.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect } from "react"; +import { FC } from "react"; import { Color } from "../../styles/colors"; import data from "../../data/2023.json"; import styled from "styled-components"; @@ -8,25 +8,26 @@ import { ROUTE_CODE_OF_CONDUCT, ROUTE_CONDITIONS, } from "../../constants/routes"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledSection = styled.section` - { +{ padding-top: 48px; - } +} - .top { - clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 50px)); - height: 51px; - background-color: ${Color.DARK_BLUE}; - border-top: 1px solid ${Color.DARK_BLUE}; - } + .top { + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 50px)); + height: 51px; + background-color: ${Color.DARK_BLUE}; + border-top: 1px solid ${Color.DARK_BLUE}; + } - .bottom { - clip-path: polygon(0 0, 100% 50px, 100% 100%, 0 100%); - margin-top: -50px; - height: 50px; - background-color: ${Color.WHITE}; - } + .bottom { + clip-path: polygon(0 0, 100% 50px, 100% 100%, 0 100%); + margin-top: -50px; + height: 50px; + background-color: ${Color.WHITE}; + } `; const StyledWave = styled.section` @@ -36,15 +37,15 @@ const StyledWave = styled.section` `; const StyledLogo = styled.img` - { +{ max-width: 30vw; flex: 2 1 auto; padding-bottom: 50px; - } - @media only screen and (max-width: ${BIG_BREAKPOINT}px) { - padding-bottom: 20px; - max-width: 65vw; - } +} + @media only screen and (max-width: ${BIG_BREAKPOINT}px) { + padding-bottom: 20px; + max-width: 65vw; + } `; const Heading = styled.h1` @@ -68,17 +69,17 @@ const StyledP = styled.p` `; const FlexDiv = styled.div` - { +{ display: flex; width: 20%; margin: 0 auto; flex-direction: column; padding-bottom: 20px; - } - @media only screen and (max-width: ${BIG_BREAKPOINT}px) { - width: 60%; - padding-bottom: 0.5rem; - } +} + @media only screen and (max-width: ${BIG_BREAKPOINT}px) { + width: 60%; + padding-bottom: 0.5rem; + } `; const StyledParagraph = styled.section` @@ -96,9 +97,7 @@ const StyledParagraph = styled.section` } `; const Diversity2023: FC> = () => { - useEffect(() => { - document.title = `Diversity - DevBcn ${data.edition}`; - }); + useDocumentTitleUpdater("Diversity", data.edition); return ( diff --git a/src/2023/Home/Home2023Wrapper.tsx b/src/2023/Home/Home2023Wrapper.tsx index a7a77d7c..cc45e91f 100644 --- a/src/2023/Home/Home2023Wrapper.tsx +++ b/src/2023/Home/Home2023Wrapper.tsx @@ -7,6 +7,7 @@ import Sponsors from "./components/Sponsors/Sponsors"; import styled from "styled-components"; import data from "../../data/2023.json"; import { useLocation } from "react-router"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` padding-bottom: 10rem; @@ -20,12 +21,13 @@ export const Home2023Wrapper: FC> = () => { const { hash } = useLocation(); React.useEffect(() => { - document.title = `Home - DevBcn - ${data.edition}`; if (hash != null && hash !== "") { const scroll = document.getElementById(hash.substring(1)); scroll?.scrollIntoView(); } }, [hash]); + + useDocumentTitleUpdater("Home", data.edition); return ( diff --git a/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx b/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx index 4ef82cc4..1fe56d78 100644 --- a/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx +++ b/src/2023/Home/components/SpeakersCarousel/SpeakerSwiper.tsx @@ -7,10 +7,11 @@ import "swiper/swiper-bundle.min.css"; import "./SpeakersCarousel.scss"; import { Link } from "react-router"; import { ROUTE_SPEAKER_DETAIL } from "../../../../constants/routes"; -import * as Sentry from "@sentry/react"; import conferenceData from "../../../../data/2023.json"; import { useFetchSpeakers } from "../../../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../../../services/useSentryErrorReport"; +import { ISpeaker } from "../../../../types/speakers"; const StyledSlideImage = styled.img` display: block; @@ -36,16 +37,34 @@ const StyledSlideText = styled.p` font-size: 0.875rem; color: white; `; + +/** Fisher-Yates shuffle algorithm using window.crypto.getRandomValues() */ +export const shuffleArray = (array: T[]): T[] => { + if (!array) { + return []; + } + const shuffledArray = [...array]; // Create a copy to avoid modifying the original array + for (let i = shuffledArray.length - 1; i > 0; i--) { + const j = Math.floor( + (window.crypto.getRandomValues(new Uint32Array(1))[0] / + (0xffffffff + 1)) * + (i + 1), + ); + [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; + } + return shuffledArray; +}; + const SpeakerSwiper: FC> = () => { const { isLoading, data, error } = useFetchSpeakers( conferenceData.sessionizeUrl, ); - const swiperSpeakers = data?.sort(() => 0.5 - Math.random()).slice(0, 20); + const swiperSpeakers: ISpeaker[] = data + ? shuffleArray(data).slice(0, 20) + : []; - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); return ( <> diff --git a/src/2023/JobOffers/JobOffers2023.tsx b/src/2023/JobOffers/JobOffers2023.tsx index cb169644..b166d8dd 100644 --- a/src/2023/JobOffers/JobOffers2023.tsx +++ b/src/2023/JobOffers/JobOffers2023.tsx @@ -14,6 +14,7 @@ import { StyledTitleContainer } from "../../styles/JobOffers/JobOffers.Style"; import CompanyOffers from "../../components/JobOffers/CompanyOffers"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const NoOffersAvailable = () => (

No job offers available yet

@@ -29,9 +30,7 @@ const MoreThanLessThan = (props: { width: number }) => ( const JobOffers2023: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `Job Offers - DevBcn - ${data.edition}`; - }, []); + useDocumentTitleUpdater("Job Offers", data.edition); return ( diff --git a/src/2023/Schedule/Schedule2023.tsx b/src/2023/Schedule/Schedule2023.tsx index 81f437d8..792d0833 100644 --- a/src/2023/Schedule/Schedule2023.tsx +++ b/src/2023/Schedule/Schedule2023.tsx @@ -13,13 +13,14 @@ import { StyledScheduleSection, } from "../../styles/Schedule/Schedule.style"; import * as Sentry from "@sentry/react"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const Schedule2023: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `Schedule - DevBcn - ${data.edition}`; + useDocumentTitleUpdater("Schedule", data.edition); + React.useEffect(() => { fetch("https://sessionize.com/api/v2/a2sw0wks/view/GridSmart") .then((value) => value.text()) .then((value) => { diff --git a/src/2023/SessionFeedback/SessionFeedback2023.tsx b/src/2023/SessionFeedback/SessionFeedback2023.tsx index 0f53ef14..9bda1b0f 100644 --- a/src/2023/SessionFeedback/SessionFeedback2023.tsx +++ b/src/2023/SessionFeedback/SessionFeedback2023.tsx @@ -15,6 +15,7 @@ import { FilterMatchMode } from "primereact/api"; import { Color } from "../../styles/colors"; import { Link } from "react-router"; import { ROUTE_TALK_DETAIL } from "../../constants/routes"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const SessionFeedback2023: FC> = () => { const bodyTemplate = React.useCallback( @@ -64,9 +65,7 @@ const SessionFeedback2023: FC> = () => { ); - React.useEffect(() => { - document.title = "DevBcn 2023 - Session Feedback"; - }); + useDocumentTitleUpdater("Session Feedback", "2023"); const header = renderHeader(); diff --git a/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx b/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx index 90c4dd65..af250753 100644 --- a/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx +++ b/src/2023/SpeakerDetail/SpeakerDetailContainer2023.tsx @@ -6,8 +6,9 @@ import SpeakerDetail2023 from "./SpeakerDetail2023"; import { useParams } from "react-router"; import { StyledContainer, StyledWaveContainer } from "./Speaker.style"; import conferenceData from "../../data/2023.json"; -import * as Sentry from "@sentry/react"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const SpeakerDetailContainer2023: FC> = () => { const { id } = useParams<{ id: string }>(); @@ -17,14 +18,10 @@ const SpeakerDetailContainer2023: FC> = () => { id, ); - if (error) { - Sentry.captureException(error); - } - React.useEffect(() => { - if (data) { - document.title = `${data[0]?.fullName} - DevBcn - ${conferenceData.edition}`; - } - }, [id, data]); + useSentryErrorReport(error); + + useDocumentTitleUpdater(data?.[0]?.fullName ?? "", conferenceData.edition); + return ( diff --git a/src/2023/Speakers/Speakers2023.tsx b/src/2023/Speakers/Speakers2023.tsx index 4ae3b68b..5b71be6c 100644 --- a/src/2023/Speakers/Speakers2023.tsx +++ b/src/2023/Speakers/Speakers2023.tsx @@ -1,6 +1,6 @@ import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; import { Color } from "../../styles/colors"; -import { FC, useCallback, useEffect } from "react"; +import { FC, useCallback } from "react"; import LessThanBlueIcon from "../../assets/images/LessThanBlueIcon.svg"; import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; @@ -20,9 +20,10 @@ import { import webData from "../../data/2023.json"; import Button from "../../components/UI/Button"; import { gaEventTracker } from "../../components/analytics/Analytics"; -import * as Sentry from "@sentry/react"; import { ISpeaker } from "../../types/speakers"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const LessThanGreaterThan = (props: { width: number }) => ( <> @@ -45,17 +46,13 @@ const Speakers2023: FC> = () => { `${webData.sessionizeUrl}/view/Speakers`, ); - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); const trackCFP = useCallback(() => { gaEventTracker("CFP", "CFP"); }, []); - useEffect(() => { - document.title = `Speakers2023 - DevBcn ${webData.edition}`; - }); + useDocumentTitleUpdater("Speakers 2023", webData.edition); const CFPStartDay = new Date(webData.cfp.startDay); const CFPEndDay = new Date(webData.cfp.endDay); diff --git a/src/2023/TalkDetail/TalkDetail.tsx b/src/2023/TalkDetail/TalkDetail.tsx index e8ec2f5d..a52e14a7 100644 --- a/src/2023/TalkDetail/TalkDetail.tsx +++ b/src/2023/TalkDetail/TalkDetail.tsx @@ -4,7 +4,7 @@ import { MOBILE_BREAKPOINT, } from "../../constants/BreakPoints"; import {Color} from "../../styles/colors"; -import React, {FC, Suspense, useEffect} from "react"; +import React, { FC, Suspense } from "react"; import LessThanIconWhite from "../../assets/images/LessThanIconWhite.svg"; import LessThanIcon from "../../assets/images/LessThanBlueIcon.svg"; import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg"; @@ -33,8 +33,9 @@ import { ROUTE_2023_TALKS, } from "../../constants/routes"; import conferenceData from "../../data/2023.json"; -import {Tag} from "../../components/Tag/Tag"; -import {IMeetingDetailProps, MyType} from "../../types/sessions"; +import { Tag } from "../../components/Tag/Tag"; +import { IMeetingDetailProps, MyType } from "../../types/sessions"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const getVideoHeight = (windowWidth: number) => { let videoHeight; @@ -102,9 +103,7 @@ const TalkDetail: FC> = ({ }) => { const { width } = useWindowSize(); - useEffect(() => { - document.title = `${meeting.title} - DevBcn ${conferenceData.edition}`; - }, [meeting.title]); + useDocumentTitleUpdater(meeting.title, conferenceData.edition); const finalMeetingInfo: MyType = { ...meeting, diff --git a/src/2023/TalkDetail/TalkDetailContainer2023.tsx b/src/2023/TalkDetail/TalkDetailContainer2023.tsx index 2d29cc21..f36bbb30 100644 --- a/src/2023/TalkDetail/TalkDetailContainer2023.tsx +++ b/src/2023/TalkDetail/TalkDetailContainer2023.tsx @@ -1,17 +1,18 @@ import { Color } from "../../styles/colors"; -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import NotFoundError from "../../components/NotFoundError/NotFoundError"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import styled from "styled-components"; import { useParams } from "react-router"; import conferenceData from "../../data/2023.json"; import { useFetchTalksById } from "../Talks/UseFetchTalks"; -import * as Sentry from "@sentry/react"; import { Session } from "../../types/sessions"; import TalkDetail from "./TalkDetail"; import { ISpeaker } from "../../types/speakers"; import { sessionAdapter } from "../../services/sessionsAdapter"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` background-color: ${Color.WHITE}; @@ -35,15 +36,8 @@ const TalkDetailContainer2023: FC> = () => { const adaptedMeeting = sessionAdapter(data?.at(0)); - useEffect(() => { - document.title = `${data?.at(0)?.title} - DevBcn - ${ - conferenceData.edition - }`; - }, [data]); - - if (error) { - Sentry.captureException(error); - } + useDocumentTitleUpdater(data?.at(0)?.title ?? "", conferenceData.edition); + useSentryErrorReport(error); return ( diff --git a/src/2023/Talks/Talks2023.tsx b/src/2023/Talks/Talks2023.tsx index b7759c44..f0a6554b 100644 --- a/src/2023/Talks/Talks2023.tsx +++ b/src/2023/Talks/Talks2023.tsx @@ -13,11 +13,12 @@ import { StyledWaveContainer, } from "./Talks.style"; 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 { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; import TrackInformation from "../../components/Talk/TrackInformation"; interface TrackInfo { @@ -27,7 +28,7 @@ interface TrackInfo { const Talks2023: FC> = () => { const [selectedGroupId, setSelectedGroupId] = useState( - null + null, ); const { isLoading, error, data } = useFetchTalks(); @@ -36,9 +37,6 @@ const Talks2023: FC> = () => { sessionStorage.getItem("selectedGroupCode"); const sessionSelectedGroupName = sessionStorage.getItem("selectedGroupName"); - - document.title = `Talks - DevBcn - ${conferenceData.edition}`; - if (sessionSelectedGroupCode && sessionSelectedGroupName) { setSelectedGroupId({ name: sessionSelectedGroupName, @@ -47,9 +45,8 @@ const Talks2023: FC> = () => { } }, []); - if (error) { - Sentry.captureException(error); - } + useDocumentTitleUpdater("Talks ", conferenceData.edition); + useSentryErrorReport(error); const dropDownOptions = [ { name: "All Tracks", code: undefined }, diff --git a/src/2023/Workshops/Workshops2023.tsx b/src/2023/Workshops/Workshops2023.tsx index f80ac7d3..14d7cf0f 100644 --- a/src/2023/Workshops/Workshops2023.tsx +++ b/src/2023/Workshops/Workshops2023.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import { Color } from "../../styles/colors"; import { @@ -12,18 +12,19 @@ import LessThanDarkBlueIcon from "../../assets/images/LessThanDarkBlueIcon.svg"; import TitleSection from "../../components/SectionTitle/TitleSection"; import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg"; import { useFetchTalks } from "../Talks/UseFetchTalks"; -import * as Sentry from "@sentry/react"; +import { TalkCard } from "../Talks/components/TalkCard"; import conferenceData from "../../data/2023.json"; import styled from "styled-components"; import { BIG_BREAKPOINT } from "../../constants/BreakPoints"; -import {TalkCard} from "../../components/Talk/TalkCard"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledSection = styled.section` - { +{ display: flex; padding: 0 10rem; flex-wrap: wrap; - } +} @media (max-width: ${BIG_BREAKPOINT}px) { padding: 1rem; @@ -37,9 +38,7 @@ const StyledSection = styled.section` `; const Workshops2023: FC> = () => { const { isLoading, data, error } = useFetchTalks(); - useEffect(() => { - document.title = `Workshops - DevBcn - ${conferenceData.edition}`; - }, []); + useDocumentTitleUpdater("Workshops", conferenceData.edition); //region workshops const categoryId = 149213; @@ -49,14 +48,12 @@ const Workshops2023: FC> = () => { ?.flatMap((group) => group.sessions) .filter((session) => session.categories.some((category) => - category.categoryItems.some((item) => categoryItemIds.has(item.id)) - ) + category.categoryItems.some((item) => categoryItemIds.has(item.id)), + ), ); //endregion - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); return ( <> diff --git a/src/2024/Cfp/CfpSection.test.tsx b/src/2024/Cfp/CfpSection.test.tsx index 43f2ea26..a97bcce1 100644 --- a/src/2024/Cfp/CfpSection.test.tsx +++ b/src/2024/Cfp/CfpSection.test.tsx @@ -9,7 +9,7 @@ describe("CfpSection", () => { it("sets document title on mount", () => { render(); expect(document.title).toBe( - `CFP Committee β€” ${conferenceData.title}β€” ${conferenceData.edition}`, + `CFP Committee β€” DevBcn - Barcelona Developers Conference β€” ${conferenceData.edition}`, ); }); diff --git a/src/2024/Cfp/CfpSection2024.tsx b/src/2024/Cfp/CfpSection2024.tsx index 2740852e..e20dba77 100644 --- a/src/2024/Cfp/CfpSection2024.tsx +++ b/src/2024/Cfp/CfpSection2024.tsx @@ -18,9 +18,10 @@ import conferenceData from "../../data/2024.json"; import { CfpTrackProps, data } from "./CfpData"; import { MemberName, TrackName } from "./Cfp.style"; import { - StyledAboutImage, - StyledSocialIconsWrapper + StyledAboutImage, + StyledSocialIconsWrapper, } from "../../views/About/components/Style.AboutCard"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; export const CfpTrackComponent: FC> = ({ track, @@ -62,35 +63,33 @@ export const CfpTrackComponent: FC> = ({ const CfpSection2024: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `CFP Committee β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, []); - return ( - <> - - - + + + - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - {data.map((track) => ( - - ))} - -
 
- - ); + color={Color.BLUE} + /> + {width > MOBILE_BREAKPOINT && ( + <> + + + + )} +
+ {data.map((track) => ( + + ))} +
+
 
+ + ); }; export default CfpSection2024; diff --git a/src/2024/HomeWrapper2024.tsx b/src/2024/HomeWrapper2024.tsx index 5668c53f..79ef0889 100644 --- a/src/2024/HomeWrapper2024.tsx +++ b/src/2024/HomeWrapper2024.tsx @@ -1,45 +1,47 @@ -import React, {FC, useState} from "react"; +import React, { FC, useState } from "react"; import styled from "styled-components"; -import {useLocation} from "react-router"; -import {BIG_BREAKPOINT} from "../constants/BreakPoints"; +import { useLocation } from "react-router"; +import { BIG_BREAKPOINT } from "../constants/BreakPoints"; -import {useEventEdition} from "../views/Home/UseEventEdition"; +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 "../types/types"; +import { Edition } from "../types/types"; +import { useDocumentTitleUpdater } from "../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` - padding-bottom: 10rem; + padding-bottom: 10rem; - @media only screen and (max-width: ${BIG_BREAKPOINT}px) { - padding-bottom: 20rem; - } + @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 ( - - - - - - - ); + const { hash } = useLocation(); + const [edition, setEdition] = useState(); + + useEventEdition(setEdition); + React.useEffect(() => { + if (hash != null && hash !== "") { + const scroll = document.getElementById(hash.substring(1)); + scroll?.scrollIntoView(); + } + }, [hash, edition]); + + useDocumentTitleUpdater("Home", edition?.edition ?? "2024"); + + return ( + + + + + + + ); }; diff --git a/src/2024/SpeakerDetail/SpeakerDetail.tsx b/src/2024/SpeakerDetail/SpeakerDetail.tsx index eabc6c61..1b655449 100644 --- a/src/2024/SpeakerDetail/SpeakerDetail.tsx +++ b/src/2024/SpeakerDetail/SpeakerDetail.tsx @@ -1,172 +1,164 @@ -import {BIG_BREAKPOINT} from "../../constants/BreakPoints"; +import { BIG_BREAKPOINT } from "../../constants/BreakPoints"; -import {FC, Suspense, useEffect} from "react"; +import { FC, Suspense } 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 { useWindowSize } from "react-use"; -import {ROUTE_SPEAKERS, ROUTE_TALK_DETAIL} from "../../constants/routes"; -import {Link} from "react-router"; -import {Color} from "../../styles/colors"; +import { ROUTE_SPEAKERS, ROUTE_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 + 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 { StyledTalkDescription } from "../../views/SpeakerDetail/SpeakerDetail.style"; +import { ISpeaker } from "../../types/speakers"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; interface ISpeakerDetailProps { - speaker: ISpeaker; + speaker: ISpeaker; } -const SpeakerDetail: FC> = ({speaker}) => { - const {width} = useWindowSize(); +const SpeakerDetail: FC> = ({ + speaker, +}) => { + const { width } = useWindowSize(); - useEffect(() => { - document.title = `${speaker.fullName} β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, [speaker.fullName]); + useDocumentTitleUpdater(speaker.fullName, conferenceData.edition); - const hasSessions = (): boolean => - (speaker.sessions && speaker.sessions.length > 0) || false; + 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} + 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} - - -
  • - ))} -
- - )} + {hasSessions() && ( + <> +

Sessions

+
    + {speaker?.sessions?.map((session) => ( +
  • + + + session + {session.name} + + +
  • + ))} +
+ + )} - - Go back - -
- - - -
-
-
-
- ); + + Go back + +
+ + + +
+
+
+
+ ); }; export default SpeakerDetail; diff --git a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx index 7c6177ef..70c6e072 100644 --- a/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx +++ b/src/2024/SpeakerDetail/SpeakerDetailContainer2024.tsx @@ -5,10 +5,11 @@ import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import SpeakerDetail from "./SpeakerDetail"; import { useParams } from "react-router"; import conferenceData from "../../data/2024.json"; -import * as Sentry from "@sentry/react"; import { StyledContainer } from "../../views/SpeakerDetail/Speaker.style"; import { StyledWaveContainer } from "../../views/Talks/Talks.style"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const SpeakerDetailContainer2024: FC> = () => { const { id } = useParams<{ id: string }>(); @@ -18,14 +19,9 @@ const SpeakerDetailContainer2024: FC> = () => { id, ); - if (error) { - Sentry.captureException(error); - } - React.useEffect(() => { - if (data) { - document.title = `${data[0]?.fullName} - DevBcn - ${conferenceData.edition}`; - } - }, [id, data]); + useSentryErrorReport(error); + + useDocumentTitleUpdater(data?.[0]?.fullName ?? "", conferenceData.edition); return ( diff --git a/src/2024/Speakers/Speakers2024.tsx b/src/2024/Speakers/Speakers2024.tsx index 751fb53e..830bbb11 100644 --- a/src/2024/Speakers/Speakers2024.tsx +++ b/src/2024/Speakers/Speakers2024.tsx @@ -1,6 +1,6 @@ import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; import { Color } from "../../styles/colors"; -import React, { FC, useCallback, useEffect } from "react"; +import React, { FC, useCallback } from "react"; import LessThanBlueIcon from "../../assets/images/LessThanBlueIcon.svg"; import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; @@ -19,10 +19,11 @@ import { import webData from "../../data/2024.json"; import Button from "../../components/UI/Button"; import { gaEventTracker } from "../../components/analytics/Analytics"; -import * as Sentry from "@sentry/react"; import { SpeakerCard } from "../../views/Speakers/components/SpeakersCard"; import { ISpeaker } from "../../types/speakers"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const LessThanGreaterThan = (props: { width: number }) => ( <> @@ -45,17 +46,13 @@ const Speakers2024: FC> = () => { `${webData.sessionizeUrl}/view/Speakers`, ); - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); const trackCFP = useCallback(() => { gaEventTracker("CFP", "CFP"); }, []); - useEffect(() => { - document.title = `Speakers β€” ${webData.title} β€” ${webData.edition}`; - }); + useDocumentTitleUpdater("Speakers", webData.edition); const CFPStartDay = new Date(webData.cfp.startDay); const CFPEndDay = new Date(webData.cfp.endDay); diff --git a/src/2024/SpeakersCarousel/SpeakerSwiper.tsx b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx index e0ad9550..661d401e 100644 --- a/src/2024/SpeakersCarousel/SpeakerSwiper.tsx +++ b/src/2024/SpeakersCarousel/SpeakerSwiper.tsx @@ -6,10 +6,14 @@ import "swiper/swiper-bundle.min.css"; import "./SpeakersCarousel.scss"; import { Link } from "react-router"; import conferenceData from "../../data/2024.json"; -import * as Sentry from "@sentry/react"; import { Color } from "../../styles/colors"; import { ROUTE_SPEAKER_DETAIL } from "../../constants/routes"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import {ISpeaker} from "../../types/speakers"; +import { + shuffleArray +} from "../../2023/Home/components/SpeakersCarousel/SpeakerSwiper"; const StyledSlideImage = styled.img` display: block; @@ -64,9 +68,7 @@ const swiperSpeakers = React.useMemo(() => { return speakersCopy.slice(0, 20); }, [data]); - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); return ( <> diff --git a/src/2024/TalkDetail/MeetingDetail.tsx b/src/2024/TalkDetail/MeetingDetail.tsx index 163e0ab5..4be33684 100644 --- a/src/2024/TalkDetail/MeetingDetail.tsx +++ b/src/2024/TalkDetail/MeetingDetail.tsx @@ -1,301 +1,301 @@ import { - BIG_BREAKPOINT, - LARGE_BREAKPOINT, - MOBILE_BREAKPOINT, + BIG_BREAKPOINT, + LARGE_BREAKPOINT, + MOBILE_BREAKPOINT, } from "../../constants/BreakPoints"; -import {Color} from "../../styles/colors"; -import React, {FC, Suspense, useEffect} from "react"; +import { Color } from "../../styles/colors"; +import React, { FC, Suspense } 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 { useWindowSize } from "react-use"; -import {Link} from "react-router"; +import { Link } from "react-router"; import { - ROUTE_2024_SPEAKER_DETAIL, - ROUTE_2024_TALKS, + ROUTE_2024_SPEAKER_DETAIL, + ROUTE_2024_TALKS, } from "../../constants/routes"; import conferenceData from "../../data/2024.json"; -import {Tag} from "../../components/Tag/Tag"; +import { Tag } from "../../components/Tag/Tag"; import styled from "styled-components"; -import {AddToCalendarButton} from "add-to-calendar-button-react"; +import { AddToCalendarButton } from "add-to-calendar-button-react"; import { - StyledContainer, - StyledDetailsContainer, - StyledFlexCol, - StyledName, - StyledNameContainer, - StyledRightContainer, - StyledSpeakerDetailContainer + StyledContainer, + StyledDetailsContainer, + StyledFlexCol, + StyledName, + StyledNameContainer, + StyledRightContainer, + StyledSpeakerDetailContainer, } from "../../views/SpeakerDetail/Speaker.style"; import { - StyledDescription, - StyledExtraInfo, - StyledLessThan, - StyledMeetingTitleContainer, - StyledTitleImg, - StyledVideoContainer, - StyledVideoTagsContainer + 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 { StyledTitle } from "../Home/Style.Home"; +import { ISpeaker } from "../../types/speakers"; +import { IMeeting } from "../../types/sessions"; +import { useDocumentTitleUpdater } from "../../services/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; - } + 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(); + return videoHeight.toString(); }; const leftVariants = { - initial: { - x: -100, - opacity: 0, - }, - animate: { - x: 0, - opacity: 1, - }, + initial: { + x: -100, + opacity: 0, + }, + animate: { + x: 0, + opacity: 1, + }, }; const rightVariants = { - initial: { - x: 100, - opacity: 0, - }, - animate: { - x: 0, - opacity: 1, - }, + initial: { + x: 100, + opacity: 0, + }, + animate: { + x: 0, + opacity: 1, + }, }; const downVariants = { - initial: { - y: 100, - opacity: 0, - }, - animate: { - y: 0, - opacity: 1, - }, + initial: { + y: 100, + opacity: 0, + }, + animate: { + y: 0, + opacity: 1, + }, }; const opacityVariants = { - initial: { - opacity: 0, - }, - animate: { - opacity: 1, - transition: { - duration: 1, - }, + 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; + text-decoration: none; + color: ${Color.BLACK_BLUE}; + font-size: 0.8rem; `; interface IMeetingDetailProps { - meeting: IMeeting; - speakers?: ISpeaker[]; + 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; + 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(); + meeting, + speakers: mySpeakers, +}) => { + const { width } = useWindowSize(); - useEffect(() => { - document.title = `${meeting.title} β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, [meeting.title]); + useDocumentTitleUpdater(meeting.title, conferenceData.edition); - const finalMeetingInfo: MyType = { - ...meeting, - speakers: mySpeakers, - }; + const finalMeetingInfo: MyType = { + ...meeting, + speakers: mySpeakers, + }; - return ( - - - - - - / {meeting.title} -

Description

- {meeting.description} - - {`${meeting.type} ${meeting.level}`} - Track: - {meeting.track} + 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 - {" "} -
-
-
- ); + + {" "} + 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/MeetingDetailContainer.tsx b/src/2024/TalkDetail/MeetingDetailContainer.tsx index 0e049ca2..da60e456 100644 --- a/src/2024/TalkDetail/MeetingDetailContainer.tsx +++ b/src/2024/TalkDetail/MeetingDetailContainer.tsx @@ -1,18 +1,19 @@ import { Color } from "../../styles/colors"; -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import NotFoundError from "../../components/NotFoundError/NotFoundError"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import styled from "styled-components"; import { useParams } from "react-router"; import conferenceData from "../../data/2024.json"; import { useFetchTalksById } from "../Talks/UseFetchTalks"; -import * as Sentry from "@sentry/react"; import MeetingDetail from "./MeetingDetail"; import { ISpeaker } from "../../types/speakers"; import { Session } from "../../types/sessions"; import { sessionAdapter } from "../../services/sessionsAdapter"; import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` background-color: ${Color.WHITE}; @@ -33,18 +34,11 @@ const MeetingDetailContainer: FC> = () => { const sessionSpeakers: ISpeaker[] | undefined = speakerData?.filter( (speaker) => talkSpeakers?.includes(speaker.id), ); + useDocumentTitleUpdater(data?.at(0)?.title ?? "", conferenceData.edition); const adaptedMeeting = sessionAdapter(data?.at(0)); - useEffect(() => { - document.title = `${data?.at(0)?.title} - DevBcn - ${ - conferenceData.edition - }`; - }, [data]); - - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); return ( diff --git a/src/2024/Talks/LiveView.tsx b/src/2024/Talks/LiveView.tsx index e0240b0e..9028a490 100644 --- a/src/2024/Talks/LiveView.tsx +++ b/src/2024/Talks/LiveView.tsx @@ -1,65 +1,60 @@ -import React, {FC, useCallback, useEffect, useMemo} from "react"; -import {useFetchLiveView} from "./UseFetchTalks"; +import React, { FC, useCallback, 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"; +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"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; 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.title} - {conference.edition} Edition -

- - {isLoading && } -
Live Schedule
- {!isBetween(today, conference.startDay, conference.endDay) && ( -

The live schedule is not ready yet

- )} - {filteredTalks?.map((session) => ( - - ))} -
- ); + 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]); + + useDocumentTitleUpdater("Live view", conference.edition); + + useSentryErrorReport(error); + + return ( + + {conference.title} +

+ {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/Talks2024.tsx b/src/2024/Talks/Talks2024.tsx index c860a590..566572ee 100644 --- a/src/2024/Talks/Talks2024.tsx +++ b/src/2024/Talks/Talks2024.tsx @@ -7,7 +7,6 @@ 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"; @@ -20,6 +19,8 @@ import { StyledWaveContainer } from "../../views/Talks/Talks.style"; import TrackInformation from "../../components/Talk/TrackInformation"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; interface TrackInfo { name: string; @@ -33,112 +34,108 @@ const Talks2024: FC> = () => { const {isLoading, error, data} = useFetchTalks(); useEffect(() => { - const sessionSelectedGroupCode = - sessionStorage.getItem("selectedGroupCode"); - const sessionSelectedGroupName = - sessionStorage.getItem("selectedGroupName"); + 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 (sessionSelectedGroupCode && sessionSelectedGroupName) { + setSelectedGroupId({ + name: sessionSelectedGroupName, + code: sessionSelectedGroupCode, + }); + } }, []); - if (error) { - Sentry.captureException(error); - } + useDocumentTitleUpdater("Talks", conferenceData.edition); + + useSentryErrorReport(error); const dropDownOptions = [ - {name: "All Tracks", code: undefined}, - ...(data !== undefined - ? data.flatMap((group) => ({ - code: group.groupId.toString(), - name: group.groupName, - })) - : []), + { 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; + ? 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 || ""); + 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) => ( - - ))} - - ) - )} -
- -
- + color={Color.WHITE} + /> + + + +
+ + + + + + +
+ {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) => ( + + ))} + + ) + )} +
+ +
+ ); }; diff --git a/src/components/NotFoundError/NotFoundError.tsx b/src/components/NotFoundError/NotFoundError.tsx index 72b76e2f..32cf74be 100644 --- a/src/components/NotFoundError/NotFoundError.tsx +++ b/src/components/NotFoundError/NotFoundError.tsx @@ -1,10 +1,11 @@ import { Color } from "../../styles/colors"; -import { FC, useEffect } from "react"; +import { FC } from "react"; import { Link } from "react-router"; import SectionWrapper from "../SectionWrapper/SectionWrapper"; import styled from "styled-components"; import ActionButtons from "../../views/Home/components/ActionButtons/ActionButtons"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` display: flex; @@ -34,10 +35,10 @@ interface INotFoundErrorProps { message?: string; } -const NotFoundError: FC> = ({ message = "Page" }) => { - useEffect(() => { - document.title = "DevBcn - page not Found"; - }); +const NotFoundError: FC> = ({ + message = "Page", +}) => { + useDocumentTitleUpdater("Page not Found", ""); return ( diff --git a/src/components/Swiper/SpeakerSwiper.tsx b/src/components/Swiper/SpeakerSwiper.tsx index f22ff077..475bcb2e 100644 --- a/src/components/Swiper/SpeakerSwiper.tsx +++ b/src/components/Swiper/SpeakerSwiper.tsx @@ -1,111 +1,111 @@ -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 "../../views/Home/components/SpeakersCarousel/SpeakersCarousel.scss"; import conferenceData from "../../data/2025.json"; -import {useFetchSpeakers} from "../../views/Speakers/UseFetchSpeakers"; -import * as Sentry from "@sentry/react"; -import {ISpeaker} from "../../types/speakers"; -import {ROUTE_SPEAKER_DETAIL} from "../../constants/routes"; -import {Link} from "react-router"; +import { useFetchSpeakers } from "../../views/Speakers/UseFetchSpeakers"; +import { ISpeaker } from "../../types/speakers"; +import { ROUTE_SPEAKER_DETAIL } from "../../constants/routes"; +import { Link } from "react-router"; +import { useSentryErrorReport } from "../../../../services/useSentryErrorReport"; +import { shuffleArray } from "../../../../2023/Home/components/SpeakersCarousel/SpeakerSwiper"; const StyledSlideImage = styled.img` - display: block; - width: 100%; - aspect-ratio: 1/1; - border-radius: 10px; + 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%; + 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; + font-size: 0.875rem; + color: white; `; const SpeakerSwiper: FC> = () => { - const {isLoading, data, error} = useFetchSpeakers(); + const { isLoading, data, error } = useFetchSpeakers( + conferenceData.sessionizeUrl, + ); + const cachedSpeakers: ISpeaker[] = data + ? shuffleArray(data).slice(0, 20) + : []; - const cachedSpeakers = React.useMemo(() => { - return data?.toSorted(() => 0.5 - Math.random()).slice(0, 20); - }, [data]); + useSentryErrorReport(error); - if (error) { - Sentry.captureException(error); - } - - return ( - <> - {isLoading &&

Loading

} - {conferenceData.carrousel.enabled && cachedSpeakers && ( - - {cachedSpeakers.map((speaker:ISpeaker) => ( - - - - - {speaker.fullName} - - - - ))} - - )} - - ); + return ( + <> + {isLoading &&

Loading

} + {conferenceData.carrousel.enabled && cachedSpeakers && ( + + {cachedSpeakers.map((speaker: ISpeaker) => ( + + + + + {speaker.fullName} + + + + ))} + + )} + + ); }; export default SpeakerSwiper; diff --git a/src/services/useDocumentTitleUpdate.ts b/src/services/useDocumentTitleUpdate.ts new file mode 100644 index 00000000..74f63ae7 --- /dev/null +++ b/src/services/useDocumentTitleUpdate.ts @@ -0,0 +1,7 @@ +import React from "react"; + +export const useDocumentTitleUpdater = (title: string, year: string) => { + React.useEffect(() => { + document.title = `${title} β€” DevBcn - Barcelona Developers Conference β€” ${year}`; + }, [title, year]); +}; diff --git a/src/services/useSentryErrorReport.ts b/src/services/useSentryErrorReport.ts new file mode 100644 index 00000000..e44b641f --- /dev/null +++ b/src/services/useSentryErrorReport.ts @@ -0,0 +1,7 @@ +import * as Sentry from "@sentry/react"; + +export const useSentryErrorReport = (error: unknown) => { + if (error instanceof Error) { + Sentry.captureException(error); + } +}; diff --git a/src/views/About/About.tsx b/src/views/About/About.tsx index 1196ae85..dad94c65 100644 --- a/src/views/About/About.tsx +++ b/src/views/About/About.tsx @@ -16,6 +16,7 @@ import { } from "../Speakers/Speakers.style"; import { StyledMarginBottom } from "../Talks/Talks.style"; import data from "../../data/2024.json"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledUsersWrapper = styled.div` padding-top: 5rem; @@ -42,9 +43,7 @@ const StyledLink = styled.a` const About: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `About us β€” ${data.title} β€” ${data.edition}`; - }, []); + useDocumentTitleUpdater("About us", data.edition); return ( diff --git a/src/views/Attendee/AttendeeInformation.tsx b/src/views/Attendee/AttendeeInformation.tsx index 0bfac404..8f3fd65c 100644 --- a/src/views/Attendee/AttendeeInformation.tsx +++ b/src/views/Attendee/AttendeeInformation.tsx @@ -1,9 +1,10 @@ -import { FC, useEffect } from "react"; +import { FC } from "react"; import { Color } from "../../styles/colors"; import styled from "styled-components"; import { BIG_BREAKPOINT } from "../../constants/BreakPoints"; import data from "../../data/2024.json"; import { format } from "date-fns"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const PrePartyImg = styled.img` { @@ -64,9 +65,7 @@ const SectionWrapper = styled.div` const AttendeeInformation: FC> = () => { const formattedDate = format(new Date(data.startDay), "EEEE, MMMM do"); - useEffect(() => { - document.title = `Attendee information β€” ${data.title} β€” ${data.edition}`; - }, []); + useDocumentTitleUpdater("Attendee information", data.edition); return (
diff --git a/src/views/Cfp/CfpSection.test.tsx b/src/views/Cfp/CfpSection.test.tsx index 509eab3e..4cee0f4b 100644 --- a/src/views/Cfp/CfpSection.test.tsx +++ b/src/views/Cfp/CfpSection.test.tsx @@ -9,7 +9,7 @@ describe("CfpSection", () => { it("sets document title on mount", () => { render(); expect(document.title).toBe( - `CFP Committee β€” ${conferenceData.title}β€” ${conferenceData.edition}`, + `CFP Committee β€” DevBcn - Barcelona Developers Conference β€” ${conferenceData.edition}`, ); }); diff --git a/src/views/Cfp/CfpSection.tsx b/src/views/Cfp/CfpSection.tsx index 7e64f274..70854254 100644 --- a/src/views/Cfp/CfpSection.tsx +++ b/src/views/Cfp/CfpSection.tsx @@ -22,6 +22,7 @@ import { import conferenceData from "../../data/2025.json"; import {CfpTrackProps, data} from "./CfpData"; import {MemberName, TrackName} from "./Cfp.style"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; export const CfpTrackComponent: FC> = ({ track, @@ -63,42 +64,43 @@ export const CfpTrackComponent: FC> = ({ ); const CfpSection: FC> = () => { - const {width} = useWindowSize(); - React.useEffect(() => { - document.title = `CFP Committee β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, []); + const { width } = useWindowSize(); - const isCFPCommitteeReady = (): boolean => data.every((track) => track.members.length > 0) + useDocumentTitleUpdater("CFP Committee", conferenceData.edition); - return ( - <> - - - - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - {!isCFPCommitteeReady() && -

CFP Committee in - progress

} - {isCFPCommitteeReady() && data.map((track) => ( - - ))} -
-
 
- - ); + color={Color.BLUE} + /> + {width > MOBILE_BREAKPOINT && ( + <> + + + + )} + + {!isCFPCommitteeReady() && ( +

CFP Committee in progress

+ )} + {isCFPCommitteeReady() && + data.map((track) => ( + + ))} + +
 
+ + ); }; export default CfpSection; diff --git a/src/views/CodeOfConduct/CodeOfConduct.tsx b/src/views/CodeOfConduct/CodeOfConduct.tsx index fe46fdf3..9bb1e8f8 100644 --- a/src/views/CodeOfConduct/CodeOfConduct.tsx +++ b/src/views/CodeOfConduct/CodeOfConduct.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import TitleSection from "../../components/SectionTitle/TitleSection"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import { BIG_BREAKPOINT, MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; @@ -21,6 +21,7 @@ import { import { StyledMarginBottom, StyledTagsWrapper } from "../Talks/Talks.style"; import data from "../../data/2024.json"; import { format } from "date-fns"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledWaveContainer = styled.div` background: ${Color.DARK_BLUE}; @@ -82,9 +83,7 @@ const StyleMoreIcon = styled.img` export const CodeOfConduct: FC = () => { const { width } = useWindowSize(); - useEffect(() => { - document.title = `Code of Conduct β€” ${data.title} β€” ${data.edition}`; - }); + useDocumentTitleUpdater("Code of Conduct", data.edition); return ( <> diff --git a/src/views/Communities/Communities.tsx b/src/views/Communities/Communities.tsx index 1aa891ed..22896ef8 100644 --- a/src/views/Communities/Communities.tsx +++ b/src/views/Communities/Communities.tsx @@ -4,6 +4,7 @@ import TwitterIcon from "../../components/Icons/Twitter"; import { Color } from "../../styles/colors"; import WebsiteIcon from "../../components/Icons/website"; import data from "../../data/2024.json"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const Heading = styled.h1` { @@ -93,9 +94,8 @@ const FoSS = styled.div` `; const Communities: FC> = () => { - React.useEffect(() => { - document.title = `Communities β€” ${data.title} β€” ${data.edition}`; - }); + useDocumentTitleUpdater("Communities", data.edition); + return ( <> FOSS & Diversity Communities diff --git a/src/views/Conditions/Conditions.tsx b/src/views/Conditions/Conditions.tsx index 887cc439..4b846400 100644 --- a/src/views/Conditions/Conditions.tsx +++ b/src/views/Conditions/Conditions.tsx @@ -2,42 +2,42 @@ import React, { FC } from "react"; import styled from "styled-components"; import { Color } from "../../styles/colors"; import data from "../../data/2024.json"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledDiv = styled.div` - { +{ width: 70%; margin: 90px auto; - } +} - p { - text-align: justify; - margin: 5px; - } + p { + text-align: justify; + margin: 5px; + } - h1, - h2, - h3, - h4 { - text-align: left; - margin-top: 10px; - margin-bottom: 20px; - } + h1, + h2, + h3, + h4 { + text-align: left; + margin-top: 10px; + margin-bottom: 20px; + } - li { - margin-left: 30px; - text-align: left; - } + li { + margin-left: 30px; + text-align: left; + } - hr { - color: ${Color.DARK_BLUE}; - margin: 20px; - } + hr { + color: ${Color.DARK_BLUE}; + margin: 20px; + } `; const Conditions: FC> = () => { - React.useEffect(() => { - document.title = `Communities β€” ${data.title} β€” ${data.edition}`; - }); + useDocumentTitleUpdater("Communities", data.edition); + return (

TERMS AND CONDITIONS

diff --git a/src/views/Diversity/Diversity.tsx b/src/views/Diversity/Diversity.tsx index 2636d196..deb931fb 100644 --- a/src/views/Diversity/Diversity.tsx +++ b/src/views/Diversity/Diversity.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import { Color } from "../../styles/colors"; import data from "../../data/2024.json"; import styled from "styled-components"; @@ -8,6 +8,7 @@ import { ROUTE_CODE_OF_CONDUCT, ROUTE_CONDITIONS, } from "../../constants/routes"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledSection = styled.section` { @@ -93,9 +94,7 @@ const StyledParagraph = styled.section` `; const Diversity: FC> = () => { - useEffect(() => { - document.title = `Diversity β€” ${data.title} β€” ${data.edition}`; - }); + useDocumentTitleUpdater("Diversity", data.edition); return ( diff --git a/src/views/Home/HomeWrapper.tsx b/src/views/Home/HomeWrapper.tsx index 5e87ec7c..646fcfa3 100644 --- a/src/views/Home/HomeWrapper.tsx +++ b/src/views/Home/HomeWrapper.tsx @@ -9,6 +9,7 @@ import styled from "styled-components"; import {useLocation} from "react-router"; import {useEventEdition} from "./UseEventEdition"; import {Edition} from "../../types/types"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` padding-bottom: 10rem; @@ -24,13 +25,14 @@ const HomeWrapper: FC> = () => { 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]); + useDocumentTitleUpdater("Home", edition?.edition ?? "2025"); + return ( diff --git a/src/views/JobOffers/JobOffers.tsx b/src/views/JobOffers/JobOffers.tsx index 64f308cc..335de69e 100644 --- a/src/views/JobOffers/JobOffers.tsx +++ b/src/views/JobOffers/JobOffers.tsx @@ -17,6 +17,7 @@ import { StyledTitleContainer, } from "../../styles/JobOffers/JobOffers.Style"; import CompanyOffers from "../../components/JobOffers/CompanyOffers"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const NoOffersAvailable = () => ( <> @@ -40,9 +41,7 @@ const MoreThanLessThan = (props: { width: number }) => ( const JobOffers: FC> = () => { const { width } = useWindowSize(); - React.useEffect(() => { - document.title = `Job Offers - ${data.title} - ${data.edition}`; - }, []); + useDocumentTitleUpdater("Job Offers", data.edition); return ( diff --git a/src/views/MeetingDetail/MeetingDetail.tsx b/src/views/MeetingDetail/MeetingDetail.tsx index f56662af..5f38927d 100644 --- a/src/views/MeetingDetail/MeetingDetail.tsx +++ b/src/views/MeetingDetail/MeetingDetail.tsx @@ -4,7 +4,7 @@ import { MOBILE_BREAKPOINT, } from "../../constants/BreakPoints"; import {Color} from "../../styles/colors"; -import React, {FC, Suspense, useEffect} from "react"; +import React, { FC, Suspense } from "react"; import LessThanIconWhite from "../../assets/images/LessThanIconWhite.svg"; import LessThanIcon from "../../assets/images/LessThanBlueIcon.svg"; import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg"; @@ -32,8 +32,9 @@ import {ROUTE_SPEAKER_DETAIL, ROUTE_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 {IMeetingDetailProps, MyType} from "../../types/sessions"; +import { AddToCalendarButton } from "add-to-calendar-button-react"; +import { IMeetingDetailProps, MyType } from "../../types/sessions"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const getVideoHeight = (windowWidth: number) => { let videoHeight; @@ -107,9 +108,7 @@ const MeetingDetail: FC> = ({ }) => { const { width } = useWindowSize(); - useEffect(() => { - document.title = `${meeting.title} β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, [meeting.title]); + useDocumentTitleUpdater(meeting.title, conferenceData.edition); const finalMeetingInfo: MyType = { ...meeting, diff --git a/src/views/MeetingDetail/TalkDetailContainer2024.tsx b/src/views/MeetingDetail/TalkDetailContainer2024.tsx index 5c0a1b60..e0950d14 100644 --- a/src/views/MeetingDetail/TalkDetailContainer2024.tsx +++ b/src/views/MeetingDetail/TalkDetailContainer2024.tsx @@ -1,17 +1,18 @@ -import {Color} from "../../styles/colors"; -import React, {FC, useEffect} from "react"; +import { Color } from "../../styles/colors"; +import React, { FC } from "react"; import NotFoundError from "../../components/NotFoundError/NotFoundError"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import styled from "styled-components"; -import {useParams} from "react-router"; +import { useParams } from "react-router"; import conferenceData from "../../data/2024.json"; -import {useFetchTalksById} from "../Talks/UseFetchTalks"; -import * as Sentry from "@sentry/react"; -import {useFetchSpeakers} from "../Speakers/UseFetchSpeakers"; +import { useFetchTalksById } from "../Talks/UseFetchTalks"; +import { useFetchSpeakers } from "../Speakers/UseFetchSpeakers"; import MeetingDetail from "./MeetingDetail"; -import {ISpeaker} from "../../types/speakers"; -import {sessionAdapter} from "../../services/sessionsAdapter"; -import {Session} from "../../types/sessions"; +import { ISpeaker } from "../../types/speakers"; +import { sessionAdapter } from "../../services/sessionsAdapter"; +import { Session } from "../../types/sessions"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledContainer = styled.div` background-color: ${Color.WHITE}; @@ -35,15 +36,8 @@ const TalkDetailContainer2024: FC> = () => { const adaptedMeeting = sessionAdapter(data?.at(0)); - useEffect(() => { - document.title = `${data?.at(0)?.title} - DevBcn - ${ - conferenceData.edition - }`; - }, [data]); - - if (error) { - Sentry.captureException(error); - } + useDocumentTitleUpdater(data?.at(0)?.title ?? "", conferenceData.edition); + useSentryErrorReport(error); return ( diff --git a/src/views/Schedule/Schedule.tsx b/src/views/Schedule/Schedule.tsx index 984b44c7..05a2f566 100644 --- a/src/views/Schedule/Schedule.tsx +++ b/src/views/Schedule/Schedule.tsx @@ -10,6 +10,7 @@ import data from "../../data/2024.json"; import * as Sentry from "@sentry/react"; import { Link } from "react-router"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; import { StyledLessIcon, StyledMoreIcon, StyledScheduleSection @@ -19,8 +20,6 @@ const Schedule: FC> = () => { const { width } = useWindowSize(); React.useEffect(() => { - document.title = `Schedule β€” ${data.title} β€” ${data.edition}`; - fetch("https://sessionize.com/api/v2/w8mdb9k5/view/GridSmart") .then((value) => value.text()) .then((value) => { @@ -32,6 +31,8 @@ const Schedule: FC> = () => { .catch((err) => Sentry.captureException(err)); }, []); + useDocumentTitleUpdater("Schedule", data.edition); + return ( diff --git a/src/views/SessionFeedback/SessionFeedback.tsx b/src/views/SessionFeedback/SessionFeedback.tsx index 9ef9ee14..73855ec2 100644 --- a/src/views/SessionFeedback/SessionFeedback.tsx +++ b/src/views/SessionFeedback/SessionFeedback.tsx @@ -16,6 +16,7 @@ import { Color } from "../../styles/colors"; import { Link } from "react-router"; import { ROUTE_TALK_DETAIL } from "../../constants/routes"; import data from "../../data/2024.json"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const SessionFeedback: FC> = () => { const bodyTemplate = React.useCallback( @@ -65,9 +66,7 @@ const SessionFeedback: FC> = () => { ); - React.useEffect(() => { - document.title = `Session Feedback β€” ${data.title} - ${data.edition}`; - }); + useDocumentTitleUpdater("Session Feedback", data.edition); const header = renderHeader(); diff --git a/src/views/SpeakerDetail/SpeakerDetail.tsx b/src/views/SpeakerDetail/SpeakerDetail.tsx index b90d84f3..26e84432 100644 --- a/src/views/SpeakerDetail/SpeakerDetail.tsx +++ b/src/views/SpeakerDetail/SpeakerDetail.tsx @@ -1,6 +1,6 @@ import {BIG_BREAKPOINT} from "../../constants/BreakPoints"; -import {FC, Suspense, useEffect} from "react"; +import { FC, Suspense } from "react"; import MoreThanIcon from "../../assets/images/MoreThanBlueIcon.svg"; import LessThan from "../../assets/images/MoreThanIcon.svg"; import SlashesWhite from "../../assets/images/SlashesWhite.svg"; @@ -31,7 +31,8 @@ import {StyledTalkDescription} from "./SpeakerDetail.style"; import {Link} from "react-router"; import {Color} from "../../styles/colors"; import conferenceData from "../../data/2024.json"; -import {ISpeaker} from "../../types/speakers"; +import { ISpeaker } from "../../types/speakers"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; interface ISpeakerDetailProps { speaker: ISpeaker; @@ -40,9 +41,7 @@ interface ISpeakerDetailProps { const SpeakerDetail: FC> = ({ speaker }) => { const { width } = useWindowSize(); - useEffect(() => { - document.title = `${speaker.fullName} β€” ${conferenceData.title} β€” ${conferenceData.edition}`; - }, [speaker.fullName]); + useDocumentTitleUpdater(speaker.fullName, conferenceData.edition); const hasSessions = (): boolean => (speaker.sessions && speaker.sessions.length > 0) || false; diff --git a/src/views/SpeakerDetail/SpeakerDetailContainer.tsx b/src/views/SpeakerDetail/SpeakerDetailContainer.tsx index f9c21258..11c74623 100644 --- a/src/views/SpeakerDetail/SpeakerDetailContainer.tsx +++ b/src/views/SpeakerDetail/SpeakerDetailContainer.tsx @@ -7,45 +7,43 @@ import { useParams } from "react-router"; import { StyledContainer, StyledWaveContainer } from "./Speaker.style"; import conferenceData from "../../data/2024.json"; import { useFetchSpeakers } from "../Speakers/UseFetchSpeakers"; -import * as Sentry from "@sentry/react"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; 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" - )} -
- - - - - -
+ const { isLoading, error, data } = useFetchSpeakers( + conferenceData.sessionizeUrl, + id, ); + + useSentryErrorReport(error); + useDocumentTitleUpdater(data?.[0]?.fullName ?? "", conferenceData.edition); + return ( + + + {isLoading &&

Loading

} + {!isLoading && data && data.length > 0 ? ( + + ) : ( + "not found" + )} +
+ + + + + +
+ ); }; export default SpeakerDetailContainer; diff --git a/src/views/Speakers/Speakers.tsx b/src/views/Speakers/Speakers.tsx index 59fafd9c..c141efa1 100644 --- a/src/views/Speakers/Speakers.tsx +++ b/src/views/Speakers/Speakers.tsx @@ -1,6 +1,6 @@ import { MOBILE_BREAKPOINT } from "../../constants/BreakPoints"; import { Color } from "../../styles/colors"; -import { FC, useCallback, useEffect } from "react"; +import { FC, useCallback } from "react"; import LessThanBlueIcon from "../../assets/images/LessThanBlueIcon.svg"; import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; @@ -21,8 +21,9 @@ 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 { ISpeaker } from "../../types/speakers"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const LessThanGreaterThan = (props: { width: number }) => ( <> @@ -41,21 +42,15 @@ const Speakers: FC> = () => { const isBetween = (startDay: Date, endDay: Date): boolean => startDay < new Date() && endDay > today; - const { error, data, isLoading } = useFetchSpeakers( - `${webData.sessionizeUrl}/view/Speakers`, - ); + const { error, data, isLoading } = useFetchSpeakers(webData.sessionizeUrl); - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); const trackCFP = useCallback(() => { gaEventTracker("CFP", "CFP"); }, []); - useEffect(() => { - document.title = `Speakers β€” ${webData.title} β€” ${webData.edition}`; - }); + useDocumentTitleUpdater("Speakers", webData.edition); const CFPStartDay = new Date(webData.cfp.startDay); const CFPEndDay = new Date(webData.cfp.endDay); diff --git a/src/views/Speakers/UseFetchSpeakers.ts b/src/views/Speakers/UseFetchSpeakers.ts index 82d0a0bf..328b0209 100644 --- a/src/views/Speakers/UseFetchSpeakers.ts +++ b/src/views/Speakers/UseFetchSpeakers.ts @@ -4,11 +4,11 @@ import { speakerAdapter } from "../../services/speakerAdapter"; import { ISpeaker } from "../../types/speakers"; export const useFetchSpeakers = ( - url: string = "https://sessionize.com/api/v2/xhudniix/view/Speakers", + url: string = "https://sessionize.com/api/v2/xhudniix", id?: string, ): UseQueryResult => { return useQuery(["api-speakers", url], async () => { - const serverResponse = await axios.get(url); + const serverResponse = await axios.get(`${url}/view/Speakers`); let returnData; if (id !== undefined) { returnData = serverResponse.data.filter( diff --git a/src/views/Talks/LiveView.tsx b/src/views/Talks/LiveView.tsx index d54078ab..cb7df4a4 100644 --- a/src/views/Talks/LiveView.tsx +++ b/src/views/Talks/LiveView.tsx @@ -1,12 +1,13 @@ -import React, { FC, useCallback, useEffect, useMemo } from "react"; +import React, { FC, useCallback, useMemo } from "react"; import { useFetchLiveView } from "./UseFetchTalks"; import Loading from "../../components/Loading/Loading"; import { UngroupedSession } from "./liveView.types"; import conference from "../../data/2024.json"; import { TalkCard } from "./components/TalkCard"; -import * as Sentry from "@sentry/react"; import { StyledMain } from "./Talks.style"; import { talkCardAdapter } from "./TalkCardAdapter"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const LiveView: FC> = () => { const { isLoading, error, data } = useFetchLiveView(); @@ -29,15 +30,9 @@ const LiveView: FC> = () => { return data?.sessions?.filter(getPredicate()); }, [data, getPredicate]); - useEffect(() => { - document.title = `Live view - ${conference.title} - ${conference.edition} Edition`; - }, []); + useDocumentTitleUpdater("Live view - ", conference.edition); - useEffect(() => { - if (error) { - Sentry.captureException(error); - } - }, [error]); + useSentryErrorReport(error); return ( diff --git a/src/views/Talks/Talks.tsx b/src/views/Talks/Talks.tsx index 8682517c..23657bc6 100644 --- a/src/views/Talks/Talks.tsx +++ b/src/views/Talks/Talks.tsx @@ -14,11 +14,12 @@ import { } from "./Talks.style"; import TrackInformation from "./components/TrackInformation"; 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 { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; interface TrackInfo { name: string; @@ -37,8 +38,6 @@ const Talks: FC> = () => { const sessionSelectedGroupName = sessionStorage.getItem("selectedGroupName"); - document.title = `Talks - ${conferenceData.title} - ${conferenceData.edition}`; - if (sessionSelectedGroupCode && sessionSelectedGroupName) { setSelectedGroupId({ name: sessionSelectedGroupName, @@ -47,9 +46,8 @@ const Talks: FC> = () => { } }, []); - if (error) { - Sentry.captureException(error); - } + useDocumentTitleUpdater("Talks", conferenceData.edition); + useSentryErrorReport(error); const dropDownOptions = [ { name: "All Tracks", code: undefined }, diff --git a/src/views/Travel/Travel.tsx b/src/views/Travel/Travel.tsx index 591c5dc8..ea79c413 100644 --- a/src/views/Travel/Travel.tsx +++ b/src/views/Travel/Travel.tsx @@ -1,41 +1,39 @@ -import React, {FC, useEffect} from "react"; -import {Venue} from "./Venue"; -import {ToBarcelona} from "./ToBarcelona"; +import React, { FC } from "react"; +import { Venue } from "./Venue"; +import { ToBarcelona } from "./ToBarcelona"; import data from "../../data/2024.json"; -import {StyledWaveContainer} from "../Speakers/Speakers.style"; +import { StyledWaveContainer } from "../Speakers/Speakers.style"; import styled from "styled-components"; -import {Color} from "../../styles/colors"; -import {Accommodation} from "./Accommodation"; +import { Color } from "../../styles/colors"; +import { Accommodation } from "./Accommodation"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledTravel = styled.div` + max-width: 85rem; + margin-left: auto; + margin-right: auto; - max-width: 85rem; - margin-left: auto; - margin-right: auto; - - .top { - clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 50px)); - height: 51px; - background-color: ${Color.LIGHT_BLUE}; - border-top: 1px solid ${Color.LIGHT_BLUE}; - } + .top { + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 50px)); + height: 51px; + background-color: ${Color.LIGHT_BLUE}; + border-top: 1px solid ${Color.LIGHT_BLUE}; + } - .bottom { - clip-path: polygon(0 0, 100% 50px, 100% 100%, 0 100%); - margin-top: -50px; - height: 50px; - background-color: ${Color.DARK_BLUE}; - } + .bottom { + clip-path: polygon(0 0, 100% 50px, 100% 100%, 0 100%); + margin-top: -50px; + height: 50px; + background-color: ${Color.DARK_BLUE}; + } - .to-barcelona { - background-color: ${Color.DARK_BLUE}; - } + .to-barcelona { + background-color: ${Color.DARK_BLUE}; + } `; const Travel: FC> = () => { - useEffect(() => { - document.title = `Travel β€” ${data.title} β€” ${data.edition}`; - }, []); + useDocumentTitleUpdater("Travel", data.edition); return (
diff --git a/src/views/Workshops/Workshops.tsx b/src/views/Workshops/Workshops.tsx index d9b911b3..d7677f04 100644 --- a/src/views/Workshops/Workshops.tsx +++ b/src/views/Workshops/Workshops.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from "react"; +import React, { FC } from "react"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import { Color } from "../../styles/colors"; import { @@ -12,34 +12,34 @@ import LessThanDarkBlueIcon from "../../assets/images/LessThanDarkBlueIcon.svg"; import TitleSection from "../../components/SectionTitle/TitleSection"; import MoreThanBlueIcon from "../../assets/images/MoreThanBlueIcon.svg"; import { useFetchTalks } from "../Talks/UseFetchTalks"; -import * as Sentry from "@sentry/react"; import { TalkCard } from "../Talks/components/TalkCard"; import conferenceData from "../../data/2025.json"; import styled from "styled-components"; import { BIG_BREAKPOINT } from "../../constants/BreakPoints"; +import { useSentryErrorReport } from "../../services/useSentryErrorReport"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledSection = styled.section` - { +{ display: flex; padding: 0 10rem; flex-wrap: wrap; - } +} - @media (max-width: ${BIG_BREAKPOINT}px) { - padding: 1rem; - flex-direction: column; - } + @media (max-width: ${BIG_BREAKPOINT}px) { + padding: 1rem; + flex-direction: column; + } - & > div { - margin: 1rem; - min-width: 14%; - } + & > div { + margin: 1rem; + min-width: 14%; + } `; const Workshops: FC> = () => { const { isLoading, data, error } = useFetchTalks(); - useEffect(() => { - document.title = `Workshops - DevBcn - ${conferenceData.edition}`; - }, []); + + useDocumentTitleUpdater("Workshops ", conferenceData.edition); //region workshops const workshopCategoryList = new Set([149213]); @@ -55,9 +55,7 @@ const Workshops: FC> = () => { ); //endregion - if (error) { - Sentry.captureException(error); - } + useSentryErrorReport(error); return ( <> diff --git a/src/views/kcd/Kcd.tsx b/src/views/kcd/Kcd.tsx index 36cf598e..1e038036 100644 --- a/src/views/kcd/Kcd.tsx +++ b/src/views/kcd/Kcd.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import SectionWrapper from "../../components/SectionWrapper/SectionWrapper"; import { Color } from "../../styles/colors"; import { @@ -17,6 +16,7 @@ import { useWindowSize } from "react-use"; import youtube from "../../assets/images/youtube.svg"; import linkedinIcon from "../../assets/images/linkedinIcon.svg"; import twitterIcon from "../../assets/images/twitterIcon.svg"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; const StyledParagraph = styled.p` color: white; @@ -60,9 +60,8 @@ const StyledImage = styled.img` export default function Kcd() { const { width } = useWindowSize(); - useEffect(() => { - document.title = `KCD Barcelona β€” ${data.title} β€” ${data.edition}`; - }); + + useDocumentTitleUpdater("KCD Barcelona", data.edition); return ( <> @@ -84,7 +83,11 @@ export default function Kcd() { - + KCD Barcelona {" "} is an event within the CNCF framework called Kubernetes Community Days diff --git a/src/views/sponsorship/Sponsorship.tsx b/src/views/sponsorship/Sponsorship.tsx index 8b6b65b7..f144f3a3 100644 --- a/src/views/sponsorship/Sponsorship.tsx +++ b/src/views/sponsorship/Sponsorship.tsx @@ -1,347 +1,337 @@ -import {FC, useEffect} from "react"; +import { FC } 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 { StyledMarginBottom } from "../Talks/Talks.style"; import data from "../../data/2025.json"; -import {format} from "date-fns"; +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"; import Button from "../../components/UI/Button"; -import {gaEventTracker} from "../../components/analytics/Analytics"; - +import { gaEventTracker } from "../../components/analytics/Analytics"; +import { useDocumentTitleUpdater } from "../../services/useDocumentTitleUpdate"; 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}) - ]; + const { width } = useWindowSize(); + const plugins = [ + new AutoPlay({ duration: 2000, direction: "NEXT", stopOnHover: false }), + ]; - const handleCLick = () => { - gaEventTracker("download brochure", "download brochure"); - }; + const handleCLick = () => { + gaEventTracker("download brochure", "download brochure"); + }; - useEffect(() => { - document.title = `Sponsorship β€” ${data.title} β€” ${data.edition}`; - }); + useDocumentTitleUpdater("Sponsorship", data.edition); - return ( -
- - - - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - - - - - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - DevBcn 2023 - sponsors - - - - + + + + {width > MOBILE_BREAKPOINT && ( + <> + + + + )} + + + + + + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + DevBcn 2023 - sponsors + + + + + + + + + {width > MOBILE_BREAKPOINT && ( + <> + + + + )} + + +

Mark Your Calendars!

+

+ DevBcn {data?.edition} is set for{" "} + + {format(new Date(data.startDay), "MMMM do")} β€” + {" ".concat(format(data.endDay, "do"))} + {" "} + 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{" "} + + - - - - - {width > MOBILE_BREAKPOINT && ( - <> - - - - )} - - -

Mark Your Calendars!

-

- DevBcn {data?.edition} is set for {format(new Date(data.startDay),"MMMM do")} β€” - {" ".concat(format(data.endDay,"do"))} 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}. -

-
- ); + detailed brochure + {" "} + {" "} + at and discover the myriad of ways you can shine at DevBcn{" "} + {data?.edition}. +

+