// @ts-nocheck 'use client'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Trophy, Star, Award, Flame, Crown, Zap, Target, BookOpen, Users, Palette, Dumbbell, Monitor, Lock, Unlock, Share2, Calendar, TrendingUp, ChevronRight, Sparkles, Heart, Shield, Music, GraduationCap, Rocket, Brain, Coffee, Medal, Gem, Sun, Moon, Eye, EyeOff, ChevronDown, Info, CheckCircle2, Clock, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Switch } from '@/components/ui/switch'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useAppStore } from '@/lib/store'; import { t } from '@/lib/i18n'; import { apiGet, apiPost } from '@/lib/api'; import { toast } from 'sonner'; // ── Types ────────────────────────────────────────────────────────────── interface BadgeData { id: string; name: string; description: string | null; icon: string; color: string; category: string; requirementType: string; requirementValue: number | null; isAuto: boolean; earned: boolean; earnedAt: string | null; } interface CharacterData { id: string; userId: string; schoolId: string; characterId: string; name: string; color: string; level: number; xp: number; mood: string; accessories: string | null; } interface LeaderboardEntry { studentId: string; firstName: string; lastName: string; xp: number; level: number; badgeCount: number; } // ── Constants ────────────────────────────────────────────────────────── const categoryConfig: Record = { competency: { label: 'Academic', icon: GraduationCap, color: 'text-emerald-600 dark:text-emerald-400', gradient: 'from-emerald-400 to-teal-500', bgLight: 'bg-emerald-50', bgDark: 'dark:bg-emerald-900/20' }, attendance: { label: 'Social', icon: Users, color: 'text-amber-600 dark:text-amber-400', gradient: 'from-amber-400 to-orange-500', bgLight: 'bg-amber-50', bgDark: 'dark:bg-amber-900/20' }, behavior: { label: 'Creative', icon: Palette, color: 'text-violet-600 dark:text-violet-400', gradient: 'from-violet-400 to-purple-500', bgLight: 'bg-violet-50', bgDark: 'dark:bg-violet-900/20' }, achievement: { label: 'Athletic', icon: Dumbbell, color: 'text-rose-600 dark:text-rose-400', gradient: 'from-rose-400 to-pink-500', bgLight: 'bg-rose-50', bgDark: 'dark:bg-rose-900/20' }, milestone: { label: 'Digital', icon: Monitor, color: 'text-sky-600 dark:text-sky-400', gradient: 'from-sky-400 to-cyan-500', bgLight: 'bg-sky-50', bgDark: 'dark:bg-sky-900/20' }, }; const iconMap: Record = { star: Star, book: BookOpen, flame: Flame, crown: Crown, trophy: Trophy, award: Award, heart: Heart, shield: Shield, music: Music, brain: Brain, target: Target, zap: Zap, coffee: Coffee, medal: Medal, gem: Gem, rocket: Rocket, }; const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.05 } }, }; const itemVariants = { hidden: { opacity: 0, y: 16 }, visible: { opacity: 1, y: 0, transition: { duration: 0.35, ease: 'easeOut' } }, }; // ── Circular Progress Component ──────────────────────────────────────── function CircularProgress({ value, size = 80, strokeWidth = 6, colorClass = 'stroke-emerald-400', children, }: { value: number; size?: number; strokeWidth?: number; colorClass?: string; children?: React.ReactNode; }) { const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const offset = circumference - (value / 100) * circumference; return (
{children}
); } // ── Confetti Effect ──────────────────────────────────────────────────── function ConfettiEffect({ active }: { active: boolean }) { const [particles, setParticles] = useState>([]); useEffect(() => { if (active) { const newParticles = Array.from({ length: 30 }, (_, i) => ({ id: i, x: Math.random() * 100, y: Math.random() * 100, color: ['bg-emerald-400', 'bg-amber-400', 'bg-rose-400', 'bg-violet-400', 'bg-sky-400', 'bg-teal-400', 'bg-pink-400'][i % 7], size: 4 + Math.random() * 8, delay: Math.random() * 0.5, shape: (['circle', 'square', 'triangle'] as const)[i % 3], })); setParticles(newParticles); const timer = setTimeout(() => setParticles([]), 3000); return () => clearTimeout(timer); } }, [active]); if (!active || particles.length === 0) return null; return (
{particles.map((p) => ( 0.5 ? 1 : -1)] }} transition={{ duration: 2 + Math.random(), delay: p.delay, ease: 'easeIn' }} /> ))}
); } // ── Badge Card Component ─────────────────────────────────────────────── function BadgeCard({ badge, onClick }: { badge: BadgeData; onClick: (badge: BadgeData) => void }) { const [showShine, setShowShine] = useState(false); const Icon = iconMap[badge.icon] || Award; const category = categoryConfig[badge.category] || categoryConfig.achievement; const isEarned = badge.earned; useEffect(() => { if (isEarned) { const timer = setTimeout(() => setShowShine(true), 500); return () => clearTimeout(timer); } }, [isEarned]); return ( onClick(badge)} >
{isEarned && showShine && ( )} {isEarned && ( )} {!isEarned && }

