feat: Phase 1 - Improve shipping zone UI (remove redundancy)
Implemented modern, Shopify-inspired shipping interface improvements. Changes: ✅ Removed redundant "Settings" button from zone cards ✅ Added subtle Edit icon button for zone management ✅ Enhanced modal to be informational (not just toggles) ✅ Removed duplicate toggles from modal (use inline toggles instead) ✅ Added zone order display with context ✅ Show Active/Inactive badges instead of toggles in modal ✅ Better visual hierarchy and spacing ✅ Improved mobile drawer layout ✅ Changed "Close" to "Done" (better UX) ✅ Changed "Advanced Settings" to "Edit in WooCommerce" Modal Now Shows: - Zone name and regions in header - Zone order with explanation - All shipping methods with: * Method name and icon * Cost display * Active/Inactive status badge * Description (if available) - Link to edit in WooCommerce User Flow: 1. See zones with inline toggles (quick enable/disable) 2. Click Edit icon → View zone details 3. See all methods and their status 4. Click "Edit in WooCommerce" for advanced settings Result: Clean, modern UI with no redundancy ✅
This commit is contained in:
@@ -119,7 +119,7 @@ export default function ShippingPage() {
|
|||||||
key={zone.id}
|
key={zone.id}
|
||||||
className="border rounded-lg p-3 md:p-4 hover:border-primary/50 transition-colors"
|
className="border rounded-lg p-3 md:p-4 hover:border-primary/50 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col md:flex-row items-start md:justify-between gap-3 mb-3 md:mb-4">
|
<div className="flex items-start justify-between gap-3 mb-3 md:mb-4">
|
||||||
<div className="flex items-start gap-2 md:gap-3 flex-1">
|
<div className="flex items-start gap-2 md:gap-3 flex-1">
|
||||||
<div className="p-1.5 md:p-2 bg-primary/10 rounded-lg text-primary flex-shrink-0">
|
<div className="p-1.5 md:p-2 bg-primary/10 rounded-lg text-primary flex-shrink-0">
|
||||||
<Globe className="h-4 w-4 md:h-5 md:w-5" />
|
<Globe className="h-4 w-4 md:h-5 md:w-5" />
|
||||||
@@ -127,27 +127,23 @@ export default function ShippingPage() {
|
|||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h3 className="font-semibold text-base md:text-lg">{zone.name}</h3>
|
<h3 className="font-semibold text-base md:text-lg">{zone.name}</h3>
|
||||||
<p className="text-xs md:text-sm text-muted-foreground truncate">
|
<p className="text-xs md:text-sm text-muted-foreground truncate">
|
||||||
Regions: {zone.regions}
|
{zone.regions}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs md:text-sm text-muted-foreground">
|
<p className="text-xs md:text-sm text-muted-foreground">
|
||||||
Rates: {zone.rates.length} shipping rate{zone.rates.length !== 1 ? 's' : ''}
|
{zone.rates.length} {zone.rates.length === 1 ? 'method' : 'methods'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<Button
|
||||||
<Button
|
variant="ghost"
|
||||||
variant="outline"
|
size="sm"
|
||||||
size="sm"
|
onClick={() => {
|
||||||
className="w-full md:w-auto"
|
setSelectedZone(zone);
|
||||||
onClick={() => {
|
setIsModalOpen(true);
|
||||||
setSelectedZone(zone);
|
}}
|
||||||
setIsModalOpen(true);
|
>
|
||||||
}}
|
<Edit className="h-4 w-4" />
|
||||||
>
|
</Button>
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
|
||||||
{__('Settings')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Shipping Rates */}
|
{/* Shipping Rates */}
|
||||||
@@ -224,62 +220,81 @@ export default function ShippingPage() {
|
|||||||
{selectedZone && (
|
{selectedZone && (
|
||||||
isDesktop ? (
|
isDesktop ? (
|
||||||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||||
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col p-0">
|
<DialogContent className="max-w-2xl max-h-[90vh] flex flex-col p-0">
|
||||||
<DialogHeader className="px-6 py-4 border-b">
|
<DialogHeader className="px-6 py-4 border-b">
|
||||||
<DialogTitle>{selectedZone.name} {__('Settings')}</DialogTitle>
|
<DialogTitle>{selectedZone.name}</DialogTitle>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{selectedZone.regions}
|
||||||
|
</p>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-y-auto p-6 min-h-0">
|
<div className="flex-1 overflow-y-auto p-6 min-h-0">
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<div className="space-y-6">
|
||||||
{__('Configure shipping zone and methods. For advanced settings, use WooCommerce.')}
|
{/* Zone Summary */}
|
||||||
</p>
|
<div className="bg-muted/50 rounded-lg p-4">
|
||||||
<div className="space-y-4">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">{__('Zone Information')}</h4>
|
<p className="text-sm font-medium">{__('Zone Order')}</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-muted-foreground">{__('Priority in shipping calculations')}</p>
|
||||||
{__('Name')}: {selectedZone.name}
|
</div>
|
||||||
</p>
|
<span className="text-2xl font-bold text-muted-foreground">{selectedZone.order}</span>
|
||||||
<p className="text-sm text-muted-foreground">
|
</div>
|
||||||
{__('Regions')}: {selectedZone.regions}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Shipping Methods */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">{__('Shipping Methods')}</h4>
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="space-y-2">
|
<h4 className="font-semibold">{__('Shipping Methods')}</h4>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{selectedZone.rates?.length} {selectedZone.rates?.length === 1 ? 'method' : 'methods'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
{selectedZone.rates?.map((rate: any) => (
|
{selectedZone.rates?.map((rate: any) => (
|
||||||
<div key={rate.id} className="border rounded-lg p-3">
|
<div key={rate.id} className="border rounded-lg p-4 hover:border-primary/50 transition-colors">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<span className="font-medium">{rate.name}</span>
|
<div className="flex-1">
|
||||||
<ToggleField
|
<div className="flex items-center gap-2 mb-1">
|
||||||
id={`modal-${selectedZone.id}-${rate.instance_id}`}
|
<Truck className="h-4 w-4 text-muted-foreground" />
|
||||||
label=""
|
<span className="font-medium" dangerouslySetInnerHTML={{ __html: rate.name }} />
|
||||||
checked={rate.enabled}
|
</div>
|
||||||
onCheckedChange={(checked) => {
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||||
handleToggle(selectedZone.id, rate.instance_id, checked);
|
<span className="flex items-center gap-1">
|
||||||
}}
|
<span>{__('Cost')}:</span>
|
||||||
disabled={togglingMethod === `${selectedZone.id}-${rate.instance_id}`}
|
<span className="font-semibold" dangerouslySetInnerHTML={{ __html: rate.price }} />
|
||||||
/>
|
</span>
|
||||||
|
{rate.description && (
|
||||||
|
<span className="text-xs" dangerouslySetInnerHTML={{ __html: rate.description }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||||
|
rate.enabled
|
||||||
|
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||||
|
: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||||
|
}`}>
|
||||||
|
{rate.enabled ? __('Active') : __('Inactive')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{__('Price')}: <span dangerouslySetInnerHTML={{ __html: rate.price }} />
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-6 py-4 border-t flex justify-between">
|
<div className="px-6 py-4 border-t flex justify-between gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&zone_id=${selectedZone.id}`} target="_blank" rel="noopener noreferrer">
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&zone_id=${selectedZone.id}`} target="_blank" rel="noopener noreferrer">
|
||||||
<ExternalLink className="h-4 w-4 mr-2" />
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
{__('Advanced Settings in WooCommerce')}
|
{__('Edit in WooCommerce')}
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setIsModalOpen(false)}>
|
<Button onClick={() => setIsModalOpen(false)}>
|
||||||
{__('Close')}
|
{__('Done')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -288,42 +303,54 @@ export default function ShippingPage() {
|
|||||||
<Drawer open={isModalOpen} onOpenChange={setIsModalOpen}>
|
<Drawer open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||||
<DrawerContent className="max-h-[90vh] flex flex-col">
|
<DrawerContent className="max-h-[90vh] flex flex-col">
|
||||||
<DrawerHeader className="border-b">
|
<DrawerHeader className="border-b">
|
||||||
<DrawerTitle>{selectedZone.name} {__('Settings')}</DrawerTitle>
|
<DrawerTitle>{selectedZone.name}</DrawerTitle>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{selectedZone.regions}
|
||||||
|
</p>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-6 min-h-0">
|
<div className="flex-1 overflow-y-auto px-4 py-6 min-h-0">
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<div className="space-y-6">
|
||||||
{__('Configure shipping zone and methods. For advanced settings, use WooCommerce.')}
|
{/* Zone Summary */}
|
||||||
</p>
|
<div className="bg-muted/50 rounded-lg p-4">
|
||||||
<div className="space-y-4">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">{__('Zone Information')}</h4>
|
<p className="text-sm font-medium">{__('Zone Order')}</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-muted-foreground">{__('Priority in shipping calculations')}</p>
|
||||||
{__('Name')}: {selectedZone.name}
|
</div>
|
||||||
</p>
|
<span className="text-2xl font-bold text-muted-foreground">{selectedZone.order}</span>
|
||||||
<p className="text-sm text-muted-foreground">
|
</div>
|
||||||
{__('Regions')}: {selectedZone.regions}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Shipping Methods */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">{__('Shipping Methods')}</h4>
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="space-y-2">
|
<h4 className="font-semibold">{__('Shipping Methods')}</h4>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{selectedZone.rates?.length} {selectedZone.rates?.length === 1 ? 'method' : 'methods'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
{selectedZone.rates?.map((rate: any) => (
|
{selectedZone.rates?.map((rate: any) => (
|
||||||
<div key={rate.id} className="border rounded-lg p-3">
|
<div key={rate.id} className="border rounded-lg p-3">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<span className="font-medium">{rate.name}</span>
|
<div className="flex-1">
|
||||||
<ToggleField
|
<div className="flex items-center gap-2 mb-1">
|
||||||
id={`drawer-${selectedZone.id}-${rate.instance_id}`}
|
<Truck className="h-4 w-4 text-muted-foreground" />
|
||||||
label=""
|
<span className="font-medium text-sm" dangerouslySetInnerHTML={{ __html: rate.name }} />
|
||||||
checked={rate.enabled}
|
</div>
|
||||||
onCheckedChange={(checked) => {
|
<div className="text-sm text-muted-foreground">
|
||||||
handleToggle(selectedZone.id, rate.instance_id, checked);
|
<span>{__('Cost')}:</span>{' '}
|
||||||
}}
|
<span className="font-semibold" dangerouslySetInnerHTML={{ __html: rate.price }} />
|
||||||
disabled={togglingMethod === `${selectedZone.id}-${rate.instance_id}`}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
<span className={`text-xs px-2 py-1 rounded-full whitespace-nowrap ${
|
||||||
|
rate.enabled
|
||||||
|
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||||
|
: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||||
|
}`}>
|
||||||
|
{rate.enabled ? __('Active') : __('Inactive')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{__('Price')}: <span dangerouslySetInnerHTML={{ __html: rate.price }} />
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -338,11 +365,11 @@ export default function ShippingPage() {
|
|||||||
>
|
>
|
||||||
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&zone_id=${selectedZone.id}`} target="_blank" rel="noopener noreferrer">
|
<a href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=shipping&zone_id=${selectedZone.id}`} target="_blank" rel="noopener noreferrer">
|
||||||
<ExternalLink className="h-4 w-4 mr-2" />
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
{__('Advanced Settings in WooCommerce')}
|
{__('Edit in WooCommerce')}
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setIsModalOpen(false)} className="w-full">
|
<Button onClick={() => setIsModalOpen(false)} className="w-full">
|
||||||
{__('Close')}
|
{__('Done')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user