import { useMemo, useState } from "react"; import { ArrowRight, Check, ChevronLeft } from "lucide-react"; import { APP_THEMES, THEME_CATEGORIES, type ThemeCategory } from "../data/themes"; import { currency } from "../lib/metrics"; import type { UserLimits } from "../types"; type OnboardingScreenProps = { onSave: (limits: UserLimits, themeId: string) => Promise; onClose: () => void; activeThemeId: string; onThemeChange: (themeId: string) => void; userName?: string; }; const STEP_COUNT = 6; const curfewOptions: Array<{ id: string; label: string; hint: string }> = [ { id: "16:00", label: "4:00 PM", hint: "Early cut-off" }, { id: "18:00", label: "6:00 PM", hint: "Balanced default" }, { id: "20:00", label: "8:00 PM", hint: "Late schedule" }, { id: "none", label: "No curfew", hint: "Only track intake" }, ]; export function OnboardingScreen({ onSave, onClose, activeThemeId, onThemeChange, userName, }: OnboardingScreenProps) { const [step, setStep] = useState(1); const [dailyCanLimit, setDailyCanLimit] = useState(2); const [dailySpendLimit, setDailySpendLimit] = useState(3.5); const [stopTime, setStopTime] = useState("18:00"); const [saving, setSaving] = useState(false); const [activeCategory, setActiveCategory] = useState("flavour"); const visibleThemes = useMemo(() => { return APP_THEMES.filter((theme) => theme.category === activeCategory); }, [activeCategory]); const activeTheme = useMemo(() => { return APP_THEMES.find((theme) => theme.id === activeThemeId) ?? APP_THEMES[0]; }, [activeThemeId]); const progress = `${(step / STEP_COUNT) * 100}%`; async function handleFinish() { setSaving(true); try { const limits: UserLimits = {}; if (dailyCanLimit !== "none") limits.dailyCanLimit = dailyCanLimit; if (dailySpendLimit !== "none") limits.dailySpendLimit = dailySpendLimit; if (stopTime !== "none") limits.stopTime = stopTime; await onSave(limits, activeThemeId); onClose(); } catch (err) { console.error("Failed to save onboarding preferences", err); } finally { setSaving(false); } } function incrementCans() { if (dailyCanLimit === "none") { setDailyCanLimit(1); return; } if (dailyCanLimit < 10) setDailyCanLimit(Number((dailyCanLimit + 0.5).toFixed(1))); } function decrementCans() { if (dailyCanLimit === "none") return; if (dailyCanLimit <= 0.5) { setDailyCanLimit("none"); return; } setDailyCanLimit(Number((dailyCanLimit - 0.5).toFixed(1))); } function incrementSpend() { if (dailySpendLimit === "none") { setDailySpendLimit(1); return; } if (dailySpendLimit < 30) setDailySpendLimit(Number((dailySpendLimit + 0.5).toFixed(2))); } function decrementSpend() { if (dailySpendLimit === "none") return; if (dailySpendLimit <= 0.5) { setDailySpendLimit("none"); return; } setDailySpendLimit(Number((dailySpendLimit - 0.5).toFixed(2))); } function goNext() { setStep((current) => Math.min(current + 1, STEP_COUNT)); } function goBack() { setStep((current) => Math.max(current - 1, 1)); } return (

Question {step} of {STEP_COUNT}

Red Bull Intake Tracker

{step === 1 && (

Energy setup

Hey {userName || "there"}. Set your baseline.

Six quick screens. Pick a theme, then set light guardrails for cans, spend, and late caffeine.

)} {step === 2 && (

1. Visual style

Choose the mood you want to see every day.

{THEME_CATEGORIES.map((cat) => { const isActive = activeCategory === cat.id; return ( ); })}
{visibleThemes.map((theme) => { const isActive = activeThemeId === theme.id; return ( ); })}
)} {step === 3 && (

2. Daily cans

What is your daily can ceiling?

App warns before logging past this number. You can change it later.

{dailyCanLimit === "none" ? "No cap" : dailyCanLimit}

{dailyCanLimit === "none" ? "Unlimited daily volume" : dailyCanLimit === 1 ? "can per day" : "cans per day"}

{dailyCanLimit === "none" && ( )}
)} {step === 4 && (

3. Daily spend

Set a daily spend line.

Useful for catching small purchases before they stack up.

{dailySpendLimit === "none" ? "No cap" : currency.format(dailySpendLimit)}

{dailySpendLimit === "none" ? "No daily budget" : "maximum per day"}

{dailySpendLimit === "none" && ( )}
)} {step === 5 && (

4. Caffeine curfew

When should late caffeine stop?

Choose when the app should warn you that sleep may take the hit.

{curfewOptions.map((timeOption) => { const isSelected = stopTime === timeOption.id; return ( ); })}
)} {step === 6 && (

Ready

This is your tracking profile.

Theme {activeTheme.label}
Daily cans {dailyCanLimit === "none" ? "No cap" : `${dailyCanLimit} ${dailyCanLimit === 1 ? "can" : "cans"}`}
Daily spend {dailySpendLimit === "none" ? "No cap" : currency.format(dailySpendLimit)}
Caffeine curfew {stopTime === "none" ? "No curfew" : stopTime}
)}
); }