feat: Merge Templates tab into Events tab with toggle + gear icon pattern
✅ UI Restructuring: - Removed Templates tab from Staff and Customer pages - Merged template editing into Events tab - Changed from 3 tabs to 2 tabs (Channels | Events) ✅ Toggle + Gear Icon Pattern (like Payment Methods): - Toggle switch to enable/disable channel for each event - Gear icon (⚙️) appears when channel is enabled - Click gear to edit template for that event/channel combination ✅ Navigation Updates: - Back button from Edit Template now navigates to Events tab - Gear icon navigates with correct recipient type (staff/customer) ✅ Applied to Both: - Staff Notifications → Events tab - Customer Notifications → Events tab ✅ Benefits: - Cleaner UI with fewer tabs - More intuitive workflow (enable → configure) - Consistent pattern across the app - Less navigation depth 🎯 Next: Restructure Channel Configuration as separate section
This commit is contained in:
@@ -7,7 +7,6 @@ import { __ } from '@/lib/i18n';
|
|||||||
import { ChevronLeft } from 'lucide-react';
|
import { ChevronLeft } from 'lucide-react';
|
||||||
import CustomerChannels from './Customer/Channels';
|
import CustomerChannels from './Customer/Channels';
|
||||||
import CustomerEvents from './Customer/Events';
|
import CustomerEvents from './Customer/Events';
|
||||||
import NotificationTemplates from './Templates';
|
|
||||||
|
|
||||||
export default function CustomerNotifications() {
|
export default function CustomerNotifications() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -16,7 +15,7 @@ export default function CustomerNotifications() {
|
|||||||
// Check for tab query param
|
// Check for tab query param
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tabParam = searchParams.get('tab');
|
const tabParam = searchParams.get('tab');
|
||||||
if (tabParam && ['channels', 'events', 'templates'].includes(tabParam)) {
|
if (tabParam && ['channels', 'events'].includes(tabParam)) {
|
||||||
setActiveTab(tabParam);
|
setActiveTab(tabParam);
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
@@ -35,10 +34,9 @@ export default function CustomerNotifications() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="channels">{__('Channels')}</TabsTrigger>
|
<TabsTrigger value="channels">{__('Channels')}</TabsTrigger>
|
||||||
<TabsTrigger value="events">{__('Events')}</TabsTrigger>
|
<TabsTrigger value="events">{__('Events')}</TabsTrigger>
|
||||||
<TabsTrigger value="templates">{__('Templates')}</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="channels" className="space-y-4">
|
<TabsContent value="channels" className="space-y-4">
|
||||||
@@ -48,10 +46,6 @@ export default function CustomerNotifications() {
|
|||||||
<TabsContent value="events" className="space-y-4">
|
<TabsContent value="events" className="space-y-4">
|
||||||
<CustomerEvents />
|
<CustomerEvents />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="templates" className="space-y-4">
|
|
||||||
<NotificationTemplates />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import { SettingsCard } from '../../components/SettingsCard';
|
import { SettingsCard } from '../../components/SettingsCard';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react';
|
import { RefreshCw, Mail, MessageCircle, Send, Bell, Settings } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { __ } from '@/lib/i18n';
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ interface NotificationChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomerEvents() {
|
export default function CustomerEvents() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// Fetch customer events
|
// Fetch customer events
|
||||||
@@ -90,6 +92,10 @@ export default function CustomerEvents() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openTemplateEditor = (eventId: string, channelId: string) => {
|
||||||
|
navigate(`/settings/notifications/edit-template?event=${eventId}&channel=${channelId}&recipient=customer`);
|
||||||
|
};
|
||||||
|
|
||||||
if (eventsLoading || channelsLoading) {
|
if (eventsLoading || channelsLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
@@ -147,11 +153,23 @@ export default function CustomerEvents() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<div className="flex items-center gap-2">
|
||||||
checked={isEnabled}
|
{isEnabled && channel.enabled && (
|
||||||
onCheckedChange={() => handleToggle(event.id, channel.id, isEnabled, recipient)}
|
<Button
|
||||||
disabled={updateMutation.isPending}
|
variant="ghost"
|
||||||
/>
|
size="sm"
|
||||||
|
onClick={() => openTemplateEditor(event.id, channel.id)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Switch
|
||||||
|
checked={isEnabled}
|
||||||
|
onCheckedChange={() => handleToggle(event.id, channel.id, isEnabled, recipient)}
|
||||||
|
disabled={updateMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ export default function EditTemplate() {
|
|||||||
// Determine if staff or customer based on event category
|
// Determine if staff or customer based on event category
|
||||||
const isStaffEvent = template.event_category === 'staff' || eventId?.includes('admin') || eventId?.includes('staff');
|
const isStaffEvent = template.event_category === 'staff' || eventId?.includes('admin') || eventId?.includes('staff');
|
||||||
const page = isStaffEvent ? 'staff' : 'customer';
|
const page = isStaffEvent ? 'staff' : 'customer';
|
||||||
navigate(`/settings/notifications/${page}?tab=templates`);
|
navigate(`/settings/notifications/${page}?tab=events`);
|
||||||
}}
|
}}
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
title={__('Back')}
|
title={__('Back')}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { __ } from '@/lib/i18n';
|
|||||||
import { ChevronLeft } from 'lucide-react';
|
import { ChevronLeft } from 'lucide-react';
|
||||||
import StaffChannels from './Staff/Channels';
|
import StaffChannels from './Staff/Channels';
|
||||||
import StaffEvents from './Staff/Events';
|
import StaffEvents from './Staff/Events';
|
||||||
import NotificationTemplates from './Templates';
|
|
||||||
|
|
||||||
export default function StaffNotifications() {
|
export default function StaffNotifications() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -16,7 +15,7 @@ export default function StaffNotifications() {
|
|||||||
// Check for tab query param
|
// Check for tab query param
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tabParam = searchParams.get('tab');
|
const tabParam = searchParams.get('tab');
|
||||||
if (tabParam && ['channels', 'events', 'templates'].includes(tabParam)) {
|
if (tabParam && ['channels', 'events'].includes(tabParam)) {
|
||||||
setActiveTab(tabParam);
|
setActiveTab(tabParam);
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
@@ -35,10 +34,9 @@ export default function StaffNotifications() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="channels">{__('Channels')}</TabsTrigger>
|
<TabsTrigger value="channels">{__('Channels')}</TabsTrigger>
|
||||||
<TabsTrigger value="events">{__('Events')}</TabsTrigger>
|
<TabsTrigger value="events">{__('Events')}</TabsTrigger>
|
||||||
<TabsTrigger value="templates">{__('Templates')}</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="channels" className="space-y-4">
|
<TabsContent value="channels" className="space-y-4">
|
||||||
@@ -48,10 +46,6 @@ export default function StaffNotifications() {
|
|||||||
<TabsContent value="events" className="space-y-4">
|
<TabsContent value="events" className="space-y-4">
|
||||||
<StaffEvents />
|
<StaffEvents />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="templates" className="space-y-4">
|
|
||||||
<NotificationTemplates />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import { SettingsCard } from '../../components/SettingsCard';
|
import { SettingsCard } from '../../components/SettingsCard';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { RefreshCw, Mail, MessageCircle, Send, Bell } from 'lucide-react';
|
import { RefreshCw, Mail, MessageCircle, Send, Bell, Settings } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { __ } from '@/lib/i18n';
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ interface NotificationChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function NotificationEvents() {
|
export default function NotificationEvents() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// Fetch staff events
|
// Fetch staff events
|
||||||
@@ -88,6 +90,10 @@ export default function NotificationEvents() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openTemplateEditor = (eventId: string, channelId: string) => {
|
||||||
|
navigate(`/settings/notifications/edit-template?event=${eventId}&channel=${channelId}&recipient=staff`);
|
||||||
|
};
|
||||||
|
|
||||||
if (eventsLoading || channelsLoading) {
|
if (eventsLoading || channelsLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
@@ -166,11 +172,23 @@ export default function NotificationEvents() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<div className="flex items-center gap-2">
|
||||||
checked={channelEnabled}
|
{channelEnabled && channel.enabled && (
|
||||||
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
<Button
|
||||||
disabled={!channel.enabled || updateMutation.isPending}
|
variant="ghost"
|
||||||
/>
|
size="sm"
|
||||||
|
onClick={() => openTemplateEditor(event.id, channel.id)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Switch
|
||||||
|
checked={channelEnabled}
|
||||||
|
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -225,11 +243,23 @@ export default function NotificationEvents() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<div className="flex items-center gap-2">
|
||||||
checked={channelEnabled}
|
{channelEnabled && channel.enabled && (
|
||||||
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
<Button
|
||||||
disabled={!channel.enabled || updateMutation.isPending}
|
variant="ghost"
|
||||||
/>
|
size="sm"
|
||||||
|
onClick={() => openTemplateEditor(event.id, channel.id)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Switch
|
||||||
|
checked={channelEnabled}
|
||||||
|
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -284,11 +314,23 @@ export default function NotificationEvents() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<div className="flex items-center gap-2">
|
||||||
checked={channelEnabled}
|
{channelEnabled && channel.enabled && (
|
||||||
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
<Button
|
||||||
disabled={!channel.enabled || updateMutation.isPending}
|
variant="ghost"
|
||||||
/>
|
size="sm"
|
||||||
|
onClick={() => openTemplateEditor(event.id, channel.id)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Switch
|
||||||
|
checked={channelEnabled}
|
||||||
|
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
||||||
|
disabled={!channel.enabled || updateMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user