← back to AbramsEgo
feat(sell): landing CTAs + PUBLIC_LANDING flag (default off)
7f1831fbfb3abc218baf13b14ed0e7a561da931b · 2026-07-01 17:25:50 -0700 · Steve
Files touched
M public/landing.htmlM server.js
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 →