import { useMemo } from 'react'; import { Button } from '@astryxdesign/core/Button'; import { Text } from '@astryxdesign/core/Text'; import { VStack } from '@astryxdesign/core/VStack'; import { Download } from 'lucide-react'; import { encodeQr, qrPath } from './qr'; /** * One branch's storefront QR — the code, and a print-quality copy of it. * * ── Why the code is derived and not stored ────────────────────────────────── * * It encodes the literal JSON `{"tenantid":N,"locationid":M}` and nothing else: * not a URL, not the shop's name, not a token. The customer app reads those two * numbers and opens THAT outlet's storefront, which is the whole mechanism — * until somebody scans it the shop is not listed for them and they cannot * order. * * Two numbers that already exist means the code is unique per branch by * construction, and it cannot drift from the branch it belongs to, be lost, or * need a migration to reissue. Nothing is written to Fiesta and nothing needs * to be: reprinting a lost poster is opening this panel again. * * ── Why it is a component and not a page ──────────────────────────────────── * * The same code has to appear at every moment somebody needs it — when a store * is onboarded, when a branch is commissioned, and any day after that from * either login. One component, one payload, so the poster taped to a counter * cannot disagree with the one printed a month later. */ export interface StoreQrPanelProps { tenantid: number; locationid: number; /** The branch's name, printed above the code and used for the file name. */ locationname?: string; /** Suburb, city — whatever is worth reading under the name. */ where?: string; /** On-screen size in px. The download is always 1024. */ size?: number; } export function StoreQrPanel({ tenantid, locationid, locationname, where, size = 220, }: StoreQrPanelProps) { const payload = useMemo( () => JSON.stringify({ tenantid, locationid }), [tenantid, locationid], ); const matrix = useMemo(() => encodeQr(payload), [payload]); const path = useMemo(() => qrPath(matrix), [matrix]); const quiet = 4; const span = matrix.length + quiet * 2; const name = locationname ?? 'your store'; /** * The print copy, drawn at 1024px into a canvas that never enters the page. * * The on-screen SVG would scale, but a poster printed from a 220px raster is * soft, and a shop prints this once and lives with it. */ function download() { const px = 1024; const canvas = document.createElement('canvas'); canvas.width = px; canvas.height = px; const context = canvas.getContext('2d'); if (!context) return; const scale = px / span; context.fillStyle = '#ffffff'; context.fillRect(0, 0, px, px); context.fillStyle = '#000000'; for (let y = 0; y < matrix.length; y += 1) { const row = matrix[y] as boolean[]; for (let x = 0; x < row.length; x += 1) { if (!row[x]) continue; // Ceil, not round: a fractional module edge left hairline gaps between // neighbouring dark squares, which a scanner reads as a broken pattern. context.fillRect( Math.floor((x + quiet) * scale), Math.floor((y + quiet) * scale), Math.ceil(scale), Math.ceil(scale), ); } } const link = document.createElement('a'); link.download = `${slug(locationname ?? 'store')}-qr.png`; link.href = canvas.toDataURL('image/png'); link.click(); } return ( {locationname ?? 'Your store'} {where ? ( {where} ) : null}
This code is this branch's alone. A shopper scans it to add the store to their app — until they do, it is not listed for them and they cannot order from it.