Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Cold visitors landing straight on /pricing had no product context. Added a compact 3-step band between the credibility strip and FAQ: enter the chassis number -> see the parts (OEM codes, diagrams, compatible parts) -> pick the right part by comparing prices. Grounded in the actual product, no ordering claims. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
200 lines
7.1 KiB
TypeScript
200 lines
7.1 KiB
TypeScript
import { CredibilityStrip } from "@/components/credibility-strip";
|
|
import { SiteHeader } from "@/components/site-header";
|
|
import { usePageMeta } from "@/hooks/use-page-meta";
|
|
import { useTranslation } from "@/lib/i18n";
|
|
import { FULL_PLAN_BRAND_LIMIT, formatTRY } from "@sase/shared";
|
|
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, ChevronDown, Info } from "lucide-react";
|
|
|
|
export const Route = createFileRoute("/pricing")({
|
|
component: PricingPage,
|
|
});
|
|
|
|
export const plans = [
|
|
{
|
|
key: "brand1",
|
|
brandLimit: 1,
|
|
priceMonthly: 20000,
|
|
priceYearly: 200000,
|
|
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer"],
|
|
},
|
|
{
|
|
key: "brand2",
|
|
brandLimit: 2,
|
|
priceMonthly: 35000,
|
|
priceYearly: 350000,
|
|
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer", "prioritySupport"],
|
|
},
|
|
{
|
|
key: "brand3",
|
|
brandLimit: 3,
|
|
priceMonthly: 50000,
|
|
priceYearly: 500000,
|
|
features: ["brandSelection", "vinSearch", "partsCatalog", "schemaViewer", "prioritySupport"],
|
|
},
|
|
{
|
|
key: "full",
|
|
brandLimit: FULL_PLAN_BRAND_LIMIT,
|
|
priceMonthly: 99900,
|
|
priceYearly: 999000,
|
|
popular: true,
|
|
features: [
|
|
"allBrands",
|
|
"vinSearch",
|
|
"partsCatalog",
|
|
"schemaViewer",
|
|
"prioritySupport",
|
|
"oemSearch",
|
|
],
|
|
},
|
|
];
|
|
|
|
function PricingPage() {
|
|
const { t } = useTranslation();
|
|
|
|
usePageMeta({
|
|
title: t("pricing.pageTitle"),
|
|
description: t("pricing.metaDescription"),
|
|
canonical: "https://sase.tr/pricing",
|
|
});
|
|
|
|
return (
|
|
<div className="min-h-screen">
|
|
<SiteHeader />
|
|
|
|
<main id="main-content" className="container mx-auto px-4 py-24">
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<p className="text-sm font-semibold uppercase tracking-wider text-primary">
|
|
{t("pricing.title")}
|
|
</p>
|
|
<h1 className="mt-3 text-4xl font-bold leading-tight">{t("pricing.heroTitle")}</h1>
|
|
<p className="mt-4 text-lg text-muted-foreground">{t("pricing.heroSubtitle")}</p>
|
|
<div className="mt-6 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm text-muted-foreground">
|
|
{[
|
|
"pricing.trialFree",
|
|
"subscription.trustNoCard",
|
|
"subscription.trustCancelAnytime",
|
|
"subscription.trustRefund",
|
|
].map((k) => (
|
|
<span key={k} className="flex items-center gap-1.5">
|
|
<Check className="h-4 w-4 text-primary" />
|
|
{t(k)}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mx-auto mt-10 flex max-w-2xl items-start justify-center gap-2 rounded-lg border border-border bg-muted/30 px-4 py-2.5 text-sm text-muted-foreground">
|
|
<Info className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
<span>{t("subscription.planHelper")}</span>
|
|
</div>
|
|
|
|
<div className="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
|
{plans.map((plan) => (
|
|
<Card
|
|
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">
|
|
{t("subscription.popular")}
|
|
</Badge>
|
|
)}
|
|
<CardHeader>
|
|
<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">{formatTRY(plan.priceMonthly)}</span>
|
|
<span className="text-muted-foreground">{t("common.perMonth")}</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t("pricing.orYearly", { price: formatTRY(plan.priceYearly) })}{" "}
|
|
<span className="font-medium text-primary">{t("pricing.yearlySave")}</span>
|
|
</p>
|
|
<ul className="space-y-2 text-sm">
|
|
{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"}>
|
|
{t("pricing.getStarted")}
|
|
</Button>
|
|
</Link>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mx-auto mt-12 max-w-5xl">
|
|
<CredibilityStrip />
|
|
</div>
|
|
|
|
<PricingHowItWorks />
|
|
|
|
<PricingFaq />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PricingHowItWorks() {
|
|
const { t } = useTranslation();
|
|
const steps = [1, 2, 3] as const;
|
|
return (
|
|
<section className="mx-auto mt-20 max-w-5xl">
|
|
<h2 className="text-center text-2xl font-bold">{t("pricing.how.title")}</h2>
|
|
<div className="mt-8 grid gap-6 sm:grid-cols-3">
|
|
{steps.map((i) => (
|
|
<div key={i} className="rounded-2xl border border-border bg-muted/20 p-6 text-center">
|
|
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-primary font-bold text-primary-foreground">
|
|
{i}
|
|
</div>
|
|
<h3 className="mt-4 font-semibold">{t(`pricing.how.s${i}t`)}</h3>
|
|
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
|
{t(`pricing.how.s${i}d`)}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function PricingFaq() {
|
|
const { t } = useTranslation();
|
|
const ids = [1, 2, 3, 4, 5, 6] as const;
|
|
return (
|
|
<section className="mx-auto mt-20 max-w-3xl">
|
|
<h2 className="text-center text-2xl font-bold">{t("pricing.faq.title")}</h2>
|
|
<div className="mt-8 divide-y divide-border overflow-hidden rounded-2xl border border-border">
|
|
{ids.map((i) => (
|
|
<details key={i} className="group">
|
|
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 p-5 font-medium">
|
|
{t(`pricing.faq.q${i}`)}
|
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
|
|
</summary>
|
|
<p className="px-5 pb-5 text-sm leading-6 text-muted-foreground">
|
|
{t(`pricing.faq.a${i}`)}
|
|
</p>
|
|
</details>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|