diff --git a/packages/widget-v2/package.json b/packages/widget-v2/package.json index cfbd31211..14a923098 100644 --- a/packages/widget-v2/package.json +++ b/packages/widget-v2/package.json @@ -56,6 +56,7 @@ "lodash.debounce": "^4.0.8", "pluralize": "^8.0.0", "rc-virtual-list": "^3.14.5", + "react-error-boundary": "^4.0.13", "react-shadow-scope": "^1.0.5", "zod": "^3.23.8" } diff --git a/packages/widget-v2/src/components/AssetChainInput.tsx b/packages/widget-v2/src/components/AssetChainInput.tsx index c553c625b..7dad04173 100644 --- a/packages/widget-v2/src/components/AssetChainInput.tsx +++ b/packages/widget-v2/src/components/AssetChainInput.tsx @@ -6,7 +6,7 @@ import { useTheme } from 'styled-components'; import { CogIcon } from '@/icons/CogIcon'; import { Button, GhostButton } from '@/components/Button'; import { useAtom } from 'jotai'; -import { skipAssets } from '@/state/skip'; +import { skipAssets } from '@/state/skipClient'; import { useUsdValue } from '@/utils/useUsdValue'; import { formatUSD } from '@/utils/intl'; diff --git a/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx b/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx index 5d69fb2a1..743fa32d2 100644 --- a/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx +++ b/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx @@ -12,9 +12,9 @@ import { import { WALLET_LIST } from '@/modals/WalletSelectorModal/WalletSelectorFlow'; import { Button } from '@/components/Button'; import { SmallText, Text } from '@/components/Typography'; -import { destinationAssetAtom, destinationWalletAtom } from '@/state/swap'; +import { destinationAssetAtom, destinationWalletAtom } from '@/state/swapPage'; import { useAtom } from 'jotai'; -import { getChain } from '@/state/skip'; +import { getChain } from '@/state/skipClient'; export const ManualAddressModal = NiceModal.create((modalProps: ModalProps) => { const { theme } = modalProps; diff --git a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx index e8d104692..94e2e1f48 100644 --- a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx +++ b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx @@ -3,7 +3,7 @@ import { Modal, ModalProps } from '@/components/Modal'; import { Column } from '@/components/Layout'; import { styled } from 'styled-components'; import { useAtom } from 'jotai'; -import { ChainWithAsset, ClientAsset, skipAssets } from '@/state/skip'; +import { ChainWithAsset, ClientAsset, skipAssets } from '@/state/skipClient'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { VirtualList } from '@/components/VirtualList'; import { diff --git a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx index 2dbce565f..56c678933 100644 --- a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx +++ b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx @@ -1,7 +1,7 @@ import { Row } from '@/components/Layout'; import { ModalRowItem } from '@/components/ModalRowItem'; import { SmallText, Text } from '@/components/Typography'; -import { ChainWithAsset, ClientAsset } from '@/state/skip'; +import { ChainWithAsset, ClientAsset } from '@/state/skipClient'; import { CircleSkeletonElement, SkeletonElement } from '@/components/Skeleton'; import { styled } from 'styled-components'; import { Chain } from '@chain-registry/types'; diff --git a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalSearchInput.tsx b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalSearchInput.tsx index b9bb35633..52b13e58a 100644 --- a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalSearchInput.tsx +++ b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalSearchInput.tsx @@ -5,7 +5,7 @@ import { SkipLogoIcon } from '@/icons/SkipLogoIcon'; import { SmallText } from '@/components/Typography'; import { SearchIcon } from '@/icons/SearchIcon'; import { StyledAssetLabel } from '@/components/AssetChainInput'; -import { ClientAsset } from '@/state/skip'; +import { ClientAsset } from '@/state/skipClient'; import { LeftArrowIcon } from '@/icons/ArrowIcon'; import { useModal } from '@ebay/nice-modal-react'; import { Button } from '@/components/Button'; diff --git a/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx b/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx index 507903cc7..0b00404ca 100644 --- a/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx +++ b/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx @@ -1,7 +1,7 @@ import { RouteResponse } from '@skip-go/client'; import { SmallText } from '@/components/Typography'; import { useAtom } from 'jotai'; -import { skipAssets, getChain, ClientAsset } from '@/state/skip'; +import { skipAssets, getChain, ClientAsset } from '@/state/skipClient'; import { Column, Row } from '@/components/Layout'; import styled, { useTheme } from 'styled-components'; import { getFormattedAssetAmount } from '@/utils/crypto'; diff --git a/packages/widget-v2/src/pages/ErrorPage/ErrorPage.tsx b/packages/widget-v2/src/pages/ErrorPage/ErrorPage.tsx new file mode 100644 index 000000000..a0e672a40 --- /dev/null +++ b/packages/widget-v2/src/pages/ErrorPage/ErrorPage.tsx @@ -0,0 +1,13 @@ +import { errorAtom } from '@/state/errorPage'; +import { useResetAtom } from 'jotai/utils'; + +export const ErrorPage = () => { + const resetError = useResetAtom(errorAtom); + + return ( +
+ error page + +
+ ); +}; diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx index 322852e32..2dba7da34 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx @@ -8,7 +8,7 @@ import { useModal } from '@ebay/nice-modal-react'; import { ManualAddressModal } from '@/modals/ManualAddressModal/ManualAddressModal'; import styled, { useTheme } from 'styled-components'; import { useAtom } from 'jotai'; -import { destinationWalletAtom } from '@/state/swap'; +import { destinationWalletAtom } from '@/state/swapPage'; import { SwapExecutionPageRouteSimple } from './SwapExecutionPageRouteSimple'; import { SwapExecutionPageRouteDetailed } from './SwapExecutionPageRouteDetailed'; @@ -17,6 +17,7 @@ import { Operation, txState } from './SwapExecutionPageRouteDetailedRow'; import { SmallText } from '@/components/Typography'; import { SignatureIcon } from '@/icons/SignatureIcon'; import pluralize from 'pluralize'; +import operations from './operations.json'; enum SwapExecutionState { destinationAddressUnset, @@ -25,14 +26,10 @@ enum SwapExecutionState { confirmed, } -export type SwapExecutionPageProps = { - operations: Operation[]; -}; - const SIGNATURES_REQUIRED = 2; const TX_DELAY_MS = 5_000; // 5 seconds -export const SwapExecutionPage = ({ operations }: SwapExecutionPageProps) => { +export const SwapExecutionPage = () => { const theme = useTheme(); const [destinationWallet] = useAtom(destinationWalletAtom); @@ -157,7 +154,10 @@ export const SwapExecutionPage = ({ operations }: SwapExecutionPageProps) => { onClick: () => setSimpleRoute(!simpleRoute), }} /> - + {renderMainButton} & { + amount?: string; +}; + +export const sourceAssetAtom = atom(); + +export const destinationAssetAtom = atom(); + +export const connectedWalletAtom = atom(); + +export const destinationWalletAtom = atom(); diff --git a/packages/widget-v2/src/state/errorPage.ts b/packages/widget-v2/src/state/errorPage.ts new file mode 100644 index 000000000..ddb34fedc --- /dev/null +++ b/packages/widget-v2/src/state/errorPage.ts @@ -0,0 +1,7 @@ +import { atomWithReset } from 'jotai/utils'; + +export const errorAtom = atomWithReset< + ExpectedErrorDetails | Error | undefined +>(undefined); + +type ExpectedErrorDetails = {}; diff --git a/packages/widget-v2/src/state/router.ts b/packages/widget-v2/src/state/router.ts new file mode 100644 index 000000000..635be0787 --- /dev/null +++ b/packages/widget-v2/src/state/router.ts @@ -0,0 +1,8 @@ +import { atom } from 'jotai'; + +export enum Routes { + SwapPage, + SwapExecutionPage, +} + +export const currentPageAtom = atom(Routes.SwapPage); diff --git a/packages/widget-v2/src/state/skip.ts b/packages/widget-v2/src/state/skipClient.ts similarity index 100% rename from packages/widget-v2/src/state/skip.ts rename to packages/widget-v2/src/state/skipClient.ts diff --git a/packages/widget-v2/src/state/swap.ts b/packages/widget-v2/src/state/swapPage.ts similarity index 89% rename from packages/widget-v2/src/state/swap.ts rename to packages/widget-v2/src/state/swapPage.ts index 7331e976c..ebd689647 100644 --- a/packages/widget-v2/src/state/swap.ts +++ b/packages/widget-v2/src/state/swapPage.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { ClientAsset } from './skip'; +import { ClientAsset } from './skipClient'; import { Wallet } from '@/components/RenderWalletList'; export type AssetAtom = Partial & { diff --git a/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx b/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx index 1168e0c33..7a4e02d5c 100644 --- a/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx +++ b/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx @@ -4,8 +4,8 @@ import { Row } from '@/components/Layout'; import { defaultTheme, lightTheme } from '@/widget/theme'; import { ManualAddressModal } from '@/modals/ManualAddressModal/ManualAddressModal'; import { useEffect, useState } from 'react'; -import { skipAssets } from '@/state/skip'; -import { destinationAssetAtom } from '@/state/swap'; +import { skipAssets } from '@/state/skipClient'; +import { destinationAssetAtom } from '@/state/swapPage'; import { useAtom } from 'jotai'; const meta = { diff --git a/packages/widget-v2/src/stories/SwapExecutionFlow.stories.tsx b/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx similarity index 82% rename from packages/widget-v2/src/stories/SwapExecutionFlow.stories.tsx rename to packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx index 230a3c11b..9146e19d5 100644 --- a/packages/widget-v2/src/stories/SwapExecutionFlow.stories.tsx +++ b/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx @@ -2,12 +2,11 @@ import type { Meta, StoryObj } from '@storybook/react'; import { renderLightAndDarkTheme } from './renderLightAndDarkTheme'; import { SwapExecutionPage } from '@/pages/SwapExecutionPage/SwapExecutionPage'; import NiceModal from '@ebay/nice-modal-react'; -import { destinationAssetAtom } from '@/state/swap'; +import { destinationAssetAtom } from '@/state/swapPage'; import { useAtom } from 'jotai'; import operations from '@/pages/SwapExecutionPage/operations.json'; -import { skipAssets } from '@/state/skip'; +import { skipAssets } from '@/state/skipClient'; import { useEffect, useState } from 'react'; -import { Operation } from '@/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow'; const meta = { title: 'Pages/SwapExecutionPage', @@ -16,8 +15,8 @@ const meta = { const [_destinationAsset, setDestinationAsset] = useAtom(destinationAssetAtom); const [shouldRender, setShouldRender] = useState(false); - const firstOperation = props.operations[0]; - const lastOperation = props.operations[props.operations.length - 1]; + const firstOperation = operations[0]; + const lastOperation = operations[operations.length - 1]; const [{ data: assets }] = useAtom(skipAssets); @@ -57,7 +56,5 @@ type Story = StoryObj; export default meta; export const SwapExecutionPageExample: Story = { - args: { - operations: operations as Operation[], - }, + args: {}, }; diff --git a/packages/widget-v2/src/utils/useUsdValue.ts b/packages/widget-v2/src/utils/useUsdValue.ts index f080fe4d8..cc036b169 100644 --- a/packages/widget-v2/src/utils/useUsdValue.ts +++ b/packages/widget-v2/src/utils/useUsdValue.ts @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { z } from 'zod'; -import { getAssets } from '@/state/skip'; +import { getAssets } from '@/state/skipClient'; interface Args { coingeckoID: string; diff --git a/packages/widget-v2/src/widget/Router.tsx b/packages/widget-v2/src/widget/Router.tsx new file mode 100644 index 000000000..65c918d28 --- /dev/null +++ b/packages/widget-v2/src/widget/Router.tsx @@ -0,0 +1,31 @@ +import { ErrorPage } from '@/pages/ErrorPage/ErrorPage'; +import { SwapExecutionPage } from '@/pages/SwapExecutionPage/SwapExecutionPage'; +import { SwapPage } from '@/pages/SwapPage/SwapPage'; +import { errorAtom } from '@/state/errorPage'; +import { Routes, currentPageAtom } from '@/state/router'; +import { useAtom } from 'jotai'; +import { ErrorBoundary } from 'react-error-boundary'; + +export const Router = () => { + const [currentPage] = useAtom(currentPageAtom); + const [error, setError] = useAtom(errorAtom); + + if (error) { + return ; + } + + switch (currentPage) { + case Routes.SwapPage: + return ( + setError(error)}> + + + ); + case Routes.SwapExecutionPage: + return ( + setError(error)}> + + + ); + } +}; diff --git a/packages/widget-v2/src/widget/Widget.tsx b/packages/widget-v2/src/widget/Widget.tsx index 7f828b53a..db7b8fc5b 100644 --- a/packages/widget-v2/src/widget/Widget.tsx +++ b/packages/widget-v2/src/widget/Widget.tsx @@ -1,10 +1,10 @@ import { ShadowDomAndProviders } from './ShadowDomAndProviders'; -import { SwapPage } from '@/pages/SwapPage/SwapPage'; import NiceModal, { useModal } from '@ebay/nice-modal-react'; import { styled } from 'styled-components'; import { Modal } from '@/components/Modal'; import { cloneElement, ReactElement } from 'react'; import { PartialTheme } from './theme'; +import { Router } from './Router'; export type SwapWidgetProps = { theme?: PartialTheme; @@ -15,7 +15,7 @@ export const SwapWidget = (props: SwapWidgetProps) => { - + @@ -26,7 +26,7 @@ const SwapWidgetWithoutNiceModalProvider = (props: SwapWidgetProps) => { return ( - + ); diff --git a/packages/widget-v2/tsconfig.app.json b/packages/widget-v2/tsconfig.app.json index 50f728991..2b65b1b31 100644 --- a/packages/widget-v2/tsconfig.app.json +++ b/packages/widget-v2/tsconfig.app.json @@ -26,7 +26,7 @@ /* Aliases */ "baseUrl": ".", "paths": { - "@*": ["src/*"] + "@/*": ["src/*"] } }, "include": ["src"] diff --git a/yarn.lock b/yarn.lock index 25d554dba..6f8d0f155 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24160,6 +24160,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^4.0.13": + version: 4.0.13 + resolution: "react-error-boundary@npm:4.0.13" + dependencies: + "@babel/runtime": ^7.12.5 + peerDependencies: + react: ">=16.13.1" + checksum: 50398d080015d51d22c6f94c56f4ea336d10232d72345b36ee6f15b6b643666d20b072829b02f091a80e5af68fe67f68a62ef0d2b649dbd759ead929304449af + languageName: node + linkType: hard + "react-hot-toast@npm:^2.4.1": version: 2.4.1 resolution: "react-hot-toast@npm:2.4.1" @@ -28495,6 +28506,7 @@ __metadata: lodash.debounce: ^4.0.8 pluralize: ^8.0.0 rc-virtual-list: ^3.14.5 + react-error-boundary: ^4.0.13 react-shadow-scope: ^1.0.5 storybook: ^8.2.6 styled-components: ^6.0.0