import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { supabase } from "@/integrations/supabase/client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { useToast } from "@/components/ui/use-toast"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { useTranslation } from "@/hooks/useTranslation"; interface HighScore { id: string; player_name: string; score: number; avg_words_per_round: number; created_at: string; session_id: string; } interface HighScoreBoardProps { currentScore: number; avgWordsPerRound: number; onClose: () => void; onPlayAgain: () => void; sessionId: string; onScoreSubmitted?: () => void; } const ITEMS_PER_PAGE = 5; const MAX_PAGES = 5; const getRankMedal = (rank: number) => { switch (rank) { case 1: return "πŸ₯‡"; case 2: return "πŸ₯ˆ"; case 3: return "πŸ₯‰"; default: return null; } }; export const HighScoreBoard = ({ currentScore, avgWordsPerRound, onClose, sessionId, onScoreSubmitted, }: HighScoreBoardProps) => { const [playerName, setPlayerName] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [hasSubmitted, setHasSubmitted] = useState(false); const [currentPage, setCurrentPage] = useState(1); const { toast } = useToast(); const t = useTranslation(); const queryClient = useQueryClient(); const { data: highScores, refetch } = useQuery({ queryKey: ["highScores"], queryFn: async () => { console.log("Fetching high scores..."); const { data, error } = await supabase .from("high_scores") .select("*") .order("score", { ascending: false }) .order("avg_words_per_round", { ascending: true }); if (error) { console.error("Error fetching high scores:", error); throw error; } console.log("Fetched high scores:", data); return data as HighScore[]; }, }); const handleSubmitScore = async () => { if (!playerName.trim() || !/^[\p{L}0-9]+$/u.test(playerName.trim())) { toast({ title: t.leaderboard.error.invalidName, description: t.leaderboard.error.invalidName, variant: "destructive", }); return; } if (currentScore < 1) { toast({ title: t.leaderboard.error.noRounds, description: t.leaderboard.error.noRounds, variant: "destructive", }); return; } if (hasSubmitted) { toast({ title: t.leaderboard.error.alreadySubmitted, description: t.leaderboard.error.alreadySubmitted, variant: "destructive", }); return; } setIsSubmitting(true); try { console.log("Checking existing score for player:", playerName.trim()); const { data: existingScores, error: fetchError } = await supabase .from("high_scores") .select("*") .eq("player_name", playerName.trim()); if (fetchError) { console.error("Error fetching existing scores:", fetchError); throw fetchError; } const existingScore = existingScores?.[0]; console.log("Existing score found:", existingScore); if (existingScore) { if (currentScore > existingScore.score) { console.log("Updating existing score..."); const { error: updateError } = await supabase .from("high_scores") .update({ score: currentScore, avg_words_per_round: avgWordsPerRound, session_id: sessionId }) .eq("id", existingScore.id); if (updateError) { console.error("Error updating score:", updateError); throw updateError; } toast({ title: t.leaderboard.error.newHighScore, description: t.leaderboard.error.beatRecord.replace( "{score}", String(existingScore.score) ), }); console.log("Score updated successfully"); await queryClient.invalidateQueries({ queryKey: ["highScores"] }); } else { toast({ title: t.leaderboard.error.notHigher .replace("{current}", String(currentScore)) .replace("{best}", String(existingScore.score)), variant: "destructive", }); setIsSubmitting(false); return; } } else { console.log("Inserting new score..."); const { error: insertError } = await supabase.from("high_scores").insert({ player_name: playerName.trim(), score: currentScore, avg_words_per_round: avgWordsPerRound, session_id: sessionId }); if (insertError) { console.error("Error inserting score:", insertError); throw insertError; } console.log("New score inserted successfully"); await queryClient.invalidateQueries({ queryKey: ["highScores"] }); } setHasSubmitted(true); onScoreSubmitted?.(); setPlayerName(""); } catch (error) { console.error("Error submitting score:", error); toast({ title: t.leaderboard.error.submitError, description: t.leaderboard.error.submitError, variant: "destructive", }); } finally { setIsSubmitting(false); } }; const handleKeyDown = async (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); await handleSubmitScore(); } }; const totalPages = highScores ? Math.min(Math.ceil(highScores.length / ITEMS_PER_PAGE), MAX_PAGES) : 0; const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const paginatedScores = highScores?.slice(startIndex, startIndex + ITEMS_PER_PAGE); const handlePreviousPage = () => { if (currentPage > 1) { setCurrentPage(p => p - 1); } }; const handleNextPage = () => { if (currentPage < totalPages) { setCurrentPage(p => p + 1); } }; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'ArrowLeft') { handlePreviousPage(); } else if (e.key === 'ArrowRight') { handleNextPage(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [currentPage, totalPages]); return (

{t.leaderboard.title}

{t.leaderboard.yourScore}: {currentScore} {t.leaderboard.roundCount} {currentScore > 0 && ` (${avgWordsPerRound.toFixed(1)} ${t.leaderboard.wordsPerRound})`}

{!hasSubmitted && currentScore > 0 && (
{ const value = e.target.value.replace(/[^a-zA-ZΓ€-ΓΏ0-9]/g, ""); setPlayerName(value); }} onKeyDown={handleKeyDown} className="flex-1" maxLength={20} />
)}
{t.leaderboard.rank} {t.leaderboard.player} {t.leaderboard.roundsColumn} {t.leaderboard.avgWords} {paginatedScores?.map((score, index) => { const absoluteRank = startIndex + index + 1; const medal = getRankMedal(absoluteRank); return ( {absoluteRank} {medal && {medal}} {score.player_name} {score.score} {score.avg_words_per_round.toFixed(1)} ); })} {!paginatedScores?.length && ( {t.leaderboard.noScores} )}
{totalPages > 1 && ( {t.leaderboard.previous} ← {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( setCurrentPage(page)} isActive={currentPage === page} > {page} ))} {t.leaderboard.next} β†’ )}
); };