Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a stats page #34

Merged
merged 8 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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>
Loading