From a221c815b66990aa0540039f140b47db5b4e0f96 Mon Sep 17 00:00:00 2001 From: Pavlos Chrysochoidis <10210143+pchrysochoidis@users.noreply.github.com> Date: Fri, 24 Feb 2023 13:05:11 +0000 Subject: [PATCH] wallet-ext: allow selecting the accounts to connect to dapp (#8413) * adds WalletListSelect component that allows selecting between the accounts of the wallet and creating new ones * when multiaccounts is enabled while connecting to a dapp user is able to choose which accounts to connect Screenshot 2023-02-18 at 21 17 55 When multiaccount is disabled Screenshot 2023-02-19 at 22 36 21 When multiaccount is enabled and user has more than one account https://user-images.githubusercontent.com/10210143/219980417-79a944d4-b409-4046-832a-09d8b98b2c40.mov NOTE: To check the accounts that are connected * switch between accounts and check the dapp status popup * or run ``` chrome.storage.local.get('permissions').then(({permissions: p}) => console.log(p['http://localhost:3000'].accounts)) ``` in the console of the extension. Screenshot 2023-02-18 at 21 22 28 Closes APPS-283 --- .../src/ui/app/components/SummaryCard.tsx | 52 +++++++++++ .../ui/app/components/WalletListSelect.tsx | 81 +++++++++++++++++ .../app/components/WalletListSelectItem.tsx | 38 ++++++++ .../menu/content/AccountsSettings.tsx | 18 +--- .../user-approve-container/index.tsx | 89 ++++++++----------- .../app/hooks/useDeriveNextAccountMutation.ts | 23 +++++ .../ui/app/pages/layout/Layout.module.scss | 1 + .../site-connect/SiteConnectPage.module.scss | 13 +-- .../src/ui/app/pages/site-connect/index.tsx | 85 +++++++++++++----- 9 files changed, 308 insertions(+), 92 deletions(-) create mode 100644 apps/wallet/src/ui/app/components/SummaryCard.tsx create mode 100644 apps/wallet/src/ui/app/components/WalletListSelect.tsx create mode 100644 apps/wallet/src/ui/app/components/WalletListSelectItem.tsx create mode 100644 apps/wallet/src/ui/app/hooks/useDeriveNextAccountMutation.ts diff --git a/apps/wallet/src/ui/app/components/SummaryCard.tsx b/apps/wallet/src/ui/app/components/SummaryCard.tsx new file mode 100644 index 0000000000000..8599d95aa2b43 --- /dev/null +++ b/apps/wallet/src/ui/app/components/SummaryCard.tsx @@ -0,0 +1,52 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { cx } from 'class-variance-authority'; + +import { Text } from '../shared/text'; + +import type { ReactNode } from 'react'; + +export type SummaryCardProps = { + header?: string; + body: ReactNode; + footer?: ReactNode; + minimalPadding?: boolean; +}; + +export function SummaryCard({ + body, + header, + footer, + minimalPadding, +}: SummaryCardProps) { + return ( +
+ {header ? ( +
+ + {header} + +
+ ) : null} +
+ {body} +
+ {footer ? ( +
+ {footer} +
+ ) : null} +
+ ); +} diff --git a/apps/wallet/src/ui/app/components/WalletListSelect.tsx b/apps/wallet/src/ui/app/components/WalletListSelect.tsx new file mode 100644 index 0000000000000..3b476e3fe4d1e --- /dev/null +++ b/apps/wallet/src/ui/app/components/WalletListSelect.tsx @@ -0,0 +1,81 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { useAccounts } from '../hooks/useAccounts'; +import { useDeriveNextAccountMutation } from '../hooks/useDeriveNextAccountMutation'; +import { Link } from '../shared/Link'; +import { SummaryCard } from './SummaryCard'; +import { WalletListSelectItem } from './WalletListSelectItem'; + +export type WalletListSelectProps = { + title: string; + values: string[]; + onChange: (values: string[]) => void; +}; + +export function WalletListSelect({ + title, + values, + onChange, +}: WalletListSelectProps) { + const accounts = useAccounts(); + const deriveNextAccount = useDeriveNextAccountMutation(); + return ( + + {accounts.map(({ address }) => ( +
  • { + const newValues = []; + let found = false; + for (const anAddress of values) { + if (anAddress === address) { + found = true; + continue; + } + newValues.push(anAddress); + } + if (!found) { + newValues.push(address); + } + onChange(newValues); + }} + > + +
  • + ))} + + } + footer={ +
    +
    + + onChange(accounts.map(({ address }) => address)) + } + /> +
    +
    + deriveNextAccount.mutate()} + /> +
    +
    + } + minimalPadding + /> + ); +} diff --git a/apps/wallet/src/ui/app/components/WalletListSelectItem.tsx b/apps/wallet/src/ui/app/components/WalletListSelectItem.tsx new file mode 100644 index 0000000000000..da3a41e0235ae --- /dev/null +++ b/apps/wallet/src/ui/app/components/WalletListSelectItem.tsx @@ -0,0 +1,38 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { CheckFill16 } from '@mysten/icons'; +import { formatAddress } from '@mysten/sui.js'; +import { cx } from 'class-variance-authority'; + +import { Text } from '../shared/text'; + +export type WalletListSelectItemProps = { + address: string; + selected: boolean; +}; + +export function WalletListSelectItem({ + address, + selected, +}: WalletListSelectItemProps) { + return ( +
    + + + {formatAddress(address)} + +
    + ); +} diff --git a/apps/wallet/src/ui/app/components/menu/content/AccountsSettings.tsx b/apps/wallet/src/ui/app/components/menu/content/AccountsSettings.tsx index 986233f19c6b1..64799e88b3880 100644 --- a/apps/wallet/src/ui/app/components/menu/content/AccountsSettings.tsx +++ b/apps/wallet/src/ui/app/components/menu/content/AccountsSettings.tsx @@ -2,15 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { useFeature } from '@growthbook/growthbook-react'; -import { useMutation } from '@tanstack/react-query'; -import { toast } from 'react-hot-toast'; import { Account } from './Account'; import { MenuLayout } from './MenuLayout'; import { useNextMenuUrl } from '_components/menu/hooks'; import { FEATURES } from '_src/shared/experimentation/features'; import { useAccounts } from '_src/ui/app/hooks/useAccounts'; -import { useBackgroundClient } from '_src/ui/app/hooks/useBackgroundClient'; +import { useDeriveNextAccountMutation } from '_src/ui/app/hooks/useDeriveNextAccountMutation'; import { Button } from '_src/ui/app/shared/ButtonUI'; export function AccountsSettings() { @@ -20,19 +18,7 @@ export function AccountsSettings() { const isMultiAccountsEnabled = useFeature( FEATURES.WALLET_MULTI_ACCOUNTS ).on; - const backgroundClient = useBackgroundClient(); - const createAccountMutation = useMutation({ - mutationFn: async () => { - await backgroundClient.deriveNextAccount(); - return null; - }, - onSuccess: () => { - toast.success('New account created'); - }, - onError: (e) => { - toast.error((e as Error).message || 'Failed to create new account'); - }, - }); + const createAccountMutation = useDeriveNextAccountMutation(); return (
    diff --git a/apps/wallet/src/ui/app/components/user-approve-container/index.tsx b/apps/wallet/src/ui/app/components/user-approve-container/index.tsx index 31f6f4da3ddd4..8c9f453a6ce45 100644 --- a/apps/wallet/src/ui/app/components/user-approve-container/index.tsx +++ b/apps/wallet/src/ui/app/components/user-approve-container/index.tsx @@ -4,12 +4,11 @@ import cl from 'classnames'; import { memo, useCallback, useMemo, useState } from 'react'; +import { Button } from '../../shared/ButtonUI'; import AccountAddress from '_components/account-address'; import ExternalLink from '_components/external-link'; -import Icon from '_components/icon'; -import LoadingIndicator from '_components/loading/LoadingIndicator'; -import type { MouseEventHandler, ReactNode } from 'react'; +import type { ReactNode } from 'react'; import st from './UserApproveContainer.module.scss'; @@ -19,9 +18,11 @@ type UserApproveContainerProps = { originFavIcon?: string; rejectTitle: string; approveTitle: string; + approveDisabled?: boolean; onSubmit: (approved: boolean) => void; isConnect?: boolean; isWarning?: boolean; + addressHidden?: boolean; }; function UserApproveContainer({ @@ -30,15 +31,16 @@ function UserApproveContainer({ children, rejectTitle, approveTitle, + approveDisabled = false, onSubmit, isConnect, isWarning, + addressHidden = false, }: UserApproveContainerProps) { const [submitting, setSubmitting] = useState(false); - const handleOnResponse = useCallback>( - async (e) => { + const handleOnResponse = useCallback( + async (allowed: boolean) => { setSubmitting(true); - const allowed = e.currentTarget.dataset.allow === 'true'; await onSubmit(allowed); setSubmitting(false); }, @@ -70,56 +72,43 @@ function UserApproveContainer({
    -
    -
    Your address
    - -
    + {!addressHidden ? ( +
    +
    Your address
    + +
    + ) : null}
    {children}
    - - + text={rejectTitle} + /> +
    diff --git a/apps/wallet/src/ui/app/hooks/useDeriveNextAccountMutation.ts b/apps/wallet/src/ui/app/hooks/useDeriveNextAccountMutation.ts new file mode 100644 index 0000000000000..ccfc62cdeac88 --- /dev/null +++ b/apps/wallet/src/ui/app/hooks/useDeriveNextAccountMutation.ts @@ -0,0 +1,23 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { useMutation } from '@tanstack/react-query'; +import { toast } from 'react-hot-toast'; + +import { useBackgroundClient } from './useBackgroundClient'; + +export function useDeriveNextAccountMutation() { + const backgroundClient = useBackgroundClient(); + return useMutation({ + mutationFn: async () => { + await backgroundClient.deriveNextAccount(); + return null; + }, + onSuccess: () => { + toast.success('New account created'); + }, + onError: (e) => { + toast.error((e as Error).message || 'Failed to create new account'); + }, + }); +} diff --git a/apps/wallet/src/ui/app/pages/layout/Layout.module.scss b/apps/wallet/src/ui/app/pages/layout/Layout.module.scss index 6fa25a12a43b1..66fdbfd3b5895 100644 --- a/apps/wallet/src/ui/app/pages/layout/Layout.module.scss +++ b/apps/wallet/src/ui/app/pages/layout/Layout.module.scss @@ -27,5 +27,6 @@ background-size: cover; width: 100%; min-height: 100vh; + height: 100vh; } } diff --git a/apps/wallet/src/ui/app/pages/site-connect/SiteConnectPage.module.scss b/apps/wallet/src/ui/app/pages/site-connect/SiteConnectPage.module.scss index e87a3754d1a13..e1e19b94fdd7f 100644 --- a/apps/wallet/src/ui/app/pages/site-connect/SiteConnectPage.module.scss +++ b/apps/wallet/src/ui/app/pages/site-connect/SiteConnectPage.module.scss @@ -19,18 +19,21 @@ } .checkmark { - font-size: 10px; - margin-right: 10px; - color: colors.$success; + font-size: 12px; + color: colors.$sui-steel-blue; } .permission { + display: flex; + flex-flow: row nowrap; + align-items: center; margin-bottom: 6px; font-weight: 500; font-size: 13px; - line-height: 17px; + line-height: 1; margin-left: 0; - color: colors.$gray-100; + color: colors.$sui-steel-darker; + gap: 10px; } .warning-wrapper { diff --git a/apps/wallet/src/ui/app/pages/site-connect/index.tsx b/apps/wallet/src/ui/app/pages/site-connect/index.tsx index 7f6de6321312c..6b86b88fc6b58 100644 --- a/apps/wallet/src/ui/app/pages/site-connect/index.tsx +++ b/apps/wallet/src/ui/app/pages/site-connect/index.tsx @@ -1,10 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +import { useFeature } from '@growthbook/growthbook-react'; +import { CheckFill12 } from '@mysten/icons'; +import { formatAddress, type SuiAddress } from '@mysten/sui.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; -import Icon, { SuiIcons } from '_components/icon'; +import { SummaryCard } from '../../components/SummaryCard'; +import { WalletListSelect } from '../../components/WalletListSelect'; +import { Text } from '../../shared/text'; import Loading from '_components/loading'; import UserApproveContainer from '_components/user-approve-container'; import { useAppDispatch, useAppSelector } from '_hooks'; @@ -12,6 +17,7 @@ import { permissionsSelectors, respondToPermissionRequest, } from '_redux/slices/permissions'; +import { FEATURES } from '_src/shared/experimentation/features'; import type { PermissionType } from '_messages/payloads/permissions'; import type { RootState } from '_redux/RootReducer'; @@ -39,19 +45,23 @@ function SiteConnectPage() { const dispatch = useAppDispatch(); const permissionRequest = useAppSelector(permissionSelector); const activeAccount = useAppSelector(({ account }) => account.address); + const isMultiAccountEnabled = useFeature(FEATURES.WALLET_MULTI_ACCOUNTS).on; + const [accountsToConnect, setAccountsToConnect] = useState( + () => (activeAccount ? [activeAccount] : []) + ); const handleOnSubmit = useCallback( (allowed: boolean) => { - if (requestID && activeAccount) { + if (requestID && accountsToConnect) { dispatch( respondToPermissionRequest({ id: requestID, - accounts: allowed ? [activeAccount] : [], + accounts: allowed ? accountsToConnect : [], allowed, }) ); } }, - [dispatch, requestID, activeAccount] + [dispatch, requestID, accountsToConnect] ); useEffect(() => { if ( @@ -97,6 +107,7 @@ function SiteConnectPage() { onSubmit={handleHideWarning} isWarning isConnect + addressHidden >

    @@ -118,27 +129,59 @@ function SiteConnectPage() { origin={permissionRequest.origin} originFavIcon={permissionRequest.favIcon} approveTitle="Connect" - rejectTitle="Cancel" + rejectTitle="Reject" onSubmit={handleOnSubmit} isConnect + addressHidden + approveDisabled={!accountsToConnect.length} > -
    App Permissions
    -
      - {permissionRequest.permissions.map( - (aPermission) => ( -
    • + {permissionRequest.permissions.map( + (aPermission) => ( +
    • + + { + permissionTypeToTxt[ + aPermission + ] + } +
    • + ) + )} +
    + } + /> + {isMultiAccountEnabled ? ( + + ) : ( + - - {permissionTypeToTxt[aPermission]} - - ) - )} - + {activeAccount + ? formatAddress(activeAccount) + : null} + + } + /> + )} ))}