feat(FN-204): i18n pricing/subscription/payment surfaces
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged: - feat(FN-204): i18n pricing/subscription/payment surfaces Files changed: .../web/src/components/payment/payment-content.tsx | 11 +- apps/web/src/lib/i18n.ts | 17 ++- apps/web/src/messages/en.json | 27 ++++- apps/web/src/messages/tr.json | 27 ++++- .../src/routes/dashboard/subscription/index.tsx | 4 +- apps/web/src/routes/pricing.tsx | 127 +++++++++++---------- 6 files changed, 135 insertions(+), 78 deletions(-) Fusion-Task-Id: FN-204
This commit is contained in:
@@ -381,7 +381,9 @@ function SubscriptionPage() {
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("subscription.currentPlan")}
|
||||
</span>
|
||||
<Badge className="bg-brand text-brand-foreground">Full Paket</Badge>
|
||||
<Badge className="bg-brand text-brand-foreground">
|
||||
{t("subscription.plans.full.name")}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { usePageMeta } from "@/hooks/use-page-meta";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Button } from "@sase/ui";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { Badge } from "@sase/ui";
|
||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
export const Route = createFileRoute("/pricing")({
|
||||
component: PricingPage,
|
||||
@@ -10,60 +12,57 @@ export const Route = createFileRoute("/pricing")({
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: "1 Marka",
|
||||
description: "Tek marka için yedek parça erişimi",
|
||||
priceMonthly: "200",
|
||||
priceYearly: "2.000",
|
||||
features: ["1 marka seçimi", "Sınırsız VIN arama", "Parça kataloğu", "Şema görüntüleyici"],
|
||||
key: "brand1",
|
||||
brandLimit: 1,
|
||||
priceMonthly: 20000,
|
||||
priceYearly: 200000,
|
||||
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer"],
|
||||
},
|
||||
{
|
||||
name: "2 Marka",
|
||||
description: "İki farklı marka için erişim",
|
||||
priceMonthly: "350",
|
||||
priceYearly: "3.500",
|
||||
key: "brand2",
|
||||
brandLimit: 2,
|
||||
priceMonthly: 35000,
|
||||
priceYearly: 350000,
|
||||
popular: true,
|
||||
features: [
|
||||
"2 marka seçimi",
|
||||
"Sınırsız VIN arama",
|
||||
"Parça kataloğu",
|
||||
"Şema görüntüleyici",
|
||||
"Öncelikli destek",
|
||||
],
|
||||
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer", "prioritySupport"],
|
||||
},
|
||||
{
|
||||
name: "3 Marka",
|
||||
description: "Üç marka için kapsamlı erişim",
|
||||
priceMonthly: "500",
|
||||
priceYearly: "5.000",
|
||||
features: [
|
||||
"3 marka seçimi",
|
||||
"Sınırsız VIN arama",
|
||||
"Parça kataloğu",
|
||||
"Şema görüntüleyici",
|
||||
"Öncelikli destek",
|
||||
],
|
||||
key: "brand3",
|
||||
brandLimit: 3,
|
||||
priceMonthly: 50000,
|
||||
priceYearly: 500000,
|
||||
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer", "prioritySupport"],
|
||||
},
|
||||
{
|
||||
name: "Full Paket",
|
||||
description: "Tüm markalara sınırsız erişim",
|
||||
priceMonthly: "999",
|
||||
priceYearly: "9.990",
|
||||
key: "full",
|
||||
brandLimit: 999,
|
||||
priceMonthly: 99900,
|
||||
priceYearly: 999000,
|
||||
features: [
|
||||
"Tüm markalar",
|
||||
"Sınırsız VIN arama",
|
||||
"Parça kataloğu",
|
||||
"Şema görüntüleyici",
|
||||
"Öncelikli destek",
|
||||
"OEM parça arama",
|
||||
"allBrands",
|
||||
"vinSearch",
|
||||
"partsCatalog",
|
||||
"schemaViewer",
|
||||
"prioritySupport",
|
||||
"oemSearch",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function formatTRY(amount: number): string {
|
||||
return new Intl.NumberFormat("tr-TR", {
|
||||
style: "currency",
|
||||
currency: "TRY",
|
||||
minimumFractionDigits: 2,
|
||||
}).format(amount / 100);
|
||||
}
|
||||
|
||||
function PricingPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
usePageMeta({
|
||||
title: "Fiyatlandırma — Sase.tr | Şase Sorgulama Planları",
|
||||
description:
|
||||
"200 TL/ay'dan başlayan şase numarası ve OEM parça sorgulama planları. 30 gün ücretsiz deneyin.",
|
||||
title: t("pricing.pageTitle"),
|
||||
description: t("pricing.metaDescription"),
|
||||
canonical: "https://sase.tr/pricing",
|
||||
});
|
||||
|
||||
@@ -76,10 +75,10 @@ function PricingPage() {
|
||||
</Link>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link to="/login">
|
||||
<Button variant="ghost">Giriş Yap</Button>
|
||||
<Button variant="ghost">{t("common.login")}</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
<Button>Kayıt Ol</Button>
|
||||
<Button>{t("common.register")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,40 +86,52 @@ function PricingPage() {
|
||||
|
||||
<main id="main-content" className="container mx-auto px-4 py-24">
|
||||
<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.</p>
|
||||
<h1 className="text-4xl font-bold">{t("pricing.title")}</h1>
|
||||
<p className="mt-4 text-lg text-muted-foreground">{t("pricing.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{plans.map((plan) => (
|
||||
<Card
|
||||
key={plan.name}
|
||||
key={plan.key}
|
||||
className={plan.popular ? "border-primary shadow-lg relative" : ""}
|
||||
>
|
||||
{plan.popular && (
|
||||
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2">Popüler</Badge>
|
||||
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
{t("subscription.popular")}
|
||||
</Badge>
|
||||
)}
|
||||
<CardHeader>
|
||||
<CardTitle>{plan.name}</CardTitle>
|
||||
<CardDescription>{plan.description}</CardDescription>
|
||||
<CardTitle>{t(`subscription.plans.${plan.key}.name`)}</CardTitle>
|
||||
<CardDescription>{t(`subscription.plans.${plan.key}.description`)}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<span className="text-3xl font-bold">{plan.priceMonthly} TL</span>
|
||||
<span className="text-muted-foreground">/ay</span>
|
||||
<span className="text-3xl font-bold">{formatTRY(plan.priceMonthly)}</span>
|
||||
<span className="text-muted-foreground">{t("common.perMonth")}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">veya {plan.priceYearly} TL/yıl</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("pricing.orYearly", {
|
||||
price: formatTRY(plan.priceYearly).replace(/[₺\s]/g, "").trim(),
|
||||
})}
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{plan.features.map((f) => (
|
||||
<li key={f} className="flex items-center gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
{plan.features.map((f) => {
|
||||
const featureLabel =
|
||||
f === "brandSelection"
|
||||
? t(`subscription.features.${f}`, { count: plan.brandLimit })
|
||||
: t(`subscription.features.${f}`);
|
||||
return (
|
||||
<li key={f} className="flex items-center gap-2">
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
{featureLabel}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<Link to="/register">
|
||||
<Button className="w-full" variant={plan.popular ? "default" : "outline"}>
|
||||
Başla
|
||||
{t("pricing.getStarted")}
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user