[object Object]

← back to Pattern Vault

chore: lint guards (stripe try/catch, checkout fetch try/catch), catalogItems refactor, v0.2.0 (session close)

f7818722f428e6bdd1ede667644d6e230e306e63 · 2026-07-02 08:07:14 -0700 · Steve Abrams

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit f7818722f428e6bdd1ede667644d6e230e306e63
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 2 08:07:14 2026 -0700

    chore: lint guards (stripe try/catch, checkout fetch try/catch), catalogItems refactor, v0.2.0 (session close)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 package-lock.json |  4 ++--
 package.json      |  2 +-
 public/index.html | 10 ++++++----
 server.js         | 20 ++++++++++++--------
 4 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 30dbbe0..3df0e48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "pattern-vault",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "pattern-vault",
-      "version": "0.1.0",
+      "version": "0.2.0",
       "dependencies": {
         "express": "^4.19.0"
       }
diff --git a/package.json b/package.json
index 147fe48..018ef4c 100644
--- a/package.json
+++ b/package.json
@@ -1 +1 @@
-{"name":"pattern-vault","version":"0.1.0","private":true,"scripts":{"start":"node server.js"},"dependencies":{"express":"^4.19.0"}}
+{"name":"pattern-vault","version":"0.2.0","private":true,"scripts":{"start":"node server.js"},"dependencies":{"express":"^4.19.0"}}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 93a7c57..d6d740b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -72,10 +72,12 @@ function pick(id,tier){const d=items.find(x=>String(x.id)===String(id));sel={id,
   document.getElementById('dlgTier').textContent=(d.licenseTiers.find(t=>t.tier===tier)||{}).label+' license';
   document.getElementById('dlgNote').textContent=cfg.note||'';dlg.showModal()}
 document.getElementById('goBtn').onclick=async()=>{
-  const r=await fetch('/api/license/checkout',{method:'POST',headers:{'Content-Type':'application/json'},
-    body:JSON.stringify({designId:sel.id,tier:sel.tier,email:document.getElementById('email').value})});
-  const j=await r.json();
-  if(j.url)location=j.url;else{document.getElementById('dlgNote').textContent=j.note||j.error;setTimeout(()=>dlg.close(),1800)}};
+  try{
+    const r=await fetch('/api/license/checkout',{method:'POST',headers:{'Content-Type':'application/json'},
+      body:JSON.stringify({designId:sel.id,tier:sel.tier,email:document.getElementById('email').value})});
+    const j=await r.json();
+    if(j.url)location=j.url;else{document.getElementById('dlgNote').textContent=j.note||j.error;setTimeout(()=>dlg.close(),1800)}
+  }catch(e){document.getElementById('dlgNote').textContent='Network error — try again'}};
 (async()=>{cfg=await(await fetch('/api/config')).json();
   document.getElementById('modeBadge').textContent=cfg.mode==='test'?'TEST MODE — no real charges':'Preview — inquiries only until checkout opens';
   items=(await(await fetch('/api/catalog')).json()).items;render()})();
diff --git a/server.js b/server.js
index c5ef64c..d869b19 100644
--- a/server.js
+++ b/server.js
@@ -27,10 +27,10 @@ app.use(express.static(path.join(__dirname, 'public')));
 
 const readJson = (f, fb) => { try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); } catch { return fb; } };
 const appendJsonl = (f, row) => fs.appendFileSync(path.join(DATA, f), JSON.stringify(row) + '\n');
+const catalogItems = () => { const raw = readJson('catalog.json', []); return Array.isArray(raw) ? raw : (raw.designs || raw.items || []); };
 
 app.get('/api/catalog', (req, res) => {
-  const raw = readJson('catalog.json', []);
-  const items = Array.isArray(raw) ? raw : (raw.designs || raw.items || []);
+  const items = catalogItems();
   res.json({ count: items.length, updatedAt: fs.statSync(path.join(DATA, 'catalog.json')).mtime, items });
 });
 
@@ -47,20 +47,24 @@ app.get('/api/config', (req, res) => res.json({
 
 app.post('/api/license/checkout', async (req, res) => {
   const { designId, tier, email } = req.body || {};
-  const raw = readJson('catalog.json', []);
-  const items = Array.isArray(raw) ? raw : (raw.designs || raw.items || []);
-  const design = items.find(d => String(d.id) === String(designId));
+  const design = catalogItems().find(d => String(d.id) === String(designId));
   if (!design) return res.status(404).json({ error: 'unknown design' });
   const t = (design.licenseTiers || []).find(x => x.tier === tier);
   if (!t) return res.status(400).json({ error: 'unknown tier' });
   appendJsonl('license-events.jsonl', { ts: new Date().toISOString(), type: stripe ? 'checkout_start' : 'inquiry', designId, tier, email: (email || '').slice(0, 254) });
   if (!stripe) return res.json({ ok: true, mode: 'inquiry', note: 'recorded — checkout opens when Stripe test keys land' });
-  const session = await stripe.checkout.sessions.create({
+  let session;
+  try {
+    session = await stripe.checkout.sessions.create({
     mode: 'payment',
     line_items: [{ price_data: { currency: 'usd', unit_amount: Math.round((t.priceUsd || 49) * 100), product_data: { name: `${design.title} — ${t.label || tier} license` } }, quantity: 1 }],
     success_url: `http://127.0.0.1:${PORT}/?licensed=${designId}`, cancel_url: `http://127.0.0.1:${PORT}/`,
-    metadata: { designId: String(designId), tier },
-  });
+      metadata: { designId: String(designId), tier },
+    });
+  } catch (err) {
+    console.error('stripe error', err.message);
+    return res.status(502).json({ error: 'checkout unavailable', detail: err.message });
+  }
   res.json({ ok: true, mode: 'test', url: session.url });
 });
 

← 8eb308a setup guide + README: codify the new-Stripe-account rail (Pa  ·  back to Pattern Vault  ·  feat: Trend Board page (/trends) — renders design-trend-scou bbe592a →