diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index 3450949..2676a5f 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -202,6 +202,7 @@ import SettingsCustomers from '@/routes/Settings/Customers'; 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 SettingsDeveloper from '@/routes/Settings/Developer'; import MorePage from '@/routes/More'; @@ -490,6 +491,7 @@ function AppRoutes() { } /> } /> } /> + } /> } /> } /> diff --git a/admin-spa/src/routes/Settings/Notifications/Customer.tsx b/admin-spa/src/routes/Settings/Notifications/Customer.tsx new file mode 100644 index 0000000..388efab --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Customer.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { SettingsLayout } from '../components/SettingsLayout'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Button } from '@/components/ui/button'; +import { __ } from '@/lib/i18n'; +import { ChevronLeft } from 'lucide-react'; +import CustomerChannels from './Customer/Channels'; +import CustomerEvents from './Customer/Events'; +import NotificationTemplates from './Templates'; + +export default function CustomerNotifications() { + const [activeTab, setActiveTab] = useState('channels'); + + return ( + + + + } + > + + + {__('Channels')} + {__('Events')} + {__('Templates')} + + + + + + + + + + + + + + + + ); +} diff --git a/admin-spa/src/routes/Settings/Notifications/Customer/Channels.tsx b/admin-spa/src/routes/Settings/Notifications/Customer/Channels.tsx new file mode 100644 index 0000000..d7bcba2 --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Customer/Channels.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsCard } from '../../components/SettingsCard'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { RefreshCw, Mail, Bell, MessageSquare, Info } from 'lucide-react'; +import { __ } from '@/lib/i18n'; + +interface NotificationChannel { + id: string; + label: string; + icon: string; + enabled: boolean; + builtin?: boolean; + addon?: string; +} + +export default function CustomerChannels() { + // Fetch channels + const { data: channels, isLoading } = useQuery({ + queryKey: ['notification-channels'], + queryFn: () => api.get('/notifications/channels'), + }); + + const getChannelIcon = (channelId: string) => { + switch (channelId) { + case 'email': + return ; + case 'push': + return ; + case 'sms': + return ; + default: + return ; + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + const builtinChannels = (channels || []).filter((c: NotificationChannel) => c.builtin); + + return ( +
+ + + + {__('Customer notifications are sent based on order status, account activities, and customer preferences. Customers can manage their notification preferences from their account page.')} + + + + +
+ {/* Email Channel */} +
+
+ +
+
+
+

{__('Email')}

+ + {__('Built-in')} + + + {__('Active')} + +
+

+ {__('Transactional emails sent to customers for order updates, account activities, and more.')} +

+
+ {__('Powered by WordPress email system')} +
+
+
+ + {/* Push Notifications */} +
+
+ +
+
+
+

{__('Push Notifications')}

+ + {__('Built-in')} + + + {__('Requires opt-in')} + +
+

+ {__('Browser push notifications for real-time order updates. Customers must enable push notifications in their account.')} +

+
+ {__('Customer-controlled from their account preferences')} +
+
+
+ + {/* SMS Channel (Coming Soon) */} +
+
+ +
+
+
+

{__('SMS Notifications')}

+ + {__('Addon')} + + + {__('Coming Soon')} + +
+

+ {__('Send SMS notifications for critical order updates and delivery notifications.')} +

+ +
+
+
+
+ + +
+

+ {__('Customers can manage their notification preferences from:')} +

+
    +
  • {__('My Account → Notification Preferences')}
  • +
  • {__('Unsubscribe links in emails')}
  • +
  • {__('Browser push notification settings')}
  • +
+
+

+ {__('Note: Transactional emails (order confirmations, shipping updates) cannot be disabled by customers as they are required for order fulfillment.')} +

+
+
+
+
+ ); +} diff --git a/admin-spa/src/routes/Settings/Notifications/Customer/Events.tsx b/admin-spa/src/routes/Settings/Notifications/Customer/Events.tsx new file mode 100644 index 0000000..1130bcd --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/Customer/Events.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { SettingsCard } from '../../components/SettingsCard'; +import { Switch } from '@/components/ui/switch'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react'; +import { toast } from 'sonner'; +import { __ } from '@/lib/i18n'; + +interface NotificationEvent { + id: string; + label: string; + description: string; + category: string; + enabled: boolean; + channels: { + [channelId: string]: { + enabled: boolean; + recipient: 'admin' | 'customer' | 'both'; + }; + }; +} + +interface NotificationChannel { + id: string; + label: string; + icon: string; + enabled: boolean; + builtin?: boolean; + addon?: string; +} + +export default function CustomerEvents() { + const queryClient = useQueryClient(); + + // Fetch customer events + const { data: eventsData, isLoading: eventsLoading } = useQuery({ + queryKey: ['notification-customer-events'], + queryFn: () => api.get('/notifications/customer/events'), + }); + + // Fetch channels + const { data: channels, isLoading: channelsLoading } = useQuery({ + queryKey: ['notification-channels'], + queryFn: () => api.get('/notifications/channels'), + }); + + // Update event mutation + const updateMutation = useMutation({ + mutationFn: async ({ eventId, channelId, enabled, recipient }: any) => { + return api.post('/notifications/events/update', { + eventId, + channelId, + enabled, + recipient, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['notification-customer-events'] }); + toast.success(__('Event settings updated')); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to update event')); + }, + }); + + const getChannelIcon = (channelId: string) => { + switch (channelId) { + case 'email': + return ; + case 'push': + return ; + case 'whatsapp': + return ; + case 'telegram': + return ; + default: + return ; + } + }; + + const handleToggle = (eventId: string, channelId: string, currentEnabled: boolean, recipient: string) => { + updateMutation.mutate({ + eventId, + channelId, + enabled: !currentEnabled, + recipient, + }); + }; + + if (eventsLoading || channelsLoading) { + return ( +
+ +
+ ); + } + + const events = eventsData || {}; + const enabledChannels = (channels || []).filter((c: NotificationChannel) => c.enabled); + + return ( +
+ {Object.entries(events).map(([category, categoryEvents]: [string, any]) => ( + +
+ {(categoryEvents as NotificationEvent[]).map((event) => ( +
+
+
+

{event.label}

+

{event.description}

+
+
+ +
+ {enabledChannels.map((channel: NotificationChannel) => { + const channelSettings = event.channels[channel.id]; + const isEnabled = channelSettings?.enabled || false; + const recipient = channelSettings?.recipient || 'customer'; + + return ( +
+
+
+ {getChannelIcon(channel.id)} +
+
+
+ {channel.label} + {channel.builtin && ( + + {__('Built-in')} + + )} +
+
+ {__('Recipient')}: {recipient} +
+
+
+ handleToggle(event.id, channel.id, isEnabled, recipient)} + disabled={updateMutation.isPending} + /> +
+ ); + })} +
+
+ ))} +
+
+ ))} + + {Object.keys(events).length === 0 && ( + +

+ {__('Customer notification events will appear here once configured.')} +

+
+ )} +
+ ); +}