From b4076dbc784792a52677b331e19a4c9a57f1f071 Mon Sep 17 00:00:00 2001 From: Aviral Saxena Date: Tue, 9 Jun 2026 18:15:17 +0530 Subject: [PATCH 1/2] Fixed some bugs --- src/pages/potd.tsx | 291 ++++++++++++++++++++++----------------------- 1 file changed, 144 insertions(+), 147 deletions(-) diff --git a/src/pages/potd.tsx b/src/pages/potd.tsx index 923ed95..4a72839 100644 --- a/src/pages/potd.tsx +++ b/src/pages/potd.tsx @@ -1,13 +1,14 @@ import React, { useEffect, useState, useCallback } from "react"; import { getPOTD } from "../services/potd_fetch"; -import { doc, getDoc, setDoc, onSnapshot, increment, runTransaction, collection, query, where, getDocs, limit } from "firebase/firestore"; +import { doc, getDoc, setDoc, onSnapshot, collection, query, where, runTransaction } from "firebase/firestore"; +import { onAuthStateChanged } from "firebase/auth"; // MUST IMPORT THIS import { db, auth } from "../lib/firebase"; import styles from "../styles/POTDpage.module.css"; import Image from "next/image"; import { formatDescription } from "../utils/formatDescription"; import { toast } from "sonner"; import { checkCodeforcesSubmission } from "../services/codeforces_api"; -import { FiCheck, FiAward, FiClock, FiExternalLink, FiZap, FiTarget, FiTrendingUp } from "react-icons/fi"; +import { FiCheck, FiAward, FiExternalLink, FiZap, FiTarget, FiTrendingUp } from "react-icons/fi"; import { BsLightningCharge } from "react-icons/bs"; interface Solver { @@ -40,7 +41,7 @@ interface UserData { } const getLocalDateString = (date: Date = new Date()): string => - `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; const getStartOfWeek = (d: Date) => { const date = new Date(d); @@ -60,9 +61,22 @@ const POTDPage: React.FC = () => { const [userData, setUserData] = useState({ coins: 0, streak: 0 }); const [isCheckingCF, setIsCheckingCF] = useState(false); const [cfUsername, setCfUsername] = useState(""); + + // 1. REACT STATE FOR AUTH (Fixes the stale auth bug) + const [currentUser, setCurrentUser] = useState(auth.currentUser); const todayDate = getLocalDateString(); - const displayDate = new Date().toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }); + const displayDate = new Date().toLocaleDateString("en-US", { + weekday: "long", month: "long", day: "numeric", + }); + + // 2. FIREBASE AUTH LISTENER + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + setCurrentUser(user); + }); + return () => unsubscribe(); + }, []); useEffect(() => { async function fetchPOTD() { @@ -74,9 +88,13 @@ const POTDPage: React.FC = () => { if (!snapshot.exists()) throw new Error("Problem not found"); const data = snapshot.data(); setProblem({ - id, title: data.title, description: data.description, rating: data.rating, tags: data.tags, problemUrl: data.problemUrl + id, title: data.title, description: data.description, rating: data.rating, tags: data.tags, problemUrl: data.problemUrl, }); - } catch (err: any) { setError(err.message || "Error fetching POTD"); } finally { setLoading(false); } + } catch (err: any) { + setError(err.message || "Error fetching POTD"); + } finally { + setLoading(false); + } } fetchPOTD(); @@ -85,13 +103,15 @@ const POTDPage: React.FC = () => { collection(db, "users"), where("lastWeeklyResetDate", "==", currentStartOfWeek) ); - const unsubWeekly = onSnapshot(weeklyQ, async (snap: any) => { - let solvers = snap.docs.map((d: any) => ({ ...d.data(), uid: d.id } as UserData)); - - - solvers.sort((a: UserData, b: UserData) => (b.weeklyPotdSolves || 0) - (a.weeklyPotdSolves || 0)); - setTopWeeklySolvers(solvers); - }, (err: any) => console.error("Error watching weekly solvers", err)); + const unsubWeekly = onSnapshot( + weeklyQ, + (snap: any) => { + let solvers = snap.docs.map((d: any) => ({ ...d.data(), uid: d.id } as UserData)); + solvers.sort((a: UserData, b: UserData) => (b.weeklyPotdSolves || 0) - (a.weeklyPotdSolves || 0)); + setTopWeeklySolvers(solvers); + }, + (err: any) => console.error("Error watching weekly solvers", err) + ); return () => unsubWeekly(); }, []); @@ -104,110 +124,126 @@ const POTDPage: React.FC = () => { if (docSnap.exists()) { const data = docSnap.data(); if (data?.problemId === problem?.id) { - solversArray = Object.entries(data?.solvers || {}).map(([uid, value]: [string, any]) => ({ - uid, username: value.username, solvedAt: value.solvedAt, codeforcesUsername: value.codeforcesUsername - })); + solversArray = Object.entries(data?.solvers || {}).map( + ([uid, value]: [string, any]) => ({ + uid, username: value.username, solvedAt: value.solvedAt, codeforcesUsername: value.codeforcesUsername, + }) + ); } } else { setDoc(submissionRef, { problemId: problem?.id || "", solvers: {} }, { merge: true }); } - - - solversArray.sort((a, b) => new Date(a.solvedAt).getTime() - new Date(b.solvedAt).getTime()); + solversArray.sort((a, b) => new Date(a.solvedAt as string).getTime() - new Date(b.solvedAt as string).getTime()); setDailySolvers(solversArray); - setUserSolved(auth.currentUser ? solversArray.some((s) => s.uid === auth.currentUser?.uid) : false); + setUserSolved(currentUser ? solversArray.some((s) => s.uid === currentUser.uid) : false); }); return () => unsubscribe(); - }, [todayDate, problem?.id]); + }, [todayDate, problem?.id, currentUser]); useEffect(() => { - if (!auth.currentUser) return; - const userRef = doc(db, "users", auth.currentUser.uid); + if (!currentUser) return; + const userRef = doc(db, "users", currentUser.uid); const unsubscribe = onSnapshot(userRef, (docSnap) => { if (docSnap.exists()) { const data = docSnap.data() as UserData; - setUserData({ coins: data.coins || 0, streak: data.streak || 0, lastSolvedDate: data.lastSolvedDate, username: data.username }); + setUserData({ + coins: data.coins || 0, streak: data.streak || 0, lastSolvedDate: data.lastSolvedDate, username: data.username, + }); setCfUsername(data.username || ""); } }); return () => unsubscribe(); - }, [auth.currentUser]); + }, [currentUser]); + + // 3. THROW ERROR INSTEAD OF SILENT RETURN (Fixes Fake Success) + const markAsSolved = useCallback(async () => { + if (!currentUser || !problem || userSolved) { + throw new Error("Unable to save: Session expired or already verified."); + } - const handleCheckCFSubmission = useCallback(async () => { - if (!auth.currentUser || !problem || userSolved || isCheckingCF) return; - if (!cfUsername) { toast.error("Please set your Codeforces username in settings"); return; } - setIsCheckingCF(true); - try { - const urlMatch = problem.problemUrl?.match(/codeforces\.com\/problemset\/problem\/(\d+)\/([A-Z]\d?)/); - if (!urlMatch) { toast.error("Invalid Codeforces problem URL"); return; } - const [, contestId, problemIndex] = urlMatch; - const hasSolved = await checkCodeforcesSubmission(cfUsername, contestId, problemIndex); - if (hasSolved) { await markAsSolved(); toast.success("🎉 Verified! Solution found!"); } - else { toast.error("No accepted submission found today."); } - } catch (error: any) { toast.error(error.message); } finally { setIsCheckingCF(false); } - }, [auth.currentUser, problem, cfUsername, userSolved, isCheckingCF]); - - const markAsSolved = async () => { - if (!auth.currentUser || !problem || userSolved) return; - - try { const submissionRef = doc(db, "potd_submissions", todayDate); - const userRef = doc(db, "users", auth.currentUser.uid); - + const userRef = doc(db, "users", currentUser.uid); + await runTransaction(db, async (transaction) => { const userDoc = await transaction.get(userRef); - const currentUserData = userDoc.exists() ? (userDoc.data() as UserData) : { coins: 0, totalCoins: 0, streak: 0 }; - - // 1. Calculate Yesterday's Date (Using your fixed local time helper) - const yesterday = new Date(); + const currentUserData = userDoc.exists() + ? (userDoc.data() as UserData) + : { coins: 0, totalCoins: 0, streak: 0 }; + + if (currentUserData.lastSolvedDate === todayDate) { + return; // Safe to return here because the transaction will still succeed without doing damage + } + + const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); const yesterdayStr = getLocalDateString(yesterday); - - // Prevent double-counting if somehow they trigger it twice today - if (currentUserData.lastSolvedDate === todayDate) return; - // 2. Calculate New Streak - let newStreak = 1; - if (currentUserData.lastSolvedDate === yesterdayStr) { - newStreak = (currentUserData.streak || 0) + 1; - } + const newStreak = currentUserData.lastSolvedDate === yesterdayStr ? (currentUserData.streak || 0) + 1 : 1; - // 3. Calculate Weekly Solves const currentStartOfWeek = getStartOfWeek(new Date()); - let newWeeklySolves = 1; - if (currentUserData.lastWeeklyResetDate === currentStartOfWeek) { - newWeeklySolves = (currentUserData.weeklyPotdSolves || 0) + 1; - } - - // 4. Update the User Doc (Use SET with MERGE instead of UPDATE) - transaction.set(userRef, { - coins: (currentUserData.coins || 0) + 50, - totalCoins: (currentUserData.totalCoins || 0) + 50, - streak: newStreak, - lastSolvedDate: todayDate, - weeklyPotdSolves: newWeeklySolves, - lastWeeklyResetDate: currentStartOfWeek - }, { merge: true }); - - // 5. Update the Submissions Doc - transaction.set(submissionRef, { - problemId: problem.id, - solvers: { - [auth.currentUser!.uid]: { - uid: auth.currentUser!.uid, - username: userData.username || auth.currentUser!.displayName || "User", - solvedAt: new Date().toISOString() - } - } - }, { merge: true }); + const newWeeklySolves = currentUserData.lastWeeklyResetDate === currentStartOfWeek ? (currentUserData.weeklyPotdSolves || 0) + 1 : 1; + + const resolvedUsername = + userData.username || + currentUser.displayName || + (currentUser.email ? currentUser.email.split("@") : "User"); + + transaction.set( + userRef, + { + coins: (currentUserData.coins || 0) + 50, totalCoins: (currentUserData.totalCoins || 0) + 50, + streak: newStreak, lastSolvedDate: todayDate, weeklyPotdSolves: newWeeklySolves, lastWeeklyResetDate: currentStartOfWeek, + }, + { merge: true } + ); + + transaction.set( + submissionRef, + { + problemId: problem.id, + solvers: { + [currentUser.uid]: { + uid: currentUser.uid, username: resolvedUsername, solvedAt: new Date().toISOString(), + }, + }, + }, + { merge: true } + ); }); + }, [currentUser, problem, userSolved, todayDate, userData.username]); - } catch (error) { - console.error(error); - toast.error('Failed to save progress.'); - } -}; + const handleCheckCFSubmission = useCallback(async () => { + if (!currentUser || !problem || userSolved || isCheckingCF) return; + + const usernameToCheck = userData.username || cfUsername; + if (!usernameToCheck) { + toast.error("Please set your Codeforces username in settings"); + return; + } + + setIsCheckingCF(true); + try { + const urlMatch = problem.problemUrl?.match(/codeforces\.com\/problemset\/problem\/(\d+)\/([A-Z]\d?)/); + if (!urlMatch) { + toast.error("Invalid Codeforces problem URL"); + return; + } + const [, contestId, problemIndex] = urlMatch; + const hasSolved = await checkCodeforcesSubmission(usernameToCheck, contestId, problemIndex); + + if (hasSolved) { + await markAsSolved(); // If this fails, it jumps to the catch block and WON'T show success + toast.success("🎉 Verified! Solution found and progress saved!"); + } else { + toast.error("No accepted submission found today."); + } + } catch (error: any) { + toast.error(error.message || "Failed to save progress"); + } finally { + setIsCheckingCF(false); + } + }, [currentUser, problem, userData.username, cfUsername, userSolved, isCheckingCF, markAsSolved]); const getDifficulty = (rating?: number) => { if (!rating) return "Unknown"; @@ -220,7 +256,6 @@ const POTDPage: React.FC = () => { if (error || !problem) return
Error: {error || "Problem not found"}
; const difficulty = getDifficulty(problem.rating); - // No longer truncating description to provide full mission details in the dashboard const fullDesc = problem.description || ""; return ( @@ -228,7 +263,6 @@ const POTDPage: React.FC = () => {
- {/* High-Impact Header */}

