"use client"; import { useMemo } from "react"; import { defineChain } from "../../../../../chains/utils.js"; import type { ThirdwebClient } from "../../../../../client/client.js"; import type { SupportedFiatCurrency } from "../../../../../pay/convert/type.js"; import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js"; import { iconSize, radius, spacing, } from "../../../../core/design-system/index.js"; import { useChainsQuery } from "../../../../core/hooks/others/useChainQuery.js"; import type { BridgePrepareResult } from "../../../../core/hooks/useBridgePrepare.js"; import { formatCurrencyAmount, formatTokenAmount, } from "../../ConnectWallet/screens/formatTokenBalance.js"; import { Container, ModalHeader } from "../../components/basic.js"; import { Button } from "../../components/buttons.js"; import { Spacer } from "../../components/Spacer.js"; import { Text } from "../../components/text.js"; import type { ModeInfo, PaymentMethod } from "../types.js"; import { PaymentOverview } from "./PaymentOverview.js"; type PaymentDetailsProps = { metadata: { title: string | undefined; description: string | undefined; }; currency: SupportedFiatCurrency; modeInfo: ModeInfo; confirmButtonLabel: string | undefined; /** * The client to use */ client: ThirdwebClient; /** * The payment method to use */ paymentMethod: PaymentMethod; /** * The prepared quote to preview */ preparedQuote: BridgePrepareResult; /** * Called when user confirms the route */ onConfirm: () => void; /** * Called when user wants to go back */ onBack: () => void; /** * Called when an error occurs */ onError: (error: Error) => void; }; export function PaymentDetails({ metadata, confirmButtonLabel, client, paymentMethod, preparedQuote, onConfirm, onBack, onError, currency, modeInfo, }: PaymentDetailsProps) { const theme = useCustomTheme(); const handleConfirm = () => { try { onConfirm(); } catch (error) { onError(error as Error); } }; const chainsQuery = useChainsQuery( preparedQuote.steps.flatMap((s) => [ defineChain(s.originToken.chainId), defineChain(s.destinationToken.chainId), ]), 10, ); const chainsMetadata = useMemo( () => chainsQuery.map((c) => c.data), [chainsQuery], ).filter((c) => !!c); // Extract common data based on quote type const getDisplayData = () => { switch (preparedQuote.type) { case "transfer": { const token = paymentMethod.type === "wallet" ? paymentMethod.originToken : undefined; if (!token) { // can never happen onError(new Error("Invalid payment method")); return { destinationAmount: "0", destinationToken: undefined, estimatedTime: 0, originAmount: "0", originToken: undefined, }; } return { destinationAmount: formatTokenAmount( preparedQuote.destinationAmount, token.decimals, ), destinationToken: token, estimatedTime: preparedQuote.estimatedExecutionTimeMs, originAmount: formatTokenAmount( preparedQuote.originAmount, token.decimals, ), originToken: token, }; } case "buy": { const method = paymentMethod.type === "wallet" ? paymentMethod : undefined; if (!method) { // can never happen onError(new Error("Invalid payment method")); return { destinationAmount: "0", destinationToken: undefined, estimatedTime: 0, originAmount: "0", originToken: undefined, }; } return { destinationAmount: formatTokenAmount( preparedQuote.destinationAmount, preparedQuote.steps[preparedQuote.steps.length - 1] ?.destinationToken?.decimals ?? 18, ), destinationToken: preparedQuote.steps[preparedQuote.steps.length - 1] ?.destinationToken, estimatedTime: preparedQuote.estimatedExecutionTimeMs, originAmount: formatTokenAmount( preparedQuote.originAmount, method.originToken.decimals, ), originToken: paymentMethod.type === "wallet" ? paymentMethod.originToken : undefined, }; } case "sell": { const method = paymentMethod.type === "wallet" ? paymentMethod : undefined; if (!method) { // can never happen onError(new Error("Invalid payment method")); return { destinationAmount: "0", destinationToken: undefined, estimatedTime: 0, originAmount: "0", originToken: undefined, }; } return { destinationAmount: formatTokenAmount( preparedQuote.destinationAmount, preparedQuote.steps[preparedQuote.steps.length - 1] ?.destinationToken?.decimals ?? 18, ), destinationToken: preparedQuote.steps[preparedQuote.steps.length - 1] ?.destinationToken, estimatedTime: preparedQuote.estimatedExecutionTimeMs, originAmount: formatTokenAmount( preparedQuote.originAmount, method.originToken.decimals, ), originToken: paymentMethod.type === "wallet" ? paymentMethod.originToken : undefined, }; } case "onramp": { const method = paymentMethod.type === "fiat" ? paymentMethod : undefined; if (!method) { // can never happen onError(new Error("Invalid payment method")); return { destinationAmount: "0", destinationToken: undefined, estimatedTime: 0, originAmount: "0", originToken: undefined, }; } return { destinationAmount: formatTokenAmount( preparedQuote.destinationAmount, preparedQuote.destinationToken.decimals, ), // Onramp starts with fiat destinationToken: preparedQuote.destinationToken, estimatedTime: undefined, originAmount: formatCurrencyAmount( method.currency, Number(preparedQuote.currencyAmount), ), originToken: undefined, }; } default: { throw new Error( `Unsupported bridge prepare type: ${(preparedQuote as unknown as { type: string }).type}`, ); } } }; const displayData = getDisplayData(); return ( {/* Quote Summary */} {displayData.destinationToken && ( )} Estimated Time {displayData.estimatedTime ? `~${Math.ceil(displayData.estimatedTime / 60000)} min` : "~2 min"} {preparedQuote.steps.length > 1 ? ( Route Length {preparedQuote.steps.length} step {preparedQuote.steps.length !== 1 ? "s" : ""} ) : null} {/* Route Steps */} {preparedQuote.steps.length > 1 && ( {preparedQuote.steps.map((step, stepIndex) => ( {/* Step Header */} {stepIndex + 1} {step.destinationToken.chainId !== step.originToken.chainId ? ( <> Bridge{" "} {step.originToken.symbol === step.destinationToken.symbol ? step.originToken.symbol : `${step.originToken.symbol} to ${step.destinationToken.symbol}`} ) : ( <> Swap {step.originToken.symbol} to{" "} {step.destinationToken.symbol} )} {step.originToken.chainId !== step.destinationToken.chainId ? ( <> { chainsMetadata.find( (c) => c.chainId === step.originToken.chainId, )?.name }{" "} to{" "} { chainsMetadata.find( (c) => c.chainId === step.destinationToken.chainId, )?.name } ) : ( chainsMetadata.find( (c) => c.chainId === step.originToken.chainId, )?.name )} ))} )} {/* Action Buttons */} ); }