fix: Polish notification UI - mobile, colors, and layout
## ✅ All UI Improvements ### 1. Contextual Header - Added contextual header to Notifications page - Consistent with Payments and Shipping pages - Saves vertical space ### 2. Mobile View Improvements **Channels Page:** - Responsive flex-col on mobile, flex-row on desktop - Full-width buttons on mobile - Better spacing and alignment - Push subscription toggle in bordered container on mobile **Templates Accordion:** - Better mobile layout - Badges wrap properly - Icon and title alignment improved - Responsive padding ### 3. Active State Colors - **Green color for active channels** (consistent with Payments) - `bg-green-500/20 text-green-600` for active - `bg-muted text-muted-foreground` for inactive - Applied to: - Events page channel icons - Channels page channel icons - Active badges ### 4. Badge Layout - Badges moved under title on mobile - Better visual hierarchy - Title → Badges → Description flow - Proper spacing between elements ### 5. Template Variables Card Removed - Variables already in template editor modal - Click-to-insert functionality - No need for separate reference card - Cleaner page layout ### 6. Accordion Polish - Better padding and spacing - Responsive layout - Icon stays visible - Badges wrap on small screens --- **Next: Email toggle and push settings backend** 🎯
This commit is contained in:
@@ -13,6 +13,7 @@ export default function NotificationsSettings() {
|
|||||||
<SettingsLayout
|
<SettingsLayout
|
||||||
title={__('Notifications')}
|
title={__('Notifications')}
|
||||||
description={__('Manage notification events, channels, and templates')}
|
description={__('Manage notification events, channels, and templates')}
|
||||||
|
action={<div />} // Empty action to trigger contextual header
|
||||||
>
|
>
|
||||||
<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-3">
|
||||||
|
|||||||
@@ -169,20 +169,27 @@ export default function NotificationChannels() {
|
|||||||
<SettingsCard title={__('Built-in Channels')} description={__('Channels included with WooNooW')}>
|
<SettingsCard title={__('Built-in Channels')} description={__('Channels included with WooNooW')}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{builtinChannels.map((channel: NotificationChannel) => (
|
{builtinChannels.map((channel: NotificationChannel) => (
|
||||||
<div key={channel.id} className="flex items-center justify-between p-4 rounded-lg border bg-card">
|
<div key={channel.id} className="flex flex-col sm:flex-row sm:items-center gap-4 p-4 rounded-lg border bg-card">
|
||||||
<div className="flex items-center gap-4 flex-1">
|
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||||
<div className="p-3 rounded-lg bg-primary/10">{getChannelIcon(channel.id)}</div>
|
<div className={`p-3 rounded-lg shrink-0 ${channel.enabled ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground'}`}>
|
||||||
<div className="flex-1">
|
{getChannelIcon(channel.id)}
|
||||||
<div className="flex items-center gap-2 mb-1">
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<h3 className="font-medium">{channel.label}</h3>
|
<h3 className="font-medium">{channel.label}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2 mb-2">
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs">
|
||||||
{__('Built-in')}
|
{__('Built-in')}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant={channel.enabled ? 'default' : 'secondary'} className="text-xs">
|
<Badge
|
||||||
|
variant={channel.enabled ? 'default' : 'secondary'}
|
||||||
|
className={`text-xs ${channel.enabled ? 'bg-green-500 hover:bg-green-600' : ''}`}
|
||||||
|
>
|
||||||
{channel.enabled ? __('Active') : __('Inactive')}
|
{channel.enabled ? __('Active') : __('Inactive')}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
{channel.id === 'email' &&
|
{channel.id === 'email' &&
|
||||||
__('Email notifications powered by WooCommerce. Configure templates and SMTP settings.')}
|
__('Email notifications powered by WooCommerce. Configure templates and SMTP settings.')}
|
||||||
{channel.id === 'push' &&
|
{channel.id === 'push' &&
|
||||||
@@ -190,7 +197,7 @@ export default function NotificationChannels() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -198,32 +205,31 @@ export default function NotificationChannels() {
|
|||||||
setSelectedChannel(channel);
|
setSelectedChannel(channel);
|
||||||
setConfigOpen(true);
|
setConfigOpen(true);
|
||||||
}}
|
}}
|
||||||
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
<Settings className="h-4 w-4 sm:mr-2" />
|
||||||
{__('Configure')}
|
<span className="sm:inline">{__('Configure')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
{channel.id === 'push' && pushSupported && (
|
{channel.id === 'push' && pushSupported && (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center justify-between sm:justify-start gap-2 p-2 sm:p-0 rounded-lg sm:rounded-none border sm:border-0">
|
||||||
<div className="flex items-center gap-2">
|
<span className="text-sm text-muted-foreground">
|
||||||
<span className="text-sm text-muted-foreground">
|
{pushSubscribed ? __('Subscribed') : __('Not subscribed')}
|
||||||
{pushSubscribed ? __('Subscribed') : __('Not subscribed')}
|
</span>
|
||||||
</span>
|
<Switch
|
||||||
<Switch
|
checked={pushSubscribed}
|
||||||
checked={pushSubscribed}
|
onCheckedChange={(checked) => {
|
||||||
onCheckedChange={(checked) => {
|
if (checked) {
|
||||||
if (checked) {
|
subscribeToPush.mutate();
|
||||||
subscribeToPush.mutate();
|
} else {
|
||||||
} else {
|
unsubscribeFromPush.mutate();
|
||||||
unsubscribeFromPush.mutate();
|
}
|
||||||
}
|
}}
|
||||||
}}
|
disabled={subscribeToPush.isPending || unsubscribeFromPush.isPending}
|
||||||
disabled={subscribeToPush.isPending || unsubscribeFromPush.isPending}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{channel.id === 'push' && !pushSupported && (
|
{channel.id === 'push' && !pushSupported && (
|
||||||
<Badge variant="destructive" className="text-xs">
|
<Badge variant="destructive" className="text-xs w-full sm:w-auto justify-center">
|
||||||
{__('Not Supported')}
|
{__('Not Supported')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default function NotificationEvents() {
|
|||||||
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-primary/10' : 'bg-muted'}`}>
|
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground'}`}>
|
||||||
{getChannelIcon(channel.id)}
|
{getChannelIcon(channel.id)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -212,7 +212,7 @@ export default function NotificationEvents() {
|
|||||||
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-primary/10' : 'bg-muted'}`}>
|
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground'}`}>
|
||||||
{getChannelIcon(channel.id)}
|
{getChannelIcon(channel.id)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -274,7 +274,7 @@ export default function NotificationEvents() {
|
|||||||
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-primary/10' : 'bg-muted'}`}>
|
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground'}`}>
|
||||||
{getChannelIcon(channel.id)}
|
{getChannelIcon(channel.id)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -125,20 +125,22 @@ export default function NotificationTemplates() {
|
|||||||
const customCount = channelTemplates.length;
|
const customCount = channelTemplates.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem key={channel.id} value={channel.id}>
|
<AccordionItem key={channel.id} value={channel.id} className="border-b">
|
||||||
<AccordionTrigger className="hover:no-underline">
|
<AccordionTrigger className="hover:no-underline py-4">
|
||||||
<div className="flex items-center gap-3 flex-1">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
<div className="p-2 rounded-lg bg-primary/10">{getChannelIcon(channel.id)}</div>
|
<div className="p-2 rounded-lg bg-primary/10 shrink-0">{getChannelIcon(channel.id)}</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-2 flex-1 min-w-0">
|
||||||
<span className="font-medium">{channel.label} {__('Templates')}</span>
|
<span className="font-medium text-left">{channel.label}</span>
|
||||||
<Badge variant="secondary" className="text-xs">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
{allEvents.length} {__('templates')}
|
<Badge variant="secondary" className="text-xs">
|
||||||
</Badge>
|
{allEvents.length} {__('templates')}
|
||||||
{customCount > 0 && (
|
|
||||||
<Badge variant="default" className="text-xs">
|
|
||||||
{customCount} {__('custom')}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
{customCount > 0 && (
|
||||||
|
<Badge variant="default" className="text-xs">
|
||||||
|
{customCount} {__('custom')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
@@ -181,47 +183,6 @@ export default function NotificationTemplates() {
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
|
|
||||||
{/* Template Variables Reference */}
|
|
||||||
<SettingsCard
|
|
||||||
title={__('Template Variables')}
|
|
||||||
description={__('Available variables you can use in your templates')}
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm mb-2">{__('Order Variables')}</h4>
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{order_number}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{order_total}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{order_status}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{order_date}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{payment_method}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{shipping_method}'}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm mb-2">{__('Customer Variables')}</h4>
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{customer_name}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{customer_email}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{customer_phone}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{billing_address}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{shipping_address}'}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm mb-2">{__('Store Variables')}</h4>
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{store_name}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{store_url}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{store_email}'}</code>
|
|
||||||
<code className="px-2 py-1 rounded bg-muted text-xs">{'{store_phone}'}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsCard>
|
|
||||||
{/* Template Editor Dialog */}
|
{/* Template Editor Dialog */}
|
||||||
{selectedEvent && selectedChannel && (
|
{selectedEvent && selectedChannel && (
|
||||||
<TemplateEditor
|
<TemplateEditor
|
||||||
|
|||||||
Reference in New Issue
Block a user