// Branch portal "Price Code" screen: give each digit (1 2 3 4 5 6 7 8 9 0) its own secret character. // Barcode labels and inventory then show the cost price written with those characters. One code for the // whole shop — saved to Shop.sales_settings.price_cipher via SalesServer PATCH /api/shop-info/price-cipher. function PriceCodeSettings({ salesUrl, token }) { const BL = window.BarcodeLabels; const DIGITS = BL.PRICE_CODE_DIGITS.split(''); const EMPTY = DIGITS.map(() => ''); const EXAMPLES = [450, 1299, 8750, 15999]; const toSlots = (cipher) => { const chars = Array.from(String(cipher || '')); return chars.length === 10 ? chars : EMPTY.slice(); }; const cleanChar = (v) => { const c = Array.from(String(v || '').replace(/\s+/g, '')).pop() || ''; return /[0-9]/.test(c) ? '' : c.toUpperCase(); }; const [saved, setSaved] = React.useState(''); const [slots, setSlots] = React.useState(EMPTY.slice()); const [quick, setQuick] = React.useState(''); const [tryAmount, setTryAmount] = React.useState('1450'); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); const [notice, setNotice] = React.useState(null); // { type: 'ok' | 'err', text } const boxes = React.useRef([]); React.useEffect(() => { let alive = true; setLoading(true); fetch(salesUrl + '/api/shop-info', { headers: { Authorization: 'Bearer ' + token } }) .then(r => r.json().then(d => ({ ok: r.ok, d }))) .then(({ ok, d }) => { if (!alive) return; if (!ok) throw new Error(); const c = BL.normalizePriceCipher(d.priceCipher); setSaved(c); setSlots(toSlots(c)); }) .catch(() => { if (alive) setNotice({ type: 'err', text: 'Could not load the price code' }); }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [salesUrl, token]); const cipher = slots.join(''); const filled = slots.filter(Boolean).length; const duplicates = React.useMemo(() => { const seen = {}; slots.forEach(c => { if (c) seen[c] = (seen[c] || 0) + 1; }); return new Set(Object.keys(seen).filter(c => seen[c] > 1)); }, [slots]); const complete = filled === 10 && duplicates.size === 0; const changed = cipher !== saved; const problem = filled === 0 ? '' : duplicates.size ? `"${[...duplicates].join('", "')}" is used for more than one digit — each digit needs its own character` : filled < 10 ? `${10 - filled} digit${10 - filled === 1 ? '' : 's'} still need a character` : ''; const flash = (type, text) => { setNotice({ type, text }); setTimeout(() => setNotice(null), 3500); }; const setSlot = (i, value) => { const c = cleanChar(value); setSlots(s => s.map((x, j) => (j === i ? c : x))); if (c && i < 9) boxes.current[i + 1]?.focus(); }; const fillFrom = (text) => { const chars = Array.from(String(text || '').replace(/[\s0-9]+/g, '').toUpperCase()).slice(0, 10); setSlots(DIGITS.map((_, i) => chars[i] || '')); }; const onBoxKey = (i, e) => { if (e.key === 'Backspace' && !slots[i] && i > 0) { e.preventDefault(); boxes.current[i - 1]?.focus(); setSlot(i - 1, ''); } else if (e.key === 'ArrowLeft' && i > 0) boxes.current[i - 1]?.focus(); else if (e.key === 'ArrowRight' && i < 9) boxes.current[i + 1]?.focus(); }; const onBoxPaste = (i, e) => { const text = e.clipboardData.getData('text'); if (Array.from(text.trim()).length > 1) { e.preventDefault(); fillFrom(text); } }; const save = async (next) => { setSaving(true); try { const res = await fetch(salesUrl + '/api/shop-info/price-cipher', { method: 'PATCH', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ cipher: next }) }); const d = await res.json().catch(() => ({})); if (!res.ok) throw new Error(d.message || 'Could not save the price code'); const c = BL.normalizePriceCipher(d.priceCipher); setSaved(c); setSlots(toSlots(c)); setQuick(''); flash('ok', c ? 'Price code saved — new labels will use it' : 'Price code removed'); } catch (err) { flash('err', err.message || 'Could not save the price code'); } finally { setSaving(false); } }; const clearCode = () => { if (!window.confirm('Remove the price code? Labels will print without it until you set a new one.')) return; save(''); }; const previewCipher = complete ? cipher : ''; const tryCode = previewCipher ? BL.encodePriceCode(tryAmount, previewCipher) : ''; const card = { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '14px', padding: '24px', boxShadow: '0 1px 3px rgba(15,23,42,0.06)' }; const btn = (bg, color) => ({ display: 'inline-flex', alignItems: 'center', gap: '6px', height: '44px', padding: '0 18px', border: 'none', borderRadius: '10px', background: bg, color, fontSize: '14px', fontWeight: 600, cursor: 'pointer' }); if (loading) { return
Give every digit its own secret letter or symbol. Barcode labels and inventory will show the cost price written in these letters, so your staff can read the purchase price but customers can't.
Digit → character
{problem || (complete ? 'All 10 digits have their own character.' : 'Letters and symbols only — numbers are not allowed.')}
Cost price in rupees (paise are rounded off).
| ₹{n.toLocaleString('en-IN')} | {BL.encodePriceCode(n, previewCipher)} |