"use client"; import { useReducer, useCallback } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import type { Template, TemplateType, CampaignType } from "@/actions/communications/templates"; import { formatDate } from "@/lib/format-date"; import { upsertTemplate } from "@/actions/communications/templates"; import { BUILT_IN_TEMPLATES, renderTemplate, TEMPLATE_VARIABLES, type EmailTemplate, } from "@/lib/email-templates"; const TEMPLATE_TYPES: { value: TemplateType; label: string }[] = [ { value: "email_template", label: "Email" }, { value: "sms_template", label: "SMS" }, { value: "internal_note", label: "Internal Note" }, ]; const CAMPAIGN_TYPES: { value: CampaignType; label: string }[] = [ { value: "operational", label: "Operational" }, { value: "marketing", label: "Marketing" }, { value: "transactional", label: "Transactional" }, ]; const TYPE_COLORS: Record = { email_template: "bg-blue-100 text-blue-700", sms_template: "bg-emerald-100 text-emerald-700", internal_note: "bg-stone-100 text-stone-600", }; // Sample variables for preview rendering const SAMPLE_VARS: Record = { first_name: "Jane", company_name: "Route Commerce", stop_name: "Tuxedo Warehouse", pickup_date: "May 15, 2026", order_total: "$450.00", balance_due: "$225.00", due_date: "May 10, 2026", item_summary: "10× Tuxedo Set, 5× Dress Shirt", custom_message: "We look forward to seeing you!", cta_text: "Shop Now", cta_url: "https://routecommerce.com", tag: "New Arrivals", headline: "Spring Collection is Here!", unsubscribe_url: "#", }; // Icon components const Icons = { plus: (className: string) => ( ), x: (className: string) => ( ), fileText: (className: string) => ( ), arrowRight: (className: string) => ( ), }; // New Template Modal — local reducer for its tiny form state. type NewModalState = { saving: boolean; name: string; templateType: TemplateType; error: string; }; type NewModalAction = | { type: "SET_SAVING"; value: boolean } | { type: "SET_NAME"; value: string } | { type: "SET_TEMPLATE_TYPE"; value: TemplateType } | { type: "SET_ERROR"; value: string } | { type: "RESET" }; const newModalInitial: NewModalState = { saving: false, name: "", templateType: "email_template", error: "", }; function newModalReducer(state: NewModalState, action: NewModalAction): NewModalState { switch (action.type) { case "SET_SAVING": return { ...state, saving: action.value }; case "SET_NAME": return { ...state, name: action.value }; case "SET_TEMPLATE_TYPE": return { ...state, templateType: action.value }; case "SET_ERROR": return { ...state, error: action.value }; case "RESET": return { ...newModalInitial }; } } // New Template Modal function NewTemplateModal({ isOpen, onClose, onSuccess, brandId }: { isOpen: boolean; onClose: () => void; onSuccess: () => void; brandId: string; }) { const router = useRouter(); const [state, dispatch] = useReducer(newModalReducer, newModalInitial); if (!isOpen) return null; const handleCreate = async () => { if (!state.name.trim()) { dispatch({ type: "SET_ERROR", value: "Template name is required" }); return; } dispatch({ type: "SET_SAVING", value: true }); dispatch({ type: "SET_ERROR", value: "" }); const result = await upsertTemplate({ brand_id: brandId, name: state.name.trim(), subject: "", body_text: "", template_type: state.templateType, }); dispatch({ type: "SET_SAVING", value: false }); if (!result.success) { dispatch({ type: "SET_ERROR", value: result.error ?? "Failed to create template" }); return; } dispatch({ type: "RESET" }); onSuccess(); router.refresh(); }; return (
{/* Backdrop */}
{/* Content */}
{state.error && (
{state.error}
)}
dispatch({ type: "SET_NAME", value: e.target.value })} className="w-full border border-[var(--admin-border)] rounded-lg px-3 py-2.5 text-sm bg-white text-[var(--admin-text-primary)] focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none" placeholder="e.g. Pickup Reminder" />
{/* Footer */}
); } // TemplateListPanel — separate reducer for its single boolean flag. type ListPanelState = { showNewModal: boolean }; type ListPanelAction = { type: "SHOW_NEW" } | { type: "HIDE_NEW" }; function listPanelReducer(_state: ListPanelState, action: ListPanelAction): ListPanelState { switch (action.type) { case "SHOW_NEW": return { showNewModal: true }; case "HIDE_NEW": return { showNewModal: false }; } } export function TemplateListPanel({ templates, brandId = "64294306-5f42-463d-a5e8-2ad6c81a96de" }: { templates: Template[], brandId?: string }) { const router = useRouter(); const [state, dispatch] = useReducer(listPanelReducer, { showNewModal: false }); return (
dispatch({ type: "HIDE_NEW" })} onSuccess={() => { dispatch({ type: "HIDE_NEW" }); router.refresh(); }} brandId={brandId} /> {/* Header */}
{Icons.fileText("w-4 h-4 text-[var(--admin-bg)]")}

