'use strict'
// ─────────────────────────────────────────────────────────────────────────────
// page() — HTML shell builder.
// Inlines ui/design.css + ui/client.js (+ optionally scanner.js) into every
// magic-link page so the client loads with zero extra requests and a server
// crash can't leave a page partially styled.
// ─────────────────────────────────────────────────────────────────────────────
const fs = require('fs')
const path = require('path')
// Read assets once at module load. Changes on disk require a service restart.
const DESIGN_CSS = fs.readFileSync(path.join(__dirname, 'design.css'), 'utf8')
const CLIENT_JS = fs.readFileSync(path.join(__dirname, 'client.js'), 'utf8')
const SCANNER_JS = fs.readFileSync(path.join(__dirname, 'scanner.js'), 'utf8')
// Nested field-scan overlay markup — injected when includeScanner is true.
const SCANNER_OVERLAY_HTML = `
📷Scanner
`
const HTML_HEADERS = { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache, no-store' }
function htmlHeaders () { return HTML_HEADERS }
// Safe JSON embed — same as components.jsonScript but avoid circular dep.
function jsonEmbed (v) { return JSON.stringify(v).replace(//g, '--\\u003e') }
// ─────────────────────────────────────────────────────────────────────────
// page(opts)
//
// title: + og title
// themeColor: optional meta theme-color (default brand-dark)
// lang: default 'fr'
// head: extra HTML injected in (meta, link, etc.)
// body: page body HTML (required)
// bootVars: object; each key becomes a top-level const in script,
// e.g. { T: token, TODAY: 'yyyy-mm-dd' } ⇒ const T=...; const TODAY=...
// cfg: object merged into window.UI_CFG for client.js
// { token, hub, today, base }
// script: page-specific JS (appended after client.js / scanner.js)
// includeScanner: inject scanner.js + overlay markup
// bodyClass: extra class on (e.g. 'pad-body' to reserve tab-bar space)
//
// Returns a complete HTML document string.
// ─────────────────────────────────────────────────────────────────────────
function page (opts) {
const {
title = 'Targo',
themeColor = '#3f3d7a',
lang = 'fr',
head = '',
body = '',
bootVars = {},
cfg = {},
script = '',
includeScanner = false,
bodyClass = '',
} = opts
// bootVars → "const K=v;" lines; JSON-safe, no user escaping needed.
const bootLines = Object.keys(bootVars).map(k => `var ${k}=${jsonEmbed(bootVars[k])};`).join('\n')
const cfgLine = `window.UI_CFG=${jsonEmbed(cfg)};`
const scannerAssets = includeScanner ? `` : ''
const scannerMarkup = includeScanner ? SCANNER_OVERLAY_HTML : ''
return `
${title}
${head}
${body}
${scannerMarkup}
${scannerAssets}
`
}
// ── Pre-baked error pages ────────────────────────────────────────────────
function pageExpired (message) {
const msg = message || `Ce lien n'est plus valide. Votre superviseur vous enverra un nouveau lien par SMS.`
return page({
title: 'Lien expiré',
body: `
🔗
Lien expiré
${msg}
`,
})
}
function pageError (message) {
const msg = message || 'Réessayez dans quelques instants.'
return page({
title: 'Erreur',
body: `