From 5153b2227ea1aee15521b3645860a84f7d55c87b Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Fri, 6 Dec 2024 13:40:39 +0100 Subject: [PATCH] feat: migrate legacy auth request to approver ux --- .eslintrc.cjs | 5 +- src/app/common/focus-tab.ts | 6 + src/app/common/utils.ts | 12 -- .../connect-account.layout.tsx} | 10 +- src/app/components/requester-flag.tsx | 25 ---- .../current-account-displayer.tsx | 47 +++++++ .../pages/choose-account/choose-account.tsx | 53 ------- .../choose-account/components/accounts.tsx | 129 ------------------ .../legacy-account-auth.tsx | 41 ++++++ .../rpc-get-addresses/rpc-get-addresses.tsx | 47 +------ .../rpc-get-addresses/use-get-addresses.ts | 6 +- src/app/routes/app-routes.tsx | 4 +- tests/specs/transactions/transactions.spec.ts | 7 +- 13 files changed, 113 insertions(+), 279 deletions(-) create mode 100644 src/app/common/focus-tab.ts rename src/app/{pages/rpc-get-addresses/components/get-addresses.layout.tsx => components/connect-account/connect-account.layout.tsx} (95%) delete mode 100644 src/app/components/requester-flag.tsx create mode 100644 src/app/features/current-account/current-account-displayer.tsx delete mode 100644 src/app/pages/choose-account/choose-account.tsx delete mode 100644 src/app/pages/choose-account/components/accounts.tsx create mode 100644 src/app/pages/legacy-account-auth/legacy-account-auth.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8b854719a1a..6fbcb6000d1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -25,16 +25,13 @@ module.exports = { 'plugin:storybook/csf', ], ignorePatterns: ['./leather-styles'], - plugins: ['react', 'react-hooks', '@typescript-eslint', 'deprecation'], + plugins: ['react', 'react-hooks', '@typescript-eslint'], settings: { react: { version: 'detect', }, }, rules: { - // This rule helps highlight areas of the code that use deprecated - // methods, such as implicit use of signed transactions - 'deprecation/deprecation': 'warn', 'no-console': ['error'], 'no-duplicate-imports': ['error'], 'prefer-const': [ diff --git a/src/app/common/focus-tab.ts b/src/app/common/focus-tab.ts new file mode 100644 index 00000000000..e7b1bba19ba --- /dev/null +++ b/src/app/common/focus-tab.ts @@ -0,0 +1,6 @@ +export function focusTabAndWindow(tabId: number | null) { + chrome.tabs.update(tabId ?? 0, { active: true }, tab => { + if (!tab) return; + chrome.windows.update(tab.windowId, { focused: true }); + }); +} diff --git a/src/app/common/utils.ts b/src/app/common/utils.ts index 46c0d8a3475..cbb00e8c663 100644 --- a/src/app/common/utils.ts +++ b/src/app/common/utils.ts @@ -204,18 +204,6 @@ export function hexToHumanReadable(hex: string) { return `0x${hex}`; } -export const slugify = (...args: (string | number)[]): string => { - const value = args.join(' '); - - return value - .normalize('NFD') // split an accented letter in the base letter and the accent - .replace(/[\u0300-\u036f]/g, '') // remove all previously split accents - .toLowerCase() - .trim() - .replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced) - .replace(/\s+/g, '-'); // separator -}; - export function getUrlHostname(url: string) { return new URL(url).hostname; } diff --git a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx b/src/app/components/connect-account/connect-account.layout.tsx similarity index 95% rename from src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx rename to src/app/components/connect-account/connect-account.layout.tsx index 6a40b309560..cdac904474b 100644 --- a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx +++ b/src/app/components/connect-account/connect-account.layout.tsx @@ -18,20 +18,20 @@ import { closeWindow } from '@shared/utils'; import { useOnMount } from '@app/common/hooks/use-on-mount'; import { FaviconDisplayer } from '@app/components/favicon-displayer/favicon-displayer'; -interface GetAddressesLayoutProps { +interface ConnectAccountLayoutProps { requester: string; switchAccount: ReactNode; onBeforeAnimation?(): void; - onUserApprovesGetAddresses(): void; + onUserApprovesGetAddresses(): void | Promise; onClickRequestedByLink(): void; } -export function GetAddressesLayout({ +export function ConnectAccountLayout({ requester, switchAccount, onBeforeAnimation, onUserApprovesGetAddresses, onClickRequestedByLink, -}: GetAddressesLayoutProps) { +}: ConnectAccountLayoutProps) { const originLogoAnimation = useAnimationControls(); const contentDisappears = useAnimationControls(); const checkmarkEnters = useAnimationControls(); @@ -61,7 +61,7 @@ export function GetAddressesLayout({ }); await checkmarkEnters.start({ scale: 0.5, dur: 0.32 }); await delay(280); - onUserApprovesGetAddresses(); + await onUserApprovesGetAddresses(); await delay(280); await originLogoAnimation.start({ scale: 0, diff --git a/src/app/components/requester-flag.tsx b/src/app/components/requester-flag.tsx deleted file mode 100644 index 068a87ec593..00000000000 --- a/src/app/components/requester-flag.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { styled } from 'leather-styles/jsx'; - -import { Flag } from '@leather.io/ui'; - -import { Favicon } from './favicon'; - -interface RequesterFlagProps { - requester: string; -} - -export function RequesterFlag({ requester }: RequesterFlagProps) { - return ( - } - align="middle" - justifyContent="center" - py="space.04" - px="space.02" - borderRadius="sm" - width="fit-content" - > - {requester} - - ); -} diff --git a/src/app/features/current-account/current-account-displayer.tsx b/src/app/features/current-account/current-account-displayer.tsx new file mode 100644 index 00000000000..585b75eb5f8 --- /dev/null +++ b/src/app/features/current-account/current-account-displayer.tsx @@ -0,0 +1,47 @@ +import { Box } from 'leather-styles/jsx'; + +import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; +import { AccountTotalBalance } from '@app/components/account-total-balance'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; +import { AccountNameLayout } from '@app/components/account/account-name'; +import { useCurrentAccountIndex } from '@app/store/accounts/account'; +import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { AccountAvatarItem } from '@app/ui/components/account/account-avatar/account-avatar-item'; + +interface CurrentAccountDisplayerProps { + onSelectAccount(): void; +} +export function CurrentAccountDisplayer({ onSelectAccount }: CurrentAccountDisplayerProps) { + const index = useCurrentAccountIndex(); + const stacksAccounts = useStacksAccounts(); + const stxAddress = stacksAccounts[index]?.address || ''; + const { data: name = '' } = useAccountDisplayName({ address: stxAddress, index }); + const bitcoinSigner = useNativeSegwitSigner(index); + const bitcoinAddress = bitcoinSigner?.(0).address || ''; + return ( + } + accountName={{name}} + avatar={ + + } + balanceLabel={ + // Hack to center element without adjusting AccountListItemLayout + + + + } + index={index} + isLoading={false} + isSelected={false} + onSelectAccount={() => onSelectAccount()} + /> + ); +} diff --git a/src/app/pages/choose-account/choose-account.tsx b/src/app/pages/choose-account/choose-account.tsx deleted file mode 100644 index 2b0eccc8e10..00000000000 --- a/src/app/pages/choose-account/choose-account.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect } from 'react'; -import { Outlet } from 'react-router-dom'; - -import { Flex, Stack, styled } from 'leather-styles/jsx'; - -import { LeatherLogomarkIcon } from '@leather.io/ui'; - -import { closeWindow } from '@shared/utils'; - -import { useCancelAuthRequest } from '@app/common/authentication/use-cancel-auth-request'; -import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; -import { RequesterFlag } from '@app/components/requester-flag'; -import { ChooseAccountsList } from '@app/pages/choose-account/components/accounts'; -import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -export function ChooseAccount() { - const { url } = useAppDetails(); - const accounts = useStacksAccounts(); - const hasConnectedStacksAccounts = accounts.length > 0; - - const cancelAuthentication = useCancelAuthRequest(); - - useOnOriginTabClose(() => closeWindow()); - - const handleUnmount = async () => cancelAuthentication(); - - useEffect(() => { - window.addEventListener('beforeunload', handleUnmount); - return () => window.removeEventListener('beforeunload', handleUnmount); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - <> - - - {url && } - - - - {hasConnectedStacksAccounts - ? 'Choose an account to connect' - : 'No connected accounts found'} - - - - {hasConnectedStacksAccounts && } - - - - ); -} diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/pages/choose-account/components/accounts.tsx deleted file mode 100644 index cb6ca27c3d4..00000000000 --- a/src/app/pages/choose-account/components/accounts.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Suspense, memo, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Virtuoso } from 'react-virtuoso'; - -import { Box, Flex, FlexProps, HStack, Stack, styled } from 'leather-styles/jsx'; - -import { PlusIcon, Pressable } from '@leather.io/ui'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useFinishAuthRequest } from '@app/common/authentication/use-finish-auth-request'; -import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; -import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { slugify } from '@app/common/utils'; -import { AccountTotalBalance } from '@app/components/account-total-balance'; -import { AcccountAddresses } from '@app/components/account/account-addresses'; -import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; -import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; -import { AccountAvatar } from '@app/ui/components/account/account-avatar/account-avatar'; - -interface AccountTitlePlaceholderProps { - account: StacksAccount; -} -function AccountTitlePlaceholder({ account }: AccountTitlePlaceholderProps) { - const name = `Account ${account?.index + 1}`; - return {name}; -} - -interface ChooseAccountItemProps extends FlexProps { - selectedAddress?: string | null; - isLoading: boolean; - account: StacksAccount; - onSelectAccount(index: number): void; -} -const ChooseAccountItem = memo( - ({ account, isLoading, onSelectAccount }: ChooseAccountItemProps) => { - const { data: name = '' } = useAccountDisplayName(account); - - const btcAddress = useNativeSegwitAccountIndexAddressIndexZero(account.index); - - const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); - - return ( - } - accountName={ - }> - {name} - - } - avatar={ - - } - balanceLabel={} - data-testid={`account-${accountSlug}-${account.index}`} - index={account.index} - isLoading={isLoading} - isSelected={false} - onSelectAccount={() => onSelectAccount(account.index)} - /> - ); - } -); - -function AddAccountAction() { - const createAccount = useCreateAccount(); - - return ( - - - - - Generate new account - - - - ); -} - -export function ChooseAccountsList() { - const finishSignIn = useFinishAuthRequest(); - const { whenWallet } = useWalletType(); - const accounts = useStacksAccounts(); - const navigate = useNavigate(); - const [selectedAccount, setSelectedAccount] = useState(null); - - const signIntoAccount = async (index: number) => { - setSelectedAccount(index); - await whenWallet({ - async software() { - await finishSignIn(index); - }, - async ledger() { - navigate(RouteUrls.ConnectLedger, { state: { index } }); - }, - })(); - }; - - if (!accounts) return null; - - return ( - - {whenWallet({ software: , ledger: <> })} - - - ( - - - - )} - /> - - - ); -} diff --git a/src/app/pages/legacy-account-auth/legacy-account-auth.tsx b/src/app/pages/legacy-account-auth/legacy-account-auth.tsx new file mode 100644 index 00000000000..e8bc5a52043 --- /dev/null +++ b/src/app/pages/legacy-account-auth/legacy-account-auth.tsx @@ -0,0 +1,41 @@ +import { closeWindow } from '@shared/utils'; + +// import { useCancelAuthRequest } from '@app/common/authentication/use-cancel-auth-request'; +import { useFinishAuthRequest } from '@app/common/authentication/use-finish-auth-request'; +import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; +// import { useOnMount } from '@app/common/hooks/use-on-mount'; +import { useSwitchAccountSheet } from '@app/common/switch-account/use-switch-account-sheet-context'; +import { openInNewTab } from '@app/common/utils/open-in-new-tab'; +import { CurrentAccountDisplayer } from '@app/features/current-account/current-account-displayer'; +import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; +import { useCurrentAccountIndex } from '@app/store/accounts/account'; + +import { ConnectAccountLayout } from '../../components/connect-account/connect-account.layout'; + +export function LegacyAccountAuth() { + const { url } = useAppDetails(); + const accountIndex = useCurrentAccountIndex(); + const finishSignIn = useFinishAuthRequest(); + const { toggleSwitchAccount } = useSwitchAccountSheet(); + + useOnOriginTabClose(() => closeWindow()); + + // const cancelAuthentication = useCancelAuthRequest(); + + // const handleUnmount = async () => cancelAuthentication(); + // useOnMount(() => window.addEventListener('beforeunload', handleUnmount)); + + if (!url) throw new Error('No app details found'); + + return ( + finishSignIn(accountIndex)} + // Here we should refocus the tab that initiated the request, however + // because the old auth code doesn't have the tab id and should be + // eventually removed, we just open in a new tab + onClickRequestedByLink={() => openInNewTab(url.origin)} + switchAccount={} + /> + ); +} diff --git a/src/app/pages/rpc-get-addresses/rpc-get-addresses.tsx b/src/app/pages/rpc-get-addresses/rpc-get-addresses.tsx index f3b899ca591..c9ff5ec2f4b 100644 --- a/src/app/pages/rpc-get-addresses/rpc-get-addresses.tsx +++ b/src/app/pages/rpc-get-addresses/rpc-get-addresses.tsx @@ -1,30 +1,14 @@ -import { Box } from 'leather-styles/jsx'; - import { closeWindow } from '@shared/utils'; -import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useSwitchAccountSheet } from '@app/common/switch-account/use-switch-account-sheet-context'; -import { AccountTotalBalance } from '@app/components/account-total-balance'; -import { AcccountAddresses } from '@app/components/account/account-addresses'; -import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; -import { AccountNameLayout } from '@app/components/account/account-name'; +import { CurrentAccountDisplayer } from '@app/features/current-account/current-account-displayer'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; -import { useCurrentAccountIndex } from '@app/store/accounts/account'; -import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { AccountAvatarItem } from '@app/ui/components/account/account-avatar/account-avatar-item'; -import { GetAddressesLayout } from './components/get-addresses.layout'; +import { ConnectAccountLayout } from '../../components/connect-account/connect-account.layout'; import { useGetAddresses } from './use-get-addresses'; export function RpcGetAddresses() { const { focusInitatingTab, origin, onUserApproveGetAddresses } = useGetAddresses(); - const index = useCurrentAccountIndex(); - const stacksAccounts = useStacksAccounts(); - const stxAddress = stacksAccounts[index]?.address || ''; - const { data: name = '' } = useAccountDisplayName({ address: stxAddress, index }); - const bitcoinSigner = useNativeSegwitSigner(index); - const bitcoinAddress = bitcoinSigner?.(0).address || ''; useOnOriginTabClose(() => closeWindow()); @@ -36,33 +20,10 @@ export function RpcGetAddresses() { } return ( - } - accountName={{name}} - avatar={ - - } - balanceLabel={ - // Hack to center element without adjusting AccountListItemLayout - - - - } - index={0} - isLoading={false} - isSelected={false} - onSelectAccount={() => toggleSwitchAccount()} - /> - } + switchAccount={} onUserApprovesGetAddresses={onUserApproveGetAddresses} /> ); diff --git a/src/app/pages/rpc-get-addresses/use-get-addresses.ts b/src/app/pages/rpc-get-addresses/use-get-addresses.ts index fdb511b599e..dabf0a01bae 100644 --- a/src/app/pages/rpc-get-addresses/use-get-addresses.ts +++ b/src/app/pages/rpc-get-addresses/use-get-addresses.ts @@ -7,6 +7,7 @@ import { logger } from '@shared/logger'; import { makeRpcSuccessResponse } from '@shared/rpc/rpc-methods'; import { analytics } from '@shared/utils/analytics'; +import { focusTabAndWindow } from '@app/common/focus-tab'; import { useRpcRequestParams } from '@app/common/rpc-helpers'; import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; @@ -23,10 +24,7 @@ export function useGetAddresses() { function focusInitatingTab() { void analytics.track('user_clicked_requested_by_link', { endpoint: 'getAddresses' }); - chrome.tabs.update(tabId ?? 0, { active: true }, tab => { - if (!tab) return; - chrome.windows.update(tab.windowId, { focused: true }); - }); + focusTabAndWindow(tabId); } return { diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 0bf52bfc341..11848ea7037 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -30,10 +30,10 @@ import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-t import { UnsupportedBrowserLayout } from '@app/features/ledger/generic-steps'; import { ConnectLedgerStart } from '@app/features/ledger/generic-steps/connect-device/connect-ledger-start'; import { RetrieveTaprootToNativeSegwit } from '@app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit'; -import { ChooseAccount } from '@app/pages/choose-account/choose-account'; import { ChooseCryptoAssetToFund } from '@app/pages/fund/choose-asset-to-fund/choose-asset-to-fund'; import { FundPage } from '@app/pages/fund/fund'; import { Home } from '@app/pages/home/home'; +import { LegacyAccountAuth } from '@app/pages/legacy-account-auth/legacy-account-auth'; 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'; @@ -262,7 +262,7 @@ function useAppRoutes() { path={RouteUrls.ChooseAccount} element={ - + } > diff --git a/tests/specs/transactions/transactions.spec.ts b/tests/specs/transactions/transactions.spec.ts index 84b38d5a537..c2ecc7c52b9 100644 --- a/tests/specs/transactions/transactions.spec.ts +++ b/tests/specs/transactions/transactions.spec.ts @@ -3,7 +3,7 @@ import { TestAppPage } from '@tests/page-object-models/test-app.page'; import { TransactionRequestPage } from '@tests/page-object-models/transaction-request.page'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; -import { stxToMicroStx } from '@leather.io/utils'; +import { delay, stxToMicroStx } from '@leather.io/utils'; import { test } from '../../fixtures/fixtures'; @@ -26,7 +26,9 @@ test.describe('Transaction signing', () => { const newPagePromise = context.waitForEvent('page'); await testAppPage.page.getByTestId(OnboardingSelectors.SignUpBtn).click(); const accountsPage = await newPagePromise; + await accountsPage.getByTestId('switch-account-item-0').click({ force: true }); await accountsPage.getByTestId('switch-account-item-1').click({ force: true }); + await accountsPage.getByRole('button').getByText('Confirm').click({ force: true }); await testAppPage.page.bringToFront(); await testAppPage.page.click('text=Debugger', { timeout: 30000, @@ -48,7 +50,8 @@ test.describe('Transaction signing', () => { const newPagePromise = context.waitForEvent('page'); await testAppPage.page.getByTestId(OnboardingSelectors.SignUpBtn).click(); const accountsPage = await newPagePromise; - await accountsPage.getByTestId('switch-account-item-0').click({ force: true }); + await accountsPage.getByRole('button').getByText('Confirm').click({ force: true }); + await delay(2000); await testAppPage.page.bringToFront(); await testAppPage.page.click('text=Debugger', { timeout: 30000,