import React, { useState, useEffect, useRef } from 'react'; const WEEKLY_SOLO_POINTS = 5; const WEEKLY_TOGETHER_POINTS = 5; const EARNED_POINTS_PASSWORD = 'password'; // Timer durations (in milliseconds) const FIFTEEN_MINUTES = 15 * 60 * 1000; const TWO_MINUTES = 2 * 60 * 1000; function App() { const [soloPoints, setSoloPoints] = useState(WEEKLY_SOLO_POINTS); const [togetherPoints, setTogetherPoints] = useState(WEEKLY_TOGETHER_POINTS); const [earnedPoints, setEarnedPoints] = useState(0); const [log, setLog] = useState([]); // e.g., [{ type: 'solo', usedAt: 'ISOString' }, ...] const [lastReset, setLastReset] = useState(null); // e.g., 'YYYY-MM-DD' // Timer states const [timerRunning, setTimerRunning] = useState(false); const [timeRemaining, setTimeRemaining] = useState(0); // For storing the active timer ID (so we can clear it on unmount) const timerRef = useRef(null); const intervalRef = useRef(null); // ----------------------------- // LOAD / SAVE localStorage // ----------------------------- useEffect(() => { loadData(); }, []); // Check for weekly reset once data is loaded useEffect(() => { if (lastReset) { resetIfNewWeek(); } // eslint-disable-next-line }, [lastReset]); // Cleanup timers on unmount useEffect(() => { return () => { if (timerRef.current) clearTimeout(timerRef.current); if (intervalRef.current) clearInterval(intervalRef.current); }; }, []); const loadData = () => { try { const storedData = localStorage.getItem('pointsData'); if (storedData) { const dataObj = JSON.parse(storedData); setSoloPoints(dataObj.soloPoints ?? WEEKLY_SOLO_POINTS); setTogetherPoints(dataObj.togetherPoints ?? WEEKLY_TOGETHER_POINTS); setEarnedPoints(dataObj.earnedPoints ?? 0); setLog(dataObj.log ?? []); setLastReset(dataObj.lastReset ?? null); } else { // If nothing in localStorage, use defaults saveData( WEEKLY_SOLO_POINTS, WEEKLY_TOGETHER_POINTS, 0, [], null ); } } catch (err) { console.error('Error reading localStorage:', err); } }; const saveData = (newSolo, newTogether, newEarned, newLog, newLastReset) => { const dataObj = { soloPoints: newSolo, togetherPoints: newTogether, earnedPoints: newEarned, log: newLog, lastReset: newLastReset }; localStorage.setItem('pointsData', JSON.stringify(dataObj)); }; // ----------------------------- // WEEKLY RESET LOGIC // ----------------------------- // Helper: get Monday of current week as 'YYYY-MM-DD' const getMonday = (date = new Date()) => { const d = new Date(date); const day = d.getDay(); // 0=Sun, 1=Mon, ... const diff = d.getDate() - day + (day === 0 ? -6 : 1); d.setDate(diff); return d.toISOString().split('T')[0]; }; const resetIfNewWeek = () => { const currentMonday = getMonday(); if (!lastReset || lastReset !== currentMonday) { // Reset solo & together points. Earned points can remain or reset, your choice. setSoloPoints(WEEKLY_SOLO_POINTS); setTogetherPoints(WEEKLY_TOGETHER_POINTS); // setEarnedPoints(0); // Uncomment if you want to reset earned each Monday setLastReset(currentMonday); saveData( WEEKLY_SOLO_POINTS, WEEKLY_TOGETHER_POINTS, earnedPoints, // or 0 if resetting log, currentMonday ); } }; // ----------------------------- // USE A POINT // ----------------------------- const usePoint = (pointType) => { if (pointType === 'solo' && soloPoints <= 0) { alert('No Solo points left!'); return; } if (pointType === 'together' && togetherPoints <= 0) { alert('No Together points left!'); return; } if (pointType === 'earned' && earnedPoints <= 0) { alert('No Earned points left!'); return; } let newSolo = soloPoints; let newTogether = togetherPoints; let newEarned = earnedPoints; if (pointType === 'solo') newSolo = soloPoints - 1; if (pointType === 'together') newTogether = togetherPoints - 1; if (pointType === 'earned') newEarned = earnedPoints - 1; // Log usage const usedAt = new Date().toISOString(); const newLog = [...log, { type: pointType, usedAt }]; // Update states setSoloPoints(newSolo); setTogetherPoints(newTogether); setEarnedPoints(newEarned); setLog(newLog); // Persist saveData(newSolo, newTogether, newEarned, newLog, lastReset); // Start 15-minute timer alert( `Using 1 ${pointType.toUpperCase()} point at ${usedAt}. \n15-minute timer started.` ); startTimer(FIFTEEN_MINUTES); }; // ----------------------------- // TIMERS (Client) // ----------------------------- const startTimer = (duration) => { // If a timer is already running, clear it if (timerRef.current) clearTimeout(timerRef.current); if (intervalRef.current) clearInterval(intervalRef.current); setTimerRunning(true); setTimeRemaining(duration); // Set up a 1-second interval to update the countdown display let remaining = duration; intervalRef.current = setInterval(() => { remaining -= 1000; setTimeRemaining(remaining); if (remaining <= 0) { clearInterval(intervalRef.current); } }, 1000); // When the timer finishes: timerRef.current = setTimeout(() => { clearInterval(intervalRef.current); setTimerRunning(false); setTimeRemaining(0); ring(); }, duration); }; const ring = () => { const wantsFinishUp = window.confirm( '15 minutes are up! \nClick "OK" to start a 2-minute finish-up timer, or "Cancel" to stop.' ); if (wantsFinishUp) { startTimer(TWO_MINUTES); } }; // ----------------------------- // ADD EARNED POINT // ----------------------------- const addEarnedPoint = () => { const pw = window.prompt('Enter password to add an earned point:'); if (pw === EARNED_POINTS_PASSWORD) { const newEarned = earnedPoints + 1; setEarnedPoints(newEarned); saveData(soloPoints, togetherPoints, newEarned, log, lastReset); alert('1 Earned Point added!'); } else { alert('Incorrect password!'); } }; // ----------------------------- // DISPLAY TIMER FORMAT // ----------------------------- const formatTime = (ms) => { if (ms <= 0) return '00:00'; const totalSeconds = Math.floor(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const mm = minutes < 10 ? `0${minutes}` : minutes; const ss = seconds < 10 ? `0${seconds}` : seconds; return `${mm}:${ss}`; }; // ----------------------------- // RENDER // ----------------------------- return (
Solo Points: {soloPoints}
Together Points: {togetherPoints}
Earned Points: {earnedPoints}
[{entry.type}] used at {entry.usedAt}
))}