diff --git a/src/app/(features)/LandingNextEvent.tsx b/src/app/(features)/LandingNextEvent.tsx
new file mode 100644
index 0000000..519f385
--- /dev/null
+++ b/src/app/(features)/LandingNextEvent.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { useAtom } from 'jotai';
+
+import { nextEventAtom, nextEventLiveAtom } from '@/atoms/nextEvent';
+import { formatSessionUrl } from '@/utils/transformers';
+
+import { EventCountDown } from '../ui/EventCountdown';
+
+export const LandingNextEvent = () => {
+ const [nextEvent] = useAtom(nextEventAtom);
+ const [liveEvent] = useAtom(nextEventLiveAtom);
+
+ return (
+
+ {nextEvent && (
+
+
{nextEvent.name}
+
+ {liveEvent ? (
+ nextEvent.session + ' Live Now'
+ ) : (
+ <>
+ {formatSessionUrl(nextEvent.session).toUpperCase()} in{' '}
+
+ >
+ )}
+
+
+
+ )}
+
+ );
+};
diff --git a/src/app/[season]/page.tsx b/src/app/[season]/page.tsx
index a23f6e0..fa7e88c 100644
--- a/src/app/[season]/page.tsx
+++ b/src/app/[season]/page.tsx
@@ -2,6 +2,7 @@
import { useAtom } from 'jotai';
+import { fetchStandings } from '@/atoms/fetchCalls';
import { seasonAtom } from '@/atoms/seasons';
import {
constructorStandingsAtom,
@@ -20,6 +21,7 @@ export default function ResultsPage() {
const [constructorStandings] = useAtom(constructorStandingsAtom);
const [driverStandings] = useAtom(driverStandingsAtom);
const [season] = useAtom(seasonAtom);
+ useAtom(fetchStandings);
return (
diff --git a/src/app/page.tsx b/src/app/page.tsx
index fd0811b..250e447 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,3 +1,4 @@
+import { LandingNextEvent } from './(features)/LandingNextEvent';
import { MainFilters } from './ui/MainFilters';
export default function Home() {
@@ -6,7 +7,7 @@ export default function Home() {
{/* Suspense */}
-
+
);
}
@@ -25,12 +26,3 @@ const Hero = () => (
);
-
-const NextRace = () => {
- return (
-
-
Winter Testing
- Bahrain Feb 21, 2024
-
- );
-};
diff --git a/src/app/ui/Countdown.tsx b/src/app/ui/Countdown.tsx
new file mode 100644
index 0000000..4d8c70c
--- /dev/null
+++ b/src/app/ui/Countdown.tsx
@@ -0,0 +1,18 @@
+import clsx from 'clsx';
+
+import { formatDuration } from '@/utils/helpers';
+
+export const Countdown = ({
+ time,
+ className,
+}: {
+ time: number;
+ className?: string;
+}) => {
+ return (
+
+ {/* Remove last 4 characters which as milliseconds */}
+ {formatDuration(time).slice(0, -4)}
+
+ );
+};
diff --git a/src/app/ui/EventCountdown.tsx b/src/app/ui/EventCountdown.tsx
new file mode 100644
index 0000000..c7622c8
--- /dev/null
+++ b/src/app/ui/EventCountdown.tsx
@@ -0,0 +1,10 @@
+import { useAtom } from 'jotai';
+
+import { nextEventTimeAtom } from '@/atoms/nextEvent';
+
+import { Countdown } from './Countdown';
+
+export const EventCountDown = () => {
+ const [nextEventCountdown] = useAtom(nextEventTimeAtom);
+ return ;
+};
diff --git a/src/app/ui/Nav.tsx b/src/app/ui/Nav.tsx
index 5c3bee2..347ec33 100644
--- a/src/app/ui/Nav.tsx
+++ b/src/app/ui/Nav.tsx
@@ -1,11 +1,44 @@
'use client';
+import clsx from 'clsx';
import { useAtom } from 'jotai';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
+import { fetchNextEvent } from '@/atoms/fetchCalls';
+import {
+ nextEventAtom,
+ nextEventEffect,
+ nextEventLiveAtom,
+} from '@/atoms/nextEvent';
import { seasonAtom } from '@/atoms/seasons';
+import { EventCountDown } from './EventCountdown';
+
+export const NextEvent = () => {
+ const [nextEvent] = useAtom(nextEventAtom);
+ const [liveEvent] = useAtom(nextEventLiveAtom);
+
+ useAtom(fetchNextEvent);
+ useAtom(nextEventEffect);
+
+ if (!nextEvent) return <>>;
+ return (
+ <>
+
+
+ {nextEvent.name}
+ in
+
+ >
+ );
+};
+
export const Nav = () => {
const router = useRouter();
const [season] = useAtom(seasonAtom);
@@ -72,11 +105,8 @@ export const Nav = () => {
-
-
-
- 53 days until Winter Testing
-
+
+
diff --git a/src/app/ui/Table.tsx b/src/app/ui/Table.tsx
deleted file mode 100644
index c338a74..0000000
--- a/src/app/ui/Table.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-'use client';
-
-import { Fragment } from 'react';
-
-export interface IStanding {
- headings: string[];
- data: { [key: string]: React.ReactNode }[];
-}
-
-interface ITable extends IStanding {
- title?: string;
- // headings: string[];
-}
-
-export const Table = ({ title, headings, data }: ITable) => {
- if (data.length <= 0 && headings.length <= 0) return;
-
- const Title = title && {title}
;
-
- return (
-
- {Title}
-
-
- {/* head */}
-
-
- {headings.map((header) => (
-
- {header.replace('_', ' ')}
- |
- ))}
- {/* Placeholder for button */} |
-
-
- {/* body */}
-
- {data.length > 0 &&
- data.map((row, i) => (
-
-
- {headings.map(
- (key) =>
- row && (
-
- {row[key]}
- |
- ),
- )}
- {/*
-
- | */}
-
-
- ))}
-
-
-
-
- );
-};
diff --git a/src/atoms/fetchCalls.tsx b/src/atoms/fetchCalls.tsx
index 3ec8054..c2fb351 100644
--- a/src/atoms/fetchCalls.tsx
+++ b/src/atoms/fetchCalls.tsx
@@ -5,10 +5,18 @@ import { atomEffect } from 'jotai-effect';
import { f1Seasons } from '@/utils/fakerData';
import { fetchAPI, lastSession, sessionTitles } from '@/utils/helpers';
-import { formatConstructorResults } from '@/utils/transformers';
+import {
+ formatConstructorResults,
+ formatNextEvent,
+} from '@/utils/transformers';
import { allConstructorAtom } from './constructors';
import { allDriversAtom } from './drivers';
+import {
+ nextEventAtom,
+ nextEventLiveAtom,
+ nextEventTimeAtom,
+} from './nextEvent';
import { raceAtom, seasonRacesAtom } from './races';
import { allSeasonsAtom, seasonAtom } from './seasons';
import { allSessionsAtom, sessionAtom } from './sessions';
@@ -133,3 +141,29 @@ export const fetchStandings = atomEffect((get, set) => {
// seasonAtom
// raceAtom
});
+
+// Get upcoming event this should be done once
+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);
+ }
+ });
+ }
+ },
+ // Dependencies: nextEventAtom
+);
diff --git a/src/atoms/nextEvent.tsx b/src/atoms/nextEvent.tsx
new file mode 100644
index 0000000..29b76ef
--- /dev/null
+++ b/src/atoms/nextEvent.tsx
@@ -0,0 +1,15 @@
+import { atom } from 'jotai';
+import { atomEffect } from 'jotai-effect';
+
+// Next Event
+export const nextEventLiveAtom = atom(false);
+export const nextEventAtom = atom(null);
+export const nextEventTimeAtom = atom(0);
+export const nextEventEffect = atomEffect((get, set) => {
+ if (get(nextEventTimeAtom) !== 0) {
+ const intervalId = setInterval(() => {
+ set(nextEventTimeAtom, (prev: number) => prev - 1000);
+ }, 1000);
+ return () => clearInterval(intervalId);
+ }
+});
diff --git a/src/results.d.ts b/src/results.d.ts
index bee5304..0699074 100644
--- a/src/results.d.ts
+++ b/src/results.d.ts
@@ -108,3 +108,11 @@ interface DataConfigSchema {
constructors: ConstructorResult[];
};
}
+
+// UI Format Next Event
+interface NextEventProps {
+ name: string;
+ session: string;
+ time: number;
+ endTime: number;
+}
diff --git a/src/utils/helpers.tsx b/src/utils/helpers.tsx
index 8f99871..713f2c1 100644
--- a/src/utils/helpers.tsx
+++ b/src/utils/helpers.tsx
@@ -43,30 +43,54 @@ export const fastestLap = (position: number, points: number) => {
}
};
-export const formatDuration = (durationInMilliseconds: number) => {
+const _second = 1000;
+const _minute = _second * 60;
+const _hour = _minute * 60;
+const _day = _hour * 24;
+
+export const formatDuration = (timeInterval: number) => {
// Pad single-digit values with leading zeros
const pad = (value: number) => {
return value < 10 ? '0' + value : value;
};
// Calculate hours, minutes, seconds, and milliseconds
- const hours = Math.floor(durationInMilliseconds / 3600000);
- const minutes = Math.floor((durationInMilliseconds % 3600000) / 60000);
- const seconds = Math.floor((durationInMilliseconds % 60000) / 1000);
- const milliseconds = durationInMilliseconds % 1000;
-
- if (hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0)
+ const milliseconds = timeInterval % _second;
+ const seconds = Math.floor((timeInterval % _minute) / _second);
+ const minutes = Math.floor((timeInterval % _hour) / _minute);
+ const hours = Math.floor((timeInterval % _day) / _hour);
+ const days = Math.floor(timeInterval / _day);
+
+ if (
+ days === 0 &&
+ hours === 0 &&
+ minutes === 0 &&
+ seconds === 0 &&
+ milliseconds === 0
+ )
return '-';
- else if (hours === 0 && minutes === 0 && seconds === 0)
+ else if (days === 0 && hours === 0 && minutes === 0 && seconds === 0)
return '0.' + pad(milliseconds);
- else if (hours === 0 && minutes === 0)
+ else if (days === 0 && hours === 0 && minutes === 0)
return seconds + '.' + pad(milliseconds);
- else if (hours === 0)
+ else if (days === 0 && hours === 0)
return minutes + ':' + pad(seconds) + '.' + pad(milliseconds);
- else
+ else if (days === 0)
return (
hours + ':' + pad(minutes) + ':' + pad(seconds) + '.' + pad(milliseconds)
);
+ else
+ return (
+ days +
+ ' days ' +
+ hours +
+ ':' +
+ pad(minutes) +
+ ':' +
+ pad(seconds) +
+ '.' +
+ pad(milliseconds)
+ );
};
export const sessionTitles = (event: ScheduleSchema) => {
diff --git a/src/utils/transformers.tsx b/src/utils/transformers.tsx
index f33e8e0..29524e9 100644
--- a/src/utils/transformers.tsx
+++ b/src/utils/transformers.tsx
@@ -61,3 +61,39 @@ export const formatConstructorResults = (drivers: DriverResult[]) =>
con.position = i + 1;
return con;
});
+
+const timeAjustment = (name: string) => (name === 'Race' ? 7200000 : 3600000);
+
+export const formatNextEvent = (data: ScheduleSchema) => {
+ const sessionTimes = Object.keys(data).filter((key) =>
+ key.match(/Session[1-5]DateUtc/g),
+ );
+
+ // Find the next session
+ const nextSessionTime = sessionTimes.find((session) => {
+ const sessionName = data[
+ session.slice(0, 8) as keyof ScheduleSchema
+ ] as string;
+ const timeWithAdjustment = Date.now() - timeAjustment(sessionName);
+ return (
+ timeWithAdjustment <
+ new Date(data[session as keyof ScheduleSchema] as string).getTime()
+ );
+ });
+
+ if (!nextSessionTime) return 'No session';
+
+ const sessionStartTime = new Date(
+ data[nextSessionTime as keyof ScheduleSchema] as string,
+ ).getTime();
+ const sessionName = data[
+ nextSessionTime.slice(0, 8) as keyof ScheduleSchema
+ ] as string;
+
+ return {
+ name: data.EventName,
+ session: sessionName,
+ time: sessionStartTime,
+ endTime: sessionStartTime + timeAjustment(sessionName),
+ };
+};