feat(demo): VIN-input CTA card replaces plain "Hesap Aç" footer

Demo footer was a single-button conversion ("Hesap Aç" → /register with no
VIN). For a parts-shop owner who just browsed the example VW Golf, the
next natural action is "let me try MY customer's VIN" — not "let me click
register and re-type". Adds an inline VIN input to the footer CTA on both
/demo and /demo/categories/:id; submitting routes to /register?vin=… so
the register teaser (see ed74e2f) lifts off where this leaves off.

* New DemoVinCta component owns the form + analytics. Empty submit still
  works as plain "Hesap Aç" (no VIN); typing a VIN flips the button label
  to "Bu VIN için Hesap Aç".
* B2B copy retained from prior commits: "Sınırsız şase sorgulamak için
  ücretsiz hesap aç" headline, "Kart bilgisi gerekmez · 30 gün ücretsiz ·
  istediğin an iptal" trust strip — see [[sase-b2b-copy-not-consumer]].
* Input placeholder reads "Müşterinizin şase numarası (17 karakter)" — B2B
  framing ("müşterinizin") not "kendi aracınız", per the same memory.
* Mobile-friendly: input + button stack vertically <sm, side-by-side ≥sm;
  full-width button on mobile clears the Chatwoot widget.
* PostHog: emits demo_to_register_click with {source, has_vin, vin_length}
  so we can split conversion by "browsed → typed VIN → registered" vs
  "browsed → bare register".

DemoBanner stays a single-CTA sticky anchor (the "Yeni VIN sorgula" gate
lives in the footer card instead — banner real estate is too narrow on
mobile for a useful input).
This commit is contained in:
2026-06-02 01:18:09 +03:00
parent ed74e2f361
commit 6643a8edad
3 changed files with 89 additions and 44 deletions

View File

@@ -0,0 +1,77 @@
import { capture } from "@/lib/posthog";
import { Button, Input } from "@sase/ui";
import { useNavigate } from "@tanstack/react-router";
import { ArrowRight } from "lucide-react";
import { type FormEvent, useState } from "react";
interface DemoVinCtaProps {
/** Analytics surface tag — e.g. "footer", "category_footer". */
source: string;
}
/**
* Conversion card shown at the bottom of /demo pages: B2B trust strip + a
* VIN input the visitor can paste their OWN VIN into. Submitting the form
* (or clicking the empty-input button) routes to /register with the VIN
* carried via `?vin=` so the register teaser can lift off where this leaves
* off. The "Yeni VIN sorgula" gate from the 1C spec lives here rather than
* in the sticky DemoBanner so the banner stays a single-CTA conversion
* anchor without crowding mobile widths.
*/
export function DemoVinCta({ source }: DemoVinCtaProps) {
const navigate = useNavigate();
const [vin, setVin] = useState("");
const trimmed = vin.trim().toUpperCase();
const hasVin = trimmed.length > 0;
const submitLabel = hasVin ? "Bu VIN için Hesap Aç" : "Hesap Aç";
const handleSubmit = (e?: FormEvent) => {
e?.preventDefault();
capture("demo_to_register_click", {
source,
has_vin: hasVin,
vin_length: trimmed.length,
});
navigate({
to: "/register",
search: hasVin ? { vin: trimmed } : {},
});
};
return (
<div className="mb-20 mt-8 rounded-lg border border-border bg-muted/30 p-5 sm:mb-0">
<div className="mb-3 flex flex-col gap-1">
<p className="text-sm font-semibold">Sınırsız şase sorgulamak için ücretsiz hesap </p>
<p className="text-xs text-muted-foreground">
Kart bilgisi gerekmez · 30 gün ücretsiz · istediğin an iptal
</p>
</div>
<form onSubmit={handleSubmit} className="flex flex-col gap-2 sm:flex-row">
<Input
value={vin}
onChange={(e) => setVin(e.target.value.toUpperCase())}
placeholder="Müşterinizin şase numarası (17 karakter)"
maxLength={17}
inputMode="text"
autoCapitalize="characters"
autoCorrect="off"
autoComplete="off"
spellCheck={false}
enterKeyHint="go"
aria-label="Müşterinizin şase numarası"
className="font-mono"
/>
<Button
type="submit"
className="w-full shrink-0 sm:w-auto"
data-faro-user-action-name={`demo-vin-cta-${source}`}
>
{submitLabel}
<ArrowRight className="ml-1 h-4 w-4" />
</Button>
</form>
</div>
);
}

