diff --git a/src/app/[season]/layout.tsx b/src/app/[season]/layout.tsx index 2bc8625..ffb7973 100644 --- a/src/app/[season]/layout.tsx +++ b/src/app/[season]/layout.tsx @@ -1,5 +1,10 @@ 'use client'; +import { useAtom } from 'jotai'; +import Error from 'next/error'; + +import { serverErrorAtom } from '@/atoms/results'; + import { MainFilters } from '../ui/MainFilters'; // Default Next Layout @@ -8,12 +13,14 @@ export default function ResultsLayout({ }: { children: React.ReactNode; }) { + const [serverError] = useAtom(serverErrorAtom); + return ( <>
- {children} + {serverError ? : children} ); } diff --git a/src/app/[season]/page.tsx b/src/app/[season]/page.tsx index fa7e88c..b072698 100644 --- a/src/app/[season]/page.tsx +++ b/src/app/[season]/page.tsx @@ -3,7 +3,6 @@ import { useAtom } from 'jotai'; import { fetchStandings } from '@/atoms/fetchCalls'; -import { seasonAtom } from '@/atoms/seasons'; import { constructorStandingsAtom, driverStandingsAtom, @@ -20,12 +19,10 @@ import { Timeline, TimelineElement } from '../ui/Timeline'; export default function ResultsPage() { const [constructorStandings] = useAtom(constructorStandingsAtom); const [driverStandings] = useAtom(driverStandingsAtom); - const [season] = useAtom(seasonAtom); useAtom(fetchStandings); return (
-

{season}

{ // Handles hydration on page load useParamToSetAtoms(); + //! Handle Server Error to stop spinning + // useAtom(serverErrorAtom) + return (
diff --git a/src/atoms/fetchCalls.tsx b/src/atoms/fetchCalls.tsx index c2fb351..d5fb129 100644 --- a/src/atoms/fetchCalls.tsx +++ b/src/atoms/fetchCalls.tsx @@ -18,6 +18,7 @@ import { nextEventTimeAtom, } from './nextEvent'; import { raceAtom, seasonRacesAtom } from './races'; +import { serverErrorAtom } from './results'; import { allSeasonsAtom, seasonAtom } from './seasons'; import { allSessionsAtom, sessionAtom } from './sessions'; import { constructorStandingsAtom, driverStandingsAtom } from './standings'; @@ -50,12 +51,22 @@ export const fetchSchedule = atomEffect( set(seasonRacesAtom, null); const params = get(seasonAtom) && `?year=${get(seasonAtom)}`; - fetchAPI('schedule' + params).then((data) => { - set(seasonRacesAtom, data.EventSchedule); + fetchAPI('schedule' + params).then( + (res: DataConfigSchema['schedule'] | ServerErrorResponse) => { + const schedule = res as DataConfigSchema['schedule']; - // Sync default year with server - set(seasonAtom, data.year); - }); + const error = res as ServerErrorResponse; + if (error.detail) { + set(serverErrorAtom, error.detail[0].msg); + return; + } + + set(seasonRacesAtom, schedule.EventSchedule); + + // Sync default year with server + set(seasonAtom, schedule.year); + }, + ); }, // Dependencies: // seasonAtom @@ -87,7 +98,14 @@ export const fetchSessionResults = atomEffect((get, set) => { url += `?session=${sessionRound}`; } - fetchAPI(url).then((drivers: DriverResult[]) => { + fetchAPI(url).then((res: DriverResult[] | ServerErrorResponse) => { + const drivers = res as DriverResult[]; + + const error = res as ServerErrorResponse; + if (error.detail) { + set(serverErrorAtom, error.detail[0].msg); + return; + } // Formulate Constructors const constructors = formatConstructorResults(drivers); @@ -115,12 +133,21 @@ export const fetchStandings = atomEffect((get, set) => { const round = race === 'All Races' ? '' : `&round=${race.RoundNumber}`; // Fetch - fetchAPI('standings' + year + round).then( - ({ - DriverStandings, - ConstructorStandings, - }: DataConfigSchema['standings']) => { + fetchAPI('standings' + year + round) + .then((res: DataConfigSchema['standings'] | ServerErrorResponse) => { + const { DriverStandings, ConstructorStandings } = + res as DataConfigSchema['standings']; + + const error = res as ServerErrorResponse; + if (error.detail) { + set(serverErrorAtom, error.detail[0].msg); + + // setDefault('standings') + return; + } + // Include Drivers in Constructors Info + const constructors = ConstructorStandings.map((cs) => { const { name } = cs.Constructor; return { @@ -134,8 +161,8 @@ export const fetchStandings = atomEffect((get, set) => { // Update standings set(constructorStandingsAtom, constructors); set(driverStandingsAtom, DriverStandings); - }, - ); + }) + .catch((err) => err); // dependencies // seasonAtom @@ -147,22 +174,30 @@ export const fetchNextEvent = atomEffect( (get, set) => { // Next event do not change, only fetch if null if (!get(nextEventAtom)) { - fetchAPI('next-event').then((data: ScheduleSchema) => { - // Get session times - const now = Date.now(); - const nextEvent = formatNextEvent(data); - - if (nextEvent === 'No session') return; - - set(nextEventAtom, nextEvent); - - if (nextEvent.time < now) { - set(nextEventLiveAtom, true); - set(nextEventTimeAtom, now - nextEvent.endTime); - } else { - set(nextEventTimeAtom, nextEvent.time - now); - } - }); + fetchAPI('next-event').then( + (res: ScheduleSchema | ServerErrorResponse) => { + const data = res as ScheduleSchema; + const error = res as ServerErrorResponse; + if (error.detail) { + set(serverErrorAtom, error.detail[0].msg); + return; + } + // Get session times + const now = Date.now(); + const nextEvent = formatNextEvent(data); + + if (nextEvent === 'No session') return; + + set(nextEventAtom, nextEvent); + + if (nextEvent.time < now) { + set(nextEventLiveAtom, true); + set(nextEventTimeAtom, now - nextEvent.endTime); + } else { + set(nextEventTimeAtom, nextEvent.time - now); + } + }, + ); } }, // Dependencies: nextEventAtom diff --git a/src/atoms/results.tsx b/src/atoms/results.tsx index eac3103..7e24066 100644 --- a/src/atoms/results.tsx +++ b/src/atoms/results.tsx @@ -15,6 +15,9 @@ import { raceAtom, seasonRacesAtom } from './races'; import { seasonAtom } from './seasons'; import { allSessionsAtom, sessionAtom } from './sessions'; +// Server Error Atom +export const serverErrorAtom = atom(''); + // Telemetry Active export const telemetryDisableAtom = atom(true); // Telemetry is disabled if no race and driver are selected diff --git a/src/results.d.ts b/src/results.d.ts index 0699074..ef5927d 100644 --- a/src/results.d.ts +++ b/src/results.d.ts @@ -94,7 +94,10 @@ interface ConstructorResult { // UI format interface DataConfigSchema { seasons: string[]; - schedule: ScheduleSchema[]; + schedule: { + year: string; + EventSchedule: ScheduleSchema[]; + }; drivers: string[]; sessions: string[]; standings: { @@ -109,6 +112,16 @@ interface DataConfigSchema { }; } +interface ServerErrorResponse { + detail: [ + { + loc: string[]; + msg: string; + input: string; + }, + ]; +} + // UI Format Next Event interface NextEventProps { name: string; diff --git a/src/utils/fakerData.tsx b/src/utils/fakerData.tsx index 657c35a..ac6b36e 100644 --- a/src/utils/fakerData.tsx +++ b/src/utils/fakerData.tsx @@ -41,31 +41,34 @@ const driverResults: DriverResult[] = Array.from(Array(10).keys()).map(() => ({ export const dataConfig: DataConfigSchema = { seasons: f1Seasons(), - schedule: Array.from(Array(3).keys()).map(() => ({ - RoundNumber: 0, - Country: faker.location.country(), - Location: faker.location.city(), - OfficialEventName: faker.word.words(5), - EventDate: faker.date.future({ years: 1 }).toString(), - EventName: faker.word.words(3), - EventFormat: 'string', - Session1: 'string', - Session1Date: faker.date.future({ years: 1 }).toString(), - Session1DateUtc: faker.date.future({ years: 1 }).toString(), - Session2: 'string', - Session2Date: faker.date.future({ years: 1 }).toString(), - Session2DateUtc: faker.date.future({ years: 1 }).toString(), - Session3: 'string', - Session3Date: faker.date.future({ years: 1 }).toString(), - Session3DateUtc: faker.date.future({ years: 1 }).toString(), - Session4: 'string', - Session4Date: faker.date.future({ years: 1 }).toString(), - Session4DateUtc: faker.date.future({ years: 1 }).toString(), - Session5: 'string', - Session5Date: faker.date.future({ years: 1 }).toString(), - Session5DateUtc: faker.date.future({ years: 1 }).toString(), - F1ApiSupport: true, - })), + schedule: { + year: faker.number.int({ min: 1950, max: 2024 }).toString(), + EventSchedule: Array.from(Array(3).keys()).map(() => ({ + RoundNumber: 0, + Country: faker.location.country(), + Location: faker.location.city(), + OfficialEventName: faker.word.words(5), + EventDate: faker.date.future({ years: 1 }).toString(), + EventName: faker.word.words(3), + EventFormat: 'string', + Session1: 'string', + Session1Date: faker.date.future({ years: 1 }).toString(), + Session1DateUtc: faker.date.future({ years: 1 }).toString(), + Session2: 'string', + Session2Date: faker.date.future({ years: 1 }).toString(), + Session2DateUtc: faker.date.future({ years: 1 }).toString(), + Session3: 'string', + Session3Date: faker.date.future({ years: 1 }).toString(), + Session3DateUtc: faker.date.future({ years: 1 }).toString(), + Session4: 'string', + Session4Date: faker.date.future({ years: 1 }).toString(), + Session4DateUtc: faker.date.future({ years: 1 }).toString(), + Session5: 'string', + Session5Date: faker.date.future({ years: 1 }).toString(), + Session5DateUtc: faker.date.future({ years: 1 }).toString(), + F1ApiSupport: true, + })), + }, drivers: [ 'All Drivers', 'Drive 1', diff --git a/src/utils/helpers.tsx b/src/utils/helpers.tsx index 713f2c1..c8b31e0 100644 --- a/src/utils/helpers.tsx +++ b/src/utils/helpers.tsx @@ -122,7 +122,7 @@ export const fetchAPI = async ( const options = statusCheck ? { headers: { cache: 'no-store' } } : {}; // Get dummy data or return false - const dummy: string[] | ScheduleSchema[] | false = + const dummy: string[] | DataConfigSchema['schedule'] | false = dataConfig[ endpoint.split('?')[0] as 'seasons' | 'schedule' | 'drivers' | 'sessions' ] || false; @@ -154,8 +154,13 @@ export const fetchAPI = async ( .then((data) => data) // Catch errors from above .catch((err) => { + // Handle server not connecting error if (err === 'Server not connecting') return dummy; - if (err.status === 404) return dummy; + + // Handle api errors + if (err.cause.status === 422) { + return err.cause.json(); + } return dummy; });