Update UI and add user personalization features
- Rename app from QuitTrack to QuitTraq
- Add metallic dark gradient background
- Change header to light purple gradient
- Add name and age collection in setup wizard
- Display personalized "Welcome {name}, you got this!" message
- Hide username/email, show only profile picture
- Change calendar to red gradient for usage days
- Update logging prompt to "just took" instead of daily total
- Add floating "Log Puff" button for easy access
- Fix calendar editing to properly update values
- Add glass-morphism effect to cards
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a812e6342c
commit
9918432686
@ -170,8 +170,31 @@
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply text-foreground;
|
||||
font-family: var(--font-sans);
|
||||
letter-spacing: var(--tracking-normal);
|
||||
background: linear-gradient(135deg,
|
||||
#1a1a2e 0%,
|
||||
#16213e 25%,
|
||||
#1a1a2e 50%,
|
||||
#0f0f1a 75%,
|
||||
#1a1a2e 100%);
|
||||
background-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 20%, rgba(120, 119, 198, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 80% 80%, rgba(74, 85, 104, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 40% 60%, rgba(45, 55, 72, 0.15) 0%, transparent 40%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "QuitTrack - Track Your Journey to Quit Smoking",
|
||||
title: "QuitTraq - Track Your Journey to Quit Smoking",
|
||||
description: "Track and manage your smoking habits, set goals, and quit safely with personalized plans.",
|
||||
};
|
||||
|
||||
|
||||
@ -15,9 +15,9 @@ export default function LoginPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<Card className="w-full max-w-md bg-card/80 backdrop-blur-sm border-white/10">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-3xl font-bold">QuitTrack</CardTitle>
|
||||
<CardTitle className="text-3xl font-bold">QuitTraq</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Track your journey to a smoke-free life
|
||||
</CardDescription>
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
savePreferences,
|
||||
saveUsageEntry,
|
||||
shouldShowUsagePrompt,
|
||||
setLastPromptDate,
|
||||
hasOneWeekOfData,
|
||||
calculateWeeklyAverage,
|
||||
generateQuitPlan,
|
||||
@ -21,6 +20,8 @@ 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;
|
||||
@ -53,14 +54,16 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleSetupComplete = (substance: 'nicotine' | 'weed') => {
|
||||
const handleSetupComplete = (data: { substance: 'nicotine' | 'weed'; name: string; age: number }) => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const newPrefs: UserPreferences = {
|
||||
substance,
|
||||
substance: data.substance,
|
||||
trackingStartDate: today,
|
||||
hasCompletedSetup: true,
|
||||
dailyGoal: null,
|
||||
quitPlan: null,
|
||||
userName: data.name,
|
||||
userAge: data.age,
|
||||
};
|
||||
savePreferences(newPrefs);
|
||||
setPreferences(newPrefs);
|
||||
@ -69,7 +72,11 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
};
|
||||
|
||||
const handleUsageSubmit = (count: number) => {
|
||||
if (!preferences) return;
|
||||
if (!preferences || count === 0) {
|
||||
setShowUsagePrompt(false);
|
||||
loadData();
|
||||
return;
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
saveUsageEntry({
|
||||
@ -77,7 +84,6 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
count,
|
||||
substance: preferences.substance,
|
||||
});
|
||||
setLastPromptDate(today);
|
||||
setShowUsagePrompt(false);
|
||||
loadData();
|
||||
};
|
||||
@ -101,36 +107,50 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="animate-pulse text-lg">Loading...</div>
|
||||
<div className="animate-pulse text-lg text-white">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="min-h-screen">
|
||||
<UserHeader user={user} />
|
||||
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
{preferences && (
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-6">
|
||||
<UsageCalendar
|
||||
usageData={usageData}
|
||||
substance={preferences.substance}
|
||||
onDataUpdate={loadData}
|
||||
/>
|
||||
<>
|
||||
{/* 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="space-y-6">
|
||||
<StatsCard usageData={usageData} substance={preferences.substance} />
|
||||
<QuitPlanCard
|
||||
plan={preferences.quitPlan}
|
||||
onGeneratePlan={handleGeneratePlan}
|
||||
hasEnoughData={hasOneWeekOfData(preferences.substance)}
|
||||
daysTracked={getDaysTracked()}
|
||||
currentAverage={calculateWeeklyAverage(preferences.substance)}
|
||||
/>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-6">
|
||||
<UsageCalendar
|
||||
usageData={usageData}
|
||||
substance={preferences.substance}
|
||||
onDataUpdate={loadData}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<StatsCard usageData={usageData} substance={preferences.substance} />
|
||||
<QuitPlanCard
|
||||
plan={preferences.quitPlan}
|
||||
onGeneratePlan={handleGeneratePlan}
|
||||
hasEnoughData={hasOneWeekOfData(preferences.substance)}
|
||||
daysTracked={getDaysTracked()}
|
||||
currentAverage={calculateWeeklyAverage(preferences.substance)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ export function QuitPlanCard({
|
||||
|
||||
if (!plan) {
|
||||
return (
|
||||
<Card>
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>Your Quit Plan</CardTitle>
|
||||
<CardDescription>
|
||||
@ -72,7 +72,7 @@ export function QuitPlanCard({
|
||||
const totalWeeks = plan.weeklyTargets.length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>Your Quit Plan</CardTitle>
|
||||
<CardDescription>
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
@ -20,15 +21,32 @@ import {
|
||||
|
||||
interface SetupWizardProps {
|
||||
open: boolean;
|
||||
onComplete: (substance: 'nicotine' | 'weed') => void;
|
||||
onComplete: (data: { substance: 'nicotine' | 'weed'; name: string; age: number }) => void;
|
||||
}
|
||||
|
||||
export function SetupWizard({ open, onComplete }: SetupWizardProps) {
|
||||
const [step, setStep] = useState(1);
|
||||
const [name, setName] = useState('');
|
||||
const [age, setAge] = useState('25');
|
||||
const [substance, setSubstance] = useState<'nicotine' | 'weed' | ''>('');
|
||||
|
||||
const ages = Array.from({ length: 83 }, (_, i) => (i + 18).toString());
|
||||
|
||||
const handleNext = () => {
|
||||
if (step === 1 && name.trim()) {
|
||||
setStep(2);
|
||||
} else if (step === 2 && age) {
|
||||
setStep(3);
|
||||
}
|
||||
};
|
||||
|
||||
const handleComplete = () => {
|
||||
if (substance) {
|
||||
onComplete(substance);
|
||||
if (substance && name.trim() && age) {
|
||||
onComplete({
|
||||
substance,
|
||||
name: name.trim(),
|
||||
age: parseInt(age, 10),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -36,39 +54,101 @@ export function SetupWizard({ open, onComplete }: SetupWizardProps) {
|
||||
<Dialog open={open}>
|
||||
<DialogContent className="sm:max-w-md" onInteractOutside={(e) => e.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Welcome to QuitTrack</DialogTitle>
|
||||
<DialogTitle>Welcome to QuitTraq</DialogTitle>
|
||||
<DialogDescription>
|
||||
Let's set up your tracking preferences to help you on your journey.
|
||||
{step === 1 && "Let's get to know you a little better."}
|
||||
{step === 2 && "Just one more thing about you."}
|
||||
{step === 3 && "Set up your tracking preferences."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="substance">What would you like to track?</Label>
|
||||
<Select value={substance} onValueChange={(v) => setSubstance(v as 'nicotine' | 'weed')}>
|
||||
<SelectTrigger id="substance">
|
||||
<SelectValue placeholder="Select substance" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="nicotine">Nicotine (Vaping/Cigarettes)</SelectItem>
|
||||
<SelectItem value="weed">Cannabis/Weed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{step === 1 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">What's your name?</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Enter your name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="text-lg"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={!name.trim()}
|
||||
className="w-full"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-muted p-4 rounded-lg space-y-2">
|
||||
<h4 className="font-medium">How it works</h4>
|
||||
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
||||
<li>Track your daily usage for one week</li>
|
||||
<li>We'll analyze your patterns</li>
|
||||
<li>Get a personalized quit plan based on your habits</li>
|
||||
<li>Gradually reduce your intake safely</li>
|
||||
</ol>
|
||||
</div>
|
||||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="age">How old are you?</Label>
|
||||
<Select value={age} onValueChange={setAge}>
|
||||
<SelectTrigger id="age" className="text-lg">
|
||||
<SelectValue placeholder="Select your age" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-60">
|
||||
{ages.map((a) => (
|
||||
<SelectItem key={a} value={a}>
|
||||
{a} years old
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setStep(1)} className="flex-1">
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleNext} disabled={!age} className="flex-1">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button onClick={handleComplete} disabled={!substance} className="w-full">
|
||||
Start Tracking
|
||||
</Button>
|
||||
{step === 3 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="substance">What would you like to track?</Label>
|
||||
<Select value={substance} onValueChange={(v) => setSubstance(v as 'nicotine' | 'weed')}>
|
||||
<SelectTrigger id="substance">
|
||||
<SelectValue placeholder="Select substance" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="nicotine">Nicotine (Vaping/Cigarettes)</SelectItem>
|
||||
<SelectItem value="weed">Cannabis/Weed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted p-4 rounded-lg space-y-2">
|
||||
<h4 className="font-medium">How it works</h4>
|
||||
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
||||
<li>Log each puff throughout the day</li>
|
||||
<li>Track your patterns for one week</li>
|
||||
<li>Get a personalized quit plan</li>
|
||||
<li>Gradually reduce your intake safely</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setStep(2)} className="flex-1">
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleComplete} disabled={!substance} className="flex-1">
|
||||
Start Tracking
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -49,7 +49,7 @@ export function StatsCard({ usageData, substance }: StatsCardProps) {
|
||||
const totalDays = substanceData.length;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>Your Stats</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@ -44,11 +44,19 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
const handleSave = () => {
|
||||
if (selectedDate) {
|
||||
const dateStr = selectedDate.toISOString().split('T')[0];
|
||||
setUsageForDate(dateStr, parseInt(editCount, 10) || 0, substance);
|
||||
const newCount = parseInt(editCount, 10) || 0;
|
||||
setUsageForDate(dateStr, newCount, substance);
|
||||
onDataUpdate();
|
||||
}
|
||||
setIsEditing(false);
|
||||
setSelectedDate(undefined);
|
||||
setEditCount('');
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
setSelectedDate(undefined);
|
||||
setEditCount('');
|
||||
};
|
||||
|
||||
const getUsageCount = useCallback((date: Date): number => {
|
||||
@ -57,11 +65,21 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
return entry?.count ?? 0;
|
||||
}, [usageData, substance]);
|
||||
|
||||
const getColorClass = useCallback((count: number): string => {
|
||||
if (count === 0) return 'bg-green-100 dark:bg-green-900/30 text-green-900 dark:text-green-100 hover:bg-green-200 dark:hover:bg-green-900/50';
|
||||
if (count <= 3) return 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-900 dark:text-yellow-100 hover:bg-yellow-200 dark:hover:bg-yellow-900/50';
|
||||
if (count <= 6) return 'bg-orange-100 dark:bg-orange-900/30 text-orange-900 dark:text-orange-100 hover:bg-orange-200 dark:hover:bg-orange-900/50';
|
||||
return 'bg-red-100 dark:bg-red-900/30 text-red-900 dark:text-red-100 hover:bg-red-200 dark:hover:bg-red-900/50';
|
||||
const getColorStyle = useCallback((count: number): React.CSSProperties => {
|
||||
if (count === 0) {
|
||||
return {
|
||||
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
||||
color: 'white',
|
||||
};
|
||||
}
|
||||
// Red gradient for any usage - more intense red for higher counts
|
||||
const intensity = Math.min(count / 10, 1); // Max intensity at 10+ uses
|
||||
const lightRed = `rgba(239, 68, 68, ${0.6 + intensity * 0.4})`;
|
||||
const darkRed = `rgba(185, 28, 28, ${0.7 + intensity * 0.3})`;
|
||||
return {
|
||||
background: `linear-gradient(135deg, ${lightRed} 0%, ${darkRed} 100%)`,
|
||||
color: 'white',
|
||||
};
|
||||
}, []);
|
||||
|
||||
const CustomDayButton = useCallback(({ day, modifiers, ...props }: DayButtonProps) => {
|
||||
@ -72,30 +90,31 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
dateToCheck.setHours(0, 0, 0, 0);
|
||||
const isFuture = dateToCheck > today;
|
||||
const count = isFuture ? -1 : getUsageCount(date);
|
||||
const colorClass = count >= 0 ? getColorClass(count) : '';
|
||||
const colorStyle = count >= 0 ? getColorStyle(count) : {};
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={`relative w-full h-full p-2 text-sm rounded-md transition-colors ${
|
||||
isFuture ? 'text-muted-foreground opacity-50' : colorClass
|
||||
} ${modifiers.today ? 'ring-2 ring-primary' : ''}`}
|
||||
onClick={() => handleDateSelect(date)}
|
||||
style={count >= 0 ? colorStyle : undefined}
|
||||
className={`relative w-full h-full p-2 text-sm rounded-md transition-all hover:opacity-80 ${
|
||||
isFuture ? 'text-muted-foreground opacity-30 cursor-not-allowed' : 'cursor-pointer shadow-sm'
|
||||
} ${modifiers.today ? 'ring-2 ring-white ring-offset-2 ring-offset-background' : ''}`}
|
||||
onClick={() => !isFuture && handleDateSelect(date)}
|
||||
disabled={isFuture}
|
||||
>
|
||||
<span>{date.getDate()}</span>
|
||||
<span className="font-medium">{date.getDate()}</span>
|
||||
{count > 0 && (
|
||||
<span className="absolute bottom-0.5 right-1 text-[10px] font-bold">
|
||||
<span className="absolute bottom-0.5 right-1 text-[10px] font-bold bg-black/20 px-1 rounded">
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}, [getUsageCount, getColorClass]);
|
||||
}, [getUsageCount, getColorStyle]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>Usage Calendar</CardTitle>
|
||||
</CardHeader>
|
||||
@ -104,7 +123,7 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={handleDateSelect}
|
||||
className="rounded-md border p-3"
|
||||
className="rounded-md border p-3 bg-background/50"
|
||||
showOutsideDays={false}
|
||||
components={{
|
||||
DayButton: CustomDayButton,
|
||||
@ -116,26 +135,21 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
/>
|
||||
<div className="mt-4 flex flex-wrap gap-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded bg-green-100 dark:bg-green-900/30 border" />
|
||||
<span>0 uses</span>
|
||||
<div className="w-4 h-4 rounded" style={{ background: 'linear-gradient(135deg, #10b981, #059669)' }} />
|
||||
<span>No usage</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded bg-yellow-100 dark:bg-yellow-900/30 border" />
|
||||
<span>1-3</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded bg-orange-100 dark:bg-orange-900/30 border" />
|
||||
<span>4-6</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded bg-red-100 dark:bg-red-900/30 border" />
|
||||
<span>7+</span>
|
||||
<div className="w-4 h-4 rounded" style={{ background: 'linear-gradient(135deg, rgba(239,68,68,0.7), rgba(185,28,28,0.8))' }} />
|
||||
<span>Has usage</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
Click any day to edit the count
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={isEditing} onOpenChange={setIsEditing}>
|
||||
<Dialog open={isEditing} onOpenChange={(open) => !open && handleCancel()}>
|
||||
<DialogContent className="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@ -144,7 +158,7 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="editCount">Number of uses</Label>
|
||||
<Label htmlFor="editCount">Total puffs for this day</Label>
|
||||
<Input
|
||||
id="editCount"
|
||||
type="number"
|
||||
@ -153,9 +167,12 @@ export function UsageCalendar({ usageData, substance, onDataUpdate }: UsageCalen
|
||||
onChange={(e) => setEditCount(e.target.value)}
|
||||
className="text-center text-lg"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
This will replace the current value
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setIsEditing(false)} className="flex-1">
|
||||
<Button variant="outline" onClick={handleCancel} className="flex-1">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave} className="flex-1">
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { getUsageForDate } from '@/lib/storage';
|
||||
|
||||
interface UsagePromptDialogProps {
|
||||
open: boolean;
|
||||
@ -25,24 +26,32 @@ export function UsagePromptDialog({
|
||||
onSubmit,
|
||||
substance,
|
||||
}: UsagePromptDialogProps) {
|
||||
const [hasUsed, setHasUsed] = useState<boolean | null>(null);
|
||||
const [count, setCount] = useState('');
|
||||
const [wantsToLog, setWantsToLog] = useState<boolean | null>(null);
|
||||
const [count, setCount] = useState('1');
|
||||
|
||||
const substanceLabel = substance === 'nicotine' ? 'nicotine (vape/cigarettes)' : 'cannabis';
|
||||
const substanceLabel = substance === 'nicotine' ? 'a puff or cigarette' : 'a hit';
|
||||
const substanceLabelPlural = substance === 'nicotine' ? 'puffs/cigarettes' : 'hits';
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const todayCount = typeof window !== 'undefined' ? getUsageForDate(today, substance) : 0;
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (hasUsed === false) {
|
||||
onSubmit(0);
|
||||
} else if (hasUsed === true && count) {
|
||||
if (wantsToLog === true && count) {
|
||||
onSubmit(parseInt(count, 10));
|
||||
}
|
||||
setHasUsed(null);
|
||||
setCount('');
|
||||
setWantsToLog(null);
|
||||
setCount('1');
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
setWantsToLog(null);
|
||||
setCount('1');
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setHasUsed(null);
|
||||
setCount('');
|
||||
setWantsToLog(null);
|
||||
setCount('1');
|
||||
onClose();
|
||||
};
|
||||
|
||||
@ -50,78 +59,72 @@ export function UsagePromptDialog({
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Daily Check-in</DialogTitle>
|
||||
<DialogTitle>Log Your Usage</DialogTitle>
|
||||
<DialogDescription>
|
||||
Tracking your usage helps you understand your habits and work towards your goals.
|
||||
Log each time you smoke to track your progress. You can log multiple times throughout the day.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{hasUsed === null ? (
|
||||
{todayCount > 0 && (
|
||||
<div className="bg-muted/50 p-3 rounded-lg text-center">
|
||||
<p className="text-sm text-muted-foreground">Today's total so far</p>
|
||||
<p className="text-2xl font-bold">{todayCount} {substanceLabelPlural}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{wantsToLog === null ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-center font-medium">
|
||||
Have you consumed any {substanceLabel} today?
|
||||
Did you just have {substanceLabel}?
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => setHasUsed(true)}
|
||||
className="w-24"
|
||||
onClick={() => setWantsToLog(true)}
|
||||
className="w-32"
|
||||
>
|
||||
Yes
|
||||
Yes, log it
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => setHasUsed(false)}
|
||||
className="w-24"
|
||||
onClick={handleSkip}
|
||||
className="w-32"
|
||||
>
|
||||
No
|
||||
No, skip
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : hasUsed === true ? (
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="count">
|
||||
How many times did you use {substanceLabel} today?
|
||||
How many {substanceLabelPlural} did you just have?
|
||||
</Label>
|
||||
<Input
|
||||
id="count"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="Enter number"
|
||||
placeholder="1"
|
||||
value={count}
|
||||
onChange={(e) => setCount(e.target.value)}
|
||||
className="text-center text-lg"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{substance === 'nicotine'
|
||||
? 'Count each vape session or cigarette'
|
||||
: 'Count each smoking/consumption session'}
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
This will be added to today's total
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setHasUsed(null)} className="flex-1">
|
||||
<Button variant="outline" onClick={() => setWantsToLog(null)} className="flex-1">
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={!count} className="flex-1">
|
||||
Log Usage
|
||||
<Button onClick={handleSubmit} disabled={!count || parseInt(count) < 1} className="flex-1">
|
||||
Log {count || 1} {parseInt(count) === 1 ? (substance === 'nicotine' ? 'puff' : 'hit') : substanceLabelPlural}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="text-4xl">🎉</div>
|
||||
<p className="font-medium">Great job staying smoke-free today!</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Every day without smoking is a victory. Keep it up!
|
||||
</p>
|
||||
<Button onClick={handleSubmit} className="w-full">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@ -3,12 +3,21 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { User } from '@/lib/session';
|
||||
import { getPreferences } from '@/lib/storage';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface UserHeaderProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export function UserHeader({ user }: UserHeaderProps) {
|
||||
const [userName, setUserName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const prefs = getPreferences();
|
||||
setUserName(prefs.userName);
|
||||
}, []);
|
||||
|
||||
const initials = [user.firstName?.[0], user.lastName?.[0]]
|
||||
.filter(Boolean)
|
||||
.join('')
|
||||
@ -19,30 +28,42 @@ export function UserHeader({ user }: UserHeaderProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b bg-card">
|
||||
<header className="border-b border-white/10" style={{
|
||||
background: 'linear-gradient(135deg, rgba(147, 112, 219, 0.9) 0%, rgba(138, 99, 210, 0.85) 50%, rgba(123, 82, 195, 0.9) 100%)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
}}>
|
||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-2xl font-bold text-primary">QuitTrack</h1>
|
||||
<h1 className="text-2xl font-bold text-white">QuitTraq</h1>
|
||||
{userName && (
|
||||
<p className="text-white/90 text-lg hidden sm:block">
|
||||
Welcome {userName}, you got this!
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.profilePictureUrl ?? undefined} alt={user.firstName ?? 'User'} />
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="hidden sm:block">
|
||||
<p className="text-sm font-medium">
|
||||
{user.firstName ? `${user.firstName} ${user.lastName ?? ''}`.trim() : user.email}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={handleLogout}>
|
||||
<Avatar className="h-10 w-10 ring-2 ring-white/30">
|
||||
<AvatarImage src={user.profilePictureUrl ?? undefined} alt={userName || 'User'} />
|
||||
<AvatarFallback className="bg-white/20 text-white">{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleLogout}
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{userName && (
|
||||
<div className="sm:hidden container mx-auto px-4 pb-3">
|
||||
<p className="text-white/90 text-base">
|
||||
Welcome {userName}, you got this!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@ export interface UserPreferences {
|
||||
hasCompletedSetup: boolean;
|
||||
dailyGoal: number | null;
|
||||
quitPlan: QuitPlan | null;
|
||||
userName: string | null;
|
||||
userAge: number | null;
|
||||
}
|
||||
|
||||
export interface QuitPlan {
|
||||
@ -77,6 +79,8 @@ export function getPreferences(): UserPreferences {
|
||||
hasCompletedSetup: false,
|
||||
dailyGoal: null,
|
||||
quitPlan: null,
|
||||
userName: null,
|
||||
userAge: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -88,6 +92,8 @@ export function getPreferences(): UserPreferences {
|
||||
hasCompletedSetup: false,
|
||||
dailyGoal: null,
|
||||
quitPlan: null,
|
||||
userName: null,
|
||||
userAge: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -110,9 +116,8 @@ export function setLastPromptDate(date: string): void {
|
||||
}
|
||||
|
||||
export function shouldShowUsagePrompt(): boolean {
|
||||
const lastPrompt = getLastPromptDate();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
return lastPrompt !== today;
|
||||
// Always show the prompt - users can log multiple times throughout the day
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getWeeklyData(substance: 'nicotine' | 'weed'): UsageEntry[] {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user