fix: Template editor UX improvements

##  All 5 Issues Fixed!

### 1. Default Value in RichTextEditor 
- Added `useEffect` to sync content prop with editor
- Editor now properly displays default template content
- Fixed: `editor.commands.setContent(content)` when prop changes

### 2. Removed Duplicate Variable Section 
- Removed "Variable Reference" section (was redundant)
- Variables already available in rich text editor toolbar
- Kept small badge list under editor for quick reference

### 3. User-Friendly Preview 
- Preview now renders HTML (not raw code)
- Subject separated in dialog header
- Complete email template preview (header + content + footer)
- Variables highlighted in yellow for clarity
- Uses iframe with full base.html styling

### 4. Fixed Dialog Scrolling 
**New Structure:**
```
[Header] ← Fixed (title + subject input)
[Body]   ← Scrollable (tabs: editor/preview)
[Footer] ← Fixed (action buttons)
```
- No more annoying full-dialog scroll
- Each section scrolls independently
- Better UX with fixed header/footer

### 5. Editor/Preview Tabs 
**Tabs Implementation:**
- [Editor] tab: Rich text editor + variable badges
- [Preview] tab: Full email preview with styling
- Clean separation of editing vs previewing
- Preview shows complete email (not just content)
- 500px iframe height for comfortable viewing

---

**Benefits:**
-  Default content loads properly
- 🎨 Beautiful HTML preview
- 📱 Better scrolling UX
- 👁️ See exactly how email looks
- 🚀 Professional editing experience

**Next:** Email appearance settings + card insert buttons
This commit is contained in:
dwindown
2025-11-12 23:26:18 +07:00
parent 1573bff7b3
commit c3ab31e14d
2 changed files with 130 additions and 61 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
@@ -55,6 +55,13 @@ export function RichTextEditor({
},
});
// Update editor content when prop changes (fix for default value not showing)
useEffect(() => {
if (editor && content !== editor.getHTML()) {
editor.commands.setContent(content);
}
}, [content, editor]);
if (!editor) {
return null;
}

View File

