/** * Email Customizer - Main Component * Full-screen email customization interface with live preview */ import { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Loader2, Save, X, Undo2, Redo2 } from 'lucide-react'; import { toast } from 'sonner'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { CustomizerSidebar } from './customizer-sidebar'; import { CustomizerPreview } from './customizer-preview'; import { useEmailCustomizer } from './hooks/use-email-customizer'; import type { EmailContentSettings, PlaceholderConfig, PlaceholderCategoryLabel, ProductCardConfig, CouponDisplayConfig } from './sections'; // Hide body scrollbar when customizer is open function useBodyScrollLock() { useEffect(() => { const originalStyle = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = originalStyle; }; }, []); } interface EmailTypeInfo { id: string; label: string; defaultContent: EmailContentSettings; } interface EmailCustomizerProps { onClose: () => void; emailType: EmailTypeInfo; initialContent: EmailContentSettings; onSaveContent: (content: EmailContentSettings) => void; /** Feature-specific placeholders (defaults to subscription set). */ placeholders?: PlaceholderConfig[]; /** Feature-specific category labels. */ placeholderCategories?: Record; /** Hint shown under the subject field. */ subjectPlaceholderHint?: string; /** Optional product card config for product-oriented emails (e.g. Back In Stock). */ productCard?: ProductCardConfig; /** Callback to persist product card changes back to feature settings. */ onSaveProductCard?: (card: ProductCardConfig) => void; /** Optional coupon display config for emails with coupons. */ couponDisplay?: CouponDisplayConfig; /** Callback to persist coupon display changes back to feature settings. */ onSaveCouponDisplay?: (config: CouponDisplayConfig) => void; } export function EmailCustomizer({ onClose, emailType, initialContent, onSaveContent, placeholders, placeholderCategories, subjectPlaceholderHint, productCard: initialProductCard, onSaveProductCard, couponDisplay: initialCouponDisplay, onSaveCouponDisplay, }: EmailCustomizerProps) { // Lock body scroll when customizer is open useBodyScrollLock(); const { settings, updateNestedSettings, devicePreview, setDevicePreview, darkModePreview, setDarkModePreview, isLoading, isSaving, saveSettings, resetToDefaults, sendTestEmail, isSendingTest, hasChanges: hasDesignChanges, undo, redo, canUndo, canRedo, } = useEmailCustomizer(); // Local state for email content const [emailContent, setEmailContent] = useState(initialContent); const [hasContentChanges, setHasContentChanges] = useState(false); // Local state for product card (only used by product-oriented emails) const [productCard, setProductCard] = useState(initialProductCard); // Local state for coupon display (only used by emails with coupons) const [couponDisplay, setCouponDisplay] = useState(initialCouponDisplay); // Reset content state when email type changes useEffect(() => { setEmailContent(initialContent); setHasContentChanges(false); }, [emailType.id, initialContent]); useEffect(() => { setProductCard(initialProductCard); }, [emailType.id, initialProductCard]); useEffect(() => { setCouponDisplay(initialCouponDisplay); }, [emailType.id, initialCouponDisplay]); const updateEmailContent = useCallback((updates: Partial) => { setEmailContent((prev) => ({ ...prev, ...updates })); setHasContentChanges(true); }, []); const updateProductCard = useCallback((updates: Partial) => { setProductCard((prev) => prev ? { ...prev, ...updates } : prev); setHasContentChanges(true); }, []); const updateCouponDisplay = useCallback((updates: Partial) => { setCouponDisplay((prev) => prev ? { ...prev, ...updates } : prev); setHasContentChanges(true); }, []); const hasChanges = hasDesignChanges || hasContentChanges; const handleSave = async () => { try { // Save design settings await saveSettings(); // Save content via callback (parent handles persisting to subscription settings) onSaveContent(emailContent); // Save product card if present if (productCard && onSaveProductCard) { onSaveProductCard(productCard); } // Save coupon display if present if (couponDisplay && onSaveCouponDisplay) { onSaveCouponDisplay(couponDisplay); } setHasContentChanges(false); toast.success('Your email customization settings have been saved.'); } catch { toast.error('Failed to save settings. Please try again.'); } }; const handleSendTestEmail = async (email: string, emailTypeId: string) => { try { await sendTestEmail(email, emailTypeId); toast.success(`A test email has been sent to ${email}.`); } catch { toast.error('Failed to send test email. Please try again.'); throw new Error('Failed to send test email'); } }; const handleClose = () => { if (hasChanges) { if (window.confirm('You have unsaved changes. Are you sure you want to close?')) { onClose(); } } else { onClose(); } }; // Keyboard shortcuts for undo/redo useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'z') { if (e.shiftKey) { e.preventDefault(); redo(); } else { e.preventDefault(); undo(); } } if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault(); redo(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [undo, redo]); if (isLoading) { return (

Loading customizer...

); } return (
{/* Top Bar */}

Customize: {emailType.label}

Edit content and design for this email

{/* Undo/Redo Buttons */}

Undo (Ctrl+Z)

Redo (Ctrl+Y)

{hasChanges && ( Unsaved changes )}
{/* Main Content */}
{/* Sidebar - Customization Options */}
updateNestedSettings('logo', updates)} onUpdateColors={(updates) => updateNestedSettings('colors', updates)} onUpdateTypography={(updates) => updateNestedSettings('typography', updates) } onUpdateLayout={(updates) => updateNestedSettings('layout', updates)} onUpdateSender={(updates) => updateNestedSettings('sender', updates)} onUpdateTable={(updates) => updateNestedSettings('table', updates)} onUpdateFooter={(updates) => updateNestedSettings('footer', updates)} onResetToDefaults={resetToDefaults} subjectPlaceholderHint={subjectPlaceholderHint} placeholders={placeholders} placeholderCategories={placeholderCategories} productCard={productCard} onUpdateProductCard={updateProductCard} couponDisplay={couponDisplay} onUpdateCouponDisplay={updateCouponDisplay} />
{/* Preview Panel */}
); } export default EmailCustomizer;