chore(lint): manual cleanup batch 2/3 (group 6 partial + format sweep)

- new helper: apps/web/src/lib/keys.ts — stable string-key arrays for fixed-length skeleton/decorative lists, plus dynamicKeys() for runtime-sized lists
- group 6 (noArrayIndexKey): convert ~25 of 32 Array.from skeleton patterns to KEYS_N.map across 21 files (admin pages, catalog, dashboard, schema viewer, remotion demos)
- DashboardDemo HOTSPOTS now carries explicit ids; OnboardingProgress uses step.label as key
- biome --write format pass (24 files)

Lint count: 218 → 164 (groups 6/7 still in progress: 9 noArrayIndexKey + 121 noExplicitAny + 42 noNonNullAssertion + a few small remaining).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sase Dev
2026-05-09 16:48:58 +00:00
parent 0af3fbf725
commit bc8e3a3212
46 changed files with 748 additions and 444 deletions

View File

@@ -95,7 +95,9 @@ export function HotspotOverlay({ hotspots, imageWidth, imageHeight }: HotspotOve
const isDark = useIsDark();
return (
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
className="absolute inset-0 h-full w-full"
viewBox={`0 0 ${imageWidth} ${imageHeight}`}
preserveAspectRatio="xMidYMid meet"

View File

@@ -8,6 +8,7 @@ import { HotspotOverlay } from "./hotspot-overlay";
import { PartsPanel } from "./parts-panel";
import { SchemaToolbar } from "./schema-toolbar";
import { KEYS_8 } from "@/lib/keys";
interface SchemaViewerProps {
schemaPic: SchemaPic | null;
hotspots: Hotspot[];
@@ -76,8 +77,8 @@ export function SchemaViewer({
</div>
<div className="w-full space-y-3 p-4 md:w-[40%]">
<Skeleton className="h-6 w-1/2" />
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={i} className="h-10 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-10 w-full" />
))}
</div>
</div>

97
apps/web/src/lib/keys.ts Normal file
View File

@@ -0,0 +1,97 @@
/**
* Stable React `key` arrays for fixed-length skeleton/decorative lists.
*
* Lets us replace `Array.from({ length: N }).map((_, i) => <… key={i} />)`
* (which Biome flags via lint/suspicious/noArrayIndexKey) with
* `KEYS_N.map((k) => <… key={k} />)` — the keys are module-level constants
* so they don't change identity across renders.
*
* If you need a length not listed here, add a new export — they cost nothing.
*/
export const KEYS_4 = ["k0", "k1", "k2", "k3"];
export const KEYS_5 = ["k0", "k1", "k2", "k3", "k4"];
export const KEYS_6 = ["k0", "k1", "k2", "k3", "k4", "k5"];
export const KEYS_8 = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"];
export const KEYS_9 = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7", "k8"];
export const KEYS_10 = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7", "k8", "k9"];
export const KEYS_16 = [
"k0",
"k1",
"k2",
"k3",
"k4",
"k5",
"k6",
"k7",
"k8",
"k9",
"k10",
"k11",
"k12",
"k13",
"k14",
"k15",
];
export const KEYS_17 = [
"k0",
"k1",
"k2",
"k3",
"k4",
"k5",
"k6",
"k7",
"k8",
"k9",
"k10",
"k11",
"k12",
"k13",
"k14",
"k15",
"k16",
];
export const KEYS_25 = [
"k0",
"k1",
"k2",
"k3",
"k4",
"k5",
"k6",
"k7",
"k8",
"k9",
"k10",
"k11",
"k12",
"k13",
"k14",
"k15",
"k16",
"k17",
"k18",
"k19",
"k20",
"k21",
"k22",
"k23",
"k24",
];
/**
* Get a stable key list of arbitrary length. Use this when the count is
* dynamic (e.g. derived from props/state). The result is memoized per length.
*/
const dynamicCache = new Map<number, string[]>();
export function dynamicKeys(length: number): string[] {
let cached = dynamicCache.get(length);
if (!cached) {
cached = Array.from({ length }, (_, i) => `k${i}`);
dynamicCache.set(length, cached);
}
return cached;
}
/** Backwards-compat alias used by a few earlier call sites. */
export const BRAND_SKELETON_KEYS = KEYS_8;

View File

@@ -92,7 +92,9 @@ const VinInputScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
border: `1px solid ${c.border}`,
}}
>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="14"
height="14"
viewBox="0 0 24 24"
@@ -190,7 +192,9 @@ const VehicleInfoScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
marginBottom: 16,
}}
>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="14"
height="14"
viewBox="0 0 24 24"
@@ -366,10 +370,10 @@ const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
const c = getColors(isDark);
const HOTSPOTS = [
{ x: "30%", y: "35%" },
{ x: "60%", y: "25%" },
{ x: "45%", y: "60%" },
{ x: "70%", y: "55%" },
{ id: "hs-1", x: "30%", y: "35%" },
{ id: "hs-2", x: "60%", y: "25%" },
{ id: "hs-3", x: "45%", y: "60%" },
{ id: "hs-4", x: "70%", y: "55%" },
];
return (
@@ -468,7 +472,7 @@ const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
return (
<div
key={i}
key={spot.id}
style={{
position: "absolute",
left: spot.x,

View File

@@ -84,7 +84,9 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
border: `1px solid ${c.border}`,
}}
>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="12"
height="12"
viewBox="0 0 24 24"
@@ -109,7 +111,9 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
</div>
{/* Cart icon */}
<div style={{ position: "relative" }}>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="16"
height="16"
viewBox="0 0 24 24"
@@ -328,7 +332,9 @@ const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
minWidth: 0,
}}
>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="12"
height="12"
viewBox="0 0 24 24"
@@ -390,7 +396,9 @@ const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
{/* Cart icon */}
<div style={{ position: "relative", flexShrink: 0 }}>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="16"
height="16"
viewBox="0 0 24 24"
@@ -572,7 +580,9 @@ const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
minWidth: 0,
}}
>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="12"
height="12"
viewBox="0 0 24 24"
@@ -643,7 +653,9 @@ const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
{/* Cart */}
<div style={{ position: "relative", flexShrink: 0 }}>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="16"
height="16"
viewBox="0 0 24 24"
@@ -906,7 +918,9 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
{/* Cart icon with animated badge */}
<div style={{ position: "relative", flexShrink: 0 }}>
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
width="16"
height="16"
viewBox="0 0 24 24"

