import { useState } from "react" import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" import { useQueryClient } from "@tanstack/react-query" import { toast } from "sonner" import { useAuth } from "@/hooks/useAuth" import { useWizardState } from "@/hooks/useWizardState" import { useCategories } from "@/hooks/useCategories" import { useTemplate } from "@/hooks/useTemplate" import { supabase } from "@/lib/supabase" import { PRESETS } from "@/data/presets" import type { Profile } from "@/lib/types" import { WizardStepper } from "@/components/setup/WizardStepper" import { IncomeStep } from "@/components/setup/IncomeStep" import { RecurringItemsStep } from "@/components/setup/RecurringItemsStep" import { ReviewStep } from "@/components/setup/ReviewStep" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle, } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" import { useEffect } from "react" export default function SetupPage() { const { t } = useTranslation() const { user, loading: authLoading } = useAuth() const navigate = useNavigate() const queryClient = useQueryClient() const [profile, setProfile] = useState(null) const [profileLoading, setProfileLoading] = useState(true) const [completing, setCompleting] = useState(false) const { categories, create } = useCategories() const { createItem } = useTemplate() useEffect(() => { if (!user) return supabase .from("profiles") .select("*") .eq("id", user.id) .single() .then(({ data }) => { if (data) setProfile(data) setProfileLoading(false) }) }, [user]) const userId = user?.id ?? "" const currency = profile?.currency ?? "EUR" const { state, setStep, setIncome, toggleItem, setItemAmount, clearState } = useWizardState(userId) const [incomeError, setIncomeError] = useState(null) const loading = authLoading || profileLoading if (loading) { return (
) } const stepTitles: Record<1 | 2 | 3, string> = { 1: t("setup.step1.title"), 2: t("setup.step2.title"), 3: t("setup.step3.title"), } const stepDescriptions: Record<1 | 2 | 3, string> = { 1: t("setup.step1.description"), 2: t("setup.step2.description"), 3: t("setup.step3.description"), } function handleNext() { if (state.currentStep === 1) { if (!state.income || state.income <= 0) { setIncomeError(t("setup.step1.validation")) return } setIncomeError(null) setStep(2) } else if (state.currentStep === 2) { setStep(3) } } function handleBack() { if (state.currentStep === 2) setStep(1) else if (state.currentStep === 3) setStep(2) } function handleSkipStep() { if (state.currentStep === 1) setStep(2) else if (state.currentStep === 2) setStep(3) else if (state.currentStep === 3) handleSkipSetup() } function handleStepClick(step: 1 | 2 | 3) { if (step <= state.currentStep) { setStep(step) } } async function handleComplete() { setCompleting(true) try { const checkedItems = PRESETS.filter( (p) => state.selectedItems[p.slug]?.checked ) // 1. Determine unique category types needed const typesNeeded = [...new Set(checkedItems.map((i) => i.type))] // 2. Create one category per type const categoryMap: Record = {} for (const type of typesNeeded) { try { const cat = await create.mutateAsync({ name: t(`categories.types.${type}`), type, }) categoryMap[type] = cat.id } catch (e: any) { // Unique constraint violation (23505) = category already exists, fetch it if (e?.code === "23505" || e?.message?.includes("duplicate")) { const existing = categories.find((c) => c.type === type) if (existing) categoryMap[type] = existing.id } else { throw e } } } // 3. If categories were fetched from cache but not found, refetch if (Object.keys(categoryMap).length < typesNeeded.length) { await queryClient.invalidateQueries({ queryKey: ["categories"] }) } // 4. Create template items for each checked preset let partialFailure = false for (const item of checkedItems) { try { await createItem.mutateAsync({ category_id: categoryMap[item.type], item_tier: item.item_tier, budgeted_amount: state.selectedItems[item.slug].amount, }) } catch { partialFailure = true } } // 5. Mark setup complete await supabase .from("profiles") .update({ setup_completed: true }) .eq("id", user!.id) // 6. Clear wizard state clearState() // 7. Invalidate queries to prevent redirect loop await queryClient.invalidateQueries({ queryKey: ["categories"] }) await queryClient.invalidateQueries({ queryKey: ["template-items"] }) // 8. Toast and redirect if (partialFailure) { toast.error(t("setup.toast.partialError")) } else { toast.success(t("setup.toast.success")) } navigate("/", { replace: true }) } catch { toast.error(t("setup.toast.error")) } finally { setCompleting(false) } } async function handleSkipSetup() { clearState() await supabase .from("profiles") .update({ setup_completed: true }) .eq("id", user!.id) navigate("/", { replace: true }) } return (
{stepTitles[state.currentStep]}

{stepDescriptions[state.currentStep]}

{state.currentStep === 1 && ( )} {state.currentStep === 2 && ( )} {state.currentStep === 3 && ( )} {/* Bottom navigation */}
{state.currentStep > 1 && ( )}
{state.currentStep < 3 && ( )} {state.currentStep === 3 && ( )}
) }