660 lines
No EOL
22 KiB
JavaScript
660 lines
No EOL
22 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Tabs,
|
|
Tab,
|
|
TextField,
|
|
Button,
|
|
Grid,
|
|
Divider,
|
|
CircularProgress,
|
|
Alert,
|
|
IconButton,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Accordion,
|
|
AccordionSummary,
|
|
AccordionDetails,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
Chip,
|
|
Tooltip,
|
|
Card,
|
|
CardContent
|
|
} from '@mui/material';
|
|
import {
|
|
Add as AddIcon,
|
|
Delete as DeleteIcon,
|
|
Edit as EditIcon,
|
|
Save as SaveIcon,
|
|
Visibility as PreviewIcon,
|
|
ExpandMore as ExpandMoreIcon,
|
|
FormatBold as BoldIcon,
|
|
FormatItalic as FormatItalicIcon,
|
|
FormatListBulleted as BulletListIcon,
|
|
FormatListNumbered as NumberedListIcon,
|
|
Link as LinkIcon,
|
|
Title as TitleIcon,
|
|
Info as InfoIcon
|
|
} from '@mui/icons-material';
|
|
import EmailEditor from 'react-email-editor';
|
|
import { useAdminSettingsByCategory, useDeleteSetting, useUpdateSetting } from '../../hooks/settingsAdminHooks';
|
|
|
|
// Available email template types
|
|
const EMAIL_TYPES = [
|
|
{ id: 'login_code', name: 'Login Code', description: 'Sent when a user requests a login code' },
|
|
{ id: 'shipping_notification', name: 'Shipping Notification', description: 'Sent when an order is shipped' },
|
|
{ id: 'order_confirmation', name: 'Order Confirmation', description: 'Sent when an order is placed' },
|
|
{ id: 'low_stock_alert', name: 'Low Stock Alert', description: 'Sent when product stock falls below threshold' },
|
|
{ id: 'welcome_email', name: 'Welcome Email', description: 'Sent when a user registers for the first time' },
|
|
{ id: 'custom', name: 'Custom Template', description: 'A custom email template for any purpose' }
|
|
];
|
|
|
|
// Template variable placeholders for each email type
|
|
const TEMPLATE_VARIABLES = {
|
|
login_code: [
|
|
{ key: '{{code}}', description: 'The login verification code' },
|
|
{ key: '{{loginLink}}', description: 'Direct login link with the code' },
|
|
{ key: '{{email}}', description: 'User\'s email address' }
|
|
],
|
|
shipping_notification: [
|
|
{ key: '{{first_name}}', description: 'Customer\'s first name' },
|
|
{ key: '{{order_id}}', description: 'Order identifier' },
|
|
{ key: '{{tracking_number}}', description: 'Shipping tracking number' },
|
|
{ key: '{{carrier}}', description: 'Shipping carrier name' },
|
|
{ key: '{{tracking_link}}', description: 'Link to track the package' },
|
|
{ key: '{{shipped_date}}', description: 'Date the order was shipped' },
|
|
{ key: '{{estimated_delivery}}', description: 'Estimated delivery date/time' },
|
|
{ key: '{{items_html}}', description: 'HTML table of ordered items' },
|
|
{ key: '{{customer_message}}', description: 'Optional message from staff' }
|
|
],
|
|
order_confirmation: [
|
|
{ key: '{{first_name}}', description: 'Customer\'s first name' },
|
|
{ key: '{{order_id}}', description: 'Order identifier' },
|
|
{ key: '{{order_date}}', description: 'Date the order was placed' },
|
|
{ key: '{{order_total}}', description: 'Total amount of the order' },
|
|
{ key: '{{shipping_address}}', description: 'Shipping address' },
|
|
{ key: '{{items_html}}', description: 'HTML table of ordered items' }
|
|
],
|
|
low_stock_alert: [
|
|
{ key: '{{product_name}}', description: 'Name of the product low in stock' },
|
|
{ key: '{{current_stock}}', description: 'Current stock quantity' },
|
|
{ key: '{{threshold}}', description: 'Low stock threshold' }
|
|
],
|
|
welcome_email: [
|
|
{ key: '{{first_name}}', description: 'User\'s first name' },
|
|
{ key: '{{email}}', description: 'User\'s email address' }
|
|
],
|
|
custom: [] // Custom templates might have any variables
|
|
};
|
|
|
|
// Sample placeholder data for preview
|
|
const PREVIEW_DATA = {
|
|
login_code: {
|
|
code: '123456',
|
|
loginLink: 'https://example.com/verify?code=123456&email=user@example.com',
|
|
email: 'user@example.com'
|
|
},
|
|
shipping_notification: {
|
|
first_name: 'Jane',
|
|
order_id: 'ORD-1234567',
|
|
tracking_number: 'TRK123456789',
|
|
carrier: 'FedEx',
|
|
tracking_link: 'https://www.fedex.com/track?123456789',
|
|
shipped_date: '2025-04-29',
|
|
estimated_delivery: '2-3 business days',
|
|
items_html: `
|
|
<tr>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">Amethyst Geode</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">1</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$49.99</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$49.99</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">Driftwood Piece</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">2</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$14.99</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$29.98</td>
|
|
</tr>
|
|
`,
|
|
customer_message: 'Thank you for your order! We packaged it with extra care.'
|
|
},
|
|
order_confirmation: {
|
|
first_name: 'John',
|
|
order_id: 'ORD-9876543',
|
|
order_date: '2025-04-29',
|
|
order_total: '$94.97',
|
|
shipping_address: '123 Main St, Anytown, CA 12345',
|
|
items_html: `
|
|
<tr>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">Polished Labradorite</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">1</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$29.99</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$29.99</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">Fossil Fish</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">1</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$64.98</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">$64.98</td>
|
|
</tr>
|
|
`
|
|
},
|
|
low_stock_alert: {
|
|
product_name: 'Amethyst Geode',
|
|
current_stock: '2',
|
|
threshold: '5'
|
|
},
|
|
welcome_email: {
|
|
first_name: 'Emily',
|
|
email: 'emily@example.com'
|
|
},
|
|
custom: {}
|
|
};
|
|
|
|
// Default templates
|
|
const DEFAULT_TEMPLATES = {
|
|
login_code: {
|
|
// Simplified template structure for React Email Editor
|
|
body: {
|
|
rows: [
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "text",
|
|
values: {
|
|
containerPadding: "10px",
|
|
textAlign: "left",
|
|
text: "<h1>Your login code is: {{code}}</h1><p>This code will expire in 15 minutes.</p><p>Or click <a href=\"{{loginLink}}\">here</a> to log in directly.</p>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
shipping_notification: {
|
|
// Simplified template - the actual structure would be more complex in the real editor
|
|
body: {
|
|
rows: [
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "text",
|
|
values: {
|
|
containerPadding: "10px",
|
|
textAlign: "center",
|
|
text: "<h1>Your Order Has Shipped!</h1><p>Order #{{order_id}}</p>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "text",
|
|
values: {
|
|
containerPadding: "10px",
|
|
textAlign: "left",
|
|
text: "<p>Hello {{first_name}},</p><p>Good news! Your order has been shipped and is on its way to you.</p>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
welcome_email: {
|
|
// Simplified template
|
|
body: {
|
|
rows: [
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "text",
|
|
values: {
|
|
containerPadding: "10px",
|
|
textAlign: "center",
|
|
text: "<h1>Welcome to Rocks, Bones & Sticks!</h1>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "text",
|
|
values: {
|
|
containerPadding: "10px",
|
|
textAlign: "left",
|
|
text: "<p>Hello {{first_name}},</p><p>Thank you for creating an account with us. We're excited to have you join our community!</p>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
const EmailTemplatesPage = () => {
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
const [editingTemplate, setEditingTemplate] = useState(null);
|
|
const [templateList, setTemplateList] = useState([]);
|
|
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
|
|
const [previewContent, setPreviewContent] = useState('');
|
|
|
|
const emailEditorRef = useRef(null);
|
|
|
|
const { data: emailSettings, isLoading, error } = useAdminSettingsByCategory('email_templates');
|
|
const deleteSettingMutation = useDeleteSetting();
|
|
const updateSetting = useUpdateSetting();
|
|
|
|
useEffect(() => {
|
|
if (emailSettings) {
|
|
const templates = emailSettings.map(setting => {
|
|
try {
|
|
const templateData = JSON.parse(setting.value);
|
|
return { id: setting.key, ...templateData };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}).filter(Boolean);
|
|
setTemplateList(templates);
|
|
}
|
|
}, [emailSettings]);
|
|
|
|
const handleTabChange = (e, newValue) => {
|
|
// Only allow tab switching if not currently editing a template
|
|
if (!editingTemplate) {
|
|
setActiveTab(newValue);
|
|
}
|
|
};
|
|
|
|
const handleEditTemplate = (template) => {
|
|
setEditingTemplate({ ...template });
|
|
|
|
// If the email editor is loaded, set its design
|
|
if (emailEditorRef.current && template.design) {
|
|
setTimeout(() => {
|
|
emailEditorRef.current.editor.loadDesign(template.design);
|
|
}, 500);
|
|
}
|
|
};
|
|
|
|
const handleSaveTemplate = async () => {
|
|
if (!editingTemplate || !emailEditorRef.current) return;
|
|
|
|
try {
|
|
// Save the design from the email editor
|
|
emailEditorRef.current.editor.exportHtml(async (data) => {
|
|
const { design, html } = data;
|
|
|
|
// Update the template with the new design and HTML
|
|
const updatedTemplate = {
|
|
...editingTemplate,
|
|
design: design, // Store the design JSON for future editing
|
|
content: html, // Store the generated HTML for rendering
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
await updateSetting.mutateAsync({
|
|
key: updatedTemplate.id,
|
|
value: JSON.stringify(updatedTemplate),
|
|
category: 'email_templates'
|
|
});
|
|
|
|
setTemplateList(prev => prev.map(t => t.id === updatedTemplate.id ? updatedTemplate : t));
|
|
setEditingTemplate(null);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to save template:', error);
|
|
}
|
|
};
|
|
|
|
const handlePreviewTemplate = (template) => {
|
|
if (template.content) {
|
|
setPreviewContent(`
|
|
<div style="max-width:600px;margin:0 auto;border:1px solid #ccc">
|
|
<div style="padding:10px;background:#f5f5f5;font-weight:bold">Subject: ${template.subject}</div>
|
|
${template.content}
|
|
</div>
|
|
`);
|
|
setPreviewDialogOpen(true);
|
|
} else if (emailEditorRef.current) {
|
|
emailEditorRef.current.editor.exportHtml((data) => {
|
|
const { html } = data;
|
|
setPreviewContent(`
|
|
<div style="max-width:600px;margin:0 auto;border:1px solid #ccc">
|
|
<div style="padding:10px;background:#f5f5f5;font-weight:bold">Subject: ${template.subject}</div>
|
|
${html}
|
|
</div>
|
|
`);
|
|
setPreviewDialogOpen(true);
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleCreateTemplate = () => {
|
|
const templateType = EMAIL_TYPES[activeTab === 0 ? 5 : activeTab - 1].id;
|
|
const templateName = EMAIL_TYPES[activeTab === 0 ? 5 : activeTab - 1].name;
|
|
|
|
// Create default HTML content for the new template
|
|
let defaultContent = `<h1>Your ${templateName}</h1><p>Start editing this template to customize it for your needs.</p>`;
|
|
|
|
// Add sample placeholders based on template type
|
|
if (templateType === 'login_code') {
|
|
defaultContent = `<h1>Your login code is: {{code}}</h1>
|
|
<p>This code will expire in 15 minutes.</p>
|
|
<p>Or click <a href="{{loginLink}}">here</a> to log in directly.</p>`;
|
|
} else if (templateType === 'shipping_notification') {
|
|
defaultContent = `<h1>Your Order Has Shipped!</h1>
|
|
<p>Hello {{first_name}},</p>
|
|
<p>Good news! Your order #{{order_id}} has been shipped and is on its way to you.</p>`;
|
|
} else if (templateType === 'welcome_email') {
|
|
defaultContent = `<h1>Welcome to Rocks, Bones & Sticks!</h1>
|
|
<p>Hello {{first_name}},</p>
|
|
<p>Thank you for creating an account with us. We're excited to have you join our community!</p>`;
|
|
}
|
|
|
|
const newTemplate = {
|
|
id: `email_template_${Date.now()}`,
|
|
name: `New ${templateName}`,
|
|
type: templateType,
|
|
subject: `Your ${templateName}`,
|
|
content: defaultContent,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
setEditingTemplate(newTemplate);
|
|
};
|
|
|
|
const onEditorReady = () => {
|
|
// You can perform any setup actions here when the editor is loaded
|
|
console.log('Email editor is ready');
|
|
|
|
// If there's a template being edited, load its design
|
|
if (editingTemplate?.design && emailEditorRef.current) {
|
|
emailEditorRef.current.editor.loadDesign(editingTemplate.design);
|
|
}
|
|
// If there's no design but we have HTML content, create a default design with that content
|
|
else if (editingTemplate?.content && emailEditorRef.current) {
|
|
const defaultDesign = {
|
|
body: {
|
|
rows: [
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "html",
|
|
values: {
|
|
html: editingTemplate.content,
|
|
containerPadding: "10px"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
};
|
|
emailEditorRef.current.editor.loadDesign(defaultDesign);
|
|
}
|
|
// If it's a new template with no design or content, load the default template
|
|
else if (editingTemplate && emailEditorRef.current) {
|
|
// Try to load a default template for the template type
|
|
const defaultTemplate = DEFAULT_TEMPLATES[editingTemplate.type] || {
|
|
body: {
|
|
rows: [
|
|
{
|
|
cells: [1],
|
|
columns: [
|
|
{
|
|
contents: [
|
|
{
|
|
type: "html",
|
|
values: {
|
|
html: "<h1>Your " + editingTemplate.name + "</h1><p>Start editing your email template here.</p>",
|
|
containerPadding: "10px"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
};
|
|
emailEditorRef.current.editor.loadDesign(defaultTemplate);
|
|
}
|
|
};
|
|
|
|
if (isLoading) return <Box textAlign="center" py={5}><CircularProgress /></Box>;
|
|
if (error) return <Alert severity="error">Error loading templates.</Alert>;
|
|
let first_name = "{{first_name}}"
|
|
return (
|
|
<Box>
|
|
<Typography variant="h4" mb={2}>Email Templates</Typography>
|
|
<Paper sx={{ mb: 3 }}>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', px: 2 }}>
|
|
<Tabs
|
|
value={activeTab}
|
|
onChange={handleTabChange}
|
|
variant="scrollable"
|
|
scrollButtons="auto"
|
|
disabled={!!editingTemplate}
|
|
>
|
|
<Tab label="All" />
|
|
{EMAIL_TYPES.map(type => <Tab key={type.id} label={type.name} />)}
|
|
</Tabs>
|
|
{activeTab > 0 && (
|
|
<Button
|
|
startIcon={<AddIcon />}
|
|
color="primary"
|
|
variant="contained"
|
|
onClick={handleCreateTemplate}
|
|
sx={{ my: 1 }}
|
|
disabled={!!editingTemplate}
|
|
>
|
|
Create {EMAIL_TYPES[activeTab - 1]?.name} Template
|
|
</Button>
|
|
)}
|
|
</Box>
|
|
</Paper>
|
|
|
|
{!editingTemplate ? (
|
|
<Grid container spacing={2}>
|
|
{templateList.length > 0 ? (
|
|
templateList
|
|
.filter(template => activeTab === 0 || template.type === EMAIL_TYPES[activeTab - 1]?.id)
|
|
.map(template => (
|
|
<Grid item xs={12} md={6} key={template.id}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
|
|
<Typography variant="h6">{template.name}</Typography>
|
|
<Chip
|
|
label={EMAIL_TYPES.find(t => t.id === template.type)?.name || 'Unknown'}
|
|
size="small"
|
|
color="primary"
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
<Typography variant="caption" display="block" color="text.secondary" gutterBottom>
|
|
Last updated: {new Date(template.updatedAt).toLocaleString()}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Subject: {template.subject}
|
|
</Typography>
|
|
<Box mt={2} display="flex" gap={1}>
|
|
<Button
|
|
onClick={() => handleEditTemplate(template)}
|
|
startIcon={<EditIcon />}
|
|
variant="outlined"
|
|
size="small"
|
|
>
|
|
Edit
|
|
</Button>
|
|
<Button
|
|
onClick={() => handlePreviewTemplate(template)}
|
|
startIcon={<PreviewIcon />}
|
|
variant="outlined"
|
|
size="small"
|
|
>
|
|
Preview
|
|
</Button>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
))
|
|
) : (
|
|
<Grid item xs={12}>
|
|
<Alert severity="info">
|
|
No email templates found. Click "Create Template" to create your first template.
|
|
</Alert>
|
|
</Grid>
|
|
)}
|
|
</Grid>
|
|
) : (
|
|
// Edit view
|
|
<Box mb={3}>
|
|
<Paper sx={{ p: 2, mb: 2 }}>
|
|
<Grid container spacing={2}>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Template Name"
|
|
value={editingTemplate?.name || ''}
|
|
onChange={(e) => setEditingTemplate(prev => ({ ...prev, name: e.target.value }))}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Subject Line"
|
|
value={editingTemplate?.subject || ''}
|
|
onChange={(e) => setEditingTemplate(prev => ({ ...prev, subject: e.target.value }))}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</Paper>
|
|
|
|
<Paper sx={{ p: 2 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<Typography variant="subtitle1" gutterBottom sx={{ mr: 1 }}>Email Content</Typography>
|
|
</Box>
|
|
|
|
<Alert severity="info" sx={{ mb: 3 }}>
|
|
<Box>
|
|
<Typography variant="subtitle1" fontWeight="medium">Tips for creating effective email templates:</Typography>
|
|
<ol>
|
|
<li>For best results, design your emails visually in Figma first</li>
|
|
<li>Export your design as HTML or use an email-specific design tool</li>
|
|
<li>Copy the HTML into an HTML block in this editor</li>
|
|
<li>Add dynamic variables like {`{{first_name}}`} as text where needed</li>
|
|
<li>Reference "Available Template Variables" below this tip</li>
|
|
</ol>
|
|
</Box>
|
|
</Alert>
|
|
|
|
|
|
<Accordion sx={{ mb: 2 }}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
<Typography>Available Template Variables</Typography>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
<Typography variant="body2" gutterBottom>
|
|
You can use these variables in your message. They will be replaced when the email is sent.
|
|
</Typography>
|
|
<List dense>
|
|
{TEMPLATE_VARIABLES[editingTemplate.type]?.map(variable => (
|
|
<ListItem key={variable.key}>
|
|
<ListItemText
|
|
primary={variable.key}
|
|
secondary={variable.description}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
|
|
|
|
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
|
<Button onClick={() => setEditingTemplate(null)}>Cancel</Button>
|
|
<Button
|
|
onClick={() => handlePreviewTemplate(editingTemplate)}
|
|
startIcon={<PreviewIcon />}
|
|
>
|
|
Preview
|
|
</Button>
|
|
<Button
|
|
onClick={handleSaveTemplate}
|
|
startIcon={<SaveIcon />}
|
|
variant="contained"
|
|
disabled={updateSetting.isLoading}
|
|
>
|
|
{updateSetting.isLoading ? <CircularProgress size={24} /> : 'Save Changes'}
|
|
</Button>
|
|
</Box>
|
|
<Box sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 1, height: '600px' }}>
|
|
<EmailEditor
|
|
ref={emailEditorRef}
|
|
onReady={onEditorReady}
|
|
minHeight="600px"
|
|
/>
|
|
</Box>
|
|
</Paper>
|
|
</Box>
|
|
)}
|
|
|
|
{/* Preview Dialog */}
|
|
<Dialog open={previewDialogOpen} onClose={() => setPreviewDialogOpen(false)} maxWidth="md" fullWidth>
|
|
<DialogTitle>Email Preview</DialogTitle>
|
|
<DialogContent>
|
|
<Box dangerouslySetInnerHTML={{ __html: previewContent }} />
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setPreviewDialogOpen(false)}>Close</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default EmailTemplatesPage; |