From 8dcddbf278628acdcd40ae8a14c43ef291d2d789 Mon Sep 17 00:00:00 2001 From: Vijayakanth Manoharan Date: Mon, 23 Mar 2026 22:40:57 -0400 Subject: [PATCH] Fix hydration mismatches and theme bootstrap --- src/app/layout.tsx | 7 ++++--- src/components/expense-workspace.tsx | 12 +++++++++--- src/components/home-dashboard.tsx | 16 ++++++++++++++-- src/components/paycheck-workspace.tsx | 16 ++++++++++++---- src/components/theme-toggle.tsx | 6 +++++- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index dfd0542..70300c3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Fraunces, Manrope } from "next/font/google"; +import Script from "next/script"; import { SiteNav } from "@/components/site-nav"; import { ThemeToggle } from "@/components/theme-toggle"; @@ -44,10 +45,10 @@ export default function RootLayout({ className={`${headingFont.variable} ${bodyFont.variable} h-full antialiased`} suppressHydrationWarning > - -
diff --git a/src/components/expense-workspace.tsx b/src/components/expense-workspace.tsx index 28ba62c..cd48122 100644 --- a/src/components/expense-workspace.tsx +++ b/src/components/expense-workspace.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState, type FormEvent } from "react"; import { getCategoryLabel, type CategoryValue } from "@/lib/categories"; -import { getCurrentMonthKey } from "@/lib/date"; +import { getCurrentMonthKey, getLocalToday } from "@/lib/date"; import { formatCurrencyFromCents } from "@/lib/money"; type SuggestionResponse = { @@ -42,7 +42,7 @@ export function ExpenseWorkspace({ categoryOptions }: Props) { }>({ title: "", amount: "", - date: new Date().toISOString().slice(0, 10), + date: "", category: (categoryOptions[0]?.value as CategoryValue | undefined) ?? "MISC", }); const [editingId, setEditingId] = useState(null); @@ -61,7 +61,13 @@ export function ExpenseWorkspace({ categoryOptions }: Props) { setExpenses(payload?.expenses ?? []); } + const timeoutId = window.setTimeout(() => { + setFormState((current) => (current.date ? current : { ...current, date: getLocalToday() })); + }, 0); + void loadExpenses(); + + return () => window.clearTimeout(timeoutId); }, []); const totalSpent = useMemo( @@ -121,7 +127,7 @@ export function ExpenseWorkspace({ categoryOptions }: Props) { setFormState({ title: "", amount: "", - date: new Date().toISOString().slice(0, 10), + date: getLocalToday(), category: (categoryOptions[0]?.value as CategoryValue | undefined) ?? "MISC", }); setSuggestionMessage(null); diff --git a/src/components/home-dashboard.tsx b/src/components/home-dashboard.tsx index 9764b5b..1fc1229 100644 --- a/src/components/home-dashboard.tsx +++ b/src/components/home-dashboard.tsx @@ -41,7 +41,7 @@ type OllamaStatus = { }; export function HomeDashboard() { - const [selectedMonth, setSelectedMonth] = useState(getCurrentMonthKey()); + const [selectedMonth, setSelectedMonth] = useState(""); const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(null); const [insightBusy, setInsightBusy] = useState(false); @@ -68,6 +68,18 @@ export function HomeDashboard() { } useEffect(() => { + const timeoutId = window.setTimeout(() => { + setSelectedMonth(getCurrentMonthKey()); + }, 0); + + return () => window.clearTimeout(timeoutId); + }, []); + + useEffect(() => { + if (!selectedMonth) { + return; + } + const timeoutId = window.setTimeout(() => { void loadDashboard(selectedMonth); }, 0); @@ -155,7 +167,7 @@ export function HomeDashboard() {

Month to date

- {snapshot ? getMonthLabel(snapshot.month) : getMonthLabel(selectedMonth)} + {snapshot ? getMonthLabel(snapshot.month) : selectedMonth ? getMonthLabel(selectedMonth) : "Loading current month..."}

([]); const [schedule, setSchedule] = useState(null); const [projectedDates, setProjectedDates] = useState([]); - const [scheduleForm, setScheduleForm] = useState({ amount: "", anchorDate: new Date().toISOString().slice(0, 10) }); + const [scheduleForm, setScheduleForm] = useState({ amount: "", anchorDate: "" }); const [showScheduleForm, setShowScheduleForm] = useState(false); const [formState, setFormState] = useState({ amount: "", - payDate: new Date().toISOString().slice(0, 10), + payDate: "", }); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); @@ -46,7 +46,15 @@ export function PaycheckWorkspace() { } } + const timeoutId = window.setTimeout(() => { + const today = getLocalToday(); + setScheduleForm((current) => (current.anchorDate ? current : { ...current, anchorDate: today })); + setFormState((current) => (current.payDate ? current : { ...current, payDate: today })); + }, 0); + void loadData(); + + return () => window.clearTimeout(timeoutId); }, []); const totalIncome = useMemo( @@ -84,7 +92,7 @@ export function PaycheckWorkspace() { setSchedule(payload.schedule); setProjectedDates(computeProjectedDates(payload.schedule.anchorDate, getCurrentMonthKey())); setShowScheduleForm(false); - setScheduleForm({ amount: "", anchorDate: new Date().toISOString().slice(0, 10) }); + setScheduleForm({ amount: "", anchorDate: getLocalToday() }); } async function handleClearSchedule() { diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx index f6a3ba5..db2920e 100644 --- a/src/components/theme-toggle.tsx +++ b/src/components/theme-toggle.tsx @@ -6,7 +6,11 @@ export function ThemeToggle() { const [isDark, setIsDark] = useState(false); useEffect(() => { - setIsDark(document.documentElement.classList.contains("dark")); + const timeoutId = window.setTimeout(() => { + setIsDark(document.documentElement.classList.contains("dark")); + }, 0); + + return () => window.clearTimeout(timeoutId); }, []); function toggle() {