feat: redesign auth layout, dashboard sidebar, landing page social proof & new static pages

Split-panel auth layout with dark promo panel, sectioned dashboard nav with theme toggle,
testimonials/social proof on landing page, e-commerce Remotion demo, and static pages
(about, blog, contact, terms, privacy, KVKK).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sase Dev
2026-02-15 11:34:09 +00:00
parent bcc614a0c8
commit 6d7d1873ee
64 changed files with 8283 additions and 434 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,18 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from "./routes/__root"
import { Route as TermsRouteImport } from "./routes/terms"
import { Route as PrivacyRouteImport } from "./routes/privacy"
import { Route as PricingRouteImport } from "./routes/pricing"
import { Route as KvkkRouteImport } from "./routes/kvkk"
import { Route as DemoRouteImport } from "./routes/demo"
import { Route as DashboardRouteImport } from "./routes/dashboard"
import { Route as ContactRouteImport } from "./routes/contact"
import { Route as BlogRouteImport } from "./routes/blog"
import { Route as AboutRouteImport } from "./routes/about"
import { Route as AuthRouteImport } from "./routes/_auth"
import { Route as IndexRouteImport } from "./routes/index"
import { Route as DashboardIndexRouteImport } from "./routes/dashboard/index"
import { Route as DashboardSettingsRouteImport } from "./routes/dashboard/settings"
import { Route as DashboardSearchRouteImport } from "./routes/dashboard/search"
import { Route as DashboardHistoryRouteImport } from "./routes/dashboard/history"
@@ -32,11 +39,26 @@ import { Route as DashboardAdminAnalyticsRouteImport } from "./routes/dashboard/
import { Route as DashboardVehiclesIdIndexRouteImport } from "./routes/dashboard/vehicles_/$id/index"
import { Route as DashboardVehiclesIdCategoriesCategoryIdRouteImport } from "./routes/dashboard/vehicles_/$id/categories_/$categoryId"
const TermsRoute = TermsRouteImport.update({
id: "/terms",
path: "/terms",
getParentRoute: () => rootRouteImport,
} as any)
const PrivacyRoute = PrivacyRouteImport.update({
id: "/privacy",
path: "/privacy",
getParentRoute: () => rootRouteImport,
} as any)
const PricingRoute = PricingRouteImport.update({
id: "/pricing",
path: "/pricing",
getParentRoute: () => rootRouteImport,
} as any)
const KvkkRoute = KvkkRouteImport.update({
id: "/kvkk",
path: "/kvkk",
getParentRoute: () => rootRouteImport,
} as any)
const DemoRoute = DemoRouteImport.update({
id: "/demo",
path: "/demo",
@@ -47,6 +69,21 @@ const DashboardRoute = DashboardRouteImport.update({
path: "/dashboard",
getParentRoute: () => rootRouteImport,
} as any)
const ContactRoute = ContactRouteImport.update({
id: "/contact",
path: "/contact",
getParentRoute: () => rootRouteImport,
} as any)
const BlogRoute = BlogRouteImport.update({
id: "/blog",
path: "/blog",
getParentRoute: () => rootRouteImport,
} as any)
const AboutRoute = AboutRouteImport.update({
id: "/about",
path: "/about",
getParentRoute: () => rootRouteImport,
} as any)
const AuthRoute = AuthRouteImport.update({
id: "/_auth",
getParentRoute: () => rootRouteImport,
@@ -56,6 +93,11 @@ const IndexRoute = IndexRouteImport.update({
path: "/",
getParentRoute: () => rootRouteImport,
} as any)
const DashboardIndexRoute = DashboardIndexRouteImport.update({
id: "/",
path: "/",
getParentRoute: () => DashboardRoute,
} as any)
const DashboardSettingsRoute = DashboardSettingsRouteImport.update({
id: "/settings",
path: "/settings",
@@ -148,9 +190,15 @@ const DashboardVehiclesIdCategoriesCategoryIdRoute =
export interface FileRoutesByFullPath {
"/": typeof IndexRoute
"/about": typeof AboutRoute
"/blog": typeof BlogRoute
"/contact": typeof ContactRoute
"/dashboard": typeof DashboardRouteWithChildren
"/demo": typeof DemoRoute
"/kvkk": typeof KvkkRoute
"/pricing": typeof PricingRoute
"/privacy": typeof PrivacyRoute
"/terms": typeof TermsRoute
"/forgot-password": typeof AuthForgotPasswordRoute
"/login": typeof AuthLoginRoute
"/register": typeof AuthRegisterRoute
@@ -159,6 +207,7 @@ export interface FileRoutesByFullPath {
"/dashboard/history": typeof DashboardHistoryRoute
"/dashboard/search": typeof DashboardSearchRoute
"/dashboard/settings": typeof DashboardSettingsRoute
"/dashboard/": typeof DashboardIndexRoute
"/dashboard/admin/analytics": typeof DashboardAdminAnalyticsRoute
"/dashboard/admin/payments": typeof DashboardAdminPaymentsRoute
"/dashboard/admin/referrals": typeof DashboardAdminReferralsRoute
@@ -171,9 +220,14 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
"/": typeof IndexRoute
"/dashboard": typeof DashboardRouteWithChildren
"/about": typeof AboutRoute
"/blog": typeof BlogRoute
"/contact": typeof ContactRoute
"/demo": typeof DemoRoute
"/kvkk": typeof KvkkRoute
"/pricing": typeof PricingRoute
"/privacy": typeof PrivacyRoute
"/terms": typeof TermsRoute
"/forgot-password": typeof AuthForgotPasswordRoute
"/login": typeof AuthLoginRoute
"/register": typeof AuthRegisterRoute
@@ -182,6 +236,7 @@ export interface FileRoutesByTo {
"/dashboard/history": typeof DashboardHistoryRoute
"/dashboard/search": typeof DashboardSearchRoute
"/dashboard/settings": typeof DashboardSettingsRoute
"/dashboard": typeof DashboardIndexRoute
"/dashboard/admin/analytics": typeof DashboardAdminAnalyticsRoute
"/dashboard/admin/payments": typeof DashboardAdminPaymentsRoute
"/dashboard/admin/referrals": typeof DashboardAdminReferralsRoute
@@ -196,9 +251,15 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
"/": typeof IndexRoute
"/_auth": typeof AuthRouteWithChildren
"/about": typeof AboutRoute
"/blog": typeof BlogRoute
"/contact": typeof ContactRoute
"/dashboard": typeof DashboardRouteWithChildren
"/demo": typeof DemoRoute
"/kvkk": typeof KvkkRoute
"/pricing": typeof PricingRoute
"/privacy": typeof PrivacyRoute
"/terms": typeof TermsRoute
"/_auth/forgot-password": typeof AuthForgotPasswordRoute
"/_auth/login": typeof AuthLoginRoute
"/_auth/register": typeof AuthRegisterRoute
@@ -207,6 +268,7 @@ export interface FileRoutesById {
"/dashboard/history": typeof DashboardHistoryRoute
"/dashboard/search": typeof DashboardSearchRoute
"/dashboard/settings": typeof DashboardSettingsRoute
"/dashboard/": typeof DashboardIndexRoute
"/dashboard/admin/analytics": typeof DashboardAdminAnalyticsRoute
"/dashboard/admin/payments": typeof DashboardAdminPaymentsRoute
"/dashboard/admin/referrals": typeof DashboardAdminReferralsRoute
@@ -221,9 +283,15 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| "/"
| "/about"
| "/blog"
| "/contact"
| "/dashboard"
| "/demo"
| "/kvkk"
| "/pricing"
| "/privacy"
| "/terms"
| "/forgot-password"
| "/login"
| "/register"
@@ -232,6 +300,7 @@ export interface FileRouteTypes {
| "/dashboard/history"
| "/dashboard/search"
| "/dashboard/settings"
| "/dashboard/"
| "/dashboard/admin/analytics"
| "/dashboard/admin/payments"
| "/dashboard/admin/referrals"
@@ -244,9 +313,14 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| "/"
| "/dashboard"
| "/about"
| "/blog"
| "/contact"
| "/demo"
| "/kvkk"
| "/pricing"
| "/privacy"
| "/terms"
| "/forgot-password"
| "/login"
| "/register"
@@ -255,6 +329,7 @@ export interface FileRouteTypes {
| "/dashboard/history"
| "/dashboard/search"
| "/dashboard/settings"
| "/dashboard"
| "/dashboard/admin/analytics"
| "/dashboard/admin/payments"
| "/dashboard/admin/referrals"
@@ -268,9 +343,15 @@ export interface FileRouteTypes {
| "__root__"
| "/"
| "/_auth"
| "/about"
| "/blog"
| "/contact"
| "/dashboard"
| "/demo"
| "/kvkk"
| "/pricing"
| "/privacy"
| "/terms"
| "/_auth/forgot-password"
| "/_auth/login"
| "/_auth/register"
@@ -279,6 +360,7 @@ export interface FileRouteTypes {
| "/dashboard/history"
| "/dashboard/search"
| "/dashboard/settings"
| "/dashboard/"
| "/dashboard/admin/analytics"
| "/dashboard/admin/payments"
| "/dashboard/admin/referrals"
@@ -293,13 +375,33 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthRoute: typeof AuthRouteWithChildren
AboutRoute: typeof AboutRoute
BlogRoute: typeof BlogRoute
ContactRoute: typeof ContactRoute
DashboardRoute: typeof DashboardRouteWithChildren
DemoRoute: typeof DemoRoute
KvkkRoute: typeof KvkkRoute
PricingRoute: typeof PricingRoute
PrivacyRoute: typeof PrivacyRoute
TermsRoute: typeof TermsRoute
}
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
"/terms": {
id: "/terms"
path: "/terms"
fullPath: "/terms"
preLoaderRoute: typeof TermsRouteImport
parentRoute: typeof rootRouteImport
}
"/privacy": {
id: "/privacy"
path: "/privacy"
fullPath: "/privacy"
preLoaderRoute: typeof PrivacyRouteImport
parentRoute: typeof rootRouteImport
}
"/pricing": {
id: "/pricing"
path: "/pricing"
@@ -307,6 +409,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof PricingRouteImport
parentRoute: typeof rootRouteImport
}
"/kvkk": {
id: "/kvkk"
path: "/kvkk"
fullPath: "/kvkk"
preLoaderRoute: typeof KvkkRouteImport
parentRoute: typeof rootRouteImport
}
"/demo": {
id: "/demo"
path: "/demo"
@@ -321,6 +430,27 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof DashboardRouteImport
parentRoute: typeof rootRouteImport
}
"/contact": {
id: "/contact"
path: "/contact"
fullPath: "/contact"
preLoaderRoute: typeof ContactRouteImport
parentRoute: typeof rootRouteImport
}
"/blog": {
id: "/blog"
path: "/blog"
fullPath: "/blog"
preLoaderRoute: typeof BlogRouteImport
parentRoute: typeof rootRouteImport
}
"/about": {
id: "/about"
path: "/about"
fullPath: "/about"
preLoaderRoute: typeof AboutRouteImport
parentRoute: typeof rootRouteImport
}
"/_auth": {
id: "/_auth"
path: ""
@@ -335,6 +465,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
"/dashboard/": {
id: "/dashboard/"
path: "/"
fullPath: "/dashboard/"
preLoaderRoute: typeof DashboardIndexRouteImport
parentRoute: typeof DashboardRoute
}
"/dashboard/settings": {
id: "/dashboard/settings"
path: "/settings"
@@ -478,6 +615,7 @@ interface DashboardRouteChildren {
DashboardHistoryRoute: typeof DashboardHistoryRoute
DashboardSearchRoute: typeof DashboardSearchRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardIndexRoute: typeof DashboardIndexRoute
DashboardAdminAnalyticsRoute: typeof DashboardAdminAnalyticsRoute
DashboardAdminPaymentsRoute: typeof DashboardAdminPaymentsRoute
DashboardAdminReferralsRoute: typeof DashboardAdminReferralsRoute
@@ -494,6 +632,7 @@ const DashboardRouteChildren: DashboardRouteChildren = {
DashboardHistoryRoute: DashboardHistoryRoute,
DashboardSearchRoute: DashboardSearchRoute,
DashboardSettingsRoute: DashboardSettingsRoute,
DashboardIndexRoute: DashboardIndexRoute,
DashboardAdminAnalyticsRoute: DashboardAdminAnalyticsRoute,
DashboardAdminPaymentsRoute: DashboardAdminPaymentsRoute,
DashboardAdminReferralsRoute: DashboardAdminReferralsRoute,
@@ -513,9 +652,15 @@ const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthRoute: AuthRouteWithChildren,
AboutRoute: AboutRoute,
BlogRoute: BlogRoute,
ContactRoute: ContactRoute,
DashboardRoute: DashboardRouteWithChildren,
DemoRoute: DemoRoute,
KvkkRoute: KvkkRoute,
PricingRoute: PricingRoute,
PrivacyRoute: PrivacyRoute,
TermsRoute: TermsRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View File

@@ -1,4 +1,5 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import { Database, ShieldCheck, Zap } from "lucide-react";
export const Route = createFileRoute("/_auth")({
component: AuthLayout,
@@ -6,8 +7,104 @@ export const Route = createFileRoute("/_auth")({
function AuthLayout() {
return (
<div className="flex min-h-screen items-center justify-center bg-muted/30 px-4">
<Outlet />
<div className="flex min-h-screen">
{/* Left Panel — Form */}
<div className="flex w-full flex-col justify-between px-6 py-8 lg:w-1/2">
<div className="flex flex-1 items-center justify-center">
<div className="w-full max-w-md">
<Outlet />
</div>
</div>
<div className="flex justify-center pt-6">
<Link
to="/"
className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
>
Sase.tr
</Link>
</div>
</div>
{/* Right Panel — Promo (always dark, hidden on mobile) */}
<div className="hidden border-l border-white/5 bg-[#09090b] text-white lg:flex lg:w-1/2 lg:flex-col lg:justify-between lg:px-12 lg:py-12">
<div className="flex flex-1 flex-col justify-center space-y-8">
{/* Heading */}
<div className="space-y-3">
<h2 className="text-3xl font-bold tracking-tight">
Doğru Parçayı İlk Seferde Bulun
</h2>
<p className="text-base leading-relaxed text-neutral-400">
Birden fazla katalogda çapraz sorgulama ile her zaman en güncel OEM
kodları. Şase numarasını girin, doğru parçayı saniyeler içinde
bulun.
</p>
</div>
{/* Stats */}
<div className="flex flex-wrap gap-2">
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs text-neutral-300">
<Zap className="size-3" />
1.2sn Sorgu
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs text-neutral-300">
<svg
className="size-3"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2" />
<circle cx="7" cy="17" r="2" />
<circle cx="17" cy="17" r="2" />
</svg>
27 Marka
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs text-neutral-300">
<Database className="size-3" />
243K+ OEM Parça
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs text-neutral-300">
<ShieldCheck className="size-3" />
%99.9 Uptime
</span>
</div>
{/* Brand row */}
<p className="text-sm text-neutral-500">
BMW · Mercedes-Benz · Audi · VW · Fiat · Renault · Toyota · Honda ·
Hyundai · Ford · Opel · Skoda
</p>
{/* Testimonial */}
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<p className="text-sm leading-relaxed text-neutral-300">
&ldquo;Sase.tr&apos;ye geçtiğimizden beri yanlış parça
siparişlerimiz neredeyse sıfıra indi. Aylık 40 saatin üzerinde
zaman tasarrufu sağlıyoruz.&rdquo;
</p>
<div className="mt-4 flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-full bg-white/10 text-sm font-medium">
MK
</div>
<div>
<p className="text-sm font-medium">Mehmet K.</p>
<p className="text-xs text-neutral-400">
Yedek Parça İşletme Sahibi
</p>
</div>
</div>
</div>
</div>
{/* Bottom trial badge */}
<div className="flex items-center gap-2 pt-6 text-sm text-neutral-400">
<ShieldCheck className="size-4" />
7 gün ücretsiz deneyin kredi kartı gerekmez
</div>
</div>
</div>
);
}

View File

@@ -2,7 +2,6 @@ import { useState } from "react";
import { Link, createFileRoute } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
import { Label } from "@sase/ui";
import { toast } from "sonner";
@@ -36,57 +35,63 @@ function ForgotPasswordPage() {
if (sent) {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle>E-posta Gönderildi</CardTitle>
<CardDescription>
Şifre sıfırlama bağlantısı {email} adresine gönderildi. Lütfen e-postanızı kontrol edin.
</CardDescription>
</CardHeader>
<CardContent>
<Link to="/login">
<Button variant="outline" className="w-full">
Giriş Sayfasına Dön
</Button>
</Link>
</CardContent>
</Card>
<div className="space-y-8">
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
E-posta Gönderildi
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Şifre sıfırlama bağlantısı {email} adresine gönderildi. Lütfen
e-postanızı kontrol edin.
</p>
</div>
<Link to="/login">
<Button variant="outline" className="w-full">
Giriş Sayfasına Dön
</Button>
</Link>
</div>
);
}
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Şifremi Unuttum</CardTitle>
<CardDescription>E-posta adresinize şifre sıfırlama bağlantısı göndereceğiz</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Gönderiliyor..." : "Bağlantı Gönder"}
</Button>
</form>
<div className="mt-4 text-center text-sm">
<Link to="/login" className="text-muted-foreground hover:underline">
Giriş Sayfasına Dön
</Link>
</div>
</CardContent>
</Card>
<div className="space-y-8">
{/* Heading */}
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
Şifremi Unuttum
</h1>
<p className="mt-2 text-sm text-muted-foreground">
E-posta adresinize şifre sıfırlama bağlantısı göndereceğiz
</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Gönderiliyor..." : "Bağlantı Gönder"}
</Button>
</form>
<p className="text-center text-sm">
<Link
to="/login"
className="text-muted-foreground hover:underline"
>
Giriş Sayfasına Dön
</Link>
</p>
</div>
);
}

View File

@@ -2,7 +2,6 @@ import { useState } from "react";
import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
import { Label } from "@sase/ui";
import { signIn } from "@/lib/auth-client";
import { toast } from "sonner";
@@ -32,72 +31,92 @@ function LoginPage() {
}
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Giriş Yap</CardTitle>
<CardDescription>Sase.tr hesabınıza giriş yapın</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Şifre</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Giriş yapılıyor..." : "Giriş Yap"}
</Button>
</form>
<div className="space-y-8">
{/* Heading */}
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
Hesabınıza Giriş Yapın
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Şase çözme ve parça kataloğuna erişmek için giriş yapın
</p>
</div>
<div className="mt-4 space-y-3">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">veya</span>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Şifre</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<Button
variant="outline"
className="w-full"
onClick={() => signIn.social({ provider: "google" })}
>
Google ile Giriş Yap
</Button>
</div>
{/* Remember me + Forgot password */}
<div className="flex items-center justify-between">
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
className="size-4 rounded border-input accent-primary"
/>
Beni hatırla
</label>
<Link
to="/forgot-password"
className="text-sm text-muted-foreground hover:underline"
>
Şifremi Unuttum
</Link>
</div>
<div className="mt-4 text-center text-sm">
<Link to="/forgot-password" className="text-muted-foreground hover:underline">
Şifremi Unuttum
</Link>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Giriş yapılıyor..." : "Giriş Yap"}
</Button>
</form>
{/* Divider + Google */}
<div className="space-y-3">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="mt-2 text-center text-sm">
Hesabınız yok mu?{" "}
<Link to="/register" className="font-medium hover:underline">
Kayıt Ol
</Link>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
veya
</span>
</div>
</CardContent>
</Card>
</div>
<Button
variant="outline"
className="w-full"
onClick={() => signIn.social({ provider: "google" })}
>
Google ile Giriş Yap
</Button>
</div>
{/* Register link */}
<p className="text-center text-sm">
Hesabınız yok mu?{" "}
<Link to="/register" className="font-medium hover:underline">
Kayıt Ol
</Link>
</p>
</div>
);
}

View File

@@ -2,7 +2,6 @@ import { useState } from "react";
import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
import { Label } from "@sase/ui";
import { signUp } from "@/lib/auth-client";
import { toast } from "sonner";
@@ -40,71 +39,74 @@ function RegisterPage() {
}
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Kayıt Ol</CardTitle>
<CardDescription>Yeni bir Sase.tr hesabı oluşturun</CardDescription>
<div className="space-y-8">
{/* Heading */}
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
Kayıt Ol
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Yeni bir Sase.tr hesabı oluşturun
</p>
{/* Trial messaging */}
<div className="mt-3 flex items-center justify-center gap-2 rounded-lg bg-emerald-500/10 px-3 py-2 text-sm text-emerald-600 dark:text-emerald-400">
<ShieldCheck className="size-4 shrink-0" />
7 gün ücretsiz deneyin kredi kartı gerekmez
</div>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Ad Soyad</Label>
<Input
id="name"
type="text"
placeholder="Ad Soyad"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Şifre</Label>
<Input
id="password"
type="password"
placeholder="En az 8 karakter, 1 büyük harf, 1 rakam"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Kayıt yapılıyor..." : "Kayıt Ol"}
</Button>
</form>
{/* Trial messaging */}
<div className="mt-3 flex items-center gap-2 rounded-lg bg-emerald-500/10 px-3 py-2 text-sm text-emerald-600 dark:text-emerald-400">
<ShieldCheck className="size-4 shrink-0" />
7 gün ücretsiz deneyin kredi kartı gerekmez
</div>
</div>
<p className="mt-3 text-center text-xs text-muted-foreground">
Kayıt olunca hemen VIN aramaya başlayın
</p>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="name">Ad Soyad</Label>
<Input
id="name"
type="text"
placeholder="Ad Soyad"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">E-posta</Label>
<Input
id="email"
type="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Şifre</Label>
<Input
id="password"
type="password"
placeholder="En az 8 karakter, 1 büyük harf, 1 rakam"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Kayıt yapılıyor..." : "Kayıt Ol"}
</Button>
</form>
<div className="mt-4 text-center text-sm">
Zaten hesabınız var mı?{" "}
<Link to="/login" className="font-medium hover:underline">
Giriş Yap
</Link>
</div>
</CardContent>
</Card>
<p className="text-center text-xs text-muted-foreground">
Kayıt olunca hemen VIN aramaya başlayın
</p>
<p className="text-center text-sm">
Zaten hesabınız var mı?{" "}
<Link to="/login" className="font-medium hover:underline">
Giriş Yap
</Link>
</p>
</div>
);
}

View File

@@ -2,7 +2,6 @@ import { useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
import { Label } from "@sase/ui";
import { toast } from "sonner";
@@ -32,39 +31,44 @@ function ResetPasswordPage() {
toast.success("Şifreniz başarıyla güncellendi.");
navigate({ to: "/login" });
} catch {
toast.error("Şifre sıfırlama başarısız. Bağlantı süresi dolmuş olabilir.");
toast.error(
"Şifre sıfırlama başarısız. Bağlantı süresi dolmuş olabilir.",
);
} finally {
setLoading(false);
}
}
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Şifre Sıfırla</CardTitle>
<CardDescription>Yeni şifrenizi belirleyin</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="password">Yeni Şifre</Label>
<Input
id="password"
type="password"
placeholder="En az 8 karakter"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Güncelleniyor..." : "Şifreyi Güncelle"}
</Button>
</form>
</CardContent>
</Card>
<div className="space-y-8">
{/* Heading */}
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
Şifre Sıfırla
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Yeni şifrenizi belirleyin
</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="password">Yeni Şifre</Label>
<Input
id="password"
type="password"
placeholder="En az 8 karakter"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Güncelleniyor..." : "Şifreyi Güncelle"}
</Button>
</form>
</div>
);
}

View File

@@ -0,0 +1,87 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
export const Route = createFileRoute("/about")({
component: AboutPage,
});
function AboutPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-3xl px-4 py-16">
<h1 className="text-4xl font-bold">Hakkımızda</h1>
<div className="mt-8 space-y-6 text-muted-foreground leading-relaxed">
<p>
Sase.tr, Türkiye'nin yedek parça sektörüne yönelik geliştirilen
dijital bir platformdur. Şase numarası (VIN) ile araç tanımlama,
orijinal parça kataloğuna erişim ve interaktif şema görüntüleme
hizmetlerini tek bir çatı altında sunar.
</p>
<h2 className="text-2xl font-semibold text-foreground">Misyonumuz</h2>
<p>
Yedek parça arama sürecini hızlandırmak, doğru parçaya ilk
seferde ulaşmayı sağlamak ve sektördeki bilgi asimetrisini ortadan
kaldırmak. Oto yedek parçacılar, servisler ve bireysel kullanıcılar
için güvenilir bir referans noktası olmayı hedefliyoruz.
</p>
<h2 className="text-2xl font-semibold text-foreground">Ne Yapıyoruz?</h2>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Şase Çözme:</strong> VIN numarası ile aracın marka,
model, yıl, motor tipi ve donanım bilgilerine anında erişim.
</li>
<li>
<strong>Orijinal Parça Kataloğu:</strong> OEM bazlı parça
numaraları, ıklamalar ve çapraz referanslar.
</li>
<li>
<strong>İnteraktif Şema:</strong> Araç şemaları üzerinden
görsel parça seçimi ve detay görüntüleme.
</li>
<li>
<strong>Çoklu Marka Desteği:</strong> Volkswagen, Audi, BMW,
Mercedes-Benz, Ford ve daha fazlası.
</li>
</ul>
<h2 className="text-2xl font-semibold text-foreground">Neden Sase.tr?</h2>
<p>
Geleneksel yöntemlerle saatler süren parça arama işlemini
dakikalara indiriyoruz. Güncel ve doğrulanmış verilerle yanlış
parça siparişinin önüne geçiyoruz. Kullanıcı dostu arayüzümüz
sayesinde teknik bilgi seviyesinden bağımsız olarak herkes
kolayca kullanabilir.
</p>
<h2 className="text-2xl font-semibold text-foreground">İletişim</h2>
<p>
Sorularınız veya birliği talepleriniz için{" "}
<a href="mailto:info@sase.tr" className="text-primary underline">
info@sase.tr
</a>{" "}
adresinden bize ulaşabilirsiniz.
</p>
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,83 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@sase/ui";
export const Route = createFileRoute("/blog")({
component: BlogPage,
});
const posts = [
{
title: "Şase Numarası (VIN) Nedir? Nasıl Okunur?",
description:
"17 haneli VIN kodunun her bir bölümünün ne anlama geldiğini, araç geçmişini nasıl ortaya çıkarabileceğinizi adım adım anlatıyoruz.",
date: "2026-02-10",
slug: "vin-nedir-nasil-okunur",
},
{
title: "Orijinal Parça mı, Muadil Parça mı?",
description:
"OEM ve aftermarket parçalar arasındaki farkları, avantaj ve dezavantajlarını karşılaştırmalı olarak inceliyoruz.",
date: "2026-01-28",
slug: "orijinal-mi-muadil-mi",
},
{
title: "Yedek Parça Aramada Dijital Dönüşüm",
description:
"Geleneksel katalog yöntemlerinden dijital platformlara geçiş sürecini ve sektöre etkilerini ele alıyoruz.",
date: "2026-01-15",
slug: "dijital-donusum",
},
{
title: "Sase.tr ile Doğru Parçayı İlk Seferde Bulun",
description:
"Platform özelliklerini kullanarak VIN bazlı arama, şema görüntüleme ve parça eşleştirme rehberi.",
date: "2026-01-05",
slug: "dogru-parcayi-bulun",
},
];
function BlogPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-4xl px-4 py-16">
<h1 className="text-4xl font-bold">Blog</h1>
<p className="mt-4 text-lg text-muted-foreground">
Yedek parça sektörü, araç bakımı ve Sase.tr hakkında güncel yazılar.
</p>
<div className="mt-12 grid gap-6 sm:grid-cols-2">
{posts.map((post) => (
<Card key={post.slug}>
<CardHeader>
<CardDescription>{post.date}</CardDescription>
<CardTitle className="text-lg">{post.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{post.description}
</p>
</CardContent>
</Card>
))}
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,87 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
export const Route = createFileRoute("/contact")({
component: ContactPage,
});
function ContactPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-3xl px-4 py-16">
<h1 className="text-4xl font-bold">İletişim</h1>
<p className="mt-4 text-lg text-muted-foreground">
Sorularınız, önerileriniz veya birliği talepleriniz için bize
ulaşın.
</p>
<div className="mt-12 grid gap-6 sm:grid-cols-2">
<Card>
<CardHeader>
<CardTitle className="text-lg">E-posta</CardTitle>
</CardHeader>
<CardContent>
<a
href="mailto:info@sase.tr"
className="text-primary underline"
>
info@sase.tr
</a>
<p className="mt-2 text-sm text-muted-foreground">
Genel sorular ve destek talepleri için.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">Destek</CardTitle>
</CardHeader>
<CardContent>
<a
href="mailto:destek@sase.tr"
className="text-primary underline"
>
destek@sase.tr
</a>
<p className="mt-2 text-sm text-muted-foreground">
Teknik sorunlar ve hesap işlemleri için.
</p>
</CardContent>
</Card>
<Card className="sm:col-span-2">
<CardHeader>
<CardTitle className="text-lg">Adres</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
İstanbul, Türkiye
</p>
<p className="mt-2 text-sm text-muted-foreground">
Çalışma saatleri: Pazartesi Cuma, 09:00 18:00
</p>
</CardContent>
</Card>
</div>
</main>
</div>
);
}

View File

@@ -1,8 +1,7 @@
import { createFileRoute, Outlet, Link, redirect, useNavigate } from "@tanstack/react-router";
import { createFileRoute, Outlet, Link, useNavigate } from "@tanstack/react-router";
import { useAuth } from "@/hooks/use-auth";
import { useTranslation } from "@/lib/i18n";
import { Button } from "@sase/ui";
import { Skeleton } from "@sase/ui";
import { Button, Separator, Skeleton } from "@sase/ui";
import {
Search,
History,
@@ -19,6 +18,13 @@ import {
LogOut,
PanelLeftClose,
PanelLeftOpen,
LayoutDashboard,
Bell,
ArrowRight,
Mail,
BookOpen,
Sun,
Moon,
} from "lucide-react";
import { useState } from "react";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
@@ -27,12 +33,23 @@ export const Route = createFileRoute("/dashboard")({
component: DashboardLayout,
});
const navItems = [
{ to: "/dashboard/search", label: "nav.search", icon: Search },
{ to: "/dashboard/history", label: "nav.history", icon: History },
{ to: "/dashboard/subscription", label: "nav.subscription", icon: CreditCard },
{ to: "/dashboard/billing", label: "nav.billing", icon: Receipt },
{ to: "/dashboard/settings", label: "nav.settings", icon: Settings },
// ─── NAV SECTIONS ─────────────────────────────────────────────────────────────
const mainMenuItems = [
{ to: "/dashboard", label: "Gösterge Paneli", icon: LayoutDashboard, exact: true },
{ to: "/dashboard/search", label: "nav.search", translatable: true, icon: Search },
{ to: "/dashboard/history", label: "nav.history", translatable: true, icon: History },
] as const;
const accountItems = [
{ to: "/dashboard/subscription", label: "nav.subscription", translatable: true, icon: CreditCard },
{ to: "/dashboard/billing", label: "nav.billing", translatable: true, icon: Receipt },
{ to: "/dashboard/settings", label: "nav.settings", translatable: true, icon: Settings },
] as const;
const supportItems = [
{ to: "/contact", label: "İletişim", icon: Mail },
{ to: "/blog", label: "Blog", icon: BookOpen },
] as const;
const adminItems = [
@@ -43,12 +60,79 @@ const adminItems = [
{ to: "/dashboard/admin/referrals", label: "Referanslar", icon: Share2 },
] as const;
// ─── HELPERS ──────────────────────────────────────────────────────────────────
function NavSection({
title,
collapsed,
}: {
title: string;
collapsed: boolean;
}) {
if (collapsed) {
return <Separator className="my-2 bg-border/50" />;
}
return (
<p className="mb-1 mt-4 px-3 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
{title}
</p>
);
}
function NavLink({
to,
label,
icon: Icon,
collapsed,
onClick,
exact,
}: {
to: string;
label: string;
icon: React.ComponentType<{ className?: string }>;
collapsed: boolean;
onClick?: () => void;
exact?: boolean;
}) {
return (
<Link
to={to}
title={collapsed ? label : undefined}
className={`flex items-center rounded-lg text-sm font-medium transition-colors hover:bg-accent [&.active]:bg-accent [&.active]:text-accent-foreground ${collapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2"}`}
activeProps={{ className: "active" }}
activeOptions={exact ? { exact: true } : undefined}
onClick={onClick}
>
<Icon className="size-4 shrink-0" />
{!collapsed && <span>{label}</span>}
</Link>
);
}
// ─── LAYOUT ───────────────────────────────────────────────────────────────────
function DashboardLayout() {
const { t } = useTranslation();
const { user, isLoading, signOut, isAdmin } = useAuth();
const navigate = useNavigate();
const [mobileOpen, setMobileOpen] = useState(false);
const [collapsed, setCollapsed] = useState(() => getUserSettings().sidebarCollapsed ?? false);
const [collapsed, setCollapsed] = useState(
() => getUserSettings().sidebarCollapsed ?? false,
);
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");
};
const toggleCollapsed = () => {
const next = !collapsed;
@@ -59,7 +143,7 @@ function DashboardLayout() {
if (isLoading) {
return (
<div className="flex min-h-screen">
<div className="hidden w-64 border-r p-4 lg:block">
<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" />
@@ -77,109 +161,224 @@ function DashboardLayout() {
return null;
}
const initials = user.name
? user.name
.split(" ")
.map((w) => w[0])
.join("")
.toUpperCase()
.slice(0, 2)
: "?";
// Resolve labels (some need translation, some are static)
function resolveLabel(item: { label: string; translatable?: boolean }) {
return item.translatable ? t(item.label) : item.label;
}
// Sidebar content (shared between desktop & mobile)
function SidebarNav({ onNavigate }: { onNavigate?: () => void }) {
return (
<>
{/* ANA MENÜ */}
<NavSection title="Ana Menü" collapsed={collapsed && !onNavigate} />
{mainMenuItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
label={resolveLabel(item)}
icon={item.icon}
collapsed={collapsed && !onNavigate}
onClick={onNavigate}
exact={(item as any).exact}
/>
))}
{/* DESTEK */}
<NavSection title="Destek" collapsed={collapsed && !onNavigate} />
{supportItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
label={resolveLabel(item)}
icon={item.icon}
collapsed={collapsed && !onNavigate}
onClick={onNavigate}
/>
))}
{/* HESAP */}
<NavSection title="Hesap" collapsed={collapsed && !onNavigate} />
{accountItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
label={resolveLabel(item)}
icon={item.icon}
collapsed={collapsed && !onNavigate}
onClick={onNavigate}
/>
))}
{/* ADMIN */}
{isAdmin && (
<>
<NavSection title="Yönetim" collapsed={collapsed && !onNavigate} />
{adminItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
label={item.label}
icon={item.icon}
collapsed={collapsed && !onNavigate}
onClick={onNavigate}
/>
))}
</>
)}
</>
);
}
return (
<div className="flex min-h-screen">
{/* Desktop Sidebar */}
<div className="flex min-h-screen bg-background">
{/* ─── Desktop Sidebar ──────────────────────────────────────────── */}
<aside
className={`hidden flex-shrink-0 border-r transition-[width] duration-200 lg:block ${collapsed ? "w-16" : "w-64"}`}
className={`hidden flex-shrink-0 border-r border-border transition-[width] duration-200 lg:flex lg:flex-col ${collapsed ? "w-[68px]" : "w-64"}`}
>
<div className="flex h-full flex-col">
<div className={`flex h-16 items-center border-b ${collapsed ? "justify-center px-2" : "justify-between px-4"}`}>
{!collapsed && (
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
{/* Logo + Collapse */}
<div
className={`flex h-16 items-center border-b border-border ${collapsed ? "justify-center px-2" : "justify-between px-4"}`}
>
{!collapsed && (
<Link to="/" className="text-xl font-bold tracking-tight">
Sase.tr
</Link>
)}
<button
type="button"
onClick={toggleCollapsed}
className="flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
{collapsed ? (
<PanelLeftOpen className="size-5" />
) : (
<PanelLeftClose className="size-5" />
)}
<button
type="button"
onClick={toggleCollapsed}
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
{collapsed ? (
<PanelLeftOpen className="h-5 w-5" />
) : (
<PanelLeftClose className="h-5 w-5" />
)}
</button>
</div>
<nav className={`flex-1 space-y-1 overflow-y-auto ${collapsed ? "p-2" : "p-4"}`}>
{navItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.to}
to={item.to}
title={collapsed ? t(item.label) : undefined}
className={`flex items-center rounded-lg text-sm font-medium transition-colors hover:bg-accent [&.active]:bg-accent [&.active]:text-accent-foreground ${collapsed ? "justify-center p-2" : "gap-3 px-3 py-2"}`}
activeProps={{ className: "active" }}
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span>{t(item.label)}</span>}
</Link>
);
})}
{isAdmin &&
adminItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.to}
to={item.to}
title={collapsed ? item.label : undefined}
className={`flex items-center rounded-lg text-sm font-medium transition-colors hover:bg-accent [&.active]:bg-accent [&.active]:text-accent-foreground ${collapsed ? "justify-center p-2" : "gap-3 px-3 py-2"}`}
activeProps={{ className: "active" }}
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span>{item.label}</span>}
</Link>
);
})}
</nav>
{/* User section - bottom */}
<div className={`border-t ${collapsed ? "p-2" : "p-3"}`}>
<button
type="button"
onClick={() => signOut()}
title={collapsed ? user.name ?? ıkış" : undefined}
className={`flex w-full items-center rounded-lg text-left transition-colors hover:bg-accent ${collapsed ? "justify-center p-2" : "gap-3 px-3 py-2"}`}
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{user.name?.charAt(0).toUpperCase() ?? "?"}
</div>
{!collapsed && (
<>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.name}</p>
<p className="truncate text-xs text-muted-foreground">{user.email}</p>
</div>
<LogOut className="h-4 w-4 shrink-0 text-muted-foreground" />
</>
)}
</button>
</div>
</button>
</div>
{/* Navigation */}
<nav
className={`flex-1 space-y-0.5 overflow-y-auto ${collapsed ? "p-2" : "px-3 py-2"}`}
>
<SidebarNav />
</nav>
{/* User Profile - Bottom */}
<div className={`border-t border-border ${collapsed ? "p-2" : "p-3"}`}>
<button
type="button"
onClick={() => signOut()}
title={collapsed ? user.name ?? ıkış" : undefined}
className={`flex w-full items-center rounded-lg text-left transition-colors hover:bg-accent ${collapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2.5"}`}
>
<div className="flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{initials}
</div>
{!collapsed && (
<>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.name}</p>
<p className="truncate text-xs text-muted-foreground">
{user.email}
</p>
</div>
<LogOut className="size-4 shrink-0 text-muted-foreground" />
</>
)}
</button>
</div>
</aside>
{/* Main Content */}
{/* ─── Main Content Area ────────────────────────────────────────── */}
<div className="flex flex-1 flex-col">
{/* Header */}
<header className="flex h-16 items-center border-b px-6 lg:hidden">
<Button
variant="ghost"
size="icon"
onClick={() => setMobileOpen(true)}
>
<Menu className="h-5 w-5" />
</Button>
{/* Top Header Bar */}
<header className="flex h-16 items-center justify-between border-b border-border px-4 sm:px-6">
{/* Left: Mobile menu + User welcome */}
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
className="lg:hidden"
onClick={() => setMobileOpen(true)}
>
<Menu className="size-5" />
</Button>
<div className="flex items-center gap-3">
<div className="hidden size-10 items-center justify-center rounded-full bg-muted text-sm font-semibold sm:flex">
{initials}
</div>
<div className="hidden sm:block">
<p className="text-sm font-semibold">{user.name}</p>
<p className="text-xs text-muted-foreground">
Sase.tr'ye hoş geldiniz 👋
</p>
</div>
</div>
</div>
{/* Right: Actions */}
<div className="flex items-center gap-2">
<Link to="/dashboard/subscription">
<Button
variant="outline"
size="sm"
className="hidden rounded-full border-border text-xs sm:inline-flex"
>
<CreditCard className="mr-1.5 size-3.5" />
Plan Yükselt
</Button>
</Link>
<button
type="button"
onClick={toggleTheme}
className="flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
aria-label="Tema değiştir"
>
{isDark ? <Sun className="size-4" /> : <Moon className="size-4" />}
</button>
<button
type="button"
className="flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
<Bell className="size-4" />
</button>
<button
type="button"
onClick={() => signOut()}
className="flex size-9 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground lg:hidden"
>
<LogOut className="size-4" />
</button>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-auto p-6">
<main className="flex-1 overflow-auto bg-muted/30 p-4 sm:p-6">
<Outlet />
</main>
{/* Footer */}
<div className="border-t border-border px-6 py-3">
<p className="text-center text-xs text-muted-foreground/60">
&copy; {new Date().getFullYear()} Sase.tr | Gizlilik Politikası,
Kullanım Koşulları
</p>
</div>
</div>
{/* Mobile Nav Overlay */}
{/* ─── Mobile Nav Overlay ───────────────────────────────────────── */}
{mobileOpen && (
<div className="fixed inset-0 z-50 lg:hidden">
<div
@@ -190,63 +389,39 @@ function DashboardLayout() {
tabIndex={-1}
/>
<aside className="absolute left-0 top-0 flex h-full w-64 flex-col bg-background shadow-lg">
<div className="flex h-16 items-center justify-between border-b px-6">
<div className="flex h-16 items-center justify-between border-b border-border px-4">
<span className="text-xl font-bold">Sase.tr</span>
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(false)}>
<X className="h-4 w-4" />
<Button
variant="ghost"
size="icon"
onClick={() => setMobileOpen(false)}
>
<X className="size-4" />
</Button>
</div>
<nav className="flex-1 space-y-1 overflow-y-auto p-4">
{navItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.to}
to={item.to}
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors hover:bg-accent [&.active]:bg-accent"
activeProps={{ className: "active" }}
onClick={() => setMobileOpen(false)}
>
<Icon className="h-4 w-4" />
{t(item.label)}
</Link>
);
})}
{isAdmin &&
adminItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.to}
to={item.to}
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors hover:bg-accent [&.active]:bg-accent"
activeProps={{ className: "active" }}
onClick={() => setMobileOpen(false)}
>
<Icon className="h-4 w-4" />
{item.label}
</Link>
);
})}
<nav className="flex-1 space-y-0.5 overflow-y-auto px-3 py-2">
<SidebarNav onNavigate={() => setMobileOpen(false)} />
</nav>
{/* User section - bottom */}
<div className="border-t p-3">
<div className="border-t border-border p-3">
<button
type="button"
onClick={() => {
signOut();
setMobileOpen(false);
}}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-accent"
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-accent"
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{user.name?.charAt(0).toUpperCase() ?? "?"}
<div className="flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground">
{initials}
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.name}</p>
<p className="truncate text-xs text-muted-foreground">{user.email}</p>
<p className="truncate text-xs text-muted-foreground">
{user.email}
</p>
</div>
<LogOut className="h-4 w-4 shrink-0 text-muted-foreground" />
<LogOut className="size-4 shrink-0 text-muted-foreground" />
</button>
</div>
</aside>

