-
Slick Telemetry
-
-
- We are Slick Telemetry, like-minded individuals and fans of Formula 1.
- We are currently building an analysis platform for F1 Data Analysis!
+
+
+ {/* Slick Telemetry */}
+
+
Slick Telemetry
+
+ Your home for Formula 1 insights
+
+
+
+
+
+ {/* Formula */}
+
+
Formula 1
+
+ The world largest science contest
+
+ {/* Call to Action */}
+
+
+
+
+
);
-
-const NextRace = () => {
- return (
-
-
Winter Testing
- Bahrain Feb 21, 2024
-
- );
-};
diff --git a/src/app/results/RaceResults.tsx b/src/app/results/RaceResults.tsx
deleted file mode 100644
index 990afda..0000000
--- a/src/app/results/RaceResults.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import { useAtom } from 'jotai';
-import Image from 'next/image';
-import { useMemo } from 'react';
-
-import { racesAtom } from '@/atoms/results';
-
-import { ISchedule } from '../lib/utils';
-
-const ResultCard = ({ data }: { data: ISchedule }) => {
- const eventDate = new Date(data.EventDate);
- const eventPassed = new Date() > eventDate;
-
- return (
-
-
-
-
-
- {data.OfficialEventName.slice(0, -5)}
-
-
-
-
-
-
- {data.Location}, {data.Country}
-
- {eventDate.toDateString()}
-
-
- {eventPassed && (
-
-
-
- )}
-
-
- );
-};
-
-const WinterTesting = ({ data }: { data: ISchedule }) => {
- const eventDate = new Date(data.EventDate);
- const eventPassed = new Date() > eventDate;
-
- return (
-
-
-
-
-
{data.OfficialEventName}
-
- {data.Location}, {data.Country}
-
-
{eventDate.toDateString()}
-
- {eventPassed && (
-
- Testing Results
-
- )}
-
-
- );
-};
-
-export const RaceSchedule = () => {
- const [races] = useAtom(racesAtom);
-
- const winterTesting = useMemo(
- () => races.find((race) => race.EventFormat === 'testing'),
- [races],
- );
- const mainEvents = useMemo(
- () => races.filter((race) => race.EventFormat !== 'testing'),
- [races],
- );
-
- return (
-
- {/* If seasonAom === current/upcomming season, then add button to bring user to next event */}
- {winterTesting &&
}
-
- {/* 10 Placeholder Cards */}
- {mainEvents.map((race) => (
-
- ))}
-
-
- );
-};
diff --git a/src/app/results/SeasonResults.tsx b/src/app/results/SeasonResults.tsx
deleted file mode 100644
index c7291d2..0000000
--- a/src/app/results/SeasonResults.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-'use client';
-
-import { RaceSchedule } from './RaceResults';
-import {
- constructorsData,
- ConstuctorHeadings,
- driverData,
- DriverHeadings,
-} from '../lib/placerholder-results';
-import { Table } from '../ui/Table';
-import { Tabs } from '../ui/Tabs';
-
-const tabHeaders = ['Races', 'Drivers', 'Constructors'];
-const tabs = [
-
,
-
-
,
-
,
-];
-
-export default function ResultsPage() {
- return (
-
-
-
- );
-}
diff --git a/src/app/results/[season]/page.tsx b/src/app/results/[season]/page.tsx
deleted file mode 100644
index 623ac15..0000000
--- a/src/app/results/[season]/page.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-'use client';
-
-import { useAtom } from 'jotai';
-
-import ResultsPage from '../SeasonResults';
-import { handleSeasonChangeAtom } from '../../../atoms/results';
-
-export default function Page({ params }: { params: { slug: string } }) {
- const [, handleSeasonChange] = useAtom(handleSeasonChangeAtom);
- handleSeasonChange(params.slug);
-
- return
;
-}
diff --git a/src/app/results/layout.tsx b/src/app/results/layout.tsx
deleted file mode 100644
index 5070ae7..0000000
--- a/src/app/results/layout.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-'use client';
-
-import { MainFilters } from '../MainFilters';
-
-// Default Next Layout
-export default function ResultsLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
- <>
-
-
-
- {children}
- >
- );
-}
diff --git a/src/app/results/page.tsx b/src/app/results/page.tsx
deleted file mode 100644
index daf84ed..0000000
--- a/src/app/results/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import ResultsPage from './SeasonResults';
-
-export default function Page() {
- return
;
-}
diff --git a/src/app/schedule/page.tsx b/src/app/schedule/page.tsx
new file mode 100644
index 0000000..eb84e78
--- /dev/null
+++ b/src/app/schedule/page.tsx
@@ -0,0 +1,10 @@
+// import { RaceSchedule } from '@/components/SelectionData/RaceSchedule';
+
+export default function SchedulePage() {
+ return (
+
+ Schedule
+ {/* , */}
+
+ );
+}
diff --git a/src/app/ui/Nav.tsx b/src/app/ui/Nav.tsx
deleted file mode 100644
index 851325c..0000000
--- a/src/app/ui/Nav.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import Link from 'next/link';
-
-export const Nav = () => (
-
-
-
- Slick Telemetry
-
-
-
-
-
-
-
-
- 53 days until Winter Testing
-
-
-
-
-);
diff --git a/src/app/ui/Table.tsx b/src/app/ui/Table.tsx
deleted file mode 100644
index ebaedd9..0000000
--- a/src/app/ui/Table.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-'use client';
-
-import { Fragment } from 'react';
-
-interface ITable {
- title?: string;
- headings: string[];
- data: { [key: string]: React.ReactNode }[];
-}
-
-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/app/ui/Tabs.tsx b/src/app/ui/Tabs.tsx
deleted file mode 100644
index fa06c91..0000000
--- a/src/app/ui/Tabs.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import clsx from 'clsx';
-import { atom, useAtom } from 'jotai';
-
-const tabView = atom
(0);
-
-interface ITabs {
- headers: string[];
- containers: React.ReactNode[];
-}
-
-export const Tabs = ({ headers, containers }: ITabs) => {
- // Testing Jotai & Atoms
- const [tabIndex, setTabIndex] = useAtom(tabView);
-
- // Active tab has matching tabIndex
- const TabButtons = headers.map((header, i) => (
- setTabIndex(i)}
- >
- {header}
-
- ));
-
- // Hide containers not matching tabIndex
- const TabContainers = containers.map((tab, i) => (
-
- {tab}
-
- ));
-
- return (
-
- );
-};
diff --git a/src/atoms/constructors.tsx b/src/atoms/constructors.tsx
new file mode 100644
index 0000000..ae143d2
--- /dev/null
+++ b/src/atoms/constructors.tsx
@@ -0,0 +1,4 @@
+import { atom } from 'jotai';
+
+// Constructor Results
+export const allConstructorAtom = atom([]);
diff --git a/src/atoms/drivers.tsx b/src/atoms/drivers.tsx
new file mode 100644
index 0000000..913d52d
--- /dev/null
+++ b/src/atoms/drivers.tsx
@@ -0,0 +1,16 @@
+import { atom } from 'jotai';
+
+export const allDriversAtom = atom(null);
+export const driverAtom = atom('All Drivers');
+
+export const handleDriverChangeAtom = atom(
+ null,
+ async (get, set, driverName: string) => {
+ const drivers = get(allDriversAtom);
+ const driver = drivers?.find((driver) => driver.FullName === driverName);
+
+ if (driver) {
+ set(driverAtom, driver);
+ }
+ },
+);
diff --git a/src/atoms/fetchCalls.tsx b/src/atoms/fetchCalls.tsx
new file mode 100644
index 0000000..6830b73
--- /dev/null
+++ b/src/atoms/fetchCalls.tsx
@@ -0,0 +1,201 @@
+// Get session results
+
+import { useAtom } from 'jotai';
+import { atomEffect } from 'jotai-effect';
+
+import { f1Seasons } from '@/lib/fakerData';
+import { fetchAPI, lastSession, sessionTitles } from '@/lib/helpers';
+import { formatConstructorResults, formatNextEvent } from '@/lib/transformers';
+
+import { allConstructorAtom } from './constructors';
+import { allDriversAtom } from './drivers';
+import {
+ nextEventAtom,
+ nextEventLiveAtom,
+ 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';
+
+export const useMainFiltersAtomFetch = () => {
+ useAtom(fetchSeasons);
+ useAtom(fetchSchedule);
+ useAtom(fetchSessionResults);
+};
+
+// Get Seasons values, this should be done once
+export const fetchSeasons = atomEffect(
+ (get, set) => {
+ const seasons = get(allSeasonsAtom);
+ // Seasons do not change, only fetch if empty array
+ if (!seasons || seasons.length <= 0) {
+ set(allSeasonsAtom, f1Seasons());
+ }
+
+ // This populates to show values are loaded
+ },
+ // Dependencies: allSeasonsAtom
+);
+
+// Based off season data
+// If season value set fetch that seasons schedule
+// otherwise get the default schedule
+export const fetchSchedule = atomEffect(
+ (get, set) => {
+ set(seasonRacesAtom, null);
+ const params = get(seasonAtom) && `?year=${get(seasonAtom)}`;
+
+ fetchAPI('schedule' + params).then(
+ (res: DataConfigSchema['schedule'] | ServerErrorResponse) => {
+ const schedule = res as DataConfigSchema['schedule'];
+
+ 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
+);
+
+// Based off race data
+// Set session and sessions from race sessions
+// Fetch race results to get drivers in the session
+export const fetchSessionResults = atomEffect((get, set) => {
+ const race = get(raceAtom);
+
+ // Confirm race has been selected
+ if (race && race !== 'All Races') {
+ // *** Base url for fetch
+ let url = `results/${get(seasonAtom)}/${race.RoundNumber}`;
+
+ // Parse race data to get session titles
+ const sessions = sessionTitles(race);
+ // Set session to last session, ideally race
+ const session = lastSession(race);
+
+ // Set values
+ set(sessionAtom, session);
+ set(allSessionsAtom, sessions);
+
+ // *** If sessions available find session round and add to url
+ if (sessions.length > 0) {
+ const sessionRound = sessions.indexOf(session) + 1;
+ url += `?session=${sessionRound}`;
+ }
+
+ 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);
+
+ // Update atom values
+ set(allDriversAtom, drivers);
+ set(allConstructorAtom, constructors);
+ });
+ }
+ // Dependencies:
+ // raceAtom
+ // seasonAtom
+});
+
+// Get Driver & Constructor Standings
+export const fetchStandings = atomEffect((get, set) => {
+ // Reset standings
+ set(driverStandingsAtom, []);
+ set(constructorStandingsAtom, []);
+ const race = get(raceAtom);
+
+ // Year
+ const year = get(seasonAtom) && `?year=${get(seasonAtom)}`;
+
+ // Round
+ const round = race === 'All Races' ? '' : `&round=${race.RoundNumber}`;
+
+ // Fetch
+ 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 {
+ ...cs,
+ Drivers: DriverStandings.filter((driver) =>
+ driver.Constructors.find((c) => c.name === name),
+ ),
+ };
+ });
+
+ // Update standings
+ set(constructorStandingsAtom, constructors);
+ set(driverStandingsAtom, DriverStandings);
+ })
+ .catch((err) => err);
+
+ // dependencies
+ // 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(
+ (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/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/atoms/races.tsx b/src/atoms/races.tsx
new file mode 100644
index 0000000..11ec9fa
--- /dev/null
+++ b/src/atoms/races.tsx
@@ -0,0 +1,20 @@
+import { atom } from 'jotai';
+
+import { allDriversAtom, driverAtom } from './drivers';
+import { allSessionsAtom } from './sessions';
+
+export const seasonRacesAtom = atom(null);
+export const raceAtom = atom('All Races');
+
+export const handleRaceChangeAtom = atom(
+ null,
+ async (get, set, raceEvent: ScheduleSchema) => {
+ // Update race
+ set(raceAtom, raceEvent);
+
+ // Reset Driver
+ set(driverAtom, 'All Drivers');
+ set(allDriversAtom, null);
+ set(allSessionsAtom, null);
+ },
+);
diff --git a/src/atoms/results.tsx b/src/atoms/results.tsx
index 2288ffe..7a7603b 100644
--- a/src/atoms/results.tsx
+++ b/src/atoms/results.tsx
@@ -1,89 +1,137 @@
-import { atom } from 'jotai';
+import { atom, useAtom } from 'jotai';
import { atomEffect } from 'jotai-effect';
+import { usePathname } from 'next/navigation';
+import { useRef } from 'react';
-import { fetchAPI, ISchedule } from '../app/lib/utils';
+import {
+ formatRaceEventName,
+ formatRaceUrl,
+ formatSessionName,
+ formatSessionUrl,
+} from '@/lib/transformers';
-export const raceAtom = atom('All Races');
-export const racesAtom = atom([]);
-export const seasonAtom = atom('2023');
-export const seasonsAtom = atom([]);
-export const driverAtom = atom('All Drivers');
-export const driversAtom = atom([]);
-export const sessionAtom = atom('Race');
-export const sessionsAtom = atom([]);
-export const telemetryDisableAtom = atom(true);
-export const resultUrlAtom = atom('/results');
+import { allDriversAtom, driverAtom } from './drivers';
+import { raceAtom, seasonRacesAtom } from './races';
+import { seasonAtom } from './seasons';
+import { allSessionsAtom, sessionAtom } from './sessions';
-export const racesDropdownAtom = atom((get) =>
- get(racesAtom).map((race) => race.EventName),
-);
+// Server Error Atom
+export const serverErrorAtom = atom('');
-export const fetchSeasons = atomEffect((get, set) => {
- fetchAPI('seasons').then((data) => set(seasonsAtom, data));
+// Telemetry Active
+export const telemetryDisableAtom = atom(true);
+// Telemetry is disabled if no race and driver are selected
+export const toggleTelemetryDisableAtom = atomEffect((get, set) => {
+ set(
+ telemetryDisableAtom,
+ get(raceAtom) === 'All Races' || get(driverAtom) === 'All Drivers',
+ );
});
-export const fetchRaces = atomEffect((get, set) => {
- fetchAPI('schedule/' + get(seasonAtom)).then((data) => set(racesAtom, data));
-});
+export const handleMainFilterSubmit = atom(null, (get) => {
+ const season = get(seasonAtom);
+ const race = get(raceAtom);
+ const driver = get(driverAtom);
+ const session = get(sessionAtom);
+ const sessions = get(allSessionsAtom);
+ const url = [];
-export const fetchDriver = atomEffect((get, set) => {
- fetchAPI('drivers').then((data) => set(driversAtom, data));
-});
-export const fetchSessions = atomEffect((get, set) => {
- fetchAPI('sessions').then((data) => set(sessionsAtom, data));
-});
+ // Return if no race specified
+ if (!season) return;
+ // Add season to url
+ else url.push(season);
-export const handleSeasonChangeAtom = atom(null, (get, set, update: string) => {
- set(seasonAtom, update);
- set(raceAtom, 'All Races');
- set(driverAtom, 'All Drivers');
- set(sessionAtom, 'Race');
- set(resultUrlAtom, '/results/' + update);
+ // Return if no race specified
+ if (race === 'All Races') return url.join('/');
+ // Add race location to url
+ else url.push(formatRaceUrl(race.EventName));
- // Todo: Update RacesAtom
-});
+ // Return if no driver specified
+ if (driver === 'All Drivers') return url.join('/');
+ // Add driver id to url
+ else url.push(driver.DriverId);
-export const handleRaceChangeAtom = atom(null, (get, set, update: string) => {
- set(raceAtom, update);
- set(driverAtom, 'All Drivers');
- set(resultUrlAtom, '/results/' + get(seasonAtom) + '/' + update);
+ // Return if no sessions
+ if ((sessions && sessions.length === 0) || session === 'Race')
+ return url.join('/');
+ // Add session to url
+ else url.push(formatSessionUrl(session));
- // Todo: Update DriversAtom
+ return url.join('/');
});
-export const handleDriverChangeAtom = atom(null, (get, set, update: string) => {
- set(driverAtom, update);
- set(sessionAtom, 'Race');
- set(
- resultUrlAtom,
- '/results/' + get(seasonAtom) + '/' + get(raceAtom) + '/' + update,
- );
+export const useParamToSetAtoms = () => {
+ const [season, eventParam, driverId, sessionParam] = usePathname()
+ .split('/')
+ .slice(1);
- // Todo: Update SessionsAtom
-});
+ const [seasonVal, setSeason] = useAtom(seasonAtom);
+ const [races] = useAtom(seasonRacesAtom);
+ const [race, setRace] = useAtom(raceAtom);
+ const [drivers] = useAtom(allDriversAtom);
+ const [driver, setDriver] = useAtom(driverAtom);
+ const [sessions] = useAtom(allSessionsAtom);
+ const [, setSession] = useAtom(sessionAtom);
-export const handleSessionChangeAtom = atom(
- null,
- (get, set, update: string) => {
- set(sessionAtom, update);
- set(
- resultUrlAtom,
- '/results/' +
- get(seasonAtom) +
- '/' +
- get(raceAtom) +
- '/' +
- get(driverAtom) +
- '/' +
- update,
- );
- },
-);
+ // Loading Refs
+ const seasonLoaded = useRef(false);
+ const raceLoaded = useRef(false);
+ const driverLoaded = useRef(false);
+ const sessionLoaded = useRef(false);
-export const toggleTelemetryDisableAtom = atomEffect((get, set) => {
- // Telemetry is disabled if no race and driver are selected
- set(
- telemetryDisableAtom,
- get(raceAtom) === 'All Races' || get(driverAtom) === 'All Drivers',
- );
-});
+ if (!seasonLoaded.current) {
+ if (season && season !== seasonVal) setSeason(season);
+ seasonLoaded.current = true;
+ }
+
+ if (!raceLoaded.current) {
+ // If no location nothing to load/check
+ if (!eventParam) {
+ raceLoaded.current = true;
+ return;
+ }
+ // if location and races to compare to
+ if (races) {
+ raceLoaded.current = true;
+ const eventName = formatRaceEventName(eventParam);
+ const raceMatch = races.find((r) => r.EventName === eventName);
+ if (race === 'All Races' || raceMatch?.EventName !== race?.EventName) {
+ setRace(raceMatch || 'All Races');
+ }
+ }
+ }
+
+ if (!driverLoaded.current) {
+ // If no driverId nothing to load/check
+ if (!driverId) {
+ driverLoaded.current = true;
+ return;
+ }
+
+ // if drivers to compare to driverId
+ if (drivers) {
+ if (eventParam && drivers.length === 0) return;
+
+ driverLoaded.current = true;
+ const driverMatch = drivers.find((d) => d.DriverId === driverId);
+
+ if (driver === 'All Drivers' || driverMatch?.DriverId !== driver.DriverId)
+ setDriver(driverMatch || 'All Drivers');
+ }
+ }
+
+ if (!sessionLoaded.current) {
+ // If no driverId nothing to load/check
+ if (!sessionParam) {
+ sessionLoaded.current = true;
+ return;
+ }
+
+ // if driver and driver to compare to
+ if (sessions) {
+ sessionLoaded.current = true;
+ const session = formatSessionName(sessionParam);
+ setSession(sessions.find((s) => s === session) || 'Race');
+ }
+ }
+};
diff --git a/src/atoms/seasons.tsx b/src/atoms/seasons.tsx
new file mode 100644
index 0000000..92cebb2
--- /dev/null
+++ b/src/atoms/seasons.tsx
@@ -0,0 +1,24 @@
+import { atom } from 'jotai';
+
+import { allDriversAtom, driverAtom } from './drivers';
+import { raceAtom, seasonRacesAtom } from './races';
+import { allSessionsAtom, sessionAtom } from './sessions';
+
+export const allSeasonsAtom = atom(null);
+// ! Need to set initial season from server default
+export const seasonAtom = atom('');
+
+export const handleSeasonChangeAtom = atom(
+ null,
+ async (_get, set, season: string) => {
+ set(seasonAtom, season);
+
+ // Reset other filter values
+ set(raceAtom, 'All Races');
+ set(seasonRacesAtom, null);
+ set(driverAtom, 'All Drivers');
+ set(allDriversAtom, []);
+ set(sessionAtom, 'Race');
+ set(allSessionsAtom, []);
+ },
+);
diff --git a/src/atoms/sessions.tsx b/src/atoms/sessions.tsx
new file mode 100644
index 0000000..6582efe
--- /dev/null
+++ b/src/atoms/sessions.tsx
@@ -0,0 +1,11 @@
+import { atom } from 'jotai';
+// Sessions
+export const allSessionsAtom = atom(null);
+export const sessionAtom = atom('Race');
+
+export const handleSessionChangeAtom = atom(
+ null,
+ async (get, set, session: string) => {
+ set(sessionAtom, session);
+ },
+);
diff --git a/src/atoms/standings.tsx b/src/atoms/standings.tsx
new file mode 100644
index 0000000..8544cfc
--- /dev/null
+++ b/src/atoms/standings.tsx
@@ -0,0 +1,5 @@
+import { atom } from 'jotai';
+
+// Cumulative Standings
+export const constructorStandingsAtom = atom([]);
+export const driverStandingsAtom = atom([]);
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
new file mode 100644
index 0000000..e92bcd0
--- /dev/null
+++ b/src/components/Footer/index.tsx
@@ -0,0 +1,16 @@
+import { fetchAPI } from '@/lib/helpers';
+
+async function Footer() {
+ const serverStatus = await fetchAPI('health', true);
+
+ return (
+
+
Footer
+
+ Server Status: {serverStatus.status || 'Offline'}
+
+
+ );
+}
+
+export { Footer };
diff --git a/src/app/ui/Dropdown.tsx b/src/components/QueryNav/Dropdown.tsx
similarity index 56%
rename from src/app/ui/Dropdown.tsx
rename to src/components/QueryNav/Dropdown.tsx
index 4ad91c7..fba0193 100644
--- a/src/app/ui/Dropdown.tsx
+++ b/src/components/QueryNav/Dropdown.tsx
@@ -1,10 +1,11 @@
// 'use client';
+import clsx from 'clsx';
import React from 'react';
import { BsFillCaretDownFill } from 'react-icons/bs';
interface IDropdown {
value: string;
- items: string[];
+ items: string[] | null;
action: (item: string) => void;
}
@@ -21,15 +22,24 @@ export const Dropdown = ({ value, items, action }: IDropdown) => {
- {value}
+ {items ? (
+ <>
+ {value}
+ >
+ ) : (
+
+ )}