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>
123 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
}
|