feat(shopping): loyalty-card modal with build-time barcodes

Adds a CreditCard button on the shopping list that opens a modal
showing the user's Coop Supercard (Data Matrix) and Migros Cumulus
(Code 128). Card numbers come from SHOPPING_COOP_SUPERCARD_NUMBER
and SHOPPING_MIGROS_CUMULUS_NUMBER env vars; a prebuild script
renders each to an SVG (~1-2 kB) in static/shopping/ so no barcode
library ships to the client. Cards missing their env var are
silently skipped, and the generated SVGs are gitignored to keep
personal numbers out of the repo.
This commit is contained in:
2026-04-23 16:21:15 +02:00
parent a8b0d3c722
commit 0ab98690eb
7 changed files with 261 additions and 4 deletions
+56
View File
@@ -0,0 +1,56 @@
/**
* Build-time generation of loyalty-card barcode SVGs.
*
* Reads card numbers from env vars and writes static/shopping/supercard.svg
* + static/shopping/cumulus.svg. Skips cards whose env var is unset so the
* site still builds in environments without secrets.
*
* SHOPPING_COOP_SUPERCARD_NUMBER → Data Matrix (Coop Supercard)
* SHOPPING_MIGROS_CUMULUS_NUMBER → Code 128 (Migros Cumulus)
*
* Run: pnpm exec vite-node scripts/generate-loyalty-cards.ts
*/
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { toSVG } from 'bwip-js/node';
const HERE = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(HERE, '..', 'static', 'shopping');
type CardSpec = {
envVar: string;
filename: string;
bcid: 'datamatrix' | 'code128';
scale: number;
};
const cards: CardSpec[] = [
{ envVar: 'SHOPPING_COOP_SUPERCARD_NUMBER', filename: 'supercard.svg', bcid: 'datamatrix', scale: 6 },
{ envVar: 'SHOPPING_MIGROS_CUMULUS_NUMBER', filename: 'cumulus.svg', bcid: 'code128', scale: 3 }
];
mkdirSync(OUT_DIR, { recursive: true });
for (const card of cards) {
const value = process.env[card.envVar]?.trim();
const outPath = resolve(OUT_DIR, card.filename);
if (!value) {
try { rmSync(outPath); } catch { /* not present */ }
console.log(`[loyalty-cards] ${card.envVar} not set — skipped ${card.filename}`);
continue;
}
const svg = toSVG({
bcid: card.bcid,
text: value,
scale: card.scale,
includetext: false,
paddingwidth: 8,
paddingheight: 8
});
writeFileSync(outPath, svg, 'utf8');
console.log(`[loyalty-cards] wrote ${card.filename} (${card.bcid})`);
}