From a00ffedc4147eaa39a0d7fc867124432f76e7904 Mon Sep 17 00:00:00 2001 From: dwindown Date: Mon, 10 Nov 2025 18:19:25 +0700 Subject: [PATCH] fix(orders): Prevent premature shipping rate fetching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Issues Fixed: ### 1. Shipping rates fetched on page load ✅ **Problem:** - Open New Order form → Shipping already calculated - Using cached/legacy values - Should wait for address to be filled **Solution:** Added address completeness check: ```ts const isShippingAddressComplete = useMemo(() => { if (!shippingData.country) return false; if (!shippingData.city) return false; // If country has states, require state const countryStates = states[shippingData.country]; if (countryStates && Object.keys(countryStates).length > 0) { if (!shippingData.state) return false; } return true; }, [shippingData.country, shippingData.state, shippingData.city]); ``` Query only enabled when address is complete: ```ts enabled: isShippingAddressComplete && items.length > 0 ``` ### 2. Unnecessary refetches ✅ **Problem:** - Every keystroke triggered refetch - staleTime: 0 meant always refetch **Solution:** ```ts staleTime: 5 * 60 * 1000 // Cache for 5 minutes ``` Query key still includes all address fields, so: - Change country → Refetch (key changed) - Change state → Refetch (key changed) - Change city → Refetch (key changed) - Change postcode → Refetch (key changed) - Same values → Use cache (key unchanged) ### 3. Order preview fetching too early ✅ **Problem:** - Preview calculated before shipping method selected - Incomplete data **Solution:** ```ts enabled: items.length > 0 && !!bCountry && !!shippingMethod ``` ## New Behavior: ### On Page Load: - ❌ No shipping fetch - ❌ No preview fetch - ✅ Clean state ### User Fills Address: 1. Enter country → Not enough 2. Enter state → Not enough 3. Enter city → ✅ **Fetch shipping rates** 4. Rates appear → First auto-selected 5. ✅ **Fetch order preview** (has method now) ### User Changes Address: 1. Change Jakarta → Bandung 2. Query key changes (city changed) 3. ✅ **Refetch shipping rates** 4. New rates appear → First auto-selected 5. ✅ **Refetch order preview** ### User Types in Same Field: 1. Type "Jak..." → "Jakarta" 2. Query key same (city still "Jakarta") 3. ❌ No refetch (use cache) 4. Efficient! ## Benefits: - ✅ No premature fetching - ✅ No unnecessary API calls - ✅ Smart caching (5 min) - ✅ Only refetch when address actually changes - ✅ Better UX and performance --- .../src/routes/Orders/partials/OrderForm.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/admin-spa/src/routes/Orders/partials/OrderForm.tsx b/admin-spa/src/routes/Orders/partials/OrderForm.tsx index 72bc72f..f6fd754 100644 --- a/admin-spa/src/routes/Orders/partials/OrderForm.tsx +++ b/admin-spa/src/routes/Orders/partials/OrderForm.tsx @@ -185,25 +185,36 @@ export default function OrderForm({ enabled: items.length > 0, }); + // Check if shipping address is complete enough to calculate rates + const isShippingAddressComplete = React.useMemo(() => { + // Need at minimum: country, state (if applicable), city + if (!shippingData.country) return false; + if (!shippingData.city) return false; + // If country has states, require state + const countryStates = states[shippingData.country]; + if (countryStates && Object.keys(countryStates).length > 0 && !shippingData.state) { + return false; + } + return true; + }, [shippingData.country, shippingData.state, shippingData.city, states]); + // Calculate shipping rates dynamically const { data: shippingRates, isLoading: shippingLoading } = useQuery({ queryKey: ['shipping-rates', items.map(i => ({ product_id: i.product_id, qty: i.qty })), shippingData.country, shippingData.state, shippingData.city, shippingData.postcode, shippingData.address_1], queryFn: async () => { - if (!shippingData.country) return null; return api.post('/shipping/calculate', { items: items.map(i => ({ product_id: i.product_id, qty: i.qty })), shipping: shippingData, }); }, - enabled: !!shippingData.country && items.length > 0, - staleTime: 0, // Always refetch when query key changes + enabled: isShippingAddressComplete && items.length > 0, + staleTime: 5 * 60 * 1000, // Cache for 5 minutes - only refetch if query key changes }); // Calculate order preview with taxes const { data: orderPreview, isLoading: previewLoading } = useQuery({ queryKey: ['order-preview', items.map(i => ({ product_id: i.product_id, qty: i.qty })), bCountry, bState, bPost, bCity, shippingData.country, shippingData.state, shippingData.city, shippingData.postcode, shippingMethod, validatedCoupons.map(c => c.code)], queryFn: async () => { - if (items.length === 0) return null; return api.post('/orders/preview', { items: items.map(i => ({ product_id: i.product_id, qty: i.qty })), billing: { country: bCountry, state: bState, postcode: bPost, city: bCity }, @@ -212,8 +223,8 @@ export default function OrderForm({ coupons: validatedCoupons.map(c => c.code), }); }, - enabled: items.length > 0 && !!bCountry, - staleTime: 0, // Always refetch when query key changes + enabled: items.length > 0 && !!bCountry && !!shippingMethod, + staleTime: 5 * 60 * 1000, // Cache for 5 minutes - only refetch if query key changes }); // --- Product search for Add Item ---