dev #51

Merged
root merged 3 commits from dev into main 2026-05-25 22:26:37 +03:00
6 changed files with 120 additions and 58 deletions

View File

@@ -6,7 +6,7 @@
<title>Şase Numarası Sorgulama &amp; OEM Parça Kataloğu | Sase.tr</title>
<meta
name="description"
content="Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 27+ marka, 243.000+ parça. Yanlış sipariş yapmadan doğru parçayı bulun."
content="Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 50+ marka, 1M+ parça. Yanlış sipariş yapmadan doğru parçayı bulun."
/>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
@@ -16,7 +16,7 @@
<meta property="og:type" content="website" />
<meta property="og:url" content="https://sase.tr" />
<meta property="og:title" content="Şase Numarası Sorgulama &amp; OEM Parça Kataloğu | Sase.tr" />
<meta property="og:description" content="Araç şase numarasını girin, saniyeler içinde doğru OEM parça kodlarına ulaşın. 27+ marka, 243.000+ parça." />
<meta property="og:description" content="Araç şase numarasını girin, saniyeler içinde doğru OEM parça kodlarına ulaşın. 50+ marka, 1M+ parça." />
<meta property="og:image" content="https://sase.tr/og-image.png" />
<meta property="og:locale" content="tr_TR" />
<meta property="og:site_name" content="Sase.tr" />
@@ -24,7 +24,7 @@
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Şase Numarası Sorgulama &amp; OEM Parça Kataloğu | Sase.tr" />
<meta name="twitter:description" content="Araç şase numarasını girin, saniyeler içinde doğru OEM parça kodlarına ulaşın. 27+ marka, 243.000+ parça." />
<meta name="twitter:description" content="Araç şase numarasını girin, saniyeler içinde doğru OEM parça kodlarına ulaşın. 50+ marka, 1M+ parça." />
<meta name="twitter:image" content="https://sase.tr/og-image.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />

View File