Templates

{templates.length} template{templates.length !== 1 ? "s" : ""}

{/* Table */} {templates.length === 0 ? (
No templates yet
) : ( router.push(`/admin/communications/templates/${id}`)} /> )}
); } function TemplatesTable({ templates, onSelect, }: { templates: Template[]; onSelect: (id: string) => void; }) { return (
{templates.map((t) => ( onSelect(t.id)} > ))}
Name Type Subject Updated
{t.name} {t.template_type} {t.subject} {formatDate(new Date(t.updated_at))} {Icons.arrowRight("h-4 w-4 text-[var(--admin-text-muted)]")}
); } // TemplateEditForm — main reducer for the edit/new form. type EditState = { saving: boolean; name: string; subject: string; bodyText: string; bodyHtml: string; templateType: TemplateType; campaignType: CampaignType | ""; error: string; activeTab: "edit" | "preview"; previewDevice: "desktop" | "mobile"; htmlMode: boolean; }; type EditAction = | { type: "SET_SAVING"; value: boolean } | { type: "SET_NAME"; value: string } | { type: "SET_SUBJECT"; value: string } | { type: "SET_BODY_TEXT"; value: string } | { type: "SET_BODY_HTML"; value: string } | { type: "SET_TEMPLATE_TYPE"; value: TemplateType } | { type: "SET_CAMPAIGN_TYPE"; value: CampaignType | "" } | { type: "SET_ERROR"; value: string } | { type: "SET_ACTIVE_TAB"; value: "edit" | "preview" } | { type: "SET_PREVIEW_DEVICE"; value: "desktop" | "mobile" } | { type: "SET_HTML_MODE"; value: boolean } | { type: "APPLY_BUILT_IN"; tpl: EmailTemplate } | { type: "APPEND_BODY_TEXT"; value: string }; function editStateFromTemplate(t?: Template): EditState { return { saving: false, name: t?.name ?? "", subject: t?.subject ?? "", bodyText: t?.body_text ?? "", bodyHtml: t?.body_html ?? "", templateType: t?.template_type ?? "email_template", campaignType: t?.campaign_type ?? "", error: "", activeTab: "edit", previewDevice: "desktop", htmlMode: !!t?.body_html, }; } function editReducer(state: EditState, action: EditAction): EditState { switch (action.type) { case "SET_SAVING": return { ...state, saving: action.value }; case "SET_NAME": return { ...state, name: action.value }; case "SET_SUBJECT": return { ...state, subject: action.value }; case "SET_BODY_TEXT": return { ...state, bodyText: action.value }; case "SET_BODY_HTML": return { ...state, bodyHtml: action.value }; case "SET_TEMPLATE_TYPE": return { ...state, templateType: action.value }; case "SET_CAMPAIGN_TYPE": return { ...state, campaignType: action.value }; case "SET_ERROR": return { ...state, error: action.value }; case "SET_ACTIVE_TAB": return { ...state, activeTab: action.value }; case "SET_PREVIEW_DEVICE": return { ...state, previewDevice: action.value }; case "SET_HTML_MODE": return { ...state, htmlMode: action.value }; case "APPLY_BUILT_IN": { const rendered = renderTemplate(action.tpl, SAMPLE_VARS); return { ...state, name: action.tpl.name, subject: rendered.subject, bodyText: rendered.body_text, bodyHtml: rendered.body_html, htmlMode: true, }; } case "APPEND_BODY_TEXT": return { ...state, bodyText: state.bodyText + action.value }; } } export function TemplateEditForm({ template, mode = "edit", brandId, }: { template?: Template; mode?: "edit" | "new"; brandId: string; }) { const router = useRouter(); const [state, dispatch] = useReducer(editReducer, template, editStateFromTemplate); const insertVariable = useCallback( (key: string) => { const token = `{{${key}}}`; const ta = document.getElementById("body-textarea") as HTMLTextAreaElement; if (ta) { const start = ta.selectionStart; const end = ta.selectionEnd; const next = state.bodyText.slice(0, start) + token + state.bodyText.slice(end); dispatch({ type: "SET_BODY_TEXT", value: next }); setTimeout(() => { ta.focus(); ta.setSelectionRange(start + token.length, start + token.length); }, 0); } else { dispatch({ type: "APPEND_BODY_TEXT", value: "\n" + token }); } }, [state.bodyText] ); const preview = { subject: state.subject || "Your Email Subject", body_text: state.bodyText, body_html: state.bodyHtml || `

${state.bodyText}

