From 9c3be1e9e2dba70bbeb9f530e3a520eb48ee11a7 Mon Sep 17 00:00:00 2001 From: Eugene Chybisov Date: Wed, 16 Nov 2022 12:58:23 +0000 Subject: [PATCH] feat: add swap partially successful message --- packages/widget-embedded/src/config.ts | 10 +- .../components/ActiveSwaps/ActiveSwapItem.tsx | 3 +- .../src/components/SwapInput/SwapInput.tsx | 3 - .../widget/src/hooks/useGasSufficiency.ts | 4 +- .../widget/src/hooks/useRouteExecution.ts | 4 +- packages/widget/src/i18n/en.json | 4 +- .../SwapPage/StatusBottomSheet.style.tsx | 14 ++- .../src/pages/SwapPage/StatusBottomSheet.tsx | 119 ++++++++++++++---- .../widget/src/pages/SwapPage/SwapPage.tsx | 16 +-- packages/widget/src/stores/routes/types.ts | 14 ++- .../stores/routes/useExecutingRoutesIds.ts | 4 +- .../stores/routes/useRouteExecutionStore.ts | 78 +++++++++--- .../src/stores/routes/useSwapHistory.ts | 5 +- packages/widget/src/stores/routes/utils.ts | 16 ++- packages/widget/src/utils/enum.ts | 2 + packages/widget/src/utils/index.ts | 1 + 16 files changed, 221 insertions(+), 76 deletions(-) create mode 100644 packages/widget/src/utils/enum.ts diff --git a/packages/widget-embedded/src/config.ts b/packages/widget-embedded/src/config.ts index b9c25656c..12a9e6a54 100644 --- a/packages/widget-embedded/src/config.ts +++ b/packages/widget-embedded/src/config.ts @@ -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: [], @@ -32,6 +33,7 @@ export const widgetBaseConfig: WidgetConfig = { defaultRouteOptions: { // slippage: 0.02, // order: 'SAFEST', + // allowSwitchChain: false, }, }, tokens: { diff --git a/packages/widget/src/components/ActiveSwaps/ActiveSwapItem.tsx b/packages/widget/src/components/ActiveSwaps/ActiveSwapItem.tsx index 5d9f232d9..8f0466c75 100644 --- a/packages/widget/src/components/ActiveSwaps/ActiveSwapItem.tsx +++ b/packages/widget/src/components/ActiveSwaps/ActiveSwapItem.tsx @@ -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'; @@ -80,7 +81,7 @@ export const ActiveSwapItem: React.FC<{ } secondary={ - status !== 'success' ? ( + status !== RouteExecutionStatus.Done ? ( = ({ formType }) => { const amountKey = SwapFormKeyHelper.getAmountKey(formType); const { field: { onChange, onBlur, value }, - fieldState: { isDirty, isTouched }, } = useController({ name: amountKey, }); const { disabledUI } = useWidgetConfig(); const ref = useRef(null); - console.log(isDirty, isTouched); - const handleChange = ( event: ChangeEvent, ) => { diff --git a/packages/widget/src/hooks/useGasSufficiency.ts b/packages/widget/src/hooks/useGasSufficiency.ts index 09662cf69..2527fcdfd 100644 --- a/packages/widget/src/hooks/useGasSufficiency.ts +++ b/packages/widget/src/hooks/useGasSufficiency.ts @@ -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 { @@ -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); diff --git a/packages/widget/src/hooks/useRouteExecution.ts b/packages/widget/src/hooks/useRouteExecution.ts index 82b4c222d..04a9ba43c 100644 --- a/packages/widget/src/hooks/useRouteExecution.ts +++ b/packages/widget/src/hooks/useRouteExecution.ts @@ -6,7 +6,7 @@ import { useLiFi, useWallet } from '../providers'; import { getUpdatedProcess, isRouteActive, - isRouteCompleted, + isRouteDone, isRouteFailed, useRouteExecutionStore, } from '../stores'; @@ -45,7 +45,7 @@ export const useRouteExecution = ( process, }); } - if (isRouteCompleted(clonedUpdatedRoute)) { + if (isRouteDone(clonedUpdatedRoute)) { emitter.emit(WidgetEvent.RouteExecutionCompleted, clonedUpdatedRoute); } if (isRouteFailed(clonedUpdatedRoute) && process) { diff --git a/packages/widget/src/i18n/en.json b/packages/widget/src/i18n/en.json index c111c3bea..e793a9f91 100644 --- a/packages/widget/src/i18n/en.json +++ b/packages/widget/src/i18n/en.json @@ -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" } }, diff --git a/packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx b/packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx index 2916f6c8e..8099285ab 100644 --- a/packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx +++ b/packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx @@ -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: @@ -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, diff --git a/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx b/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx index 5de0c6122..392b73e3b 100644 --- a/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx +++ b/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx @@ -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 = ({ @@ -53,6 +59,29 @@ export const StatusBottomSheet: React.FC = ({ 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(); @@ -67,18 +96,41 @@ export const StatusBottomSheet: React.FC = ({ }; 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', ); @@ -90,7 +142,8 @@ export const StatusBottomSheet: React.FC = ({ } const processMessage = getProcessMessage(t, getChainById, step, process); title = processMessage.title; - message = processMessage.message; + primaryMessage = processMessage.message; + handlePrimaryButton = handleClose; break; } default: @@ -98,11 +151,12 @@ export const StatusBottomSheet: React.FC = ({ } 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(); } @@ -115,30 +169,43 @@ export const StatusBottomSheet: React.FC = ({ - {status === 'idle' ? : null} - {status === 'success' ? : null} - {status === 'error' ? : null} + {status === RouteExecutionStatus.Idle ? ( + + ) : null} + {status === RouteExecutionStatus.Done ? ( + + ) : null} + {hasEnumFlag(status, RouteExecutionStatus.Partial) || + hasEnumFlag(status, RouteExecutionStatus.Refunded) ? ( + + ) : null} + {hasEnumFlag(status, RouteExecutionStatus.Failed) ? ( + + ) : null} {title} - {status === 'success' ? ( + {hasEnumFlag(status, RouteExecutionStatus.Done) ? ( ) : null} - {message} + {primaryMessage} + {secondaryMessage ? ( + {secondaryMessage} + ) : null} - - {status === 'success' ? ( + {hasEnumFlag(status, RouteExecutionStatus.Done) ? (