|
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> |
|
); |
|
}; |