import React, { useEffect, useState, useCallback } from 'react' import { EmailEditorProvider, EmailEditor, IEmailTemplate } from 'easy-email-editor' import { ExtensionProps, StandardLayout } from 'easy-email-extensions' import { BasicType, AdvancedType, JsonToMjml } from 'easy-email-core' // ───────────────────────────────────────────────────────────────────────────── // Targo email editor — wraps easy-email-editor with our hub integration: // // 1. On mount: read ?name= from URL, GET its MJML from the hub // 2. Render easy-email with the loaded MJML // 3. On save (Cmd-S or button): convert easy-email JSON → MJML, PUT to hub // 4. postMessage to parent window so the wrapping ops UI knows we saved // // Hub URL is read from VITE_HUB_URL env (defaults to msg.gigafibre.ca in prod). // The hub does the MJML → HTML compilation server-side; we just send the MJML. // ───────────────────────────────────────────────────────────────────────────── const HUB_URL = (import.meta as any).env?.VITE_HUB_URL || 'https://msg.gigafibre.ca' // Merge tags exposed to the editor's "Variables" panel. These map to the // Mustache variables the hub renders at send time. const MERGE_TAGS = { firstname: '{{firstname}}', lastname: '{{lastname}}', email: '{{email}}', amount: '{{amount}}', gift_url: '{{gift_url}}', description: '{{description}}', expiry: '{{expiry}}', commitment_months: '{{commitment_months}}', year: '{{year}}', } // Minimal initial template returned when the hub has no content yet (rare — // since we always pre-create gift-email-fr.mjml). Kept defensive. function emptyTemplate(): IEmailTemplate { return { subject: 'Une offre exclusive de TARGO', subTitle: 'Comme toi, on aime les connexions stables et les relations durables.', content: { type: BasicType.PAGE, data: { value: { breakpoint: '480px', headAttributes: '', 'font-size': '16px', 'line-height': '1.5', 'font-family': "'Plus Jakarta Sans', Helvetica, Arial, sans-serif", }, }, attributes: { 'background-color': '#F5FAF7', width: '600px', }, children: [], } as any, } } export function EmailEditorApp() { const [templateName, setTemplateName] = useState('') const [initialValues, setInitialValues] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) // Read template name from URL and fetch its MJML content from the hub on mount useEffect(() => { const params = new URLSearchParams(window.location.search) const name = params.get('name') || 'gift-email-fr' setTemplateName(name) ;(async () => { try { const res = await fetch(`${HUB_URL}/campaigns/templates/${encodeURIComponent(name)}`) if (!res.ok) throw new Error(`Hub returned ${res.status}`) const data = await res.json() // Three sources of truth, in priority order: // 1. .json file → easy-email JSON tree (fast, full restore) // 2. .mjml file → MJML source (no auto-importer, start blank) // 3. nothing → empty page if (data.json) { try { const parsed = typeof data.json === 'string' ? JSON.parse(data.json) : data.json setInitialValues(parsed as IEmailTemplate) } catch (e: any) { setError(`Stored JSON is invalid (${e.message}) — starting blank`) setInitialValues(emptyTemplate()) } } else if (data.mjml) { setError(`Existing MJML (${(data.mjml || '').length}b) cannot be auto-imported into easy-email. ` + `Reconstructing this template once with the drag-drop blocks here will save an editable JSON snapshot for next time.`) setInitialValues(emptyTemplate()) } else { setInitialValues(emptyTemplate()) } } catch (e: any) { setError(`Could not load template "${name}": ${e.message}`) setInitialValues(emptyTemplate()) } finally { setLoading(false) } })() }, []) // Save → convert easy-email's JSON tree to MJML, PUT to hub const onSave = useCallback(async (values: IEmailTemplate) => { if (!templateName) return setSaving(true) setError(null) try { const mjmlSource = JsonToMjml({ data: values.content as any, mode: 'production', context: values.content as any, }) // Send BOTH the compiled MJML (for send-worker) AND the raw easy-email // JSON tree (for next-load restore). Hub persists .mjml + .html + .json // — the JSON file is the canonical editing source going forward. const res = await fetch(`${HUB_URL}/campaigns/templates/${encodeURIComponent(templateName)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mjml: mjmlSource, json: values }), }) if (!res.ok) { const errBody = await res.json().catch(() => ({})) throw new Error(errBody.error || `Hub returned ${res.status}`) } // Notify parent window (the ops UI iframing us) that we saved if (window.parent !== window) { window.parent.postMessage( { type: 'email-editor:saved', template: templateName, ts: Date.now() }, '*', ) } // Visual confirmation (toast handled by easy-email's own UI) } catch (e: any) { setError(`Save failed: ${e.message}`) } finally { setSaving(false) } }, [templateName]) if (loading) { return
Loading template…
} if (!initialValues) { return
{error}
} return (
{/* Top bar — shows template name + save state + parent communication */}
TARGO Email Editor · {templateName} {saving && · Saving…} {error && ( {error} )}
{/* Editor */}
`{{${tag}}}`} onSubmit={onSave} > {() => ( )}
) } // Block categories shown in the left sidebar — same set easy-email uses by // default, organized for email composition. const DEFAULT_CATEGORIES: ExtensionProps['categories'] = [ { label: 'Content', active: true, blocks: [ { type: AdvancedType.TEXT }, { type: AdvancedType.BUTTON }, { type: AdvancedType.IMAGE }, { type: AdvancedType.DIVIDER }, { type: AdvancedType.SPACER }, { type: AdvancedType.HERO }, { type: AdvancedType.WRAPPER }, ], }, { label: 'Layout', active: true, displayType: 'column', blocks: [ { title: '1 column', payload: [['100%']], }, { title: '2 columns', payload: [['50%', '50%']], }, { title: '3 columns', payload: [['33%', '33%', '33%']], }, { title: '4 columns', payload: [['25%', '25%', '25%', '25%']], }, ], }, ]