fix: Modal refresh + improved accordion UX
Fixes: ✅ Modal now shows newly added methods immediately ✅ Accordion chevron on right (standard pattern) ✅ Remove button moved to content area Changes: 1. Added useEffect to sync selectedZone with zones data - Modal now updates when methods are added/deleted 2. Restructured accordion: Before: [Truck Icon] Name/Price [Chevron] [Delete] After: [Truck Icon] Name/Price [Chevron →] 3. Button layout in expanded content: [Remove] | [Cancel] [Save] Benefits: ✅ Clearer visual hierarchy ✅ Remove action grouped with other actions ✅ Standard accordion pattern (chevron on right) ✅ Better mobile UX (no accidental deletes) Next: Research shipping addon integration patterns
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
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';
|
||||||
@@ -53,6 +53,16 @@ export default function ShippingPage() {
|
|||||||
enabled: showAddMethod,
|
enabled: showAddMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sync selectedZone with zones data when it changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedZone && zones.length > 0) {
|
||||||
|
const updatedZone = zones.find((z: any) => z.id === selectedZone.id);
|
||||||
|
if (updatedZone) {
|
||||||
|
setSelectedZone(updatedZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [zones]);
|
||||||
|
|
||||||
// Toggle shipping method mutation
|
// Toggle shipping method mutation
|
||||||
const toggleMutation = useMutation({
|
const toggleMutation = useMutation({
|
||||||
mutationFn: async ({ zoneId, instanceId, enabled }: { zoneId: number; instanceId: number; enabled: boolean }) => {
|
mutationFn: async ({ zoneId, instanceId, enabled }: { zoneId: number; instanceId: number; enabled: boolean }) => {
|
||||||
@@ -334,8 +344,7 @@ export default function ShippingPage() {
|
|||||||
}}>
|
}}>
|
||||||
{selectedZone.rates?.map((rate: any) => (
|
{selectedZone.rates?.map((rate: any) => (
|
||||||
<AccordionItem key={rate.id} value={String(rate.instance_id)} className="border rounded-lg mb-2">
|
<AccordionItem key={rate.id} value={String(rate.instance_id)} className="border rounded-lg mb-2">
|
||||||
<div className="flex items-center justify-between pr-4">
|
<AccordionTrigger className="hover:no-underline px-4">
|
||||||
<AccordionTrigger className="flex-1 hover:no-underline px-4">
|
|
||||||
<div className="flex items-center gap-3 flex-1">
|
<div className="flex items-center gap-3 flex-1">
|
||||||
<Truck className="h-4 w-4 text-muted-foreground" />
|
<Truck className="h-4 w-4 text-muted-foreground" />
|
||||||
<div className="flex-1 text-left">
|
<div className="flex-1 text-left">
|
||||||
@@ -353,18 +362,6 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleDeleteMethod(rate.instance_id);
|
|
||||||
}}
|
|
||||||
disabled={deleteMethodMutation.isPending}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<AccordionContent className="px-4 pb-4">
|
<AccordionContent className="px-4 pb-4">
|
||||||
{methodSettings[rate.instance_id] ? (
|
{methodSettings[rate.instance_id] ? (
|
||||||
<div className="space-y-4 pt-2">
|
<div className="space-y-4 pt-2">
|
||||||
@@ -422,7 +419,20 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-2">
|
<div className="flex justify-between gap-2 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteMethod(rate.instance_id);
|
||||||
|
setExpandedMethod('');
|
||||||
|
}}
|
||||||
|
disabled={deleteMethodMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4 mr-1" />
|
||||||
|
{__('Remove')}
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -454,6 +464,7 @@ export default function ShippingPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center py-4">
|
<div className="flex items-center justify-center py-4">
|
||||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||||
@@ -512,8 +523,7 @@ export default function ShippingPage() {
|
|||||||
}}>
|
}}>
|
||||||
{selectedZone.rates?.map((rate: any) => (
|
{selectedZone.rates?.map((rate: any) => (
|
||||||
<AccordionItem key={rate.id} value={String(rate.instance_id)} className="border rounded-lg mb-2">
|
<AccordionItem key={rate.id} value={String(rate.instance_id)} className="border rounded-lg mb-2">
|
||||||
<div className="flex items-center justify-between pr-2">
|
<AccordionTrigger className="hover:no-underline px-3 py-2">
|
||||||
<AccordionTrigger className="flex-1 hover:no-underline px-3 py-2">
|
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<Truck className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
<Truck className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
<div className="flex-1 text-left min-w-0">
|
<div className="flex-1 text-left min-w-0">
|
||||||
@@ -531,19 +541,6 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-7 w-7 p-0"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleDeleteMethod(rate.instance_id);
|
|
||||||
}}
|
|
||||||
disabled={deleteMethodMutation.isPending}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3 w-3 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<AccordionContent className="px-3 pb-3">
|
<AccordionContent className="px-3 pb-3">
|
||||||
{methodSettings[rate.instance_id] ? (
|
{methodSettings[rate.instance_id] ? (
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
@@ -592,7 +589,20 @@ export default function ShippingPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-1">
|
<div className="flex justify-between gap-2 pt-1">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteMethod(rate.instance_id);
|
||||||
|
setExpandedMethod('');
|
||||||
|
}}
|
||||||
|
disabled={deleteMethodMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3 mr-1" />
|
||||||
|
{__('Remove')}
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -624,6 +634,7 @@ export default function ShippingPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center py-3">
|
<div className="flex items-center justify-center py-3">
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||||
|
|||||||
Reference in New Issue
Block a user