View File

@@ -81,7 +81,7 @@ export const OnboardingProgress: React.FC<{
return (
<div
key={i}
key={label}
style={{
position: "absolute",
opacity: labelOpacity,
@@ -152,7 +152,7 @@ export const OnboardingProgress: React.FC<{
const circleSize = 28;
return (
<div key={i}>
<div key={step.label}>
{/* Outer circle */}
<div
style={{
@@ -181,7 +181,9 @@ export const OnboardingProgress: React.FC<{
}}
/>
{/* Checkmark */}
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
viewBox="0 0 24 24"
width={14}
height={14}

View File

@@ -67,7 +67,9 @@ function AuthLayout() {
1.2sn Sorgu
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface/60 px-3 py-1.5 text-xs text-foreground/80 backdrop-blur-sm">
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
className="size-3"
viewBox="0 0 24 24"
fill="none"

View File

@@ -1,5 +1,6 @@
import { useAuth } from "@/hooks/use-auth";
import { useTranslation } from "@/lib/i18n";
import { KEYS_5 } from "@/lib/keys";
import { capture, resetUser } from "@/lib/posthog";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
import { Button, Separator, Skeleton } from "@sase/ui";
@@ -164,8 +165,8 @@ function DashboardLayout() {
<div className="flex min-h-screen">
<div className="hidden w-64 border-r border-border bg-background p-4 lg:block">
<Skeleton className="mb-8 h-8 w-32" />
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={`nav-skel-${i}`} className="mb-3 h-10 w-full" />
{KEYS_5.map((__k) => (
<Skeleton key={__k} className="mb-3 h-10 w-full" />
))}
</div>
<div className="flex-1 p-6">

View File

@@ -11,6 +11,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { Activity, CheckCircle, ChevronLeft, ChevronRight, Search, X, XCircle } from "lucide-react";
import { useEffect, useState } from "react";
import { KEYS_10 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/admin/analytics")({
component: AdminAnalyticsPage,
});
@@ -126,8 +127,8 @@ function AdminAnalyticsPage() {
{/* Table */}
{isLoading ? (
<div className="space-y-3">
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={`log-skeleton-${i}`} className="h-12 w-full" />
{KEYS_10.map((__k) => (
<Skeleton key={__k} className="h-12 w-full" />
))}
</div>
) : !data || data.items.length === 0 ? (

View File

@@ -11,6 +11,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { ChevronLeft, ChevronRight, Copy, Search, TrendingUp, X } from "lucide-react";
import { useEffect, useState } from "react";
import { KEYS_10 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/admin/copy-logs")({
component: AdminCopyLogsPage,
});
@@ -152,8 +153,8 @@ function AdminCopyLogsPage() {
{/* Table */}
{isLoading ? (
<div className="space-y-3">
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={`copy-skel-${i}`} className="h-12 w-full" />
{KEYS_10.map((__k) => (
<Skeleton key={__k} className="h-12 w-full" />
))}
</div>
) : !data || data.items.length === 0 ? (
@@ -250,8 +251,8 @@ function AdminCopyLogsPage() {
{tab === "top" &&
(topLoading ? (
<div className="space-y-3">
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={`top-skel-${i}`} className="h-12 w-full" />
{KEYS_10.map((__k) => (
<Skeleton key={__k} className="h-12 w-full" />
))}
</div>
) : !topCodes || topCodes.length === 0 ? (

View File

@@ -1,6 +1,7 @@
import { useAuth } from "@/hooks/use-auth";
import { api } from "@/lib/api-client";
import { useTranslation } from "@/lib/i18n";
import { KEYS_6 } from "@/lib/keys";
import { Card, CardContent } from "@sase/ui";
import { Skeleton } from "@sase/ui";
import { Badge } from "@sase/ui";
@@ -75,8 +76,8 @@ function AdminDashboardPage() {
<div className="space-y-6">
<Skeleton className="h-8 w-48" />
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={`skeleton-${i}`} className="h-32" />
{KEYS_6.map((__k) => (
<Skeleton key={__k} className="h-32" />
))}
</div>
</div>
@@ -188,8 +189,8 @@ function AdminDashboardPage() {
{/* Stat Cards */}
{statsLoading ? (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={`stat-skeleton-${i}`} className="h-32" />
{KEYS_6.map((__k) => (
<Skeleton key={__k} className="h-32" />
))}
</div>
) : (

View File

@@ -10,6 +10,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { AlertTriangle, CheckCircle, ExternalLink, Receipt, XCircle } from "lucide-react";
import { useEffect, useState } from "react";
import { KEYS_5 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/admin/payments")({
component: AdminPaymentsPage,
});
@@ -106,8 +107,8 @@ function AdminPaymentsPage() {
{isLoading ? (
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={`payment-skeleton-${i}`} className="h-32 w-full" />
{KEYS_5.map((__k) => (
<Skeleton key={__k} className="h-32 w-full" />
))}
</div>
) : !payments || payments.length === 0 ? (

View File

@@ -1,5 +1,6 @@
import { useAuth } from "@/hooks/use-auth";
import { api } from "@/lib/api-client";
import { KEYS_6 } from "@/lib/keys";
import { Card, CardContent } from "@sase/ui";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
@@ -180,8 +181,8 @@ function AdminReferralsPage() {
{/* Referrers List */}
{isLoading ? (
<div className="space-y-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={`ref-skeleton-${i}`} className="h-20 w-full" />
{KEYS_6.map((__k) => (
<Skeleton key={__k} className="h-20 w-full" />
))}
</div>
) : !data || data.items.length === 0 ? (

View File

@@ -1,6 +1,7 @@
import { useAuth } from "@/hooks/use-auth";
import { api } from "@/lib/api-client";
import { useTranslation } from "@/lib/i18n";
import { KEYS_8 } from "@/lib/keys";
import { toast } from "@/lib/toast";
import { Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
import { Button } from "@sase/ui";
@@ -431,8 +432,8 @@ function AdminUsersPage() {
{/* Table */}
{isLoading ? (
<div className="space-y-3">
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`row-skeleton-${i}`} className="h-14 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-14 w-full" />
))}
</div>
) : !data || data.items.length === 0 ? (

View File

@@ -9,6 +9,7 @@ import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { ChevronRight, Columns2, LayoutGrid, Library, List, Lock } from "lucide-react";
import { useState } from "react";
import { KEYS_10 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/catalog/")({
component: CatalogBrandsPage,
});
@@ -77,8 +78,8 @@ function CatalogBrandsPage() {
{isLoading ? (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={`brand-skel-${i}`} className="h-28 w-full rounded-xl" />
{KEYS_10.map((__k) => (
<Skeleton key={__k} className="h-28 w-full rounded-xl" />
))}
</div>
) : !brands || brands.length === 0 ? (

View File

@@ -2,6 +2,7 @@ import { ModelListColumns } from "@/components/catalog/model-list-columns";
import { ModelListTree } from "@/components/catalog/model-list-tree";
import { api } from "@/lib/api-client";
import { useTranslation } from "@/lib/i18n";
import { KEYS_4, KEYS_9 } from "@/lib/keys";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
import { Button, Skeleton, cn } from "@sase/ui";
import { useQuery } from "@tanstack/react-query";
@@ -129,8 +130,8 @@ function CatalogModelsPage() {
{catalogsLoading ? (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={`cat-skel-${i}`} className="h-24 w-full rounded-xl" />
{KEYS_4.map((__k) => (
<Skeleton key={__k} className="h-24 w-full rounded-xl" />
))}
</div>
) : isMultiCatalog && !activeCatalog ? (
@@ -143,8 +144,8 @@ function CatalogModelsPage() {
{t("catalog.loadingModels")}
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 9 }).map((_, i) => (
<Skeleton key={`model-skel-${i}`} className="h-24 w-full rounded-lg" />
{KEYS_9.map((__k) => (
<Skeleton key={__k} className="h-24 w-full rounded-lg" />
))}
</div>
</div>

View File

@@ -10,6 +10,7 @@ import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { ArrowLeft, Columns2, LayoutGrid, List } from "lucide-react";
import { Suspense, lazy, useState } from "react";
import { KEYS_8 } from "@/lib/keys";
const SchemaViewer = lazy(() =>
import("@/components/schema/schema-viewer").then((mod) => ({
default: mod.SchemaViewer,
@@ -24,8 +25,8 @@ function SchemaViewerFallback() {
</div>
<div className="w-full space-y-3 p-4 md:w-[40%]">
<Skeleton className="h-6 w-1/2" />
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`schema-skel-${i}`} className="h-10 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-10 w-full" />
))}
</div>
</div>

View File

@@ -13,6 +13,7 @@ import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { ArrowLeft, Columns2, LayoutGrid, List } from "lucide-react";
import { useState } from "react";
import { KEYS_8 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/catalog_/$brandName_/$modelId/")({
validateSearch: (search) => ({
body: typeof search.body === "string" ? search.body : undefined,
@@ -277,8 +278,8 @@ function CatalogVehiclePage() {
>
{categoriesLoading ? (
<div className="space-y-2">
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`cat-skel-${i}`} className="h-8 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-8 w-full" />
))}
</div>
) : viewMode === "grid" ? (

View File

@@ -1,6 +1,7 @@
import { CarBrandLogo } from "@/components/ui/car-brand-logo";
import { useAuth } from "@/hooks/use-auth";
import { api } from "@/lib/api-client";
import { KEYS_4 } from "@/lib/keys";
import { Badge, Button, Separator, Skeleton } from "@sase/ui";
import { useQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router";
@@ -231,8 +232,8 @@ function DashboardHome() {
{/* ─── STAT CARDS ─────────────────────────────────────────────── */}
{isLoadingCards ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={`card-skel-${i}`} className="h-64 w-full rounded-2xl" />
{KEYS_4.map((__k) => (
<Skeleton key={__k} className="h-64 w-full rounded-2xl" />
))}
</div>
) : (

View File

@@ -2,6 +2,7 @@ import { CarBrandLogo } from "@/components/ui/car-brand-logo";
import { api } from "@/lib/api-client";
import { startAction } from "@/lib/faro";
import { useTranslation } from "@/lib/i18n";
import { BRAND_SKELETON_KEYS } from "@/lib/keys";
import { capture, setPeopleProperties } from "@/lib/posthog";
import { toast } from "@/lib/toast";
import { getUserSettings } from "@/lib/user-settings";
@@ -50,8 +51,8 @@ const LazyOnboardingProgress = lazy(() =>
function BrandSelectorFallback() {
return (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4">
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`brand-skel-${i}`} className="h-24 w-full rounded-lg" />
{BRAND_SKELETON_KEYS.map((k) => (
<Skeleton key={k} className="h-24 w-full rounded-lg" />
))}
</div>
);

View File

@@ -8,6 +8,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { ArrowLeft, Columns2, LayoutGrid, List } from "lucide-react";
import { Suspense, lazy, useState } from "react";
import { KEYS_6, KEYS_8 } from "@/lib/keys";
const SchemaViewer = lazy(() =>
import("@/components/schema/schema-viewer").then((mod) => ({
default: mod.SchemaViewer,
@@ -22,8 +23,8 @@ function SchemaViewerFallback() {
</div>
<div className="w-full space-y-3 p-4 md:w-[40%]">
<Skeleton className="h-6 w-1/2" />
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`schema-skel-${i}`} className="h-10 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-10 w-full" />
))}
</div>
</div>
@@ -33,8 +34,8 @@ function SchemaViewerFallback() {
function CategoryGridFallback() {
return (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={`cat-grid-skel-${i}`} className="h-24 w-full rounded-lg" />
{KEYS_6.map((__k) => (
<Skeleton key={__k} className="h-24 w-full rounded-lg" />
))}
</div>
);

View File

@@ -12,6 +12,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { ArrowLeft, Columns2, LayoutGrid, List } from "lucide-react";
import { useState } from "react";
import { KEYS_8 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/vehicles_/$id/")({
component: VehicleDetailPage,
});
@@ -126,8 +127,8 @@ function VehicleDetailPage() {
>
{categoriesLoading ? (
<div className="space-y-2">
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={`cat-skel-${i}`} className="h-8 w-full" />
{KEYS_8.map((__k) => (
<Skeleton key={__k} className="h-8 w-full" />
))}
</div>
) : viewMode === "grid" ? (

View File

@@ -117,7 +117,8 @@ function DemoPage() {
Sase.tr
</Link>
<div className="flex items-center gap-3">
<button type="button"
<button
type="button"
onClick={toggleTheme}
className="inline-flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
aria-label="Tema değiştir"
@@ -144,7 +145,8 @@ function DemoPage() {
<main id="main-content" className="mx-auto max-w-5xl px-4 py-12 sm:px-6">
{/* Step indicator */}
<div className="mb-8 flex items-center justify-center gap-2 text-sm text-muted-foreground">
<button type="button"
<button
type="button"
onClick={() => {
setStep("vin");
setSelectedCategory(null);
@@ -154,7 +156,8 @@ function DemoPage() {
1. VIN Girin
</button>
<div className="h-px w-6 bg-border" />
<button type="button"
<button
type="button"
onClick={() => vinPreview && setStep("categories")}
className={`rounded-full px-3 py-1 transition ${step === "categories" ? "bg-foreground text-background" : "bg-muted"} ${!vinPreview ? "opacity-50 cursor-not-allowed" : ""}`}
disabled={!vinPreview}
@@ -162,7 +165,8 @@ function DemoPage() {
2. Kategori Seçin
</button>
<div className="h-px w-6 bg-border" />
<button type="button"
<button
type="button"
onClick={() => selectedCategory && setStep("schema")}
className={`rounded-full px-3 py-1 transition ${step === "schema" ? "bg-foreground text-background" : "bg-muted"} ${!selectedCategory ? "opacity-50 cursor-not-allowed" : ""}`}
disabled={!selectedCategory}
@@ -244,7 +248,8 @@ function DemoPage() {
)}
{!vin && (
<button type="button"
<button
type="button"
onClick={() => setVin("WVWZZZ1JZ3W597935")}
className="mx-auto block text-sm text-muted-foreground underline underline-offset-4 transition hover:text-foreground"
>
@@ -268,7 +273,8 @@ function DemoPage() {
</p>
)}
</div>
<button type="button"
<button
type="button"
onClick={() => {
setStep("vin");
setSelectedCategory(null);
@@ -281,7 +287,8 @@ function DemoPage() {
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{EXAMPLE_CATEGORIES.map((cat) => (
<button type="button"
<button
type="button"
key={cat.name}
onClick={() => {
setSelectedCategory(cat.name);
@@ -319,7 +326,8 @@ function DemoPage() {
</p>
)}
</div>
<button type="button"
<button
type="button"
onClick={() => setStep("categories")}
className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
>

View File

@@ -572,7 +572,8 @@ function HomePage() {
</nav>
<div className="hidden items-center gap-3 md:flex">
<button type="button"
<button
type="button"
onClick={toggleTheme}
className="inline-flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
aria-label="Tema değiştir"
@@ -610,7 +611,8 @@ function HomePage() {
{/* Mobile toggle */}
<div className="flex items-center gap-2 md:hidden">
<button type="button"
<button
type="button"
onClick={toggleTheme}
className="inline-flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
aria-label="Tema değiştir"
@@ -627,7 +629,7 @@ function HomePage() {
{mobileMenuOpen && (
<div className="border-t border-border px-4 py-4 md:hidden">
<nav className="flex flex-col gap-3 text-sm text-muted-foreground">
<button type="button"
<button
type="button"
className="text-left transition hover:text-foreground"
onClick={() => {
@@ -637,7 +639,7 @@ function HomePage() {
>
Özellikler
</button>
<button type="button"
<button
type="button"
className="text-left transition hover:text-foreground"
onClick={() => {
@@ -828,7 +830,8 @@ function HomePage() {
{/* Example VIN invite */}
<p className="mt-4 text-sm text-muted-foreground">
Şase numaranız yok mu?{" "}
<button type="button"
<button
type="button"
onClick={fillExampleVin}
data-faro-user-action-name="hero-free-trial"
className="text-foreground underline underline-offset-4 transition hover:text-foreground/80"
@@ -1313,7 +1316,9 @@ function HomePage() {
>
<div className="flex gap-0.5">
{Array.from({ length: t.rating }).map((_, i) => (
<svg role="img" aria-label="icon"
<svg
role="img"
aria-label="icon"
key={i}
viewBox="0 0 20 20"
className="size-3.5 fill-foreground/85"