Add QuitPlanCard with 7-day tracking and personalized quit plan

- Restore QuitPlanCard component under calendar on dashboard
- Yellow gradient during tracking phase (before 7 days of data)
- Pink gradient when quit plan is active
- Track unique days with logged data for countdown
- Generate 4-week plan with 25% weekly reduction
- White text throughout for readability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Avery Felts 2026-01-24 01:45:59 -07:00
parent 26e64ac572
commit ec0d83586d
3 changed files with 97 additions and 49 deletions

View File

@ -8,6 +8,7 @@ import {
savePreferencesAsync,
saveUsageEntryAsync,
shouldShowUsagePrompt,
generateQuitPlan,
UserPreferences,
UsageEntry,
} from '@/lib/storage';
@ -16,6 +17,7 @@ import { SetupWizard } from './SetupWizard';
import { UsagePromptDialog } from './UsagePromptDialog';
import { UsageCalendar } from './UsageCalendar';
import { StatsCard } from './StatsCard';
import { QuitPlanCard } from './QuitPlanCard';
import { Button } from '@/components/ui/button';
import { PlusCircle } from 'lucide-react';
@ -98,6 +100,19 @@ export function Dashboard({ user }: DashboardProps) {
setRefreshKey(prev => prev + 1);
};
const handleGeneratePlan = async () => {
if (!preferences) return;
const plan = generateQuitPlan(preferences.substance);
const updatedPrefs = {
...preferences,
quitPlan: plan,
};
await savePreferencesAsync(updatedPrefs);
setPreferences(updatedPrefs);
setRefreshKey(prev => prev + 1);
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
@ -133,6 +148,12 @@ export function Dashboard({ user }: DashboardProps) {
onDataUpdate={loadData}
userId={user.id}
/>
<QuitPlanCard
key={`quit-plan-${refreshKey}`}
plan={preferences.quitPlan}
onGeneratePlan={handleGeneratePlan}
usageData={usageData}
/>
</div>
<div className="space-y-6">
<StatsCard key={`stats-nicotine-${refreshKey}`} usageData={usageData} substance="nicotine" />

View File

@ -2,62 +2,82 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { QuitPlan, getCurrentWeekTarget } from '@/lib/storage';
import { QuitPlan, UsageEntry } from '@/lib/storage';
import { Target, TrendingDown } from 'lucide-react';
interface QuitPlanCardProps {
plan: QuitPlan | null;
onGeneratePlan: () => void;
hasEnoughData: boolean;
daysTracked: number;
currentAverage: number;
usageData: UsageEntry[];
}
export function QuitPlanCard({
plan,
onGeneratePlan,
hasEnoughData,
daysTracked,
currentAverage,
usageData,
}: QuitPlanCardProps) {
const currentTarget = getCurrentWeekTarget();
// Count unique days with any logged data
const uniqueDaysWithData = new Set(usageData.map(e => e.date)).size;
const daysRemaining = Math.max(0, 7 - uniqueDaysWithData);
const hasEnoughData = uniqueDaysWithData >= 7;
// Calculate current average
const totalUsage = usageData.reduce((sum, e) => sum + e.count, 0);
const currentAverage = uniqueDaysWithData > 0 ? Math.round(totalUsage / uniqueDaysWithData) : 0;
if (!plan) {
return (
<Card className="bg-card/80 backdrop-blur-sm">
<Card className="backdrop-blur-sm shadow-lg drop-shadow-md border-yellow-500/30" style={{
background: 'linear-gradient(135deg, rgba(234, 179, 8, 0.2) 0%, rgba(202, 138, 4, 0.15) 100%)'
}}>
<CardHeader>
<CardTitle>Your Quit Plan</CardTitle>
<CardDescription>
Track your usage for 7 days to receive a personalized quit plan
<CardTitle className="flex items-center gap-2 text-white">
<Target className="h-5 w-5 text-yellow-400" />
Your Personalized Plan
</CardTitle>
<CardDescription className="text-white/70">
We&apos;re tracking your usage to build your custom quit plan
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-muted p-4 rounded-lg">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium">Tracking Progress</span>
<span className="text-sm text-muted-foreground">{daysTracked}/7 days</span>
<div className="bg-yellow-500/20 border border-yellow-500/30 p-4 rounded-lg">
<div className="flex justify-between items-center mb-3">
<span className="text-sm font-medium text-white">Tracking Progress</span>
<span className="text-sm text-yellow-300 font-bold">
{daysRemaining > 0 ? `${daysRemaining} days left` : 'Ready!'}
</span>
</div>
<div className="w-full bg-background rounded-full h-2">
<div className="w-full bg-white/10 rounded-full h-3">
<div
className="bg-primary h-2 rounded-full transition-all"
style={{ width: `${Math.min(100, (daysTracked / 7) * 100)}%` }}
className="bg-gradient-to-r from-yellow-400 to-yellow-500 h-3 rounded-full transition-all"
style={{ width: `${Math.min(100, (uniqueDaysWithData / 7) * 100)}%` }}
/>
</div>
<p className="text-xs text-white/60 mt-2 text-center">
{uniqueDaysWithData} of 7 days tracked
</p>
</div>
{hasEnoughData ? (
<div className="space-y-3">
<p className="text-sm text-muted-foreground">
You&apos;ve tracked for a week. Your average daily usage is{' '}
<strong>{currentAverage}</strong> times per day.
<p className="text-sm text-white text-center">
Great work! Your average daily usage is{' '}
<strong className="text-yellow-300">{currentAverage}</strong> per day.
</p>
<Button onClick={onGeneratePlan} className="w-full">
<Button onClick={onGeneratePlan} className="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-semibold">
<TrendingDown className="mr-2 h-4 w-4" />
Generate My Quit Plan
</Button>
</div>
) : (
<p className="text-sm text-muted-foreground text-center">
Keep tracking! We need at least 7 days of data to create your personalized plan.
</p>
<div className="text-center space-y-2">
<p className="text-sm text-white">
Log your usage each day. After 7 days, we&apos;ll create a personalized plan to help you reduce by <strong className="text-yellow-300">25% each week</strong>.
</p>
<p className="text-xs text-white/60">
Your plan will be tailored to your habits
</p>
</div>
)}
</CardContent>
</Card>
@ -70,50 +90,57 @@ export function QuitPlanCard({
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24 * 7)
) + 1;
const totalWeeks = plan.weeklyTargets.length;
const currentTarget = weekNumber <= totalWeeks ? plan.weeklyTargets[weekNumber - 1] : 0;
return (
<Card className="bg-card/80 backdrop-blur-sm">
<Card className="backdrop-blur-sm shadow-lg drop-shadow-md border-pink-500/30" style={{
background: 'linear-gradient(135deg, rgba(236, 72, 153, 0.2) 0%, rgba(219, 39, 119, 0.15) 100%)'
}}>
<CardHeader>
<CardTitle>Your Quit Plan</CardTitle>
<CardDescription>
Week {Math.min(weekNumber, totalWeeks)} of {totalWeeks}
<CardTitle className="flex items-center gap-2 text-white">
<TrendingDown className="h-5 w-5 text-pink-400" />
Your Quit Plan
</CardTitle>
<CardDescription className="text-white/70">
Week {Math.min(weekNumber, totalWeeks)} of {totalWeeks} - 25% weekly reduction
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-primary/10 p-4 rounded-lg">
<p className="text-sm text-muted-foreground mb-1">This week&apos;s target</p>
<p className="text-3xl font-bold text-primary">
{currentTarget !== null ? `${currentTarget} per day` : 'Complete!'}
<div className="bg-pink-500/20 border border-pink-500/30 p-4 rounded-lg text-center">
<p className="text-sm text-white/70 mb-1">This week&apos;s daily target</p>
<p className="text-4xl font-bold text-pink-300">
{currentTarget !== null && currentTarget > 0 ? currentTarget : '0'}
</p>
<p className="text-sm text-white/60">per day</p>
</div>
<div className="space-y-2">
<p className="text-sm font-medium">Weekly reduction plan:</p>
<div className="grid grid-cols-6 gap-2">
<p className="text-sm font-medium text-white">Weekly targets:</p>
<div className="grid grid-cols-4 gap-2">
{plan.weeklyTargets.map((target, index) => (
<div
key={index}
className={`text-center p-2 rounded ${
className={`text-center p-2 rounded-lg ${
index + 1 === weekNumber
? 'bg-primary text-primary-foreground'
? 'bg-pink-500 text-white'
: index + 1 < weekNumber
? 'bg-green-100 dark:bg-green-900/30 text-green-900 dark:text-green-100'
: 'bg-muted'
? 'bg-pink-900/50 text-pink-200'
: 'bg-white/10 text-white/60'
}`}
>
<p className="text-xs">W{index + 1}</p>
<p className="text-xs">Week {index + 1}</p>
<p className="font-bold">{target}</p>
</div>
))}
</div>
</div>
<div className="text-sm text-muted-foreground">
<div className="text-sm text-white/70 bg-white/5 p-3 rounded-lg">
<p>
<strong>Baseline:</strong> {plan.baselineAverage} uses/day
<strong className="text-white">Started at:</strong> {plan.baselineAverage}/day
</p>
<p>
<strong>End date:</strong> {new Date(plan.endDate).toLocaleDateString()}
<strong className="text-white">Goal:</strong> Quit by {new Date(plan.endDate).toLocaleDateString()}
</p>
</div>
</CardContent>

View File

@ -231,15 +231,15 @@ export function generateQuitPlan(substance: 'nicotine' | 'weed', _userId?: strin
const today = new Date();
const startDate = today.toISOString().split('T')[0];
// 6-week reduction plan
// 4-week reduction plan with 25% weekly reduction
const endDate = new Date(today);
endDate.setDate(endDate.getDate() + 42);
endDate.setDate(endDate.getDate() + 28);
// Gradual reduction: each week reduce by ~15-20%
// Gradual reduction: each week reduce by 25%
const weeklyTargets: number[] = [];
let current = baseline;
for (let i = 0; i < 6; i++) {
current = Math.max(0, Math.round(current * 0.8));
for (let i = 0; i < 4; i++) {
current = Math.max(0, Math.round(current * 0.75));
weeklyTargets.push(current);
}