@lifi/widget
Version:
LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.
154 lines • 8.85 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import Delete from '@mui/icons-material/Delete';
import { Box, Button, Tooltip } from '@mui/material';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { ContractComponent } from '../../components/ContractComponent/ContractComponent.js';
import { WarningMessages } from '../../components/Messages/WarningMessages.js';
import { PageContainer } from '../../components/PageContainer.js';
import { getStepList } from '../../components/Step/StepList.js';
import { TransactionDetails } from '../../components/TransactionDetails.js';
import { useAddressActivity } from '../../hooks/useAddressActivity.js';
import { useHeader } from '../../hooks/useHeader.js';
import { useNavigateBack } from '../../hooks/useNavigateBack.js';
import { useRouteExecution } from '../../hooks/useRouteExecution.js';
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js';
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js';
import { useFieldActions } from '../../stores/form/useFieldActions.js';
import { RouteExecutionStatus } from '../../stores/routes/types.js';
import { WidgetEvent } from '../../types/events.js';
import { HiddenUI } from '../../types/widget.js';
import { getAccumulatedFeeCostsBreakdown } from '../../utils/fees.js';
import { ConfirmToAddressSheet } from './ConfirmToAddressSheet.js';
import { ExchangeRateBottomSheet } from './ExchangeRateBottomSheet.js';
import { RouteTracker } from './RouteTracker.js';
import { StartTransactionButton } from './StartTransactionButton.js';
import { StatusBottomSheet } from './StatusBottomSheet.js';
import { TokenValueBottomSheet } from './TokenValueBottomSheet.js';
import { calculateValueLossPercentage, getTokenValueLossThreshold, } from './utils.js';
export const TransactionPage = () => {
const { t } = useTranslation();
const { setFieldValue } = useFieldActions();
const emitter = useWidgetEvents();
const { navigateBack } = useNavigateBack();
const { subvariant, subvariantOptions, contractSecondaryComponent, hiddenUI, } = useWidgetConfig();
const { state } = useLocation();
const stateRouteId = state?.routeId;
const [routeId, setRouteId] = useState(stateRouteId);
const [routeRefreshing, setRouteRefreshing] = useState(false);
const tokenValueBottomSheetRef = useRef(null);
const exchangeRateBottomSheetRef = useRef(null);
const confirmToAddressSheetRef = useRef(null);
const onAcceptExchangeRateUpdate = (resolver, data) => {
exchangeRateBottomSheetRef.current?.open(resolver, data);
};
const { route, status, executeRoute, restartRoute, deleteRoute } = useRouteExecution({
routeId: routeId,
onAcceptExchangeRateUpdate,
});
const { toAddress, hasActivity, isLoading: isLoadingAddressActivity, isFetched: isActivityAddressFetched, } = useAddressActivity(route?.toChainId);
const getHeaderTitle = () => {
if (subvariant === 'custom') {
return t(`header.${subvariantOptions?.custom ?? 'checkout'}`);
}
if (route) {
const transactionType = route.fromChainId === route.toChainId ? 'swap' : 'bridge';
return status === RouteExecutionStatus.Idle
? t(`button.${transactionType}Review`)
: t(`header.${transactionType}`);
}
return t('header.exchange');
};
const headerAction = useMemo(() => status === RouteExecutionStatus.Idle ? (_jsx(RouteTracker, { observableRouteId: stateRouteId, onChange: setRouteId, onFetching: setRouteRefreshing })) : undefined, [stateRouteId, status]);
useHeader(getHeaderTitle(), headerAction);
// biome-ignore lint/correctness/useExhaustiveDependencies: We want to emit event only when the page is mounted
useEffect(() => {
if (status === RouteExecutionStatus.Idle) {
emitter.emit(WidgetEvent.ReviewTransactionPageEntered, route);
}
}, []);
if (!route) {
return null;
}
const handleExecuteRoute = () => {
if (tokenValueBottomSheetRef.current?.isOpen()) {
const { gasCostUSD, feeCostUSD } = getAccumulatedFeeCostsBreakdown(route);
const fromAmountUSD = Number.parseFloat(route.fromAmountUSD);
const toAmountUSD = Number.parseFloat(route.toAmountUSD);
emitter.emit(WidgetEvent.RouteHighValueLoss, {
fromAmountUSD,
toAmountUSD,
gasCostUSD,
feeCostUSD,
valueLoss: calculateValueLossPercentage(fromAmountUSD, toAmountUSD, gasCostUSD, feeCostUSD),
});
}
tokenValueBottomSheetRef.current?.close();
executeRoute();
setFieldValue('fromAmount', '');
if (subvariant === 'custom') {
setFieldValue('fromToken', '');
setFieldValue('toToken', '');
}
};
const handleStartClick = async () => {
if (status === RouteExecutionStatus.Idle) {
if (toAddress &&
!hasActivity &&
!isLoadingAddressActivity &&
isActivityAddressFetched &&
!hiddenUI?.includes(HiddenUI.LowAddressActivityConfirmation)) {
confirmToAddressSheetRef.current?.open();
return;
}
const { gasCostUSD, feeCostUSD } = getAccumulatedFeeCostsBreakdown(route);
const fromAmountUSD = Number.parseFloat(route.fromAmountUSD);
const toAmountUSD = Number.parseFloat(route.toAmountUSD);
const tokenValueLossThresholdExceeded = getTokenValueLossThreshold(fromAmountUSD, toAmountUSD, gasCostUSD, feeCostUSD);
if (tokenValueLossThresholdExceeded && subvariant !== 'custom') {
tokenValueBottomSheetRef.current?.open();
}
else {
handleExecuteRoute();
}
}
if (status === RouteExecutionStatus.Failed) {
restartRoute();
}
};
const handleRemoveRoute = () => {
navigateBack();
deleteRoute();
};
const getButtonText = () => {
switch (status) {
case RouteExecutionStatus.Idle:
switch (subvariant) {
case 'custom':
return subvariantOptions?.custom === 'deposit'
? t('button.deposit')
: t('button.buy');
case 'refuel':
return t('button.startBridging');
default: {
const transactionType = route.fromChainId === route.toChainId ? 'Swapping' : 'Bridging';
return t(`button.start${transactionType}`);
}
}
case RouteExecutionStatus.Failed:
return t('button.tryAgain');
default:
return '';
}
};
return (_jsxs(PageContainer, { bottomGutters: true, children: [getStepList(route, subvariant), subvariant === 'custom' && contractSecondaryComponent ? (_jsx(ContractComponent, { sx: { marginTop: 2 }, children: contractSecondaryComponent })) : null, _jsx(TransactionDetails, { route: route, sx: { marginTop: 2 } }), status === RouteExecutionStatus.Idle ||
status === RouteExecutionStatus.Failed ? (_jsxs(_Fragment, { children: [_jsx(WarningMessages, { mt: 2, route: route, allowInteraction: true }), _jsxs(Box, { sx: {
mt: 2,
display: 'flex',
}, children: [_jsx(StartTransactionButton, { text: getButtonText(), onClick: handleStartClick, route: route, loading: routeRefreshing || isLoadingAddressActivity }), status === RouteExecutionStatus.Failed ? (_jsx(Tooltip, { title: t('button.removeTransaction'), placement: "bottom-end", children: _jsx(Button, { onClick: handleRemoveRoute, sx: {
minWidth: 48,
marginLeft: 1,
}, children: _jsx(Delete, {}) }) })) : null] })] })) : null, status ? _jsx(StatusBottomSheet, { status: status, route: route }) : null, subvariant !== 'custom' ? (_jsx(TokenValueBottomSheet, { route: route, ref: tokenValueBottomSheetRef, onContinue: handleExecuteRoute })) : null, _jsx(ExchangeRateBottomSheet, { ref: exchangeRateBottomSheetRef }), !hiddenUI?.includes(HiddenUI.LowAddressActivityConfirmation) ? (_jsx(ConfirmToAddressSheet, { ref: confirmToAddressSheetRef, onContinue: handleExecuteRoute, toAddress: toAddress, toChainId: route.toChainId })) : null] }));
};
//# sourceMappingURL=TransactionPage.js.map