// ABOUTME: Main dashboard page for PhaseFlow. // ABOUTME: Displays today's training decision, biometrics, and quick actions. "use client"; import { useCallback, useEffect, useState } from "react"; import { DataPanel } from "@/components/dashboard/data-panel"; import { DecisionCard } from "@/components/dashboard/decision-card"; import { MiniCalendar } from "@/components/dashboard/mini-calendar"; import { NutritionPanel } from "@/components/dashboard/nutrition-panel"; import { OverrideToggles } from "@/components/dashboard/override-toggles"; import { DashboardSkeleton } from "@/components/dashboard/skeletons"; import type { CyclePhase, Decision, NutritionGuidance, OverrideType, PhaseConfig, } from "@/types"; interface TodayData { decision: Decision; cycleDay: number; phase: CyclePhase; phaseConfig: PhaseConfig; daysUntilNextPhase: number; cycleLength: number; biometrics: { hrvStatus: string; bodyBatteryCurrent: number; bodyBatteryYesterdayLow: number; weekIntensityMinutes: number; phaseLimit: number; }; nutrition: NutritionGuidance; } interface UserData { id: string; email: string; activeOverrides: OverrideType[]; lastPeriodDate: string | null; cycleLength: number; } export default function Dashboard() { const [todayData, setTodayData] = useState(null); const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [calendarYear, setCalendarYear] = useState(new Date().getFullYear()); const [calendarMonth, setCalendarMonth] = useState(new Date().getMonth()); const handleCalendarMonthChange = useCallback( (year: number, month: number) => { setCalendarYear(year); setCalendarMonth(month); }, [], ); const fetchTodayData = useCallback(async () => { const response = await fetch("/api/today"); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Failed to fetch today data"); } return data as TodayData; }, []); const fetchUserData = useCallback(async () => { const response = await fetch("/api/user"); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Failed to fetch user data"); } return data as UserData; }, []); useEffect(() => { async function loadData() { try { setLoading(true); setError(null); const [today, user] = await Promise.all([ fetchTodayData(), fetchUserData(), ]); setTodayData(today); setUserData(user); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } finally { setLoading(false); } } loadData(); }, [fetchTodayData, fetchUserData]); const handleOverrideToggle = async (override: OverrideType) => { if (!userData) return; const isActive = userData.activeOverrides.includes(override); const method = isActive ? "DELETE" : "POST"; try { const response = await fetch("/api/overrides", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ override }), }); if (!response.ok) { throw new Error("Failed to update override"); } const result = await response.json(); // Update local state with new overrides setUserData((prev) => prev ? { ...prev, activeOverrides: result.activeOverrides } : null, ); // Refetch today data to get updated decision const newTodayData = await fetchTodayData(); setTodayData(newTodayData); } catch (err) { setError( err instanceof Error ? err.message : "Failed to toggle override", ); } }; return (

PhaseFlow

Settings
{loading && } {error && (

Error: {error}

{error.includes("lastPeriodDate") && (

Please log your period start date to get started.

)}
)} {!loading && !error && todayData && userData && (
{/* Cycle Info */}

Day {todayData.cycleDay} ยท {todayData.phase}

{todayData.daysUntilNextPhase} days until next phase

{/* Decision Card */} {/* Data and Nutrition Grid */}
{/* Override Toggles */} {/* Mini Calendar */} {userData.lastPeriodDate && ( )}
)}
); }