feat(web): post-signup goes to the catalog only for typed VINs

Until now any VIN carried from the landing — including the example VIN
filled by "Örnek aramayı deneyin" — auto-decoded after signup and jumped
straight to the vehicle catalog, skipping the search page entirely. That
is the right behaviour for a visitor who typed their own VIN, but the
example button is meant for exploration; users who clicked it should
land on the search page so they can look around.

Track an `isExampleVin` flag on the landing (set by fillExampleVin,
cleared on any keystroke), carry it through to /register and onward to
/dashboard/search as `example=1`, and skip the auto-decode there when
the flag is present (the VIN is still prefilled). User-typed VINs keep
the existing land-on-catalog behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 17:22:00 +03:00
parent 81d1086fef
commit 29178c0eaa
3 changed files with 34 additions and 11 deletions

View File

@@ -16,10 +16,11 @@ export const Route = createFileRoute("/_auth/register")({
component: RegisterPage,
validateSearch: (
search: Record<string, unknown>,
): { vin?: string; ref?: string; plan?: string } => ({
): { vin?: string; ref?: string; plan?: string; example?: string } => ({
vin: (search.vin as string) || undefined,
ref: (search.ref as string) || undefined,
plan: (search.plan as string) || undefined,
example: (search.example as string) || undefined,
}),
});
@@ -28,7 +29,7 @@ export const Route = createFileRoute("/_auth/register")({
const PENDING_PLAN_KEY = "sase-pending-plan";
function RegisterPage() {
const { vin, ref, plan } = Route.useSearch();
const { vin, ref, plan, example } = Route.useSearch();
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -50,6 +51,7 @@ function RegisterPage() {
"/dashboard/search?welcome=1",
vin ? `&vin=${encodeURIComponent(vin)}` : "",
refCode.trim() ? `&ref=${encodeURIComponent(refCode.trim().toUpperCase())}` : "",
example === "1" ? "&example=1" : "",
].join("");
async function handleSubmit(e: React.FormEvent) {

View File

@@ -99,16 +99,22 @@ export const Route = createFileRoute("/dashboard/search")({
component: SearchPage,
validateSearch: (
search: Record<string, unknown>,
): { vin?: string; welcome?: string; ref?: string } => ({
): { vin?: string; welcome?: string; ref?: string; example?: string } => ({
vin: search.vin ? String(search.vin) : undefined,
welcome: search.welcome ? String(search.welcome) : undefined,
ref: search.ref ? String(search.ref) : undefined,
example: search.example ? String(search.example) : undefined,
}),
});
function SearchPage() {
const navigate = useNavigate();
const { vin: vinParam, welcome: welcomeParam, ref: refParam } = Route.useSearch();
const {
vin: vinParam,
welcome: welcomeParam,
ref: refParam,
example: exampleParam,
} = Route.useSearch();
const inputRef = useRef<HTMLInputElement>(null);
const focusFiredRef = useRef(false);
const querySourceRef = useRef<"manual" | "paste" | "history" | "landing">("manual");
@@ -351,15 +357,20 @@ function SearchPage() {
if (didInitFromUrlRef.current) return;
didInitFromUrlRef.current = true;
const carried = vinParam?.toUpperCase().trim();
const isExample = exampleParam === "1";
if (carried) {
setVin(carried);
if (welcomeParam === "1") {
pendingVinRef.current = carried;
} else {
autoDecode(carried);
// Example VINs from the landing should just prefill — the visitor wanted
// to explore, not commit. User-typed VINs are decoded straight through.
if (!isExample) {
if (welcomeParam === "1") {
pendingVinRef.current = carried;
} else {
autoDecode(carried);
}
}
}
if (vinParam || welcomeParam || refParam) {
if (vinParam || welcomeParam || refParam || exampleParam) {
navigate({ to: "/dashboard/search", replace: true, search: {} });
}
}, []);

View File

@@ -466,6 +466,9 @@ export function HomePage() {
const navigate = useNavigate();
const { isAuthenticated } = useAuth();
const [vin, setVin] = useState("");
// Distinguishes a VIN the visitor typed from one filled by the example
// button — only typed VINs should auto-open the catalog after signup.
const [isExampleVin, setIsExampleVin] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
// Karusel: tıklanan marka adı (n = animasyonu her tıklamada yeniden tetiklemek için)
const [selectedBrand, setSelectedBrand] = useState<{ name: string; n: number } | null>(null);
@@ -558,7 +561,10 @@ export function HomePage() {
if (!loggedIn) {
setDecodeLoading(false);
navigate({ to: "/register", search: { vin: trimmed } });
navigate({
to: "/register",
search: { vin: trimmed, example: isExampleVin ? "1" : undefined },
});
return;
}
@@ -590,6 +596,7 @@ export function HomePage() {
const fillExampleVin = () => {
setVin("WVWZZZ1JZ3W597935");
setIsExampleVin(true);
};
// ─── Social proof impression tracking ────────────────────────────────────
@@ -875,7 +882,10 @@ export function HomePage() {
<VinBrandIcon vin={vin} className="absolute left-4 top-1/2 -translate-y-1/2" />
<Input
value={vin}
onChange={(e) => setVin(e.target.value.toUpperCase())}
onChange={(e) => {
setVin(e.target.value.toUpperCase());
setIsExampleVin(false);
}}
placeholder="Örnek: WVWZZZ1JZ3W597935"
maxLength={17}
className="h-12 rounded-full border-border bg-muted pl-12 pr-4 font-mono text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:ring-ring sm:h-14 sm:text-base"