Skip to content

Commit 0ea8987

Browse files
authored
[SDK] Fix: Error message cleanup on UB embed (#6716)
1 parent 1b2caec commit 0ea8987

File tree

10 files changed

+161
-47
lines changed

10 files changed

+161
-47
lines changed

.changeset/moody-pants-play.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Miscellaneous PayEmbed error improvements:
6+
- Adds title and message to PayEmbed errors
7+
- Prevents propagating raw errors to the user when in purchase or transaction mode
8+
- Fixes bubble alignment on pulsing animation

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/Stepper.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ const pulseAnimation = keyframes`
7272
const PulsingDot = /* @__PURE__ */ StyledDiv(() => {
7373
return {
7474
background: "currentColor",
75-
width: "9px",
76-
height: "9px",
75+
width: "10px",
76+
height: "10px",
7777
borderRadius: "50%",
7878
'&[data-active="true"]': {
7979
animation: `${pulseAnimation} 1s infinite`,

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,14 @@ export function TransactionModeScreen(props: {
186186
) : activeAccount ? (
187187
<Container flex="column" gap="sm">
188188
{insufficientFunds && (
189-
<Text size="sm" color="danger" style={{ textAlign: "center" }}>
190-
Insufficient funds
191-
</Text>
189+
<div>
190+
<Text color="danger" size="xs" center multiline>
191+
Insufficient Funds
192+
</Text>
193+
<Text size="xs" center multiline>
194+
Select another token or pay with a debit card.
195+
</Text>
196+
</div>
192197
)}
193198
<Container
194199
flex="row"

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import {
1313
import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js";
1414
import { useBuyWithFiatQuote } from "../../../../../../core/hooks/pay/useBuyWithFiatQuote.js";
1515
import { PREFERRED_FIAT_PROVIDER_STORAGE_KEY } from "../../../../../../core/utils/storage.js";
16-
import {
17-
defaultMessage,
18-
getErrorMessage,
19-
} from "../../../../../utils/errors.js";
16+
import { getErrorMessage } from "../../../../../utils/errors.js";
2017
import {
2118
Drawer,
2219
DrawerOverlay,
@@ -180,7 +177,7 @@ export function FiatScreenContent(props: {
180177
)}
181178

182179
<Container flex="column" gap="sm">
183-
<Text size="sm">Pay with credit card</Text>
180+
<Text size="sm">Pay with a debit card</Text>
184181
<div>
185182
<PayWithCreditCard
186183
isLoading={fiatQuoteQuery.isLoading}
@@ -242,9 +239,14 @@ export function FiatScreenContent(props: {
242239
/>
243240
</Text>
244241
) : (
245-
<Text color="danger" size="sm" center multiline>
246-
{errorMsg.message || defaultMessage}
247-
</Text>
242+
<div>
243+
<Text color="danger" size="xs" center multiline>
244+
{errorMsg.title}
245+
</Text>
246+
<Text size="xs" center multiline>
247+
{errorMsg.message}
248+
</Text>
249+
</div>
248250
)}
249251
</div>
250252
)}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ConfirmationScreen.tsx

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { CrossCircledIcon } from "@radix-ui/react-icons";
2-
import { useState } from "react";
1+
import { useMemo, useState } from "react";
32
import { trackPayEvent } from "../../../../../../../analytics/track/pay.js";
43
import type { Chain } from "../../../../../../../chains/types.js";
54
import type { ThirdwebClient } from "../../../../../../../client/client.js";
@@ -13,7 +12,6 @@ import {
1312
waitForReceipt,
1413
} from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
1514
import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
16-
import { iconSize } from "../../../../../../core/design-system/index.js";
1715
import { Spacer } from "../../../../components/Spacer.js";
1816
import { Spinner } from "../../../../components/Spinner.js";
1917
import { StepBar } from "../../../../components/StepBar.js";
@@ -25,6 +23,7 @@ import { StyledDiv } from "../../../../design-system/elements.js";
2523
import type { ERC20OrNativeToken } from "../../nativeToken.js";
2624
import { Step } from "../Stepper.js";
2725
import type { PayerInfo } from "../types.js";
26+
import { ErrorText } from "./ErrorText.js";
2827
import { SwapSummary } from "./SwapSummary.js";
2928
import { addPendingTx } from "./pendingSwapTx.js";
3029

@@ -59,13 +58,46 @@ export function SwapConfirmationScreen(props: {
5958
const initialStep = needsApprovalStep ? "approval" : "swap";
6059

6160
const [step, setStep] = useState<"approval" | "swap">(initialStep);
61+
const [error, setError] = useState<string | undefined>();
6262
const [status, setStatus] = useState<
6363
"pending" | "success" | "error" | "idle"
6464
>("idle");
6565

6666
const receiver = props.quote.swapDetails.toAddress;
6767
const sender = props.quote.swapDetails.fromAddress;
6868

69+
const uiErrorMessage = useMemo(() => {
70+
if (step === "approval" && status === "error" && error) {
71+
if (error.toLowerCase().includes("user rejected")) {
72+
return {
73+
title: "Failed to Approve",
74+
message: "Your wallet rejected the approval request.",
75+
};
76+
}
77+
return {
78+
title: "Failed to Approve",
79+
message:
80+
"Your wallet failed to approve the transaction for an unknown reason. Please try again or contact support.",
81+
};
82+
}
83+
84+
if (step === "swap" && status === "error" && error) {
85+
if (error.toLowerCase().includes("user rejected")) {
86+
return {
87+
title: "Failed to Confirm",
88+
message: "Your wallet rejected the confirmation request.",
89+
};
90+
}
91+
return {
92+
title: "Failed to Confirm",
93+
message:
94+
"Your wallet failed to confirm the transaction for an unknown reason. Please try again or contact support.",
95+
};
96+
}
97+
98+
return undefined;
99+
}, [error, step, status]);
100+
69101
return (
70102
<Container p="lg">
71103
<ModalHeader title={props.title} onBack={props.onBack} />
@@ -128,15 +160,12 @@ export function SwapConfirmationScreen(props: {
128160
</>
129161
)}
130162

131-
{status === "error" && (
163+
{uiErrorMessage && (
132164
<>
133-
<Container flex="row" gap="xs" center="both" color="danger">
134-
<CrossCircledIcon width={iconSize.sm} height={iconSize.sm} />
135-
<Text color="danger" size="sm">
136-
{step === "approval" ? "Failed to Approve" : "Failed to Confirm"}
137-
</Text>
138-
</Container>
139-
165+
<ErrorText
166+
title={uiErrorMessage.title}
167+
message={uiErrorMessage.message}
168+
/>
140169
<Spacer y="md" />
141170
</>
142171
)}
@@ -224,6 +253,7 @@ export function SwapConfirmationScreen(props: {
224253
setStatus("idle");
225254
} catch (e) {
226255
console.error(e);
256+
setError((e as Error).message);
227257
setStatus("error");
228258
}
229259
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { CrossCircledIcon } from "@radix-ui/react-icons";
2+
import { iconSize } from "../../../../../../core/design-system/index.js";
3+
import { Container } from "../../../../components/basic.js";
4+
import { Text } from "../../../../components/text.js";
5+
6+
export function ErrorText(props: {
7+
title: string;
8+
message: string;
9+
}) {
10+
return (
11+
<Container gap="xxs" flex="column">
12+
<Container flex="row" gap="xxs" center="both" color="danger">
13+
<CrossCircledIcon width={iconSize.sm} height={iconSize.sm} />
14+
<Text color="danger" size="sm">
15+
{props.title}
16+
</Text>
17+
</Container>
18+
<Text center size="xs">
19+
{props.message}
20+
</Text>
21+
</Container>
22+
);
23+
}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import type { Account } from "../../../../../../../wallets/interfaces/wallet.js"
1212
import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js";
1313
import { useWalletBalance } from "../../../../../../core/hooks/others/useWalletBalance.js";
1414
import { useBuyWithCryptoQuote } from "../../../../../../core/hooks/pay/useBuyWithCryptoQuote.js";
15-
import {
16-
defaultMessage,
17-
getErrorMessage,
18-
} from "../../../../../utils/errors.js";
15+
import { getErrorMessage } from "../../../../../utils/errors.js";
1916
import type { PayEmbedConnectOptions } from "../../../../PayEmbed.js";
2017
import {
2118
Drawer,
@@ -301,17 +298,25 @@ export function SwapScreenContent(props: {
301298
/>
302299
</Text>
303300
) : (
304-
<Text color="danger" size="xs" center multiline>
305-
{errorMsg.message || defaultMessage}
306-
</Text>
301+
<div>
302+
<Text color="danger" size="xs" center multiline>
303+
{errorMsg.title}
304+
</Text>
305+
<Text size="xs" center multiline>
306+
{errorMsg.message}
307+
</Text>
308+
</div>
307309
)}
308310
</div>
309311
)}
310312

311313
{!errorMsg && isNotEnoughBalance && (
312314
<div>
313315
<Text color="danger" size="xs" center multiline>
314-
Insufficient funds
316+
Insufficient Funds
317+
</Text>
318+
<Text size="xs" center multiline>
319+
Select another token or pay with a debit card.
315320
</Text>
316321
</div>
317322
)}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ export function TokenSelectorScreen(props: {
276276
>
277277
<CardStackIcon width={iconSize.md} height={iconSize.md} />
278278
<Text size="sm" color="primaryText">
279-
Pay with credit card
279+
Pay with a debit card
280280
</Text>
281281
</Container>
282282
</Button>
@@ -362,7 +362,7 @@ function WalletRowWithBalances(props: {
362362
) : (
363363
<Container style={{ padding: spacing.sm }}>
364364
<Text size="sm" color="secondaryText">
365-
Insufficient funds
365+
Insufficient Funds
366366
</Text>
367367
</Container>
368368
)}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CheckCircledIcon } from "@radix-ui/react-icons";
22
import { useQuery } from "@tanstack/react-query";
3-
import { useState } from "react";
3+
import { useMemo, useState } from "react";
44
import type { Chain } from "../../../../../../../chains/types.js";
55
import { getCachedChain } from "../../../../../../../chains/utils.js";
66
import type { ThirdwebClient } from "../../../../../../../client/client.js";
@@ -34,6 +34,7 @@ import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js";
3434
import { Step } from "../Stepper.js";
3535
import type { PayerInfo } from "../types.js";
3636
import { ConnectorLine } from "./ConfirmationScreen.js";
37+
import { ErrorText } from "./ErrorText.js";
3738
import { SwapSummary } from "./SwapSummary.js";
3839

3940
type TransferConfirmationScreenProps = {
@@ -109,6 +110,42 @@ export function TransferConfirmationScreen(
109110
refetchInterval: 30 * 1000,
110111
});
111112

113+
const uiErrorMessage = useMemo(() => {
114+
if (step === "approve" && status.id === "error" && status.error) {
115+
if (status.error.toLowerCase().includes("user rejected")) {
116+
return {
117+
title: "Failed to Approve",
118+
message: "Your wallet rejected the approval request.",
119+
};
120+
}
121+
return {
122+
title: "Failed to Approve",
123+
message:
124+
"Your wallet failed to approve the transaction for an unknown reason. Please try again or contact support.",
125+
};
126+
}
127+
128+
if (
129+
(step === "transfer" || step === "execute") &&
130+
status.id === "error" &&
131+
status.error
132+
) {
133+
if (status.error.toLowerCase().includes("user rejected")) {
134+
return {
135+
title: "Failed to Confirm",
136+
message: "Your wallet rejected the confirmation request.",
137+
};
138+
}
139+
return {
140+
title: "Failed to Confirm",
141+
message:
142+
"Your wallet failed to confirm the transaction for an unknown reason. Please try again or contact support.",
143+
};
144+
}
145+
146+
return undefined;
147+
}, [step, status]);
148+
112149
if (transferQuery.isLoading) {
113150
return (
114151
<Container p="lg">
@@ -190,15 +227,12 @@ export function TransferConfirmationScreen(
190227
</>
191228
)}
192229

193-
{status.id === "error" && (
230+
{uiErrorMessage && (
194231
<>
195-
<Container flex="row" gap="xs" center="both" color="danger">
196-
<Text color="danger" size="sm" style={{ textAlign: "center" }}>
197-
{step === "transfer"
198-
? `${status.error || "Failed to Transfer"}`
199-
: "Failed to Execute"}
200-
</Text>
201-
</Container>
232+
<ErrorText
233+
title={uiErrorMessage.title}
234+
message={uiErrorMessage.message}
235+
/>
202236
<Spacer y="md" />
203237
</>
204238
)}
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
type ApiError = {
22
code: string;
3-
message?: string;
3+
title: string;
4+
message: string;
45
data?: {
56
minimumAmountUSDCents?: string;
67
requestedAmountUSDCents?: string;
@@ -9,19 +10,25 @@ type ApiError = {
910
};
1011
};
1112

12-
export const defaultMessage = "Unable to get price quote";
1313
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
1414
export function getErrorMessage(err: any): ApiError {
1515
if (typeof err.error === "object" && err.error.code) {
1616
if (err.error.code === "MINIMUM_PURCHASE_AMOUNT") {
1717
return {
1818
code: "MINIMUM_PURCHASE_AMOUNT",
19-
message: "Purchase amount is too low",
19+
title: "Amount Too Low",
20+
message:
21+
"The requested amount is less than the minimum purchase. Try another provider or amount.",
2022
};
2123
}
2224
}
25+
26+
console.error(err);
27+
2328
return {
2429
code: "UNABLE_TO_GET_PRICE_QUOTE",
25-
message: defaultMessage,
30+
title: "Failed to Find Quote",
31+
message:
32+
"We couldn't get a quote for this token pair. Select another token or pay with a debit card.",
2633
};
2734
}

0 commit comments

Comments
 (0)