description-improv / src /components /HighScoreBoard.tsx
Felix Zieger
daily challenges
a64b653
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>
);
};