Avery Felts 39a1e858fb Fix per-user data isolation by passing userId explicitly to all storage operations
- Pass user.id explicitly to all storage function calls instead of relying on global state
- Add userId prop to UsageCalendar and UsagePromptDialog components
- Fix UserHeader to use user.id when fetching preferences
- Add refreshKey to force calendar re-render after logging usage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 22:01:16 -07:00

186 lines
5.5 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { User } from '@/lib/session';
import {
getUsageData,
getPreferences,
savePreferences,
saveUsageEntry,
shouldShowUsagePrompt,
hasOneWeekOfData,
calculateWeeklyAverage,
generateQuitPlan,
setCurrentUserId,
UserPreferences,
UsageEntry,
} from '@/lib/storage';
import { UserHeader } from './UserHeader';
import { SetupWizard } from './SetupWizard';
import { UsagePromptDialog } from './UsagePromptDialog';
import { UsageCalendar } from './UsageCalendar';
import { QuitPlanCard } from './QuitPlanCard';
import { StatsCard } from './StatsCard';
import { Button } from '@/components/ui/button';
import { PlusCircle } from 'lucide-react';
interface DashboardProps {
user: User;
}
export function Dashboard({ user }: DashboardProps) {
const [preferences, setPreferences] = useState<UserPreferences | null>(null);
const [usageData, setUsageData] = useState<UsageEntry[]>([]);
const [showSetup, setShowSetup] = useState(false);
const [showUsagePrompt, setShowUsagePrompt] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [refreshKey, setRefreshKey] = useState(0);
const loadData = useCallback(() => {
// Always pass user.id explicitly to ensure correct data is loaded
const prefs = getPreferences(user.id);
const usage = getUsageData(user.id);
setPreferences(prefs);
setUsageData(usage);
setRefreshKey(prev => prev + 1);
return prefs;
}, [user.id]);
useEffect(() => {
// Set the current user ID for all storage operations
setCurrentUserId(user.id);
const prefs = loadData();
if (!prefs.hasCompletedSetup) {
setShowSetup(true);
} else if (shouldShowUsagePrompt()) {
setShowUsagePrompt(true);
}
setIsLoading(false);
}, [user.id, loadData]);
const handleSetupComplete = (data: { substance: 'nicotine' | 'weed'; name: string; age: number }) => {
const today = new Date().toISOString().split('T')[0];
const newPrefs: UserPreferences = {
substance: data.substance,
trackingStartDate: today,
hasCompletedSetup: true,
dailyGoal: null,
quitPlan: null,
userName: data.name,
userAge: data.age,
};
savePreferences(newPrefs, user.id);
setPreferences(newPrefs);
setShowSetup(false);
setShowUsagePrompt(true);
setRefreshKey(prev => prev + 1);
};
const handleUsageSubmit = (count: number) => {
if (!preferences) {
setShowUsagePrompt(false);
return;
}
if (count > 0) {
const today = new Date().toISOString().split('T')[0];
saveUsageEntry({
date: today,
count,
substance: preferences.substance,
}, user.id);
}
setShowUsagePrompt(false);
// Reload data and force calendar refresh
const usage = getUsageData(user.id);
setUsageData(usage);
setRefreshKey(prev => prev + 1);
};
const handleGeneratePlan = () => {
if (!preferences) return;
const plan = generateQuitPlan(preferences.substance, user.id);
const updatedPrefs = { ...preferences, quitPlan: plan };
savePreferences(updatedPrefs, user.id);
setPreferences(updatedPrefs);
};
const getDaysTracked = (): number => {
if (!preferences?.trackingStartDate) return 0;
const startDate = new Date(preferences.trackingStartDate);
const today = new Date();
return Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)) + 1;
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-pulse text-lg text-white">Loading...</div>
</div>
);
}
return (
<div className="min-h-screen">
<UserHeader user={user} />
<main className="container mx-auto px-4 py-8">
{preferences && (
<>
{/* Floating Log Button */}
<div className="fixed bottom-6 right-6 z-50">
<Button
size="lg"
onClick={() => setShowUsagePrompt(true)}
className="h-14 px-6 rounded-full shadow-lg bg-gradient-to-r from-primary to-primary/80 hover:from-primary/90 hover:to-primary/70"
>
<PlusCircle className="mr-2 h-5 w-5" />
Log Puff
</Button>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-6">
<UsageCalendar
key={refreshKey}
usageData={usageData}
substance={preferences.substance}
onDataUpdate={loadData}
userId={user.id}
/>
</div>
<div className="space-y-6">
<StatsCard usageData={usageData} substance={preferences.substance} />
<QuitPlanCard
plan={preferences.quitPlan}
onGeneratePlan={handleGeneratePlan}
hasEnoughData={hasOneWeekOfData(preferences.substance, user.id)}
daysTracked={getDaysTracked()}
currentAverage={calculateWeeklyAverage(preferences.substance, user.id)}
/>
</div>
</div>
</>
)}
</main>
<SetupWizard open={showSetup} onComplete={handleSetupComplete} />
{preferences && (
<UsagePromptDialog
open={showUsagePrompt}
onClose={() => setShowUsagePrompt(false)}
onSubmit={handleUsageSubmit}
substance={preferences.substance}
userId={user.id}
/>
)}
</div>
);
}