← 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
M package-lock.jsonM package.jsonM public/index.htmlM server.js
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 →