@@ -2,7 +2,7 @@ import { useEffect } from "react";
const DEFAULT_TITLE = "Şase Numarası Sorgulama & OEM Parça Kataloğu | Sase.tr";
const DEFAULT_DESCRIPTION =
"Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 27+ marka, 243.000+ parça. Yanlış sipariş yapmadan doğru parçayı bulun.";
"Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 50+ marka, 1M+ parça. Yanlış sipariş yapmadan doğru parçayı bulun.";
const DEFAULT_CANONICAL = "https://sase.tr";
interface PageMetaOptions {

View File

@@ -109,11 +109,11 @@ function AuthLayout() {
<circle cx="7" cy="17" r="2" />
<circle cx="17" cy="17" r="2" />
</svg>
27 Marka
50+ Marka
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface/60 px-3 py-1.5 text-xs text-foreground/80 backdrop-blur-sm">
<Database className="size-3" />
<span className="tabular">243K+ OEM Parça</span>
<span className="tabular">1M+ OEM Parça</span>
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-border bg-surface/60 px-3 py-1.5 text-xs text-foreground/80 backdrop-blur-sm">
<ShieldCheck className="size-3" />

View File

@@ -1,3 +1,4 @@
import { api } from "@/lib/api-client";
import { signIn, signUp } from "@/lib/auth-client";
import { startAction } from "@/lib/faro";
import { track as trackMeta } from "@/lib/meta-pixel";
@@ -7,7 +8,7 @@ import { Button } from "@sase/ui";
import { Input } from "@sase/ui";
import { Label } from "@sase/ui";
import { Link, createFileRoute } from "@tanstack/react-router";
import { ShieldCheck } from "lucide-react";
import { Check, Eye, EyeOff, ShieldCheck } from "lucide-react";
import { useState } from "react";
export const Route = createFileRoute("/_auth/register")({
@@ -25,6 +26,13 @@ function RegisterPage() {
const [password, setPassword] = useState("");
const [refCode, setRefCode] = useState(ref || "");
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showRefCode, setShowRefCode] = useState(!!ref);
const passwordRules = [
{ ok: password.length >= 8, label: "8+ karakter" },
{ ok: /[A-Z]/.test(password), label: "1 büyük harf" },
{ ok: /[0-9]/.test(password), label: "1 rakam" },
];
// Land new users straight on the VIN search (value-first). The activation
// "motion" + trial provisioning now runs as a modal on the search page
// (see WelcomeOnboardingModal), so we carry welcome/vin/ref across.
@@ -40,16 +48,40 @@ function RegisterPage() {
setLoading(true);
try {
await signUp.email({ name, email, password, callbackURL: redirectUrl });
capture("user_signed_up", { method: "email" });
trackMeta("CompleteRegistration", { method: "email" });
// better-auth returns { error } rather than throwing (see login.tsx).
// The old code awaited without checking error, so failed signups still
// reported success, fired user_signed_up, and redirected to the dashboard.
const { error } = await signUp.email({ name, email, password, callbackURL: redirectUrl });
if (error) {
let exists = false;
try {
({ exists } = await api.post<{ exists: boolean }>("/users/check-email", { email }));
} catch {
// fall through to a generic message
}
if (exists) {
toast.error("Bu e-posta zaten kayıtlı", {
description: "Bunun yerine giriş yapmayı deneyin.",
});
} else {
toast.error("Kayıt başarısız", {
description:
error.message ?? "Şifre en az 8 karakter, 1 büyük harf ve 1 rakam içermelidir.",
});
}
return;
}
// The referral code travels via `?ref=` in redirectUrl; the welcome
// onboarding modal on the search page is the single place that applies it
// (covers both email and Google OAuth signups).
capture("user_signed_up", { method: "email" });
trackMeta("CompleteRegistration", { method: "email" });
toast.success("Hesap oluşturuldu!");
window.location.href = redirectUrl;
} catch {
toast.error("Kayıt başarısız. Bu e-posta zaten kullanılıyor olabilir.");
toast.error("Bağlantı hatası", { description: "Lütfen tekrar deneyin." });
} finally {
setLoading(false);
}
@@ -137,38 +169,74 @@ function RegisterPage() {
</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>
<div className="space-y-2">
<Label htmlFor="refCode">
Referans Kodu <span className="text-muted-foreground">(opsiyonel)</span>
</Label>
<Input
id="refCode"
type="text"
placeholder="örn. MEU8EHF7"
value={refCode}
onChange={(e) => setRefCode(e.target.value.toUpperCase())}
maxLength={20}
autoComplete="off"
className="font-mono uppercase"
/>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="En az 8 karakter"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
className="pr-10"
/>
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground transition-colors hover:text-foreground"
aria-label={showPassword ? "Şifreyi gizle" : "Şifreyi göster"}
>
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
</button>
</div>
{password.length > 0 && (
<ul className="flex flex-wrap gap-x-3 gap-y-1 text-xs">
{passwordRules.map((rule) => (
<li
key={rule.label}
className={`flex items-center gap-1 ${
rule.ok ? "text-emerald-600 dark:text-emerald-400" : "text-muted-foreground"
}`}
>
<Check className={`size-3 ${rule.ok ? "opacity-100" : "opacity-30"}`} />
{rule.label}
</li>
))}
</ul>
)}
</div>
{showRefCode ? (
<div className="space-y-2">
<Label htmlFor="refCode">
Referans Kodu <span className="text-muted-foreground">(opsiyonel)</span>
</Label>
<Input
id="refCode"
type="text"
placeholder="örn. MEU8EHF7"
value={refCode}
onChange={(e) => setRefCode(e.target.value.toUpperCase())}
maxLength={20}
autoComplete="off"
className="font-mono uppercase"
/>
</div>
) : (
<button
type="button"
onClick={() => setShowRefCode(true)}
className="text-sm text-muted-foreground transition-colors hover:text-foreground hover:underline"
>
Referans kodun var mı?
</button>
)}
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Kayıt yapılıyor..." : "Kayıt Ol"}
{loading ? "Kayıt yapılıyor..." : "Ücretsiz Başla"}
</Button>
</form>
<p className="text-center text-xs text-muted-foreground">
Kayıt olunca hemen VIN aramaya başlayın
Kayıt olunca hemen VIN aramaya başlarsın · Spam göndermeyiz
</p>
<p className="text-center text-sm">

View File

@@ -1,6 +1,6 @@
import { usePageMeta } from "@/hooks/use-page-meta";
import { SiteHeader } from "@/components/site-header";
import { useBlogPost } from "@/hooks/use-blog";
import { usePageMeta } from "@/hooks/use-page-meta";
import { Button } from "@sase/ui";
import { Link, createFileRoute } from "@tanstack/react-router";
import { ChevronRight } from "lucide-react";
@@ -97,7 +97,7 @@ const POSTS: Record<string, BlogPost> = {
<p>
Sase.tr platformunda VIN numaranızı girerek saniyeler içinde aracınızın tam teknik
bilgilerine ve orijinal parça kataloğuna erişebilirsiniz. 27'den fazla markayı destekleyen
bilgilerine ve orijinal parça kataloğuna erişebilirsiniz. 50'den fazla markayı destekleyen
platformumuzla yanlış parça siparişlerine son verin.
</p>
</div>
@@ -252,8 +252,8 @@ const POSTS: Record<string, BlogPost> = {
<p>
Sase.tr, Türkiye'nin yedek parça sektörüne özgü geliştirilen bu dijital dönüşümün
öncüsüdür. VIN/şase numarası sorgulama, çoklu katalog entegrasyonu ve interaktif şema
görüntüleme özellikleriyle geleneksel yapış biçimlerini dönüştürmektedir. Platform, 27+
marka ve 243.000'den fazla OEM parça numarasıyla sektörün en kapsamlı dijital kataloğunu
görüntüleme özellikleriyle geleneksel yapış biçimlerini dönüştürmektedir. Platform, 50+
marka ve 1 milyondan fazla OEM parça numarasıyla sektörün en kapsamlı dijital kataloğunu
sunmaktadır.
</p>
</div>
@@ -331,7 +331,7 @@ const POSTS: Record<string, BlogPost> = {
Parça bulamadığınızda kategori ağacında bir seviye yukarı çıkarak daha geniş bir arama
yapabilirsiniz.
</li>
<li>27+ markanın tamamına erişmek için Full Paket aboneliği en avantajlı seçenektir.</li>
<li>50+ markanın tamamına erişmek için Full Paket aboneliği en avantajlı seçenektir.</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Sonuç</h2>
@@ -355,18 +355,14 @@ const mdComponents = {
h3: (props: React.ComponentProps<"h3">) => (
<h3 className="text-lg font-semibold text-foreground" {...props} />
),
ul: (props: React.ComponentProps<"ul">) => (
<ul className="list-disc space-y-2 pl-6" {...props} />
),
ul: (props: React.ComponentProps<"ul">) => <ul className="list-disc space-y-2 pl-6" {...props} />,
ol: (props: React.ComponentProps<"ol">) => (
<ol className="list-decimal space-y-2 pl-6" {...props} />
),
strong: (props: React.ComponentProps<"strong">) => (
<strong className="text-foreground" {...props} />
),
a: (props: React.ComponentProps<"a">) => (
<a className="text-foreground underline" {...props} />
),
a: (props: React.ComponentProps<"a">) => <a className="text-foreground underline" {...props} />,
};
function MarkdownBody({ markdown }: { markdown: string }) {
@@ -406,9 +402,7 @@ function BlogPostPage() {
else if (isLoading) body = <p className="text-muted-foreground">Yükleniyor</p>;
else
body = (
<p className="text-muted-foreground">
{isError ? "Yazı bulunamadı." : "Yazı yüklenemedi."}
</p>
<p className="text-muted-foreground">{isError ? "Yazı bulunamadı." : "Yazı yüklenemedi."}</p>
);
return (

View File

@@ -256,7 +256,7 @@ const BENTO_ITEMS = [
},
{
icon: Car,
title: "27+ Marka",
title: "50+ Marka",
description: "Avrupa, Asya ve Amerikan markaları tek çatı altında.",
stat: null,
wide: false,
@@ -294,7 +294,7 @@ const BENTO_ITEMS = [
const STATS = [
{ value: "1.2sn", label: "Ortalama sorgu süresi" },
{ value: "27", label: "Desteklenen marka" },
{ value: "243K+", label: "OEM parça numarası" },
{ value: "1M+", label: "OEM parça numarası" },
{ value: "%99.9", label: "Platform erişilebilirlik" },
];
@@ -385,7 +385,7 @@ export function HomePage() {
usePageMeta({
title: "Şase Numarası Sorgulama & OEM Parça Kataloğu | Sase.tr",
description:
"Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 27+ marka, 243.000+ parça. Yanlış sipariş yapmadan doğru parçayı bulun.",
"Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 50+ marka, 1M+ parça. Yanlış sipariş yapmadan doğru parçayı bulun.",
canonical: "https://sase.tr",
});
@@ -947,8 +947,8 @@ export function HomePage() {
<div className="mt-8 flex flex-wrap items-center justify-center gap-4 sm:gap-6">
{[
{ icon: Zap, label: "Ortalama 1.2sn Sonuç" },
{ icon: Car, label: "27 Marka Desteği" },
{ icon: Database, label: "243.000+ OEM Parça" },
{ icon: Car, label: "50+ Marka Desteği" },
{ icon: Database, label: "1M+ OEM Parça" },
].map(({ icon: Icon, label }) => (
<div
key={label}
@@ -966,7 +966,7 @@ export function HomePage() {
<section className="border-y border-border py-12">
<div className="mx-auto max-w-7xl px-4 sm:px-6">
<p className="mb-8 text-center text-sm text-muted-foreground">
Desteklenen 27+ Otomobil Markası
Desteklenen 50+ Otomobil Markası
</p>
</div>
<div className="carousel-track relative overflow-hidden">
@@ -1669,7 +1669,7 @@ export function HomePage() {
</p>
<ul className="mt-5 grid grid-cols-1 gap-x-6 gap-y-3 sm:grid-cols-2">
{[
"Tüm 27+ markaya erişim",
"Tüm 50+ markaya erişim",
"Sınırsız VIN arama",
"OEM parça kataloğu",
"İnteraktif şema görüntüleyici",