{badge.name}

{badge.description}

{isEarned && badge.earnedAt && (

{new Date(badge.earnedAt).toLocaleDateString()}

)} {!isEarned && badge.requirementValue && (

{badge.requirementType === 'mastery_level' ? `Level ${badge.requirementValue}` : `${badge.requirementValue} pts`}

)}
); } // ── Badge Detail Modal ───────────────────────────────────────────────── function BadgeDetailModal({ badge, open, onClose, onShare }: { badge: BadgeData | null; open: boolean; onClose: () => void; onShare: (badge: BadgeData) => void }) { if (!badge) return null; const Icon = iconMap[badge.icon] || Award; const category = categoryConfig[badge.category] || categoryConfig.achievement; const CatIcon = category.icon; const isEarned = badge.earned; return ( {isEarned ? : } {isEarned ? t('achievements.badge_detail') : t('achievements.badge_locked')} {t('achievements.badge_detail_desc')}
{isEarned && ( )}

{badge.name}

{badge.description}

{category.label}
{badge.isAuto ? t('achievements.auto_badge') : t('achievements.manual_badge')}
{isEarned && badge.earnedAt && (
{t('achievements.earned_on')} {new Date(badge.earnedAt).toLocaleDateString()}
)} {!isEarned && badge.requirementValue && (
{t('achievements.progress')} 0 / {badge.requirementValue}
)}
{isEarned && ( )}
); } // ── Main Component ───────────────────────────────────────────────────── export default function StudentAchievementsView() { const { currentUser, locale } = useAppStore(); const [badges, setBadges] = useState([]); const [character, setCharacter] = useState(null); const [leaderboard, setLeaderboard] = useState([]); const [isLoading, setIsLoading] = useState(true); const [activeTab, setActiveTab] = useState('badges'); const [showConfetti, setShowConfetti] = useState(false); const [selectedBadge, setSelectedBadge] = useState(null); const [showBadgeDetail, setShowBadgeDetail] = useState(false); const [showShareDialog, setShowShareDialog] = useState(false); const [leaderboardOptIn, setLeaderboardOptIn] = useState(true); const [filterCategory, setFilterCategory] = useState('all'); const fetchAchievements = useCallback(async () => { if (!currentUser?.schoolId) return; setIsLoading(true); try { const data = await apiGet<{ badges: BadgeData[]; earnedBadges: unknown[]; character: CharacterData | null; leaderboard: LeaderboardEntry[]; totalXP: number; level: number; }>(`/api/student-achievements?schoolId=${currentUser.schoolId}`); if (data) { setBadges(data.badges || []); setCharacter(data.character || null); setLeaderboard(data.leaderboard || []); } } catch { // Use demo data on error setBadges(demoBadges); setCharacter(demoCharacter); setLeaderboard(demoLeaderboard); } finally { setIsLoading(false); } }, [currentUser?.schoolId]); useEffect(() => { fetchAchievements(); }, [fetchAchievements]); // Computed values const earnedBadges = useMemo(() => badges.filter(b => b.earned), [badges]); const lockedBadges = useMemo(() => badges.filter(b => !b.earned), [badges]); const totalXP = character?.xp ?? 0; const level = character?.level ?? 1; const xpForNextLevel = level * 100; const xpInCurrentLevel = totalXP - (level - 1) * 100; const xpProgress = xpForNextLevel > 0 ? (xpInCurrentLevel / xpForNextLevel) * 100 : 0; const recentlyEarnedBadges = useMemo(() => { return earnedBadges .filter(b => b.earnedAt) .sort((a, b) => new Date(b.earnedAt!).getTime() - new Date(a.earnedAt!).getTime()) .slice(0, 4); }, [earnedBadges]); const categoryStats = useMemo(() => { const stats: Record = {}; badges.forEach(b => { if (!stats[b.category]) stats[b.category] = { earned: 0, total: 0, progress: 0 }; stats[b.category].total++; if (b.earned) stats[b.category].earned++; }); Object.keys(stats).forEach(key => { stats[key].progress = stats[key].total > 0 ? (stats[key].earned / stats[key].total) * 100 : 0; }); return stats; }, [badges]); const filteredBadges = useMemo(() => { if (filterCategory === 'all') return badges; return badges.filter(b => b.category === filterCategory); }, [badges, filterCategory]); const filteredEarned = useMemo(() => filteredBadges.filter(b => b.earned), [filteredBadges]); const filteredLocked = useMemo(() => filteredBadges.filter(b => !b.earned), [filteredBadges]); const handleBadgeClick = (badge: BadgeData) => { setSelectedBadge(badge); setShowBadgeDetail(true); }; const handleShare = (badge: BadgeData) => { setSelectedBadge(badge); setShowBadgeDetail(false); setShowShareDialog(true); }; const handleShareConfirm = () => { toast.success(t('achievements.share_success')); setShowConfetti(true); setTimeout(() => setShowConfetti(false), 3000); setShowShareDialog(false); setSelectedBadge(null); }; const handleLeaderboardOptIn = (checked: boolean) => { setLeaderboardOptIn(checked); toast.success(checked ? t('achievements.leaderboard_joined') : t('achievements.leaderboard_left')); }; // ── Demo Data ────────────────────────────────────────────────────── const demoBadges: BadgeData[] = [ { id: '1', name: 'First Steps', description: 'Complete your first competency', icon: 'star', color: '#10b981', category: 'competency', requirementType: 'mastery_level', requirementValue: 1, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 14).toISOString() }, { id: '2', name: 'Bookworm', description: 'Complete 10 notebook pages', icon: 'book', color: '#f59e0b', category: 'competency', requirementType: 'progress_entries', requirementValue: 10, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 7).toISOString() }, { id: '3', name: 'Streak Master', description: '7-day learning streak', icon: 'flame', color: '#ef4444', category: 'attendance', requirementType: 'attendance_rate', requirementValue: 7, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 2).toISOString() }, { id: '4', name: 'Competency King', description: 'Reach level 3 in any subject', icon: 'crown', color: '#8b5cf6', category: 'achievement', requirementType: 'mastery_level', requirementValue: 3, isAuto: true, earned: false, earnedAt: null }, { id: '5', name: 'Perfect Score', description: 'Score 100% on an assessment', icon: 'trophy', color: '#f59e0b', category: 'achievement', requirementType: 'custom', requirementValue: 100, isAuto: true, earned: false, earnedAt: null }, { id: '6', name: 'Team Player', description: 'Help 5 classmates', icon: 'heart', color: '#ec4899', category: 'behavior', requirementType: 'custom', requirementValue: 5, isAuto: false, earned: true, earnedAt: new Date(Date.now() - 86400000 * 3).toISOString() }, { id: '7', name: 'Creative Mind', description: 'Create 3 original projects', icon: 'palette', color: '#8b5cf6', category: 'behavior', requirementType: 'custom', requirementValue: 3, isAuto: false, earned: false, earnedAt: null }, { id: '8', name: 'Digital Native', description: 'Complete 20 online exercises', icon: 'monitor', color: '#06b6d4', category: 'milestone', requirementType: 'progress_entries', requirementValue: 20, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 5).toISOString() }, { id: '9', name: 'Math Wizard', description: 'Master all math competencies', icon: 'brain', color: '#10b981', category: 'competency', requirementType: 'mastery_level', requirementValue: 4, isAuto: true, earned: false, earnedAt: null }, { id: '10', name: 'Rising Star', description: 'Earn 10 badges', icon: 'star', color: '#f59e0b', category: 'milestone', requirementType: 'custom', requirementValue: 10, isAuto: true, earned: false, earnedAt: null }, { id: '11', name: 'Quick Learner', description: 'Complete a study session', icon: 'rocket', color: '#6366f1', category: 'milestone', requirementType: 'custom', requirementValue: 1, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000).toISOString() }, { id: '12', name: 'Focus Champion', description: 'Complete 5 Pomodoro sessions', icon: 'target', color: '#059669', category: 'achievement', requirementType: 'custom', requirementValue: 5, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 4).toISOString() }, { id: '13', name: 'Social Butterfly', description: 'Participate in 10 group activities', icon: 'users', color: '#f59e0b', category: 'attendance', requirementType: 'custom', requirementValue: 10, isAuto: true, earned: false, earnedAt: null }, { id: '14', name: 'Fitness Pro', description: 'Complete 20 PE lessons', icon: 'dumbbell', color: '#ef4444', category: 'achievement', requirementType: 'custom', requirementValue: 20, isAuto: true, earned: false, earnedAt: null }, { id: '15', name: 'Tech Guru', description: 'Submit 15 digital assignments', icon: 'monitor', color: '#06b6d4', category: 'milestone', requirementType: 'custom', requirementValue: 15, isAuto: true, earned: true, earnedAt: new Date(Date.now() - 86400000 * 6).toISOString() }, ]; const demoCharacter: CharacterData = { id: '1', userId: '1', schoolId: '1', characterId: 'owl', name: 'Hootie', color: '#10b981', level: 5, xp: 450, mood: 'happy', accessories: null, }; const demoLeaderboard: LeaderboardEntry[] = [ { studentId: '1', firstName: 'Max', lastName: 'M.', xp: 450, level: 5, badgeCount: 6 }, { studentId: '2', firstName: 'Anna', lastName: 'S.', xp: 380, level: 4, badgeCount: 5 }, { studentId: '3', firstName: 'Lukas', lastName: 'W.', xp: 320, level: 4, badgeCount: 4 }, { studentId: '4', firstName: 'Sophie', lastName: 'K.', xp: 290, level: 3, badgeCount: 4 }, { studentId: '5', firstName: 'Tim', lastName: 'B.', xp: 250, level: 3, badgeCount: 3 }, { studentId: '6', firstName: 'Lena', lastName: 'F.', xp: 220, level: 3, badgeCount: 2 }, { studentId: '7', firstName: 'Julian', lastName: 'R.', xp: 180, level: 2, badgeCount: 3 }, { studentId: '8', firstName: 'Mia', lastName: 'H.', xp: 150, level: 2, badgeCount: 2 }, ]; // ── Render ───────────────────────────────────────────────────────── if (isLoading) { return (
{[1, 2, 3, 4].map(i => (
))}
{[1, 2, 3, 4, 5, 6].map(i => (
))}
); } return ( {/* ── Header with gradient banner */} {/* Decorative background shapes */}

{t('achievements.title')}

{t('achievements.subtitle')}

{t('achievements.level')} {level}

{totalXP} XP

{/* ── XP Progress Bar with shimmer effect */}

{t('achievements.xp_progress')}

{xpInCurrentLevel} / {xpForNextLevel} XP

{t('achievements.level')} {level} {t('achievements.next_level')}: {level + 1}
{/* Shimmer effect */}

{xpForNextLevel - xpInCurrentLevel} XP {t('achievements.to_next_level')}

{Math.round(xpProgress)}%

{/* ── Stats Cards ────────────────────────────────────────────── */} {[ { label: t('achievements.badges_earned'), value: earnedBadges.length, icon: Award, color: 'from-emerald-400 to-teal-500' }, { label: t('achievements.badges_locked'), value: lockedBadges.length, icon: Lock, color: 'from-gray-400 to-gray-500' }, { label: t('achievements.total_xp'), value: totalXP, icon: Zap, color: 'from-amber-400 to-amber-500' }, { label: t('achievements.current_level'), value: level, icon: Crown, color: 'from-violet-400 to-purple-500' }, ].map((stat, idx) => { const Icon = stat.icon; return (

{stat.value}

{stat.label}

); })}
{/* ── Recently Earned Badges ─────────────────────────────────── */} {recentlyEarnedBadges.length > 0 && ( {t('achievements.recently_earned')}
{recentlyEarnedBadges.map((badge) => { const Icon = iconMap[badge.icon] || Award; const category = categoryConfig[badge.category] || categoryConfig.achievement; return ( handleBadgeClick(badge)} >

{badge.name}

{badge.earnedAt ? new Date(badge.earnedAt).toLocaleDateString() : ''}

); })}
)} {/* ── Category Progress with Animated Circles ────────────────── */} {t('achievements.category_progress')}
{Object.entries(categoryStats).map(([category, stats]) => { const config = categoryConfig[category] || categoryConfig.achievement; const Icon = config.icon; return ( setFilterCategory(filterCategory === category ? 'all' : category)} >

{config.label}

{stats.earned}/{stats.total} {t('achievements.badges')}

{filterCategory === category && ( )}
); })}
{/* ── Main Content Tabs ──────────────────────────────────────── */} {t('achievements.badges')} {t('achievements.leaderboard')} {t('achievements.challenges')} {/* ── Badges Tab ─────────────────────────────────────────── */} {/* Category Filter */}
{Object.entries(categoryConfig).map(([key, config]) => { const Icon = config.icon; return ( ); })}
{/* Earned Badges */}

