diff --git a/packages/widget-embedded/src/index.tsx b/packages/widget-embedded/src/index.tsx index 6db25082c..0f5f824e8 100644 --- a/packages/widget-embedded/src/index.tsx +++ b/packages/widget-embedded/src/index.tsx @@ -14,8 +14,8 @@ const config: WidgetConfig = { enabledChains: JSON.parse(process.env.LIFI_ENABLED_CHAINS_JSON!), fromChain: 'pol', toChain: 'bsc', - // fromToken: '0x0000000000000000000000000000000000000000', - // toToken: '0xcc42724c6683b7e57334c4e856f4c9965ed682bd', + fromToken: '0x0000000000000000000000000000000000000000', + toToken: '0xcc42724c6683b7e57334c4e856f4c9965ed682bd', useInternalWalletManagement: true, containerStyle: { width: 480, diff --git a/packages/widget/src/App.tsx b/packages/widget/src/App.tsx index 90a7526f6..bffe8122c 100644 --- a/packages/widget/src/App.tsx +++ b/packages/widget/src/App.tsx @@ -6,11 +6,11 @@ import { WidgetConfig } from '.'; import { AppContainer } from './components/AppContainer'; import { Header } from './components/Header'; import { queryClient } from './config/queryClient'; +import { MainPage } from './pages/MainPage'; import { SelectTokenPage } from './pages/SelectTokenPage'; import { SelectWalletPage } from './pages/SelectWalletPage'; import { SettingsPage } from './pages/SettingsPage'; import { SwapPage } from './pages/SwapPage'; -import { SwappingPage } from './pages/SwappingPage'; import { SwapRoutesPage } from './pages/SwapRoutesPage'; import { SwapFormProvider } from './providers/SwapFormProvider'; import { WalletProvider } from './providers/WalletProvider'; @@ -37,7 +37,7 @@ export const App: React.FC = ({ config }) => {
- } /> + } /> } @@ -55,7 +55,7 @@ export const App: React.FC = ({ config }) => { path={routes.swapRoutes} element={} /> - } /> + } /> diff --git a/packages/widget/src/components/SwapButton/SwapButton.tsx b/packages/widget/src/components/SwapButton/SwapButton.tsx index d18042ebc..ce159e764 100644 --- a/packages/widget/src/components/SwapButton/SwapButton.tsx +++ b/packages/widget/src/components/SwapButton/SwapButton.tsx @@ -6,7 +6,7 @@ import { useChains, useCurrentRoute, useHasSufficientBalance, - useSetExecutionRoute, + useSetExecutableRoute, useSwapRoutes, } from '../../hooks'; import { SwapFormKeyHelper } from '../../providers/SwapFormProvider'; @@ -21,7 +21,7 @@ export const SwapButton: React.FC = () => { const { getChainById } = useChains(); const { account, switchChain } = useWallet(); const [currentRoute] = useCurrentRoute(); - const setExecutionRoute = useSetExecutionRoute(); + const setExecutableRoute = useSetExecutableRoute(); const { routes: swapRoutes, isLoading, isFetching } = useSwapRoutes(); @@ -47,7 +47,7 @@ export const SwapButton: React.FC = () => { currentRoute && swapRoutes?.some((route) => route.id === currentRoute.id) ) { - setExecutionRoute(currentRoute); + setExecutableRoute(currentRoute); navigate(routes.swap, { state: { routeId: currentRoute.id } }); } }; diff --git a/packages/widget/src/components/SwapInProgress/SwapInProgress.style.ts b/packages/widget/src/components/SwapInProgress/SwapInProgress.style.ts new file mode 100644 index 000000000..61037f56c --- /dev/null +++ b/packages/widget/src/components/SwapInProgress/SwapInProgress.style.ts @@ -0,0 +1,13 @@ +import { styled } from '@mui/material/styles'; +import { CardContainer, CardHeader } from '../Card'; + +export const Card = styled(CardContainer)(({ theme }) => ({ + borderColor: '#F7D4FF', + background: '#FEF0FF', +})); + +export const RouteCard = styled(CardHeader)(({ theme }) => ({ + cursor: 'pointer', + paddingTop: 0, + paddingBottom: 0, +})); diff --git a/packages/widget/src/components/SwapInProgress/SwapInProgress.tsx b/packages/widget/src/components/SwapInProgress/SwapInProgress.tsx new file mode 100644 index 000000000..c04e259d7 --- /dev/null +++ b/packages/widget/src/components/SwapInProgress/SwapInProgress.tsx @@ -0,0 +1,69 @@ +import { + ArrowForward as ArrowForwardIcon, + KeyboardArrowRight as KeyboardArrowRightIcon, +} from '@mui/icons-material'; +import { Avatar, AvatarGroup, Box, BoxProps, Stack } from '@mui/material'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { useExecutingRoutes } from '../../hooks'; +import { routes } from '../../utils/routes'; +import { CardTitle } from '../Card'; +import { Card, RouteCard } from './SwapInProgress.style'; + +export const SwapInProgress: React.FC = (props) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const executingRoutes = useExecutingRoutes(); + + const handleCardClick = useCallback( + (routeId: string) => { + navigate(routes.swap, { state: { routeId } }); + }, + [navigate], + ); + + if (!executingRoutes?.length) { + return null; + } + + return ( + + Swaps in progress + + {executingRoutes.map(({ route, status }) => ( + handleCardClick(route.id)} + avatar={ + + + {route.fromToken.symbol[0]} + + + {route.toToken.symbol[0]} + + + } + action={} + title={ + + {route.fromToken.symbol} + + {route.toToken.symbol} + + } + /> + ))} + + + ); +}; diff --git a/packages/widget/src/components/SwapInProgress/index.ts b/packages/widget/src/components/SwapInProgress/index.ts new file mode 100644 index 000000000..94f1a9d39 --- /dev/null +++ b/packages/widget/src/components/SwapInProgress/index.ts @@ -0,0 +1 @@ +export * from './SwapInProgress'; diff --git a/packages/widget/src/hooks/useRouteExecution/types.ts b/packages/widget/src/hooks/useRouteExecution/types.ts index 46f557f9c..ee8f5cb98 100644 --- a/packages/widget/src/hooks/useRouteExecution/types.ts +++ b/packages/widget/src/hooks/useRouteExecution/types.ts @@ -4,7 +4,7 @@ export interface RouteExecutionStore { currentRoute?: Route; routes: Record; setCurrentRoute: (route?: Route) => void; - setExecutionRoute: (route: Route) => void; + setExecutableRoute: (route: Route) => void; updateRoute: (route: Route) => void; restartRoute: (routeId: string) => void; } diff --git a/packages/widget/src/hooks/useRouteExecution/useRouteStore.ts b/packages/widget/src/hooks/useRouteExecution/useRouteStore.ts index c2dec3e61..ad4e9a838 100644 --- a/packages/widget/src/hooks/useRouteExecution/useRouteStore.ts +++ b/packages/widget/src/hooks/useRouteExecution/useRouteStore.ts @@ -1,78 +1,96 @@ import { Route } from '@lifinance/sdk'; import produce from 'immer'; import create from 'zustand'; +import { persist } from 'zustand/middleware'; import shallow from 'zustand/shallow'; import type { RouteExecutionStore } from './types'; -export const useRouteStore = create((set) => ({ - routes: {}, - setCurrentRoute: (route?: Route) => - set( - produce((state: RouteExecutionStore) => { - state.currentRoute = route; - }), - ), - setExecutionRoute: (route: Route) => - set( - produce((state: RouteExecutionStore) => { - if (!state.routes[route.id]) { - // clean previous idle routes that were not executed - Object.keys(state.routes) - .filter((routeId) => state.routes[routeId].status === 'idle') - .forEach((routeId) => delete state.routes[routeId]); - state.routes[route.id] = { - route, - status: 'idle', - }; - } - }), - ), - updateRoute: (route: Route) => - set( - produce((state: RouteExecutionStore) => { - state.routes[route.id].route = route; - const isFailed = route.steps.some( - (step) => step.execution?.status === 'FAILED', - ); - if (isFailed) { - state.routes[route.id].status = 'error'; - return; - } - const isDone = route.steps.every( - (step) => step.execution?.status === 'DONE', - ); - if (isDone) { - state.routes[route.id].status = 'success'; - return; - } - const isLoading = route.steps.some((step) => step.execution); - if (isLoading) { - state.routes[route.id].status = 'loading'; - } - }), - ), - restartRoute: (routeId: string) => - set( - produce((state: RouteExecutionStore) => { - state.routes[routeId].route.steps.forEach((step) => { - const stepHasFailed = step.execution?.status === 'FAILED'; - // check if the step has been cancelled which is a "failed" state - const stepHasBeenCancelled = step.execution?.process.some( - (process) => process.status === 'CANCELLED', - ); - if (step.execution && (stepHasFailed || stepHasBeenCancelled)) { - step.execution.status = 'RESUME'; - // remove last (failed) process - step.execution.process.pop(); - } - }); - state.routes[routeId].status = 'loading'; - }), - ), -})); +export const useRouteStore = create()( + persist( + (set) => ({ + routes: {}, + setCurrentRoute: (route?: Route) => + set( + produce((state: RouteExecutionStore) => { + state.currentRoute = route; + }), + ), + setExecutableRoute: (route: Route) => + set( + produce((state: RouteExecutionStore) => { + if (!state.routes[route.id]) { + // clean previous idle routes that were not executed + Object.keys(state.routes) + .filter((routeId) => state.routes[routeId].status === 'idle') + .forEach((routeId) => delete state.routes[routeId]); + state.routes[route.id] = { + route, + status: 'idle', + }; + state.currentRoute = route; + } + }), + ), + updateRoute: (route: Route) => + set( + produce((state: RouteExecutionStore) => { + state.routes[route.id].route = route; + const isFailed = route.steps.some( + (step) => step.execution?.status === 'FAILED', + ); + if (isFailed) { + state.routes[route.id].status = 'error'; + return; + } + const isDone = route.steps.every( + (step) => step.execution?.status === 'DONE', + ); + if (isDone) { + state.routes[route.id].status = 'success'; + return; + } + const isLoading = route.steps.some((step) => step.execution); + if (isLoading) { + state.routes[route.id].status = 'loading'; + } + }), + ), + restartRoute: (routeId: string) => + set( + produce((state: RouteExecutionStore) => { + state.routes[routeId].route.steps.forEach((step) => { + const stepHasFailed = step.execution?.status === 'FAILED'; + // check if the step has been cancelled which is a "failed" state + const stepHasBeenCancelled = step.execution?.process.some( + (process) => process.status === 'CANCELLED', + ); + if (step.execution && (stepHasFailed || stepHasBeenCancelled)) { + step.execution.status = 'RESUME'; + // remove last (failed) process + step.execution.process.pop(); + } + }); + state.routes[routeId].status = 'loading'; + }), + ), + }), + { + name: 'li.fi-widget-executable-routes', + partialize: (state) => ({ routes: state.routes }), + }, + ), +); -export const useSetExecutionRoute = () => { - return useRouteStore((state) => state.setExecutionRoute); +export const useSetExecutableRoute = () => { + return useRouteStore((state) => state.setExecutableRoute); +}; + +export const useExecutingRoutes = () => { + return useRouteStore((state) => + Object.values(state.routes).filter( + (route) => route.status === 'loading' || route.status === 'error', + ), + ); }; export const useCurrentRoute = (): [ diff --git a/packages/widget/src/pages/MainPage/MainPage.style.tsx b/packages/widget/src/pages/MainPage/MainPage.style.tsx new file mode 100644 index 000000000..5dbb42128 --- /dev/null +++ b/packages/widget/src/pages/MainPage/MainPage.style.tsx @@ -0,0 +1,17 @@ +import { Box, Container } from '@mui/material'; +import { styled } from '@mui/system'; + +export const FormContainer = styled(Container)({ + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + position: 'relative', +}); + +export const FormBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + paddingTop: theme.spacing(2), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), +})); diff --git a/packages/widget/src/pages/MainPage/MainPage.tsx b/packages/widget/src/pages/MainPage/MainPage.tsx new file mode 100644 index 000000000..400f87ab6 --- /dev/null +++ b/packages/widget/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,35 @@ +import { Box } from '@mui/material'; +import { ReverseTokensButton } from '../../components/ReverseTokensButton'; +import { SelectTokenButton } from '../../components/SelectTokenButton'; +import { SwapButton } from '../../components/SwapButton'; +import { SwapInProgress } from '../../components/SwapInProgress'; +import { SwapInput } from '../../components/SwapInput'; +import { SwapRoutes } from '../../components/SwapRoutes'; +import { FormBox, FormContainer } from './MainPage.style'; + +export const MainPage: React.FC = () => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/widget/src/pages/SwapPage/SendToRecipientForm.tsx b/packages/widget/src/pages/MainPage/SendToRecipientForm.tsx similarity index 100% rename from packages/widget/src/pages/SwapPage/SendToRecipientForm.tsx rename to packages/widget/src/pages/MainPage/SendToRecipientForm.tsx diff --git a/packages/widget/src/pages/MainPage/index.ts b/packages/widget/src/pages/MainPage/index.ts new file mode 100644 index 000000000..e71de313d --- /dev/null +++ b/packages/widget/src/pages/MainPage/index.ts @@ -0,0 +1 @@ +export * from './MainPage'; diff --git a/packages/widget/src/pages/SwappingPage/CircularProgress.style.tsx b/packages/widget/src/pages/SwapPage/CircularProgress.style.tsx similarity index 100% rename from packages/widget/src/pages/SwappingPage/CircularProgress.style.tsx rename to packages/widget/src/pages/SwapPage/CircularProgress.style.tsx diff --git a/packages/widget/src/pages/SwappingPage/CircularProgress.tsx b/packages/widget/src/pages/SwapPage/CircularProgress.tsx similarity index 100% rename from packages/widget/src/pages/SwappingPage/CircularProgress.tsx rename to packages/widget/src/pages/SwapPage/CircularProgress.tsx diff --git a/packages/widget/src/pages/SwappingPage/ExecutionItem.style.tsx b/packages/widget/src/pages/SwapPage/ExecutionItem.style.tsx similarity index 100% rename from packages/widget/src/pages/SwappingPage/ExecutionItem.style.tsx rename to packages/widget/src/pages/SwapPage/ExecutionItem.style.tsx diff --git a/packages/widget/src/pages/SwappingPage/ExecutionItem.tsx b/packages/widget/src/pages/SwapPage/ExecutionItem.tsx similarity index 100% rename from packages/widget/src/pages/SwappingPage/ExecutionItem.tsx rename to packages/widget/src/pages/SwapPage/ExecutionItem.tsx diff --git a/packages/widget/src/pages/SwappingPage/StatusBottomSheet.style.tsx b/packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx similarity index 100% rename from packages/widget/src/pages/SwappingPage/StatusBottomSheet.style.tsx rename to packages/widget/src/pages/SwapPage/StatusBottomSheet.style.tsx diff --git a/packages/widget/src/pages/SwappingPage/StatusBottomSheet.tsx b/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx similarity index 97% rename from packages/widget/src/pages/SwappingPage/StatusBottomSheet.tsx rename to packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx index 890fb1de9..101bf178e 100644 --- a/packages/widget/src/pages/SwappingPage/StatusBottomSheet.tsx +++ b/packages/widget/src/pages/SwapPage/StatusBottomSheet.tsx @@ -17,7 +17,7 @@ import { iconStyles, } from './StatusBottomSheet.style'; import { StepToken } from './StepToken'; -import { Button } from './SwappingPage.style'; +import { Button } from './SwapPage.style'; import { getProcessMessage } from './utils'; export const StatusBottomSheet: React.FC = ({ @@ -112,7 +112,9 @@ export const StatusBottomSheet: React.FC = ({ /> ) : null} - {message} + + {message} + + ) : null} + {status === 'error' ? ( + + ) : null} + + ); }; diff --git a/packages/widget/src/pages/SwappingPage/utils.ts b/packages/widget/src/pages/SwapPage/utils.ts similarity index 100% rename from packages/widget/src/pages/SwappingPage/utils.ts rename to packages/widget/src/pages/SwapPage/utils.ts diff --git a/packages/widget/src/pages/SwapRoutesPage/SwapRoutesPage.tsx b/packages/widget/src/pages/SwapRoutesPage/SwapRoutesPage.tsx index 407781178..0a9b2d59d 100644 --- a/packages/widget/src/pages/SwapRoutesPage/SwapRoutesPage.tsx +++ b/packages/widget/src/pages/SwapRoutesPage/SwapRoutesPage.tsx @@ -3,22 +3,28 @@ import { Route } from '@lifinance/sdk'; import { BoxProps, Skeleton } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { SwapRouteCard } from '../../components/SwapRouteCard'; -import { useCurrentRoute, useSwapRoutes } from '../../hooks'; +import { + useCurrentRoute, + useSetExecutableRoute, + useSwapRoutes, +} from '../../hooks'; +import { routes } from '../../utils/routes'; import { Stack } from './SwapRoutesPage.style'; export const SwapRoutesPage: React.FC = () => { const navigate = useNavigate(); - const { routes, isLoading, isFetching } = useSwapRoutes(); - const [currentRoute, setCurrentRoute] = useCurrentRoute(); + const { routes: swapRoutes, isLoading, isFetching } = useSwapRoutes(); + const [currentRoute] = useCurrentRoute(); + const setExecutableRoute = useSetExecutableRoute(); - if (!routes?.length && !isLoading && !isFetching) { + if (!swapRoutes?.length && !isLoading && !isFetching) { // TODO: make no routes message return null; } const handleRouteClick = (route: Route) => { - setCurrentRoute(route); - navigate(-1); + setExecutableRoute(route); + navigate(routes.swap, { state: { routeId: route.id } }); }; return ( @@ -33,7 +39,7 @@ export const SwapRoutesPage: React.FC = () => { sx={{ borderRadius: 1 }} /> )) - : routes?.map((route, index) => ( + : swapRoutes?.map((route, index) => ( ({ - padding: theme.spacing(1, 3, 3, 3), -})); - -export const Button = styled(MuiButton)(({ theme }) => ({ - textTransform: 'none', - borderRadius: theme.shape.borderRadius, - padding: theme.spacing(1.25, 2), - fontSize: '1rem', - marginTop: theme.spacing(2), -})); diff --git a/packages/widget/src/pages/SwappingPage/SwappingPage.tsx b/packages/widget/src/pages/SwappingPage/SwappingPage.tsx deleted file mode 100644 index 4967103d0..000000000 --- a/packages/widget/src/pages/SwappingPage/SwappingPage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useRouteExecution } from '@lifinance/widget/hooks'; -import { Fragment } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useLocation } from 'react-router-dom'; -import { StatusBottomSheet } from './StatusBottomSheet'; -import { StepDivider } from './StepDivider'; -import { StepItem } from './StepItem'; -import { Button, Container } from './SwappingPage.style'; - -export const SwappingPage: React.FC = () => { - const { t } = useTranslation(); - const { state }: any = useLocation(); - const { route, status, executeRoute, restartRoute } = useRouteExecution( - state.routeId as string, - ); - - return ( - - {route?.steps.map((step, index, steps) => ( - - - {steps.length > 1 && index !== steps.length - 1 ? ( - - ) : null} - - ))} - {status === 'idle' ? ( - - ) : null} - {status === 'error' ? ( - - ) : null} - - - ); -}; diff --git a/packages/widget/src/pages/SwappingPage/index.ts b/packages/widget/src/pages/SwappingPage/index.ts deleted file mode 100644 index 9ecca1c84..000000000 --- a/packages/widget/src/pages/SwappingPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SwappingPage';