import React, { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { SettingsLayout } from './components/SettingsLayout'; import { SettingsCard } from './components/SettingsCard'; import { ToggleField } from './components/ToggleField'; import { Button } from '@/components/ui/button'; 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 { 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 { toast } from 'sonner'; import { __ } from '@/lib/i18n'; import { useMediaQuery } from '@/hooks/use-media-query'; interface ShippingRate { id: string; name: string; price: string; condition?: string; transitTime?: string; } interface ShippingZone { id: string; name: string; regions: string; rates: ShippingRate[]; } export default function ShippingPage() { const queryClient = useQueryClient(); const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin'; const [togglingMethod, setTogglingMethod] = useState(null); const [selectedZone, setSelectedZone] = useState(null); const [showAddMethod, setShowAddMethod] = useState(false); const [expandedMethod, setExpandedMethod] = useState(''); const [methodSettings, setMethodSettings] = useState>({}); const [deletingMethod, setDeletingMethod] = useState<{ zoneId: number; instanceId: number; name: string } | null>(null); const [showAddZone, setShowAddZone] = useState(false); const [editingZone, setEditingZone] = useState(null); const [deletingZone, setDeletingZone] = useState(null); const isDesktop = useMediaQuery("(min-width: 768px)"); // Fetch shipping zones from WooCommerce const { data: zones = [], isLoading, refetch } = useQuery({ queryKey: ['shipping-zones'], queryFn: () => api.get('/settings/shipping/zones'), staleTime: 5 * 60 * 1000, // 5 minutes }); // Fetch available shipping methods const { data: availableMethods = [] } = useQuery({ queryKey: ['available-shipping-methods'], queryFn: () => api.get('/settings/shipping/methods/available'), enabled: showAddMethod, }); // Sync selectedZone with zones data when it changes useEffect(() => { if (selectedZone && zones && zones.length > 0) { const updatedZone = zones.find((z: any) => z.id === selectedZone.id); if (updatedZone && JSON.stringify(updatedZone) !== JSON.stringify(selectedZone)) { setSelectedZone(updatedZone); } } }, [zones, selectedZone]); // Toggle shipping method mutation const toggleMutation = useMutation({ mutationFn: async ({ zoneId, instanceId, enabled }: { zoneId: number; instanceId: number; enabled: boolean }) => { return api.post(`/settings/shipping/zones/${zoneId}/methods/${instanceId}/toggle`, { enabled }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Shipping method updated successfully')); }, onError: (error: any) => { toast.error(error?.message || __('Failed to update shipping method')); }, onSettled: () => { setTogglingMethod(null); }, }); // Add shipping method mutation const addMethodMutation = useMutation({ mutationFn: async ({ zoneId, methodId }: { zoneId: number; methodId: string }) => { return api.post(`/settings/shipping/zones/${zoneId}/methods`, { method_id: methodId }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Shipping method added successfully')); setShowAddMethod(false); }, onError: (error: any) => { toast.error(error?.message || __('Failed to add shipping method')); }, }); // Delete shipping method mutation const deleteMethodMutation = useMutation({ mutationFn: async ({ zoneId, instanceId }: { zoneId: number; instanceId: number }) => { return api.del(`/settings/shipping/zones/${zoneId}/methods/${instanceId}`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Delivery option deleted')); }, onError: (error: any) => { toast.error(error?.message || __('Failed to delete delivery option')); }, }); // Fetch method settings when accordion expands const fetchMethodSettings = async (instanceId: number) => { if (!selectedZone || methodSettings[instanceId]) return; try { const settings = await api.get(`/settings/shipping/zones/${selectedZone.id}/methods/${instanceId}/settings`); setMethodSettings(prev => ({ ...prev, [instanceId]: settings })); } catch (error) { console.error('Failed to fetch method settings:', error); } }; // Update method settings mutation const updateSettingsMutation = useMutation({ mutationFn: async ({ zoneId, instanceId, settings }: { zoneId: number; instanceId: number; settings: any }) => { return api.post(`/settings/shipping/zones/${zoneId}/methods/${instanceId}/settings`, { settings }); }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); // Clear cached settings to force refetch setMethodSettings(prev => { const newSettings = { ...prev }; delete newSettings[variables.instanceId]; return newSettings; }); toast.success(__('Settings saved')); setExpandedMethod(''); }, onError: (error: any) => { toast.error(error?.message || __('Failed to save settings')); }, }); const handleToggle = (zoneId: number, instanceId: number, enabled: boolean) => { setTogglingMethod(`${zoneId}-${instanceId}`); toggleMutation.mutate({ zoneId, instanceId, enabled }); }; const handleAddMethod = (methodId: string) => { if (selectedZone) { addMethodMutation.mutate({ zoneId: selectedZone.id, methodId }); } }; const handleDeleteMethod = (instanceId: number, methodName: string) => { if (selectedZone) { setDeletingMethod({ zoneId: selectedZone.id, instanceId, name: methodName }); } }; const confirmDelete = () => { if (deletingMethod) { deleteMethodMutation.mutate({ zoneId: deletingMethod.zoneId, instanceId: deletingMethod.instanceId }); setDeletingMethod(null); } }; // Zone mutations const createZoneMutation = useMutation({ mutationFn: async (data: { name: string; regions: any[] }) => { return api.post('/settings/shipping/zones', data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Zone created successfully')); setShowAddZone(false); }, onError: (error: any) => { toast.error(error?.message || __('Failed to create zone')); }, }); const updateZoneMutation = useMutation({ mutationFn: async ({ zoneId, ...data }: { zoneId: number; name?: string; regions?: any[] }) => { return api.wpFetch(`/woonoow/v1/settings/shipping/zones/${zoneId}`, { method: 'PUT', body: JSON.stringify(data), }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Zone updated successfully')); setEditingZone(null); }, onError: (error: any) => { toast.error(error?.message || __('Failed to update zone')); }, }); const deleteZoneMutation = useMutation({ mutationFn: async (zoneId: number) => { return api.del(`/settings/shipping/zones/${zoneId}`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['shipping-zones'] }); toast.success(__('Zone deleted successfully')); setDeletingZone(null); }, onError: (error: any) => { toast.error(error?.message || __('Failed to delete zone')); }, }); if (isLoading) { return (
); } return ( refetch()} disabled={isLoading} > {__('Refresh')} } > {/* Shipping Zones */} setShowAddZone(true)} > {__('Add Zone')} } > {zones.length === 0 ? (

{__('No shipping zones configured yet. Create your first zone to start offering shipping.')}

) : (
{zones.map((zone: any) => (

{zone.name}

{zone.regions}

{zone.rates.length} {zone.rates.length === 1 ? 'method' : 'methods'}

{/* Shipping Rates */}
{zone.rates?.map((rate: any) => (
{rate.transitTime && ( • {rate.transitTime} )} {rate.condition && ( • {rate.condition} )}
handleToggle(zone.id, rate.instance_id, checked)} disabled={togglingMethod === `${zone.id}-${rate.instance_id}`} />
))}
))}
)}
{/* Help Card */}

