"use client"; import { CheckIcon, ClockIcon, Cross1Icon } from "@radix-ui/react-icons"; import type { RouteStep } from "../../../../bridge/types/Route.js"; import type { Chain } from "../../../../chains/types.js"; import { defineChain } from "../../../../chains/utils.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import type { WindowAdapter } from "../../../core/adapters/WindowAdapter.js"; import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js"; import { iconSize, radius, spacing, } from "../../../core/design-system/index.js"; import type { BridgePrepareRequest, BridgePrepareResult, } from "../../../core/hooks/useBridgePrepare.js"; import { type CompletedStatusResult, useStepExecutor, } from "../../../core/hooks/useStepExecutor.js"; import { Container, ModalHeader } from "../components/basic.js"; import { Button } from "../components/buttons.js"; import { ChainName } from "../components/ChainName.js"; import { Spacer } from "../components/Spacer.js"; import { Spinner } from "../components/Spinner.js"; import { Text } from "../components/text.js"; type StepRunnerProps = { title: string | undefined; request: BridgePrepareRequest; /** * Wallet instance for executing transactions */ wallet: Wallet | undefined; /** * Thirdweb client for API calls */ client: ThirdwebClient; /** * Window adapter for opening URLs (web/RN) */ windowAdapter: WindowAdapter; /** * Whether to automatically start the transaction process */ autoStart: boolean; /** * Called when all steps are completed - receives array of completed status results */ onComplete: (completedStatuses: CompletedStatusResult[]) => void; /** * Called when user cancels the flow */ onCancel: (() => void) | undefined; /** * Called when user clicks the back button */ onBack: () => void; /** * Prepared quote to use */ preparedQuote: BridgePrepareResult; }; export function StepRunner({ title, request, wallet, client, windowAdapter, onComplete, onCancel, onBack, autoStart, preparedQuote, }: StepRunnerProps) { const theme = useCustomTheme(); // Use the real step executor hook const { currentStep, progress, executionState, onrampStatus, steps, error, start, cancel, retry, } = useStepExecutor({ autoStart, client, onComplete: (completedStatuses: CompletedStatusResult[]) => { onComplete(completedStatuses); }, wallet, preparedQuote, windowAdapter, }); const handleCancel = () => { cancel(); if (onCancel) { onCancel(); } }; const handleRetry = () => { retry(); }; const getStepStatus = ( stepIndex: number, ): "pending" | "executing" | "completed" | "failed" => { if (!currentStep || !steps) { // Not started yet return stepIndex === 0 ? (error ? "failed" : "pending") : "pending"; } const currentStepIndex = steps.findIndex((step) => step === currentStep); if (stepIndex < currentStepIndex) return "completed"; if (stepIndex === currentStepIndex && executionState === "executing") return "executing"; if (stepIndex === currentStepIndex && error) return "failed"; if ( stepIndex === currentStepIndex && executionState === "idle" && progress === 100 ) return "completed"; return "pending"; }; const getStatusIcon = ( status: "pending" | "executing" | "completed" | "failed", ) => { switch (status) { case "completed": return ( ); case "executing": return ; case "failed": return ( ); default: return ( ); } }; const getStepBackgroundColor = ( status: "pending" | "executing" | "completed" | "failed", ) => { switch (status) { case "completed": return theme.colors.tertiaryBg; case "executing": return theme.colors.tertiaryBg; case "failed": return theme.colors.tertiaryBg; default: return theme.colors.tertiaryBg; } }; const getIconBackgroundColor = ( status: "pending" | "executing" | "completed" | "failed", ) => { switch (status) { case "completed": return theme.colors.success; case "executing": return theme.colors.accentButtonBg; case "failed": return theme.colors.danger; default: return theme.colors.borderColor; } }; const getStepDescription = (step: RouteStep) => { const { originToken, destinationToken } = step; // If tokens are the same, it's likely a bridge operation if (originToken.chainId !== destinationToken.chainId) { return ( Bridge {originToken.symbol} to{" "} ); } // If different tokens on same chain, it's a swap if (originToken.symbol !== destinationToken.symbol) { return ( Swap {originToken.symbol} to {destinationToken.symbol} ); } // Fallback to step number return ( Process transaction ); }; const getStepStatusText = ( status: "pending" | "executing" | "completed" | "failed", ) => { switch (status) { case "executing": return "Processing..."; case "completed": return "Completed"; case "pending": return "Waiting..."; case "failed": return "Failed"; default: return "Unknown"; } }; return ( {/* Progress Bar */} Progress {progress}% {/* Steps List */} {request.type === "onramp" && onrampStatus ? ( {getStatusIcon(onrampStatus)} {request.onramp.slice(0, 1).toUpperCase() + request.onramp.slice(1)} {getStepStatusText(onrampStatus)} ) : null} {steps?.map((step, index) => { const status = getStepStatus(index); return ( {getStatusIcon(status)} {getStepDescription(step)} {getStepStatusText(status)} ); })} {error ? ( error.message || "An error occurred. Please try again." ) : ( <> Keep this window open until all
transactions are complete. )}
{/* Action Buttons */} {error ? ( ) : executionState === "idle" && progress === 0 ? ( ) : executionState === "executing" || executionState === "auto-starting" ? ( ) : null}
); } function getDestinationChain(request: BridgePrepareRequest): Chain { switch (request.type) { case "onramp": return defineChain(request.chainId); case "buy": case "sell": return defineChain(request.destinationChainId); case "transfer": return defineChain(request.chainId); default: throw new Error("Invalid quote type"); } }