Skip to content

Commit f3f49cd

Browse files
transaction execution
1 parent e600e26 commit f3f49cd

File tree

8 files changed

+85
-40
lines changed

8 files changed

+85
-40
lines changed

packages/thirdweb/src/react/core/hooks/connection/ConnectButtonProps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ export type PayUIOptions = Prettify<
115115
* Callback to be called when the user successfully completes the purchase.
116116
*/
117117
onPurchaseSuccess?: (
118-
info:
118+
// TODO: remove this type from the callback entirely or adapt it from the new format
119+
info?:
119120
| {
120121
type: "crypto";
121122
status: BuyWithCryptoStatus;

packages/thirdweb/src/react/core/hooks/transaction/useSendTransaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export type SendTransactionPayModalConfig =
7070
* Callback to be called when the user successfully completes the purchase.
7171
*/
7272
onPurchaseSuccess?: (
73-
info:
73+
info?:
7474
| {
7575
type: "crypto";
7676
status: BuyWithCryptoStatus;

packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,10 @@ export function BridgeOrchestrator({
317317
state.context.preparedQuote &&
318318
state.context.completedStatuses && (
319319
<SuccessScreen
320+
uiOptions={uiOptions}
320321
preparedQuote={state.context.preparedQuote}
321322
completedStatuses={state.context.completedStatuses}
322-
onClose={handleComplete}
323-
onNewPayment={() => send({ type: "RESET" })}
323+
onDone={handleComplete}
324324
windowAdapter={webWindowAdapter}
325325
/>
326326
)}

packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export function StepRunner({
199199
}
200200
};
201201

202-
const getStepDescription = (step: RouteStep, index: number) => {
202+
const getStepDescription = (step: RouteStep) => {
203203
const { originToken, destinationToken } = step;
204204

205205
// If tokens are the same, it's likely a bridge operation
@@ -372,7 +372,7 @@ export function StepRunner({
372372
</Container>
373373

374374
<Container flex="column" gap="3xs" style={{ flex: 1 }}>
375-
{getStepDescription(step, index)}
375+
{getStepDescription(step)}
376376
<Text size="xs" color="secondaryText">
377377
{getStepStatusText(status)}
378378
</Text>

packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ import { Spacer } from "../../components/Spacer.js";
1010
import { Container, ModalHeader } from "../../components/basic.js";
1111
import { Button } from "../../components/buttons.js";
1212
import { Text } from "../../components/text.js";
13+
import type { UIOptions } from "../BridgeOrchestrator.js";
1314
import { PaymentReceipt } from "./PaymentReceipt.js";
1415

1516
export interface SuccessScreenProps {
17+
/**
18+
* UI options
19+
*/
20+
uiOptions: UIOptions;
1621
/**
1722
* Prepared quote from Bridge.prepare
1823
*/
@@ -26,12 +31,7 @@ export interface SuccessScreenProps {
2631
/**
2732
* Called when user closes the success screen
2833
*/
29-
onClose: () => void;
30-
31-
/**
32-
* Called when user wants to start a new payment
33-
*/
34-
onNewPayment?: () => void;
34+
onDone: () => void;
3535

3636
/**
3737
* Window adapter for opening URLs
@@ -42,9 +42,10 @@ export interface SuccessScreenProps {
4242
type ViewState = "success" | "detail";
4343

4444
export function SuccessScreen({
45+
uiOptions,
4546
preparedQuote,
4647
completedStatuses,
47-
onClose,
48+
onDone,
4849
windowAdapter,
4950
}: SuccessScreenProps) {
5051
const theme = useCustomTheme();
@@ -112,8 +113,8 @@ export function SuccessScreen({
112113
View Payment Receipt
113114
</Button>
114115

115-
<Button variant="accent" fullWidth onClick={onClose}>
116-
Done
116+
<Button variant="accent" fullWidth onClick={onDone}>
117+
{uiOptions.mode === "transaction" ? "Continue" : "Done"}
117118
</Button>
118119
</Container>
119120

packages/thirdweb/src/react/web/ui/PayEmbed.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useQuery } from "@tanstack/react-query";
4-
import {} from "react";
4+
import { useState } from "react";
55
import type { Token } from "../../../bridge/index.js";
66
import type { Chain } from "../../../chains/types.js";
77
import type { ThirdwebClient } from "../../../client/client.js";
@@ -28,6 +28,7 @@ import {
2828
import { UnsupportedTokenScreen } from "./Bridge/UnsupportedTokenScreen.js";
2929
import { EmbedContainer } from "./ConnectWallet/Modal/ConnectEmbed.js";
3030
import { useConnectLocale } from "./ConnectWallet/locale/getConnectLocale.js";
31+
import { ExecutingTxScreen } from "./TransactionButton/ExecutingScreen.js";
3132
import { DynamicHeight } from "./components/DynamicHeight.js";
3233
import { Spinner } from "./components/Spinner.js";
3334
import type { LocaleId } from "./types.js";
@@ -326,6 +327,9 @@ type UIOptionsResult =
326327
export function PayEmbed(props: PayEmbedProps) {
327328
const localeQuery = useConnectLocale(props.locale || "en_US");
328329
const theme = props.theme || "dark";
330+
const [screen, setScreen] = useState<"main" | "transaction-execution">(
331+
"main",
332+
);
329333

330334
const bridgeDataQuery = useQuery({
331335
queryKey: ["bridgeData", props],
@@ -465,6 +469,20 @@ export function PayEmbed(props: PayEmbedProps) {
465469
<Spinner size="xl" color="secondaryText" />
466470
</div>
467471
);
472+
} else if (
473+
screen === "transaction-execution" &&
474+
bridgeDataQuery.data?.type === "success" &&
475+
bridgeDataQuery.data.data.mode === "transaction"
476+
) {
477+
content = (
478+
<ExecutingTxScreen
479+
tx={bridgeDataQuery.data.data.transaction}
480+
closeModal={() => setScreen("main")}
481+
onTxSent={() => {
482+
props.payOptions?.onPurchaseSuccess?.();
483+
}}
484+
/>
485+
);
468486
} else if (bridgeDataQuery.data?.type === "unsupported_token") {
469487
// Show unsupported token screen
470488
content = <UnsupportedTokenScreen chain={bridgeDataQuery.data.chain} />;
@@ -478,6 +496,13 @@ export function PayEmbed(props: PayEmbedProps) {
478496
connectLocale={localeQuery.data}
479497
purchaseData={props.payOptions?.purchaseData}
480498
paymentLinkId={props.paymentLinkId}
499+
onComplete={() => {
500+
if (props.payOptions?.mode === "transaction") {
501+
setScreen("transaction-execution");
502+
} else {
503+
props.payOptions?.onPurchaseSuccess?.();
504+
}
505+
}}
481506
quickOptions={
482507
(props.payOptions as FundWalletOptions)?.prefillBuy?.quickOptions
483508
}

packages/thirdweb/src/react/web/ui/TransactionButton/ExecutingScreen.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { CheckCircledIcon, ExternalLinkIcon } from "@radix-ui/react-icons";
1+
import { CheckIcon, ExternalLinkIcon } from "@radix-ui/react-icons";
22
import { useCallback, useEffect, useRef, useState } from "react";
33
import type { Hex } from "viem";
44
import type { WaitForReceiptOptions } from "../../../../transaction/actions/wait-for-tx-receipt.js";
55
import type { PreparedTransaction } from "../../../../transaction/prepare-transaction.js";
66
import { formatExplorerTxUrl } from "../../../../utils/url.js";
7+
import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
78
import { iconSize } from "../../../core/design-system/index.js";
89
import { useChainExplorers } from "../../../core/hooks/others/useChainQuery.js";
910
import { useSendTransaction } from "../../hooks/transaction/useSendTransaction.js";
@@ -29,6 +30,7 @@ export function ExecutingTxScreen(props: {
2930
const [status, setStatus] = useState<"loading" | "failed" | "sent">(
3031
"loading",
3132
);
33+
const theme = useCustomTheme();
3234

3335
const sendTx = useCallback(async () => {
3436
setStatus("loading");
@@ -67,14 +69,31 @@ export function ExecutingTxScreen(props: {
6769
{status === "loading" && <Spinner size="xxl" color="accentText" />}
6870
{status === "failed" && <AccentFailIcon size={iconSize["3xl"]} />}
6971
{status === "sent" && (
70-
<Container color="success" flex="row" center="both">
71-
<CheckCircledIcon
72-
width={iconSize["3xl"]}
73-
height={iconSize["3xl"]}
72+
<Container
73+
center="both"
74+
flex="row"
75+
style={{
76+
width: "64px",
77+
height: "64px",
78+
borderRadius: "50%",
79+
backgroundColor: theme.colors.tertiaryBg,
80+
marginBottom: "16px",
81+
border: `2px solid ${theme.colors.success}`,
82+
animation: "successBounce 0.6s ease-out",
83+
}}
84+
>
85+
<CheckIcon
86+
width={iconSize.xl}
87+
height={iconSize.xl}
88+
color={theme.colors.success}
89+
style={{
90+
animation: "checkAppear 0.3s ease-out 0.3s both",
91+
}}
7492
/>
7593
</Container>
7694
)}
7795
</Container>
96+
7897
<Spacer y="lg" />
7998

8099
<Text color="primaryText" center size="lg">
@@ -87,7 +106,7 @@ export function ExecutingTxScreen(props: {
87106
{status === "failed" && txError ? txError.message || "" : ""}
88107
</Text>
89108

90-
<Spacer y="xxl" />
109+
<Spacer y="xl" />
91110

92111
{status === "failed" && (
93112
<Button variant="accent" fullWidth onClick={sendTx}>
@@ -97,12 +116,8 @@ export function ExecutingTxScreen(props: {
97116

98117
{status === "sent" && (
99118
<>
100-
<Button variant="accent" fullWidth onClick={props.closeModal}>
101-
Done
102-
</Button>
103119
{txHash && (
104120
<>
105-
<Spacer y="sm" />
106121
<ButtonLink
107122
fullWidth
108123
variant="outline"
@@ -121,8 +136,12 @@ export function ExecutingTxScreen(props: {
121136
View on Explorer
122137
<ExternalLinkIcon width={iconSize.sm} height={iconSize.sm} />
123138
</ButtonLink>
139+
<Spacer y="sm" />
124140
</>
125141
)}
142+
<Button variant="accent" fullWidth onClick={props.closeModal}>
143+
Done
144+
</Button>
126145
</>
127146
)}
128147
</Container>

packages/thirdweb/src/stories/Bridge/SuccessScreen.stories.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { stringify } from "viem";
3-
import type { WindowAdapter } from "../../react/core/adapters/WindowAdapter.js";
43
import type { Theme } from "../../react/core/design-system/index.js";
5-
import type { BridgePrepareResult } from "../../react/core/hooks/useBridgePrepare.js";
64
import type { CompletedStatusResult } from "../../react/core/hooks/useStepExecutor.js";
75
import { webWindowAdapter } from "../../react/web/adapters/WindowAdapter.js";
8-
import { SuccessScreen } from "../../react/web/ui/Bridge/payment-success/SuccessScreen.js";
6+
import {
7+
SuccessScreen,
8+
type SuccessScreenProps,
9+
} from "../../react/web/ui/Bridge/payment-success/SuccessScreen.js";
910
import { ModalThemeWrapper } from "../utils.js";
10-
import { simpleBuyQuote, simpleOnrampQuote } from "./fixtures.js";
11+
import {
12+
FUND_WALLET_UI_OPTIONS,
13+
simpleBuyQuote,
14+
simpleOnrampQuote,
15+
} from "./fixtures.js";
1116

1217
const mockBuyCompletedStatuses: CompletedStatusResult[] = JSON.parse(
1318
stringify([
@@ -70,13 +75,8 @@ const mockOnrampCompletedStatuses: CompletedStatusResult[] = JSON.parse(
7075
);
7176

7277
// Props interface for the wrapper component
73-
interface SuccessScreenWithThemeProps {
74-
preparedQuote: BridgePrepareResult;
75-
completedStatuses: CompletedStatusResult[];
76-
onClose: () => void;
77-
onNewPayment?: () => void;
78+
interface SuccessScreenWithThemeProps extends SuccessScreenProps {
7879
theme: "light" | "dark" | Theme;
79-
windowAdapter: WindowAdapter;
8080
}
8181

8282
// Wrapper component to provide theme context
@@ -105,19 +105,18 @@ const meta = {
105105
args: {
106106
preparedQuote: simpleBuyQuote,
107107
completedStatuses: mockBuyCompletedStatuses,
108-
onClose: () => console.log("Success screen closed"),
109-
onNewPayment: () => console.log("New payment started"),
108+
onDone: () => console.log("Success screen closed"),
110109
theme: "dark",
111110
windowAdapter: webWindowAdapter,
111+
uiOptions: FUND_WALLET_UI_OPTIONS.ethDefault,
112112
},
113113
argTypes: {
114114
theme: {
115115
control: "select",
116116
options: ["light", "dark"],
117117
description: "Theme for the component",
118118
},
119-
onClose: { action: "success screen closed" },
120-
onNewPayment: { action: "new payment started" },
119+
onDone: { action: "success screen closed" },
121120
},
122121
} satisfies Meta<typeof SuccessScreenWithTheme>;
123122

0 commit comments

Comments
 (0)