From 704e9942e16cee19ac81d042397dd45f1d250fb6 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 13 Nov 2025 13:15:30 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20Email=20Global=20Customization=20Page!?= =?UTF-8?q?=20=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 3. Email Global Customization **Features:** - Brand Colors (Primary & Secondary) - Hero Card Gradient (Start & End colors) - Button Styling (Text color) - Logo & Branding (Logo URL, Header/Footer text) - Live color previews - Reset to defaults **Settings:** - `primary_color` - Primary buttons (#7f54b3) - `secondary_color` - Outline buttons (#7f54b3) - `hero_gradient_start` - Hero card gradient start (#667eea) - `hero_gradient_end` - Hero card gradient end (#764ba2) - `button_text_color` - Button text (#ffffff) - `logo_url` - Store logo URL - `header_text` - Email header text - `footer_text` - Email footer text **UI Features:** - Color pickers with hex input - Live gradient preview - Live button preview - Back navigation - Reset to defaults button - Save/loading states **Navigation:** - Added card to Notifications page - Route: `/settings/notifications/email-customization` - API: `/notifications/email-settings` **Files:** - `routes/Settings/Notifications.tsx` - Added card - `routes/Settings/Notifications/EmailCustomization.tsx` - NEW - `App.tsx` - Added route Ready to apply these settings to email templates! 🚀 --- admin-spa/src/App.tsx | 2 + .../src/routes/Settings/Notifications.tsx | 35 +- .../Notifications/EmailCustomization.tsx | 377 ++++++++++++++++++ 3 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx 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.')} +

+
+
+
+ ); +}