diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index 7b8e5ac..da21c81 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -203,6 +203,7 @@ import SettingsLocalPickup from '@/routes/Settings/LocalPickup'; import SettingsNotifications from '@/routes/Settings/Notifications'; import StaffNotifications from '@/routes/Settings/Notifications/Staff'; import CustomerNotifications from '@/routes/Settings/Notifications/Customer'; +import EmailCustomization from '@/routes/Settings/Notifications/EmailCustomization'; import EditTemplate from '@/routes/Settings/Notifications/EditTemplate'; import SettingsDeveloper from '@/routes/Settings/Developer'; import MorePage from '@/routes/More'; @@ -493,6 +494,7 @@ function AppRoutes() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/admin-spa/src/routes/Settings/Notifications.tsx b/admin-spa/src/routes/Settings/Notifications.tsx index 0189301..6fe8ecd 100644 --- a/admin-spa/src/routes/Settings/Notifications.tsx +++ b/admin-spa/src/routes/Settings/Notifications.tsx @@ -4,7 +4,7 @@ import { SettingsLayout } from './components/SettingsLayout'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { __ } from '@/lib/i18n'; -import { Bell, Users, ChevronRight, Activity } from 'lucide-react'; +import { Bell, Users, ChevronRight, Activity, Palette } from 'lucide-react'; export default function NotificationsSettings() { return ( @@ -80,6 +80,39 @@ export default function NotificationsSettings() { + {/* Email Customization */} + + +
+
+ +
+
+ {__('Email Customization')} + + {__('Customize email appearance and branding')} + +
+
+
+ +

+ {__('Set your brand colors, logo, and email styling. Customize header, footer, and button colors for all email templates.')} +

+
+
+ {__('Colors, Logo, Styling')} +
+ + + +
+
+
+ {/* Activity Log */} diff --git a/admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx b/admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx new file mode 100644 index 0000000..ced0e09 --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx @@ -0,0 +1,377 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsLayout } from '../components/SettingsLayout'; +import { SettingsCard } from '../components/SettingsCard'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { __ } from '@/lib/i18n'; +import { ArrowLeft, RefreshCw } from 'lucide-react'; +import { toast } from 'sonner'; + +interface EmailSettings { + primary_color: string; + secondary_color: string; + hero_gradient_start: string; + hero_gradient_end: string; + button_text_color: string; + logo_url: string; + header_text: string; + footer_text: string; +} + +export default function EmailCustomization() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + // Fetch email settings + const { data: settings, isLoading } = useQuery({ + queryKey: ['email-settings'], + queryFn: () => api.get('/notifications/email-settings'), + placeholderData: { + primary_color: '#7f54b3', + secondary_color: '#7f54b3', + hero_gradient_start: '#667eea', + hero_gradient_end: '#764ba2', + button_text_color: '#ffffff', + logo_url: '', + header_text: '', + footer_text: '', + }, + }); + + const [formData, setFormData] = useState(settings || { + primary_color: '#7f54b3', + secondary_color: '#7f54b3', + hero_gradient_start: '#667eea', + hero_gradient_end: '#764ba2', + button_text_color: '#ffffff', + logo_url: '', + header_text: '', + footer_text: '', + }); + + // Update form when settings load + React.useEffect(() => { + if (settings) { + setFormData(settings); + } + }, [settings]); + + const saveMutation = useMutation({ + mutationFn: async () => { + return api.post('/notifications/email-settings', formData); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['email-settings'] }); + toast.success(__('Email settings saved successfully')); + }, + onError: (error: any) => { + toast.error(error.message || __('Failed to save email settings')); + }, + }); + + const resetMutation = useMutation({ + mutationFn: async () => { + return api.del('/notifications/email-settings'); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['email-settings'] }); + toast.success(__('Email settings reset to defaults')); + }, + onError: (error: any) => { + toast.error(error.message || __('Failed to reset email settings')); + }, + }); + + const handleSave = async () => { + return new Promise((resolve, reject) => { + saveMutation.mutate(undefined, { + onSuccess: () => resolve(), + onError: () => reject(), + }); + }); + }; + + const handleReset = () => { + if (!confirm(__('Are you sure you want to reset all email customization to defaults?'))) return; + resetMutation.mutate(); + }; + + const handleChange = (field: keyof EmailSettings, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + if (isLoading) { + return ( + +
+ +
+
+ ); + } + + return ( + + + + + } + > +
+ {/* Brand Colors */} + +
+
+ +
+ handleChange('primary_color', e.target.value)} + className="w-20 h-10 p-1 cursor-pointer" + /> + handleChange('primary_color', e.target.value)} + placeholder="#7f54b3" + className="flex-1" + /> +
+

+ {__('Used for primary buttons and main accents')} +

+
+ +
+ +
+ handleChange('secondary_color', e.target.value)} + className="w-20 h-10 p-1 cursor-pointer" + /> + handleChange('secondary_color', e.target.value)} + placeholder="#7f54b3" + className="flex-1" + /> +
+

+ {__('Used for outline buttons and borders')} +

+
+
+
+ + {/* Hero Card Gradient */} + +
+
+ +
+ handleChange('hero_gradient_start', e.target.value)} + className="w-20 h-10 p-1 cursor-pointer" + /> + handleChange('hero_gradient_start', e.target.value)} + placeholder="#667eea" + className="flex-1" + /> +
+
+ +
+ +
+ handleChange('hero_gradient_end', e.target.value)} + className="w-20 h-10 p-1 cursor-pointer" + /> + handleChange('hero_gradient_end', e.target.value)} + placeholder="#764ba2" + className="flex-1" + /> +
+
+
+ + {/* Preview */} +
+

{__('Preview')}

+

{__('This is how your hero cards will look')}

+
+
+ + {/* Button Styling */} + +
+
+ +
+ handleChange('button_text_color', e.target.value)} + className="w-20 h-10 p-1 cursor-pointer" + /> + handleChange('button_text_color', e.target.value)} + placeholder="#ffffff" + className="flex-1" + /> +
+

+ {__('Text color for buttons (usually white for dark buttons)')} +

+
+ + {/* Button Preview */} +
+ + +
+
+
+ + {/* Logo & Branding */} + +
+
+ + handleChange('logo_url', e.target.value)} + placeholder="https://example.com/logo.png" + /> +

+ {__('Full URL to your logo image (recommended: 200x60px)')} +

+
+ +
+ + handleChange('header_text', e.target.value)} + placeholder={__('Your Store Name')} + /> +

+ {__('Text shown in email header (leave empty to use store name)')} +

+
+ +
+ + handleChange('footer_text', e.target.value)} + placeholder={__('© 2024 Your Store. All rights reserved.')} + /> +

+ {__('Text shown in email footer (copyright, address, etc.)')} +

+
+
+
+ + {/* Info Box */} +
+

+ {__('Note:')} {__('These settings will apply to all email templates. Individual templates can still override specific content, but colors and branding will be consistent across all emails.')} +

+
+
+
+ ); +}