Files
sase.tr/apps/web/src/routes/demo.tsx
Fusion 53a0dfab7d
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
feat(FN-186): update all header/nav logo instances from Sase.tr to SASE with tracking-wider (+2 more)
Commits merged:
- fix(FN-186): fix pre-existing Biome formatting error in vehicles.service.spec.ts
- feat(FN-186): complete Step 2 — update i18n tabSasetr keys from Sase.Tr to SASE
- feat(FN-186): complete Step 1 — update all header/nav logo instances from Sase.tr to SASE with tracking-wider

Files changed:
apps/api/src/vehicles/vehicles.service.spec.ts | 38 ++++++++++++++------------
 apps/web/src/messages/en.json                  |  2 +-
 apps/web/src/messages/tr.json                  |  2 +-
 apps/web/src/routes/_auth.tsx                  |  4 +--
 apps/web/src/routes/about.tsx                  |  4 +--
 apps/web/src/routes/blog.tsx                   |  4 +--
 apps/web/src/routes/blog_/$slug.tsx            |  4 +--
 apps/web/src/routes/contact.tsx                |  4 +--
 apps/web/src/routes/dashboard.tsx              |  6 ++--
 apps/web/src/routes/demo.tsx                   |  4 +--
 apps/web/src/routes/index.tsx                  |  4 +--
 apps/web/src/routes/kvkk.tsx                   |  4 +--
 apps/web/src/routes/pricing.tsx                |  4 +--
 apps/web/src/routes/privacy.tsx                |  4 +--
 apps/web/src/routes/terms.tsx                  |  4 +--
 15 files changed, 47 insertions(+), 45 deletions(-)

Fusion-Task-Id: FN-186
2026-05-11 14:13:41 +00:00