{t('achievements.earned_badges')} ({filteredEarned.length})

{filteredEarned.map(badge => (
{t('achievements.share')}
))}
{/* Locked Badges */} {filteredLocked.length > 0 && (

{t('achievements.locked_badges')} ({filteredLocked.length})

{filteredLocked.map(badge => ( ))}
)}
{/* ── Leaderboard Tab ────────────────────────────────────── */}
{t('achievements.class_leaderboard')}
{leaderboardOptIn ? (
{leaderboard.map((entry, idx) => { const rank = idx + 1; const isCurrentUser = entry.studentId === currentUser?.id; const rankColors = [ 'from-amber-400 to-amber-500', 'from-gray-300 to-gray-400', 'from-orange-400 to-orange-500', ]; const rankIcons = [Crown, Medal, Award]; return (
{rank <= 3 ? React.createElement(rankIcons[rank - 1], { className: 'h-4 w-4' }) : {rank}}
{entry.firstName[0]}{entry.lastName[0]}

{entry.firstName} {entry.lastName} {isCurrentUser && `(${t('achievements.you')})`}

{t('achievements.level')} {entry.level} · {entry.badgeCount} {t('achievements.badges')}

{entry.xp}
); })}
) : (

{t('achievements.leaderboard_opt_out')}

)}
{/* ── Challenges Tab ─────────────────────────────────────── */} {/* Daily Challenge */}
{t('achievements.daily_challenge')} +50 XP
{[ { title: t('achievements.daily_challenge_1'), desc: t('achievements.daily_challenge_1_desc'), xp: 50, progress: 60, icon: Brain }, { title: t('achievements.daily_challenge_2'), desc: t('achievements.daily_challenge_2_desc'), xp: 30, progress: 0, icon: BookOpen }, { title: t('achievements.daily_challenge_3'), desc: t('achievements.daily_challenge_3_desc'), xp: 20, progress: 100, icon: Target }, ].map((challenge, idx) => { const Icon = challenge.icon; return (
0 ? 'bg-gradient-to-br from-amber-400 to-amber-500 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-400' } shadow-sm`}>

{challenge.title}

{challenge.progress === 100 ? t('achievements.completed') : `+${challenge.xp} XP`}

{challenge.desc}

{challenge.progress > 0 && challenge.progress < 100 && (
{challenge.progress}%
)}
); })}
{/* Weekly Challenge */}
{t('achievements.weekly_challenge')} +200 XP

{t('achievements.weekly_challenge_title')}

{t('achievements.weekly_challenge_desc')}

45%

{t('achievements.time_remaining')}: 3 {t('achievements.days')}

{/* Achievement Streaks */}
{t('achievements.streaks')}
{[ { label: t('achievements.current_streak'), value: 5, unit: t('achievements.days'), color: 'from-rose-400 to-rose-500' }, { label: t('achievements.longest_streak'), value: 12, unit: t('achievements.days'), color: 'from-amber-400 to-amber-500' }, { label: t('achievements.total_badges'), value: earnedBadges.length, unit: t('achievements.badges'), color: 'from-emerald-400 to-teal-500' }, ].map((stat, idx) => (
{stat.value}

{stat.label}

{stat.unit}

))}
{/* ── Badge Detail Modal ─────────────────────────────────────── */} { setShowBadgeDetail(false); setSelectedBadge(null); }} onShare={handleShare} /> {/* ── Share Dialog ───────────────────────────────────────────── */} {t('achievements.share_with_parents')} {t('achievements.share_description')} {selectedBadge && (
{React.createElement(iconMap[selectedBadge.icon] || Award, { className: 'h-5 w-5' })}

{selectedBadge.name}

{selectedBadge.description}

)}
); } // ── Label component (reuse from shadcn) ──────────────────────────────── function Label({ className, children, ...props }: React.LabelHTMLAttributes & { className?: string }) { return ; }