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:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user