import React, { useState, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { SettingsLayout } from '../components/SettingsLayout'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { EmailBuilder, EmailBlock, blocksToMarkdown, markdownToBlocks } from '@/components/EmailBuilder'; import { CodeEditor } from '@/components/ui/code-editor'; import { Label } from '@/components/ui/label'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { ArrowLeft, Eye, Edit, RotateCcw, FileText, Send } from 'lucide-react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { toast } from 'sonner'; import { __ } from '@/lib/i18n'; import { markdownToHtml } from '@/lib/markdown-utils'; export default function EditTemplate() { // Mobile responsive check const [isMobile, setIsMobile] = useState(false); useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth < 768); checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const queryClient = useQueryClient(); const eventId = searchParams.get('event'); const channelId = searchParams.get('channel'); const recipientType = searchParams.get('recipient') || 'customer'; // Default to customer const [subject, setSubject] = useState(''); const [markdownContent, setMarkdownContent] = useState(''); // Source of truth: Markdown const [blocks, setBlocks] = useState([]); // Visual mode view (derived from markdown) const [activeTab, setActiveTab] = useState('preview'); // Send Test Email state const [testEmailDialogOpen, setTestEmailDialogOpen] = useState(false); const [testEmail, setTestEmail] = useState(''); // Fetch email customization settings (for non-color settings like logo, footer, social links) const { data: emailSettings } = useQuery({ queryKey: ['email-settings'], queryFn: () => api.get('/notifications/email-settings'), }); // Fetch appearance settings for unified colors const { data: appearanceSettings } = useQuery({ queryKey: ['appearance-settings'], queryFn: () => api.get('/appearance/settings'), }); // Fetch template const { data: template, isLoading, error } = useQuery({ queryKey: ['notification-template', eventId, channelId, recipientType], queryFn: async () => { console.log('Fetching template for:', eventId, channelId, recipientType); const response = await api.get(`/notifications/templates/${eventId}/${channelId}?recipient=${recipientType}`); console.log('API Response:', response); console.log('API Response.data:', response.data); console.log('API Response type:', typeof response); // The api.get might already unwrap response.data // Return the response directly if it has the template fields if (response && (response.subject !== undefined || response.body !== undefined)) { console.log('Returning response directly:', response); return response; } // Otherwise return response.data if (response && response.data) { console.log('Returning response.data:', response.data); return response.data; } return null; }, enabled: !!eventId && !!channelId, }); useEffect(() => { if (template) { setSubject(template.subject || ''); // Always treat body as markdown (source of truth) const markdown = template.body || ''; setMarkdownContent(markdown); // Convert to blocks for visual mode const initialBlocks = markdownToBlocks(markdown); setBlocks(initialBlocks); } }, [template]); const handleSave = async () => { try { await api.post(`/notifications/templates/${eventId}/${channelId}`, { subject, body: markdownContent, // Save markdown (source of truth) recipient: recipientType, }); queryClient.invalidateQueries({ queryKey: ['notification-templates'] }); queryClient.invalidateQueries({ queryKey: ['notification-template', eventId, channelId, recipientType] }); toast.success(__('Template saved successfully')); } catch (error: any) { toast.error(error?.message || __('Failed to save template')); } }; const handleReset = async () => { if (!confirm(__('Are you sure you want to reset this template to default?'))) return; try { await api.del(`/notifications/templates/${eventId}/${channelId}?recipient=${recipientType}`); queryClient.invalidateQueries({ queryKey: ['notification-templates'] }); queryClient.invalidateQueries({ queryKey: ['notification-template', eventId, channelId, recipientType] }); toast.success(__('Template reset to default')); } catch (error: any) { toast.error(error?.message || __('Failed to reset template')); } }; // Send test email mutation const sendTestMutation = useMutation({ mutationFn: async (email: string) => { return api.post(`/notifications/templates/${eventId}/${channelId}/send-test`, { email, recipient: recipientType, }); }, onSuccess: (data: any) => { toast.success(data.message || __('Test email sent successfully')); setTestEmailDialogOpen(false); setTestEmail(''); }, onError: (error: any) => { toast.error(error?.message || __('Failed to send test email')); }, }); const handleSendTest = () => { if (!testEmail || !testEmail.includes('@')) { toast.error(__('Please enter a valid email address')); return; } sendTestMutation.mutate(testEmail); }; // Visual mode: Update blocks → Markdown (source of truth) const handleBlocksChange = (newBlocks: EmailBlock[]) => { setBlocks(newBlocks); const markdown = blocksToMarkdown(newBlocks); setMarkdownContent(markdown); // Update markdown (source of truth) }; // Markdown mode: Update markdown → Blocks (for visual sync) const handleMarkdownChange = (newMarkdown: string) => { setMarkdownContent(newMarkdown); // Update source of truth const newBlocks = markdownToBlocks(newMarkdown); setBlocks(newBlocks); // Keep blocks in sync }; // Variable keys for the rich text editor dropdown - from API (contextual per event) const variableKeys = template?.available_variables ? Object.keys(template.available_variables).map(k => k.replace(/^\{|}$/g, '')) : []; // Parse [card] tags and [button] shortcodes for preview const parseCardsForPreview = (content: string) => { // Parse card blocks - new [card:type] syntax let parsed = content.replace(/\[card:(\w+)\](.*?)\[\/card\]/gs, (match, type, cardContent) => { const cardClass = `card card-${type}`; const htmlContent = markdownToHtml(cardContent.trim()); return `
${htmlContent}
`; }); // Parse card blocks - old [card type="..."] syntax (backward compatibility) parsed = parsed.replace(/\[card([^\]]*)\](.*?)\[\/card\]/gs, (match, attributes, cardContent) => { let cardClass = 'card'; const typeMatch = attributes.match(/type=["']([^"']+)["']/); if (typeMatch) { cardClass += ` card-${typeMatch[1]}`; } const bgMatch = attributes.match(/bg=["']([^"']+)["']/); const bgStyle = bgMatch ? `background-image: url(${bgMatch[1]}); background-size: cover; background-position: center;` : ''; // Convert markdown inside card to HTML const htmlContent = markdownToHtml(cardContent.trim()); return `
${htmlContent}
`; }); // Parse button shortcodes - new [button:style](url)Text[/button] syntax parsed = parsed.replace(/\[button:(\w+)\]\(([^)]+)\)([^\[]+)\[\/button\]/g, (match, style, url, text) => { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; return `

${text.trim()}

`; }); // Parse button shortcodes - old [button url="..."]Text[/button] syntax (backward compatibility) parsed = parsed.replace(/\[button\s+url=["']([^"']+)["'](?:\s+style=["'](solid|outline)["'])?\]([^\[]+)\[\/button\]/g, (match, url, style, text) => { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; return `

${text.trim()}

`; }); return parsed; }; // Generate preview HTML const generatePreviewHTML = () => { // Convert markdown to HTML for preview let previewBody = parseCardsForPreview(markdownContent); // Replace store-identity variables with actual data const storeVariables: { [key: string]: string } = { store_name: 'My WordPress Store', site_url: window.location.origin, store_email: 'store@example.com', }; Object.entries(storeVariables).forEach(([key, value]) => { const regex = new RegExp(`\\{${key}\\}`, 'g'); previewBody = previewBody.replace(regex, value); }); // Replace dynamic variables with sample data (not just highlighting) const sampleData: { [key: string]: string } = { order_number: '12345', order_total: '$99.99', order_status: 'Processing', order_date: new Date().toLocaleDateString(), order_url: '#', completion_date: new Date().toLocaleDateString(), order_items_list: ``, order_items_table: `
Product Qty Price
Premium T-Shirt
Size: L, Color: Blue
2 $49.98
Classic Jeans
Size: 32, Color: Dark Blue
1 $79.99
`, customer_name: 'John Doe', customer_email: 'john@example.com', customer_phone: '+1 234 567 8900', payment_method: 'Credit Card', payment_url: '#', shipping_method: 'Standard Shipping', tracking_number: 'TRACK123456', tracking_url: '#', shipping_carrier: 'Standard Shipping', refund_amount: '$50.00', billing_address: '123 Main St, City, State 12345', shipping_address: '123 Main St, City, State 12345', transaction_id: 'TXN123456789', payment_date: new Date().toLocaleDateString(), payment_status: 'Completed', review_url: '#', shop_url: '#', my_account_url: '#', payment_retry_url: '#', vip_dashboard_url: '#', vip_free_shipping_threshold: '$50', current_year: new Date().getFullYear().toString(), site_name: 'My WordPress Store', store_name: 'My WordPress Store', site_url: '#', store_email: 'store@example.com', support_email: 'support@example.com', // Account-related URLs and variables login_url: '#', reset_link: '#', reset_key: 'abc123xyz', user_login: 'johndoe', user_email: 'john@example.com', user_temp_password: '••••••••', customer_first_name: 'John', customer_last_name: 'Doe', // Campaign/Newsletter variables content: '

This is sample content that would be replaced with your actual campaign content.

', campaign_title: 'Newsletter Campaign', }; Object.keys(sampleData).forEach((key) => { const regex = new RegExp(`\\{${key}\\}`, 'g'); previewBody = previewBody.replace(regex, sampleData[key]); }); // Highlight variables that don't have sample data // Use plain text [variable] instead of HTML spans to avoid breaking href attributes variableKeys.forEach((key: string) => { if (!storeVariables[key] && !sampleData[key]) { previewBody = previewBody.replace(new RegExp(`\\{${key}\\}`, 'g'), `[${key}]`); } }); // Get email settings for preview - use UNIFIED appearance settings for colors const settings = emailSettings || {}; const appearColors = appearanceSettings?.data?.general?.colors || appearanceSettings?.general?.colors || {}; const primaryColor = appearColors.primary || '#7f54b3'; const secondaryColor = appearColors.secondary || '#7f54b3'; const heroGradientStart = appearColors.gradientStart || '#667eea'; const heroGradientEnd = appearColors.gradientEnd || '#764ba2'; const heroTextColor = '#ffffff'; // Always white on gradient const buttonTextColor = '#ffffff'; // Always white on primary const bodyBgColor = settings.body_bg_color || '#f8f8f8'; const socialIconColor = settings.social_icon_color || 'white'; const logoUrl = settings.logo_url || ''; const headerText = settings.header_text || 'My WordPress Store'; const footerText = settings.footer_text || `© ${new Date().getFullYear()} My WordPress Store. All rights reserved.`; const socialLinks = settings.social_links || []; // Replace {current_year} in footer const processedFooter = footerText.replace('{current_year}', new Date().getFullYear().toString()); // Generate social icons HTML with PNG images // Get plugin URL from config, with fallback const pluginUrl = (window as any).woonoowData?.pluginUrl || (window as any).WNW_CONFIG?.pluginUrl || '/wp-content/plugins/woonoow/'; const socialIconsHtml = socialLinks.length > 0 ? `
${socialLinks.map((link: any) => ` ${link.platform} `).join('')}
` : ''; return `
${logoUrl ? `${headerText}` : `${headerText}`}
${previewBody}
`; }; // Helper function to get social icon emoji const getSocialIcon = (platform: string) => { const icons: Record = { facebook: '📘', twitter: '🐦', instagram: '📷', linkedin: '💼', youtube: '📺', website: '🌐', }; return icons[platform] || '🔗'; }; if (!eventId || !channelId) { return (
{__('Invalid template parameters')}
); } // Don't render form until template data is loaded if (isLoading || !template) { return (
{__('Loading template data...')}
); } return ( <> } > {/* Subject */}
setSubject(e.target.value)} placeholder={__('Enter notification subject')} />

{channelId === 'email' ? __('Email subject line') : __('Push notification title')}

{/* Body */}
{/* Three-tab system: Preview | Visual | Markdown */}
{__('Preview')} {__('Visual')} {__('Markdown')}
{/* Preview Tab */} {activeTab === 'preview' && (