Skip to content

Commit

Permalink
feat: FRON-11 showcase next upcoming event (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lombardoc4 authored Feb 12, 2024
1 parent 5261264 commit c6b2629
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 99 deletions.
36 changes: 36 additions & 0 deletions src/app/(features)/LandingNextEvent.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex bg-base-300 px-4 py-8'>
{nextEvent && (
<div className='mx-auto'>
<h2 className='text-4xl font-bold'>{nextEvent.name}</h2>
<p className='text-2xl'>
{liveEvent ? (
nextEvent.session + ' Live Now'
) : (
<>
{formatSessionUrl(nextEvent.session).toUpperCase()} in{' '}
<EventCountDown />
</>
)}
</p>
<button className='btn btn-secondary btn-sm my-2'>
Previous Results
</button>
</div>
)}
</div>
);
};
2 changes: 2 additions & 0 deletions src/app/[season]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useAtom } from 'jotai';

import { fetchStandings } from '@/atoms/fetchCalls';
import { seasonAtom } from '@/atoms/seasons';
import {
constructorStandingsAtom,
Expand All @@ -20,6 +21,7 @@ export default function ResultsPage() {
const [constructorStandings] = useAtom(constructorStandingsAtom);
const [driverStandings] = useAtom(driverStandingsAtom);
const [season] = useAtom(seasonAtom);
useAtom(fetchStandings);

return (
<main>
Expand Down
12 changes: 2 additions & 10 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LandingNextEvent } from './(features)/LandingNextEvent';
import { MainFilters } from './ui/MainFilters';

export default function Home() {
Expand All @@ -6,7 +7,7 @@ export default function Home() {
<Hero />

{/* Suspense */}
<NextRace />
<LandingNextEvent />
</main>
);
}
Expand All @@ -25,12 +26,3 @@ const Hero = () => (
</div>
</div>
);

const NextRace = () => {
return (
<div className='bg-base-300 px-4 py-8'>
<h2 className='text-2xl'>Winter Testing</h2>
Bahrain Feb 21, 2024
</div>
);
};
18 changes: 18 additions & 0 deletions src/app/ui/Countdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import clsx from 'clsx';

import { formatDuration } from '@/utils/helpers';

export const Countdown = ({
time,
className,
}: {
time: number;
className?: string;
}) => {
return (
<span className={clsx(className)}>
{/* Remove last 4 characters which as milliseconds */}
{formatDuration(time).slice(0, -4)}
</span>
);
};
10 changes: 10 additions & 0 deletions src/app/ui/EventCountdown.tsx
Original file line number Diff line number Diff line change
@@ -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 <Countdown time={nextEventCountdown} />;
};
40 changes: 35 additions & 5 deletions src/app/ui/Nav.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div
className={clsx('h-3 w-3 rounded-full', {
'bg-info': !liveEvent,
'bg-success': liveEvent,
})}
></div>
<p className='text-sm font-bold'>
<span className='underline'>{nextEvent.name}</span> <br />
in <EventCountDown />
</p>
</>
);
};

export const Nav = () => {
const router = useRouter();
const [season] = useAtom(seasonAtom);
Expand Down Expand Up @@ -72,11 +105,8 @@ export const Nav = () => {
</li>
</ul>
</div>
<div className='hidden items-center gap-2 lg:flex'>
<div className='h-3 w-3 rounded-full bg-amber-300'></div>
<p className='text-sm font-bold'>
53 days until <span className='underline'>Winter Testing</span>
</p>
<div className='hidden min-w-48 items-center gap-2 lg:flex'>
<NextEvent />
</div>
</div>
</div>
Expand Down
72 changes: 0 additions & 72 deletions src/app/ui/Table.tsx

This file was deleted.

36 changes: 35 additions & 1 deletion src/atoms/fetchCalls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
);
15 changes: 15 additions & 0 deletions src/atoms/nextEvent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { atom } from 'jotai';
import { atomEffect } from 'jotai-effect';

// Next Event
export const nextEventLiveAtom = atom(false);
export const nextEventAtom = atom<NextEventProps | null>(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);
}
});
8 changes: 8 additions & 0 deletions src/results.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,11 @@ interface DataConfigSchema {
constructors: ConstructorResult[];
};
}

// UI Format Next Event
interface NextEventProps {
name: string;
session: string;
time: number;
endTime: number;
}
46 changes: 35 additions & 11 deletions src/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading

0 comments on commit c6b2629

Please sign in to comment.