`, }; const handleSave = async () => { dispatch({ type: "SET_SAVING", value: true }); dispatch({ type: "SET_ERROR", value: "" }); const result = await upsertTemplate({ id: template?.id, brand_id: brandId, name: state.name, subject: state.subject, body_text: state.bodyText, body_html: (state.htmlMode && state.bodyHtml) ? state.bodyHtml : undefined, template_type: state.templateType, campaign_type: (state.campaignType as CampaignType) || undefined, }); dispatch({ type: "SET_SAVING", value: false }); if (!result.success) { dispatch({ type: "SET_ERROR", value: result.error ?? "Failed to save" }); return; } router.push("/admin/communications/templates"); router.refresh(); }; return (
{/* Header */} dispatch({ type: "SET_ACTIVE_TAB", value: v })} /> {state.error && (
{state.error}
)} {state.activeTab === "edit" ? ( dispatch({ type: "SET_NAME", value: v })} onSubjectChange={(v) => dispatch({ type: "SET_SUBJECT", value: v })} onBodyTextChange={(v) => dispatch({ type: "SET_BODY_TEXT", value: v })} onBodyHtmlChange={(v) => dispatch({ type: "SET_BODY_HTML", value: v })} onTemplateTypeChange={(v) => dispatch({ type: "SET_TEMPLATE_TYPE", value: v })} onCampaignTypeChange={(v) => dispatch({ type: "SET_CAMPAIGN_TYPE", value: v })} onHtmlModeChange={(v) => dispatch({ type: "SET_HTML_MODE", value: v })} onApplyBuiltIn={(tpl) => dispatch({ type: "APPLY_BUILT_IN", tpl })} onInsertVariable={insertVariable} onAppendSubject={(v) => dispatch({ type: "SET_SUBJECT", value: state.subject + v })} onSave={handleSave} /> ) : ( dispatch({ type: "SET_PREVIEW_DEVICE", value: v })} /> )}
); } function TemplateEditHeader({ mode, activeTab, onTabChange, }: { mode: "edit" | "new"; activeTab: "edit" | "preview"; onTabChange: (v: "edit" | "preview") => void; }) { return (
{Icons.fileText("w-4 h-4 text-[var(--admin-bg)]")}

{mode === "new" ? "New Template" : "Edit Template"}

{mode === "new" ? "Create a new email template" : "Update template settings"}

); } type EditFormPanelProps = { state: EditState; onNameChange: (v: string) => void; onSubjectChange: (v: string) => void; onBodyTextChange: (v: string) => void; onBodyHtmlChange: (v: string) => void; onTemplateTypeChange: (v: TemplateType) => void; onCampaignTypeChange: (v: CampaignType | "") => void; onHtmlModeChange: (v: boolean) => void; onApplyBuiltIn: (tpl: EmailTemplate) => void; onInsertVariable: (key: string) => void; onAppendSubject: (v: string) => void; onSave: () => void; }; function EditFormPanel({ state, onNameChange, onSubjectChange, onBodyTextChange, onBodyHtmlChange, onTemplateTypeChange, onCampaignTypeChange, onHtmlModeChange, onApplyBuiltIn, onInsertVariable, onAppendSubject, onSave, }: EditFormPanelProps) { return (
); } function TemplateInfoSection({ name, templateType, campaignType, onNameChange, onTemplateTypeChange, onCampaignTypeChange, onApplyBuiltIn, }: { name: string; templateType: TemplateType; campaignType: CampaignType | ""; onNameChange: (v: string) => void; onTemplateTypeChange: (v: TemplateType) => void; onCampaignTypeChange: (v: CampaignType | "") => void; onApplyBuiltIn: (tpl: EmailTemplate) => void; }) { return (

Template Info

{/* Built-in template picker */}
Start from:
onNameChange(e.target.value)} className="w-full border border-[var(--admin-border)] rounded-lg px-3 py-2 text-sm bg-white text-[var(--admin-text-primary)]" placeholder="e.g. Pickup Reminder" />
); } function SubjectSection({ subject, onSubjectChange, onAppendSubject, }: { subject: string; onSubjectChange: (v: string) => void; onAppendSubject: (v: string) => void; }) { return (

Subject Line

onSubjectChange(e.target.value)} className="w-full border border-[var(--admin-border)] rounded-lg px-3 py-2 text-sm bg-white text-[var(--admin-text-primary)]" placeholder="Email subject line" />

Variables:{" "} {TEMPLATE_VARIABLES.slice(0, 6).map((v) => ( ))}

); } function BodySection({ htmlMode, bodyText, bodyHtml, onHtmlModeChange, onBodyTextChange, onBodyHtmlChange, onInsertVariable, }: { htmlMode: boolean; bodyText: string; bodyHtml: string; onHtmlModeChange: (v: boolean) => void; onBodyTextChange: (v: string) => void; onBodyHtmlChange: (v: string) => void; onInsertVariable: (key: string) => void; }) { return (

Message Body

{/* Variable toolbar */}
Insert: {TEMPLATE_VARIABLES.map((v) => ( ))}
{htmlMode ? ( ) : ( )}

Tip: Click any variable button above to insert it at the cursor position. Variables are replaced when the email is sent.

); } function HtmlBodyEditors({ bodyText, bodyHtml, onBodyTextChange, onBodyHtmlChange, }: { bodyText: string; bodyHtml: string; onBodyTextChange: (v: string) => void; onBodyHtmlChange: (v: string) => void; }) { return ( <>