Skip to content

Commit d1e34b1

Browse files
committed
fix for false positives error toast
Signed-off-by: d.phan <d.phan@salesforce.com>
1 parent d9bb37f commit d1e34b1

File tree

5 files changed

+57
-14
lines changed

5 files changed

+57
-14
lines changed

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default function ShippingAddress(props) {
5959
const selectedShippingAddress = deliveryShipments[0]?.shippingAddress
6060
const targetDeliveryShipmentId = deliveryShipments[0]?.shipmentId || 'me'
6161
const isAddressFilled = selectedShippingAddress?.address1 && selectedShippingAddress?.city
62-
const {step, STEPS, goToStep, goToNextStep, contactPhone} = useCheckout()
62+
const {step, STEPS, goToStep, goToNextStep, contactPhone, setConsolidationLock} = useCheckout()
6363
const createCustomerAddress = useShopperCustomersMutation('createCustomerAddress')
6464
const updateCustomerAddress = useShopperCustomersMutation('updateCustomerAddress')
6565
const updateShippingAddressForShipment = useShopperBasketsMutation(
@@ -115,6 +115,14 @@ export default function ShippingAddress(props) {
115115
const targetShipmentId = targetShipment?.shipmentId || DEFAULT_SHIPMENT_ID
116116
let basketAfterItemMoves = null
117117

118+
// Do not advance the step while basket mutations are in flight
119+
const willConsolidate = deliveryItems.some(
120+
(item) => item.shipmentId !== targetShipmentId
121+
)
122+
if (willConsolidate) {
123+
setConsolidationLock(true)
124+
}
125+
118126
await updateShippingAddressForShipment.mutateAsync({
119127
parameters: {
120128
basketId: basket.basketId,
@@ -173,6 +181,7 @@ export default function ShippingAddress(props) {
173181
}
174182
// Remove any empty shipments. Use updated basket if available
175183
await removeEmptyShipments(basketAfterItemMoves || basket)
184+
setConsolidationLock(false)
176185

177186
// For registered shoppers: if an existing shipping method is still valid for the new address,
178187
// skip the Shipping Options step and go straight to Payment.
@@ -199,6 +208,7 @@ export default function ShippingAddress(props) {
199208
console.error('Error submitting shipping address:', error)
200209
}
201210
} finally {
211+
setConsolidationLock(false)
202212
setIsManualSubmitLoading(false)
203213
}
204214
}

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.test.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ jest.mock(
116116
},
117117
goToStep: mockGoToStep,
118118
goToNextStep: mockGoToNextStep,
119-
contactPhone: '(727) 555-0000'
119+
contactPhone: '(727) 555-0000',
120+
setConsolidationLock: jest.fn()
120121
})
121122
})
122123
)
@@ -524,7 +525,8 @@ describe('ShippingAddress Component', () => {
524525
},
525526
goToStep: mockGoToStep,
526527
goToNextStep: mockGoToNextStep,
527-
contactPhone: '(727) 555-9999'
528+
contactPhone: '(727) 555-9999',
529+
setConsolidationLock: jest.fn()
528530
})
529531
})
530532
)
@@ -820,7 +822,8 @@ describe('ShippingAddress Component', () => {
820822
},
821823
goToStep: mockGoToStep,
822824
goToNextStep: mockGoToNextStep,
823-
contactPhone: '(727) 555-0000'
825+
contactPhone: '(727) 555-0000',
826+
setConsolidationLock: jest.fn()
824827
})
825828
})
826829
)
@@ -1052,7 +1055,8 @@ describe('ShippingAddress Component', () => {
10521055
},
10531056
goToStep: mockGoToStep,
10541057
goToNextStep: mockGoToNextStep,
1055-
contactPhone: '(727) 555-0000'
1058+
contactPhone: '(727) 555-0000',
1059+
setConsolidationLock: jest.fn()
10561060
})
10571061
})
10581062
)
@@ -1213,7 +1217,8 @@ describe('ShippingAddress Component', () => {
12131217
},
12141218
goToStep: mockGoToStep,
12151219
goToNextStep: mockGoToNextStep,
1216-
contactPhone: '(727) 555-0000'
1220+
contactPhone: '(727) 555-0000',
1221+
setConsolidationLock: jest.fn()
12171222
})
12181223
})
12191224
)

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export default function ShippingOptions() {
5353
const [noMethodsToastShown, setNoMethodsToastShown] = useState(false)
5454
const [shipmentIdsWithNoMethods, setShipmentIdsWithNoMethods] = useState(() => new Set())
5555
const [resolvedShipmentIds, setResolvedShipmentIds] = useState(() => new Set())
56+
const [singleShipmentFetchCompleted, setSingleShipmentFetchCompleted] = useState(false)
57+
const prevIsFetchingRef = useRef(false)
5658

5759
const productItems = basket?.productItems || []
5860
const deliveryShipments =
@@ -93,7 +95,9 @@ export default function ShippingOptions() {
9395
const wasMulti = prevHasMultipleRef.current
9496
prevHasMultipleRef.current = hasMultipleDeliveryShipments
9597

96-
if (wasMulti && !hasMultipleDeliveryShipments) return
98+
if (wasMulti && !hasMultipleDeliveryShipments) {
99+
return
100+
}
97101
if (step === STEPS.SHIPPING_OPTIONS) return
98102

99103
setNoMethodsToastShown(false)
@@ -160,9 +164,24 @@ export default function ShippingOptions() {
160164
noShippingMethodsToast
161165
])
162166

163-
// Single shipment: show toast when methods data is available and has no delivery methods
167+
// Prevent false positives from stale cached data for single-shipment error toast
168+
useEffect(() => {
169+
setSingleShipmentFetchCompleted(false)
170+
prevIsFetchingRef.current = false
171+
}, [step, hasMultipleDeliveryShipments])
172+
173+
// Track the single-shipment query's fetch lifecycle
174+
useEffect(() => {
175+
const wasFetching = prevIsFetchingRef.current
176+
prevIsFetchingRef.current = isShippingMethodsFetching
177+
if (wasFetching && !isShippingMethodsFetching) {
178+
setSingleShipmentFetchCompleted(true)
179+
}
180+
}, [isShippingMethodsFetching])
181+
164182
const singleShipmentNoMethods =
165183
!hasMultipleDeliveryShipments &&
184+
singleShipmentFetchCompleted &&
166185
step === STEPS.SHIPPING_OPTIONS &&
167186
!isShippingMethodsFetching &&
168187
shippingMethods != null &&

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,12 @@ describe('ShippingOptions Component', () => {
223223
})
224224

225225
test('shows error toast and hides controls when no shipping methods are available', async () => {
226-
const emptyMethodsPayload = {
227-
data: {applicableShippingMethods: [], defaultShippingMethodId: 'std'}
228-
}
229-
commerceSdk.useShippingMethodsForShipment.mockImplementation(() => emptyMethodsPayload)
226+
commerceSdk.useShippingMethodsForShipment
227+
.mockReturnValueOnce({data: null, isFetching: true})
228+
.mockReturnValue({
229+
data: {applicableShippingMethods: [], defaultShippingMethodId: 'std'},
230+
isFetching: false
231+
})
230232
mockUpdateShippingMethod.mutateAsync.mockResolvedValue({})
231233

232234
renderWithProviders(<ShippingOptions />)

packages/template-retail-react-app/app/pages/checkout-one-click/util/checkout-context.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: BSD-3-Clause
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
import React, {useEffect, useState} from 'react'
7+
import React, {useEffect, useRef, useState} from 'react'
88
import PropTypes from 'prop-types'
99
import useEinstein from '@salesforce/retail-react-app/app/hooks/use-einstein'
1010
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
@@ -39,6 +39,7 @@ export const CheckoutProvider = ({children}) => {
3939
const einstein = useEinstein()
4040
const [step, setStep] = useState()
4141
const [contactPhone, setContactPhone] = useState('')
42+
const consolidationLockRef = useRef(false)
4243

4344
const CHECKOUT_STEPS_LIST = [
4445
'CONTACT_INFO',
@@ -56,6 +57,7 @@ export const CheckoutProvider = ({children}) => {
5657
if (!customer || !basket) {
5758
return
5859
}
60+
if (consolidationLockRef.current) return
5961

6062
let step = STEPS.REVIEW_ORDER
6163

@@ -134,13 +136,18 @@ export const CheckoutProvider = ({children}) => {
134136

135137
const goToStep = (step) => setStep(step)
136138

139+
const setConsolidationLock = (locked) => {
140+
consolidationLockRef.current = locked
141+
}
142+
137143
const value = {
138144
step,
139145
STEPS,
140146
goToNextStep,
141147
goToStep,
142148
contactPhone,
143-
setContactPhone
149+
setContactPhone,
150+
setConsolidationLock
144151
}
145152

146153
return <CheckoutContext.Provider value={value}>{children}</CheckoutContext.Provider>

0 commit comments

Comments
 (0)