Skip to content

Commit

Permalink
feat: add swap in progress draft
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed May 24, 2022
1 parent ec55aa1 commit ffbd288
Show file tree
Hide file tree
Showing 30 changed files with 316 additions and 205 deletions.
4 changes: 2 additions & 2 deletions packages/widget-embedded/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions packages/widget/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -37,7 +37,7 @@ export const App: React.FC<AppProps> = ({ config }) => {
<SwapFormProvider>
<Header />
<Routes>
<Route path={routes.home} element={<SwapPage />} />
<Route path={routes.home} element={<MainPage />} />
<Route
path={routes.selectWallet}
element={<SelectWalletPage />}
Expand All @@ -55,7 +55,7 @@ export const App: React.FC<AppProps> = ({ config }) => {
path={routes.swapRoutes}
element={<SwapRoutesPage />}
/>
<Route path={routes.swap} element={<SwappingPage />} />
<Route path={routes.swap} element={<SwapPage />} />
</Routes>
</SwapFormProvider>
</WalletProvider>
Expand Down
6 changes: 3 additions & 3 deletions packages/widget/src/components/SwapButton/SwapButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useChains,
useCurrentRoute,
useHasSufficientBalance,
useSetExecutionRoute,
useSetExecutableRoute,
useSwapRoutes,
} from '../../hooks';
import { SwapFormKeyHelper } from '../../providers/SwapFormProvider';
Expand All @@ -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();

Expand All @@ -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 } });
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
}));
69 changes: 69 additions & 0 deletions packages/widget/src/components/SwapInProgress/SwapInProgress.tsx
Original file line number Diff line number Diff line change
@@ -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<BoxProps> = (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 (
<Card {...props}>
<CardTitle>Swaps in progress</CardTitle>
<Stack spacing={2} py={2}>
{executingRoutes.map(({ route, status }) => (
<RouteCard
key={route.id}
onClick={() => handleCardClick(route.id)}
avatar={
<AvatarGroup total={2}>
<Avatar
src={route.fromToken.logoURI}
alt={route.fromToken.symbol}
sx={{ background: 'white' }}
>
{route.fromToken.symbol[0]}
</Avatar>
<Avatar
src={route.toToken.logoURI}
alt={route.toToken.symbol}
sx={{ background: 'white' }}
>
{route.toToken.symbol[0]}
</Avatar>
</AvatarGroup>
}
action={<KeyboardArrowRightIcon />}
title={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{route.fromToken.symbol}
<ArrowForwardIcon fontSize="small" sx={{ paddingX: 0.5 }} />
{route.toToken.symbol}
</Box>
}
/>
))}
</Stack>
</Card>
);
};
1 change: 1 addition & 0 deletions packages/widget/src/components/SwapInProgress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SwapInProgress';
2 changes: 1 addition & 1 deletion packages/widget/src/hooks/useRouteExecution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface RouteExecutionStore {
currentRoute?: Route;
routes: Record<string, RouteExecution>;
setCurrentRoute: (route?: Route) => void;
setExecutionRoute: (route: Route) => void;
setExecutableRoute: (route: Route) => void;
updateRoute: (route: Route) => void;
restartRoute: (routeId: string) => void;
}
Expand Down
154 changes: 86 additions & 68 deletions packages/widget/src/hooks/useRouteExecution/useRouteStore.ts
Original file line number Diff line number Diff line change
@@ -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<RouteExecutionStore>((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<RouteExecutionStore>()(
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 = (): [
Expand Down
17 changes: 17 additions & 0 deletions packages/widget/src/pages/MainPage/MainPage.style.tsx
Original file line number Diff line number Diff line change
@@ -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),
}));
35 changes: 35 additions & 0 deletions packages/widget/src/pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<FormContainer disableGutters>
<FormBox>
<SwapInProgress mb={3} />
<SelectTokenButton formType="from" />
<Box
sx={{ display: 'flex', justifyContent: 'center', height: 40 }}
py={0.5}
>
<ReverseTokensButton />
</Box>
<Box mb={3}>
<SelectTokenButton formType="to" />
</Box>
<Box mb={3}>
<SwapInput formType="from" />
</Box>
<SwapRoutes mb={3} />
</FormBox>
<Box px={3} pb={3}>
<SwapButton />
</Box>
</FormContainer>
);
};
1 change: 1 addition & 0 deletions packages/widget/src/pages/MainPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MainPage';
Loading

0 comments on commit ffbd288

Please sign in to comment.