Files
sase.tr/apps/web/src/components/subscription/brand-selector.tsx
Sase Dev 3184e4c619 style: apply biome safe auto-fixes
Run 'biome check --fix' on apps/api/src and apps/web/src to clear
the safe-fixable lint backlog (151 files: parseInt → Number.parseInt,
isNaN → Number.isNaN, organize imports, etc.). 769 errors remain
that require manual changes (mostly noExplicitAny).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:13:56 +00:00

136 lines
4.2 KiB
TypeScript

import { CarBrandLogo } from "@/components/ui/car-brand-logo";
import { api } from "@/lib/api-client";
import { useTranslation } from "@/lib/i18n";
import { Card, CardContent } from "@sase/ui";
import { Badge } from "@sase/ui";
import { Skeleton } from "@sase/ui";
import { cn } from "@sase/ui";
import { useQuery } from "@tanstack/react-query";
import { Check } from "lucide-react";
import { useEffect, useState } from "react";
interface Brand {
id: string;
name: string;
slug: string;
logoUrl?: string;
}
interface BrandSelectorProps {
maxBrands: number;
selectedBrandIds: string[];
onSelectionChange: (brandIds: string[]) => void;
isFullPlan?: boolean;
}
export function BrandSelector({
maxBrands,
selectedBrandIds,
onSelectionChange,
isFullPlan = false,
}: BrandSelectorProps) {
const { t } = useTranslation();
const [selected, setSelected] = useState<string[]>(selectedBrandIds);
const { data: brands, isLoading } = useQuery({
queryKey: ["brands"],
queryFn: () => api.get<Brand[]>("/brands"),
});
useEffect(() => {
setSelected(selectedBrandIds);
}, [selectedBrandIds]);
useEffect(() => {
if (isFullPlan && brands) {
const allIds = brands.map((b) => b.id);
setSelected(allIds);
onSelectionChange(allIds);
}
}, [isFullPlan, brands, onSelectionChange]);
function toggleBrand(brandId: string) {
if (isFullPlan) return;
let next: string[];
if (selected.includes(brandId)) {
next = selected.filter((id) => id !== brandId);
} else {
if (selected.length >= maxBrands) return;
next = [...selected, brandId];
}
setSelected(next);
onSelectionChange(next);
}
if (isLoading) {
return (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4">
{["s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8"].map((id) => (
<Skeleton key={id} className="h-24 w-full rounded-lg" />
))}
</div>
);
}
if (!brands || brands.length === 0) {
return <p className="text-sm text-muted-foreground">{t("common.noData")}</p>;
}
const isMaxReached = !isFullPlan && selected.length >= maxBrands;
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-medium">{t("subscription.selectBrands")}</p>
<Badge variant={isFullPlan ? "default" : "secondary"}>
{isFullPlan
? t("subscription.allBrandsSelected")
: `${selected.length}/${maxBrands} ${t("subscription.brandsSelected")}`}
</Badge>
</div>
{isMaxReached && (
<p className="text-xs text-amber-600">{t("subscription.maxBrandsReached")}</p>
)}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4">
{brands.map((brand) => {
const isSelected = selected.includes(brand.id) || isFullPlan;
const isDisabled = !isSelected && isMaxReached && !isFullPlan;
return (
<Card
key={brand.id}
className={cn(
"cursor-pointer transition-all hover:shadow-md",
isSelected && "border-primary ring-2 ring-primary/20",
isDisabled && "cursor-not-allowed opacity-50",
isFullPlan && "cursor-default",
)}
onClick={() => !isDisabled && toggleBrand(brand.id)}
>
<CardContent className="flex flex-col items-center justify-center p-4">
<div className="relative">
<CarBrandLogo
brandName={brand.name}
logoUrl={brand.logoUrl}
size={48}
className="mb-2"
/>
{isSelected && (
<div className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-primary-foreground">
<Check className="h-3 w-3" />
</div>
)}
</div>
<span className="text-center text-sm font-medium">{brand.name}</span>
</CardContent>
</Card>
);
})}
</div>
</div>
);
}