veza/apps/web/src/components/gamification/ProfileXPView.tsx

199 lines
7.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { XPBar } from './XPBar';
import { AchievementCard } from './AchievementCard';
import { TrendingUp, Target, Crown, Zap, Loader2 } from 'lucide-react';
import { Achievement } from '../../types';
import { gamificationService } from '../../services/gamificationService';
import { logger } from '@/utils/logger';
interface ProfileXPViewProps {
username: string;
}
export const ProfileXPView: React.FC<ProfileXPViewProps> = ({ username }) => {
const [xpData, setXpData] = useState<any>(null);
const [recentAchievements, setRecentAchievements] = useState<Achievement[]>(
[],
);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const [xp, achievements] = await Promise.all([
gamificationService.getUserXP('me'),
gamificationService.getAchievements('me'),
]);
setXpData(xp);
setRecentAchievements(achievements.slice(0, 3));
} catch (e) {
logger.error('Error loading profile XP data', {
error: e instanceof Error ? e.message : String(e),
stack: e instanceof Error ? e.stack : undefined,
username,
});
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading)
return (
<div className="flex justify-center py-20">
<Loader2 className="w-10 h-10 text-kodo-cyan animate-spin" />
</div>
);
return (
<div className="space-y-8 animate-fadeIn pb-20">
<h2 className="text-2xl font-display font-bold text-white mb-6">
LEVEL & PROGRESS
</h2>
{/* Main XP Card */}
<Card
variant="gaming"
className="p-8 relative overflow-hidden border-kodo-gold/30"
>
<div className="relative z-10 flex flex-col md:flex-row items-center gap-8">
{/* Level Badge */}
<div className="flex flex-col items-center justify-center">
<div className="w-24 h-24 bg-gradient-to-b from-kodo-gold to-orange-600 rounded-full flex items-center justify-center shadow-[0_0_30px_rgba(234,179,8,0.4)] border-4 border-black">
<div className="text-4xl font-black text-black">
{xpData.level}
</div>
</div>
<div className="mt-2 text-kodo-gold font-bold uppercase tracking-widest text-sm">
Level
</div>
</div>
{/* Progress */}
<div className="flex-1 w-full space-y-4">
<div className="flex justify-between items-end">
<div>
<h3 className="text-2xl font-bold text-white">{username}</h3>
<p className="text-kodo-content-dim text-sm">
Producer Rank #{xpData.rank}
</p>
</div>
<div className="text-right">
<div className="text-2xl font-mono font-bold text-kodo-gold">
{xpData.current} XP
</div>
<div className="text-xs text-kodo-content-dim">
Next Level: {xpData.next} XP
</div>
</div>
</div>
<XPBar
currentXP={xpData.current}
nextLevelXP={xpData.next}
level={xpData.level}
size="lg"
showLabels={false}
/>
<div className="flex gap-4 pt-2">
<div className="bg-black/30 px-3 py-1 rounded text-xs text-kodo-content-dim">
<span className="text-white font-bold">
{xpData.totalEarned.toLocaleString()}
</span>{' '}
Total Lifetime XP
</div>
<div className="bg-black/30 px-3 py-1 rounded text-xs text-kodo-content-dim">
<span className="text-kodo-lime font-bold">+12%</span> vs Last
Week
</div>
</div>
</div>
</div>
</Card>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card variant="default" className="flex items-center gap-4 p-4">
<div className="w-12 h-12 bg-kodo-ink rounded-lg flex items-center justify-center text-kodo-gold">
<Crown className="w-6 h-6" />
</div>
<div>
<div className="text-xs text-kodo-content-dim uppercase font-bold">
Global Rank
</div>
<div className="text-xl font-bold text-white">#{xpData.rank}</div>
</div>
</Card>
<Card variant="default" className="flex items-center gap-4 p-4">
<div className="w-12 h-12 bg-kodo-ink rounded-lg flex items-center justify-center text-kodo-cyan">
<Zap className="w-6 h-6" />
</div>
<div>
<div className="text-xs text-kodo-content-dim uppercase font-bold">
Daily Streak
</div>
<div className="text-xl font-bold text-white">12 Days</div>
</div>
</Card>
<Card variant="default" className="flex items-center gap-4 p-4">
<div className="w-12 h-12 bg-kodo-ink rounded-lg flex items-center justify-center text-kodo-magenta">
<Target className="w-6 h-6" />
</div>
<div>
<div className="text-xs text-kodo-content-dim uppercase font-bold">
Quests Complete
</div>
<div className="text-xl font-bold text-white">8/10</div>
</div>
</Card>
</div>
{/* Recent Achievements */}
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="font-bold text-white">Recent Achievements</h3>
<Button variant="ghost" size="sm">
View All
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{recentAchievements.map((ach) => (
<AchievementCard key={ach.id} achievement={ach} />
))}
</div>
</div>
{/* XP History Graph (Mock) */}
<Card variant="default">
<h3 className="font-bold text-white mb-6 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-kodo-cyan" /> XP History
</h3>
<div className="h-48 flex items-end gap-2 px-2">
{Array.from({ length: 14 }).map((_, i) => (
<div
key={i}
className="flex-1 flex flex-col justify-end gap-1 h-full group relative cursor-pointer"
>
<div
className="w-full bg-kodo-gold rounded-t opacity-50 group-hover:opacity-100 transition-opacity"
style={{ height: `${Math.random() * 60 + 10}%` }}
></div>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none whitespace-nowrap">
+{Math.floor(Math.random() * 500)} XP
</div>
</div>
))}
</div>
<div className="flex justify-between text-xs text-kodo-content-dim mt-2">
<span>14 Days Ago</span>
<span>Today</span>
</div>
</Card>
</div>
);
};