feat(predict): Bottom Sheet - Try Again Toast for failed Payments cp-7.77.0#30167
Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 21e91db. Configure here.
| ToastService.showToast({ | ||
| variant: ToastVariants.Icon, | ||
| labelOptions: [ | ||
| { | ||
| label: strings('predict.order.prediction_failed'), | ||
| isBold: true, | ||
| }, | ||
| ], |
There was a problem hiding this comment.
Why call showToast directly here, rather than catching the failure as a PredictController event and trigger the toast in usePredictToastRegistrations?
There was a problem hiding this comment.
I started with that approach (you can see the legacy fallback for it still in. I moved to a state-based trigger here because of a timing race in the event path:
The controller publishes 'failed' only when isBackgroundOrder is true
eg: when state.activeBuyOrders[address].transactionId differs from params.transactionId. That field is cleared by the slip's unmount cleanup (clearActiveOrderTransactionId). On the happy path the slip animates closed → cleanup runs → transactionId is undefined → eventually deposit confirms → isBackgroundOrder is true → event publishes.
But on faster networks (or when the user reopens the slip mid-flight) the deposit can confirm before the cleanup runs, so the state still has transactionId set → isBackgroundOrder is false → no 'failed' event → no toast. I hit this on device while testing PWAT with ETH, the failure was completely silent.
A state-based trigger sidesteps this entirely: we react to activeOrder.error transitioning falsy → truthy, which is a reliable React signal regardless of timing. The provider already subscribes to usePredictActiveOrder() for other reasons, so reading error here is essentially free.
The event-based handler is still wired up as a fallback (suppressed when the provider is mounted to avoid double-firing), so the legacy non-bottom-sheet flow keeps working unchanged.
brianacnguyen
left a comment
There was a problem hiding this comment.
Toast change is not needed
- description is already optional, adding a compact version is not needed
- the CTA button is supposed to go below the texts, not on the same line. this is a new design that's not approved by design yet https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/branch/8KPwHulIorYFJiTWK8MapC/%F0%9F%A6%8A-MMDS-Components?m=auto&node-id=4571-6292&t=VWfVjOSHqWZhEqX4-1
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
Tag selection rationale:
No other areas are affected - no changes to core infrastructure, navigation, shared components, or other feature areas. Performance Test Selection: |
|
no longer making any DS changes




PR: fix(predict): replace bet slip auto-reopen with auto-dismissing Retry toast
Description
Problem
When paying with any token (PWAT) for a Predict bet, the bet slip would pop back up unexpectedly while the deposit was still in flight. The "Prediction in progress" loading toast would appear, then the slip would re-open over it (often before the deposit even confirmed on-chain), and then stay stuck open after the order completed. This was confusing and felt broken.
Root cause: the auto-reopen
useEffectinPredictPreviewSheetContext— added in #29184 to surface inline error banners after background failures — fired on any transientactiveOrder.errorvalue. The PredictController briefly setserrorduring its internal retry paths (PredictController.ts:1277and:2300) even on flows that ultimately succeed, so the slip popped back up over toasts that were still mid-loading. The reopened slip didn't close onSUCCESSeither, because the freshly-mountedusePredictBuyActionsinstance hasdidInitiateOrderRef = falseand skips the SUCCESS pop.Solution
Replaced the auto-reopen with a user-initiated reopen via an auto-dismissing Retry toast. The toast lives ~3s; tapping Retry within that window reopens the slip with the original market context and the inline
order_failedbanner. If the user does nothing, the toast fades out andactiveOrder.erroris automatically cleared so the next slip open is a clean state (no stale banner flash).sequenceDiagram participant User participant Slip as Bet slip participant Ctrl as PredictController participant Toast User->>Slip: Confirm bet (PWAT) Ctrl->>Ctrl: state -> DEPOSITING Slip->>Slip: animate close Toast->>User: "Prediction in progress" loading toast alt Order succeeds Ctrl-->>Toast: 'confirmed' event Toast->>User: "Prediction placed" success toast else Order fails Ctrl-->>Toast: state.error transitions truthy Toast->>User: auto-dismissing "Failed to place prediction" + Retry (inline) opt User taps Retry within ~3s Toast->>Toast: cancel auto-clear timer Toast->>Slip: openBuySheet(lastBuyParams) Slip->>User: bet slip reopens with inline order_failed banner end opt User does nothing Toast->>Toast: auto-dismisses (~3s) Toast->>Ctrl: clearOrderError() end endKey changes
useEffectanddismissedWithErrorReffromPredictPreviewSheetContext.tsx.ToastService.showToast(...)wheneveractiveOrder.errortransitions falsy → truthy AND the bottom-sheet flow is enabled AND the slip is closed AND we have remembered buy params from a previous open. This mirrors the original auto-reopen condition but surfaces a toast instead of taking over the screen. Using state (not the controller's'failed'event) avoids the timing race onisBackgroundOrderthat the event-based path is subject to.isPredictSheetProviderMounted()so the legacy event-based toast inusePredictToastRegistrations.tsxcan suppress itself when the provider is mounted (avoids a duplicate failure toast).clearErrorTimerRef3-second timer that callsclearOrderError()after the toast auto-dismisses, so an unhandled failure doesn't leave a staleactiveOrder.errorfor the next slip open. The timer is cancelled when the user taps Retry (so the reopened slip can show theorder_failedbanner) and on provider unmount (so we don'tsetStateafter teardown).order_failedbanner handles the per-slip error UX (preserves PR feat(predict): bPredict Bottom Sheet Errors PRED-836 #29184's intent).Toast shape
ToastVariants.Icon[avatar icon] [bold label + description] [Retry]on a single row.iconName:IconName.ErroriconColor:theme.colors.error.default(red — see "known limitations" below)backgroundColor:theme.colors.error.muted(soft red wash, matching the standard error-avatar look used inNetworkConnectionBanner,ErrorBoundary,AlertModal, etc.)hasNoTimeout: false(auto-dismisses on platform default ~2.75s visibility + 0.25s exit)closeButtonOptions:{ label: 'Retry', variant: ButtonVariants.Link, onPress }— the inline Retry actionLocale keys
All existing — no new strings:
predict.order.prediction_failed— toast titlepredict.order.order_failed_generic— toast descriptionpredict.order.retry— Retry action labelOut of scope (intentionally)
Toastcomponent is unchanged on this PR (an earlier draft added an opt-incompactprop, which has since been reverted in favor of the existingcloseButtonOptionsAPI).usePredictToastRegistrations.tsxcontinue to use the existingaccent04.normalindigo background — only the new bottom-sheet failure toast was switched to the conventionalerror.mutedred wash. Harmonizing the rest is a follow-up.Known limitations
error.svgasset (app/component-library/components/Icons/Icon/assets/error.svg) has hardcodedfill="none"on the root andfill="#121314"on the path, so the small Error glyph paints near-black regardless of theiconColorwe pass. This affects everyIconName.Errorcallsite in the app, not just ours. Filed for the design-system-engineers team. Theerror.mutedsoft red background masks the issue here visually (dark glyph on light red wash reads correctly as "error"), but the glyph itself only becomes red once the SVG asset is fixed upstream.Changelog
CHANGELOG entry: Fixed an issue where the Predict bet slip would unexpectedly reopen during a pay-with-any-token deposit and remain open after the order completed. Background failures now surface a "Failed to place prediction" toast with a Retry action that reopens the slip with the order-failed banner; if the user doesn't tap Retry, the toast auto-dismisses and the order error is cleared automatically.
Related issues
Fixes:
Jira Ticket: https://consensyssoftware.atlassian.net/browse/PRED-883
Manual testing steps
Screenshots/Recordings
Before
After
newErrorToastV2.mov
Pre-merge author checklist
PredictPreviewSheetContext.test.tsx(28 tests, including a dedicatedfailure toast (state-based trigger)suite and afailure toast auto-clear timersuite usingjest.useFakeTimers()) and updatedusePredictToastRegistrations.test.tsxfor the suppression branch. Coverage on touched files: rerunyarn jest --coverageafter final cleanup and update.clearErrorTimerRefrationale, and the state-based trigger comment block.Performance checks (if applicable)
Pre-merge reviewer checklist
Note
Medium Risk
Changes Predict order-failure UX from auto-reopening the bottom sheet to a state-driven toast with retry and an auto-clear timer, which could affect error handling timing and user flows. Also adjusts toast suppression logic to avoid duplicates when the provider is mounted.
Overview
Predict bottom-sheet order failures no longer auto-reopen the buy slip; instead
PredictPreviewSheetProviderwatchesactiveOrder.errortransitions and shows a non-persistent Retry toast (viaToastService) that reopens the slip with the last buy params only if the user taps it.Adds a ~3s auto-clear timer to call
clearOrderError()after the toast dismisses (cancelled on Retry and on provider unmount) to avoid stale inline error banners, and updatesusePredictToastRegistrationsto suppress its legacy'failed'toast when the provider is mounted.Tests were expanded/updated to cover the new toast trigger conditions, retry behavior, timer cancellation/cleanup, and to harden hook tests against leaked mounts/promises.
Reviewed by Cursor Bugbot for commit adcbad0. Bugbot is set up for automated code reviews on this repo. Configure here.