@@ -236,46 +270,29 @@ const POTDPage: React.FC = () => {

{displayDate} — Complete your daily trial.

- Master a new algorithmic challenge every 24 hours. Boost your rating, - earn coins, and maintain your streak to climb the elite leaderboard. + Master a new algorithmic challenge every 24 hours. Boost your rating, earn coins, + and maintain your streak to climb the elite leaderboard.

- POTD Mascot + POTD Mascot
- - {/* Unified Mission Details */}

{problem.title}

- - {difficulty} - + {difficulty} {problem.rating && ( - - Rating: {problem.rating} - + Rating: {problem.rating} )}
-
- +
{problem.tags && (
{problem.tags.slice(0, 6).map((tag) => ( @@ -285,50 +302,35 @@ const POTDPage: React.FC = () => { )}
- {/* Action & Stats Center */}
💰
- Reward - {userData.coins} Coins + Coins + {userData.coins}
🔥
- Current Streak + Streak {userData.streak} Days
- - Solve Challenge - + Solve Challenge {userSolved ? ( -
- Verification Complete -
+
Verification Complete
) : ( - )} {problem.problemUrl && ( - + View on Codeforces )} @@ -336,10 +338,7 @@ const POTDPage: React.FC = () => {
- {/* Sidebar Leaderboards */}
-
@@ -401,4 +398,4 @@ const POTDPage: React.FC = () => { ); }; -export default POTDPage; +export default POTDPage; \ No newline at end of file From 6babaff095a3a5d50a2cda4f21dbd1d061a55ab4 Mon Sep 17 00:00:00 2001 From: Aviral Saxena Date: Tue, 9 Jun 2026 18:35:38 +0530 Subject: [PATCH 2/2] Fixed some bugs --- src/pages/potd.tsx | 10 ++++++++-- src/utils/formatDescription.tsx | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pages/potd.tsx b/src/pages/potd.tsx index 4a72839..1da4dc6 100644 --- a/src/pages/potd.tsx +++ b/src/pages/potd.tsx @@ -74,6 +74,12 @@ const POTDPage: React.FC = () => { useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setCurrentUser(user); + + if (!user) { + setUserData({ coins: 0, streak: 0 }); + setCfUsername(""); + setUserSolved(false); + } }); return () => unsubscribe(); }, []); @@ -172,7 +178,7 @@ const POTDPage: React.FC = () => { : { coins: 0, totalCoins: 0, streak: 0 }; if (currentUserData.lastSolvedDate === todayDate) { - return; // Safe to return here because the transaction will still succeed without doing damage + throw new Error("You have already verified today's POTD."); } const yesterday = new Date(); @@ -187,7 +193,7 @@ const POTDPage: React.FC = () => { const resolvedUsername = userData.username || currentUser.displayName || - (currentUser.email ? currentUser.email.split("@") : "User"); + (currentUser.email ? currentUser.email.split("@")[0] : "User"); transaction.set( userRef, diff --git a/src/utils/formatDescription.tsx b/src/utils/formatDescription.tsx index ce412fb..9902e82 100644 --- a/src/utils/formatDescription.tsx +++ b/src/utils/formatDescription.tsx @@ -7,10 +7,18 @@ * @param description - Raw problem description text * @returns Formatted HTML string */ +const escapeHtml = (value: string): string => + value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); + export function formatDescription(description: string): string { if (!description) return ""; - let formatted = description; + let formatted = escapeHtml(description); // Convert **text** to text for bold // This regex matches **text** but avoids matching *** or ****