export function qrImageUrl(text) { const prefix = location.pathname.startsWith('/business-demo/') ? '/business-demo' : ''; return `${prefix}/api/qr?text=${encodeURIComponent(text)}`; } // QR payloads contain only the public destination and merchant context. export function moduleQrUrl(destination, npub) { const url = new URL(destination, location.origin); if (!['http:', 'https:'].includes(url.protocol)) throw new Error('Invalid module URL'); const surface = url.searchParams.get('surface'); url.username = ''; url.password = ''; url.search = ''; url.hash = ''; if (surface) url.searchParams.set('surface', surface); url.searchParams.set('npub', npub); return url.href; } const escapeHtml = value => String(value).replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])); // Both public links and business modules share the exact same QR control. // Free-link URLs are already resolved by the API: preserve them verbatim. export function qrButton(id, name, url, {free = false, hidden = false} = {}) { return ``; } export function mountModuleQrs(root) { const dialog = document.createElement('dialog'); dialog.className = 'module-qr-dialog'; dialog.setAttribute('aria-labelledby', 'module-qr-title'); dialog.setAttribute('aria-describedby', 'module-qr-description'); dialog.innerHTML = `

Quick access

Scan to open this merchant’s module on your phone.

Download QR

`; document.body.append(dialog); const title = dialog.querySelector('h2'); const img = dialog.querySelector('img'); const status = dialog.querySelector('.module-qr-image [role=status]'); const urlLink = dialog.querySelector('.module-qr-url'); const feedback = dialog.querySelector('.module-qr-feedback'); const download = dialog.querySelector('[data-qr-download]'); const print = dialog.querySelector('[data-qr-print]'); img.onload = () => { img.hidden = false; status.textContent = ''; download.hidden = false; print.disabled = false; }; img.onerror = () => { img.hidden = true; status.textContent = 'QR code could not load. You can still open or copy the link.'; download.hidden = true; print.disabled = true; }; root.addEventListener('click', (event) => { const button = event.target.closest('[data-qr-trigger]'); if (!button || button.hidden) return; const { qrUrl, qrName, qrTrigger } = button.dataset; dialog.querySelector('#module-qr-description').textContent = button.hasAttribute('data-free-qr') ? 'Scan to open this merchant’s public page on your phone.' : 'Scan to open this merchant’s module on your phone.'; title.textContent = qrName; urlLink.href = qrUrl; urlLink.textContent = qrUrl; feedback.textContent = ''; img.hidden = true; status.textContent = 'Loading QR code…'; print.disabled = true; download.hidden = true; img.alt = `QR code for ${qrName}`; img.src = qrImageUrl(qrUrl); download.href = img.src; download.download = `justworks-${qrTrigger}-qr.svg`; dialog.showModal(); }); dialog.querySelector('.module-qr-close').onclick = () => dialog.close(); dialog.addEventListener('click', (event) => { if (event.target !== dialog) return; const bounds = dialog.getBoundingClientRect(); if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) dialog.close(); }); // Native modal dialogs restore focus on close. Do not also restore it from // the queued `close` event: that can steal focus from the next QR trigger. dialog.addEventListener('keydown', (event) => { if (event.key !== 'Tab') return; const controls = [...dialog.querySelectorAll('button:not(:disabled), a[href]')].filter(node => !node.hidden); const first = controls[0], last = controls.at(-1); if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } }); dialog.querySelector('[data-qr-copy]').onclick = async () => { try { await navigator.clipboard.writeText(urlLink.href); feedback.textContent = 'Link copied.'; } catch { feedback.textContent = 'Could not copy automatically. Select and copy the link above.'; } }; print.onclick = () => window.print(); }