Skip to content

Commit

Permalink
feat: add swap partially successful message
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Nov 16, 2022
1 parent 75eb492 commit 9c3be1e
Show file tree
Hide file tree
Showing 16 changed files with 221 additions and 76 deletions.
10 changes: 6 additions & 4 deletions packages/widget-embedded/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export const widgetBaseConfig: WidgetConfig = {
// fromChain: 137,
// toChain: 10,
// fromToken: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
// toToken: '0x7f5c764cbc14f9669b88837ca1490cca17c31607',
// fromAmount: '10',
// disableColorSchemes: true,
// toToken: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', // 0x0000000000000000000000000000000000000000
// fromAmount: '20',
// toAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0',
disableTelemetry: true,

disableTelemetry: false,
// disableColorSchemes: true,
variant: 'expandable',
integrator: 'li.fi-playground',
disabledChains: [],
Expand All @@ -32,6 +33,7 @@ export const widgetBaseConfig: WidgetConfig = {
defaultRouteOptions: {
// slippage: 0.02,
// order: 'SAFEST',
// allowSwitchChain: false,
},
},
tokens: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import { ListItemAvatar, ListItemText, Typography } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { useProcessMessage, useRouteExecution } from '../../hooks';
import { RouteExecutionStatus } from '../../stores';
import { navigationRoutes } from '../../utils';
import { StepTimer } from '../Step/StepTimer';
import { TokenAvatar, TokenAvatarGroup } from '../TokenAvatar';
Expand Down Expand Up @@ -80,7 +81,7 @@ export const ActiveSwapItem: React.FC<{
</Typography>
}
secondary={
status !== 'success' ? (
status !== RouteExecutionStatus.Done ? (
<Typography
fontWeight={400}
fontSize={12}
Expand Down
3 changes: 0 additions & 3 deletions packages/widget/src/components/SwapInput/SwapInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@ export const SwapInput: React.FC<SwapFormTypeProps> = ({ formType }) => {
const amountKey = SwapFormKeyHelper.getAmountKey(formType);
const {
field: { onChange, onBlur, value },
fieldState: { isDirty, isTouched },
} = useController({
name: amountKey,
});
const { disabledUI } = useWidgetConfig();
const ref = useRef<HTMLInputElement>(null);

console.log(isDirty, isTouched);

const handleChange = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/hooks/useGasSufficiency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import Big from 'big.js';
import { useChains } from '.';
import { useWallet } from '../providers';
import { isRouteCompleted } from '../stores';
import { isRouteDone } from '../stores';
import { useTokenBalance } from './useTokenBalance';

export interface GasSufficiency {
Expand Down Expand Up @@ -125,7 +125,7 @@ export const useGasSufficiency = (route?: Route) => {
} = useQuery(
['funds-sufficiency-check', account.address, route?.id],
async () => {
if (!account.address || !fromToken || !route || isRouteCompleted(route)) {
if (!account.address || !fromToken || !route || isRouteDone(route)) {
return null;
}
let currentTokenBalance = Big(fromToken?.amount ?? 0);
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/hooks/useRouteExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useLiFi, useWallet } from '../providers';
import {
getUpdatedProcess,
isRouteActive,
isRouteCompleted,
isRouteDone,
isRouteFailed,
useRouteExecutionStore,
} from '../stores';
Expand Down Expand Up @@ -45,7 +45,7 @@ export const useRouteExecution = (
process,
});
}
if (isRouteCompleted(clonedUpdatedRoute)) {
if (isRouteDone(clonedUpdatedRoute)) {
emitter.emit(WidgetEvent.RouteExecutionCompleted, clonedUpdatedRoute);
}
if (isRouteFailed(clonedUpdatedRoute) && process) {
Expand Down
4 changes: 3 additions & 1 deletion packages/widget/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,12 @@
"stepSwapAndBridge": "Swap and bridge",
"success": {
"message": {
"swapSuccessful": "There are now {{amount, number(maximumFractionDigits: 4)}} {{tokenSymbol}} in wallet {{walletAddress}} on {{chainName}} chain."
"swapPartiallySuccessful": "We've tried to complete the swap, but {{tool}} ran out of liquidity for {{tokenSymbol}} token.",
"swapSuccessful": "There are now {{amount, number(maximumFractionDigits: 4)}} {{tokenSymbol}} in {{walletAddress}} wallet on {{chainName}} chain."
},
"title": {
"gasSwapSuccessful": "Gas swap successful",
"swapPartiallySuccessful": "Swap partially successful",
"swapSuccessful": "Swap successful"
}
},
Expand Down
14 changes: 9 additions & 5 deletions packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { Theme } from '@mui/material';
import { Box } from '@mui/material';
import { alpha, darken, styled } from '@mui/material/styles';
import type { RouteExecutionStatus } from '../../stores';
import { RouteExecutionStatus } from '../../stores';

const getStatusColor = (status: RouteExecutionStatus, theme: Theme) => {
type StatusColor = RouteExecutionStatus | 'warning';

const getStatusColor = (status: StatusColor, theme: Theme) => {
switch (status) {
case 'success':
case RouteExecutionStatus.Done:
return { color: theme.palette.success.main, alpha: 0.15, darken: 0 };
case 'error':
case RouteExecutionStatus.Failed:
return { color: theme.palette.error.main, alpha: 0.2, darken: 0 };
case RouteExecutionStatus.Done | RouteExecutionStatus.Partial:
case RouteExecutionStatus.Done | RouteExecutionStatus.Refunded:
case 'warning':
return { color: theme.palette.warning.main, alpha: 0.7, darken: 0.4 };
default:
Expand All @@ -24,7 +28,7 @@ export const IconContainer = styled(Box)(({ theme }) => ({

export const IconCircle = styled(Box, {
shouldForwardProp: (prop) => prop !== 'status',
})<{ status: RouteExecutionStatus }>(({ theme, status }) => {
})<{ status: StatusColor }>(({ theme, status }) => {
const {
color,
alpha: alphaValue,
Expand Down
119 changes: 93 additions & 26 deletions packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import {
} from '../../hooks';
import { SwapFormKey } from '../../providers';
import type { RouteExecution } from '../../stores';
import { navigationRoutes, shortenWalletAddress } from '../../utils';
import { RouteExecutionStatus } from '../../stores';
import {
formatTokenAmount,
hasEnumFlag,
navigationRoutes,
shortenWalletAddress,
} from '../../utils';
import { IconCircle, IconContainer } from './StatusBottomSheet.style';

export const StatusBottomSheet: React.FC<RouteExecution> = ({
Expand Down Expand Up @@ -53,6 +59,29 @@ export const StatusBottomSheet: React.FC<RouteExecution> = ({
navigateBack();
};

const handlePartialDone = () => {
clearFromAmount();
if (
toToken.chainId !== route.toToken.chainId &&
toToken.address !== route.toToken.address
) {
setValue(
SwapFormKey.FromAmount,
formatTokenAmount(toToken.amount, toToken.decimals),
{ shouldTouch: true },
);
setValue(SwapFormKey.FromChain, toToken.chainId, { shouldTouch: true });
setValue(SwapFormKey.FromToken, toToken.address, { shouldTouch: true });
setValue(SwapFormKey.ToChain, route.toToken.chainId, {
shouldTouch: true,
});
setValue(SwapFormKey.ToToken, route.toToken.address, {
shouldTouch: true,
});
}
navigateBack();
};

const handleClose = () => {
clearFromAmount();
ref.current?.close();
Expand All @@ -67,18 +96,41 @@ export const StatusBottomSheet: React.FC<RouteExecution> = ({
};

let title;
let message;
let primaryMessage;
let secondaryMessage;
let handlePrimaryButton = handleDone;
switch (status) {
case 'success':
case RouteExecutionStatus.Done: {
title = t('swap.success.title.swapSuccessful');
message = t('swap.success.message.swapSuccessful', {
amount: token?.amount,
tokenSymbol: token?.symbol,
chainName: getChainById(route.toChainId)?.name,
walletAddress: shortenWalletAddress(route.toAddress),
if (token) {
primaryMessage = t('swap.success.message.swapSuccessful', {
amount: token.amount,
tokenSymbol: token.symbol,
chainName: getChainById(token.chainId)?.name,
walletAddress: shortenWalletAddress(route.toAddress),
});
}
handlePrimaryButton = handleDone;
break;
}
case RouteExecutionStatus.Done | RouteExecutionStatus.Partial: {
title = t('swap.success.title.swapPartiallySuccessful');
primaryMessage = t('swap.success.message.swapPartiallySuccessful', {
tool: route.steps.at(-1)?.toolDetails.name,
tokenSymbol: route.steps.at(-1)?.action.toToken.symbol,
});
if (token) {
secondaryMessage = t('swap.success.message.swapSuccessful', {
amount: token.amount,
tokenSymbol: token.symbol,
chainName: getChainById(token.chainId)?.name,
walletAddress: shortenWalletAddress(route.toAddress),
});
}
handlePrimaryButton = handlePartialDone;
break;
case 'error': {
}
case RouteExecutionStatus.Failed: {
const step = route.steps.find(
(step) => step.execution?.status === 'FAILED',
);
Expand All @@ -90,19 +142,21 @@ export const StatusBottomSheet: React.FC<RouteExecution> = ({
}
const processMessage = getProcessMessage(t, getChainById, step, process);
title = processMessage.title;
message = processMessage.message;
primaryMessage = processMessage.message;
handlePrimaryButton = handleClose;
break;
}
default:
break;
}

useEffect(() => {
const hasSuccessFlag = hasEnumFlag(status, RouteExecutionStatus.Done);
if (
(status === 'success' || status === 'error') &&
(hasSuccessFlag || hasEnumFlag(status, RouteExecutionStatus.Failed)) &&
!ref.current?.isOpen()
) {
if (status === 'success') {
if (hasSuccessFlag) {
refetchNewBalance();
refetch();
}
Expand All @@ -115,30 +169,43 @@ export const StatusBottomSheet: React.FC<RouteExecution> = ({
<Box p={3}>
<IconContainer>
<IconCircle status={status} mb={1}>
{status === 'idle' ? <InfoIcon color="primary" /> : null}
{status === 'success' ? <DoneIcon color="success" /> : null}
{status === 'error' ? <WarningIcon color="error" /> : null}
{status === RouteExecutionStatus.Idle ? (
<InfoIcon color="primary" />
) : null}
{status === RouteExecutionStatus.Done ? (
<DoneIcon color="success" />
) : null}
{hasEnumFlag(status, RouteExecutionStatus.Partial) ||
hasEnumFlag(status, RouteExecutionStatus.Refunded) ? (
<WarningIcon color="warning" />
) : null}
{hasEnumFlag(status, RouteExecutionStatus.Failed) ? (
<WarningIcon color="error" />
) : null}
</IconCircle>
<Typography py={1} fontSize={18} fontWeight={700}>
{title}
</Typography>
{status === 'success' ? (
{hasEnumFlag(status, RouteExecutionStatus.Done) ? (
<Token token={toToken} py={1} disableDescription />
) : null}
</IconContainer>
<Typography py={1}>{message}</Typography>
<Typography py={1}>{primaryMessage}</Typography>
{secondaryMessage ? (
<Typography py={1}>{secondaryMessage}</Typography>
) : null}
<Box mt={2}>
<Button
variant="contained"
fullWidth
onClick={status === 'success' ? handleDone : handleClose}
>
{status === 'idle' ? t('button.ok') : null}
{status === 'success' ? t('button.done') : null}
{status === 'error' ? t('button.seeDetails') : null}
<Button variant="contained" fullWidth onClick={handlePrimaryButton}>
{status === RouteExecutionStatus.Idle ? t('button.ok') : null}
{hasEnumFlag(status, RouteExecutionStatus.Done)
? t('button.done')
: null}
{status === RouteExecutionStatus.Failed
? t('button.seeDetails')
: null}
</Button>
</Box>
{status === 'success' ? (
{hasEnumFlag(status, RouteExecutionStatus.Done) ? (
<Box mt={2}>
<Button variant="text" onClick={handleSeeDetails} fullWidth>
{t('button.seeDetails')}
Expand Down
16 changes: 9 additions & 7 deletions packages/widget/src/pages/SwapPage/SwapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { BottomSheetBase } from '@lifi/widget/components/BottomSheet';
import { Delete as DeleteIcon } from '@mui/icons-material';
import { Box, Button, Tooltip } from '@mui/material';
import { Fragment, useCallback, useRef } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import type { BottomSheetBase } from '../../components/BottomSheet';
import { GasSufficiencyMessage } from '../../components/GasSufficiencyMessage';
import { Step } from '../../components/Step';
import { StepDivider } from '../../components/StepDivider';
import { SwapButton } from '../../components/SwapButton';
import { useNavigateBack, useRouteExecution } from '../../hooks';
import { SwapFormKey } from '../../providers';
import { RouteExecutionStatus } from '../../stores';
import { StatusBottomSheet } from './StatusBottomSheet';
import { Container } from './SwapPage.style';
import {
Expand All @@ -36,15 +37,15 @@ export const SwapPage: React.FC = () => {
}, [executeRoute, setValue]);

const handleSwapClick = async () => {
if (status === 'idle') {
if (status === RouteExecutionStatus.Idle) {
const thresholdExceeded = getTokenValueLossThreshold(route);
if (thresholdExceeded) {
tokenValueBottomSheetRef.current?.open();
} else {
handleExecuteRoute();
}
}
if (status === 'error') {
if (status === RouteExecutionStatus.Failed) {
restartRoute();
}
};
Expand All @@ -56,9 +57,9 @@ export const SwapPage: React.FC = () => {

const getSwapButtonText = () => {
switch (status) {
case 'idle':
case RouteExecutionStatus.Idle:
return t('button.startSwap');
case 'error':
case RouteExecutionStatus.Failed:
return t('button.restartSwap');
default:
return '';
Expand Down Expand Up @@ -88,7 +89,8 @@ export const SwapPage: React.FC = () => {
</Fragment>
);
})}
{status === 'idle' || status === 'error' ? (
{status === RouteExecutionStatus.Idle ||
status === RouteExecutionStatus.Failed ? (
<>
<GasSufficiencyMessage route={route} mt={2} />
<Box mt={2} display="flex">
Expand All @@ -99,7 +101,7 @@ export const SwapPage: React.FC = () => {
// disable={status === 'idle' && (isValidating || !isValid)}
enableLoading
/>
{status === 'error' ? (
{status === RouteExecutionStatus.Failed ? (
<Tooltip
title={t('button.removeSwap')}
placement="bottom-end"
Expand Down
Loading

0 comments on commit 9c3be1e

Please sign in to comment.