feat: Implement live Shipping settings page
Implemented functional Shipping settings page with WooCommerce integration. Features: ✅ Fetch shipping zones from WooCommerce API ✅ Display zones with rates in card layout ✅ Refresh button to reload data ✅ "View in WooCommerce" button for full settings ✅ Edit zone links to WooCommerce ✅ Add zone link to WooCommerce ✅ Loading states with spinner ✅ Empty state when no zones configured ✅ Internationalization (i18n) throughout ✅ Shipping tips help card Implementation: - Uses React Query for data fetching - Integrates with WooCommerce shipping API - Links to WooCommerce for detailed configuration - Clean, modern UI matching Payments page - Responsive design API Endpoint: - GET /settings/shipping/zones Note: Full CRUD operations handled in WooCommerce for now. Future: Add inline editing capabilities.
This commit is contained in:
@@ -131,3 +131,8 @@
|
|||||||
/* --- WooNooW: Popper menus & fullscreen fixes --- */
|
/* --- WooNooW: Popper menus & fullscreen fixes --- */
|
||||||
[data-radix-popper-content-wrapper] { z-index: 2147483647 !important; }
|
[data-radix-popper-content-wrapper] { z-index: 2147483647 !important; }
|
||||||
body.woonoow-fullscreen .woonoow-app { overflow: visible; }
|
body.woonoow-fullscreen .woonoow-app { overflow: visible; }
|
||||||
|
|
||||||
|
/* a[href] {
|
||||||
|
color: rgb(34 197 94);
|
||||||
|
font-weight: bold;
|
||||||
|
} */
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
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 { ToggleField } from './components/ToggleField';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Globe, Truck, MapPin, Edit, Trash2 } from 'lucide-react';
|
import { Globe, Truck, MapPin, Edit, Trash2, RefreshCw, Loader2, ExternalLink } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
interface ShippingRate {
|
interface ShippingRate {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -22,68 +25,79 @@ interface ShippingZone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ShippingPage() {
|
export default function ShippingPage() {
|
||||||
const [zones] = useState<ShippingZone[]>([
|
const queryClient = useQueryClient();
|
||||||
{
|
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
||||||
id: 'domestic',
|
|
||||||
name: 'Domestic (Indonesia)',
|
|
||||||
regions: 'All Indonesia',
|
|
||||||
rates: [
|
|
||||||
{
|
|
||||||
id: 'standard',
|
|
||||||
name: 'Standard Shipping',
|
|
||||||
price: 'Rp 15,000',
|
|
||||||
transitTime: '3-5 business days',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'express',
|
|
||||||
name: 'Express Shipping',
|
|
||||||
price: 'Rp 30,000',
|
|
||||||
transitTime: '1-2 business days',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'free',
|
|
||||||
name: 'Free Shipping',
|
|
||||||
price: 'Free',
|
|
||||||
condition: 'Order total > Rp 500,000',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'international',
|
|
||||||
name: 'International',
|
|
||||||
regions: 'Rest of world',
|
|
||||||
rates: [
|
|
||||||
{
|
|
||||||
id: 'intl',
|
|
||||||
name: 'International Shipping',
|
|
||||||
price: 'Calculated',
|
|
||||||
transitTime: '7-14 business days',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [localPickup, setLocalPickup] = useState(true);
|
// Fetch shipping zones from WooCommerce
|
||||||
const [showDeliveryEstimates, setShowDeliveryEstimates] = useState(true);
|
const { data: zones = [], isLoading, refetch } = useQuery({
|
||||||
|
queryKey: ['shipping-zones'],
|
||||||
|
queryFn: () => api.get('/settings/shipping/zones'),
|
||||||
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
|
});
|
||||||
|
|
||||||
const handleSave = async () => {
|
if (isLoading) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
return (
|
||||||
toast.success('Shipping settings have been updated successfully.');
|
<SettingsLayout
|
||||||
};
|
title={__('Shipping & Delivery')}
|
||||||
|
description={__('Manage how you ship products to customers')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsLayout
|
<SettingsLayout
|
||||||
title="Shipping & Delivery"
|
title={__('Shipping & Delivery')}
|
||||||
description="Manage how you ship products to customers"
|
description={__('Manage how you ship products to customers')}
|
||||||
onSave={handleSave}
|
action={
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
|
{__('Refresh')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping`} target="_blank" rel="noopener noreferrer">
|
||||||
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
|
{__('View in WooCommerce')}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{/* Shipping Zones */}
|
{/* Shipping Zones */}
|
||||||
<SettingsCard
|
<SettingsCard
|
||||||
title="Shipping Zones"
|
title={__('Shipping Zones')}
|
||||||
description="Create zones to group regions with similar shipping rates"
|
description={__('Create zones to group regions with similar shipping rates')}
|
||||||
>
|
>
|
||||||
|
{zones.length === 0 ? (
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
{__('No shipping zones configured yet.')}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping`} target="_blank" rel="noopener noreferrer">
|
||||||
|
{__('Configure in WooCommerce')}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{zones.map((zone) => (
|
{zones.map((zone: any) => (
|
||||||
<div
|
<div
|
||||||
key={zone.id}
|
key={zone.id}
|
||||||
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
|
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
|
||||||
@@ -104,21 +118,22 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="outline" size="sm">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&zone_id=${zone.id}`} target="_blank" rel="noopener noreferrer">
|
||||||
<Edit className="h-4 w-4 mr-2" />
|
<Edit className="h-4 w-4 mr-2" />
|
||||||
Edit zone
|
{__('Edit zone')}
|
||||||
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
{zone.id !== 'domestic' && (
|
|
||||||
<Button variant="ghost" size="sm">
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Shipping Rates */}
|
{/* Shipping Rates */}
|
||||||
<div className="pl-11 space-y-2">
|
<div className="pl-11 space-y-2">
|
||||||
{zone.rates.map((rate) => (
|
{zone.rates?.map((rate: any) => (
|
||||||
<div
|
<div
|
||||||
key={rate.id}
|
key={rate.id}
|
||||||
className="flex items-center justify-between py-2 px-3 bg-muted/50 rounded-md"
|
className="flex items-center justify-between py-2 px-3 bg-muted/50 rounded-md"
|
||||||
@@ -146,86 +161,27 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Button variant="outline" className="w-full">
|
<Button
|
||||||
+ Add shipping zone
|
variant="outline"
|
||||||
</Button>
|
className="w-full"
|
||||||
</div>
|
asChild
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Local Pickup */}
|
|
||||||
<SettingsCard
|
|
||||||
title="Local Pickup"
|
|
||||||
description="Let customers pick up orders from your location"
|
|
||||||
>
|
>
|
||||||
<ToggleField
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&action=add_zone`} target="_blank" rel="noopener noreferrer">
|
||||||
id="localPickup"
|
+ {__('Add shipping zone')}
|
||||||
label="Enable local pickup"
|
</a>
|
||||||
description="Customers can choose to pick up their order instead of shipping"
|
|
||||||
checked={localPickup}
|
|
||||||
onCheckedChange={setLocalPickup}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{localPickup && (
|
|
||||||
<div className="mt-4 p-4 border rounded-lg space-y-3">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<MapPin className="h-5 w-5 text-primary mt-0.5" />
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="font-medium">Main Store</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Jl. Example No. 123, Jakarta 12345
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Mon-Fri: 9:00 AM - 5:00 PM
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button variant="ghost" size="sm">
|
|
||||||
<Edit className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className="w-full">
|
|
||||||
+ Add pickup location
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Shipping Options */}
|
|
||||||
<SettingsCard
|
|
||||||
title="Shipping Options"
|
|
||||||
description="Additional shipping settings"
|
|
||||||
>
|
|
||||||
<ToggleField
|
|
||||||
id="deliveryEstimates"
|
|
||||||
label="Show delivery estimates"
|
|
||||||
description="Display estimated delivery dates at checkout"
|
|
||||||
checked={showDeliveryEstimates}
|
|
||||||
onCheckedChange={setShowDeliveryEstimates}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ToggleField
|
|
||||||
id="hideCosts"
|
|
||||||
label="Hide shipping costs until address entered"
|
|
||||||
description="Require customers to enter their address before showing shipping costs"
|
|
||||||
checked={false}
|
|
||||||
onCheckedChange={() => { /* TODO: Implement */ }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ToggleField
|
|
||||||
id="requireAddress"
|
|
||||||
label="Require shipping address"
|
|
||||||
description="Always collect shipping address, even for digital products"
|
|
||||||
checked={false}
|
|
||||||
onCheckedChange={() => { /* TODO: Implement */ }}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Help Card */}
|
{/* Help Card */}
|
||||||
<div className="bg-muted/50 border rounded-lg p-4">
|
<div className="bg-muted/50 border rounded-lg p-4">
|
||||||
<p className="text-sm font-medium mb-2">💡 Shipping tips</p>
|
<p className="text-sm font-medium mb-2">💡 {__('Shipping tips')}</p>
|
||||||
<ul className="text-sm text-muted-foreground space-y-1">
|
<ul className="text-sm text-muted-foreground space-y-1">
|
||||||
<li>• Offer free shipping for orders above a certain amount to increase average order value</li>
|
<li>• {__('Offer free shipping for orders above a certain amount to increase average order value')}</li>
|
||||||
<li>• Provide multiple shipping options to give customers flexibility</li>
|
<li>• {__('Provide multiple shipping options to give customers flexibility')}</li>
|
||||||
<li>• Set realistic delivery estimates to manage customer expectations</li>
|
<li>• {__('Set realistic delivery estimates to manage customer expectations')}</li>
|
||||||
|
<li>• {__('Configure detailed shipping settings in WooCommerce for full control')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user