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:
1262
apps/web/src/remotion/EcommerceDemo.tsx
Normal file
1262
apps/web/src/remotion/EcommerceDemo.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
“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.”
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
87
apps/web/src/routes/about.tsx
Normal file
87
apps/web/src/routes/about.tsx
Normal 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ı, açı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 iş 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>
|
||||
);
|
||||
}
|
||||
83
apps/web/src/routes/blog.tsx
Normal file
83
apps/web/src/routes/blog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
87
apps/web/src/routes/contact.tsx
Normal file
87
apps/web/src/routes/contact.tsx
Normal 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 iş 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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
© {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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
461
apps/web/src/routes/dashboard/index.tsx
Normal file
461
apps/web/src/routes/dashboard/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 →
|
||||
</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 →
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
“{t.text}”
|
||||
</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">
|
||||
© {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">
|
||||
© {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">
|
||||
|
||||
203
apps/web/src/routes/kvkk.tsx
Normal file
203
apps/web/src/routes/kvkk.tsx
Normal 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 açı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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
150
apps/web/src/routes/privacy.tsx
Normal file
150
apps/web/src/routes/privacy.tsx
Normal 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 açı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>
|
||||
);
|
||||
}
|
||||
150
apps/web/src/routes/terms.tsx
Normal file
150
apps/web/src/routes/terms.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user