| import { useState } from "react"; | |
| import { supabase } from "@/integrations/supabase/client"; | |
| import { useQuery, useQueryClient } from "@tanstack/react-query"; | |
| import { useToast } from "@/components/ui/use-toast"; | |
| import { useTranslation } from "@/hooks/useTranslation"; | |
| import { ThemeFilter } from "./game/leaderboard/ThemeFilter"; | |
| import { ScoreSubmissionForm } from "./game/leaderboard/ScoreSubmissionForm"; | |
| import { ScoresTable } from "./game/leaderboard/ScoresTable"; | |
| import { LeaderboardHeader } from "./game/leaderboard/LeaderboardHeader"; | |
| import { LeaderboardPagination } from "./game/leaderboard/LeaderboardPagination"; | |
| import { getDailyGames } from "@/services/dailyGameService"; | |
| import { useNavigate } from "react-router-dom"; | |
| interface HighScore { | |
| id: string; | |
| player_name: string; | |
| score: number; | |
| avg_words_per_round: number; | |
| created_at: string; | |
| session_id: string; | |
| theme: string; | |
| game?: { | |
| language: string; | |
| }; | |
| game_id?: string; | |
| } | |
| interface HighScoreBoardProps { | |
| currentScore?: number; | |
| avgWordsPerRound?: number; | |
| onClose?: () => void; | |
| gameId?: string; | |
| sessionId?: string; | |
| onScoreSubmitted?: () => void; | |
| showThemeFilter?: boolean; | |
| initialTheme?: string; | |
| } | |
| const ITEMS_PER_PAGE = 5; | |
| export const HighScoreBoard = ({ | |
| currentScore = 0, | |
| avgWordsPerRound = 0, | |
| onClose, | |
| gameId = "", | |
| sessionId = "", | |
| onScoreSubmitted, | |
| showThemeFilter = true, | |
| initialTheme = "standard", | |
| }: HighScoreBoardProps) => { | |
| const [playerName, setPlayerName] = useState(""); | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| const [hasSubmitted, setHasSubmitted] = useState(false); | |
| const [currentPage, setCurrentPage] = useState(1); | |
| const [selectedMode, setSelectedMode] = useState<'daily' | 'all-time'>('daily'); | |
| const { toast } = useToast(); | |
| const t = useTranslation(); | |
| const queryClient = useQueryClient(); | |
| const navigate = useNavigate(); | |
| const showScoreInfo = sessionId !== "" && currentScore > 0; | |
| const { data: highScores } = useQuery({ | |
| queryKey: ["highScores", selectedMode, gameId], | |
| queryFn: async () => { | |
| console.log("Fetching high scores for mode:", selectedMode, "gameId:", gameId); | |
| let query = supabase | |
| .from("high_scores") | |
| .select("*, game:games(language)") | |
| .order("score", { ascending: false }) | |
| .order("avg_words_per_round", { ascending: true }); | |
| if (gameId) { | |
| query = query.eq('game_id', gameId); | |
| console.log("Filtering scores by game_id:", gameId); | |
| } else if (selectedMode === 'daily') { | |
| const dailyGames = await getDailyGames(); | |
| const dailyGameIds = dailyGames.map(game => game.game_id); | |
| query = query.in('game_id', dailyGameIds); | |
| console.log("Filtering scores by daily game_ids:", dailyGameIds); | |
| } | |
| const { data, error } = await query; | |
| 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() || !/^[a-zA-ZÀ-ÿ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("Submitting score via Edge Function..."); | |
| const { data, error } = await supabase.functions.invoke('submit-high-score', { | |
| body: { | |
| playerName: playerName.trim(), | |
| score: currentScore, | |
| avgWordsPerRound, | |
| sessionId, | |
| theme: initialTheme, | |
| gameId | |
| } | |
| }); | |
| if (error) { | |
| console.error("Error submitting score:", error); | |
| throw error; | |
| } | |
| console.log("Score submitted successfully:", data); | |
| if (data.success) { | |
| toast({ | |
| title: data.isUpdate ? t.leaderboard.scoreUpdated : t.leaderboard.scoreSubmitted, | |
| description: data.isUpdate ? t.leaderboard.scoreUpdatedDesc : t.leaderboard.scoreSubmittedDesc, | |
| }); | |
| setHasSubmitted(true); | |
| onScoreSubmitted?.(); | |
| setPlayerName(""); | |
| await queryClient.invalidateQueries({ queryKey: ["highScores"] }); | |
| } | |
| } 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<HTMLInputElement>) => { | |
| if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| await handleSubmitScore(); | |
| } | |
| }; | |
| const handlePlayGame = async (gameId: string) => { | |
| try { | |
| console.log("Creating new session for game:", gameId); | |
| const { data: session, error } = await supabase | |
| .from('sessions') | |
| .insert({ | |
| game_id: gameId | |
| }) | |
| .select() | |
| .single(); | |
| if (error) throw error; | |
| console.log("Session created:", session); | |
| navigate(`/game/${gameId}`); | |
| onClose?.(); | |
| } catch (error) { | |
| console.error('Error creating session:', error); | |
| toast({ | |
| title: t.game.error.title, | |
| description: t.game.error.description, | |
| variant: "destructive", | |
| }); | |
| } | |
| }; | |
| const totalPages = highScores ? Math.ceil(highScores.length / ITEMS_PER_PAGE) : 0; | |
| const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; | |
| const paginatedScores = highScores?.slice(startIndex, startIndex + ITEMS_PER_PAGE); | |
| return ( | |
| <div className="space-y-6"> | |
| <LeaderboardHeader | |
| currentScore={currentScore} | |
| avgWordsPerRound={avgWordsPerRound} | |
| showScoreInfo={showScoreInfo} | |
| /> | |
| {showThemeFilter && !gameId && ( | |
| <ThemeFilter | |
| selectedMode={selectedMode} | |
| onModeChange={setSelectedMode} | |
| /> | |
| )} | |
| {!hasSubmitted && currentScore > 0 && ( | |
| <ScoreSubmissionForm | |
| playerName={playerName} | |
| setPlayerName={setPlayerName} | |
| isSubmitting={isSubmitting} | |
| hasSubmitted={hasSubmitted} | |
| onSubmit={handleSubmitScore} | |
| onKeyDown={handleKeyDown} | |
| /> | |
| )} | |
| <ScoresTable | |
| scores={paginatedScores || []} | |
| startIndex={startIndex} | |
| showThemeColumn={selectedMode === 'daily'} | |
| onPlayGame={handlePlayGame} | |
| selectedMode={selectedMode} | |
| /> | |
| <LeaderboardPagination | |
| currentPage={currentPage} | |
| totalPages={totalPages} | |
| onPreviousPage={() => setCurrentPage(p => Math.max(1, p - 1))} | |
| onNextPage={() => setCurrentPage(p => Math.min(totalPages, p + 1))} | |
| /> | |
| </div> | |
| ); | |
| }; |