Files
GearBox/src/client/components/onboarding/OnboardingItemBrowser.tsx
Jean-Luc Makiola 8d7a668da4
All checks were successful
CI / ci (push) Successful in 1m23s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 1m20s
fix: resolve lint errors from phase 32/33/34 execution
Auto-fixed formatting issues and removed unused imports introduced
by background execution agents across currency, i18n, and sharing code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:32:32 +02:00

123 lines
3.7 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { getTagsForHobbies } from "@/shared/hobbyConfig";
import { usePopularItems } from "../../hooks/useOnboarding";
import { SelectableItemCard } from "./SelectableItemCard";
interface OnboardingItemBrowserProps {
selectedHobbies: string[];
selectedItemIds: Set<number>;
onToggleItem: (itemId: number) => void;
onContinue: () => void;
onSkip: () => void;
}
export function OnboardingItemBrowser({
selectedHobbies,
selectedItemIds,
onToggleItem,
onContinue,
onSkip,
}: OnboardingItemBrowserProps) {
const { t } = useTranslation("onboarding");
const tags = getTagsForHobbies(selectedHobbies);
const { data: items, isLoading } = usePopularItems(tags);
const hasItems = items && items.length > 0;
// Group items by category, cap at 5 categories with 4 items each
const MAX_CATEGORIES = 5;
const MAX_PER_CATEGORY = 4;
const allGrouped = hasItems
? items.reduce<Record<string, typeof items>>((acc, item) => {
const cat = item.category || "other";
const label = cat.charAt(0).toUpperCase() + cat.slice(1);
if (!acc[label]) acc[label] = [];
acc[label].push(item);
return acc;
}, {})
: {};
// Take top categories by item count, limit items per category
const categories = Object.keys(allGrouped)
.sort((a, b) => allGrouped[b].length - allGrouped[a].length)
.slice(0, MAX_CATEGORIES);
const grouped = Object.fromEntries(
categories.map((cat) => [cat, allGrouped[cat].slice(0, MAX_PER_CATEGORY)]),
);
return (
<div className="flex flex-col items-center min-h-screen px-8 py-16">
<div className="max-w-5xl w-full text-center">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{selectedHobbies.length === 1
? t("items.title", { hobby: selectedHobbies[0] })
: t("items.titleMultiple")}
</h1>
<p className="text-base text-gray-500 mb-8">{t("items.subtitle")}</p>
{isLoading && (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-2 border-gray-200 border-t-gray-700 rounded-full animate-spin" />
</div>
)}
{!isLoading && !hasItems && (
<div className="py-12 text-center">
<h2 className="text-lg font-semibold text-gray-900 mb-2">
{t("items.noCatalog")}
</h2>
<p className="text-base text-gray-500 mb-8">
{t("items.noCatalogDescription")}
</p>
</div>
)}
{!isLoading &&
hasItems &&
categories.map((cat) => (
<div key={cat} className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 text-left mb-3">
{cat}
</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
{grouped[cat].map((item) => (
<SelectableItemCard
key={item.id}
brand={item.brand}
model={item.model}
imageUrl={item.imageUrl}
weightGrams={item.weightGrams}
priceCents={item.priceCents}
ownerCount={item.ownerCount}
selected={selectedItemIds.has(item.id)}
onClick={() => onToggleItem(item.id)}
/>
))}
</div>
</div>
))}
<div className="flex items-center justify-center gap-4">
{hasItems && selectedItemIds.size > 0 && (
<button
type="button"
onClick={onContinue}
className="px-8 py-3 bg-gray-700 hover:bg-gray-800 text-white font-medium rounded-lg transition-colors"
>
{t("items.reviewCount", { count: selectedItemIds.size })}
</button>
)}
<button
type="button"
onClick={onSkip}
className="text-sm text-gray-400 hover:text-gray-600 transition-colors"
>
{t("common:actions.skipStep")}
</button>
</div>
</div>
</div>
);
}