"use client"; /** * CheckoutSuccessClient * * Shown after Stripe payment success. * * - If already signed in: redirect immediately to /activate/[orderId]. * - If not signed in: show email + password sign-up form (no phone). * After sign-up or sign-in, redirect to /activate/[orderId]. * * The orderId is verified server-side before this component renders. */ import { useState, FormEvent, useEffect, useCallback } from "react"; import { useSignUp, useSignIn } from "@clerk/nextjs"; import { useClerk, useAuth } from "@clerk/nextjs"; import { useRouter } from "next/navigation"; import { completeEmailSignUpAfterVerify } from "@/lib/complete-email-sign-up"; type AuthMode = "signup" | "signin"; type FlowStep = "idle" | "credentials" | "verify" | "redirecting" | "claim-error"; interface Props { orderId: string; sessionId: string; templateId: string; purchaserEmail: string | null; } export function CheckoutSuccessClient({ orderId, sessionId, templateId, purchaserEmail, }: Props) { const { isLoaded: authLoaded, isSignedIn } = useAuth(); const { signUp, isLoaded: signUpLoaded } = useSignUp(); const { signIn, isLoaded: signInLoaded } = useSignIn(); const { setActive } = useClerk(); const router = useRouter(); const activateUrl = `/activate/${encodeURIComponent(orderId)}`; const [mode, setMode] = useState("signup"); const [step, setStep] = useState("idle"); const [email, setEmail] = useState(purchaserEmail ?? ""); const [password, setPassword] = useState(""); const [code, setCode] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const emailLocked = Boolean(purchaserEmail); const claimOrderAndRedirect = useCallback(async (afterAuth = false) => { setStep("redirecting"); setError(null); try { const res = await fetch("/api/checkout/claim", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId, templateId }), }); if (!res.ok) { const err = (await res.json()) as { error?: string }; throw new Error(err.error ?? "Failed to link your purchase to this account"); } router.replace(activateUrl); } catch (err) { setStep(afterAuth || isSignedIn ? "claim-error" : "credentials"); setError(err instanceof Error ? err.message : "Failed to link your purchase to this account"); } }, [activateUrl, isSignedIn, router, sessionId, templateId]); // If already signed in, claim the paid order before activating. useEffect(() => { if (!authLoaded) return; if (isSignedIn) { void claimOrderAndRedirect(true); } else { setStep("credentials"); } }, [authLoaded, isSignedIn, claimOrderAndRedirect]); function clerkErrMsg(err: unknown): string { const e = err as { errors?: Array<{ longMessage?: string; message?: string }> }; return ( e?.errors?.[0]?.longMessage ?? e?.errors?.[0]?.message ?? "Something went wrong. Please try again." ); } // ── Sign-up flow ────────────────────────────────────────────────────────── async function handleSignUp(e: FormEvent) { e.preventDefault(); if (!signUpLoaded || !signUp) return; if (purchaserEmail && email.toLowerCase() !== purchaserEmail.toLowerCase()) { setError("Use the same email address you entered at checkout."); return; } setError(null); setLoading(true); try { await signUp.create({ emailAddress: email, password }); await signUp.prepareEmailAddressVerification({ strategy: "email_code" }); setStep("verify"); } catch (err) { setError(clerkErrMsg(err)); } finally { setLoading(false); } } async function handleVerify(e: FormEvent) { e.preventDefault(); if (!signUpLoaded || !signUp) return; setError(null); setLoading(true); try { const result = await signUp.attemptEmailAddressVerification({ code }); const completion = await completeEmailSignUpAfterVerify(signUp, result); if (completion.status === "complete") { await setActive({ session: completion.sessionId }); await claimOrderAndRedirect(true); } else { setError(completion.message); } } catch (err) { setError(clerkErrMsg(err)); } finally { setLoading(false); } } // ── Sign-in flow ────────────────────────────────────────────────────────── async function handleSignIn(e: FormEvent) { e.preventDefault(); if (!signInLoaded || !signIn) return; if (purchaserEmail && email.toLowerCase() !== purchaserEmail.toLowerCase()) { setError("Use the same email address you entered at checkout."); return; } setError(null); setLoading(true); try { const result = await signIn.create({ identifier: email, password, }); if (result.status === "complete" && result.createdSessionId) { await setActive({ session: result.createdSessionId }); await claimOrderAndRedirect(true); } else { setError("Sign-in incomplete. Please try again."); } } catch (err) { setError(clerkErrMsg(err)); } finally { setLoading(false); } } // ── Render ──────────────────────────────────────────────────────────────── if (step === "idle" || step === "redirecting") { return (

{step === "redirecting" ? "Activating your eSIM…" : "Loading…"}

); } if (step === "claim-error") { return (

✓ Payment successful!

We could not link this purchase to your account.

{error && (

{error}

)}
); } return (
{/* Payment success banner */}

✓ Payment successful!

Create an account to activate your eSIM.

{/* Auth card */}
{/* Mode toggle */} {step === "credentials" && (
)} {/* Sign-up: credentials step */} {step === "credentials" && mode === "signup" && ( <>

Create your account

{emailLocked ? "Use the email from your checkout to create your account." : "Email and password — no phone required."}

setEmail(e.target.value)} className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none" placeholder="you@example.com" />
setPassword(e.target.value)} className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none" placeholder="Min. 8 characters" />
{error && (

{error}

)}
)} {/* Sign-in: credentials step */} {step === "credentials" && mode === "signin" && ( <>

Sign in

{emailLocked ? "Sign in with the email from your checkout to activate your eSIM." : "Sign in to activate your eSIM."}

setEmail(e.target.value)} className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none" placeholder="you@example.com" />
setPassword(e.target.value)} className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none" placeholder="Your password" />
{error && (

{error}

)}
)} {/* Email verification step */} {step === "verify" && ( <>

Check your email

We sent a 6-digit code to {email}.

setCode(e.target.value.replace(/\D/g, ""))} className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-center text-lg tracking-[0.4em] text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none" placeholder="000000" />
{error && (

{error}

)}
)}

Your payment is confirmed. Account setup is required to receive your eSIM QR code.

); }