Skip to content

Commit 6499e49

Browse files
incorporate post buy transaction in the state machine
1 parent f3f49cd commit 6499e49

File tree

9 files changed

+186
-113
lines changed

9 files changed

+186
-113
lines changed

packages/thirdweb/src/react/core/machines/paymentMachine.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export type PaymentMachineEvent =
7676
| { type: "ROUTE_CONFIRMED" }
7777
| { type: "EXECUTION_COMPLETE"; completedStatuses: CompletedStatusResult[] }
7878
| { type: "ERROR_OCCURRED"; error: Error }
79+
| { type: "CONTINUE_TO_TRANSACTION" }
7980
| { type: "RETRY" }
8081
| { type: "RESET" }
8182
| { type: "BACK" };
@@ -87,6 +88,7 @@ type PaymentMachineState =
8788
| "preview"
8889
| "execute"
8990
| "success"
91+
| "post-buy-transaction"
9092
| "error";
9193

9294
/**
@@ -235,6 +237,12 @@ export function usePaymentMachine(
235237
break;
236238

237239
case "success":
240+
if (event.type === "CONTINUE_TO_TRANSACTION")
241+
return "post-buy-transaction";
242+
if (event.type === "RESET") return "init";
243+
break;
244+
245+
case "post-buy-transaction":
238246
if (event.type === "RESET") return "init";
239247
break;
240248

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { webWindowAdapter } from "../../adapters/WindowAdapter.js";
1616
import en from "../ConnectWallet/locale/en.js";
1717
import type { ConnectLocale } from "../ConnectWallet/locale/types.js";
1818
import type { PayEmbedConnectOptions } from "../PayEmbed.js";
19+
import { ExecutingTxScreen } from "../TransactionButton/ExecutingScreen.js";
1920
import { Container } from "../components/basic.js";
2021
import { DirectPayment } from "./DirectPayment.js";
2122
import { ErrorBanner } from "./ErrorBanner.js";
@@ -136,8 +137,18 @@ export function BridgeOrchestrator({
136137
// Use the payment machine hook
137138
const [state, send] = usePaymentMachine(adapters, uiOptions.mode);
138139

139-
// Handle completion
140-
const handleComplete = useCallback(() => {
140+
// Handle buy completion
141+
const handleBuyComplete = useCallback(() => {
142+
if (uiOptions.mode === "transaction") {
143+
send({ type: "CONTINUE_TO_TRANSACTION" });
144+
} else {
145+
onComplete?.();
146+
send({ type: "RESET" });
147+
}
148+
}, [onComplete, send, uiOptions.mode]);
149+
150+
// Handle post-buy transaction completion
151+
const handlePostBuyTransactionComplete = useCallback(() => {
141152
onComplete?.();
142153
send({ type: "RESET" });
143154
}, [onComplete, send]);
@@ -320,8 +331,21 @@ export function BridgeOrchestrator({
320331
uiOptions={uiOptions}
321332
preparedQuote={state.context.preparedQuote}
322333
completedStatuses={state.context.completedStatuses}
323-
onDone={handleComplete}
334+
onDone={handleBuyComplete}
335+
windowAdapter={webWindowAdapter}
336+
/>
337+
)}
338+
339+
{state.value === "post-buy-transaction" &&
340+
uiOptions.mode === "transaction" &&
341+
uiOptions.transaction && (
342+
<ExecutingTxScreen
343+
tx={uiOptions.transaction}
324344
windowAdapter={webWindowAdapter}
345+
closeModal={handlePostBuyTransactionComplete}
346+
onTxSent={() => {
347+
// Do nothing
348+
}}
325349
/>
326350
)}
327351
</Container>

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ export function StepRunner({
230230
}
231231

232232
// Fallback to step number
233-
return "Process transaction";
233+
return (
234+
<Text size="sm" color="primaryText">
235+
Process transaction
236+
</Text>
237+
);
234238
};
235239

236240
const getOnrampDescription = (

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
getChainMetadata,
1111
} from "../../../../../chains/utils.js";
1212
import { shortenHex } from "../../../../../utils/address.js";
13+
import { formatExplorerTxUrl } from "../../../../../utils/url.js";
1314
import type { WindowAdapter } from "../../../../core/adapters/WindowAdapter.js";
1415
import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
1516
import {
@@ -285,7 +286,9 @@ function CompletedStepDetailCard({
285286
: () => {
286287
const explorer = txInfo.chain.explorers?.[0];
287288
if (explorer) {
288-
windowAdapter.open(`${explorer.url}/tx/${txInfo.id}`);
289+
windowAdapter.open(
290+
formatExplorerTxUrl(explorer.url, txInfo.id),
291+
);
289292
}
290293
}
291294
}

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

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22

33
import { useQuery } from "@tanstack/react-query";
4-
import { useState } from "react";
54
import type { Token } from "../../../bridge/index.js";
65
import type { Chain } from "../../../chains/types.js";
76
import type { ThirdwebClient } from "../../../client/client.js";
@@ -28,7 +27,6 @@ import {
2827
import { UnsupportedTokenScreen } from "./Bridge/UnsupportedTokenScreen.js";
2928
import { EmbedContainer } from "./ConnectWallet/Modal/ConnectEmbed.js";
3029
import { useConnectLocale } from "./ConnectWallet/locale/getConnectLocale.js";
31-
import { ExecutingTxScreen } from "./TransactionButton/ExecutingScreen.js";
3230
import { DynamicHeight } from "./components/DynamicHeight.js";
3331
import { Spinner } from "./components/Spinner.js";
3432
import type { LocaleId } from "./types.js";
@@ -327,9 +325,6 @@ type UIOptionsResult =
327325
export function PayEmbed(props: PayEmbedProps) {
328326
const localeQuery = useConnectLocale(props.locale || "en_US");
329327
const theme = props.theme || "dark";
330-
const [screen, setScreen] = useState<"main" | "transaction-execution">(
331-
"main",
332-
);
333328

334329
const bridgeDataQuery = useQuery({
335330
queryKey: ["bridgeData", props],
@@ -469,20 +464,6 @@ export function PayEmbed(props: PayEmbedProps) {
469464
<Spinner size="xl" color="secondaryText" />
470465
</div>
471466
);
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-
);
486467
} else if (bridgeDataQuery.data?.type === "unsupported_token") {
487468
// Show unsupported token screen
488469
content = <UnsupportedTokenScreen chain={bridgeDataQuery.data.chain} />;
@@ -497,11 +478,7 @@ export function PayEmbed(props: PayEmbedProps) {
497478
purchaseData={props.payOptions?.purchaseData}
498479
paymentLinkId={props.paymentLinkId}
499480
onComplete={() => {
500-
if (props.payOptions?.mode === "transaction") {
501-
setScreen("transaction-execution");
502-
} else {
503-
props.payOptions?.onPurchaseSuccess?.();
504-
}
481+
props.payOptions?.onPurchaseSuccess?.();
505482
}}
506483
quickOptions={
507484
(props.payOptions as FundWalletOptions)?.prefillBuy?.quickOptions

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

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ 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 type { WindowAdapter } from "../../../core/adapters/WindowAdapter.js";
78
import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
89
import { iconSize } from "../../../core/design-system/index.js";
910
import { useChainExplorers } from "../../../core/hooks/others/useChainQuery.js";
@@ -12,14 +13,15 @@ import { AccentFailIcon } from "../ConnectWallet/icons/AccentFailIcon.js";
1213
import { Spacer } from "../components/Spacer.js";
1314
import { Spinner } from "../components/Spinner.js";
1415
import { Container, ModalHeader } from "../components/basic.js";
15-
import { Button, ButtonLink } from "../components/buttons.js";
16+
import { Button } from "../components/buttons.js";
1617
import { Text } from "../components/text.js";
1718

1819
export function ExecutingTxScreen(props: {
1920
tx: PreparedTransaction;
2021
closeModal: () => void;
2122
onTxSent: (data: WaitForReceiptOptions) => void;
2223
onBack?: () => void;
24+
windowAdapter: WindowAdapter;
2325
}) {
2426
const sendTxCore = useSendTransaction({
2527
payModal: false,
@@ -94,7 +96,7 @@ export function ExecutingTxScreen(props: {
9496
)}
9597
</Container>
9698

97-
<Spacer y="lg" />
99+
<Spacer y="md" />
98100

99101
<Text color="primaryText" center size="lg">
100102
{status === "loading" && "Sending transaction"}
@@ -118,24 +120,23 @@ export function ExecutingTxScreen(props: {
118120
<>
119121
{txHash && (
120122
<>
121-
<ButtonLink
123+
<Button
124+
variant="secondary"
122125
fullWidth
123-
variant="outline"
124-
href={formatExplorerTxUrl(
125-
chainExplorers.explorers[0]?.url ?? "",
126-
txHash,
127-
)}
128-
target="_blank"
129-
as="a"
130-
gap="xs"
131-
style={{
132-
textDecoration: "none",
133-
color: "inherit",
126+
onClick={() => {
127+
props.windowAdapter.open(
128+
formatExplorerTxUrl(
129+
chainExplorers.explorers[0]?.url ?? "",
130+
txHash,
131+
),
132+
);
134133
}}
134+
gap="xs"
135+
color="primaryText"
135136
>
136137
View on Explorer
137138
<ExternalLinkIcon width={iconSize.sm} height={iconSize.sm} />
138-
</ButtonLink>
139+
</Button>
139140
<Spacer y="sm" />
140141
</>
141142
)}
@@ -144,6 +145,39 @@ export function ExecutingTxScreen(props: {
144145
</Button>
145146
</>
146147
)}
148+
149+
{/* CSS Animations */}
150+
<style>
151+
{`
152+
@keyframes successBounce {
153+
0% {
154+
transform: scale(0.3);
155+
opacity: 0;
156+
}
157+
50% {
158+
transform: scale(1.05);
159+
}
160+
70% {
161+
transform: scale(0.9);
162+
}
163+
100% {
164+
transform: scale(1);
165+
opacity: 1;
166+
}
167+
}
168+
169+
@keyframes checkAppear {
170+
0% {
171+
transform: scale(0);
172+
opacity: 0;
173+
}
174+
100% {
175+
transform: scale(1);
176+
opacity: 1;
177+
}
178+
}
179+
`}
180+
</style>
147181
</Container>
148182
);
149183
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { PayUIOptions } from "../../../core/hooks/connection/ConnectButtonP
1111
import { useActiveAccount } from "../../../core/hooks/wallets/useActiveAccount.js";
1212
import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js";
1313
import type { SupportedTokens } from "../../../core/utils/defaultTokens.js";
14+
import { webWindowAdapter } from "../../adapters/WindowAdapter.js";
1415
import { LoadingScreen } from "../../wallets/shared/LoadingScreen.js";
1516
import { BridgeOrchestrator } from "../Bridge/BridgeOrchestrator.js";
1617
import { useConnectLocale } from "../ConnectWallet/locale/getConnectLocale.js";
@@ -94,6 +95,7 @@ function TransactionModalContent(props: ModalProps & { onBack?: () => void }) {
9495
tx={props.tx}
9596
closeModal={props.onClose}
9697
onTxSent={props.onTxSent}
98+
windowAdapter={webWindowAdapter}
9799
/>
98100
);
99101
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { ModalThemeWrapper } from "../utils.js";
1111
import {
1212
FUND_WALLET_UI_OPTIONS,
13+
TRANSACTION_UI_OPTIONS,
1314
simpleBuyQuote,
1415
simpleOnrampQuote,
1516
} from "./fixtures.js";
@@ -202,3 +203,15 @@ export const ComplexPaymentLight: Story = {
202203
backgrounds: { default: "light" },
203204
},
204205
};
206+
207+
export const TransactionPayment: Story = {
208+
args: {
209+
theme: "light",
210+
preparedQuote: simpleBuyQuote,
211+
completedStatuses: mockBuyCompletedStatuses,
212+
uiOptions: TRANSACTION_UI_OPTIONS.contractInteraction,
213+
},
214+
parameters: {
215+
backgrounds: { default: "light" },
216+
},
217+
};

0 commit comments

Comments
 (0)