"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");
}
}