Fix hydration mismatches and theme bootstrap
This commit is contained in:
@@ -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
|
||||
>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
</head>
|
||||
<body className="min-h-full bg-[linear-gradient(180deg,#f8f3ea_0%,#f5efe4_28%,#fbfaf7_100%)] text-stone-950 dark:bg-[linear-gradient(180deg,#1a1714_0%,#1c1a17_28%,#1e1c19_100%)] dark:text-stone-100">
|
||||
<Script id="theme-script" strategy="beforeInteractive">
|
||||
{themeScript}
|
||||
</Script>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col px-4 py-6 sm:px-6 lg:px-8">
|
||||
<header className="mb-10 flex flex-col gap-4 rounded-[2rem] border border-white/70 bg-white/80 px-6 py-5 shadow-[0_20px_50px_rgba(120,90,50,0.08)] backdrop-blur sm:flex-row sm:items-center sm:justify-between dark:border-stone-700/60 dark:bg-stone-900/80">
|
||||
<div>
|
||||
|
||||
@@ -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<string | null>(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);
|
||||
|
||||
@@ -41,7 +41,7 @@ type OllamaStatus = {
|
||||
};
|
||||
|
||||
export function HomeDashboard() {
|
||||
const [selectedMonth, setSelectedMonth] = useState(getCurrentMonthKey());
|
||||
const [selectedMonth, setSelectedMonth] = useState("");
|
||||
const [snapshot, setSnapshot] = useState<DashboardSnapshot | null>(null);
|
||||
const [error, setError] = useState<string | null>(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() {
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-stone-500 dark:text-stone-400">Month to date</p>
|
||||
<h2 className="mt-2 text-3xl font-semibold text-stone-950 dark:text-white">
|
||||
{snapshot ? getMonthLabel(snapshot.month) : getMonthLabel(selectedMonth)}
|
||||
{snapshot ? getMonthLabel(snapshot.month) : selectedMonth ? getMonthLabel(selectedMonth) : "Loading current month..."}
|
||||
</h2>
|
||||
</div>
|
||||
<input
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useMemo, useState, type FormEvent } from "react";
|
||||
|
||||
import { getCurrentMonthKey } from "@/lib/date";
|
||||
import { getCurrentMonthKey, getLocalToday } from "@/lib/date";
|
||||
import { formatCurrencyFromCents } from "@/lib/money";
|
||||
|
||||
type PaycheckRecord = {
|
||||
@@ -21,11 +21,11 @@ export function PaycheckWorkspace() {
|
||||
const [paychecks, setPaychecks] = useState<PaycheckRecord[]>([]);
|
||||
const [schedule, setSchedule] = useState<PaySchedule | null>(null);
|
||||
const [projectedDates, setProjectedDates] = useState<string[]>([]);
|
||||
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<string | null>(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() {
|
||||
|
||||
@@ -6,7 +6,11 @@ export function ThemeToggle() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setIsDark(document.documentElement.classList.contains("dark"));
|
||||
}, 0);
|
||||
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, []);
|
||||
|
||||
function toggle() {
|
||||
|
||||
Reference in New Issue
Block a user