[object Object]

← back to AbramsEgo

feat(sell): landing CTAs + PUBLIC_LANDING flag (default off)

7f1831fbfb3abc218baf13b14ed0e7a561da931b · 2026-07-01 17:25:50 -0700 · Steve

Files touched

Diff

commit 7f1831fbfb3abc218baf13b14ed0e7a561da931b
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 1 17:25:50 2026 -0700

    feat(sell): landing CTAs + PUBLIC_LANDING flag (default off)
---
 public/landing.html | 22 ++++++++++++++--------
 server.js           | 23 +++++++++++++++++++++++
 2 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/public/landing.html b/public/landing.html
index a134808..a0bc33f 100644
--- a/public/landing.html
+++ b/public/landing.html
@@ -90,6 +90,7 @@
 <body>
 <header>
   <div class="logo"><b>Abrams</b>Ego</div>
+  <span class="tag" id="test-badge" hidden>TEST MODE — no real charges</span>
   <nav class="nav">
     <a href="#features">Features</a>
     <a href="#pricing">Pricing</a>
@@ -241,24 +242,29 @@ const usd = (n)=> n==null? '—' : '$'+Number(n).toFixed(n<1?4:2);
   }catch(e){ /* snapshot not ready — leave dashes */ }
 })();
 
-// Stripe checkout rail (TEST mode). If test keys are installed the tier buttons
-// become live checkout buttons; otherwise the note says keys aren't in yet.
+// Tier CTAs ← GET /api/sell/config. When checkout is ready the Hosted/Pro
+// buttons create Stripe Checkout Sessions; otherwise they keep their
+// #waitlist anchor fallback. mode=test shows the header badge.
 (async function wireCheckout(){
   const note = $('pricing-note');
-  let st = null;
-  try { st = await (await fetch('/api/stripe/status')).json(); } catch(e){}
-  if (!st || !st.configured) {
+  let cfg = null;
+  try { cfg = await (await fetch('/api/sell/config')).json(); } catch(e){}
+  if (!cfg || !cfg.checkoutReady) {
     note.textContent = 'Stripe test keys not yet installed — checkout opens once they land. Join the waitlist to be first.';
-    return;
+    return; // buttons keep their #waitlist hrefs
   }
-  note.textContent = 'Checkout is in Stripe TEST mode — card 4242 4242 4242 4242 works, no real charges.';
+  if (cfg.mode === 'test') {
+    $('test-badge').hidden = false;
+    note.textContent = 'Checkout is in Stripe TEST mode — card 4242 4242 4242 4242 works, no real charges.';
+  }
+  const checkoutPath = cfg.checkoutPath || '/api/checkout';
   document.querySelectorAll('[data-tier]').forEach((btn)=>{
     btn.textContent = 'Subscribe (test)';
     btn.addEventListener('click', async (e)=>{
       e.preventDefault();
       btn.textContent = '…creating session';
       try {
-        const r = await fetch('/api/checkout',{method:'POST',headers:{'Content-Type':'application/json'},
+        const r = await fetch(checkoutPath,{method:'POST',headers:{'Content-Type':'application/json'},
           body:JSON.stringify({tier:btn.dataset.tier})});
         const j = await r.json();
         if (r.ok && j.url) { location.href = j.url; return; }
diff --git a/server.js b/server.js
index 3d0e877..9cc3575 100644
--- a/server.js
+++ b/server.js
@@ -863,8 +863,20 @@ app.use(express.json({ limit: '256kb' }));
 // Basic-Auth gate on everything EXCEPT /api/healthz (smoke tests + uptime probes)
 const AUTH_USER = process.env.ABRAMSEGO_USER || 'admin';
 const AUTH_PASS = process.env.ABRAMSEGO_PASS || 'DW2024!';
+// PUBLIC_LANDING=1 opens ONLY the marketing surface: GET /landing (+ its html
+// alias), GET /api/sell/config, POST /api/waitlist. The landing page is fully
+// self-contained (inline CSS/JS) so no other static assets are needed. Default
+// 0 = everything stays auth-gated. Flipping to 1 is Steve's gated switch —
+// nothing in this repo sets it.
+const PUBLIC_LANDING = (process.env.PUBLIC_LANDING || '0') === '1';
+function isPublicLandingPath(req) {
+  if (req.method === 'GET') return req.path === '/landing' || req.path === '/landing.html' || req.path === '/api/sell/config';
+  if (req.method === 'POST') return req.path === '/api/waitlist';
+  return false;
+}
 app.use((req, res, next) => {
   if (req.path === '/api/healthz') return next();
+  if (PUBLIC_LANDING && isPublicLandingPath(req)) return next();
   const cred = basicAuth(req);
   if (cred && cred.name === AUTH_USER && cred.pass === AUTH_PASS) return next();
   res.set('WWW-Authenticate', 'Basic realm="AbramsEgo"');
@@ -898,6 +910,17 @@ app.post('/api/revenue/record', async (req, res) => {
 // Stripe checkout state for the landing page ("test keys not yet installed" note)
 app.get('/api/stripe/status', (req, res) => res.json({ ...stripeStatus, tiers: Object.keys(TIERS) }));
 
+// Sell config for the landing CTAs — safe for public exposure (PUBLIC_LANDING
+// allowlists it): reports only whether checkout is ready and how, never keys
+// or fleet internals. mode is "test" until Steve flips live himself.
+app.get('/api/sell/config', (req, res) => res.json({
+  checkoutReady: !!stripe,
+  mode: stripe ? 'test' : 'none',
+  checkoutPath: '/api/checkout',
+  tiers: Object.keys(TIERS),
+  note: stripeStatus.note,
+}));
+
 // Create a Checkout Session for a paid tier (DTD 2026-07-01: Option B —
 // server-created sessions). TEST mode only; 503 until sk_test_ keys land.
 // Subscriptions priced inline so nothing has to pre-exist in the dashboard.

← 4dc12bf feat(sell): stripe TEST checkout + webhook (dtd: B — Checkou  ·  back to AbramsEgo  ·  queue: task 34 done — landing CTAs + PUBLIC_LANDING flag lan 2261b68 →