feat(web): pricing — by-the-numbers stat strip, verifiable facts only
Adds a four-stat band below the hero CTAs — 27 brand catalogs, 1M+ OEM/alt parts, 30-day free trial, 7-day no-questions refund. Big tabular-nums numerals with small labels; divides on sm+. Every number is product or policy fact we can defend — no fabricated customer counts, no testimonial copy. Side effect: FAQPage JSON-LD now injected into <head> via useEffect (was an inline <script dangerouslySetInnerHTML>). Same SEO payload, less attack surface, tagged with data-sase-faq so it's diff-friendly.
This commit is contained in:
@@ -545,6 +545,12 @@
|
||||
"yearly": "Yearly"
|
||||
},
|
||||
"yearlyBadge": "2 months free",
|
||||
"stats": {
|
||||
"brands": { "value": "27", "label": "brand catalogs" },
|
||||
"parts": { "value": "1M+", "label": "OEM & alternative parts" },
|
||||
"trial": { "value": "30 days", "label": "full access, free" },
|
||||
"refund": { "value": "7 days", "label": "no-questions refund" }
|
||||
},
|
||||
"pageTitle": "Pricing — Sase.tr | Chassis Search Plans",
|
||||
"how": {
|
||||
"title": "How it works",
|
||||
|
||||
@@ -545,6 +545,12 @@
|
||||
"yearly": "Yıllık"
|
||||
},
|
||||
"yearlyBadge": "2 ay bedava",
|
||||
"stats": {
|
||||
"brands": { "value": "27", "label": "marka kataloğu" },
|
||||
"parts": { "value": "1M+", "label": "OEM ve alternatif parça" },
|
||||
"trial": { "value": "30 gün", "label": "tüm özellikler ücretsiz" },
|
||||
"refund": { "value": "7 gün", "label": "koşulsuz iade" }
|
||||
},
|
||||
"pageTitle": "Fiyatlandırma — Sase.tr | Şase Sorgulama Planları",
|
||||
"how": {
|
||||
"title": "Nasıl çalışır",
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Info,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/pricing")({
|
||||
component: PricingPage,
|
||||
@@ -134,6 +134,8 @@ function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PricingStatsStrip />
|
||||
|
||||
<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>
|
||||
@@ -162,6 +164,37 @@ function PricingPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Stat row — verifiable product/policy facts only (catalog size, trial length,
|
||||
// refund window). No customer-count claims, no testimonials.
|
||||
function PricingStatsStrip() {
|
||||
const { t } = useTranslation();
|
||||
const items = [
|
||||
{ value: "pricing.stats.brands.value", label: "pricing.stats.brands.label" },
|
||||
{ value: "pricing.stats.parts.value", label: "pricing.stats.parts.label" },
|
||||
{ value: "pricing.stats.trial.value", label: "pricing.stats.trial.label" },
|
||||
{ value: "pricing.stats.refund.value", label: "pricing.stats.refund.label" },
|
||||
];
|
||||
return (
|
||||
<div className="mx-auto mt-12 max-w-5xl">
|
||||
<dl className="grid grid-cols-2 gap-y-6 rounded-2xl border border-border bg-muted/20 p-6 sm:grid-cols-4 sm:gap-y-0 sm:divide-x sm:divide-border">
|
||||
{items.map((it) => (
|
||||
<div
|
||||
key={it.value}
|
||||
className="flex flex-col items-center justify-center px-4 text-center"
|
||||
>
|
||||
<dt className="text-3xl font-bold leading-none tabular-nums text-foreground sm:text-4xl">
|
||||
{t(it.value)}
|
||||
</dt>
|
||||
<dd className="mt-2 text-xs uppercase tracking-wider text-muted-foreground sm:text-sm sm:normal-case sm:tracking-normal">
|
||||
{t(it.label)}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BillingPeriodToggle({
|
||||
value,
|
||||
onChange,
|
||||
@@ -354,25 +387,40 @@ export function PricingHowItWorks() {
|
||||
);
|
||||
}
|
||||
|
||||
const FAQ_IDS = [1, 2, 3, 4, 5, 6] as const;
|
||||
|
||||
export function PricingFaq() {
|
||||
const { t } = useTranslation();
|
||||
const ids = [1, 2, 3, 4, 5, 6] as const;
|
||||
// Emit FAQPage JSON-LD so Google can render rich-result Q/A blocks in SERP
|
||||
// — pure SEO upside, no UI cost. Renders inert <script type=application/ld+json>.
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: ids.map((i) => ({
|
||||
"@type": "Question",
|
||||
name: t(`pricing.faq.q${i}`),
|
||||
acceptedAnswer: { "@type": "Answer", text: t(`pricing.faq.a${i}`) },
|
||||
})),
|
||||
};
|
||||
|
||||
// Inject FAQPage JSON-LD into <head> so Google can render rich-result Q/A
|
||||
// blocks in SERP. Imperative head injection keeps us off React's `script`
|
||||
// path and avoids dangerouslySetInnerHTML — content is i18n-sourced anyway,
|
||||
// but this is also the cleanest way to render structured data.
|
||||
useEffect(() => {
|
||||
const payload = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: FAQ_IDS.map((i) => ({
|
||||
"@type": "Question",
|
||||
name: t(`pricing.faq.q${i}`),
|
||||
acceptedAnswer: { "@type": "Answer", text: t(`pricing.faq.a${i}`) },
|
||||
})),
|
||||
};
|
||||
const script = document.createElement("script");
|
||||
script.type = "application/ld+json";
|
||||
script.setAttribute("data-sase-faq", "1");
|
||||
script.text = JSON.stringify(payload);
|
||||
document.head.appendChild(script);
|
||||
return () => {
|
||||
script.remove();
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
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) => (
|
||||
{FAQ_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}`)}
|
||||
@@ -384,11 +432,6 @@ export function PricingFaq() {
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: structured-data payload, content is from our own i18n bundle. */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user