const { useCallback, useEffect, useMemo, useRef, useState } = React; const CORRECT_PASSWORD = "upalupal"; const BELL_SOUND_URL = "https://orangefreesounds.com/wp-content/uploads/2024/02/Happy-bell-sound-effect.mp3"; const AUTH_COOKIE_NAME = "mt_auth"; const AUTH_DURATION_MS = 30 * 60 * 1000; // 30 minutes const QUOTES = [ "Meditation is not evasion; it is a serene encounter with reality. — Thich Nhat Hanh", "The thing about meditation is: you become more and more you. — David Lynch", "Quiet the mind, and the soul will speak. — Ma Jaya Sati Bhagavati", "Meditation is the discovery that the point of life is always arrived at in the immediate moment. — Alan Watts", "Inhale the future, exhale the past. — Unknown", "The goal of meditation isn't to control your thoughts, it's to stop letting them control you. — Unknown", "Within you there is a stillness and a sanctuary to which you can retreat at any time. — Hermann Hesse", "Meditation is the tongue of the soul and the language of our spirit. — Jeremy Taylor", "The quieter you become, the more you can hear. — Ram Dass", "You should sit in meditation for twenty minutes every day—unless you're too busy; then you should sit for an hour. — Zen Proverb", ]; const TIMER_PRESETS = [ { label: "2 min", value: 120 }, { label: "5 min", value: 300 }, { label: "10 min", value: 600 }, ]; const YOUTUBE_EMBED = "https://www.youtube.com/embed/8sYK7lm3UKg?si=5-UyVXF5I7VI6rkg"; // Sidebar Component function Sidebar({ isOpen, currentPage, onNavigate, onToggle }) { const menuItems = [ { id: "meditation", label: "Meditation", icon: "🧘" }, { id: "pomodoro", label: "Pomodoro", icon: "🍅" }, { id: "journal", label: "Journal", icon: "📔" }, { id: "finance", label: "Finance", icon: "💰" }, ]; return ( <> {/* Sidebar Toggle Button */} {/* Sidebar */}

Menu

{/* Overlay for mobile */} {isOpen && (
)} ); } // Pomodoro Page Component function PomodoroPage() { return (

Pomodoro Timer 🍅

Focus & Productivity

Coming soon! Use the Pomodoro Technique to boost your productivity.

🍅

Pomodoro Timer

This feature is under development. Check back soon!

); } // Journal Page Component function JournalPage() { return (

Journal 📔

Meditation Journal

Coming soon! Track your meditation insights and reflections.

📔

Journal

This feature is under development. Check back soon!

); } // Finance Page Component // Delete Confirmation Modal Component function DeleteConfirmModal({ isOpen, spending, onConfirm, onCancel }) { if (!isOpen || !spending) return null; return (
🗑️

Delete Spending?

Are you sure you want to delete this spending record?

৳{spending.amount.toFixed(2)}

{spending.note && (

{spending.note}

)}

