Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6f0b005
feat(api): add archive endpoint
dev-viinz Oct 22, 2024
0cc0550
chore(api): remove unused
dev-viinz Oct 23, 2024
f290757
feat(dash): add archive page
dev-viinz Oct 23, 2024
48ead2a
feat: wip on the display
dev-viinz Nov 3, 2024
22d11c5
feat: finish design draft + add start and end date of event
dev-viinz Nov 3, 2024
1e1919a
Merge branch 'main' into feature/archive
dev-viinz Nov 3, 2024
21ab74d
feat: add year picker
dev-viinz Nov 4, 2024
7839e99
fix: remove 2017
dev-viinz Nov 4, 2024
1d4b979
feat(api): add caching
dev-viinz Nov 4, 2024
5735989
feat: add details page
dev-viinz Nov 4, 2024
87b3b82
Merge branch 'main' into feature/archive
dev-viinz Nov 4, 2024
c7d7ded
Merge branch 'main' into feature/archive
dev-viinz Nov 25, 2024
8dcd367
fix: await params
dev-viinz Dec 1, 2024
a2146e9
fix: await params
dev-viinz Dec 1, 2024
36483d9
fix(dash): clean up the archive dates and times
dev-viinz Dec 1, 2024
3bb3123
feat: add dropdown to archive for better vivibility with more years
dev-viinz Dec 1, 2024
f21171a
feat: use dropwdown
dev-viinz Dec 1, 2024
be93933
Merge branch 'slowlydev:main' into main
dev-viinz Apr 14, 2025
6e24ce4
feat: wip on the display
dev-viinz Nov 3, 2024
9764882
feat: finish design draft + add start and end date of event
dev-viinz Nov 3, 2024
b79af22
feat: add year picker
dev-viinz Nov 4, 2024
ca07c58
fix: remove 2017
dev-viinz Nov 4, 2024
8593a7e
feat(api): add caching
dev-viinz Nov 4, 2024
13a881e
feat: add details page
dev-viinz Nov 4, 2024
1e3bd6f
fix: await params
dev-viinz Dec 1, 2024
1eab4be
fix: await params
dev-viinz Dec 1, 2024
ebb5286
fix(dash): clean up the archive dates and times
dev-viinz Dec 1, 2024
9c5d7ab
feat: add dropdown to archive for better vivibility with more years
dev-viinz Dec 1, 2024
04eef57
feat: use dropwdown
dev-viinz Dec 1, 2024
f17db08
Merge branch 'feature/archive' of https://github.com/dev-viinz/f1-das…
dev-viinz Apr 14, 2025
0b3acbf
Merge branch 'develop' of github.com:slowlydev/f1-dash into feature/a…
slowlydev May 27, 2025
9d2c32e
fix: use new axum paths
slowlydev May 27, 2025
4ea43ee
fix: remove footers and update env var names
slowlydev May 27, 2025
337e9b8
refactor: remove unsessary layout
slowlydev May 27, 2025
20f643e
feat: unify design of archive and schedule
slowlydev May 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions dash/src/app/(nav)/archive/[year]/[key]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { utc } from "moment";
import Link from "next/link";

import type { Meeting } from "@/types/archive.type";

import { env } from "@/env";

const getArchiveForYear = async (year: string): Promise<Meeting[] | null> => {
try {
const nextReq = await fetch(`${env.API_URL}/api/archive/${year}`, {
next: { revalidate: 60 * 60 * 4 },
});
const schedule: Meeting[] = await nextReq.json();
return schedule;
} catch {
return null;
}
};

type Prams = Promise<{
key: string;
year: string;
}>;

type Props = {
params: Prams;
};

export default async function MeetingDetailsPage({ params }: Props) {
const { key, year } = await params;

const archive = await getArchiveForYear(year);
const meeting = archive?.find((meet) => meet.key.toString() === key);

if (!meeting) {
return (
<div className="container mx-auto max-w-screen-lg px-4 pb-8">
<div className="flex h-44 flex-col items-center justify-center">
<p>No meeting details found for key: {key}</p>
</div>
</div>
);
}

return (
<div className="container mx-auto max-w-screen-lg px-4 pb-8">
<Link href={`/archive/${year}`}>
<div className="mt-4 text-blue-500 hover:underline">← Back to Year Overview</div>
</Link>

<div className="my-4">
<h1 className="text-3xl font-bold">{meeting.officialName}</h1>
<p className="text-sm text-zinc-500">{meeting.country.name}</p>
<p className="mt-1 text-sm text-zinc-400 italic">{meeting.location}</p>
<p className="mt-2 text-sm text-zinc-600">
{utc(meeting.sessions[0].startDate).local().format("MMMM D, YYYY")} -{" "}
{utc(meeting.sessions[meeting.sessions.length - 1].endDate)
.local()
.format("MMMM D, YYYY")}
</p>
<div className="mt-4">
<h2 className="text-2xl font-bold">Sessions</h2>
<ul className="mt-2">
{meeting.sessions.map((session, index) => (
<li key={index} className="mb-4">
<h3 className="text-xl font-semibold">{session.name}</h3>
<p className="text-sm text-zinc-500">{utc(session.startDate).local().format("MMMM D, YYYY")}</p>
<p className="text-sm text-zinc-500">
{utc(session.startDate).local().format("HH:mm")} -{utc(session.endDate).local().format("HH:mm")}
</p>
</li>
))}
</ul>
</div>
</div>
</div>
);
}
91 changes: 91 additions & 0 deletions dash/src/app/(nav)/archive/[year]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { notFound } from "next/navigation";
import { connection } from "next/server";