View File

@@ -160,7 +160,7 @@ function AdminAnalyticsPage() {
<div className="min-w-[900px]">
<div className="grid grid-cols-8 items-center gap-4 border-b px-6 py-3 text-sm font-medium text-muted-foreground">
<span>Kullanici</span>
<span>VIN</span>
<span>Şase</span>
<span>Marka</span>
<span>Kaynak</span>
<span className="text-center">Durum</span>

View File

@@ -34,7 +34,7 @@ function HistoryPage() {
Henüz arama yapmadınız.
<br />
<Link to="/dashboard/search" className="text-primary hover:underline">
VIN arama sayfasına gidin
Şase arama sayfasına gidin
</Link>
</CardContent>
</Card>

View File

@@ -0,0 +1,461 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { api } from "@/lib/api-client";
import { Button, Badge, Skeleton, Separator } from "@sase/ui";
import {
Search,
Car,
Database,
User,
ArrowRight,
Crown,
Calendar,
CheckCircle2,
} from "lucide-react";
export const Route = createFileRoute("/dashboard/")({
component: DashboardHome,
});
// ─── TYPES ────────────────────────────────────────────────────────────────────
interface DashboardStats {
totalSearches: number;
monthlySearches: number;
totalVehicles: number;
recentVehicles: number;
}
interface SubscriptionBrand {
brandId: string;
brandName: string;
}
interface Subscription {
status: string;
plan?: { name: string; key: string };
billingPeriod: string;
brands?: SubscriptionBrand[];
startDate?: string;
endDate?: string;
}
// ─── STAT CARD ────────────────────────────────────────────────────────────────
function StatCard({
icon: Icon,
value,
label,
detail,
detailValue,
buttonLabel,
buttonTo,
progress,
}: {
icon: React.ComponentType<{ className?: string }>;
value: string;
label: string;
detail?: string;
detailValue?: string;
buttonLabel?: string;
buttonTo?: string;
progress?: number;
}) {
return (
<div className="flex flex-col rounded-2xl border border-border bg-background p-5 sm:p-6">
{/* Icon + Value + Label */}
<div className="flex-1 space-y-4">
<div className="inline-flex size-12 items-center justify-center rounded-xl bg-muted">
<Icon className="size-5 text-muted-foreground" />
</div>
<div>
<p className="font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight">
{value}
</p>
<p className="mt-0.5 text-sm font-medium text-muted-foreground">
{label}
</p>
</div>
{detail && (
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">{detail}</span>
<span className="font-medium">{detailValue}</span>
</div>
)}
</div>
{/* Divider */}
<Separator className="my-4 bg-border" />
{/* Progress bar or button */}
{progress !== undefined ? (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Kullanım</span>
<span className="font-semibold">{progress}%</span>
</div>
<div className="h-2 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary transition-all"
style={{ width: `${Math.min(progress, 100)}%` }}
/>
</div>
</div>
) : buttonLabel && buttonTo ? (
<Link to={buttonTo}>
<Button
variant="outline"
className="w-full justify-center rounded-lg border-border text-sm"
>
{buttonLabel}
<ArrowRight className="ml-1.5 size-3.5" />
</Button>
</Link>
) : null}
</div>
);
}
// ─── PROFILE CARD ─────────────────────────────────────────────────────────────
function ProfileCard({
name,
email,
initials,
}: {
name: string;
email: string;
initials: string;
}) {
return (
<div className="flex flex-col rounded-2xl border border-border bg-background p-5 sm:p-6">
<div className="flex-1 space-y-4">
<div className="inline-flex size-12 items-center justify-center rounded-xl bg-muted">
<User className="size-5 text-muted-foreground" />
</div>
<div>
<p className="font-[family-name:var(--font-display)] text-2xl font-bold tracking-tight">
{name}
</p>
<p className="mt-0.5 text-sm text-muted-foreground">{email}</p>
</div>
</div>
<Separator className="my-4 bg-border" />
<Link to="/dashboard/settings">
<Button
variant="outline"
className="w-full justify-center rounded-lg border-border text-sm"
>
Profil Ayarları
<ArrowRight className="ml-1.5 size-3.5" />
</Button>
</Link>
</div>
);
}
// ─── PAGE ─────────────────────────────────────────────────────────────────────
function DashboardHome() {
const { user } = useAuth();
const { data: stats, isLoading: statsLoading } = useQuery({
queryKey: ["dashboard", "stats"],
queryFn: async () => {
// Try to get stats from the API, fall back to defaults
try {
return await api.get<DashboardStats>("/dashboard/stats");
} catch {
return null;
}
},
});
const { data: subscription, isLoading: subLoading } = useQuery({
queryKey: ["subscription", "me"],
queryFn: async () => {
try {
return await api.get<Subscription>("/subscriptions/me");
} catch {
return null;
}
},
});
const { data: history } = useQuery({
queryKey: ["vehicles", "history"],
queryFn: async () => {
try {
return await api.get<any[]>("/vehicles/history?limit=5");
} catch {
return [];
}
},
});
const initials = user?.name
? user.name
.split(" ")
.map((w) => w[0])
.join("")
.toUpperCase()
.slice(0, 2)
: "?";
const totalSearches = stats?.totalSearches ?? history?.length ?? 0;
const monthlySearches = stats?.monthlySearches ?? 0;
const totalVehicles = stats?.totalVehicles ?? history?.length ?? 0;
const recentVehicles = stats?.recentVehicles ?? 0;
const brandCount = subscription?.brands?.length ?? 0;
const maxBrands =
subscription?.plan?.key === "full"
? 27
: subscription?.plan?.key === "brand3"
? 3
: subscription?.plan?.key === "brand2"
? 2
: subscription?.plan?.key === "brand1"
? 1
: 0;
const isLoadingCards = statsLoading || subLoading;
return (
<div className="mx-auto max-w-6xl space-y-8">
{/* ─── 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" />
))}
</div>
) : (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* 1. Toplam Arama */}
<StatCard
icon={Search}
value={String(totalSearches)}
label="Toplam Arama"
detail="Bu Ay"
detailValue={String(monthlySearches)}
buttonLabel="Şase Çöz"
buttonTo="/dashboard/search"
/>
{/* 2. Araç Kaydı */}
<StatCard
icon={Car}
value={String(totalVehicles)}
label="Araç Kaydı"
detail="Son 30 Gün"
detailValue={String(recentVehicles)}
buttonLabel="Geçmişe Git"
buttonTo="/dashboard/history"
/>
{/* 3. Aktif Marka */}
<StatCard
icon={Database}
value={maxBrands > 0 ? `${brandCount}/${maxBrands}` : "—"}
label="Aktif Marka"
detail="Erişilebilir Marka"
detailValue={maxBrands > 0 ? String(maxBrands) : "—"}
progress={
maxBrands > 0 ? Math.round((brandCount / maxBrands) * 100) : undefined
}
buttonLabel={maxBrands === 0 ? "Plan Seçin" : undefined}
buttonTo={maxBrands === 0 ? "/dashboard/subscription" : undefined}
/>
{/* 4. Profil */}
<ProfileCard
name={user?.name ?? "—"}
email={user?.email ?? "—"}
initials={initials}
/>
</div>
)}
{/* ─── ACTIVE SUBSCRIPTION ────────────────────────────────────── */}
<div className="space-y-4">
<div>
<h2 className="font-[family-name:var(--font-display)] text-2xl font-bold tracking-tight">
Aktif Aboneliğiniz
</h2>
<p className="mt-1 text-sm text-muted-foreground">
Şase çözme ve parça kataloğuna tek tıkla erişin
</p>
</div>
{subLoading ? (
<Skeleton className="h-48 w-full rounded-2xl" />
) : subscription && subscription.status === "active" ? (
<div className="rounded-2xl border border-border bg-background p-5 sm:p-6">
<div className="flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between">
{/* Plan Info */}
<div className="flex-1 space-y-4">
<div className="flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary/10">
<Crown className="size-5 text-primary" />
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-bold">
{subscription.plan?.name ?? "Aktif Plan"}
</h3>
<Badge variant="default" className="bg-emerald-600 text-xs">
Aktif
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{subscription.billingPeriod === "yearly"
? "Yıllık"
: "Aylık"}{" "}
abonelik
</p>
</div>
</div>
{/* Brands */}
{subscription.brands && subscription.brands.length > 0 && (
<div className="flex flex-wrap gap-2">
{subscription.brands.map((b) => (
<Badge key={b.brandId} variant="outline">
{b.brandName}
</Badge>
))}
</div>
)}
{/* Dates */}
<div className="flex flex-wrap gap-6 text-sm">
{subscription.startDate && (
<div className="flex items-center gap-1.5 text-muted-foreground">
<Calendar className="size-3.5" />
Başlangıç:{" "}
<span className="font-medium text-foreground">
{new Date(subscription.startDate).toLocaleDateString(
"tr-TR",
)}
</span>
</div>
)}
{subscription.endDate && (
<div className="flex items-center gap-1.5 text-muted-foreground">
<Calendar className="size-3.5" />
Bitiş:{" "}
<span className="font-medium text-foreground">
{new Date(subscription.endDate).toLocaleDateString(
"tr-TR",
)}
</span>
</div>
)}
</div>
{/* Features */}
<div className="flex flex-wrap gap-x-4 gap-y-1">
{[
"Sınırsız şase arama",
"Parça kataloğu",
"İnteraktif şema",
].map((f) => (
<span
key={f}
className="flex items-center gap-1.5 text-sm text-muted-foreground"
>
<CheckCircle2 className="size-3.5 text-emerald-500" />
{f}
</span>
))}
</div>
</div>
{/* CTA */}
<div className="flex flex-col gap-2">
<Link to="/dashboard/search">
<Button className="w-full rounded-lg sm:w-auto">
Şase Ara
<ArrowRight className="ml-1.5 size-4" />
</Button>
</Link>
<Link to="/dashboard/subscription">
<Button
variant="outline"
className="w-full rounded-lg border-border text-sm sm:w-auto"
>
Plan Detayları
</Button>
</Link>
</div>
</div>
</div>
) : (
/* No subscription */
<div className="rounded-2xl border border-dashed border-border bg-background p-8 text-center">
<div className="mx-auto flex size-14 items-center justify-center rounded-2xl bg-muted">
<Crown className="size-6 text-muted-foreground" />
</div>
<h3 className="mt-4 text-lg font-semibold">Henüz aktif planınız yok</h3>
<p className="mt-2 text-sm text-muted-foreground">
Şase çözme, parça kataloğu ve interaktif şemalara erişmek için bir plan seçin.
</p>
<Link to="/dashboard/subscription">
<Button className="mt-4 rounded-lg">
Plan Seçin
<ArrowRight className="ml-1.5 size-4" />
</Button>
</Link>
</div>
)}
</div>
{/* ─── SON ARAMALAR ───────────────────────────────────────────── */}
{history && history.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="font-[family-name:var(--font-display)] text-2xl font-bold tracking-tight">
Son Aramalar
</h2>
<Link
to="/dashboard/history"
className="text-sm text-muted-foreground transition hover:text-foreground"
>
Tümünü Gör
</Link>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{history.slice(0, 3).map((v: any) => (
<Link
key={v.id}
to="/dashboard/vehicles/$id"
params={{ id: v.id }}
>
<div className="group flex items-center gap-4 rounded-2xl border border-border bg-background p-4 transition-colors hover:bg-accent">
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-muted">
<Car className="size-4 text-muted-foreground" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">
{v.brandName} {v.model}
</p>
<p className="truncate font-mono text-xs text-muted-foreground">
{v.vin}
</p>
</div>
<Badge variant="secondary" className="shrink-0">
{v.year}
</Badge>
</div>
</Link>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -1,42 +1,136 @@
import { useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState, useEffect, useRef } from "react";
import { createFileRoute, useNavigate, Link } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Card, CardContent } from "@sase/ui";
import { Search } from "lucide-react";
import { Button, Input, Badge, Separator } from "@sase/ui";
import {
Search,
Car,
Loader2,
Clock,
AlertCircle,
} from "lucide-react";
import { api, ApiError } from "@/lib/api-client";
import { toast } from "sonner";
// ─── HELPERS ──────────────────────────────────────────────────────────────────
const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/;
function isValidVin(vin: string): boolean {
if (!vin || vin.length !== 17) return false;
return VIN_REGEX.test(vin.toUpperCase());
}
/** Auto-correct I→1, O→0, Q→9 and return which chars were fixed */
function sanitizeVin(raw: string): { cleaned: string; corrections: string[] } {
const corrections: string[] = [];
const cleaned = raw.replace(/[IOQioq]/g, (ch) => {
const upper = ch.toUpperCase();
if (upper === "I") { corrections.push("I→1"); return "1"; }
if (upper === "O") { corrections.push("O→0"); return "0"; }
corrections.push("Q→9"); return "9";
});
return { cleaned, corrections };
}
// ─── ROUTE ────────────────────────────────────────────────────────────────────
export const Route = createFileRoute("/dashboard/search")({
component: SearchPage,
});
function SearchPage() {
const navigate = useNavigate();
const inputRef = useRef<HTMLInputElement>(null);
const [vin, setVin] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showHistory, setShowHistory] = useState(false);
// Live preview state
const [preview, setPreview] = useState<{
brandName: string;
model: string;
year: string;
engine: string;
source?: string;
} | null>(null);
const [previewLoading, setPreviewLoading] = useState(false);
const [previewError, setPreviewError] = useState(false);
const { data: history } = useQuery({
queryKey: ["vehicles", "history"],
queryFn: () => api.get<any[]>("/vehicles/history?limit=20"),
});
// ─── Ctrl+K shortcut ───────────────────────────────────────────────────────
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
inputRef.current?.focus();
}
}
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
// ─── VIN preview fetch (17 chars) ──────────────────────────────────────────
useEffect(() => {
if (vin.length !== 17) {
setPreview(null);
setPreviewError(false);
return;
}
const controller = new AbortController();
setPreviewLoading(true);
setPreviewError(false);
fetch(`/api/vehicles/preview/${vin}`, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("Not found");
return res.json();
})
.then((data) => {
const r = data.data !== undefined ? data.data : data;
if (r?.brandName) {
setPreview({
brandName: r.brandName,
model: r.model || "—",
year: r.year ? String(r.year) : "—",
engine: r.engine || "—",
source: r.source,
});
} else {
setPreviewError(true);
}
setPreviewLoading(false);
})
.catch((err) => {
if (err.name !== "AbortError") {
setPreviewError(true);
setPreviewLoading(false);
}
});
return () => controller.abort();
}, [vin]);
// ─── Auto-focus ────────────────────────────────────────────────────────────
useEffect(() => {
inputRef.current?.focus();
}, []);
// ─── Submit ────────────────────────────────────────────────────────────────
async function handleSearch(e: React.FormEvent) {
e.preventDefault();
setError(null);
const cleanVin = vin.toUpperCase().trim();
if (!isValidVin(cleanVin)) {
setError("Geçersiz VIN. 17 karakter olmalı, I, O, Q harfleri kullanılamaz.");
setError(
"Geçersiz şase. 17 karakter olmalı, I, O, Q harfleri kullanılamaz.",
);
return;
}
@@ -53,68 +147,224 @@ function SearchPage() {
} else {
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
}
toast.error("VIN arama başarısız");
toast.error("Şase arama başarısız");
setLoading(false);
}
}
function handleVinChange(raw: string) {
const upper = raw.toUpperCase();
const { cleaned, corrections } = sanitizeVin(upper);
setVin(cleaned);
setError(null);
if (corrections.length > 0) {
const unique = [...new Set(corrections)];
toast.info(`Otomatik düzeltildi: ${unique.join(", ")}`, {
description: "Şase numarasında I, O, Q harfleri kullanılamaz",
duration: 2500,
});
}
}
function fillExampleVin() {
setVin("WVWZZZ1JZ3W597935");
inputRef.current?.focus();
}
return (
<div className="mx-auto max-w-4xl space-y-6">
<div>
<h2 className="text-2xl font-bold">VIN Arama</h2>
<p className="text-muted-foreground">Araç VIN numarasını girerek yedek parça kataloğuna erişin</p>
<div className="mx-auto max-w-3xl space-y-6">
{/* ─── SECTION 1: Hero Input Card ─────────────────────────────────── */}
<div className="rounded-2xl border border-border bg-background p-6 sm:p-8">
{/* Header */}
<div className="flex flex-col items-center text-center">
<div className="inline-flex size-14 items-center justify-center rounded-2xl bg-muted">
<Search className="size-6 text-muted-foreground" />
</div>
<h2 className="mt-4 font-[family-name:var(--font-display)] text-2xl font-bold tracking-tight">
Şase Arama
</h2>
<p className="mt-1 text-sm text-muted-foreground">
Araç şase numarasını girerek yedek parça kataloğuna erişin
</p>
</div>
<Separator className="my-6 bg-border" />
{/* Form */}
<form onSubmit={handleSearch} className="space-y-4">
{/* Input */}
<div className="relative">
<Search className="absolute left-4 top-1/2 size-5 -translate-y-1/2 text-muted-foreground" />
<Input
ref={inputRef}
placeholder="Şase numarasını girin (17 karakter)"
value={vin}
onChange={(e) => handleVinChange(e.target.value)}
maxLength={17}
className={`h-14 rounded-xl bg-muted/50 pl-12 font-mono tracking-wider ${vin.length === 0 ? "pr-24" : "pr-4"}`}
/>
{vin.length === 0 && (
<div className="pointer-events-none absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1 text-xs text-muted-foreground">
<kbd className="rounded border border-border bg-muted px-1.5 py-0.5 font-sans text-[10px]">
Ctrl
</kbd>
<span>+</span>
<kbd className="rounded border border-border bg-muted px-1.5 py-0.5 font-sans text-[10px]">
K
</kbd>
</div>
)}
</div>
{/* 17-segment progress bar */}
<div className="flex gap-0.5">
{Array.from({ length: 17 }).map((_, i) => (
<div
key={i}
className={`h-1.5 flex-1 rounded-full transition-colors duration-200 ${
i < vin.length ? "bg-emerald-500" : "bg-muted"
}`}
/>
))}
</div>
{/* Counter + Example VIN */}
<div className="flex items-center justify-between text-sm">
<span className="tabular-nums text-muted-foreground">
{vin.length}/17 karakter
</span>
<button
type="button"
onClick={fillExampleVin}
className="text-muted-foreground underline underline-offset-4 transition hover:text-foreground"
>
Örnek şase deneyin &rarr;
</button>
</div>
{/* Submit button */}
<Button
type="submit"
disabled={loading || vin.length !== 17}
className="h-12 w-full rounded-xl"
>
{loading ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : (
<Search className="mr-2 size-4" />
)}
Şase Çöz
</Button>
{/* Error card */}
{error && (
<div className="flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 p-4">
<AlertCircle className="mt-0.5 size-4 shrink-0 text-destructive" />
<p className="text-sm text-destructive">{error}</p>
</div>
)}
</form>
</div>
<Card>
<CardContent className="pt-6">
<form onSubmit={handleSearch} className="flex gap-3">
<div className="relative flex-1">
<Input
placeholder="VIN numarasını girin (17 karakter)"
value={vin}
onChange={(e) => {
setVin(e.target.value.toUpperCase());
setError(null);
}}
onFocus={() => setShowHistory(true)}
onBlur={() => setShowHistory(false)}
maxLength={17}
className="font-mono text-lg tracking-wider"
/>
{showHistory && vin.length === 0 && history && history.length > 0 && (
<div className="absolute z-50 mt-1 max-h-80 w-full overflow-y-auto rounded-md border bg-popover shadow-md">
{history.map((v: any) => (
<div
key={v.id}
className="flex cursor-pointer items-center justify-between px-3 py-2 hover:bg-accent"
onMouseDown={(e) => {
e.preventDefault();
setVin(v.vin);
setShowHistory(false);
}}
>
<span className="font-mono text-sm">{v.vin}</span>
<span className="text-xs text-muted-foreground">
{v.brandName} {v.model} {v.year}
</span>
</div>
))}
</div>
)}
</div>
<Button type="submit" disabled={loading || vin.length !== 17}>
{loading ? (
<span className="animate-spin">...</span>
) : (
<Search className="h-4 w-4" />
)}
Ara
</Button>
</form>
{error && <p className="mt-3 text-sm text-destructive">{error}</p>}
</CardContent>
</Card>
{/* ─── SECTION 2: Live Preview Card ───────────────────────────────── */}
{previewLoading && (
<div className="flex items-center justify-center gap-3 rounded-2xl border border-border bg-background p-6">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
<span className="text-sm text-muted-foreground">
Araç bilgileri alınıyor...
</span>
</div>
)}
{preview && !previewLoading && (
<div className="rounded-2xl border border-emerald-500/30 bg-background p-5 sm:p-6">
<div className="flex items-start gap-4">
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-emerald-500/10">
<Car className="size-5 text-emerald-500" />
</div>
<div className="min-w-0 flex-1">
<p className="font-[family-name:var(--font-display)] text-lg font-bold">
{preview.brandName} {preview.model}
</p>
<p className="mt-0.5 text-sm text-muted-foreground">
{preview.year}
{preview.engine !== "—" && ` — Motor: ${preview.engine}`}
</p>
<div className="mt-3 flex flex-wrap gap-2">
<Badge
variant="default"
className="bg-emerald-600 text-xs text-white"
>
Araç tanımlandı
</Badge>
{preview.source && (
<Badge variant="outline" className="text-xs">
Kaynak: {preview.source}
</Badge>
)}
</div>
</div>
</div>
<Separator className="my-4 bg-border" />
<p className="text-sm text-muted-foreground">
Şase Çöz butonuna tıklayarak tam parça kataloğuna erişin.
</p>
</div>
)}
{/* ─── SECTION 4: Son Aramalar ────────────────────────────────────── */}
{history && history.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="size-4 text-muted-foreground" />
<h3 className="font-[family-name:var(--font-display)] text-lg font-bold tracking-tight">
Son Aramalar
</h3>
</div>
<Link
to="/dashboard/history"
className="text-sm text-muted-foreground transition hover:text-foreground"
>
Tümünü Gör &rarr;
</Link>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{history.slice(0, 6).map((v: any) => (
<button
key={v.id}
type="button"
onClick={() => {
setVin(v.vin);
inputRef.current?.focus();
}}
className="group flex items-center gap-4 rounded-2xl border border-border bg-background p-4 text-left transition-colors hover:bg-accent"
>
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-muted">
<Car className="size-4 text-muted-foreground" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">
{v.brandName} {v.model}
</p>
<p className="truncate font-mono text-xs text-muted-foreground">
{v.vin}
</p>
</div>
<Badge variant="secondary" className="shrink-0">
{v.year}
</Badge>
</button>
))}
</div>
<p className="text-center text-xs text-muted-foreground">
Geçmiş aramayı tıklayın şase otomatik doldurulur
</p>
</div>
)}
</div>
);
}