{spending.date}

); } function FinancePage() { const [dailyBudget, setDailyBudget] = useState(100); const [spendings, setSpendings] = useState([]); const [spendingAmount, setSpendingAmount] = useState(""); const [spendingNote, setSpendingNote] = useState(""); const [loading, setLoading] = useState(true); const [todayDate] = useState(new Date().toISOString().split("T")[0]); // Delete confirmation modal state const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [spendingToDelete, setSpendingToDelete] = useState(null); // Fetch budget config and spendings on mount useEffect(() => { const fetchBudgetData = async () => { try { const [configRes, spendingsRes] = await Promise.all([ fetch("/api/budget/config"), fetch("/api/budget/spendings"), ]); if (configRes.ok) { const config = await configRes.json(); setDailyBudget(config.daily_budget); } if (spendingsRes.ok) { const data = await spendingsRes.json(); setSpendings(data.spendings || []); } } catch (error) { // Handle error silently } finally { setLoading(false); } }; fetchBudgetData(); }, []); // Calculate available balance for today (simple: daily budget - today's spending) const calculateAvailableToday = () => { const todaySpending = spendings .filter((s) => s.date === todayDate) .reduce((sum, s) => sum + s.amount, 0); return Math.max(0, dailyBudget - todaySpending); }; // Calculate available balance for the month (30 days budget - month's spending) const calculateAvailableMonth = () => { const monthlyBudget = dailyBudget * 30; // 30 days const now = new Date(); const currentMonth = now.getMonth(); const currentYear = now.getFullYear(); const monthSpending = spendings .filter((s) => { const spendingDate = new Date(s.date); return spendingDate.getMonth() === currentMonth && spendingDate.getFullYear() === currentYear; }) .reduce((sum, s) => sum + s.amount, 0); return Math.max(0, monthlyBudget - monthSpending); }; const getTodaySpending = () => { return spendings .filter((s) => s.date === todayDate) .reduce((sum, s) => sum + s.amount, 0); }; // Get monthly spending data grouped by date const getMonthlySpendingData = () => { const now = new Date(); const currentMonth = now.getMonth(); const currentYear = now.getFullYear(); // Filter spendings for current month const monthSpendings = spendings.filter((s) => { const spendingDate = new Date(s.date); return spendingDate.getMonth() === currentMonth && spendingDate.getFullYear() === currentYear; }); // Group by date const groupedByDate = {}; monthSpendings.forEach((s) => { if (!groupedByDate[s.date]) { groupedByDate[s.date] = { total: 0, items: [] }; } groupedByDate[s.date].total += s.amount; groupedByDate[s.date].items.push(s); }); // Sort dates in descending order const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a) ); // Calculate total for the month const monthTotal = monthSpendings.reduce((sum, s) => sum + s.amount, 0); return { groupedByDate, sortedDates, monthTotal }; }; const handleAddSpending = async () => { if (!spendingAmount || parseFloat(spendingAmount) <= 0) return; try { const response = await fetch("/api/budget/spendings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ date: todayDate, amount: parseFloat(spendingAmount), note: spendingNote, }), }); if (response.ok) { const newSpending = await response.json(); setSpendings([...spendings, newSpending]); setSpendingAmount(""); setSpendingNote(""); } else { const errorData = await response.json(); } } catch (error) { } }; // Open delete confirmation modal const handleDeleteClick = (spending) => { setSpendingToDelete(spending); setDeleteModalOpen(true); }; // Cancel delete const handleDeleteCancel = () => { setDeleteModalOpen(false); setSpendingToDelete(null); }; // Confirm delete const handleDeleteConfirm = async () => { if (!spendingToDelete) return; try { const response = await fetch( `/api/budget/spendings/${spendingToDelete.date}/${spendingToDelete.amount}`, { method: "DELETE" } ); if (response.ok) { setSpendings( spendings.filter( (s) => !(s.date === spendingToDelete.date && s.amount === spendingToDelete.amount) ) ); } } catch (error) { // Handle error silently } finally { setDeleteModalOpen(false); setSpendingToDelete(null); } }; if (loading) { return (

Loading...

); } const availableToday = calculateAvailableToday(); const availableMonth = calculateAvailableMonth(); const todaySpending = getTodaySpending(); const todaySpendings = spendings.filter((s) => s.date === todayDate); const { groupedByDate, sortedDates, monthTotal } = getMonthlySpendingData(); // Format date for display const formatDisplayDate = (dateStr) => { const date = new Date(dateStr); const options = { weekday: 'short', month: 'short', day: 'numeric' }; return date.toLocaleDateString('en-US', options); }; const currentMonthName = new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); return (
{/* Delete Confirmation Modal */}

Daily Budget 💰

Budget Tracker

Track your daily spending with smart rollover system

{/* Available Balance Cards */}
{/* Available Today */}

Available Today

৳{availableToday.toFixed(2)}

Today's Spent: ৳{todaySpending.toFixed(2)}

{/* Available for Month */}

Available for Month

৳{availableMonth.toFixed(2)}

