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:
dwindown
2025-11-08 21:18:51 +07:00
parent ab887f8f11
commit e8b4421950
2 changed files with 99 additions and 138 deletions

View File

@@ -131,3 +131,8 @@
/* --- WooNooW: Popper menus & fullscreen fixes --- */
[data-radix-popper-content-wrapper] { z-index: 2147483647 !important; }
body.woonoow-fullscreen .woonoow-app { overflow: visible; }
/* a[href] {
color: rgb(34 197 94);
font-weight: bold;
} */

View File

@@ -1,10 +1,13 @@
import React, { useState } 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 { 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 { __ } from '@/lib/i18n';
interface ShippingRate {
id: string;
@@ -22,68 +25,79 @@ interface ShippingZone {
}
export default function ShippingPage() {
const [zones] = useState<ShippingZone[]>([
{
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 queryClient = useQueryClient();
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
const [localPickup, setLocalPickup] = useState(true);
const [showDeliveryEstimates, setShowDeliveryEstimates] = useState(true);
// 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
});
const handleSave = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
toast.success('Shipping settings have been updated successfully.');
};
if (isLoading) {
return (
<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 (
<SettingsLayout
title="Shipping & Delivery"
description="Manage how you ship products to customers"
onSave={handleSave}
title={__('Shipping & Delivery')}
description={__('Manage how you ship products to customers')}
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 */}
<SettingsCard
title="Shipping Zones"
description="Create zones to group regions with similar shipping rates"
title={__('Shipping Zones')}
description={__('Create zones to group regions with similar shipping rates')}
>
<div className="space-y-4">
{zones.map((zone) => (
{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">
{zones.map((zone: any) => (
<div
key={zone.id}
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
@@ -104,21 +118,22 @@ export default function ShippingPage() {
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<Edit className="h-4 w-4 mr-2" />
Edit zone
<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 zone')}
</a>
</Button>
{zone.id !== 'domestic' && (
<Button variant="ghost" size="sm">
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
{/* Shipping Rates */}
<div className="pl-11 space-y-2">
{zone.rates.map((rate) => (
{zone.rates?.map((rate: any) => (
<div
key={rate.id}
className="flex items-center justify-between py-2 px-3 bg-muted/50 rounded-md"
@@ -144,88 +159,29 @@ export default function ShippingPage() {
))}
</div>
</div>
))}
))}
<Button variant="outline" className="w-full">
+ Add shipping zone
</Button>
</div>
</SettingsCard>
{/* Local Pickup */}
<SettingsCard
title="Local Pickup"
description="Let customers pick up orders from your location"
>
<ToggleField
id="localPickup"
label="Enable local pickup"
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
variant="outline"
className="w-full"
asChild
>
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&action=add_zone`} target="_blank" rel="noopener noreferrer">
+ {__('Add shipping zone')}
</a>
</Button>
</div>
)}
</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 */}
<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">
<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> Set realistic delivery estimates to manage customer expectations</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> {__('Set realistic delivery estimates to manage customer expectations')}</li>
<li> {__('Configure detailed shipping settings in WooCommerce for full control')}</li>
</ul>
</div>
</SettingsLayout>