View File

@@ -22,11 +22,13 @@ import {
Moon,
Loader2,
Gift,
Puzzle,
} from "lucide-react";
import { useState, useEffect } from "react";
import { Player } from "@remotion/player";
import { SchemaDemo } from "@/remotion/SchemaDemo";
import { DashboardDemo } from "@/remotion/DashboardDemo";
import { EcommerceDemo } from "@/remotion/EcommerceDemo";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
// ─── DATA ────────────────────────────────────────────────────────────────────
@@ -255,6 +257,37 @@ const COMPARISON = [
{ label: "Aylık zaman tasarrufu", manual: "—", sase: "~40 saat" },
];
const TESTIMONIALS = [
{
name: "Ahmet Y.",
role: "Yedek Parça Mağazası Sahibi",
initials: "AY",
text: "Eskiden bir parça aramak için 3-4 katalog açıyordum. Sase.tr ile şase numarasını giriyorum, 30 saniyede doğru OEM kodunu buluyorum. İade oranımız %80 düştü.",
rating: 5,
},
{
name: "Mehmet K.",
role: "Oto Servis İşletmecisi",
initials: "MK",
text: "Sase.tr'ye geçtiğimizden beri yanlış parça siparişlerimiz neredeyse sıfıra indi. Aylık 40 saatin üzerinde zaman tasarrufu sağlıyoruz.",
rating: 5,
},
{
name: "Özge D.",
role: "E-Ticaret Yöneticisi",
initials: "ÖD",
text: "Online mağazamıza VIN entegrasyonu ekledik. Müşteriler şase numarasını giriyor, sadece uyumlu parçalar listeleniyor. İade oranımız %42 düştü.",
rating: 5,
},
{
name: "Burak T.",
role: "Filo Yöneticisi",
initials: "BT",
text: "200+ araçlık filomuz için parça yönetimi çok kolay hale geldi. Özellikle interaktif şema özelliği montaj sürecini çok hızlandırıyor.",
rating: 5,
},
];
// ─── HELPERS ─────────────────────────────────────────────────────────────────
function SectionBadge({ children }: { children: React.ReactNode }) {
@@ -498,6 +531,21 @@ function HomePage() {
Her yanlış parça siparişi ortalama 450 TL'ye mal olur
</div>
{/* Social proof badge */}
<div className="mt-3 inline-flex items-center gap-3 rounded-full border border-border bg-muted/50 px-4 py-2 text-xs text-muted-foreground">
<div className="flex -space-x-2">
{["AY", "MK", "ÖD", "BT"].map((initials) => (
<div key={initials} className="flex size-6 items-center justify-center rounded-full border-2 border-background bg-muted text-[10px] font-medium">
{initials}
</div>
))}
</div>
<Separator orientation="vertical" className="h-4 bg-border" />
<span>⭐ 4.9/5</span>
<Separator orientation="vertical" className="h-4 bg-border" />
<span>500+ İşletme Güveniyor</span>
</div>
<h1 className="font-[family-name:var(--font-display)] text-4xl font-bold leading-[1.1] tracking-tight sm:text-5xl lg:text-7xl">
Müşteriniz Şase Verdi —
<br />
@@ -884,11 +932,13 @@ function HomePage() {
</section>
{/* ─── 8. RAKAMLARLA SASE.TR ────────────────────────────────────── */}
<section className="px-4 py-24 sm:px-6">
<section className="bg-[#09090b] px-4 py-24 text-white sm:px-6">
<div className="mx-auto max-w-7xl">
<div className="text-center">
<SectionBadge>Platform</SectionBadge>
<h2 className="mt-4 font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-1.5 text-xs font-medium text-neutral-400">
Platform
</span>
<h2 className="mt-4 font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight text-white sm:text-4xl lg:text-5xl">
Rakamlarla Sase.tr
</h2>
</div>
@@ -897,12 +947,12 @@ function HomePage() {
{STATS.map((stat) => (
<div
key={stat.label}
className="rounded-2xl border border-border bg-surface p-6 text-center sm:p-8"
className="rounded-2xl border border-white/10 bg-white/5 p-6 text-center sm:p-8"
>
<p className="font-[family-name:var(--font-display)] text-4xl font-bold text-foreground sm:text-5xl">
<p className="font-[family-name:var(--font-display)] text-4xl font-bold text-white sm:text-5xl">
{stat.value}
</p>
<p className="mt-2 text-sm text-muted-foreground">
<p className="mt-2 text-sm text-neutral-400">
{stat.label}
</p>
</div>
@@ -963,6 +1013,142 @@ function HomePage() {
</div>
</section>
{/* ─── 9.5. E-TİCARET ENTEGRASYONU ────────────────────────────── */}
<section className="px-4 py-24 sm:px-6">
<div className="mx-auto max-w-7xl">
<div className="text-center">
<SectionBadge>E-Ticaret Entegrasyonu</SectionBadge>
<h2 className="mt-4 font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
Kendi Sitenize Şase Çözme Gücü Katın
</h2>
<p className="mx-auto mt-4 max-w-2xl text-muted-foreground">
Müşterileriniz yanlış parça sipariş edip iade mi açıyor?
Sase.tr entegrasyonu ile VIN bazlı filtreleme ekleyin —
iadeler düşsün, dönüşüm artsın.
</p>
</div>
<div className="mt-16 overflow-hidden rounded-2xl bg-surface p-6 sm:p-8 lg:p-12">
<div className="flex flex-col gap-8 lg:flex-row lg:items-center lg:gap-12">
{/* Left — Video */}
<div className="flex-1">
<BrowserFrame title="otoyedekparca.co — Sase.tr Entegrasyonu">
<Player
component={EcommerceDemo}
compositionWidth={960}
compositionHeight={540}
durationInFrames={420}
fps={30}
loop
autoPlay
style={{ width: "100%" }}
inputProps={{ isDark }}
/>
</BrowserFrame>
</div>
{/* Right — Text + Bullets + CTA */}
<div className="flex-1 space-y-5">
<div className="inline-flex items-center gap-2 rounded-lg bg-muted px-3 py-1.5 text-sm text-muted-foreground">
<Puzzle className="size-4" />
Kolay Entegrasyon
</div>
<h3 className="font-[family-name:var(--font-display)] text-2xl font-bold sm:text-3xl">
Birkaç Satır Kod, Büyük Fark
</h3>
<p className="text-muted-foreground">
E-ticaret sitenize Sase.tr'nin VIN çözme motorunu entegre edin.
Müşterileriniz şase numarasını girsin sadece araçlarına uygun
parçalar listelensin.
</p>
<ul className="space-y-2">
{[
"API & Widget — Birkaç satır kodla sitenize entegre edin",
"VIN Bazlı Filtreleme — Sadece uyumlu parçalar görünsün",
"İade Oranını Düşürün — Doğru parça, ilk seferde",
"White-Label — Widget sitenizin tasarımına uyum sağlar",
].map((b) => (
<li
key={b}
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<CheckCircle2 className="size-4 shrink-0 text-emerald-500" />
{b}
</li>
))}
</ul>
<Link to="/contact">
<Button
variant="outline"
className="mt-2 rounded-full border-border text-muted-foreground hover:bg-muted hover:text-foreground"
>
İletişime Geçin
<ArrowRight className="ml-2 size-4" />
</Button>
</Link>
</div>
</div>
</div>
{/* Stats grid */}
<div className="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
{[
{ value: "%42", label: "Daha Az İade" },
{ value: "%35", label: "Daha Yüksek Dönüşüm" },
{ value: "<30dk", label: "Entegrasyon Süresi" },
].map((stat) => (
<div
key={stat.label}
className="rounded-2xl border border-border bg-surface p-6 text-center"
>
<p className="font-[family-name:var(--font-display)] text-3xl font-bold text-emerald-500 sm:text-4xl">
{stat.value}
</p>
<p className="mt-2 text-sm text-muted-foreground">
{stat.label}
</p>
</div>
))}
</div>
</div>
</section>
{/* ─── 9.6. TESTIMONIALS ────────────────────────────────────── */}
<section className="bg-surface-alt px-4 py-24 sm:px-6">
<div className="mx-auto max-w-7xl">
<div className="text-center">
<SectionBadge>Müşteri Yorumları</SectionBadge>
<h2 className="mt-4 font-[family-name:var(--font-display)] text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
İşletme Sahipleri Ne Diyor?
</h2>
</div>
<div className="mt-16 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{TESTIMONIALS.map((t) => (
<div key={t.name} className="rounded-2xl border border-border bg-surface p-6">
<div className="flex gap-0.5">
{Array.from({ length: t.rating }).map((_, i) => (
<span key={i} className="text-amber-400"></span>
))}
</div>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
&ldquo;{t.text}&rdquo;
</p>
<div className="mt-4 flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-full bg-muted text-sm font-medium">
{t.initials}
</div>
<div>
<p className="text-sm font-medium">{t.name}</p>
<p className="text-xs text-muted-foreground">{t.role}</p>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* ─── 10. PRICING ─────────────────────────────────────────────── */}
<section className="px-4 py-24 sm:px-6">
<div className="mx-auto max-w-6xl">
@@ -972,7 +1158,7 @@ function HomePage() {
Günde 7 TL'den Başlayan Fiyatlar
</h2>
<p className="mx-auto mt-4 max-w-xl text-muted-foreground">
Tek bir yanlış parça iadesinin maliyetinden daha az. Tüm fiyatlar KDV dahildir.
Tek bir yanlış parça iadesinin maliyetinden daha az.
</p>
</div>
@@ -1077,7 +1263,7 @@ function HomePage() {
</div>
<div className="flex-1">
<h3 className="font-[family-name:var(--font-display)] text-xl font-bold sm:text-2xl">
Meslektaşlarınızı Davet Edin
Arkadaşını Davet Et
</h3>
<p className="mt-2 text-sm text-muted-foreground">
3 davet = +7 gün ücretsiz, 5 davet = +1 ay ücretsiz
@@ -1189,19 +1375,19 @@ function HomePage() {
<h4 className="text-sm font-semibold text-muted-foreground">Şirket</h4>
<ul className="mt-3 space-y-2 text-sm text-muted-foreground">
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/about" className="transition hover:text-foreground">
Hakkımızda
</a>
</Link>
</li>
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/blog" className="transition hover:text-foreground">
Blog
</a>
</Link>
</li>
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/contact" className="transition hover:text-foreground">
İletişim
</a>
</Link>
</li>
</ul>
</div>
@@ -1211,19 +1397,19 @@ function HomePage() {
<h4 className="text-sm font-semibold text-muted-foreground">Yasal</h4>
<ul className="mt-3 space-y-2 text-sm text-muted-foreground">
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/privacy" className="transition hover:text-foreground">
Gizlilik Politikası
</a>
</Link>
</li>
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/terms" className="transition hover:text-foreground">
Kullanım Koşulları
</a>
</Link>
</li>
<li>
<a href="#" className="transition hover:text-foreground">
<Link to="/kvkk" className="transition hover:text-foreground">
KVKK
</a>
</Link>
</li>
</ul>
</div>
@@ -1232,9 +1418,15 @@ function HomePage() {
<Separator className="bg-border" />
<div className="flex flex-col items-center justify-between gap-4 py-6 sm:flex-row">
<p className="text-sm text-muted-foreground/70">
&copy; {new Date().getFullYear()} Sase.tr. Tüm hakları saklıdır.
</p>
<div className="flex flex-col items-center gap-3 sm:flex-row">
<p className="text-sm text-muted-foreground/70">
&copy; {new Date().getFullYear()} Sase.tr. Tüm hakları saklıdır.
</p>
<div className="flex items-center gap-2 text-sm text-muted-foreground/70">
<span className="size-2 rounded-full bg-emerald-500" />
Tüm servisler aktif
</div>
</div>
<div className="flex items-center gap-4 text-muted-foreground/70">
{/* Social links - minimal */}
<a href="#" className="transition hover:text-foreground">

View File

@@ -0,0 +1,203 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
export const Route = createFileRoute("/kvkk")({
component: KvkkPage,
});
function KvkkPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-3xl px-4 py-16">
<h1 className="text-4xl font-bold">
KVKK Aydınlatma Metni
</h1>
<p className="mt-2 text-sm text-muted-foreground">
6698 Sayılı Kişisel Verilerin Korunması Kanunu Kapsamında
Aydınlatma Metni
</p>
<div className="mt-8 space-y-8 text-muted-foreground leading-relaxed">
<section>
<h2 className="text-xl font-semibold text-foreground">
1. Veri Sorumlusu
</h2>
<p className="mt-3">
6698 sayılı Kişisel Verilerin Korunması Kanunu ("KVKK")
uyarınca, kişisel verileriniz veri sorumlusu sıfatıyla
Sase.tr tarafından aşağıda ıklanan kapsamda işlenmektedir.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
2. İşlenen Kişisel Veriler
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>
<strong>Kimlik Bilgileri:</strong> Ad, soyad
</li>
<li>
<strong>İletişim Bilgileri:</strong> E-posta adresi,
telefon numarası (isteğe bağlı)
</li>
<li>
<strong>İşlem Güvenliği:</strong> IP adresi, oturum
bilgileri, log kayıtları
</li>
<li>
<strong>Kullanım Verileri:</strong> Arama geçmişi,
platform kullanım istatistikleri
</li>
<li>
<strong>Finansal Bilgiler:</strong> Fatura bilgileri,
abonelik durumu
</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
3. Kişisel Verilerin İşlenme Amaçları
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>
Üyelik işlemlerinin gerçekleştirilmesi ve hesap yönetimi
</li>
<li>
Platform hizmetlerinin sunulması ve iyileştirilmesi
</li>
<li>
Ödeme ve faturalama işlemlerinin yürütülmesi
</li>
<li>
Müşteri destek taleplerinin karşılanması
</li>
<li>
Yasal yükümlülüklerin yerine getirilmesi (5651 sayılı
Kanun kapsamında log tutma yükümlülüğü dahil)
</li>
<li>
İstatistiksel analiz ve hizmet geliştirme
</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
4. Hukuki Sebepler
</h2>
<p className="mt-3">
Kişisel verileriniz, KVKK'nın 5. maddesinde belirtilen
aşağıdaki hukuki sebeplere dayanılarak işlenmektedir:
</p>
<ul className="mt-2 list-disc space-y-2 pl-6">
<li>Sözleşmenin kurulması ve ifası</li>
<li>Kanunlarda açıkça öngörülmesi</li>
<li>Veri sorumlusunun meşru menfaati</li>
<li>Açık rıza (gerekli hallerde)</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
5. Verilerin Aktarımı
</h2>
<p className="mt-3">
Kişisel verileriniz, hizmetin sunulması amacıyla yurt
içindeki iş ortaklarımız (hosting, ödeme altyapısı) ve
yasal zorunluluk halinde yetkili kamu kurum ve kuruluşlarıyla
paylaşılabilir. Yurt dışına veri aktarımı, KVKK'nın 9.
maddesi kapsamındaki güvencelere uygun olarak
gerçekleştirilir.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
6. Veri Saklama Süresi
</h2>
<p className="mt-3">
Kişisel verileriniz, işlenme amaçlarının gerektirdiği süre
boyunca ve yasal saklama yükümlülükleri kapsamında muhafaza
edilir. Süre sona erdiğinde veriler silinir, yok edilir
veya anonim hale getirilir.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
7. Haklarınız
</h2>
<p className="mt-3">
KVKK'nın 11. maddesi uyarınca aşağıdaki haklara
sahipsiniz:
</p>
<ul className="mt-2 list-disc space-y-2 pl-6">
<li>Kişisel verilerinizin işlenip işlenmediğini öğrenme</li>
<li>İşlenmişse buna ilişkin bilgi talep etme</li>
<li>
İşlenme amacını ve amacına uygun kullanılıp
kullanılmadığını öğrenme
</li>
<li>
Yurt içinde veya yurt dışında aktarıldığı üçüncü
kişileri bilme
</li>
<li>
Eksik veya yanlış işlenmişse düzeltilmesini isteme
</li>
<li>
KVKK'nın 7. maddesindeki şartlar çerçevesinde silinmesini
veya yok edilmesini isteme
</li>
<li>
İşlenen verilerin münhasıran otomatik sistemler
vasıtasıyla analiz edilmesi suretiyle aleyhinize bir
sonucun ortaya çıkmasına itiraz etme
</li>
<li>
Kanuna aykırı işlenmesi sebebiyle zarara uğramanız
halinde zararın giderilmesini talep etme
</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
8. Başvuru Yöntemi
</h2>
<p className="mt-3">
Yukarıda belirtilen haklarınızı kullanmak için{" "}
<a
href="mailto:info@sase.tr"
className="text-primary underline"
>
info@sase.tr
</a>{" "}
adresine kimliğinizi tespit edici belgelerle birlikte
yazılı olarak başvurabilirsiniz. Başvurular en geç 30 gün
içinde sonuçlandırılır.
</p>
</section>
</div>
</main>
</div>
);
}

View File

@@ -81,7 +81,7 @@ function PricingPage() {
<div className="text-center">
<h1 className="text-4xl font-bold">Fiyatlandırma</h1>
<p className="mt-4 text-lg text-muted-foreground">
İhtiyacınıza uygun planı seçin. Tüm fiyatlar KDV dahildir.
İhtiyacınıza uygun planı seçin.
</p>
</div>

View File

@@ -0,0 +1,150 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
export const Route = createFileRoute("/privacy")({
component: PrivacyPage,
});
function PrivacyPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-3xl px-4 py-16">
<h1 className="text-4xl font-bold">Gizlilik Politikası</h1>
<p className="mt-2 text-sm text-muted-foreground">
Son güncelleme: 15 Şubat 2026
</p>
<div className="mt-8 space-y-8 text-muted-foreground leading-relaxed">
<section>
<h2 className="text-xl font-semibold text-foreground">
1. Genel Bakış
</h2>
<p className="mt-3">
Sase.tr ("Platform") olarak kullanıcılarımızın gizliliğine
önem veriyoruz. Bu politika, kişisel verilerinizin nasıl
toplandığını, işlendiğini ve korunduğunu ıklar.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
2. Toplanan Veriler
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>
<strong>Hesap Bilgileri:</strong> Ad, e-posta adresi,
telefon numarası (isteğe bağlı).
</li>
<li>
<strong>Kullanım Verileri:</strong> Arama geçmişi, VIN
sorguları, sayfa görüntüleme istatistikleri.
</li>
<li>
<strong>Teknik Veriler:</strong> IP adresi, tarayıcı türü,
cihaz bilgisi, çerez verileri.
</li>
<li>
<strong>Ödeme Bilgileri:</strong> Ödeme işlemleri üçüncü
parti ödeme sağlayıcıları aracılığıyla gerçekleştirilir.
Kredi kartı bilgileri tarafımızca saklanmaz.
</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
3. Verilerin Kullanım Amacı
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>Hizmetin sunulması ve iyileştirilmesi</li>
<li>Kullanıcı hesaplarının yönetimi</li>
<li>Müşteri desteği sağlanması</li>
<li>Yasal yükümlülüklerin yerine getirilmesi</li>
<li>İstatistiksel analiz ve raporlama</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
4. Veri Paylaşımı
</h2>
<p className="mt-3">
Kişisel verileriniz, yasal zorunluluklar dışında üçüncü
taraflarla paylaşılmaz. Hizmet sağlayıcılarımız (hosting,
ödeme altyapısı) yalnızca hizmetin işletilmesi için gerekli
olan verilere erişir ve gizlilik sözleşmeleri ile bağlıdır.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
5. Çerezler
</h2>
<p className="mt-3">
Platform, oturum yönetimi ve kullanıcı deneyimini
iyileştirmek amacıyla çerezler kullanır. Zorunlu çerezler
hizmetin çalışması için gereklidir. Analitik çerezler
kullanıcı tercihine bağlıdır.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
6. Veri Güvenliği
</h2>
<p className="mt-3">
Verileriniz SSL/TLS şifrelemesi ile korunur. Sunucularımız
güvenli veri merkezlerinde barındırılır ve düzenli güvenlik
denetimleri yapılır.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
7. Haklarınız
</h2>
<p className="mt-3">
KVKK kapsamında kişisel verilerinize erişim, düzeltme,
silme ve işlemeye itiraz etme haklarına sahipsiniz.
Talepleriniz için{" "}
<a
href="mailto:info@sase.tr"
className="text-primary underline"
>
info@sase.tr
</a>{" "}
adresine başvurabilirsiniz.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
8. Değişiklikler
</h2>
<p className="mt-3">
Bu politika zaman zaman güncellenebilir. Önemli
değişiklikler e-posta veya platform içi bildirim yoluyla
duyurulur.
</p>
</section>
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,150 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@sase/ui";
export const Route = createFileRoute("/terms")({
component: TermsPage,
});
function TermsPage() {
return (
<div className="min-h-screen">
<header className="border-b">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<Link to="/" className="text-xl font-bold">
Sase.tr
</Link>
<div className="flex items-center gap-4">
<Link to="/login">
<Button variant="ghost">Giriş Yap</Button>
</Link>
<Link to="/register">
<Button>Kayıt Ol</Button>
</Link>
</div>
</div>
</header>
<main className="container mx-auto max-w-3xl px-4 py-16">
<h1 className="text-4xl font-bold">Kullanım Koşulları</h1>
<p className="mt-2 text-sm text-muted-foreground">
Son güncelleme: 15 Şubat 2026
</p>
<div className="mt-8 space-y-8 text-muted-foreground leading-relaxed">
<section>
<h2 className="text-xl font-semibold text-foreground">
1. Kabul ve Onay
</h2>
<p className="mt-3">
Sase.tr platformunu ("Platform") kullanarak bu kullanım
koşullarını kabul etmiş sayılırsınız. Koşulları kabul
etmiyorsanız platformu kullanmayınız.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
2. Hizmet Tanımı
</h2>
<p className="mt-3">
Platform, şase numarası (VIN) ile araç tanımlama, orijinal
yedek parça kataloğuna erişim ve interaktif şema görüntüleme
hizmetleri sunar. Hizmetler abonelik modeli ile sunulur.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
3. Hesap Oluşturma
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>Kayıt sırasında doğru ve güncel bilgi vermekle yükümlüsünüz.</li>
<li>Hesap bilgilerinizin güvenliğinden siz sorumlusunuz.</li>
<li>Hesabınızı üçüncü kişilerle paylaşamazsınız.</li>
<li>18 yaşından küçükler platformu kullanamaz.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
4. Abonelik ve Ödeme
</h2>
<ul className="mt-3 list-disc space-y-2 pl-6">
<li>
Abonelikler aylık veya yıllık olarak faturalandırılır.
</li>
<li>
Yıllık aboneliklerde indirimli fiyat uygulanır.
</li>
<li>
Abonelik, dönem sonunda otomatik olarak yenilenir.
</li>
<li>
İptal işlemi mevcut dönemin sonunda geçerli olur;
kalan süre için iade yapılmaz.
</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
5. Kullanım Kuralları
</h2>
<p className="mt-3">Aşağıdaki eylemler yasaktır:</p>
<ul className="mt-2 list-disc space-y-2 pl-6">
<li>Platformdaki verilerin toplu olarak çekilmesi (scraping)</li>
<li>API'nin izinsiz kullanımı veya kötüye kullanımı</li>
<li>Diğer kullanıcıların hizmetini engelleyecek faaliyetler</li>
<li>Platform verilerinin ticari amaçla yeniden dağıtılması</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
6. Fikri Mülkiyet
</h2>
<p className="mt-3">
Platform üzerindeki tüm içerik, tasarım, yazılım ve
veritabanı Sase.tr'ye aittir. Kullanıcılar yalnızca
kişisel kullanım amacıyla erişim hakkına sahiptir.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
7. Sorumluluk Sınırı
</h2>
<p className="mt-3">
Platform, sunulan bilgilerin doğruluğu için azami özeni
gösterir ancak verilerin eksiksiz veya hatasız olduğunu
garanti etmez. Yedek parça alım kararlarında son sorumluluk
kullanıcıya aittir.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
8. Hizmet Değişiklikleri
</h2>
<p className="mt-3">
Sase.tr, hizmet içeriğini, fiyatlandırmayı ve bu koşulları
önceden bildirimde bulunarak değiştirme hakkını saklı tutar.
Önemli değişiklikler en az 30 gün önce duyurulur.
</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground">
9. Uyuşmazlık Çözümü
</h2>
<p className="mt-3">
Bu koşullar Türkiye Cumhuriyeti kanunlarına tabidir.
Uyuşmazlıklarda İstanbul mahkemeleri ve icra daireleri
yetkilidir.
</p>
</section>
</div>
</main>
</div>
);
}