Monthly Budget: ৳{(dailyBudget * 30).toFixed(2)}

{/* Add Spending Section */}

Add Spending

setSpendingAmount(e.target.value)} placeholder="Enter amount (৳)" className="w-full rounded-xl border-2 border-slate-200 bg-slate-50 px-4 py-3 text-slate-900 placeholder-slate-500 focus:border-teal-500 focus:outline-none focus:ring-2 focus:ring-teal-200 mb-4" />
{[20, 50, 100, 200].map((amount) => ( ))}
setSpendingNote(e.target.value)} placeholder="Add note (optional)" className="w-full rounded-xl border-2 border-slate-200 bg-slate-50 px-4 py-3 text-slate-900 placeholder-slate-500 focus:border-teal-500 focus:outline-none focus:ring-2 focus:ring-teal-200 mb-4" />
{/* Today's Spending History */}

Today's Spending History

{todaySpendings.length === 0 ? (
📅

No spending yet

Your spending history will appear here

) : (
{todaySpendings.map((spending, index) => (

৳{spending.amount.toFixed(2)}

{spending.note && (

{spending.note}

)}
))}
)}
{/* Monthly Spending Overview */}
📊

Monthly Spending

{currentMonthName}

৳{monthTotal.toFixed(2)}

{sortedDates.length === 0 ? (
📈

No spending this month

Start tracking your expenses to see monthly data

) : (
{sortedDates.map((date) => (
{/* Date Header */}
📅 {formatDisplayDate(date)} {date === todayDate && ( Today )}
৳{groupedByDate[date].total.toFixed(2)}
{/* Spending Items */}
{groupedByDate[date].items.map((spending, idx) => (
৳{spending.amount.toFixed(2)} {spending.note && ( {spending.note} )}
))}
))}
)}
); } function createInitialStopwatchState() { return { elapsedMs: 0, isRunning: false, startTimestamp: null, hasSession: false, initialStartTimestamp: null, }; } function createInitialCountdownState() { return { elapsedMs: 0, targetDurationMs: 0, isRunning: false, hasSession: false, startTimestamp: null, initialStartTimestamp: null, isCompleting: false, }; } function formatDurationHMS(totalSeconds) { const safeSeconds = Math.max(0, Math.floor(totalSeconds)); const hours = Math.floor(safeSeconds / 3600) .toString() .padStart(2, "0"); const minutes = Math.floor((safeSeconds % 3600) / 60) .toString() .padStart(2, "0"); const seconds = (safeSeconds % 60).toString().padStart(2, "0"); return `${hours}:${minutes}:${seconds}`; } function formatCountdown(totalSeconds) { const safeSeconds = Math.max(0, Math.floor(totalSeconds)); const minutes = Math.floor(safeSeconds / 60) .toString() .padStart(2, "0"); const seconds = (safeSeconds % 60).toString().padStart(2, "0"); return `${minutes}:${seconds}`; } function secondsFromInputs(inputs) { const minutes = Number.parseInt(inputs.minutes, 10); const seconds = Number.parseInt(inputs.seconds, 10); const safeMinutes = Number.isFinite(minutes) ? minutes : 0; const safeSeconds = Number.isFinite(seconds) ? seconds : 0; return Math.max(0, safeMinutes * 60 + Math.min(safeSeconds, 59)); } function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function formatDateTime(value) { const date = new Date(value); if (Number.isNaN(date.getTime())) { return "--"; } return date.toLocaleString(); } function getRandomQuote() { return QUOTES[Math.floor(Math.random() * QUOTES.length)]; } function readCookie(name) { return document.cookie .split(";") .map((part) => part.trim()) .find((part) => part.startsWith(`${name}=`)) ?.split("=")[1]; } function setCookie(name, value, { expires } = {}) { const expiresStr = expires instanceof Date ? `; expires=${expires.toUTCString()}` : ""; document.cookie = `${name}=${value}; path=/; SameSite=Lax${expiresStr}`; } function clearCookie(name) { document.cookie = `${name}=; Max-Age=0; path=/; SameSite=Lax`; } async function requestJson(url, options) { const response = await fetch(url, options); if (!response.ok) { let message = `Request failed with status ${response.status}`; try { const data = await response.json(); if (data && data.detail) { message = data.detail; } } catch (error) { // Ignore parse errors, fall back to default message } throw new Error(message); } if (response.status === 204) { return null; } return response.json(); } function GradientButton({ className = "", children, ...rest }) { const base = "rounded-full px-5 py-2.5 font-semibold shadow-sm transition bg-gradient-to-r from-brand-rose to-brand-roseLight text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-rose disabled:cursor-not-allowed disabled:opacity-50"; return ( ); } function PasswordModal({ isVisible, password, onPasswordChange, onSubmit, error, inputRef, }) { if (!isVisible) { return null; } return (

🔒 Access Required

Enter the password to unlock the meditation tracker.

onPasswordChange(event.target.value)} /> {error ? (

{error}

) : null}
Unlock
); } function CountdownCompleteModal({ open, quote, onClose }) { useEffect(() => { if (!open) { return undefined; } function onKeyDown(event) { if (event.key === "Escape") { onClose(); } } window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [open, onClose]); if (!open) { return null; } return (

🧘 Session Complete!

{quote}

); } function MeditationApp() { const [isAuthenticated, setIsAuthenticated] = useState(false); const [password, setPassword] = useState(""); const [passwordError, setPasswordError] = useState(""); const passwordInputRef = useRef(null); const [authExpiry, setAuthExpiry] = useState(null); const authTimeoutRef = useRef(null); // Sidebar and navigation state - default closed on mobile, open on desktop const [sidebarOpen, setSidebarOpen] = useState(() => { // Check if window exists (for SSR) and if it's desktop (>= 1024px) if (typeof window !== 'undefined') { return window.innerWidth >= 1024; } return false; // Default to closed if window is not available }); const [currentPage, setCurrentPage] = useState("meditation"); const [isLoadingData, setIsLoadingData] = useState(false); const [dataError, setDataError] = useState(""); const [sessions, setSessions] = useState([]); const [stats, setStats] = useState(null); const [stopwatch, setStopwatch] = useState(createInitialStopwatchState); const [countdown, setCountdown] = useState(createInitialCountdownState); const stopwatchRef = useRef(stopwatch); const countdownRef = useRef(countdown); const [stopwatchTick, setStopwatchTick] = useState(0); const [countdownTick, setCountdownTick] = useState(0); const stopwatchIntervalRef = useRef(null); const countdownIntervalRef = useRef(null); const [timerInputs, setTimerInputs] = useState({ minutes: "0", seconds: "0" }); const [showCountdownModal, setShowCountdownModal] = useState(false); const [modalQuote, setModalQuote] = useState(""); const [weeklyGoalMinutes, setWeeklyGoalMinutes] = useState(""); const weeklyGoalInputRef = useRef(null); const [isSavingWeeklyGoal, setIsSavingWeeklyGoal] = useState(false); const bellRef = useRef(null); useEffect(() => { stopwatchRef.current = stopwatch; }, [stopwatch]); useEffect(() => { countdownRef.current = countdown; }, [countdown]); useEffect(() => { if (stopwatch.isRunning) { stopwatchIntervalRef.current = window.setInterval(() => { setStopwatchTick((tick) => tick + 1); }, 250); } return () => { if (stopwatchIntervalRef.current) { window.clearInterval(stopwatchIntervalRef.current); stopwatchIntervalRef.current = null; } }; }, [stopwatch.isRunning]); useEffect(() => { if (countdown.isRunning) { countdownIntervalRef.current = window.setInterval(() => { setCountdownTick((tick) => tick + 1); }, 250); } return () => { if (countdownIntervalRef.current) { window.clearInterval(countdownIntervalRef.current); countdownIntervalRef.current = null; } }; }, [countdown.isRunning]); useEffect(() => { return () => { if (authTimeoutRef.current) { window.clearTimeout(authTimeoutRef.current); authTimeoutRef.current = null; } }; }, []); useEffect(() => { bellRef.current = new Audio(BELL_SOUND_URL); }, []); const loadAllData = useCallback(async () => { setIsLoadingData(true); try { const [sessionsData, statsData] = await Promise.all([ requestJson("/api/sessions"), requestJson("/api/stats"), ]); setSessions(Array.isArray(sessionsData) ? sessionsData : []); setStats(statsData ?? null); setDataError(""); } catch (error) { console.error(error); setDataError(error.message || "Failed to load data."); } finally { setIsLoadingData(false); } }, []); const resetStopwatchState = useCallback(() => { const next = createInitialStopwatchState(); stopwatchRef.current = next; setStopwatch(next); }, []); const resetCountdownState = useCallback(() => { const next = createInitialCountdownState(); countdownRef.current = next; setCountdown(next); }, []); const handleStopwatchStart = useCallback(() => { setStopwatch((prev) => { if (prev.isRunning) { return prev; } const now = Date.now(); const next = prev.hasSession ? { ...prev, isRunning: true, startTimestamp: now } : { ...createInitialStopwatchState(), isRunning: true, hasSession: true, startTimestamp: now, initialStartTimestamp: now, }; stopwatchRef.current = next; return next; }); }, []); const handleStopwatchPause = useCallback(() => { setStopwatch((prev) => { if (!prev.isRunning || !prev.startTimestamp) { return prev; } const now = Date.now(); const next = { ...prev, elapsedMs: prev.elapsedMs + (now - prev.startTimestamp), isRunning: false, startTimestamp: null, }; stopwatchRef.current = next; return next; }); }, []); const handleStopwatchFinish = useCallback(async () => { const current = stopwatchRef.current; if (!current.hasSession) { return; } let totalMs = current.elapsedMs; if (current.isRunning && current.startTimestamp) { totalMs += Date.now() - current.startTimestamp; } if (totalMs <= 0) { resetStopwatchState(); return; } const durationSeconds = totalMs / 1000; const endTime = new Date(); const startTime = current.initialStartTimestamp ? new Date(current.initialStartTimestamp) : new Date(endTime.getTime() - totalMs); try { await requestJson("/api/sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ start_time: startTime.toISOString(), end_time: endTime.toISOString(), duration_seconds: durationSeconds, }), }); await loadAllData(); } catch (error) { window.alert(error.message || "Failed to save session."); } finally { resetStopwatchState(); } }, [loadAllData, resetStopwatchState]); const handleCountdownStart = useCallback(() => { setCountdown((prev) => { if (prev.isRunning) { return prev; } const now = Date.now(); if (!prev.hasSession) { const totalSeconds = secondsFromInputs(timerInputs); if (totalSeconds <= 0) { window.alert("Please set a timer duration greater than zero."); return prev; } const next = { ...createInitialCountdownState(), elapsedMs: 0, targetDurationMs: totalSeconds * 1000, isRunning: true, hasSession: true, startTimestamp: now, initialStartTimestamp: now, }; countdownRef.current = next; return next; } const next = { ...prev, isRunning: true, startTimestamp: now }; countdownRef.current = next; return next; }); }, [timerInputs]); const handleCountdownPause = useCallback(() => { setCountdown((prev) => { if (!prev.isRunning || !prev.startTimestamp) { return prev; } const now = Date.now(); const next = { ...prev, elapsedMs: prev.elapsedMs + (now - prev.startTimestamp), isRunning: false, startTimestamp: null, }; countdownRef.current = next; return next; }); }, []); const handleCountdownFinish = useCallback( async (autoComplete = false) => { const current = countdownRef.current; if (!current.hasSession || current.isCompleting) { return; } const now = Date.now(); let totalMs = current.elapsedMs; if (current.isRunning && current.startTimestamp) { totalMs += now - current.startTimestamp; } if (totalMs <= 0) { resetCountdownState(); return; } let durationSeconds = totalMs / 1000; if (autoComplete && current.targetDurationMs > 0) { durationSeconds = current.targetDurationMs / 1000; } const endTime = new Date(); const startTime = current.initialStartTimestamp ? new Date(current.initialStartTimestamp) : new Date(endTime.getTime() - durationSeconds * 1000); const updatingState = { ...current, isRunning: false, isCompleting: true, startTimestamp: null, }; countdownRef.current = updatingState; setCountdown(updatingState); try { await requestJson("/api/sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ start_time: startTime.toISOString(), end_time: endTime.toISOString(), duration_seconds: durationSeconds, }), }); await loadAllData(); if (autoComplete) { setModalQuote(getRandomQuote()); setShowCountdownModal(true); } } catch (error) { window.alert(error.message || "Failed to save session."); } finally { resetCountdownState(); } }, [loadAllData, resetCountdownState] ); const handleCountdownReset = useCallback(() => { resetCountdownState(); setTimerInputs({ minutes: "0", seconds: "0" }); }, [resetCountdownState]); useEffect(() => { if (!countdown.isRunning || !countdown.hasSession || countdown.isCompleting) { return; } const totalElapsed = countdown.elapsedMs + (countdown.startTimestamp ? Date.now() - countdown.startTimestamp : 0); if ( countdown.targetDurationMs > 0 && totalElapsed >= countdown.targetDurationMs ) { if (bellRef.current) { try { bellRef.current.currentTime = 0; void bellRef.current.play(); } catch (error) { // Ignore autoplay failures } } handleCountdownFinish(true); } }, [ countdownTick, countdown.elapsedMs, countdown.hasSession, countdown.isCompleting, countdown.isRunning, countdown.startTimestamp, countdown.targetDurationMs, handleCountdownFinish, ]); useEffect(() => { if (!isAuthenticated) { passwordInputRef.current?.focus(); } }, [isAuthenticated]); useEffect(() => { if (isAuthenticated) { loadAllData(); } }, [isAuthenticated, loadAllData]); useEffect(() => { if (!stats) { return; } const minutes = stats.weekly_target_seconds && stats.weekly_target_seconds > 0 ? Math.round(stats.weekly_target_seconds / 60) : ""; if (document.activeElement !== weeklyGoalInputRef.current) { setWeeklyGoalMinutes(minutes ? String(minutes) : ""); } }, [stats]); const handleLogout = useCallback(() => { if (authTimeoutRef.current) { window.clearTimeout(authTimeoutRef.current); authTimeoutRef.current = null; } clearCookie(AUTH_COOKIE_NAME); setIsAuthenticated(false); setAuthExpiry(null); setPassword(""); setPasswordError("Session expired. Please log in again."); passwordInputRef.current?.focus(); }, []); const scheduleLogout = useCallback( (expiryTimestamp) => { if (authTimeoutRef.current) { window.clearTimeout(authTimeoutRef.current); authTimeoutRef.current = null; } const remaining = expiryTimestamp - Date.now(); if (remaining <= 0) { handleLogout(); return; } authTimeoutRef.current = window.setTimeout(() => { handleLogout(); }, remaining); }, [handleLogout] ); const handlePasswordSubmit = useCallback( (event) => { event.preventDefault(); if (password.trim() === CORRECT_PASSWORD) { setIsAuthenticated(true); setPassword(""); setPasswordError(""); const expiryTimestamp = Date.now() + AUTH_DURATION_MS; setAuthExpiry(expiryTimestamp); setCookie(AUTH_COOKIE_NAME, String(expiryTimestamp), { expires: new Date(expiryTimestamp), }); scheduleLogout(expiryTimestamp); return; } setPasswordError("❌ Wrong password. Try again."); setPassword(""); window.requestAnimationFrame(() => { passwordInputRef.current?.focus(); }); }, [password, scheduleLogout] ); const handleTimerInputChange = useCallback((field, value) => { setTimerInputs((prev) => { const next = { ...prev, [field]: value }; return next; }); }, []); useEffect(() => { const cookieValue = readCookie(AUTH_COOKIE_NAME); if (!cookieValue) { return; } const parsed = Number(cookieValue); if (!Number.isFinite(parsed) || parsed <= Date.now()) { clearCookie(AUTH_COOKIE_NAME); return; } setIsAuthenticated(true); setAuthExpiry(parsed); scheduleLogout(parsed); }, [scheduleLogout]); useEffect(() => { if (!authExpiry) { return; } scheduleLogout(authExpiry); }, [authExpiry, scheduleLogout]); const applyTimerPreset = useCallback((seconds) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.max(seconds % 60, 0); setTimerInputs({ minutes: String(minutes), seconds: String(remainingSeconds), }); }, []); const handleWeeklyGoalSubmit = useCallback( async (event) => { event.preventDefault(); const minutesValue = Number(weeklyGoalMinutes); if (!Number.isFinite(minutesValue) || minutesValue <= 0) { window.alert("Please enter a positive number of minutes."); return; } try { setIsSavingWeeklyGoal(true); await requestJson("/api/weekly-target", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ target_seconds: minutesValue * 60 }), }); await loadAllData(); } catch (error) { window.alert(error.message || "Unable to update target."); } finally { setIsSavingWeeklyGoal(false); } }, [weeklyGoalMinutes, loadAllData] ); const handleDeleteSession = useCallback( async (sessionId) => { try { await requestJson(`/api/sessions/${sessionId}`, { method: "DELETE", }); await loadAllData(); } catch (error) { window.alert(error.message || "Failed to delete session."); } }, [loadAllData] ); const statsSnapshot = useMemo( () => ({ weekly_seconds: stats?.weekly_seconds ?? 0, monthly_seconds: stats?.monthly_seconds ?? 0, yearly_seconds: stats?.yearly_seconds ?? 0, total_seconds: stats?.total_seconds ?? 0, total_sessions: stats?.total_sessions ?? 0, weekly_target_seconds: stats?.weekly_target_seconds ?? 0, weekly_progress_percentage: stats?.weekly_progress_percentage ?? 0, }), [stats] ); const countdownConfiguredMs = useMemo( () => secondsFromInputs(timerInputs) * 1000, [timerInputs] ); const renderNow = Date.now(); const stopwatchDisplay = formatDurationHMS( (stopwatch.elapsedMs + (stopwatch.isRunning && stopwatch.startTimestamp ? renderNow - stopwatch.startTimestamp : 0)) / 1000 ); const countdownElapsedMs = countdown.elapsedMs + (countdown.isRunning && countdown.startTimestamp ? renderNow - countdown.startTimestamp : 0); const countdownRemainingMs = countdown.hasSession ? Math.max(countdown.targetDurationMs - countdownElapsedMs, 0) : countdownConfiguredMs; const countdownDisplay = formatCountdown(countdownRemainingMs / 1000); const weeklyTargetSeconds = statsSnapshot.weekly_target_seconds; const weeklyProgressLabel = weeklyTargetSeconds > 0 ? `${Math.round(statsSnapshot.weekly_progress_percentage)}%` : "Set a target"; const weeklyProgressWidth = weeklyTargetSeconds > 0 ? `${clamp(statsSnapshot.weekly_progress_percentage, 0, 100)}%` : "0%"; const isStatsLoading = isLoadingData && !stats; return (
{/* Sidebar */} {isAuthenticated && ( setSidebarOpen(!sidebarOpen)} /> )} {/* Main Content */}
{/* Meditation Page Content */} {currentPage === "meditation" && ( <>

Upal's Meditation Tracker 🕉️

Track, reflect, and celebrate your practice...

Use the stopwatch or countdown timer to log sessions, stay aligned with your weekly target, and review your progress over time.

{isLoadingData ? (

Refreshing your latest stats…

) : null}
{dataError ? (
{dataError}
) : null}