fix: Shipping toggle refresh + AlertDialog + Local Pickup nav + Notifications info
## 1. Fixed Shipping Method Toggle State ✅ - Updated useEffect to properly sync selectedZone with zones data - Added JSON comparison to prevent infinite loops - Toggle now refreshes zone data correctly ## 2. Replace confirm() with AlertDialog ✅ - Added AlertDialog component for delete confirmation - Shows method name in confirmation message - Better UX with proper dialog styling - Updated both desktop and mobile versions ## 3. Added Local Pickup to Navigation ✅ - Added "Local Pickup" menu item in Settings - Now accessible from Settings > Local Pickup - Path: /settings/local-pickup ## 4. Shipping Cost Shortcodes ✅ - Already supported via HTML rendering - WooCommerce shortcodes like [fee percent="10"] work - [qty], [cost] are handled by WooCommerce backend - No additional SPA work needed ## 5. Enhanced Notifications Page ✅ - Added comprehensive info card explaining: - What WooNooW provides (simple toggle) - What WooCommerce provides (advanced config) - Clear guidance on when to use each - Links to WooCommerce for templates/styling - Replaced ToggleField with Switch for simpler usage ## Key Decisions: ✅ AlertDialog > confirm() for better UX ✅ Notifications = Simple toggle + guidance to WC ✅ Shortcodes handled by WooCommerce (no SPA work) ✅ Local Pickup now discoverable in nav
This commit is contained in:
@@ -111,6 +111,7 @@ function getStaticFallbackTree(): MainNode[] {
|
|||||||
{ label: 'Store Details', mode: 'spa' as const, path: '/settings/store' },
|
{ label: 'Store Details', mode: 'spa' as const, path: '/settings/store' },
|
||||||
{ label: 'Payments', mode: 'spa' as const, path: '/settings/payments' },
|
{ label: 'Payments', mode: 'spa' as const, path: '/settings/payments' },
|
||||||
{ label: 'Shipping & Delivery', mode: 'spa' as const, path: '/settings/shipping' },
|
{ label: 'Shipping & Delivery', mode: 'spa' as const, path: '/settings/shipping' },
|
||||||
|
{ label: 'Local Pickup', mode: 'spa' as const, path: '/settings/local-pickup' },
|
||||||
{ label: 'Tax', mode: 'spa' as const, path: '/settings/tax' },
|
{ label: 'Tax', mode: 'spa' as const, path: '/settings/tax' },
|
||||||
{ label: 'Checkout', mode: 'spa' as const, path: '/settings/checkout' },
|
{ label: 'Checkout', mode: 'spa' as const, path: '/settings/checkout' },
|
||||||
{ label: 'Customer Accounts', mode: 'spa' as const, path: '/settings/customers' },
|
{ label: 'Customer Accounts', mode: 'spa' as const, path: '/settings/customers' },
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import { SettingsLayout } from './components/SettingsLayout';
|
import { SettingsLayout } from './components/SettingsLayout';
|
||||||
import { SettingsCard } from './components/SettingsCard';
|
import { SettingsCard } from './components/SettingsCard';
|
||||||
import { ToggleField } from './components/ToggleField';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ExternalLink, RefreshCw, Mail } from 'lucide-react';
|
import { ExternalLink, RefreshCw, Mail } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -65,6 +65,38 @@ export default function NotificationsSettings() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
{/* Info Card */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('About Email Notifications')}
|
||||||
|
description={__('Quick enable/disable controls for WooCommerce emails')}
|
||||||
|
>
|
||||||
|
<div className="text-sm text-muted-foreground space-y-2">
|
||||||
|
<p>
|
||||||
|
{__('WooNooW provides simple toggle controls to enable or disable email notifications. For advanced customization like email templates, styling, content, and recipients, use the WooCommerce settings page.')}
|
||||||
|
</p>
|
||||||
|
<p className="font-medium text-foreground">
|
||||||
|
{__('What you can do here:')}
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
||||||
|
<li>{__('Enable/disable customer emails (order confirmations, shipping updates, etc.)')}</li>
|
||||||
|
<li>{__('Enable/disable admin emails (new order notifications, low stock alerts, etc.)')}</li>
|
||||||
|
<li>{__('View current sender name and email address')}</li>
|
||||||
|
</ul>
|
||||||
|
<p className="font-medium text-foreground mt-3">
|
||||||
|
{__('For advanced configuration:')}
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
||||||
|
<li>{__('Email templates and HTML/CSS styling')}</li>
|
||||||
|
<li>{__('Email subject lines and content')}</li>
|
||||||
|
<li>{__('Custom recipient addresses')}</li>
|
||||||
|
<li>{__('Additional email headers')}</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-3">
|
||||||
|
{__('Use the "Edit in WooCommerce" links below or the advanced settings link at the bottom.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Customer Emails */}
|
{/* Customer Emails */}
|
||||||
<SettingsCard
|
<SettingsCard
|
||||||
title={__('Customer Notifications')}
|
title={__('Customer Notifications')}
|
||||||
@@ -81,9 +113,9 @@ export default function NotificationsSettings() {
|
|||||||
<p className="text-sm text-muted-foreground">{email.description}</p>
|
<p className="text-sm text-muted-foreground">{email.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<ToggleField
|
<Switch
|
||||||
checked={email.enabled === 'yes'}
|
checked={email.enabled === 'yes'}
|
||||||
onChange={(checked) => toggleMutation.mutate({
|
onCheckedChange={(checked) => toggleMutation.mutate({
|
||||||
emailId: email.id,
|
emailId: email.id,
|
||||||
enabled: checked
|
enabled: checked
|
||||||
})}
|
})}
|
||||||
@@ -124,9 +156,9 @@ export default function NotificationsSettings() {
|
|||||||
<p className="text-sm text-muted-foreground">{email.description}</p>
|
<p className="text-sm text-muted-foreground">{email.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<ToggleField
|
<Switch
|
||||||
checked={email.enabled === 'yes'}
|
checked={email.enabled === 'yes'}
|
||||||
onChange={(checked) => toggleMutation.mutate({
|
onCheckedChange={(checked) => toggleMutation.mutate({
|
||||||
emailId: email.id,
|
emailId: email.id,
|
||||||
enabled: checked
|
enabled: checked
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SettingsCard } from './components/SettingsCard';
|
|||||||
import { ToggleField } from './components/ToggleField';
|
import { ToggleField } from './components/ToggleField';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer';
|
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer';
|
||||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||||
import { Globe, Truck, MapPin, Edit, Trash2, RefreshCw, Loader2, ExternalLink, Settings, Plus, X, ChevronDown } from 'lucide-react';
|
import { Globe, Truck, MapPin, Edit, Trash2, RefreshCw, Loader2, ExternalLink, Settings, Plus, X, ChevronDown } from 'lucide-react';
|
||||||
@@ -33,10 +34,10 @@ export default function ShippingPage() {
|
|||||||
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
||||||
const [togglingMethod, setTogglingMethod] = useState<string | null>(null);
|
const [togglingMethod, setTogglingMethod] = useState<string | null>(null);
|
||||||
const [selectedZone, setSelectedZone] = useState<any | null>(null);
|
const [selectedZone, setSelectedZone] = useState<any | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const [showAddMethod, setShowAddMethod] = useState(false);
|
const [showAddMethod, setShowAddMethod] = useState(false);
|
||||||
const [expandedMethod, setExpandedMethod] = useState<string>('');
|
const [expandedMethod, setExpandedMethod] = useState<string>('');
|
||||||
const [methodSettings, setMethodSettings] = useState<Record<string, any>>({});
|
const [methodSettings, setMethodSettings] = useState<Record<string, any>>({});
|
||||||
|
const [deletingMethod, setDeletingMethod] = useState<{ zoneId: number; instanceId: number; name: string } | null>(null);
|
||||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||||
|
|
||||||
// Fetch shipping zones from WooCommerce
|
// Fetch shipping zones from WooCommerce
|
||||||
@@ -55,13 +56,13 @@ export default function ShippingPage() {
|
|||||||
|
|
||||||
// Sync selectedZone with zones data when it changes
|
// Sync selectedZone with zones data when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedZone && zones.length > 0) {
|
if (selectedZone && zones && zones.length > 0) {
|
||||||
const updatedZone = zones.find((z: any) => z.id === selectedZone.id);
|
const updatedZone = zones.find((z: any) => z.id === selectedZone.id);
|
||||||
if (updatedZone) {
|
if (updatedZone && JSON.stringify(updatedZone) !== JSON.stringify(selectedZone)) {
|
||||||
setSelectedZone(updatedZone);
|
setSelectedZone(updatedZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [zones]);
|
}, [zones, selectedZone]);
|
||||||
|
|
||||||
// Toggle shipping method mutation
|
// Toggle shipping method mutation
|
||||||
const toggleMutation = useMutation({
|
const toggleMutation = useMutation({
|
||||||
@@ -153,9 +154,19 @@ export default function ShippingPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteMethod = (instanceId: number) => {
|
const handleDeleteMethod = (instanceId: number, methodName: string) => {
|
||||||
if (selectedZone && confirm(__('Are you sure you want to delete this shipping method?'))) {
|
if (selectedZone) {
|
||||||
deleteMethodMutation.mutate({ zoneId: selectedZone.id, instanceId });
|
setDeletingMethod({ zoneId: selectedZone.id, instanceId, name: methodName });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (deletingMethod) {
|
||||||
|
deleteMethodMutation.mutate({
|
||||||
|
zoneId: deletingMethod.zoneId,
|
||||||
|
instanceId: deletingMethod.instanceId
|
||||||
|
});
|
||||||
|
setDeletingMethod(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -424,7 +435,7 @@ export default function ShippingPage() {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteMethod(rate.instance_id);
|
handleDeleteMethod(rate.instance_id, rate.title);
|
||||||
setExpandedMethod('');
|
setExpandedMethod('');
|
||||||
}}
|
}}
|
||||||
disabled={deleteMethodMutation.isPending}
|
disabled={deleteMethodMutation.isPending}
|
||||||
@@ -594,7 +605,7 @@ export default function ShippingPage() {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteMethod(rate.instance_id);
|
handleDeleteMethod(rate.instance_id, rate.title);
|
||||||
setExpandedMethod('');
|
setExpandedMethod('');
|
||||||
}}
|
}}
|
||||||
disabled={deleteMethodMutation.isPending}
|
disabled={deleteMethodMutation.isPending}
|
||||||
@@ -696,6 +707,28 @@ export default function ShippingPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<AlertDialog open={!!deletingMethod} onOpenChange={() => setDeletingMethod(null)}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{__('Delete Shipping Method?')}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{__('Are you sure you want to delete')} <strong>{deletingMethod?.name}</strong>?
|
||||||
|
{' '}{__('This action cannot be undone.')}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{__('Cancel')}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={confirmDelete}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
{__('Delete')}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user