feat: display gamification stats on user profile page

Add gamification card to user profile showing:
- Total weighted points and rank
- Recordings and playbacks count
- Unlocked achievements with icons and dates
- Link to leaderboard

Updates user profile page server load to fetch gamification data
from /api/sexy/gamification/user/:id endpoint.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Valknar XXX
2025-10-28 13:36:32 +01:00
parent 064894b8bb
commit 6b3d770182
2 changed files with 96 additions and 0 deletions

View File

@@ -30,12 +30,20 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
const likesData = await likesResponse.json();
const likesCount = likesData.data?.[0]?.count || 0;
// Fetch gamification data
const gamificationResponse = await fetch(`/api/sexy/gamification/user/${id}`);
let gamification = null;
if (gamificationResponse.ok) {
gamification = await gamificationResponse.json();
}
return {
user,
stats: {
comments_count: commentsCount,
likes_count: likesCount,
},
gamification,
isOwnProfile: locals.authStatus.user?.id === id,
};
} catch (error) {

View File

@@ -137,5 +137,93 @@ let joinDate = $derived(
</div>
</CardContent>
</Card>
<!-- Gamification Card -->
{#if data.gamification?.stats}
<Card class="max-w-3xl mx-auto mt-6 bg-card/90 backdrop-blur-sm border-border/50">
<CardContent class="p-6 md:p-8">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold flex items-center gap-2">
<span class="icon-[ri--trophy-line] w-6 h-6 text-primary"></span>
{$_("gamification.stats")}
</h2>
<Button variant="outline" size="sm" href="/leaderboard">
<span class="icon-[ri--bar-chart-line] w-4 h-4 mr-2"></span>
{$_("gamification.leaderboard")}
</Button>
</div>
<!-- Stats Grid -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="text-center p-4 rounded-lg bg-accent/10">
<div class="text-3xl font-bold text-primary">
{Math.round(data.gamification.stats.total_weighted_points)}
</div>
<div class="text-sm text-muted-foreground mt-1">
{$_("gamification.points")}
</div>
</div>
<div class="text-center p-4 rounded-lg bg-accent/10">
<div class="text-3xl font-bold text-primary">
#{data.gamification.stats.rank}
</div>
<div class="text-sm text-muted-foreground mt-1">
{$_("gamification.rank")}
</div>
</div>
<div class="text-center p-4 rounded-lg bg-accent/10">
<div class="text-3xl font-bold text-primary">
{data.gamification.stats.recordings_count}
</div>
<div class="text-sm text-muted-foreground mt-1">
{$_("gamification.recordings")}
</div>
</div>
<div class="text-center p-4 rounded-lg bg-accent/10">
<div class="text-3xl font-bold text-primary">
{data.gamification.stats.playbacks_count}
</div>
<div class="text-sm text-muted-foreground mt-1">
{$_("gamification.plays")}
</div>
</div>
</div>
<!-- Achievements -->
{#if data.gamification.achievements?.length > 0}
<div class="pt-6 border-t border-border/50">
<h3 class="text-lg font-semibold mb-4 flex items-center gap-2">
<span class="icon-[ri--award-line] w-5 h-5 text-primary"></span>
{$_("gamification.achievements")} ({data.gamification.achievements.length})
</h3>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-3">
{#each data.gamification.achievements as achievement (achievement.id)}
<div
class="flex flex-col items-center gap-2 p-3 rounded-lg bg-accent/10 border border-border/30 hover:border-primary/50 transition-colors"
title={achievement.description}
>
<span class="text-3xl">{achievement.icon || "🏆"}</span>
<span class="text-xs font-medium text-center leading-tight">
{achievement.name}
</span>
{#if achievement.date_unlocked}
<span class="text-xs text-muted-foreground">
{new Date(achievement.date_unlocked).toLocaleDateString($locale)}
</span>
{/if}
</div>
{/each}
</div>
</div>
{:else}
<div class="pt-6 border-t border-border/50 text-center text-muted-foreground">
<span class="icon-[ri--trophy-line] w-8 h-8 mx-auto mb-2 opacity-50"></span>
<p class="text-sm">No achievements unlocked yet</p>
</div>
{/if}
</CardContent>
</Card>
{/if}
</div>
</div>