import { useEffect, useMemo, useState } from 'react' import { brandDefaults } from './brand/brandDefaults' import type { Brand } from './email/types' import { interpolate } from './email/interpolate' import { renderEmailTemplate } from './email/render' import { BrandEditor } from './components/BrandEditor' import { TemplatePicker } from './components/TemplatePicker' import { VariablesForm } from './components/VariablesForm' import { EmailPreview } from './components/EmailPreview' import { ExportPanel } from './components/ExportPanel' import { templateRegistry } from './templates/templates' function initDataForTemplate(templateVars: { key: string; defaultValue?: string }[]) { // Keep defaults stable when brand changes; this function only depends on template. const next: Record = {} for (const v of templateVars) next[v.key] = v.defaultValue ?? '' return next } export default function App() { const templates = templateRegistry const [brand, setBrand] = useState(brandDefaults) const [templateId, setTemplateId] = useState(templates[0]?.id ?? '') const template = useMemo(() => templates.find((t) => t.id === templateId) ?? templates[0], [templateId, templates]) const [data, setData] = useState>(() => { return initDataForTemplate(template?.variables ?? []) }) const handleTemplateChange = (nextId: string) => { setTemplateId(nextId) // Update variables immediately so the preview/exports switch reliably. const nextTemplate = templates.find((t) => t.id === nextId) if (nextTemplate) setData(initDataForTemplate(nextTemplate.variables)) } const subject = useMemo(() => { if (!template) return '' return interpolate(template.subjectTemplate, { ...data, hotelName: brand.hotelName }) }, [template, data, brand.hotelName]) const [html, setHtml] = useState('') const [text, setText] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { if (!template) return let cancelled = false // Clear previous output immediately so the iframe can't appear "stuck". setHtml('') setText('') const handle = setTimeout(async () => { setLoading(true) setError(null) try { const res = await renderEmailTemplate({ brand, template, data }) if (cancelled) return setHtml(res.html) setText(res.text) } catch (e) { if (cancelled) return setError(e instanceof Error ? e.message : 'Failed to render template') setHtml('') setText('') } finally { if (cancelled) return setLoading(false) } }, 350) return () => { cancelled = true clearTimeout(handle) } }, [brand, template, data]) const styles = { page: { minHeight: '100vh', padding: 18, background: '#0b1220', color: '#e5e7eb', fontFamily: 'ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"', }, grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 16, alignItems: 'start', }, card: { background: '#0f172a', border: '1px solid rgba(148, 163, 184, 0.25)', borderRadius: 14, padding: 14, }, sectionTitle: { margin: 0, fontSize: 14, fontWeight: 700, color: '#f1f5f9', }, } return (
Resend-ready Email Templates
Edit branding + variables, preview the branded email, then export HTML + plain text.
Template: {template?.name ?? '-'}
{template ? (
) : (
No template selected.
)}
) }