diff --git a/.changeset/fifty-baboons-run.md b/.changeset/fifty-baboons-run.md new file mode 100644 index 00000000..3b929e10 --- /dev/null +++ b/.changeset/fifty-baboons-run.md @@ -0,0 +1,5 @@ +--- +"authenticlash": minor +--- + +Add stats page diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 09be49f6..8a133a02 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -10,7 +10,8 @@ const links = [ { name: 'Games', href: '/games' }, - { name: 'Gallery', href: '/gallery' } + { name: 'Gallery', href: '/gallery' }, + { name: 'Stats', href: '/stats' } ]; let isMainMenuOpen = false; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c0ceea9b..0fb0de8a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -62,7 +62,7 @@
-
+
diff --git a/src/routes/stats/+page.server.ts b/src/routes/stats/+page.server.ts new file mode 100644 index 00000000..de7fcc9b --- /dev/null +++ b/src/routes/stats/+page.server.ts @@ -0,0 +1,61 @@ +import { fail, redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ locals: { getSession, supabase } }) => { + const session = await getSession(); + if (!session) { + redirect(303, '/auth/login'); + } + + const userId = session.user.id; + if (!userId) return fail(401, { message: 'User not found' }); + + const { data: games, error } = await supabase + .from('games') + .select( + 'id, creator, code, end_at, name, participation ( profile_id, score, total_score, nickname, nickname_image_url )' + ); + + if (error) { + return fail(500, { message: error }); + } + + const participatedGames = games + .filter((game) => game.participation.some((p) => p.profile_id === userId)) + .sort((a, b) => new Date(b.end_at).getTime() - new Date(a.end_at).getTime()); + + const allParticipations = participatedGames.flatMap((game) => + game.participation.filter((p) => p.profile_id === userId) + ); + + const allScores = allParticipations.flatMap((p) => p.score); + + const totalScoreAcrossGames = allScores.reduce((acc, score) => acc + score, 0); + + const average2FAScore = totalScoreAcrossGames / allScores.length; + + const averageTotalScore = + allParticipations.reduce((acc, p) => acc + p.total_score, 0) / allParticipations.length; + + const median2FAscore = allScores.sort((a, b) => a - b)[Math.floor(allScores.length / 2)]; + + const wins = participatedGames + .map((game) => { + const highscoreList = game.participation.sort((a, b) => b.total_score - a.total_score); + return highscoreList[0].profile_id === userId; + }) + .filter(Boolean).length; + + return { + stats: { + numberOfGames: participatedGames.length, + allScores, + totalScoreAcrossGames, + average2FAScore, + averageTotalScore, + median2FAscore, + wins + }, + title: 'Stats' + }; +}; diff --git a/src/routes/stats/+page.svelte b/src/routes/stats/+page.svelte new file mode 100644 index 00000000..bb5847c5 --- /dev/null +++ b/src/routes/stats/+page.svelte @@ -0,0 +1,82 @@ + + +
+ {#if numberOfGames} +
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ + + + +

+ +

+
+ +

+ +

+
+
+ {:else} +
+ +

No past games found

+

Get started by creating a new AuthentiClash session.

+ +
+ {/if} +
diff --git a/src/routes/stats/StatsCard.svelte b/src/routes/stats/StatsCard.svelte new file mode 100644 index 00000000..1785735f --- /dev/null +++ b/src/routes/stats/StatsCard.svelte @@ -0,0 +1,24 @@ + + +
+

{title}

+ +
diff --git a/src/routes/stats/StatsNumber.svelte b/src/routes/stats/StatsNumber.svelte new file mode 100644 index 00000000..09701d7b --- /dev/null +++ b/src/routes/stats/StatsNumber.svelte @@ -0,0 +1,11 @@ + + +{currentNum.toFixed(decimals)}