💡 {__('Shipping tips')}

  • • {__('Offer free shipping for orders above a certain amount to increase average order value')}
  • • {__('Provide multiple shipping options to give customers flexibility')}
  • • {__('Set realistic delivery estimates to manage customer expectations')}
  • • {__('Configure detailed shipping settings in WooCommerce for full control')}
{/* Settings Modal/Drawer */} {selectedZone && ( isDesktop ? ( !open && setSelectedZone(null)}> {selectedZone.name}

{selectedZone.regions}

{/* Add Delivery Option Button */} {/* Delivery Options Accordion */} { setExpandedMethod(value); if (value) { const instanceId = parseInt(value); fetchMethodSettings(instanceId); } }}> {selectedZone.rates?.map((rate: any) => (
{rate.enabled ? __('On') : __('Off')}
{methodSettings[rate.instance_id] ? (

{__('Name shown to customers at checkout')}

{methodSettings[rate.instance_id].settings?.cost && (
{methodSettings[rate.instance_id].settings.cost.description && (

)}

)} {methodSettings[rate.instance_id].settings?.min_amount && (
{methodSettings[rate.instance_id].settings.min_amount.description && (

)}

)}
) : (
)}
))}
) : ( !open && setSelectedZone(null)}> {selectedZone.name}

{selectedZone.regions}

{/* Add Delivery Option Button */} {/* Delivery Options Accordion (Mobile) */} { setExpandedMethod(value); if (value) { const instanceId = parseInt(value); fetchMethodSettings(instanceId); } }}> {selectedZone.rates?.map((rate: any) => (
{rate.enabled ? __('On') : __('Off')}
{methodSettings[rate.instance_id] ? (
{methodSettings[rate.instance_id].settings?.cost && (
{methodSettings[rate.instance_id].settings.cost.description && (

)}

)} {methodSettings[rate.instance_id].settings?.min_amount && (
)}
) : (
)}
))}
) )} {/* Add Delivery Option Dialog */} {__('Add Delivery Option')}

{__('Select a delivery option to add:')}

{availableMethods.map((method: any) => ( ))}
{/* Delete Confirmation Dialog */} setDeletingMethod(null)}> {__('Delete Shipping Method?')} {__('Are you sure you want to delete')} {deletingMethod?.name}? {' '}{__('This action cannot be undone.')} {__('Cancel')} {__('Delete')} ); }