import { useContext, useEffect, useMemo, useReducer, useRef, } from 'react'; import { useTranslation } from 'react-i18next'; import { AddTokensWidgetParams, IMTBLWidgetEvents } from '@imtbl/checkout-sdk'; import { v4 as uuidv4 } from 'uuid'; import { Stack, CloudImage, useTheme } from '@biom3/react'; import { Environment } from '@imtbl/config'; import { sendAddTokensCloseEvent } from './AddTokensWidgetEvents'; import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; import { AddTokensActions, AddTokensContext, addTokensReducer, initialAddTokensState, } from './context/AddTokensContext'; import { AddTokensWidgetViews } from '../../context/view-context/AddTokensViewContextTypes'; import { initialViewState, SharedViews, ViewActions, ViewContext, viewReducer, } from '../../context/view-context/ViewContext'; import { AddTokens } from './views/AddTokens'; import { ErrorView } from '../../views/error/ErrorView'; import { useSquid } from '../../lib/squid/hooks/useSquid'; import { fetchChains } from '../../lib/squid/functions/fetchChains'; import { StrongCheckoutWidgetsConfig } from '../../lib/withDefaultWidgetConfig'; import { Review } from './views/Review'; import { fetchBalances } from '../../lib/squid/functions/fetchBalances'; import { useTokens } from '../../lib/squid/hooks/useTokens'; import { useProvidersContext } from '../../context/providers-context/ProvidersContext'; import { orchestrationEvents } from '../../lib/orchestrationEvents'; import { getRemoteImage } from '../../lib/utils'; import { isValidAddress } from '../../lib/validations/widgetValidators'; import { amountInputValidation } from '../../lib/validations/amountInputValidations'; import { useError } from './hooks/useError'; import { AddTokensErrorTypes, AddTokensExperiments } from './types'; import { ServiceUnavailableErrorView } from '../../views/error/ServiceUnavailableErrorView'; export type AddTokensWidgetInputs = Omit & { config: StrongCheckoutWidgetsConfig; }; export default function AddTokensWidget({ showOnrampOption = true, showSwapOption = true, showBridgeOption = true, toTokenAddress: toTokenAddressParameter, toAmount, showBackButton, config, experiments, }: AddTokensWidgetInputs) { const fetchingBalances = useRef(false); const { base: { colorMode } } = useTheme(); const [viewState, viewDispatch] = useReducer(viewReducer, { ...initialViewState, view: { type: AddTokensWidgetViews.ADD_TOKENS }, history: [{ type: AddTokensWidgetViews.ADD_TOKENS }], }); const { t } = useTranslation(); const viewReducerValues = useMemo( () => ({ viewState, viewDispatch, }), [viewState, viewReducer], ); const [addTokensState, addTokensDispatch] = useReducer( addTokensReducer, initialAddTokensState, ); const { providersState: { checkout, fromProvider }, } = useProvidersContext(); const { id, squid, chains } = addTokensState; const addTokensReducerValues = useMemo( () => ({ addTokensState, addTokensDispatch, }), [addTokensState, addTokensDispatch], ); const squidSdk = useSquid(checkout); const tokensResponse = useTokens(checkout); const { showErrorHandover } = useError(checkout.config.environment); useEffect(() => { addTokensDispatch({ payload: { type: AddTokensActions.SET_ID, id: uuidv4(), }, }); if (experiments) { addTokensDispatch({ payload: { type: AddTokensActions.SET_EXPERIMENTS, experiments, }, }); } }, []); const toTokenAddress = useMemo(() => { if (toTokenAddressParameter) { return toTokenAddressParameter; } if (experiments?.[AddTokensExperiments.PRESELECTED_TOKEN]) { return experiments[AddTokensExperiments.PRESELECTED_TOKEN]; } return undefined; }, [experiments, toTokenAddressParameter]); useEffect(() => { if (config.environment !== Environment.PRODUCTION) { showErrorHandover(AddTokensErrorTypes.ENVIRONMENT_ERROR); } }, [config, id]); useEffect(() => { if (!checkout) return; (async () => { addTokensDispatch({ payload: { type: AddTokensActions.SET_IS_SWAP_AVAILABLE, isSwapAvailable: await checkout.isSwapAvailable(), }, }); })(); }, [checkout]); useEffect(() => { const isInvalidToTokenAddress = toTokenAddress && !isValidAddress(toTokenAddress); const isInvalidToAmount = toAmount && !amountInputValidation(toAmount); if (isInvalidToTokenAddress || isInvalidToAmount) { showErrorHandover(AddTokensErrorTypes.INVALID_PARAMETERS); } }, [toTokenAddress, toAmount, id]); useEffect(() => { if (!squid) return; addTokensDispatch({ payload: { type: AddTokensActions.SET_CHAINS, chains: fetchChains(squid), }, }); }, [squid]); useEffect(() => { if (!squid || !chains || !fromProvider || fetchingBalances.current) return; (async () => { try { fetchingBalances.current = true; const balances = await fetchBalances(squid, chains, fromProvider); addTokensDispatch({ payload: { type: AddTokensActions.SET_BALANCES, balances, }, }); } finally { fetchingBalances.current = false; } })(); }, [squid, chains, fromProvider]); useEffect(() => { if (!squidSdk) return; addTokensDispatch({ payload: { type: AddTokensActions.SET_SQUID, squid: squidSdk, }, }); }, [squidSdk]); useEffect(() => { if (!tokensResponse) return; addTokensDispatch({ payload: { type: AddTokensActions.SET_TOKENS, tokens: tokensResponse, }, }); }, [tokensResponse]); const { eventTargetState: { eventTarget }, } = useContext(EventTargetContext); const errorAction = () => { viewDispatch({ payload: { type: ViewActions.UPDATE_VIEW, view: { type: AddTokensWidgetViews.ADD_TOKENS }, }, }); }; return ( )} sx={{ pos: 'absolute', h: '100%', w: '100%', objectFit: 'cover', objectPosition: 'center', }} /> {viewState.view.type === AddTokensWidgetViews.ADD_TOKENS && ( sendAddTokensCloseEvent(eventTarget)} onBackButtonClick={() => { orchestrationEvents.sendRequestGoBackEvent( eventTarget, IMTBLWidgetEvents.IMTBL_ADD_TOKENS_WIDGET_EVENT, {}, ); }} /> )} {viewState.view.type === AddTokensWidgetViews.REVIEW && ( sendAddTokensCloseEvent(eventTarget)} onBackButtonClick={() => { viewDispatch({ payload: { type: ViewActions.GO_BACK, }, }); }} showBackButton /> )} {viewState.view.type === SharedViews.ERROR_VIEW && ( sendAddTokensCloseEvent(eventTarget)} /> )} {viewState.view.type === SharedViews.SERVICE_UNAVAILABLE_ERROR_VIEW && ( sendAddTokensCloseEvent(eventTarget)} onBackButtonClick={() => { viewDispatch({ payload: { type: ViewActions.GO_BACK, }, }); }} /> )} ); }