View File

@@ -1,11 +1,12 @@
import { DemoBanner } from "@/components/demo/demo-banner";
import { DemoVinCta } from "@/components/demo/demo-vin-cta";
import { usePageMeta } from "@/hooks/use-page-meta";
import { ApiError, api } from "@/lib/api-client";
import { KEYS_8 } from "@/lib/keys";
import { capture } from "@/lib/posthog";
import { cleanModelName } from "@/lib/vehicle";
import type { CategoryNode, Vehicle } from "@sase/shared";
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@sase/ui";
import { Card, CardContent, CardHeader, CardTitle, Skeleton } from "@sase/ui";
import { useQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router";
import { ArrowRight, FolderOpen } from "lucide-react";
@@ -133,31 +134,11 @@ function DemoVehiclePage() {
</CardContent>
</Card>
{/* Footer CTA — second conversion surface after browsing. Mobile layout
stacks (col) with full-width button; Chatwoot widget sits in the
bottom-right so we leave breathing room and keep the CTA above it
with `mb-20` on mobile. */}
<div className="mb-20 mt-8 flex flex-col items-start gap-3 rounded-lg border border-border bg-muted/30 p-5 sm:mb-0 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="text-sm font-semibold">Sınırsız şase sorgulamak için ücretsiz hesap </p>
<p className="mt-0.5 text-xs text-muted-foreground">
Kart bilgisi gerekmez · 30 gün ücretsiz · istediğin an iptal
</p>
</div>
<Button
asChild
className="w-full shrink-0 sm:w-auto"
data-faro-user-action-name="demo-footer-signup"
>
<Link
to="/register"
onClick={() => capture("demo_to_register_click", { source: "footer" })}
>
Hesap
<ArrowRight className="ml-1 h-4 w-4" />
</Link>
</Button>
</div>
{/* Footer conversion card — VIN input lets the visitor jump straight
from the demo into a signup pre-filled with THEIR vehicle, instead
of having to bounce back to the hero. Empty submit = same as
plain "Hesap Aç". */}
<DemoVinCta source="footer" />
</main>
</div>
);

View File

@@ -1,4 +1,5 @@
import { DemoBanner } from "@/components/demo/demo-banner";
import { DemoVinCta } from "@/components/demo/demo-vin-cta";
import { SchemaViewer } from "@/components/schema/schema-viewer";
import { usePageMeta } from "@/hooks/use-page-meta";
import type { CategorySchema } from "@/hooks/use-parts";
@@ -189,24 +190,10 @@ function DemoCategoryPage() {
</Suspense>
))}
{/* Footer CTA — present on every leaf so the conversion path is always
one click away after the user has just had an "aha" moment. Mobile
layout stacks with a full-width button and clears the Chatwoot
widget that sits in the bottom-right (`mb-20` on mobile). */}
{data && !data.loadError && (
<div className="mb-20 mt-6 flex flex-col items-start gap-3 rounded-lg border border-border bg-muted/30 p-5 sm:mb-0 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm font-semibold">Sınırsız şase sorgulamak için ücretsiz hesap </p>
<Button asChild className="w-full shrink-0 sm:w-auto">
<Link
to="/register"
onClick={() => capture("demo_to_register_click", { source: "category_footer" })}
>
Hesap
<ArrowRight className="ml-1 h-4 w-4" />
</Link>
</Button>
</div>
)}
{/* Footer conversion card — VIN input at the "aha moment" lets the
visitor pivot straight from browsing the demo to signing up with
their own vehicle pre-filled. */}
{data && !data.loadError && <DemoVinCta source="category_footer" />}
</main>
</div>
);