Skip to content

Commit

Permalink
Merge pull request #34 from Shtian/feat/stats-page
Browse files Browse the repository at this point in the history
Add a stats page
  • Loading branch information
Shtian authored Mar 28, 2024
2 parents bdbb3dd + 4f35d33 commit 58d0322
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-baboons-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"authenticlash": minor
---

Add stats page
3 changes: 2 additions & 1 deletion src/lib/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

<Toast />
<Header {session} />
<div class="mx-auto min-h-full max-w-7xl px-4 py-8 sm:px-6 sm:py-32 lg:px-8 lg:py-24">
<div class="mx-auto min-h-full max-w-7xl px-4 pt-8 sm:px-6 lg:pt-16">
<div class="mx-auto max-w-2xl">
<slot />
</div>
Expand Down
61 changes: 61 additions & 0 deletions src/routes/stats/+page.server.ts
Original file line number Diff line number Diff line change
@@ -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'
};
};
82 changes: 82 additions & 0 deletions src/routes/stats/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts">
import { RocketIcon } from 'lucide-svelte';
import StatsCard from './StatsCard.svelte';
import StatsNumber from './StatsNumber.svelte';
import ScoreGraph from '$lib/components/ScoreGraph.svelte';
export let data;
const {
numberOfGames,
allScores,
totalScoreAcrossGames,
average2FAScore,
averageTotalScore,
median2FAscore,
wins
} = data.stats;
</script>

<div class="mx-auto max-w-[1200px] px-6 lg:px-8 lg:py-10">
{#if numberOfGames}
<div class="grid grid-cols-12 gap-4">
<StatsCard title="Number of Games">
<p class="text-center text-4xl font-bold">
<StatsNumber value={numberOfGames} />
</p>
</StatsCard>
<StatsCard title="Number of Wins">
<p class="text-center text-4xl font-bold">
<StatsNumber value={wins} />
</p>
</StatsCard>
<StatsCard title="Average Total Score">
<p class="text-center text-4xl font-bold">
<StatsNumber value={averageTotalScore} decimals={2} />
</p>
</StatsCard>
<StatsCard title="Total accumulated score">
<p class="text-center text-4xl font-bold">
<StatsNumber value={totalScoreAcrossGames} />
</p>
</StatsCard>
<StatsCard title="2FA value history" cols="full">
<ScoreGraph scores={allScores} height={300} />
</StatsCard>
<StatsCard title="Median 2FA Value">
<p class="text-center text-4xl font-bold">
<StatsNumber value={median2FAscore} />
</p>
</StatsCard>
<StatsCard title="Average 2FA Value">
<p class="text-center text-4xl font-bold">
<StatsNumber value={average2FAScore} decimals={2} />
</p>
</StatsCard>
</div>
{:else}
<div class="mx-auto max-w-7xl py-6 text-center sm:px-6 lg:px-8 lg:py-10">
<RocketIcon class="mx-auto h-12 w-12 text-gray-400" />
<h3 class="mt-2 text-sm font-semibold text-white">No past games found</h3>
<p class="mt-1 text-sm text-white">Get started by creating a new AuthentiClash session.</p>
<div class="mt-6">
<a
href="/games/create"
type="button"
class="inline-flex items-center rounded-md bg-clash-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-clash-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-clash-600"
>
<svg
class="-ml-0.5 mr-1.5 h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
New AuthentiClash
</a>
</div>
</div>
{/if}
</div>
24 changes: 24 additions & 0 deletions src/routes/stats/StatsCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
export let title: string;
export let cols: 'single' | 'half' | 'full' = 'half';
let colClass = 'sm:col-span-6';
switch (cols) {
case 'half':
colClass = 'sm:col-span-6';
break;
case 'full':
colClass = 'sm:col-span-12';
break;
case 'single':
colClass = 'sm:col-span-1';
break;
}
</script>

<div
class={`col-span-12 ${colClass} flex flex-col gap-4 rounded-lg border-[1px] border-gray-700 p-6 shadow-sm`}
>
<h2 class="text-xl text-gray-300">{title}</h2>
<slot />
</div>
11 changes: 11 additions & 0 deletions src/routes/stats/StatsNumber.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang="ts">
import { tweened } from 'svelte/motion';
export let value: number;
export let decimals = 0;
export let duration = 300;
const progress = tweened(0, { duration });
progress.set(100);
$: currentNum = ($progress * value) / 100;
</script>

<span class="tabular-nums">{currentNum.toFixed(decimals)}</span>

0 comments on commit 58d0322

Please sign in to comment.