Fix email template preview UX and debug template loading
- Convert EmailTemplatePreview from bottom card to modal Dialog component - Replace problematic bottom preview with clean modal popup - Add proper modal state management (open/close handlers) - Debug template loading with comprehensive error handling and logging - Add user feedback for template seeding and loading errors - Improve fetchData() and seedTemplates() with try-catch blocks - Add console logging for debugging template initialization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,16 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { Eye, Send, Mail } from 'lucide-react';
|
||||
import { Eye, Send, Mail, X } from 'lucide-react';
|
||||
import { EmailTemplateRenderer, ShortcodeProcessor } from '@/lib/email-templates/master-template';
|
||||
|
||||
interface NotificationTemplate {
|
||||
@@ -22,9 +30,17 @@ interface EmailTemplatePreviewProps {
|
||||
template: NotificationTemplate;
|
||||
onTest?: (template: NotificationTemplate) => void;
|
||||
isTestSending?: boolean;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function EmailTemplatePreview({ template, onTest, isTestSending = false }: EmailTemplatePreviewProps) {
|
||||
export function EmailTemplatePreview({
|
||||
template,
|
||||
onTest,
|
||||
isTestSending = false,
|
||||
open,
|
||||
onClose
|
||||
}: EmailTemplatePreviewProps) {
|
||||
const [previewMode, setPreviewMode] = useState<'master' | 'content'>('master');
|
||||
const [testEmail, setTestEmail] = useState('');
|
||||
const [showTestForm, setShowTestForm] = useState(false);
|
||||
@@ -60,6 +76,25 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
|
||||
const previewHtml = generatePreview();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Mail className="w-5 h-5" />
|
||||
Preview: {template.name}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Preview template email dengan master styling
|
||||
</DialogDescription>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onClose}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Preview Controls */}
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -134,7 +169,7 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
|
||||
{previewMode === 'master' ? 'Full Email Preview' : 'Content Preview'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-white" style={{ height: '600px', overflow: 'hidden' }}>
|
||||
<div className="bg-white" style={{ height: '500px', overflow: 'hidden' }}>
|
||||
<iframe
|
||||
srcDoc={previewHtml}
|
||||
className="w-full h-full border-0"
|
||||
@@ -163,6 +198,32 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Template Actions */}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
{!showTestForm && (
|
||||
<Button
|
||||
onClick={() => setShowTestForm(true)}
|
||||
className="flex-1"
|
||||
>
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
Test Email
|
||||
</Button>
|
||||
)}
|
||||
{showTestForm && (
|
||||
<Button
|
||||
onClick={() => setShowTestForm(false)}
|
||||
variant="outline"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -346,26 +346,55 @@ export function NotifikasiTab() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set());
|
||||
const [testingTemplate, setTestingTemplate] = useState<string | null>(null);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<NotificationTemplate | null>(null);
|
||||
const [previewMode, setPreviewMode] = useState<'master' | 'content'>('master');
|
||||
const [previewTemplate, setPreviewTemplate] = useState<NotificationTemplate | null>(null);
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
console.log('Fetching templates...');
|
||||
// Fetch templates
|
||||
const { data: templatesData } = await supabase.from('notification_templates').select('*').order('key');
|
||||
const { data: templatesData, error: fetchError } = await supabase.from('notification_templates').select('*').order('key');
|
||||
|
||||
if (fetchError) {
|
||||
console.error('Error fetching templates:', fetchError);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Gagal mengambil template: ' + fetchError.message,
|
||||
variant: 'destructive'
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Templates data:', templatesData);
|
||||
|
||||
if (templatesData && templatesData.length > 0) {
|
||||
console.log('Setting templates from database:', templatesData.length);
|
||||
setTemplates(templatesData);
|
||||
} else {
|
||||
console.log('No templates found, seeding default templates...');
|
||||
// Seed default templates if none exist
|
||||
await seedTemplates();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unexpected error in fetchData:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Terjadi kesalahan tak terduga saat mengambil data',
|
||||
variant: 'destructive'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const seedTemplates = async () => {
|
||||
try {
|
||||
console.log('Seeding default templates...');
|
||||
const toInsert = DEFAULT_TEMPLATES.map(t => ({
|
||||
key: t.key,
|
||||
name: t.name,
|
||||
@@ -374,8 +403,36 @@ export function NotifikasiTab() {
|
||||
email_body_html: t.defaultBody,
|
||||
webhook_url: '',
|
||||
}));
|
||||
|
||||
console.log('Inserting templates:', toInsert.length);
|
||||
const { data, error } = await supabase.from('notification_templates').insert(toInsert).select();
|
||||
if (!error && data) setTemplates(data);
|
||||
|
||||
if (error) {
|
||||
console.error('Error seeding templates:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Gagal membuat template default: ' + error.message,
|
||||
variant: 'destructive'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Templates seeded successfully:', data);
|
||||
if (data) {
|
||||
setTemplates(data);
|
||||
toast({
|
||||
title: 'Berhasil',
|
||||
description: `Berhasil membuat ${data.length} template default`
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unexpected error in seedTemplates:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Terjadi kesalahan saat membuat template default',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -587,7 +644,8 @@ export function NotifikasiTab() {
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateTemplate(template);
|
||||
setSelectedTemplate(template);
|
||||
setPreviewTemplate(template);
|
||||
setIsPreviewOpen(true);
|
||||
}}
|
||||
className="shadow-sm flex-1"
|
||||
>
|
||||
@@ -620,33 +678,14 @@ export function NotifikasiTab() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Consolidated Email Preview */}
|
||||
{selectedTemplate && (
|
||||
<Card className="border-2 border-border">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>Preview: {selectedTemplate.name}</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSelectedTemplate(null)}
|
||||
>
|
||||
Tutup Preview
|
||||
</Button>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Preview template email dengan master styling
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Modal Email Preview */}
|
||||
<EmailTemplatePreview
|
||||
template={selectedTemplate}
|
||||
template={previewTemplate!}
|
||||
open={isPreviewOpen}
|
||||
onClose={() => setIsPreviewOpen(false)}
|
||||
onTest={sendTestEmail}
|
||||
isTestSending={testingTemplate === selectedTemplate.id}
|
||||
isTestSending={testingTemplate === previewTemplate?.id}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user