424 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { usePageMeta } from "@/hooks/use-page-meta";
import { KEYS_16, KEYS_17 } from "@/lib/keys";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
import { Button, Input } from "@sase/ui";
import { Link, createFileRoute } from "@tanstack/react-router";
import {
ArrowRight,
Car,
FolderTree,
Loader2,
Lock,
Moon,
MousePointerClick,
Search,
Sun,
} from "lucide-react";
import { useEffect, useState } from "react";
export const Route = createFileRoute("/demo")({
component: DemoPage,
});
const EXAMPLE_CATEGORIES = [
{ name: "Motor", subcategories: ["Silindir Kapağı", "Krank Mili", "Piston", "Yağ Pompası"] },
{ name: "Şasi & Süspansiyon", subcategories: ["Amortisör", "Salıncak", "Rotil", "Viraj Demiri"] },
{ name: "Elektrik", subcategories: ["Alternatör", "Marş Motoru", "Kablo Tesisatı", "Sensörler"] },
{ name: "Karoseri", subcategories: ["Kapı Paneli", "Tampon", "Ayna", "Far"] },
{ name: "Klima & Isıtma", subcategories: ["Kompresör", "Kalorifer", "Radyatör", "Fan Motoru"] },
];
const EXAMPLE_SCHEMA_PARTS = [
{ code: "1J0 820 803F", name: "Klima Kompresörü", position: "A1" },
{ code: "1J0 819 031A", name: "Kalorifer Motoru", position: "B3" },
{ code: "1J0 698 151G", name: "Ön Fren Balatası", position: "C2" },
{ code: "1J0 407 271J", name: "Alt Salıncak", position: "D1" },
];
function DemoPage() {
usePageMeta({
title: "Demo — Sase.tr | Şase Sorgulamayı Deneyin",
description: "Ücretsiz demo ile şase numarası sorgulama ve OEM parça kataloğunu keşfedin.",
canonical: "https://sase.tr/demo",
});
const [vin, setVin] = useState("");
const [vinPreview, setVinPreview] = useState<{
make: string;
model: string;
year: string;
engine: string;
} | null>(null);
const [vinLoading, setVinLoading] = useState(false);
const [vinError, setVinError] = useState(false);
const [step, setStep] = useState<"vin" | "categories" | "schema">("vin");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [isDark, setIsDark] = useState(() => {
const theme = getUserSettings().theme ?? "dark";
if (theme === "system") {
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}
return theme === "dark";
});
const toggleTheme = () => {
const next = isDark ? "light" : "dark";
document.documentElement.classList.toggle("dark", next === "dark");
setUserSetting("theme", next);
setIsDark(next === "dark");
};
// NHTSA VIN decode
useEffect(() => {
if (vin.length !== 17) {
setVinPreview(null);
setVinError(false);
return;
}
const controller = new AbortController();
setVinLoading(true);
setVinError(false);
fetch(`/api/vehicles/preview/${vin}`, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("Bulunamadı");
return res.json();
})
.then((data) => {
const r = data.data !== undefined ? data.data : data;
if (r?.brandName) {
setVinPreview({
make: r.brandName,
model: r.model || "—",
year: r.year ? String(r.year) : "—",
engine: r.engine || "—",
});
} else {
setVinError(true);
}
setVinLoading(false);
})
.catch((err) => {
if (err.name !== "AbortError") {
setVinError(true);
setVinLoading(false);
}
});
return () => controller.abort();
}, [vin]);
return (
<div className="min-h-screen bg-background text-foreground">
{/* Header */}
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
<div className="mx-auto flex h-16 max-w-5xl items-center justify-between px-4 sm:px-6">
<Link to="/" className="text-xl font-bold tracking-wider">
SASE
</Link>
<div className="flex items-center gap-3">
<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"
>
{isDark ? <Sun className="size-4" /> : <Moon className="size-4" />}
</button>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground">
<span className="size-1.5 rounded-full bg-brand" />
Demo
</span>
<Link to="/register">
<Button
size="sm"
className="rounded-full bg-foreground text-background hover:bg-foreground/90"
>
Tam Erişim
<ArrowRight className="ml-1.5 size-3.5" />
</Button>
</Link>
</div>
</div>
</header>
<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"
onClick={() => {
setStep("vin");
setSelectedCategory(null);
}}
className={`rounded-full px-3 py-1 transition ${step === "vin" ? "bg-foreground text-background" : "bg-muted"}`}
>
1. VIN Girin
</button>
<div className="h-px w-6 bg-border" />
<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}
>
2. Kategori Seçin
</button>
<div className="h-px w-6 bg-border" />
<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}
>
3. Şema & Parçalar
</button>
</div>
{/* Step 1: VIN Input */}
{step === "vin" && (
<div className="mx-auto max-w-xl space-y-6">
<div className="text-center">
<h1 className="font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight sm:text-4xl">
VIN ile Araç Tanımlama
</h1>
<p className="mt-3 text-muted-foreground">
17 haneli VIN numaranızı girin, aracınızı tanıyalım.
</p>
</div>
<div className="relative">
<Search className="absolute left-4 top-1/2 size-5 -translate-y-1/2 text-muted-foreground" />
<Input
value={vin}
onChange={(e) => setVin(e.target.value.toUpperCase())}
placeholder="Örnek: WVWZZZ1JZ3W597935"
maxLength={17}
className="h-14 rounded-2xl border-border bg-muted pl-12 pr-4 font-mono text-foreground placeholder:text-muted-foreground/70 focus-visible:ring-ring"
/>
</div>
{/* Progress bar */}
<div className="flex gap-0.5">
{KEYS_17.map((k, i) => (
<div
key={k}
className={`h-1 flex-1 rounded-full transition-colors duration-200 ${
i < vin.length ? "bg-brand" : "bg-border"
}`}
/>
))}
</div>
{vinLoading && (
<div className="flex items-center justify-center gap-2 rounded-2xl border border-border bg-surface p-4">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
<span className="text-sm text-muted-foreground">Araç bilgileri alınıyor...</span>
</div>
)}
{vinPreview && !vinLoading && (
<div className="animate-fade-in-up rounded-2xl border border-brand/30 bg-surface p-6">
<div className="flex items-center gap-3">
<Car className="size-6 text-brand" />
<div>
<p className="font-semibold text-foreground">
{vinPreview.make} {vinPreview.model}
</p>
<p className="text-sm text-muted-foreground">
{vinPreview.year} {vinPreview.engine !== "—" ? `${vinPreview.engine}` : ""}
</p>
</div>
</div>
<Button
onClick={() => setStep("categories")}
variant="brand"
className="mt-4 w-full rounded-full"
>
Parça Kataloğuna Devam Et
<ArrowRight className="ml-2 size-4" />
</Button>
</div>
)}
{vinError && !vinLoading && (
<div className="rounded-2xl border border-border bg-surface p-4 text-center text-sm text-muted-foreground">
VIN bilgisi bulunamadı. Lütfen kontrol edin.
</div>
)}
{!vin && (
<button
type="button"
onClick={() => setVin("WVWZZZ1JZ3W597935")}
className="mx-auto block text-sm text-muted-foreground underline underline-offset-4 transition hover:text-foreground"
>
Örnek VIN ile deneyin
</button>
)}
</div>
)}
{/* Step 2: Categories (static mockup) */}
{step === "categories" && (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="font-[family-name:var(--font-display)] text-2xl font-bold">
Parça Kategorileri
</h2>
{vinPreview && (
<p className="mt-1 text-sm text-muted-foreground">
{vinPreview.make} {vinPreview.model} ({vinPreview.year})
</p>
)}
</div>
<button
type="button"
onClick={() => {
setStep("vin");
setSelectedCategory(null);
}}
className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
>
Farklı VIN dene
</button>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{EXAMPLE_CATEGORIES.map((cat) => (
<button
type="button"
key={cat.name}
onClick={() => {
setSelectedCategory(cat.name);
setStep("schema");
}}
className="group rounded-2xl border border-border bg-surface p-5 text-left transition hover:border-foreground/20"
>
<div className="mb-3 inline-flex rounded-lg bg-muted p-2.5">
<FolderTree className="size-5 text-muted-foreground" />
</div>
<h3 className="font-semibold">{cat.name}</h3>
<p className="mt-1 text-xs text-muted-foreground">
{cat.subcategories.join(" • ")}
</p>
<div className="mt-3 text-xs text-muted-foreground/70 transition group-hover:text-foreground">
{cat.subcategories.length} alt kategori
</div>
</button>
))}
</div>
</div>
)}
{/* Step 3: Schema & Parts (static mockup with overlay) */}
{step === "schema" && (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="font-[family-name:var(--font-display)] text-2xl font-bold">
{selectedCategory} Şema & Parçalar
</h2>
{vinPreview && (
<p className="mt-1 text-sm text-muted-foreground">
{vinPreview.make} {vinPreview.model} ({vinPreview.year})
</p>
)}
</div>
<button
type="button"
onClick={() => setStep("categories")}
className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
>
Kategorilere dön
</button>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Schema mockup with watermark */}
<div className="relative overflow-hidden rounded-2xl border border-border bg-surface">
<div className="p-4">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<MousePointerClick className="size-4" />
İnteraktif Şema
</div>
</div>
<div className="relative aspect-square bg-muted/50 p-6">
{/* Simplified schema grid */}
<div className="grid h-full grid-cols-4 grid-rows-4 gap-2">
{KEYS_16.map((k, i) => (
<div
key={k}
className={`flex items-center justify-center rounded-lg border border-border text-xs text-muted-foreground ${
[2, 5, 9, 13].includes(i)
? "border-brand/50 bg-brand/10 text-brand"
: "bg-muted/50"
}`}
>
{[2, 5, 9, 13].includes(i)
? EXAMPLE_SCHEMA_PARTS[[2, 5, 9, 13].indexOf(i)]?.position
: ""}
</div>
))}
</div>
{/* Watermark overlay */}
<div className="absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[2px]">
<div className="text-center">
<Lock className="mx-auto size-8 text-muted-foreground" />
<p className="mt-2 text-sm font-medium text-muted-foreground">
Tam şema erişimi için kayıt olun
</p>
</div>
</div>
</div>
</div>
{/* Parts list */}
<div className="space-y-3">
<h3 className="text-sm font-medium text-muted-foreground">OEM Parça Listesi</h3>
{EXAMPLE_SCHEMA_PARTS.map((part, idx) => (
<div
key={part.code}
className="flex items-center justify-between rounded-xl border border-border bg-surface p-4"
>
<div>
<span className="font-mono text-sm text-foreground">{part.code}</span>
<p className="mt-0.5 text-sm text-muted-foreground">{part.name}</p>
</div>
{idx < 2 ? (
<span className="rounded-full bg-brand/10 px-2 py-0.5 text-xs text-brand">
Görünür
</span>
) : (
<span className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
<Lock className="mr-1 inline size-3" />
Kilitli
</span>
)}
</div>
))}
{/* Signup overlay CTA */}
<div className="mt-6 rounded-2xl border-2 border-dashed border-border bg-surface p-6 text-center">
<h3 className="font-semibold">Tüm parçaları ve şemaları görün</h3>
<p className="mt-2 text-sm text-muted-foreground">
30 gün ücretsiz deneyin kredi kartı gerekmez
</p>
<Link to="/register">
<Button className="mt-4 rounded-full bg-foreground text-background hover:bg-foreground/90">
Ücretsiz Kayıt Ol
<ArrowRight className="ml-2 size-4" />
</Button>
</Link>
</div>
</div>
</div>
</div>
)}
</main>
</div>
);
}