From 115766cf60191545c5f686e62c92a6f05dcd59cc Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 12 Apr 2026 20:48:41 +0200 Subject: [PATCH] feat(30-03): replace OnboardingWizard with catalog-driven OnboardingFlow Swap old 4-step modal wizard with new full-screen, hobby-personalized onboarding experience. Delete OnboardingWizard.tsx. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/client/components/OnboardingWizard.tsx | 319 --------------------- src/client/routes/__root.tsx | 6 +- 2 files changed, 3 insertions(+), 322 deletions(-) delete mode 100644 src/client/components/OnboardingWizard.tsx diff --git a/src/client/components/OnboardingWizard.tsx b/src/client/components/OnboardingWizard.tsx deleted file mode 100644 index a830723..0000000 --- a/src/client/components/OnboardingWizard.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import { useState } from "react"; -import { useCreateCategory } from "../hooks/useCategories"; -import { useCreateItem } from "../hooks/useItems"; -import { useUpdateSetting } from "../hooks/useSettings"; -import { LucideIcon } from "../lib/iconData"; -import { IconPicker } from "./IconPicker"; - -interface OnboardingWizardProps { - onComplete: () => void; -} - -export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { - const [step, setStep] = useState(1); - - // Step 2 state - const [categoryName, setCategoryName] = useState(""); - const [categoryIcon, setCategoryIcon] = useState(""); - const [categoryError, setCategoryError] = useState(""); - const [createdCategoryId, setCreatedCategoryId] = useState( - null, - ); - - // Step 3 state - const [itemName, setItemName] = useState(""); - const [itemWeight, setItemWeight] = useState(""); - const [itemPrice, setItemPrice] = useState(""); - const [itemError, setItemError] = useState(""); - - const createCategory = useCreateCategory(); - const createItem = useCreateItem(); - const updateSetting = useUpdateSetting(); - - function handleSkip() { - updateSetting.mutate( - { key: "onboardingComplete", value: "true" }, - { onSuccess: onComplete }, - ); - } - - function handleCreateCategory() { - const name = categoryName.trim(); - if (!name) { - setCategoryError("Please enter a category name"); - return; - } - setCategoryError(""); - createCategory.mutate( - { name, icon: categoryIcon.trim() || undefined }, - { - onSuccess: (created) => { - setCreatedCategoryId(created.id); - setStep(3); - }, - onError: (err) => { - setCategoryError(err.message || "Failed to create category"); - }, - }, - ); - } - - function handleCreateItem() { - const name = itemName.trim(); - if (!name) { - setItemError("Please enter an item name"); - return; - } - if (!createdCategoryId) return; - - setItemError(""); - const payload: any = { - name, - categoryId: createdCategoryId, - }; - if (itemWeight) payload.weightGrams = Number(itemWeight); - if (itemPrice) payload.priceCents = Math.round(Number(itemPrice) * 100); - - createItem.mutate(payload, { - onSuccess: () => setStep(4), - onError: (err) => { - setItemError(err.message || "Failed to add item"); - }, - }); - } - - function handleDone() { - updateSetting.mutate( - { key: "onboardingComplete", value: "true" }, - { onSuccess: onComplete }, - ); - } - - return ( -
- {/* Backdrop */} -
- - {/* Card */} -
- {/* Step indicator */} -
- {[1, 2, 3].map((s) => ( -
- ))} -
- - {/* Step 1: Welcome */} - {step === 1 && ( -
-

- Welcome to GearBox! -

-

- Track your gear, compare weights, and plan smarter purchases. - Let's set up your first category and item. -

- - -
- )} - - {/* Step 2: Create category */} - {step === 2 && ( -
-

- Create a category -

-

- Categories help you organize your gear (e.g. Shelter, Cooking, - Clothing). -

- -
-
- - setCategoryName(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" - placeholder="e.g. Shelter" - /> -
- -
- - -
- - {categoryError && ( -

{categoryError}

- )} -
- - - -
- )} - - {/* Step 3: Add item */} - {step === 3 && ( -
-

- Add your first item -

-

- Add a piece of gear to your collection. -

- -
-
- - setItemName(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" - placeholder="e.g. Big Agnes Copper Spur" - /> -
- -
-
- - setItemWeight(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" - placeholder="e.g. 1200" - /> -
-
- - setItemPrice(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" - placeholder="e.g. 349.99" - /> -
-
- - {itemError &&

{itemError}

} -
- - - -
- )} - - {/* Step 4: Done */} - {step === 4 && ( -
-
- -
-

- You're all set! -

-

- Your first item has been added. You can now browse your - collection, add more gear, and track your setup. -

- -
- )} -
-
- ); -} diff --git a/src/client/routes/__root.tsx b/src/client/routes/__root.tsx index 97a8ef7..3479ee5 100644 --- a/src/client/routes/__root.tsx +++ b/src/client/routes/__root.tsx @@ -18,7 +18,7 @@ import { CatalogSearchOverlay } from "../components/CatalogSearchOverlay"; import { ConfirmDialog } from "../components/ConfirmDialog"; import { ExternalLinkDialog } from "../components/ExternalLinkDialog"; import { FabMenu } from "../components/FabMenu"; -import { OnboardingWizard } from "../components/OnboardingWizard"; +import { OnboardingFlow } from "../components/onboarding/OnboardingFlow"; import { TopNav } from "../components/TopNav"; import { useAuth } from "../hooks/useAuth"; import { useDeleteCandidate } from "../hooks/useCandidates"; @@ -188,9 +188,9 @@ function RootLayout() { {/* Auth Prompt Modal */} - {/* Onboarding Wizard */} + {/* Onboarding Flow */} {showWizard && ( - setWizardDismissed(true)} /> + setWizardDismissed(true)} /> )}
);