Files
iti-technology/apps/api/src/emails/layout.ts
T
Lorenzo SanesiandClaude Opus 5.5 b83ce039fb
Deploy stack su Proxmox / deploy (push) Failing after 3s
Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA),
apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/;
infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e
workflow Gitea Actions per il deploy via SSH a ogni push su master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
2026-10-20 09:30:48 +02:00

59 lines
3.1 KiB
TypeScript

import { html, SafeHtml, safeUrl } from "./html.js"
/** Email renderizzata: oggetto, HTML (layout comune) e testo semplice. */
export type RenderedEmail = { subject: string; html: string; text: string }
const BRAND = "ITI TECHNOLOGY"
const COLOR = "#0b5c7a"
const FOOTER_TEXT = `${BRAND} · trattamento acqua, energia rinnovabile, comfort e mobilità elettrica`
/** Pulsante (link) in stile brand; l'URL passa da safeUrl. */
export const button = (label: string, url: string) => html`<table role="presentation" cellpadding="0" cellspacing="0" style="margin:24px 0">
<tr><td style="background:${COLOR};border-radius:6px">
<a href="${safeUrl(url)}" style="display:inline-block;padding:12px 24px;color:#ffffff;text-decoration:none;font-weight:bold">${label}</a>
</td></tr>
</table>`
export const paragraph = (content: SafeHtml | string) => html`<p style="margin:0 0 16px">${content}</p>`
/** Tabella chiave/valore (riepiloghi d'ordine, preventivi, prenotazioni). */
export function table(rows: readonly (readonly [string, SafeHtml | string])[], opts: { boldLast?: boolean } = {}) {
return html`<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="border-collapse:collapse;margin:0 0 16px">
${rows.map(
([label, value], i) =>
html`<tr>
<td style="padding:6px 0;border-bottom:1px solid #e5e7eb${opts.boldLast && i === rows.length - 1 ? ";font-weight:bold" : ""}">${label}</td>
<td style="padding:6px 0;border-bottom:1px solid #e5e7eb;text-align:right${opts.boldLast && i === rows.length - 1 ? ";font-weight:bold" : ""}">${value}</td>
</tr>`,
)}
</table>`
}
/**
* Layout comune: intestazione brand, contenuto, piè di pagina. HTML semplice a tabelle con stili
* inline (compatibile con i client di posta). `preheader` = anteprima mostrata nella lista messaggi.
*/
export function layout(opts: { subject: string; preheader: string; body: SafeHtml; text: string }): RenderedEmail {
const page = html`<!doctype html>
<html lang="it">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${opts.subject}</title></head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;color:#111827;line-height:1.5">
<span style="display:none;max-height:0;overflow:hidden;opacity:0">${opts.preheader}</span>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;padding:24px 0">
<tr><td align="center">
<table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;width:100%;background:#ffffff;border-radius:8px;overflow:hidden">
<tr><td style="background:${COLOR};padding:20px 32px;color:#ffffff;font-size:20px;font-weight:bold;letter-spacing:1px">${BRAND}</td></tr>
<tr><td style="padding:32px">${opts.body}</td></tr>
<tr><td style="padding:16px 32px;background:#f9fafb;color:#6b7280;font-size:12px">${FOOTER_TEXT}<br>Questa è un'email automatica.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>`
return {
subject: opts.subject,
html: page.value,
text: `${opts.text.trim()}\n\n--\n${FOOTER_TEXT}\n`,
}
}