From 7ee0aff52f285a4d440f37c9ac79ed26f9e78e3e Mon Sep 17 00:00:00 2001 From: Avery Felts Date: Sun, 1 Feb 2026 12:09:42 -0700 Subject: [PATCH] Fix usage timestamp persistence and clean up debug code --- src/app/api/preferences/route.ts | 15 +++++---- src/components/Dashboard.tsx | 13 ++++++-- src/components/HealthTimelineCard.tsx | 48 ++++++++++++++++++++++----- src/lib/d1.ts | 22 +++++++++--- src/lib/storage.ts | 3 ++ 5 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/app/api/preferences/route.ts b/src/app/api/preferences/route.ts index 6ba1b84..4d87fec 100644 --- a/src/app/api/preferences/route.ts +++ b/src/app/api/preferences/route.ts @@ -71,22 +71,25 @@ export async function POST(request: NextRequest) { lastWeedUsageTime?: string; }; - // Validation + // Validation & Normalization if (body.substance && !['nicotine', 'weed'].includes(body.substance)) { return NextResponse.json({ error: 'Invalid substance' }, { status: 400 }); } if (body.trackingStartDate && !/^\d{4}-\d{2}-\d{2}$/.test(body.trackingStartDate)) { return NextResponse.json({ error: 'Invalid trackingStartDate format' }, { status: 400 }); } - if (body.dailyGoal !== undefined && (typeof body.dailyGoal !== 'number' || body.dailyGoal < 0)) { + + // Loose type checking for numbers (allow strings that parse to numbers) + const dailyGoal = Number(body.dailyGoal); + if (body.dailyGoal !== undefined && body.dailyGoal !== null && (isNaN(dailyGoal) || dailyGoal < 0)) { return NextResponse.json({ error: 'Invalid dailyGoal' }, { status: 400 }); } - if (body.userName && (typeof body.userName !== 'string' || body.userName.length > 100)) { - return NextResponse.json({ error: 'Invalid userName' }, { status: 400 }); - } - if (body.userAge !== undefined && (typeof body.userAge !== 'number' || body.userAge < 0 || body.userAge > 120)) { + + const userAge = Number(body.userAge); + if (body.userAge !== undefined && body.userAge !== null && (isNaN(userAge) || userAge < 0 || userAge > 120)) { return NextResponse.json({ error: 'Invalid userAge' }, { status: 400 }); } + if (body.religion && !['christian', 'secular'].includes(body.religion)) { return NextResponse.json({ error: 'Invalid religion' }, { status: 400 }); } diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index 54ef562..ecaa87e 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -218,8 +218,17 @@ export function Dashboard({ user }: DashboardProps) { ...preferences, [substance === 'nicotine' ? 'lastNicotineUsageTime' : 'lastWeedUsageTime']: now, }; - await savePreferencesAsync(latestPrefs); - setPreferences(latestPrefs); + + // Force specific fields to be present to avoid partial update issues + // This ensures that even if preferences is stale, we explicitly set the usage time + const payload: UserPreferences = { + ...latestPrefs, + lastNicotineUsageTime: substance === 'nicotine' ? now : (latestPrefs.lastNicotineUsageTime ?? null), + lastWeedUsageTime: substance === 'weed' ? now : (latestPrefs.lastWeedUsageTime ?? null), + }; + + await savePreferencesAsync(payload); + setPreferences(payload); } setActiveLoggingSubstance(null); diff --git a/src/components/HealthTimelineCard.tsx b/src/components/HealthTimelineCard.tsx index e0d9647..6c604ef 100644 --- a/src/components/HealthTimelineCard.tsx +++ b/src/components/HealthTimelineCard.tsx @@ -18,6 +18,7 @@ import { Cigarette, Leaf } from 'lucide-react'; +import { getTodayString, getLocalDateString } from '@/lib/date-utils'; interface HealthTimelineCardProps { usageData: UsageEntry[]; @@ -225,29 +226,57 @@ function HealthTimelineCardComponent({ // Calculate last usage timestamps only when data changes const lastUsageTimes = useMemo(() => { const getTimestamp = (substance: 'nicotine' | 'weed') => { - // 1. Check for stored timestamp first - const stored = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime; - if (stored) return new Date(stored).getTime(); + let lastTime = 0; - // 2. Fallback to usage data + // 1. Check for stored timestamp + const stored = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime; + if (stored) { + lastTime = new Date(stored).getTime(); + } + + // 2. Check usage data (usually more up-to-date for "just logged") const lastEntry = usageData .filter(e => e.substance === substance && e.count > 0) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0]; if (lastEntry) { + const todayStr = getTodayString(); + + // Calculate local midnight for today + const now = new Date(); + const localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + + // If usage recorded today + if (lastEntry.date === todayStr) { + // Check if the stored timestamp belongs to today (is after midnight) + if (lastTime >= localMidnight) { + return lastTime; + } + + // Fallback: If we have usage "Today" but no valid timestamp, + // we must assume it just happened or we missed the timestamp. + // Returning Date.now() resets the timer to 0. + return Date.now(); + } + const d = new Date(lastEntry.date); d.setHours(23, 59, 59, 999); - return d.getTime(); + const entryTime = d.getTime(); + + // Take the more recent of the two + lastTime = Math.max(lastTime, entryTime); } - // 3. Fallback to start date - if (preferences?.trackingStartDate) { + + + // 3. Fallback to start date if no usage found + if (lastTime === 0 && preferences?.trackingStartDate) { const d = new Date(preferences.trackingStartDate); d.setHours(0, 0, 0, 0); - return d.getTime(); + lastTime = d.getTime(); } - return null; + return lastTime || null; }; return { @@ -302,6 +331,7 @@ function HealthTimelineCardComponent({ + ); diff --git a/src/lib/d1.ts b/src/lib/d1.ts index 67fb451..ca7f74f 100644 --- a/src/lib/d1.ts +++ b/src/lib/d1.ts @@ -74,17 +74,29 @@ export async function upsertPreferencesD1(userId: string, data: Partial 1) { // At least updatedAt is always there + await db.prepare( + `UPDATE UserPreferences SET ${updates.join(', ')} WHERE userId = ?` + ).bind(...values).run(); + } + } else { // Insert await db.prepare( diff --git a/src/lib/storage.ts b/src/lib/storage.ts index a90c365..b14990b 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -181,6 +181,9 @@ export async function savePreferencesAsync(preferences: UserPreferences): Promis }); if (response.ok) { preferencesCache = preferences; + } else { + const err = await response.json(); + console.error('[Storage] Error saving preferences:', response.status, err); } } catch (error) { console.error('Error saving preferences:', error);