Implements 5-step onboarding: Welcome, Hobby Picker, Item Browser, Review, and Done. Includes hobby card selection, popular item grid with check/uncheck, review list with remove, CSS step transitions, and responsive grid layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
3.5 KiB
TypeScript
129 lines
3.5 KiB
TypeScript
import { LucideIcon } from "../../lib/iconData";
|
|
|
|
interface ReviewItem {
|
|
id: number;
|
|
brand: string | null;
|
|
model: string;
|
|
imageUrl: string | null;
|
|
category: string | null;
|
|
}
|
|
|
|
interface OnboardingReviewProps {
|
|
items: ReviewItem[];
|
|
onRemoveItem: (itemId: number) => void;
|
|
onConfirm: () => void;
|
|
onSkip: () => void;
|
|
isSubmitting: boolean;
|
|
}
|
|
|
|
export function OnboardingReview({
|
|
items,
|
|
onRemoveItem,
|
|
onConfirm,
|
|
onSkip,
|
|
isSubmitting,
|
|
}: OnboardingReviewProps) {
|
|
// Group by category
|
|
const grouped = new Map<string, ReviewItem[]>();
|
|
for (const item of items) {
|
|
const cat = item.category || "Uncategorized";
|
|
if (!grouped.has(cat)) grouped.set(cat, []);
|
|
grouped.get(cat)!.push(item);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-center min-h-screen px-8">
|
|
<div className="max-w-2xl w-full text-center">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
Your starting collection
|
|
</h1>
|
|
<p className="text-base text-gray-500 mb-8">
|
|
{items.length > 0
|
|
? `${items.length} ${items.length === 1 ? "item" : "items"} ready to add`
|
|
: "No items selected — you can always add gear later from the catalog."}
|
|
</p>
|
|
|
|
{items.length > 0 && (
|
|
<div className="text-left mb-8">
|
|
{[...grouped.entries()].map(([category, catItems]) => (
|
|
<div key={category} className="mb-4">
|
|
<div className="text-xs font-medium text-gray-400 uppercase tracking-wide mb-2">
|
|
{category}
|
|
</div>
|
|
{catItems.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="flex items-center gap-3 py-2 border-b border-gray-50"
|
|
>
|
|
<div className="w-10 h-10 rounded-lg overflow-hidden bg-gray-50 shrink-0">
|
|
{item.imageUrl ? (
|
|
<img
|
|
src={item.imageUrl}
|
|
alt={item.model}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center">
|
|
<LucideIcon
|
|
name="package"
|
|
size={16}
|
|
className="text-gray-300"
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm text-gray-900 truncate">
|
|
{item.brand
|
|
? `${item.brand} ${item.model}`
|
|
: item.model}
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => onRemoveItem(item.id)}
|
|
className="text-gray-300 hover:text-red-500 transition-colors shrink-0"
|
|
>
|
|
<LucideIcon name="x" size={16} />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-col items-center gap-3">
|
|
{items.length > 0 ? (
|
|
<button
|
|
type="button"
|
|
onClick={onConfirm}
|
|
disabled={isSubmitting}
|
|
className="px-8 py-3 bg-gray-700 hover:bg-gray-800 disabled:opacity-50 text-white font-medium rounded-lg transition-colors"
|
|
>
|
|
{isSubmitting ? "Adding..." : "Add to my collection"}
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={onSkip}
|
|
className="px-8 py-3 bg-gray-700 hover:bg-gray-800 text-white font-medium rounded-lg transition-colors"
|
|
>
|
|
Continue
|
|
</button>
|
|
)}
|
|
{items.length > 0 && (
|
|
<button
|
|
type="button"
|
|
onClick={onSkip}
|
|
className="text-sm text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
Skip this step
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|