import { env } from "@/env";

import Meeting from "../meeting";
import type { Meeting as MeetingType } from "@/types/archive.type";
import { Suspense } from "react";

const getArchiveForYear = async (year: string) => {
await connection();

try {
const nextReq = await fetch(`${env.API_URL}/api/archive/${year}`, {
cache: "no-store",
});
const schedule: MeetingType[] = await nextReq.json();
return schedule;
} catch (e) {
console.error("error fetching next round", e);
return null;
}
};

export default async function ArchivePage({ params }: { params: Promise<{ year: string }> }) {
const { year } = await params;

if (year < "2018") notFound();

return (
<div>
<div className="my-4">
<h1 className="text-3xl">{year} Archive</h1>
<p className="text-zinc-500">All times are local time</p>
</div>

<Suspense fallback={<Loading />}>
<Meetings year={year} />
</Suspense>
</div>
);
}

const Meetings = async ({ year }: { year: string }) => {
const meetings = await getArchiveForYear(year);

if (!meetings) {
return (
<div className="flex h-44 flex-col items-center justify-center">
<p>No archive data found for {year}</p>
</div>
);
}

return (
<ul className="grid grid-cols-1 gap-2 md:grid-cols-2">
{meetings.map((meeting) => (
<Meeting meeting={meeting} key={meeting.key} year={year} />
))}
</ul>
);
};

const Loading = () => {
return (
<ul className="grid grid-cols-1 gap-2 md:grid-cols-2">
{Array.from({ length: 10 }).map((_, i) => (
<MeetingLoading key={`meeting.${i}`} />
))}
</ul>
);
};

const MeetingLoading = () => {
return (
<div className="flex flex-col gap-2 rounded-lg border border-zinc-800 p-3">
<div className="flex gap-2">
<div className="h-12 w-16 animate-pulse rounded-md bg-zinc-800" />

<div className="flex flex-1 flex-col gap-1">
<div className="h-4 w-full animate-pulse rounded-md bg-zinc-800" />
<div className="h-4 w-1/3 animate-pulse rounded-md bg-zinc-800" />
</div>
</div>

<div className="h-4 w-3/8 animate-pulse rounded-md bg-zinc-800" />

<div className="h-4 w-4/8 animate-pulse rounded-md bg-zinc-800" />
</div>
);
};
35 changes: 35 additions & 0 deletions dash/src/app/(nav)/archive/meeting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Link from "next/link";
import { utc } from "moment";

import type { Meeting } from "@/types/archive.type";

import { getCountryCode } from "@/lib/country";

import Flag from "@/components/Flag";

type Props = {
meeting: Meeting;
year: string;
};

export default function Meeting({ meeting, year }: Props) {
const start = meeting.sessions[0].startDate;
const end = meeting.sessions[meeting.sessions.length - 1].endDate;

return (
<Link className="rounded-lg border border-zinc-800 p-3" href={`/archive/${year}/${meeting.key}`}>
<div className="mb-2 flex gap-2">
<Flag className="h-8 w-11" countryCode={getCountryCode(meeting.country.name)} />
<p className="flex-1 leading-snug font-bold">{meeting.officialName}</p>
</div>

<p className="text-zinc-500">
{meeting.country.name} - {meeting.location}
</p>

<p className="text-zinc-500">
{utc(start).local().format("MMMM D, YYYY")} - {utc(end).local().format("MMMM D, YYYY")}
</p>
</Link>
);
}
6 changes: 6 additions & 0 deletions dash/src/app/(nav)/archive/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { redirect } from "next/navigation";

export default function ArchiveRedirectPage() {
const currentYear = new Date().getFullYear();
redirect(`/archive/${currentYear}`);
}
5 changes: 5 additions & 0 deletions dash/src/app/(nav)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type Props = {
};

