import React, { useState, useEffect, useCallback } from 'react'; import { EmailTemplate } from '../types'; import { saveTemplate, generateSQL, generateSelectSQL, getTemplates, generateTemplateKey, generateN8nCode } from '../services/storage'; import { generateEmailContent } from '../services/geminiService'; import RichTextEditor from './RichTextEditor'; import { Save, ArrowLeft, Eye, Database, Wand2, Copy, Check, Code } from 'lucide-react'; interface Props { templateId?: string; initialTemplate?: EmailTemplate; onBack: () => void; onSave: () => void; } const DEFAULT_HEADER = `

My Company

`; const DEFAULT_BODY = `

Gentile {{first_name}},

This is a default message body.

`; const DEFAULT_FOOTER = `

© 2024 My Company. All rights reserved.

`; const Editor: React.FC = ({ initialTemplate, onBack, onSave }) => { const [name, setName] = useState(initialTemplate?.name || 'New Template'); const [description, setDescription] = useState(initialTemplate?.description || ''); const [subject, setSubject] = useState(initialTemplate?.subject || 'Welcome to our service'); const [header, setHeader] = useState(initialTemplate?.header || DEFAULT_HEADER); const [body, setBody] = useState(initialTemplate?.body || DEFAULT_BODY); const [footer, setFooter] = useState(initialTemplate?.footer || DEFAULT_FOOTER); const [activeTab, setActiveTab] = useState<'header' | 'body' | 'footer'>('body'); const [showSqlModal, setShowSqlModal] = useState(false); const [detectedVars, setDetectedVars] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [isSaving, setIsSaving] = useState(false); const [aiPrompt, setAiPrompt] = useState(''); const [showAiModal, setShowAiModal] = useState(false); const [nameError, setNameError] = useState(''); // Variable detection logic const detectVariables = useCallback(() => { const regex = /\{\{([\w\d_-]+)\}\}/g; const allText = `${subject} ${header} ${body} ${footer}`; const matches = [...allText.matchAll(regex)]; const uniqueVars = Array.from(new Set(matches.map(m => m[1]))); setDetectedVars(uniqueVars); }, [header, body, footer, subject]); useEffect(() => { detectVariables(); }, [detectVariables]); // Clear name error when typing useEffect(() => { if (nameError) setNameError(''); }, [name]); const handleSave = async () => { const newKey = generateTemplateKey(name); if (!newKey) { setNameError('Template name cannot be empty or symbols only.'); alert('Template name cannot be empty.'); return; } setIsSaving(true); try { // Check for duplicates const allTemplates = await getTemplates(); const isDuplicate = allTemplates.some(t => { // Exclude current template if we are editing if (initialTemplate && t.id === initialTemplate.id) return false; return generateTemplateKey(t.name) === newKey; }); if (isDuplicate) { setNameError('A template with this name already exists.'); alert('A template with this name (or resulting ID) already exists. Please choose a unique name.'); setIsSaving(false); return; } const newTemplate: EmailTemplate = { id: initialTemplate?.id || crypto.randomUUID(), name, description, subject, header, body, footer, variables: detectedVars, updatedAt: new Date().toISOString() }; await saveTemplate(newTemplate); onSave(); } catch (e) { alert("Failed to save template. Check server logs."); } finally { setIsSaving(false); } }; const handleAiGenerate = async () => { if (!aiPrompt.trim()) return; setIsGenerating(true); try { const generated = await generateEmailContent(aiPrompt, activeTab); if (activeTab === 'header') setHeader(generated); if (activeTab === 'body') setBody(generated); if (activeTab === 'footer') setFooter(generated); setShowAiModal(false); setAiPrompt(''); } catch (e) { alert("Error generating content. Please check API Key configuration."); } finally { setIsGenerating(false); } }; const tempTemplateObj = { id: initialTemplate?.id || 'PREVIEW', name, description, subject, header, body, footer, variables: detectedVars, updatedAt: '' }; const currentInsertSql = showSqlModal ? generateSQL(tempTemplateObj) : ''; const currentSelectSql = showSqlModal ? generateSelectSQL(tempTemplateObj) : ''; const currentN8nCode = showSqlModal ? generateN8nCode(tempTemplateObj) : ''; const getActiveContent = () => { switch (activeTab) { case 'header': return header; case 'body': return body; case 'footer': return footer; } }; const setActiveContent = (val: string) => { switch (activeTab) { case 'header': setHeader(val); break; case 'body': setBody(val); break; case 'footer': setFooter(val); break; } }; return (
{/* Top Bar */}

{initialTemplate ? 'Edit Template' : 'Create Template'}

{/* Left: Inputs */}
{/* Metadata */}
setName(e.target.value)} className={`w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-brand-500 outline-none bg-white ${nameError ? 'border-red-500 focus:border-red-500' : 'border-slate-300 focus:border-brand-500'}`} placeholder="e.g., Welcome Email" /> {nameError &&

{nameError}

}

Must be unique. Used to generate the database key: {generateTemplateKey(name) || '...'}

setSubject(e.target.value)} className="w-full px-3 py-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none bg-white" placeholder="Subject line... (supports {{placeholders}})" />
{/* Variable Manager */}
Active Variables Auto-detected from text
{detectedVars.length === 0 && No variables detected yet. Type {'{{name}}'} to add one.} {detectedVars.map(v => ( {`{{${v}}}`} ))}
{/* Tabs */}
{(['header', 'body', 'footer'] as const).map((tab) => ( ))}
{/* Editor Area */}

Use the "Variable" button in the toolbar to insert placeholders like {'{{name}}'}.

{/* Right: Live Preview */}
Live Preview
Renders as standard HTML
{/* Scrollable container: flex-1 ensures it takes available space, overflow-y-auto enables scrolling */}
{/* mx-auto centers the card without using flexbox on the parent which can cause scroll issues */}
{/* Simulate Subject Line in Preview */}
Subject:

{subject.replace(/\{\{([\w\d_-]+)\}\}/g, (match, p1) => `${match}`)}

{/* Email Content */}
{/* SQL Modal */} {showSqlModal && (

Integration Details

{/* INSERT Section */}
                        {currentInsertSql}
                      
                        {currentSelectSql}
                      
{/* n8n Code Section */}
                      {currentN8nCode}
                    

Paste this into a Code Node connected after your SQL node. Replace REPLACE_WITH_VALUE with your actual data variables (e.g. $('NodeName').item.json.name).

)} {/* AI Modal */} {showAiModal && (

AI Content Generator

Describe what you want for the {activeTab} section. The AI will generate HTML code with placeholders.