From e366e6180a25234a4dfe9f270b6e2aac346da3a2 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:31:45 +0100 Subject: [PATCH] fix: refactor modals to overlay on top of backgroundLocation consistently, closes #4028 --- .../hooks/use-background-location-redirect.ts | 34 +++ src/app/common/hooks/use-location-state.ts | 7 + .../features/activity-list/activity-list.tsx | 36 ++-- src/app/features/asset-list/asset-list.tsx | 3 +- .../features/collectibles/collectibles.tsx | 2 +- .../components/add-collectible.tsx | 9 +- .../components/bitcoin/inscription.tsx | 5 +- .../generic-steps/device-busy/device-busy.tsx | 2 +- .../operation-rejected/operation-rejected.tsx | 5 +- .../settings-dropdown/settings-dropdown.tsx | 15 +- .../features/theme-drawer/theme-drawer.tsx | 4 +- src/app/pages/fund/fund.tsx | 2 +- .../pages/home/components/account-actions.tsx | 14 +- src/app/pages/home/components/home-tabs.tsx | 15 +- src/app/pages/home/home.tsx | 24 ++- .../components/receive-tokens.layout.tsx | 13 +- src/app/pages/receive/receive-btc.tsx | 7 +- src/app/pages/receive/receive-modal.tsx | 154 +++++++++----- src/app/pages/receive/receive-ordinal.tsx | 4 +- src/app/pages/receive/receive-stx.tsx | 9 +- .../pages/select-network/select-network.tsx | 4 +- .../sign-out-confirm/sign-out-confirm.tsx | 4 +- src/app/routes/app-routes.tsx | 200 +++--------------- src/app/routes/legacy-request-routes.tsx | 61 ++++++ src/app/routes/ordinal-routes.tsx | 23 ++ src/app/routes/receive-routes.tsx | 27 +++ src/app/routes/request-routes.tsx | 67 ++++++ src/app/routes/rpc-routes.tsx | 39 ++++ src/app/routes/settings-routes.tsx | 15 ++ src/shared/route-urls.ts | 33 +-- 30 files changed, 543 insertions(+), 294 deletions(-) create mode 100644 src/app/common/hooks/use-background-location-redirect.ts create mode 100644 src/app/routes/legacy-request-routes.tsx create mode 100644 src/app/routes/ordinal-routes.tsx create mode 100644 src/app/routes/receive-routes.tsx create mode 100644 src/app/routes/request-routes.tsx create mode 100644 src/app/routes/rpc-routes.tsx create mode 100644 src/app/routes/settings-routes.tsx diff --git a/src/app/common/hooks/use-background-location-redirect.ts b/src/app/common/hooks/use-background-location-redirect.ts new file mode 100644 index 00000000000..1b45a71aa59 --- /dev/null +++ b/src/app/common/hooks/use-background-location-redirect.ts @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +// import { RouteUrls } from '@shared/route-urls'; +import { useLocationState } from '@app/common/hooks/use-location-state'; + +// PETE this is the last thing +// - test opening in new tabs +// - find what fails and where then apply Kyrans advice +// - I think it works everywhere but Home tabs + +/* +when modals are opened in a new tab they lose the location.state.backgroundLocation + this hook sets the backgroundLocation to be RouteUrls.Home to improve UX +*/ + +export function useBackgroundLocationRedirect() { + const { pathname, state } = useLocation(); + const navigate = useNavigate(); + const backgroundLocation = useLocationState('backgroundLocation'); + + // console.log('backgroundLocation', backgroundLocation); + // debugger; + useEffect(() => { + void (async () => { + // if (backgroundLocation === undefined) { + // return navigate(pathname, { + // state: { backgroundLocation: { pathname: RouteUrls.Home }, ...state }, + // }); + // } + return false; + })(); + }, [backgroundLocation, navigate, pathname, state]); +} diff --git a/src/app/common/hooks/use-location-state.ts b/src/app/common/hooks/use-location-state.ts index db1a95f496e..ba748de5b03 100644 --- a/src/app/common/hooks/use-location-state.ts +++ b/src/app/common/hooks/use-location-state.ts @@ -5,6 +5,13 @@ import get from 'lodash.get'; import { isUndefined } from '@shared/utils'; +type LocationState = string | undefined | number | Location; + +export function useLocationState(propName: string): T; +export function useLocationState( + propName: string, + defaultValue: string +): T; export function useLocationState(propName: string): string | undefined; export function useLocationState(propName: string, defaultValue: string): string; export function useLocationState(propName: string, defaultValue?: string) { diff --git a/src/app/features/activity-list/activity-list.tsx b/src/app/features/activity-list/activity-list.tsx index b28cf472e9e..39901c29d22 100644 --- a/src/app/features/activity-list/activity-list.tsx +++ b/src/app/features/activity-list/activity-list.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { Outlet } from 'react-router-dom'; import uniqby from 'lodash.uniqby'; @@ -110,21 +111,24 @@ export function ActivityList() { ); return ( - - {hasSubmittedTransactions && } - {hasPendingTransactions && ( - - )} - {hasTransactions && ( - - )} - + <> + + {hasSubmittedTransactions && } + {hasPendingTransactions && ( + + )} + {hasTransactions && ( + + )} + + + ); } diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index 003beb1bbc0..dabd187afa4 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -1,5 +1,4 @@ -import { Outlet } from 'react-router-dom'; -import { useNavigate } from 'react-router-dom'; +import { Outlet, useNavigate } from 'react-router-dom'; import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { Stack } from 'leather-styles/jsx'; diff --git a/src/app/features/collectibles/collectibles.tsx b/src/app/features/collectibles/collectibles.tsx index 0dcbac128b0..ef07e977204 100644 --- a/src/app/features/collectibles/collectibles.tsx +++ b/src/app/features/collectibles/collectibles.tsx @@ -31,7 +31,7 @@ export function Collectibles() { subHeader={whenWallet({ software: ( navigate(RouteUrls.RetriveTaprootFunds)} + onSelectRetrieveBalance={() => navigate(RouteUrls.RetrieveTaprootFunds)} /> ), ledger: null, diff --git a/src/app/features/collectibles/components/add-collectible.tsx b/src/app/features/collectibles/components/add-collectible.tsx index d4124706180..85f87d6f3a6 100644 --- a/src/app/features/collectibles/components/add-collectible.tsx +++ b/src/app/features/collectibles/components/add-collectible.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Box } from '@stacks/ui'; import { token } from 'leather-styles/tokens'; @@ -26,13 +26,18 @@ const backgroundProps = { export function AddCollectible() { const navigate = useNavigate(); const analytics = useAnalytics(); + const location = useLocation(); return ( { void analytics.track('select_add_new_collectible'); - navigate(RouteUrls.ReceiveCollectible); + navigate(`${RouteUrls.Receive}/${RouteUrls.ReceiveCollectible}`, { + state: { + backgroundLocation: location, + }, + }); }} subtitle="Collectible" title="Add new" diff --git a/src/app/features/collectibles/components/bitcoin/inscription.tsx b/src/app/features/collectibles/components/bitcoin/inscription.tsx index 4a6b4cb78f3..079ce31291d 100644 --- a/src/app/features/collectibles/components/bitcoin/inscription.tsx +++ b/src/app/features/collectibles/components/bitcoin/inscription.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Inscription as InscriptionType } from '@shared/models/inscription.model'; import { RouteUrls } from '@shared/route-urls'; @@ -18,10 +18,11 @@ interface InscriptionProps { export function Inscription({ rawInscription }: InscriptionProps) { const inscription = convertInscriptionToSupportedInscriptionType(rawInscription); const navigate = useNavigate(); + const location = useLocation(); function openSendInscriptionModal() { navigate(RouteUrls.SendOrdinalInscription, { - state: { inscription }, + state: { inscription, backgroundLocation: location }, }); } diff --git a/src/app/features/ledger/generic-steps/device-busy/device-busy.tsx b/src/app/features/ledger/generic-steps/device-busy/device-busy.tsx index 59f5d835383..4283fb8f4ed 100644 --- a/src/app/features/ledger/generic-steps/device-busy/device-busy.tsx +++ b/src/app/features/ledger/generic-steps/device-busy/device-busy.tsx @@ -2,7 +2,7 @@ import { useLocationState } from '@app/common/hooks/use-location-state'; import { DeviceBusyLayout } from '@app/features/ledger/generic-steps'; export function DeviceBusy() { - const description = useLocationState('description'); + const description = useLocationState('description'); return ( ); diff --git a/src/app/features/ledger/generic-steps/operation-rejected/operation-rejected.tsx b/src/app/features/ledger/generic-steps/operation-rejected/operation-rejected.tsx index f43ba9cb628..702481d4b5c 100644 --- a/src/app/features/ledger/generic-steps/operation-rejected/operation-rejected.tsx +++ b/src/app/features/ledger/generic-steps/operation-rejected/operation-rejected.tsx @@ -4,7 +4,10 @@ import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigat export function OperationRejected() { const ledgerNavigate = useLedgerNavigate(); - const description = useLocationState('description', 'The operation on device was rejected'); + const description = useLocationState( + 'description', + 'The operation on device was rejected' + ); return ( { void analytics.track('click_change_theme_menu_item'); - navigate(RouteUrls.ChangeTheme, { relative: 'path' }); + navigate(RouteUrls.ChangeTheme, { + relative: 'path', + state: { backgroundLocation: location }, + }); })} > Change theme @@ -128,7 +131,10 @@ export function SettingsDropdown() { data-testid={SettingsSelectors.ChangeNetworkAction} onClick={wrappedCloseCallback(() => { void analytics.track('click_change_network_menu_item'); - navigate(RouteUrls.SelectNetwork, { relative: 'path' }); + navigate(RouteUrls.SelectNetwork, { + relative: 'path', + state: { backgroundLocation: location }, + }); })} > @@ -159,7 +165,10 @@ export function SettingsDropdown() { - navigate(RouteUrls.SignOutConfirm, { relative: 'path' }) + navigate(RouteUrls.SignOutConfirm, { + relative: 'path', + state: { backgroundLocation: location }, + }) )} data-testid={SettingsSelectors.SignOutListItem} > diff --git a/src/app/features/theme-drawer/theme-drawer.tsx b/src/app/features/theme-drawer/theme-drawer.tsx index 4a363ca3cec..f8c2ebd5f41 100644 --- a/src/app/features/theme-drawer/theme-drawer.tsx +++ b/src/app/features/theme-drawer/theme-drawer.tsx @@ -1,13 +1,15 @@ import { useNavigate } from 'react-router-dom'; +import { useLocationState } from '@app/common/hooks/use-location-state'; import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { ThemeList } from './theme-list'; export function ThemesDrawer() { const navigate = useNavigate(); + const backgroundLocation = useLocationState('backgroundLocation'); return ( - navigate('..')}> + navigate(backgroundLocation ?? '..')}> ); diff --git a/src/app/pages/fund/fund.tsx b/src/app/pages/fund/fund.tsx index a5ebda094b8..2539d621af1 100644 --- a/src/app/pages/fund/fund.tsx +++ b/src/app/pages/fund/fund.tsx @@ -12,13 +12,13 @@ import { FundLayout } from './fund.layout'; export function FundPage() { const navigate = useNavigate(); + const currentAccount = useCurrentStacksAccount(); const { data: balances } = useCurrentStacksAccountAnchoredBalances(); useRouteHeader(
navigate(RouteUrls.Home)} title=" " />); if (!currentAccount || !balances) return ; - return ( <> diff --git a/src/app/pages/home/components/account-actions.tsx b/src/app/pages/home/components/account-actions.tsx index 0cc727a5428..68b95eafac8 100644 --- a/src/app/pages/home/components/account-actions.tsx +++ b/src/app/pages/home/components/account-actions.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { Flex, FlexProps } from 'leather-styles/jsx'; @@ -16,7 +16,11 @@ import { SendButton } from './send-button'; export function AccountActions(props: FlexProps) { const navigate = useNavigate(); + const location = useLocation(); const isBitcoinEnabled = useConfigBitcoinEnabled(); + const receivePath = isBitcoinEnabled + ? RouteUrls.Receive + : `${RouteUrls.Receive}/${RouteUrls.ReceiveStx}`; return ( @@ -26,7 +30,13 @@ export function AccountActions(props: FlexProps) { data-testid={HomePageSelectors.ReceiveCryptoAssetBtn} icon={} label="Receive" - onClick={() => navigate(isBitcoinEnabled ? RouteUrls.Receive : RouteUrls.ReceiveStx)} + onClick={() => + navigate(receivePath, { + state: { + backgroundLocation: location, + }, + }) + } /> ('backgroundLocation'); const tabs = useMemo( () => [ { slug: RouteUrls.Home, label: 'assets' }, - { slug: RouteUrls.Activity, label: 'activity' }, + { slug: `${RouteUrls.Home}${RouteUrls.Activity}`, label: 'activity' }, ], [] ); - - const getActiveTab = useCallback( - () => tabs.findIndex(tab => tab.slug === pathname), - [tabs, pathname] - ); + const getActiveTab = useCallback(() => { + const path = backgroundLocation ? backgroundLocation.pathname : location?.pathname; + return tabs.findIndex(tab => tab.slug === path); + }, [tabs, backgroundLocation, location]); const setActiveTab = useCallback( (index: number) => navigate(tabs[index]?.slug), diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index d3668dea66c..8644ed8a4c7 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -1,14 +1,17 @@ -import { Outlet, useNavigate } from 'react-router-dom'; +import { Navigate, Route, useNavigate } from 'react-router-dom'; +import { Outlet, Routes, useLocation } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { useTrackFirstDeposit } from '@app/common/hooks/analytics/transactions-analytics.hooks'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; +import { useLocationState } from '@app/common/hooks/use-location-state'; import { useOnMount } from '@app/common/hooks/use-on-mount'; import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { Header } from '@app/components/header'; +import { ActivityList } from '@app/features/activity-list/activity-list'; +import { AssetsList } from '@app/features/asset-list/asset-list'; import { InAppMessages } from '@app/features/hiro-messages/in-app-messages'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { CurrentAccount } from './components/account-area'; import { HomeTabs } from './components/home-tabs'; @@ -17,8 +20,10 @@ import { HomeLayout } from './components/home.layout'; export function Home() { const { decodedAuthRequest } = useOnboardingState(); - const stacksAccount = useCurrentStacksAccount(); const navigate = useNavigate(); + const location = useLocation(); + const backgroundLocation = useLocationState('backgroundLocation'); + useTrackFirstDeposit(); useRouteHeader( @@ -35,7 +40,18 @@ export function Home() { return ( }> - + <> + {/* + To overlay modal on nested routes backgroundLocation is used + to trick the router into thinking its on the same page + */} + + } /> + } /> + } /> + + {backgroundLocation && } + ); diff --git a/src/app/pages/receive/components/receive-tokens.layout.tsx b/src/app/pages/receive/components/receive-tokens.layout.tsx index 9ed80ed1976..2a740109e12 100644 --- a/src/app/pages/receive/components/receive-tokens.layout.tsx +++ b/src/app/pages/receive/components/receive-tokens.layout.tsx @@ -3,8 +3,8 @@ import { useNavigate } from 'react-router-dom'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; import { Box, Flex, styled } from 'leather-styles/jsx'; -import { RouteUrls } from '@shared/route-urls'; - +import { useBackgroundLocationRedirect } from '@app/common/hooks/use-background-location-redirect'; +import { useLocationState } from '@app/common/hooks/use-location-state'; import { AddressDisplayer } from '@app/components/address-displayer/address-displayer'; import { LeatherButton } from '@app/components/button/button'; import { BaseDrawer } from '@app/components/drawer/base-drawer'; @@ -19,11 +19,18 @@ interface ReceiveTokensLayoutProps { warning?: React.JSX.Element; } export function ReceiveTokensLayout(props: ReceiveTokensLayoutProps) { + useBackgroundLocationRedirect(); + const { address, accountName, onCopyAddressToClipboard, title, warning } = props; const navigate = useNavigate(); + const backgroundLocation = useLocationState('backgroundLocation'); return ( - navigate(RouteUrls.Home)}> + navigate(backgroundLocation.pathname ?? '..')} + > {warning && warning} diff --git a/src/app/pages/receive/receive-btc.tsx b/src/app/pages/receive/receive-btc.tsx index 169a5aa78df..5ed7a60fef9 100644 --- a/src/app/pages/receive/receive-btc.tsx +++ b/src/app/pages/receive/receive-btc.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import toast from 'react-hot-toast'; import { useLocation } from 'react-router-dom'; @@ -5,6 +6,7 @@ import { useClipboard } from '@stacks/ui'; import get from 'lodash.get'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useBackgroundLocationRedirect } from '@app/common/hooks/use-background-location-redirect'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; @@ -15,6 +17,7 @@ interface ReceiveBtcModalType { } export function ReceiveBtcModal({ type = 'btc' }: ReceiveBtcModalType) { + useBackgroundLocationRedirect(); const analytics = useAnalytics(); const { state } = useLocation(); @@ -26,11 +29,11 @@ export function ReceiveBtcModal({ type = 'btc' }: ReceiveBtcModalType) { const { onCopy } = useClipboard(btcAddress); - function copyToClipboard() { + const copyToClipboard = useCallback(() => { void analytics.track('copy_btc_address_to_clipboard'); toast.success('Copied to clipboard!'); onCopy(); - } + }, [analytics, onCopy]); return ( ('backgroundLocation'); const navigate = useNavigate(); const btcAddressNativeSegwit = useCurrentAccountNativeSegwitAddressIndexZero(); const stxAddress = useCurrentAccountStxAddressState(); - const accountIndex = get(location.state, 'accountIndex', undefined); + const accountIndex = useLocationState('accountIndex'); const btcAddressTaproot = useZeroIndexTaprootAddress(accountIndex); const { onCopy: onCopyBtc } = useClipboard(btcAddressNativeSegwit); @@ -63,67 +66,104 @@ export function ReceiveModal({ type = 'full' }: ReceiveModalProps) { ); return ( - navigate('../')} - > - - - {title} - - {type === 'full' && ( - + <> + navigate(backgroundLocation.pathname ?? '..')} + > + + + {title} + + {type === 'full' && ( + + } + dataTestId={HomePageSelectors.ReceiveBtcNativeSegwitQrCodeBtn} + onCopyAddress={() => copyToClipboard(onCopyBtc)} + onClickQrCode={() => + navigate(RouteUrls.ReceiveBtc, { + state: { backgroundLocation }, + }) + } + title="Bitcoin" + /> + } + dataTestId={HomePageSelectors.ReceiveStxQrCodeBtn} + onCopyAddress={() => copyToClipboard(onCopyStx)} + onClickQrCode={() => + navigate(RouteUrls.ReceiveStx, { + relative: 'route', + state: { backgroundLocation }, + }) + } + title="Stacks" + /> + + )} + + } + dataTestId={HomePageSelectors.ReceiveBtcTaprootQrCodeBtn} + onCopyAddress={() => + copyToClipboard(onCopyOrdinal, 'select_stamp_to_add_new_collectible') + } + onClickQrCode={() => { + void analytics.track('select_inscription_to_add_new_collectible'); + // navigate(RouteUrls.ReceiveCollectibleOrdinal, { state: { btcAddressTaproot } }); + // TODO improve and refactor + // using absolute path here so it opens from Add new OR inside Receive modal + // FIXME - BUG - not seeing taproot when open in new tab + navigate(`/${RouteUrls.Receive}/${RouteUrls.ReceiveCollectibleOrdinal}`, { + state: { + btcAddressTaproot, + backgroundLocation: backgroundLocation, + }, + }); + }} + title="Ordinal inscription" + /> } - dataTestId={HomePageSelectors.ReceiveBtcNativeSegwitQrCodeBtn} - onCopyAddress={() => copyToClipboard(onCopyBtc)} - onClickQrCode={() => navigate(RouteUrls.ReceiveBtc)} - title="Bitcoin" + icon={} + onClickQrCode={() => + navigate(`/${RouteUrls.Receive}/${RouteUrls.ReceiveBtcStamp}`, { + state: { + backgroundLocation: backgroundLocation, + }, + }) + } + onCopyAddress={() => + copyToClipboard(onCopyBtc, 'select_stamp_to_add_new_collectible') + } + title="Bitcoin Stamp" /> } - dataTestId={HomePageSelectors.ReceiveStxQrCodeBtn} - onCopyAddress={() => copyToClipboard(onCopyStx)} - onClickQrCode={() => navigate(RouteUrls.ReceiveStx)} - title="Stacks" + onCopyAddress={() => copyToClipboard(onCopyStx, 'select_nft_to_add_new_collectible')} + onClickQrCode={() => + navigate(`/${RouteUrls.Receive}/${RouteUrls.ReceiveStx}`, { + state: { + backgroundLocation: backgroundLocation, + }, + }) + } + title="Stacks NFT" /> - )} - - } - dataTestId={HomePageSelectors.ReceiveBtcTaprootQrCodeBtn} - onCopyAddress={() => - copyToClipboard(onCopyOrdinal, 'select_stamp_to_add_new_collectible') - } - onClickQrCode={() => { - void analytics.track('select_inscription_to_add_new_collectible'); - navigate(RouteUrls.ReceiveCollectibleOrdinal, { state: { btcAddressTaproot } }); - }} - title="Ordinal inscription" - /> - } - onClickQrCode={() => navigate(RouteUrls.ReceiveBtcStamp)} - onCopyAddress={() => copyToClipboard(onCopyBtc, 'select_stamp_to_add_new_collectible')} - title="Bitcoin Stamp" - /> - } - onCopyAddress={() => copyToClipboard(onCopyStx, 'select_nft_to_add_new_collectible')} - onClickQrCode={() => navigate(RouteUrls.ReceiveStx)} - title="Stacks NFT" - /> - - - + + + + {/* Outlet here for nested token routes */} + + ); } diff --git a/src/app/pages/receive/receive-ordinal.tsx b/src/app/pages/receive/receive-ordinal.tsx index 1897adaea24..423623e9037 100644 --- a/src/app/pages/receive/receive-ordinal.tsx +++ b/src/app/pages/receive/receive-ordinal.tsx @@ -4,14 +4,16 @@ import { useLocation } from 'react-router-dom'; import { useClipboard } from '@stacks/ui'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useBackgroundLocationRedirect } from '@app/common/hooks/use-background-location-redirect'; import { ReceiveBtcModalWarning } from './components/receive-btc-warning'; import { ReceiveTokensLayout } from './components/receive-tokens.layout'; export function ReceiveOrdinalModal() { + useBackgroundLocationRedirect(); const analytics = useAnalytics(); const { state } = useLocation(); - const { onCopy } = useClipboard(state.btcAddressTaproot); + const { onCopy } = useClipboard(state?.btcAddressTaproot); function copyToClipboard() { void analytics.track('copy_address_to_add_new_inscription'); diff --git a/src/app/pages/receive/receive-stx.tsx b/src/app/pages/receive/receive-stx.tsx index 02b1c203123..ed234005dca 100644 --- a/src/app/pages/receive/receive-stx.tsx +++ b/src/app/pages/receive/receive-stx.tsx @@ -1,24 +1,27 @@ +import { useCallback } from 'react'; import toast from 'react-hot-toast'; import { useClipboard } from '@stacks/ui'; import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useBackgroundLocationRedirect } from '@app/common/hooks/use-background-location-redirect'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ReceiveTokensLayout } from './components/receive-tokens.layout'; export function ReceiveStxModal() { + useBackgroundLocationRedirect(); const currentAccount = useCurrentStacksAccount(); const analytics = useAnalytics(); const { onCopy } = useClipboard(currentAccount?.address ?? ''); const accountName = useCurrentAccountDisplayName(); - function copyToClipboard() { + const copyToClipboard = useCallback(() => { void analytics.track('copy_stx_address_to_clipboard'); - toast.success('Copied to clipboard'); + toast.success('Copied to clipboard!'); onCopy(); - } + }, [analytics, onCopy]); if (!currentAccount) return null; diff --git a/src/app/pages/select-network/select-network.tsx b/src/app/pages/select-network/select-network.tsx index 17573742a28..fb9497a07d8 100644 --- a/src/app/pages/select-network/select-network.tsx +++ b/src/app/pages/select-network/select-network.tsx @@ -4,6 +4,7 @@ import { WalletDefaultNetworkConfigurationIds } from '@shared/constants'; import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useLocationState } from '@app/common/hooks/use-location-state'; import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { NetworkListLayout } from '@app/pages/select-network/components/network-list.layout'; import { NetworkListItem } from '@app/pages/select-network/network-list-item'; @@ -20,6 +21,7 @@ export function SelectNetwork() { const analytics = useAnalytics(); const networksActions = useNetworksActions(); const currentNetwork = useCurrentNetworkState(); + const backgroundLocation = useLocationState('backgroundLocation'); function addNetwork() { void analytics.track('add_network'); @@ -38,7 +40,7 @@ export function SelectNetwork() { } function closeNetworkModal() { - navigate('..'); + navigate(backgroundLocation.pathname ?? '..'); } return ( diff --git a/src/app/pages/sign-out-confirm/sign-out-confirm.tsx b/src/app/pages/sign-out-confirm/sign-out-confirm.tsx index eba398fe0a3..c8653da60e9 100644 --- a/src/app/pages/sign-out-confirm/sign-out-confirm.tsx +++ b/src/app/pages/sign-out-confirm/sign-out-confirm.tsx @@ -3,12 +3,14 @@ import { useNavigate } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; +import { useLocationState } from '@app/common/hooks/use-location-state'; import { SignOutConfirmLayout } from './sign-out-confirm.layout'; export function SignOutConfirmDrawer() { const { signOut } = useKeyActions(); const navigate = useNavigate(); + const backgroundLocation = useLocationState('backgroundLocation'); return ( navigate('..')} + onUserSafelyReturnToHomepage={() => navigate(backgroundLocation.pathname ?? '..')} /> ); } diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 1ef80f077f9..a39b82fa74b 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -9,11 +9,9 @@ import { import { RouteUrls } from '@shared/route-urls'; -import { BroadcastErrorDrawer } from '@app/components/broadcast-error-drawer/broadcast-error-drawer'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { ActivityList } from '@app/features/activity-list/activity-list'; import { AddNetwork } from '@app/features/add-network/add-network'; -import { AssetsList } from '@app/features/asset-list/asset-list'; import { Container } from '@app/features/container/container'; import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer'; import { IncreaseBtcFeeDrawer } from '@app/features/increase-fee-drawer/increase-btc-fee-drawer'; @@ -23,10 +21,8 @@ import { leatherIntroDialogRoutes } from '@app/features/leather-intro-dialog/lea import { ledgerJwtSigningRoutes } from '@app/features/ledger/flows/jwt-signing/ledger-sign-jwt.routes'; import { requestBitcoinKeysRoutes } from '@app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys'; import { requestStacksKeysRoutes } from '@app/features/ledger/flows/request-stacks-keys/ledger-request-stacks-keys'; -import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-tx.routes'; import { RetrieveTaprootToNativeSegwit } from '@app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit'; -import { ThemesDrawer } from '@app/features/theme-drawer/theme-drawer'; import { BitcoinContractRequest } from '@app/pages/bitcoin-contract-request/bitcoin-contract-request'; import { ChooseAccount } from '@app/pages/choose-account/choose-account'; import { FundPage } from '@app/pages/fund/fund'; @@ -35,39 +31,25 @@ import { AllowDiagnosticsModal } from '@app/pages/onboarding/allow-diagnostics/a import { BackUpSecretKeyPage } from '@app/pages/onboarding/back-up-secret-key/back-up-secret-key'; import { SignIn } from '@app/pages/onboarding/sign-in/sign-in'; import { WelcomePage } from '@app/pages/onboarding/welcome/welcome'; -import { PsbtRequest } from '@app/pages/psbt-request/psbt-request'; -import { ReceiveBtcModal } from '@app/pages/receive/receive-btc'; -import { ReceiveModal } from '@app/pages/receive/receive-modal'; -import { ReceiveOrdinalModal } from '@app/pages/receive/receive-ordinal'; -import { ReceiveStxModal } from '@app/pages/receive/receive-stx'; import { RequestError } from '@app/pages/request-error/request-error'; -import { RpcGetAddresses } from '@app/pages/rpc-get-addresses/rpc-get-addresses'; -import { rpcSendTransferRoutes } from '@app/pages/rpc-send-transfer/rpc-send-transfer.routes'; -import { RpcSignPsbt } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt'; -import { RpcSignPsbtSummary } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt-summary'; import { RpcSignStacksTransaction } from '@app/pages/rpc-sign-stacks-transaction/rpc-sign-stacks-transaction'; -import { SelectNetwork } from '@app/pages/select-network/select-network'; import { BroadcastError } from '@app/pages/send/broadcast-error/broadcast-error'; import { LockBitcoinSummary } from '@app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary'; -import { SendInscriptionContainer } from '@app/pages/send/ordinal-inscription/components/send-inscription-container'; -import { SendInscriptionChooseFee } from '@app/pages/send/ordinal-inscription/send-inscription-choose-fee'; -import { SendInscriptionForm } from '@app/pages/send/ordinal-inscription/send-inscription-form'; -import { SendInscriptionReview } from '@app/pages/send/ordinal-inscription/send-inscription-review'; -import { SendInscriptionSummary } from '@app/pages/send/ordinal-inscription/sent-inscription-summary'; import { sendCryptoAssetFormRoutes } from '@app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes'; -import { SignOutConfirmDrawer } from '@app/pages/sign-out-confirm/sign-out-confirm'; -import { StacksMessageSigningRequest } from '@app/pages/stacks-message-signing-request/stacks-message-signing-request'; import { swapRoutes } from '@app/pages/swap/swap.routes'; -import { TransactionRequest } from '@app/pages/transaction-request/transaction-request'; import { UnauthorizedRequest } from '@app/pages/unauthorized-request/unauthorized-request'; import { Unlock } from '@app/pages/unlock'; -import { ProfileUpdateRequest } from '@app/pages/update-profile-request/update-profile-request'; import { ViewSecretKey } from '@app/pages/view-secret-key/view-secret-key'; import { AccountGate } from '@app/routes/account-gate'; +import { sendOrdinalRoutes } from '@app/routes/ordinal-routes'; +import { receiveRoutes } from '@app/routes/receive-routes'; +import { legacyRequestRoutes } from '@app/routes/request-routes'; +import { rpcRequestRoutes } from '@app/routes/rpc-routes'; +import { settingsRoutes } from '@app/routes/settings-routes'; import { OnboardingGate } from './onboarding-gate'; -function SuspenseLoadingSpinner() { +export function SuspenseLoadingSpinner() { return ; } @@ -77,160 +59,41 @@ export function AppRoutes() { } function useAppRoutes() { - const settingsModalRoutes = ( - - } /> - } /> - } /> - - ); - - const legacyRequestRoutes = ( - <> - - }> - - - - } - > - {ledgerStacksTxSigningRoutes} - } /> - } /> - - - }> - - - - } - > - {ledgerStacksMessageSigningRoutes} - - - }> - - - - } - /> - - }> - - - - } - /> - - ); - - const rpcRequestRoutes = ( - <> - - - - } - /> - {rpcSendTransferRoutes} - { - const { RpcSignBip322MessageRoute } = await import( - '@app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message' - ); - return { Component: RpcSignBip322MessageRoute }; - }} - /> - - - - } - /> - - - - } - /> - - ); - return createHashRouter( createRoutesFromElements( }> } > - } /> - } /> - - {leatherIntroDialogRoutes} - - {requestBitcoinKeysRoutes} - {requestStacksKeysRoutes} - } /> - - }> - {ledgerStacksTxSigningRoutes} + {/* Need to declare this here so settings fire on activity */} + }> + {settingsRoutes} - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - - }> - } /> - } - /> - } - /> - } - /> - } /> - + {/* Modal routes overlaid on home need to be nested here + with relative paths to open in new tabs */} + {receiveRoutes} + {settingsRoutes} + {ledgerStacksTxSigningRoutes} + {sendOrdinalRoutes} + - {settingsModalRoutes} + {requestBitcoinKeysRoutes} + {requestStacksKeysRoutes} + } /> + }> {ledgerStacksTxSigningRoutes} + } /> + } /> + + {ledgerStacksTxSigningRoutes} + {ledgerJwtSigningRoutes} + } > - } /> - } /> - } /> - {settingsModalRoutes} + {receiveRoutes} + {settingsRoutes} {sendCryptoAssetFormRoutes} @@ -324,10 +186,10 @@ function useAppRoutes() { } > - {settingsModalRoutes} + {settingsRoutes} }> - {settingsModalRoutes} + {settingsRoutes} {leatherIntroDialogRoutes} diff --git a/src/app/routes/legacy-request-routes.tsx b/src/app/routes/legacy-request-routes.tsx new file mode 100644 index 00000000000..834ffc3f11e --- /dev/null +++ b/src/app/routes/legacy-request-routes.tsx @@ -0,0 +1,61 @@ +// https://gist.github.com/Azayzel/6438e3c5b2d16381ced14fe0da78c123 +// investigaste moving to an object config to clean this up. Also possibly using lazy load (maybe not now) +// https://www.youtube.com/watch?v=YF9L6PUAMWk +// https://www.ryanjyost.com/react-routing/ +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { ReceiveBtcModal } from '@app/pages/receive/receive-btc'; +import { ReceiveModal } from '@app/pages/receive/receive-modal'; +import { ReceiveOrdinalModal } from '@app/pages/receive/receive-ordinal'; +import { ReceiveStxModal } from '@app/pages/receive/receive-stx'; + +export function ReceiveRoutes() { + return ( + + }> + } /> + } /> + {/* Needed to show receive ordinal in Receive flow */} + } /> + + + {/* Needed to show receive ordinal in Add flow */} + } + /> + } /> + + ); +} + +export const receiveRoutesArray = [ + { + path: RouteUrls.Receive, + element: , + routes: [ + { + path: RouteUrls.ReceiveStx, + element: , + }, + { + path: RouteUrls.ReceiveCollectibleOrdinal, + element: , + }, + { + path: RouteUrls.ReceiveBtc, + element: , + }, + ], + }, + { + path: `${RouteUrls.Receive}/${RouteUrls.ReceiveCollectible}`, + element: , + }, + { + path: RouteUrls.ReceiveCollectibleOrdinal, + element: , + }, +]; diff --git a/src/app/routes/ordinal-routes.tsx b/src/app/routes/ordinal-routes.tsx new file mode 100644 index 00000000000..41e8e02516d --- /dev/null +++ b/src/app/routes/ordinal-routes.tsx @@ -0,0 +1,23 @@ +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { BroadcastError } from '@app/pages/send/broadcast-error/broadcast-error'; +import { SendInscriptionContainer } from '@app/pages/send/ordinal-inscription/components/send-inscription-container'; +import { SendInscriptionChooseFee } from '@app/pages/send/ordinal-inscription/send-inscription-choose-fee'; +import { SendInscriptionForm } from '@app/pages/send/ordinal-inscription/send-inscription-form'; +import { SendInscriptionReview } from '@app/pages/send/ordinal-inscription/send-inscription-review'; +import { SendInscriptionSummary } from '@app/pages/send/ordinal-inscription/sent-inscription-summary'; + +export const sendOrdinalRoutes = ( + }> + } /> + } + /> + } /> + } /> + } /> + +); diff --git a/src/app/routes/receive-routes.tsx b/src/app/routes/receive-routes.tsx new file mode 100644 index 00000000000..47d56cc4d62 --- /dev/null +++ b/src/app/routes/receive-routes.tsx @@ -0,0 +1,27 @@ +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { ReceiveBtcModal } from '@app/pages/receive/receive-btc'; +import { ReceiveModal } from '@app/pages/receive/receive-modal'; +import { ReceiveOrdinalModal } from '@app/pages/receive/receive-ordinal'; +import { ReceiveStxModal } from '@app/pages/receive/receive-stx'; + +export const receiveRoutes = ( + + }> + } /> + } /> + } /> + {/* Needed to show receive ordinal in Receive flow */} + } /> + + + {/* Needed to show receive ordinal in Add flow */} + } + /> + } /> + +); diff --git a/src/app/routes/request-routes.tsx b/src/app/routes/request-routes.tsx new file mode 100644 index 00000000000..c8dd964ecd9 --- /dev/null +++ b/src/app/routes/request-routes.tsx @@ -0,0 +1,67 @@ +import { Suspense } from 'react'; +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { BroadcastErrorDrawer } from '@app/components/broadcast-error-drawer/broadcast-error-drawer'; +import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer'; +import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; +import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-tx.routes'; +import { PsbtRequest } from '@app/pages/psbt-request/psbt-request'; +import { StacksMessageSigningRequest } from '@app/pages/stacks-message-signing-request/stacks-message-signing-request'; +import { TransactionRequest } from '@app/pages/transaction-request/transaction-request'; +import { ProfileUpdateRequest } from '@app/pages/update-profile-request/update-profile-request'; +import { AccountGate } from '@app/routes/account-gate'; +import { SuspenseLoadingSpinner } from '@app/routes/app-routes'; + +// #4028: TODO These are labelled legacy, maybe we can just remove them? +export const legacyRequestRoutes = ( + <> + + }> + + + + } + > + {ledgerStacksTxSigningRoutes} + } /> + } /> + + + }> + + + + } + > + {ledgerStacksMessageSigningRoutes} + + + }> + + + + } + /> + + }> + + + + } + /> + +); diff --git a/src/app/routes/rpc-routes.tsx b/src/app/routes/rpc-routes.tsx new file mode 100644 index 00000000000..9b0b2aeb9d6 --- /dev/null +++ b/src/app/routes/rpc-routes.tsx @@ -0,0 +1,39 @@ +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { RpcGetAddresses } from '@app/pages/rpc-get-addresses/rpc-get-addresses'; +import { rpcSendTransferRoutes } from '@app/pages/rpc-send-transfer/rpc-send-transfer.routes'; +import { RpcSignPsbt } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt'; +import { AccountGate } from '@app/routes/account-gate'; + +export const rpcRequestRoutes = ( + <> + + + + } + /> + {rpcSendTransferRoutes} + { + const { RpcSignBip322MessageRoute } = await import( + '@app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message' + ); + return { Component: RpcSignBip322MessageRoute }; + }} + /> + + + + } + /> + +); diff --git a/src/app/routes/settings-routes.tsx b/src/app/routes/settings-routes.tsx new file mode 100644 index 00000000000..a4d69e69c1d --- /dev/null +++ b/src/app/routes/settings-routes.tsx @@ -0,0 +1,15 @@ +import { Route } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { ThemesDrawer } from '@app/features/theme-drawer/theme-drawer'; +import { SelectNetwork } from '@app/pages/select-network/select-network'; +import { SignOutConfirmDrawer } from '@app/pages/sign-out-confirm/sign-out-confirm'; + +export const settingsRoutes = ( + + } /> + } /> + } /> + +); diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index 266fe6478c7..5887de98e79 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -23,22 +23,27 @@ export enum RouteUrls { // Active wallet routes Home = '/', - Activity = '/activity', + // Tab nested relative paths + Activity = 'activity', + // Active wallet routes AddNetwork = '/add-network', ChooseAccount = '/choose-account', Fund = '/fund', + // TODO investigate deprecating fund routes FundReceive = '/fund/receive', FundReceiveStx = '/fund/receive/stx', FundReceiveBtc = '/fund/receive/btc', IncreaseStxFee = '/increase-fee/stx', IncreaseBtcFee = '/increase-fee/btc', IncreaseFeeSent = '/increase-fee/sent', - Receive = '/receive', - ReceiveCollectible = '/receive/collectible', - ReceiveCollectibleOrdinal = '/receive/collectible/ordinal', - ReceiveStx = '/receive/stx', - ReceiveBtc = '/receive/btc', - ReceiveBtcStamp = '/receive/btc-stamp', + // nested routes must have relative paths + Receive = 'receive', + // TODO - investigate un-nesting stx and btc as it may make for cleaner re-directs elsewhere - manybe pass prop? + ReceiveStx = 'stx', + ReceiveBtc = 'btc', + ReceiveBtcStamp = 'btc-stamp', + ReceiveCollectible = 'collectible', + ReceiveCollectibleOrdinal = 'collectible/ordinal', Send = '/send-transaction', ViewSecretKey = '/view-secret-key', @@ -55,7 +60,7 @@ export enum RouteUrls { EditNonce = 'edit-nonce', SelectNetwork = 'choose-network', SignOutConfirm = 'sign-out', - RetriveTaprootFunds = 'retrive-taproot-funds', + RetrieveTaprootFunds = 'retrieve-taproot-funds', // Send crypto asset routes SendCryptoAsset = '/send', @@ -76,12 +81,12 @@ export enum RouteUrls { SentBrc20Summary = '/send/brc20/:ticker/summary', // Send ordinal inscriptions - SendOrdinalInscription = '/send/ordinal-inscription', - SendOrdinalInscriptionChooseFee = '/send/ordinal-inscription/choose-fee', - SendOrdinalInscriptionReview = '/send/ordinal-inscription/review', - SendOrdinalInscriptionSummary = '/send/ordinal-inscription/', - SendOrdinalInscriptionSent = '/send/ordinal-inscription/sent', - SendOrdinalInscriptionError = '/send/ordinal-inscription/error', + SendOrdinalInscription = 'send/ordinal-inscription', + SendOrdinalInscriptionChooseFee = 'send/ordinal-inscription/choose-fee', + SendOrdinalInscriptionReview = 'send/ordinal-inscription/review', + SendOrdinalInscriptionSummary = 'send/ordinal-inscription/', + SendOrdinalInscriptionSent = 'send/ordinal-inscription/sent', + SendOrdinalInscriptionError = 'send/ordinal-inscription/error', // Swap routes Swap = '/swap',