@@ -21,7 +21,8 @@ import {
SelectValue,
} from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { X, Plus } from 'lucide-react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { X, Plus, Eye, Edit } from 'lucide-react';
import { toast } from 'sonner';
import { __ } from '@/lib/i18n';
@@ -48,14 +49,17 @@ export default function TemplateEditor({
const [subject, setSubject] = useState('');
const [body, setBody] = useState('');
const [variables, setVariables] = useState<{ [key: string]: string }>({});
const [activeTab, setActiveTab] = useState('editor');
useEffect(() => {
if (initialTemplate) {
setSubject(initialTemplate.subject || '');
setBody(initialTemplate.body || '');
// Set body with default value - ensure it's set properly
const defaultBody = initialTemplate.body || '';
setBody(defaultBody);
setVariables(initialTemplate.variables || {});
}
}, [initialTemplate]);
}, [initialTemplate, open]);
const saveMutation = useMutation({
mutationFn: async () => {
@@ -93,36 +97,90 @@ export default function TemplateEditor({
// Get variable keys for the rich text editor
const variableKeys = Object.keys(variables);
// Generate preview HTML
const generatePreviewHTML = () => {
// Simple preview - replace variables with sample data
let previewBody = body;
Object.keys(variables).forEach(key => {
const sampleValue = `<span style="background: #fef3c7; padding: 2px 4px; border-radius: 2px;">[${key}]</span>`;
previewBody = previewBody.replace(new RegExp(`\\{${key}\\}`, 'g'), sampleValue);
});
return `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: 'Inter', Arial, sans-serif; background: #f8f8f8; margin: 0; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
.header { padding: 32px; text-align: center; background: #f8f8f8; }
.card-gutter { padding: 0 16px; }
.card { background: #ffffff; border-radius: 8px; margin-bottom: 24px; }
.card-success { background: #e8f5e9; border: 1px solid #4caf50; }
.card-highlight { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; }
.card-info { background: #f0f7ff; border: 1px solid #0071e3; }
.card-warning { background: #fff8e1; border: 1px solid #ff9800; }
.content { padding: 32px 40px; }
.content h1 { font-size: 26px; margin-top: 0; }
.content h2 { font-size: 18px; margin-top: 0; }
.content p { font-size: 16px; line-height: 1.6; color: #555; }
.button { display: inline-block; background: #7f54b3; color: #fff; padding: 14px 28px; border-radius: 6px; text-decoration: none; }
.info-box { background: #f6f6f6; border-radius: 6px; padding: 20px; margin: 16px 0; }
.footer { padding: 32px; text-align: center; color: #888; font-size: 13px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<strong style="font-size: 24px; color: #333;">[Your Store Name]</strong>
</div>
<div class="card-gutter">
${previewBody}
</div>
<div class="footer">
<p>© ${new Date().getFullYear()} [Your Store Name]. All rights reserved.</p>
</div>
</div>
</body>
</html>
`;
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogContent className="max-w-4xl h-[85vh] flex flex-col p-0">
{/* Header - Fixed */}
<DialogHeader className="px-6 pt-6 pb-4 border-b">
<DialogTitle>
{__('Edit Template')}: {eventLabel} - {channelLabel}
</DialogTitle>
<DialogDescription>
{__('Customize the notification template. Use variables like {customer_name} to personalize messages.')}
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{/* Subject */}
<div className="space-y-2">
<Label htmlFor="subject">{__('Subject / Title')}</Label>
<div className="space-y-2 pt-2">
<Label htmlFor="subject" className="text-sm">{__('Subject / Title')}</Label>
<Input
id="subject"
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder={__('Enter notification subject')}
/>
<p className="text-xs text-muted-foreground">
{channelId === 'email'
? __('Email subject line')
: __('Push notification title')}
</p>
</div>
</DialogHeader>
{/* Body */}
{/* Body - Scrollable */}
<div className="flex-1 overflow-y-auto px-6 py-4">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="editor" className="flex items-center gap-2">
<Edit className="h-4 w-4" />
{__('Editor')}
</TabsTrigger>
<TabsTrigger value="preview" className="flex items-center gap-2">
<Eye className="h-4 w-4" />
{__('Preview')}
</TabsTrigger>
</TabsList>
{/* Editor Tab */}
<TabsContent value="editor" className="space-y-4 mt-4">
<div className="space-y-2">
<Label htmlFor="body">{__('Message Body')}</Label>
<RichTextEditor
@@ -131,42 +189,46 @@ export default function TemplateEditor({
placeholder={__('Enter notification message')}
variables={variableKeys}
/>
<p className="text-xs text-muted-foreground">
{__('Click variables below to insert them into your message')}
<div className="mt-2">
<p className="text-xs text-muted-foreground mb-2">
{__('Available Variables:')}
</p>
</div>
{/* Variable Reference */}
<div className="space-y-3">
<Label>{__('Variable Reference')}</Label>
<div className="flex flex-wrap gap-2">
{Object.entries(variables).map(([key, label]) => (
{Object.keys(variables).map((key) => (
<Badge
key={key}
variant="secondary"
className="cursor-default"
variant="outline"
className="text-xs font-mono"
>
<Plus className="h-3 w-3 mr-1" />
{`{${key}}`}
</Badge>
))}
</div>
</div>
</div>
</TabsContent>
{/* Preview Tab */}
<TabsContent value="preview" className="mt-4">
<div className="space-y-2">
<Label>{__('Email Preview')}</Label>
<div className="rounded-lg border bg-white overflow-hidden">
<iframe
srcDoc={generatePreviewHTML()}
className="w-full h-[500px] border-0"
title="Email Preview"
/>
</div>
<p className="text-xs text-muted-foreground">
{__('Click a variable to insert it at cursor position')}
{__('This is how your email will look. Variables are highlighted in yellow.')}
</p>
</div>
{/* Preview */}
<div className="space-y-2">
<Label>{__('Preview')}</Label>
<div className="rounded-lg border bg-muted/50 p-4 space-y-2">
<div className="font-medium text-sm">{subject || __('(No subject)')}</div>
<div className="text-sm whitespace-pre-wrap">{body || __('(No message)')}</div>
</div>
</div>
</TabsContent>
</Tabs>
</div>
<DialogFooter className="gap-2">
{/* Footer - Fixed */}
<DialogFooter className="px-6 py-4 border-t gap-2">
<Button variant="outline" onClick={() => resetMutation.mutate()} disabled={saveMutation.isPending || resetMutation.isPending}>
{__('Reset to Default')}
</Button>