import { Box, Heading } from '@biom3/react';
import { useCallback, useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { parseUnits } from 'ethers';
import { SalePaymentTypes } from '@imtbl/checkout-sdk';
import {
OrderSummarySubViews,
SaleWidgetViews,
} from '../../../context/view-context/SaleViewContextTypes';
import { useSaleContext } from '../context/SaleContextProvider';
import {
CryptoFiatActions,
CryptoFiatContext,
} from '../../../context/crypto-fiat-context/CryptoFiatContext';
import { OrderReview } from '../components/OrderReview';
import { useFundingBalances } from '../hooks/useFundingBalances';
import { getTopUpViewData } from '../functions/getTopUpViewData';
import {
FundingBalance,
FundingBalanceType,
SaleErrorTypes,
SignPaymentTypes,
} from '../types';
import { FundingRouteExecute } from '../components/FundingRouteExecute/FundingRouteExecute';
import { useSaleEvent } from '../hooks/useSaleEvents';
import { LoadingHandover } from './LoadingHandover';
import {
TransactionMethod,
getRiveAnimationName,
transactionRiveAnimations,
} from '../hooks/useHandoverSteps';
import { HandoverTarget } from '../../../context/handover-context/HandoverContext';
import { ViewContext, ViewActions, SharedViews } from '../../../context/view-context/ViewContext';
import { useHandover } from '../../../lib/hooks/useHandover';
import { errorToString, getRemoteRive } from '../../../lib/utils';
import { ConnectLoaderContext } from '../../../context/connect-loader-context/ConnectLoaderContext';
import { getPricingBySymbol } from '../utils/pricing';
import { fetchRiskAssessmentV2, isSingleAddressSanctioned } from '../../../lib/riskAssessment';
type OrderSummaryProps = {
subView: OrderSummarySubViews;
};
export function OrderSummary({ subView }: OrderSummaryProps) {
const { t } = useTranslation();
const { sendFailedEvent } = useSaleEvent();
const {
fromTokenAddress,
collectionName,
goToErrorView,
goBackToPaymentMethods,
sign,
selectedCurrency,
setPaymentMethod,
environment,
paymentMethod,
orderQuote,
items,
} = useSaleContext();
const { connectLoaderState } = useContext(ConnectLoaderContext);
const { checkout, provider } = connectLoaderState;
const { viewDispatch, viewState } = useContext(ViewContext);
const { cryptoFiatDispatch, cryptoFiatState } = useContext(CryptoFiatContext);
const { addHandover, closeHandover } = useHandover({
id: HandoverTarget.GLOBAL,
});
const onPayWithCard = (paymentType: SalePaymentTypes) => goBackToPaymentMethods(paymentType);
const signAndProceed = (tokenAddress?: string) => {
addHandover({
animationUrl: getRemoteRive(
environment,
getRiveAnimationName(TransactionMethod.APPROVE),
),
inputValue:
transactionRiveAnimations[TransactionMethod.APPROVE].inputValues.start,
children: (
{t('views.PAYMENT_METHODS.handover.initial')}
),
});
sign(SignPaymentTypes.CRYPTO, tokenAddress);
viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SaleWidgetViews.PAY_WITH_COINS,
},
},
});
};
const onFundingRouteExecuted = () => {
signAndProceed(selectedCurrency?.address);
};
const onProceedToBuy = useCallback(async (fundingBalance: FundingBalance) => {
if (!checkout || !provider) return;
const { type, fundingItem } = fundingBalance;
// Calculate total amount from all order items for risk assessment
// Based on how item prices are calculated in OrderItems.tsx
const totalAmount = items.reduce((total, item) => {
const pricing = getPricingBySymbol(
fundingItem.token.symbol,
orderQuote?.products[item.productId]?.pricing,
cryptoFiatState.conversions,
);
const itemAmount = pricing?.amount || 0;
return total + itemAmount;
}, 0);
// Perform sanctions check once we have valid asset and amount data
const address = await (await provider?.getSigner())?.getAddress();
if (!address) {
return;
}
if (!fundingItem.token.address) {
throw new Error('Invalid data: fundingItem.token.address is missing');
}
const riskAssessmentData = [{
address,
tokenAddr: fundingItem.token.address,
amount: parseUnits(totalAmount.toString(), fundingItem.token.decimals),
}];
const riskAssessment = await fetchRiskAssessmentV2(riskAssessmentData, checkout.config);
if (riskAssessment && isSingleAddressSanctioned(riskAssessment, address)) {
const error = new Error('Sanctioned address');
sendFailedEvent(error.message, {}, [], { riskAssessment, paymentMethod });
viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SharedViews.SERVICE_UNAVAILABLE_ERROR_VIEW,
error,
},
},
});
return;
}
if (type === FundingBalanceType.SUFFICIENT) {
signAndProceed(fundingItem.token.address);
return;
}
closeHandover();
viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SaleWidgetViews.ORDER_SUMMARY,
subView: OrderSummarySubViews.EXECUTE_FUNDING_ROUTE,
data: {
fundingBalance,
onFundingRouteExecuted,
},
},
},
});
}, [paymentMethod, sendFailedEvent, viewDispatch, checkout, provider]);
const {
fundingBalances,
loadingBalances,
fundingBalancesResult,
fundingBalancesError,
transactionRequirement,
gasFees,
queryFundingBalances,
} = useFundingBalances();
// If funding balances failed to load, transition to error view
useEffect(() => {
if (!fundingBalancesError) return;
closeHandover();
goToErrorView(SaleErrorTypes.SERVICE_BREAKDOWN, {
error: errorToString(fundingBalancesError),
});
}, [fundingBalancesError, goToErrorView, closeHandover]);
// Initialise funding balances
useEffect(() => {
if (subView !== OrderSummarySubViews.INIT || !fromTokenAddress) return;
queryFundingBalances();
}, [subView, fromTokenAddress]);
// If one or more balances found, go to Order Review
useEffect(() => {
if (fundingBalances.length === 0) return;
closeHandover();
viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SaleWidgetViews.ORDER_SUMMARY,
subView: OrderSummarySubViews.REVIEW_ORDER,
},
},
});
}, [fundingBalances]);
// If no balances, Go to Top Up View
useEffect(() => {
if (loadingBalances || !fundingBalancesResult.length) return;
if (fundingBalances.length > 0) return;
try {
// suggest to top up base currency balance
const smartCheckoutResult = fundingBalancesResult.find(
(result) => result.currency.base,
)?.smartCheckoutResult!;
const data = getTopUpViewData(
smartCheckoutResult.transactionRequirements,
);
setPaymentMethod(undefined);
closeHandover();
viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SharedViews.TOP_UP_VIEW,
data,
},
},
});
} catch (error: unknown) {
goToErrorView(SaleErrorTypes.SERVICE_BREAKDOWN, {
error: errorToString(error),
});
}
}, [fundingBalances, loadingBalances, fundingBalancesResult]);
// Refresh conversion rates, once all balances are loaded
useEffect(() => {
if (
!cryptoFiatDispatch
|| fundingBalances.length === 0
|| loadingBalances
) {
return;
}
const tokenSymbols = fundingBalances.map(
({ fundingItem }) => fundingItem.token.symbol,
);
cryptoFiatDispatch({
payload: {
type: CryptoFiatActions.SET_TOKEN_SYMBOLS,
tokenSymbols,
},
});
}, [cryptoFiatDispatch, fundingBalances, loadingBalances]);
return (
{subView === OrderSummarySubViews.INIT && (
)}
{subView === OrderSummarySubViews.REVIEW_ORDER && (
)}
{subView === OrderSummarySubViews.EXECUTE_FUNDING_ROUTE && (
)}
);
}