/* The one HTML layout every ThreadCount email is built from.
*
* Lifted out of lib/billing-mail.cjs so the coordinator and staff emails read the same way as the
* billing ones: the same colours, the same text wordmark, one button, a footer. CommonJS with no
* dependencies so the app, the timers and the previews all render the same file.
*
* Colour is hard-coded from app/globals.css (`--color-*`): email clients do not read CSS
* variables. Tables, inline styles, a single 600px column, no images — the wordmark is text, so the
* mail looks the same with images blocked.
*
* Slots: `eyebrow` (the small label above the headline), `title`, `preheader`, `intro` (paragraphs),
* `rows` (a details table of [label, value]), `list` (garment lines or bullets), `code` (a big
* collection code), `cta` ({label, href}), `closing` (paragraphs in the quieter colour), `footer`
* ({facility, contact, links: [[label, href]], entity, textLinks}). `text` is built from the same
* parts for callers that have no hand-written plain-text body. */
const C = {
bg: "#f3f2f2", surface: "#eae9e9", text: "#201e1d", accent: "#ec3013", accent700: "#b8240e", accent300: "#ffc4b8",
divider: "#cfcccb", n600: "#6c6764", n700: "#57534f", n800: "#3a3735", white: "#ffffff",
};
const FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
const MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
}
function money(cents, currency) {
return new Intl.NumberFormat("en-AU", { style: "currency", currency: (currency || "AUD").toUpperCase() }).format((cents || 0) / 100);
}
function longDate(d) {
const x = d instanceof Date ? d : new Date(d);
return x.toLocaleDateString("en-AU", { day: "numeric", month: "long", year: "numeric", timeZone: "Australia/Brisbane" });
}
function siteUrl() {
return (process.env.NEXT_PUBLIC_SITE_URL || "https://threadcount.tech").replace(/\/+$/, "");
}
/**
* @param {{ eyebrow?: string, title: string, preheader?: string, intro?: string | string[],
* rows?: [string, string][], list?: string[], code?: { label?: string, value: string },
* cta?: { label: string, href: string }, closing?: string | string[],
* footer?: { facility?: string, contact?: string, links?: [string, string][], entity?: string, textLinks?: [string, string][] } }} o
* @returns {{ html: string, text: string }}
*/
function layout(o) {
const { eyebrow, title, preheader, intro, rows, list, code, cta, closing, footer } = o;
const base = siteUrl();
const f = footer || {};
const entity = f.entity === undefined ? (process.env.INVOICE_ENTITY || "ThreadCount") : f.entity;
const abn = f.entity === undefined && process.env.INVOICE_ABN ? ` · ABN ${process.env.INVOICE_ABN}` : "";
const links = f.links || [["Support", `${base}/support`]];
const paras = (Array.isArray(intro) ? intro : [intro]).filter(Boolean);
const closingParas = (Array.isArray(closing) ? closing : [closing]).filter(Boolean);
const rowsHtml = rows && rows.length
? `
${rows.map(([k, v]) => `
| ${esc(k)} |
${esc(v)} |
`).join("")}
`
: "";
const listHtml = list && list.length
? `
${list.map((l) => `| ${esc(l)} |
`).join("")}
`
: "";
const codeHtml = code
? `
|
${esc(code.label || "Collection code")}
${esc(code.value)}
|
`
: "";
const ctaHtml = cta
? ``
: "";
const html = `
${esc(title)}
${esc(preheader || title)}
|
|
|
${esc(eyebrow || "ThreadCount")}
${esc(title)}
|
|
${paras.map((p) => ` ${esc(p)} `).join("")}
${listHtml}
${rowsHtml}
${codeHtml}
${ctaHtml}
${closingParas.map((p) => `${esc(p)} `).join("")}
|
|
${f.facility ? `${esc(f.facility)}${f.contact ? ` · ${esc(f.contact)}` : ""} ` : ""}
${links.map(([label, href]) => `${esc(label)}`).join(" · ")}${links.length ? " " : ""}
${esc(entity)}${esc(abn)}
|
|
`;
const textLinks = f.textLinks || links;
const text = [
title.toUpperCase(),
"",
...paras,
"",
...(list && list.length ? [...list, ""] : []),
...(rows && rows.length ? rows.map(([k, v]) => `${k}: ${v}`) : []),
...(rows && rows.length ? [""] : []),
...(code ? [`${code.label || "Collection code"}: ${code.value}`, ""] : []),
...(cta ? [`${cta.label}: ${cta.href}`, ""] : []),
...closingParas,
...(closingParas.length ? [""] : []),
f.facility ? `${f.facility}${f.contact ? ` · ${f.contact}` : ""}` : "",
...textLinks.map(([label, href]) => `${label}: ${href}`),
`${entity}${abn}`,
].filter((l, i, a) => !(l === "" && a[i - 1] === "")).join("\n");
return { html, text };
}
module.exports = { layout, esc, money, longDate, siteUrl, C, FONT };