From da2ebfc646d9c83516da35bc0fc8fe5ecbbdd8d5 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:29:56 -0500 Subject: [PATCH] Donations: show modal when we can't apply the user's badge Co-authored-by: yash-signal --- _locales/en/messages.json | 8 ++++++++ ts/components/DonationErrorModal.tsx | 5 +++++ ts/components/PreferencesDonations.tsx | 14 ++++++++++---- ts/components/ToastManager.stories.tsx | 4 ---- ts/components/ToastManager.tsx | 10 ---------- ts/types/Donations.ts | 2 ++ ts/types/Toast.tsx | 2 -- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9ce23e6ec5..0d8e58a8e2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -9070,6 +9070,14 @@ "messageformat": "Donation succeeded, but we could not update your badge settings", "description": "Toast shown when donation completes successfully but badge application fails" }, + "icu:Donations__BadgeApplicationFailed__Title": { + "messageformat": "Donation succeeded, but we could not update your badge settings", + "description": "Modal title shown when donation completes successfully but badge application fails" + }, + "icu:Donations__BadgeApplicationFailed__Description": { + "messageformat": "To update your badge settings on your mobile device, go to Settings → Donate to Signal → Badges.", + "description": "Modal body text shown when badge application fails after successful donation" + }, "icu:Donations__DonationInterrupted": { "messageformat": "Donation interrupted", "description": "Title of the dialog shown when starting up if a donation had been started, and we've saved payment information, but the charge hasn't happened yet" diff --git a/ts/components/DonationErrorModal.tsx b/ts/components/DonationErrorModal.tsx index 624aa0f5a2..7eb71c4748 100644 --- a/ts/components/DonationErrorModal.tsx +++ b/ts/components/DonationErrorModal.tsx @@ -45,6 +45,11 @@ export function DonationErrorModal(props: PropsType): JSX.Element { body = i18n('icu:Donations__TimedOut__Description'); break; } + case donationErrorTypeSchema.Enum.BadgeApplicationFailed: { + title = i18n('icu:Donations__BadgeApplicationFailed__Title'); + body = i18n('icu:Donations__BadgeApplicationFailed__Description'); + break; + } default: throw missingCaseError(props.errorType); diff --git a/ts/components/PreferencesDonations.tsx b/ts/components/PreferencesDonations.tsx index 4b8fc60c3e..4176d3ab01 100644 --- a/ts/components/PreferencesDonations.tsx +++ b/ts/components/PreferencesDonations.tsx @@ -628,6 +628,12 @@ export function PreferencesDonations({ i18n={i18n} onClose={() => { setIsSubmitted(false); + if ( + workflow?.type === 'DONE' && + lastError === donationErrorTypeSchema.Enum.BadgeApplicationFailed + ) { + clearWorkflow(); + } updateLastError(undefined); }} /> @@ -675,13 +681,13 @@ export function PreferencesDonations({ badge={donationBadge} applyDonationBadge={applyDonationBadge} onClose={(error?: Error) => { - clearWorkflow(); if (error) { log.error('Badge application failed:', error.message); - showToast({ - toastType: ToastType.DonationCompletedAndBadgeApplicationFailed, - }); + updateLastError( + donationErrorTypeSchema.Enum.BadgeApplicationFailed + ); } else { + clearWorkflow(); showToast({ toastType: ToastType.DonationCompleted, }); diff --git a/ts/components/ToastManager.stories.tsx b/ts/components/ToastManager.stories.tsx index fc94bd604e..814eb18925 100644 --- a/ts/components/ToastManager.stories.tsx +++ b/ts/components/ToastManager.stories.tsx @@ -41,10 +41,6 @@ function getToast(toastType: ToastType): AnyToast { }; case ToastType.Blocked: return { toastType: ToastType.Blocked }; - case ToastType.DonationCompletedAndBadgeApplicationFailed: - return { - toastType: ToastType.DonationCompletedAndBadgeApplicationFailed, - }; case ToastType.BlockedGroup: return { toastType: ToastType.BlockedGroup }; case ToastType.CallHistoryCleared: diff --git a/ts/components/ToastManager.tsx b/ts/components/ToastManager.tsx index 6f68e4295a..62731d1bd6 100644 --- a/ts/components/ToastManager.tsx +++ b/ts/components/ToastManager.tsx @@ -118,16 +118,6 @@ export function renderToast({ ); } - if (toastType === ToastType.DonationCompletedAndBadgeApplicationFailed) { - return ( - - {i18n( - 'icu:Donations__Toast__DonationCompletedAndBadgeApplicationFailed' - )} - - ); - } - if (toastType === ToastType.Blocked) { return {i18n('icu:unblockToSend')}; } diff --git a/ts/types/Donations.ts b/ts/types/Donations.ts index 954bd375a8..6c6ec8ef53 100644 --- a/ts/types/Donations.ts +++ b/ts/types/Donations.ts @@ -26,6 +26,8 @@ export const donationErrorTypeSchema = z.enum([ 'PaymentDeclined', // When it's been too long since the last step of the donation, and card wasn't charged 'TimedOut', + // When donation succeeds but badge application fails + 'BadgeApplicationFailed', ]); export type DonationErrorType = z.infer; diff --git a/ts/types/Toast.tsx b/ts/types/Toast.tsx index 6cdd5fca80..02fe916fd5 100644 --- a/ts/types/Toast.tsx +++ b/ts/types/Toast.tsx @@ -8,7 +8,6 @@ export enum ToastType { AlreadyRequestedToJoin = 'AlreadyRequestedToJoin', AttachmentDownloadFailed = 'AttachmentDownloadFailed', AttachmentDownloadStillInProgress = 'AttachmentDownloadStillInProgress', - DonationCompletedAndBadgeApplicationFailed = 'DonationCompletedAndBadgeApplicationFailed', Blocked = 'Blocked', BlockedGroup = 'BlockedGroup', CallHistoryCleared = 'CallHistoryCleared', @@ -104,7 +103,6 @@ export type AnyToast = toastType: ToastType.AttachmentDownloadStillInProgress; parameters: { count: number }; } - | { toastType: ToastType.DonationCompletedAndBadgeApplicationFailed } | { toastType: ToastType.Blocked } | { toastType: ToastType.BlockedGroup } | { toastType: ToastType.CallHistoryCleared }