export default function Layout({ children }: Props) {
const year = new Date().getFullYear();

return (
<>
<nav className="sticky top-0 left-0 z-10 flex h-12 w-full items-center justify-between gap-4 border-b border-zinc-800 p-2 px-4 backdrop-blur-lg">
Expand All @@ -25,6 +27,9 @@ export default function Layout({ children }: Props) {
<Link className="transition duration-100 active:scale-95" href="/schedule">
Schedule
</Link>
<Link className="transition duration-100 active:scale-95" href={`/archive/${year}`}>
Archive
</Link>
<Link className="transition duration-100 active:scale-95" href="/help">
Help
</Link>
Expand Down
116 changes: 106 additions & 10 deletions dash/src/app/(nav)/schedule/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
import { connection } from "next/server";
import { Suspense } from "react";
import { utc } from "moment";

import NextRound from "@/components/schedule/NextRound";
import Schedule from "@/components/schedule/Schedule";
import { env } from "@/env";

import type { Round as RoundType } from "@/types/schedule.type";

import Countdown from "./countdown";
import Round from "./round";

export const getNext = async () => {
await connection();

try {
const nextReq = await fetch(`${env.API_URL}/api/schedule/next`, {
cache: "no-store",
});
const next: RoundType = await nextReq.json();
return next;
} catch (e) {
console.error("error fetching next round", e);
return null;
}
};

export const getSchedule = async () => {
await connection();

try {
const scheduleReq = await fetch(`${env.API_URL}/api/schedule`, {
cache: "no-store",
});
const schedule: RoundType[] = await scheduleReq.json();
return schedule;
} catch (e) {
console.error("error fetching schedule", e);
return null;
}
};

export default async function SchedulePage() {
return (
Expand All @@ -27,16 +63,76 @@ export default async function SchedulePage() {
);
}

async function NextRound() {
const next = await getNext();

if (!next) {
return (
<div className="flex h-44 flex-col items-center justify-center">
<p>No upcomming weekend found</p>
</div>
);
}

const nextSession = next.sessions.filter((s) => utc(s.start) > utc() && s.kind.toLowerCase() !== "race")[0];
const nextRace = next.sessions.find((s) => s.kind.toLowerCase() == "race");

return (
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
{nextSession || nextRace ? (
<div className="flex flex-col gap-4">
{nextSession && <Countdown next={nextSession} type="other" />}
{nextRace && <Countdown next={nextRace} type="race" />}
</div>
) : (
<div className="flex flex-col items-center justify-center">
<p>No upcomming sessions found</p>
</div>
)}

<Round round={next} />
</div>
);
}

async function Schedule() {
const schedule = await getSchedule();

if (!schedule) {
return (
<div className="flex h-44 flex-col items-center justify-center">
<p>Schedule not found</p>
</div>
);
}

const next = schedule.filter((round) => !round.over)[0];

return (
<div className="mb-20 grid grid-cols-1 gap-2 md:grid-cols-2">
{schedule.map((round, roundI) => (
<Round nextName={next?.name} round={round} key={`round.${roundI}`} />
))}
</div>
);
}

// loading ui

const RoundLoading = () => {
return (
<div className="flex flex-col gap-1">
<div className="h-12 w-full animate-pulse rounded-md bg-zinc-800" />
<div className="flex flex-col gap-1 rounded-lg border border-zinc-800">
<div className="flex items-center gap-2 border-b border-zinc-800 p-3">
<div className="h-12 w-16 animate-pulse rounded-md bg-zinc-800" />
<div className="h-8 flex-1 animate-pulse rounded-md bg-zinc-800" />
</div>

<div className="grid grid-cols-3 gap-8 pt-1">
<div className="grid grid-cols-3 gap-2 p-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={`day.${i}`} className="grid grid-rows-2 gap-2">
<div className="h-12 w-full animate-pulse rounded-md bg-zinc-800" />
<div className="h-12 w-full animate-pulse rounded-md bg-zinc-800" />
<div key={`day.${i}`} className="flex flex-col gap-2">
<div className="h-8 w-full animate-pulse rounded-md bg-zinc-800" />
<div className="h-10 w-full animate-pulse rounded-md bg-zinc-800" />
<div className="h-10 w-full animate-pulse rounded-md bg-zinc-800" />
</div>
))}
</div>
Expand All @@ -46,7 +142,7 @@ const RoundLoading = () => {

const NextRoundLoading = () => {
return (
<div className="grid h-44 grid-cols-1 gap-8 sm:grid-cols-2">
<div className="grid grid-cols-1 sm:grid-cols-2">
<div className="flex flex-col gap-4">
<div className="h-1/2 w-3/4 animate-pulse rounded-md bg-zinc-800" />
<div className="h-1/2 w-3/4 animate-pulse rounded-md bg-zinc-800" />
Expand All @@ -59,7 +155,7 @@ const NextRoundLoading = () => {

const FullScheduleLoading = () => {
return (
<div className="mb-20 grid grid-cols-1 gap-8 md:grid-cols-2">
<div className="mb-20 grid grid-cols-1 gap-2 md:grid-cols-2">
{Array.from({ length: 6 }).map((_, i) => (
<RoundLoading key={`round.${i}`} />
))}
Expand Down
Loading