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'}
setShowSqlModal(true)}
className="flex items-center gap-2 px-4 py-2 text-slate-600 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 font-medium transition-colors"
>
Integration
{isSaving ? 'Saving...' : 'Save Template'}
{/* Left: Inputs */}
{/* Metadata */}
{/* 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) => (
setActiveTab(tab)}
className={`px-4 py-2 font-medium text-sm capitalize ${
activeTab === tab
? 'text-brand-600 border-b-2 border-brand-600'
: 'text-slate-500 hover:text-slate-700'
}`}
>
{tab}
))}
{/* Editor Area */}
Content Editor
setShowAiModal(true)}
className="text-xs flex items-center gap-1 text-purple-600 hover:text-purple-700 font-medium bg-purple-50 px-2 py-1 rounded"
>
Generate with AI
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
setShowSqlModal(false)} className="text-slate-400 hover:text-slate-600">
×
{/* INSERT Section */}
1. Setup (Run once in DB)
{currentInsertSql}
navigator.clipboard.writeText(currentInsertSql)}
className="absolute top-2 right-2 p-2 bg-slate-700 text-white rounded hover:bg-slate-600"
title="Copy INSERT SQL"
>
2. Fetch (n8n SQL Node)
{currentSelectSql}
navigator.clipboard.writeText(currentSelectSql)}
className="absolute top-2 right-2 p-2 bg-slate-600 text-white rounded hover:bg-slate-500"
title="Copy SELECT SQL"
>
{/* n8n Code Section */}
3. Populate (n8n Code Node)
{currentN8nCode}
navigator.clipboard.writeText(currentN8nCode)}
className="absolute top-2 right-2 p-2 bg-white border border-slate-200 text-slate-600 rounded hover:bg-slate-50 shadow-sm"
title="Copy JS Code"
>
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.
)}
);
};
export default Editor;