Files
lynkeduppro-crm/public/thanks.html
tanweer919 d82d71d0b2 feat(founder): payment→register flow + founder-only registration gate
- Next API routes: /api/register + /api/checkout (Stripe session) + /api/stripe-webhook
  (signature-verified; fulfils through be-crm), ported from the landing's serverless
  functions. Shared founder lead validation in src/lib/founder/lead.ts.
- /api/founder/lookup + /api/founder/session proxy be-crm's public endpoints.
- /thanks polls session_id → founder token → redirects to /portal/register?ft=<token>;
  falls back to the emailed link if the webhook is briefly delayed.
- register-flow: reads ?ft=, validates + prefills (email locked, name, phone) from the
  paid founder record, passes founderToken to crm.account.register (claims token +
  provisions owner). Open registration is now blocked — only a paid founder (ft) or a
  team invitee may register; everyone else sees a 'Founder access required' gate.
2026-07-18 00:23:55 +05:30

148 lines
7.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Welcome to LynkedUp Pro — you're a Founding Member</title>
<meta name="robots" content="noindex" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet" />
<style>
:root { --orange:#ff9321; --blue:#4dc5ff; }
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh;
font-family: "Plus Jakarta Sans", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
color: #fff;
background:
radial-gradient(circle at top left, #8c4a13 0%, transparent 38%),
radial-gradient(circle at bottom right, #1597e5 0%, transparent 40%),
#14100c;
display: flex; align-items: center; justify-content: center; padding: 32px 18px;
}
.card {
width: 100%; max-width: 620px;
background: rgba(18,18,22,.72); border: 1px solid rgba(255,255,255,.10);
border-radius: 22px; padding: 44px 40px; text-align: center;
box-shadow: 0 30px 80px rgba(0,0,0,.5); backdrop-filter: blur(6px);
}
.badge {
display: inline-flex; align-items: center; gap: 8px;
padding: 7px 15px; border-radius: 999px; margin-bottom: 22px;
background: linear-gradient(90deg, var(--orange), var(--blue));
color: #0d0d0f; font-weight: 800; font-size: 12.5px; letter-spacing: .02em;
}
.check {
width: 66px; height: 66px; margin: 0 auto 22px; border-radius: 50%;
background: linear-gradient(135deg, var(--orange), var(--blue));
display: flex; align-items: center; justify-content: center;
font-size: 34px; color: #0d0d0f; font-weight: 800;
}
h1 { margin: 0 0 10px; font-size: 27px; font-weight: 800; letter-spacing: -.02em; line-height: 1.2; }
p.lead { margin: 0 auto 26px; max-width: 480px; color: #c9c9d2; font-size: 15px; line-height: 1.6; }
.steps { text-align: left; margin: 0 auto 30px; max-width: 470px; display: grid; gap: 12px; }
.step { display: flex; gap: 13px; align-items: flex-start; padding: 14px 16px;
background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.07); border-radius: 13px; }
.step .n { flex: 0 0 26px; height: 26px; border-radius: 50%;
background: rgba(255,147,33,.16); color: var(--orange);
display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px; }
.step .t { font-size: 14px; line-height: 1.5; color: #e6e6ea; }
.step .t b { color: #fff; }
.cta { display: inline-block; margin-top: 6px; padding: 14px 30px; border-radius: 12px;
text-decoration: none; color: #fff; font-weight: 700; font-size: 15px;
background: linear-gradient(90deg, var(--orange), var(--blue)); box-shadow: 0 10px 25px rgba(0,0,0,.3); }
.fine { margin-top: 22px; font-size: 12px; color: #8a8a93; line-height: 1.6; }
.fine a { color: var(--blue); text-decoration: none; }
.demo-note { margin: -6px auto 24px; max-width: 470px; padding: 11px 15px; border-radius: 12px;
background: rgba(77,197,255,.12); border: 1px solid rgba(77,197,255,.32); color: #bfe6ff; font-size: 13px; line-height: 1.55; }
.ref { margin-top: 10px; font-size: 11px; color: #6f6f78; word-break: break-all; }
@media (max-width: 560px) { .card { padding: 34px 22px; } h1 { font-size: 23px; } }
</style>
</head>
<body>
<main class="card">
<div class="check"></div>
<span class="badge" id="badge">🎉 You're a Founding Member</span>
<h1 id="headline">Payment received — welcome to LynkedUp&nbsp;Pro</h1>
<p class="lead" id="lead">
Your Founders Lifetime License is locked in at the founder price. A confirmation
email is on its way, and our onboarding team will reach out shortly to get your
account set up.
</p>
<p class="demo-note" id="demoNote" hidden>
🎬 <b>Demo mode.</b> This was a walkthrough of the full flow — no payment was taken
and no card was charged.
</p>
<div class="steps">
<div class="step"><span class="n">1</span><span class="t"><b>Check your inbox.</b> Your receipt and a welcome email with next steps are on the way. (Peek in spam if you don't see them in a few minutes.)</span></div>
<div class="step"><span class="n">2</span><span class="t"><b>We'll set you up.</b> Our team will contact you at the details you provided to activate your license and walk you through onboarding.</span></div>
<div class="step"><span class="n">3</span><span class="t"><b>Watch for launch updates.</b> As a Founding Member you'll hear first about new features and your rollout timeline.</span></div>
</div>
<a class="cta" id="setupCta" href="/portal/register">Set up your account</a>
<p class="fine" id="setupStatus" style="margin-top:10px;">Preparing your account…</p>
<p class="fine">
Questions? Email <a href="mailto:support@lynkeduppro.com">support@lynkeduppro.com</a> and reference your order below.
</p>
<p class="ref" id="ref"></p>
</main>
<script>
(function () {
var q = new URLSearchParams(location.search);
var demo = q.get("demo") === "1";
// Demo mode ($0 no-card flow from page/19.html): reframe the page so nobody
// thinks a real charge happened.
if (demo) {
document.getElementById("badge").textContent = "🎬 Demo complete";
document.getElementById("headline").innerHTML = "That's the full flow — start to finish";
document.getElementById("lead").textContent =
"You just walked through the entire Founders License journey. On the live page this is where the real payment would complete.";
document.getElementById("demoNote").hidden = false;
}
// Show the Stripe session id (from success_url) so support can look up the order.
var id = q.get("session_id");
if (id) document.getElementById("ref").textContent = "Order reference: " + id;
var status = document.getElementById("setupStatus");
var cta = document.getElementById("setupCta");
if (demo || !id) {
// No real purchase to attribute — just offer the register page.
if (status) status.textContent = "";
return;
}
// Poll be-crm (via our proxy) for the founder token minted by the Stripe
// webhook, then redirect to the prefilled register page. The welcome email
// carries the same link, so this is a convenience — nothing is lost if the
// webhook is briefly delayed.
var tries = 0, MAX = 15; // ~30s at 2s intervals
function poll() {
tries++;
fetch("/api/founder/session?session_id=" + encodeURIComponent(id), { cache: "no-store" })
.then(function (r) { return r.json(); })
.then(function (d) {
if (d && d.status === "ready" && d.token) {
var url = "/portal/register?ft=" + encodeURIComponent(d.token);
if (cta) cta.setAttribute("href", url);
if (status) status.textContent = "Redirecting you to finish setup…";
window.location.replace(url);
return;
}
if (tries < MAX) { setTimeout(poll, 2000); return; }
if (status) status.innerHTML = "We emailed you a secure link to finish setting up your account — check your inbox (and spam).";
})
.catch(function () {
if (tries < MAX) { setTimeout(poll, 2000); return; }
if (status) status.innerHTML = "We emailed you a secure link to finish setting up your account — check your inbox (and spam).";
});
}
poll();
})();
</script>
</body>
</html>