Skip to content

Commit

Permalink
wallet-ext: allow selecting the accounts to connect to dapp (#8413)
Browse files Browse the repository at this point in the history
* 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

<img width="361" alt="Screenshot 2023-02-18 at 21 17 55"
src="https://user-images.githubusercontent.com/10210143/219900181-82696483-cd7e-44dd-94a4-fe3d8a379db1.png">

When multiaccount is disabled
<img width="375" alt="Screenshot 2023-02-19 at 22 36 21"
src="https://user-images.githubusercontent.com/10210143/219980363-28a36ce0-ba5d-4743-88e2-3031c9e7bf60.png">

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.
<img width="959" alt="Screenshot 2023-02-18 at 21 22 28"
src="https://user-images.githubusercontent.com/10210143/219900158-798d1843-31e2-47fe-81af-3d4d993680a6.png">

Closes APPS-283
  • Loading branch information
pchrysochoidis committed Mar 1, 2023
1 parent 59d156e commit a221c81
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 92 deletions.
52 changes: 52 additions & 0 deletions apps/wallet/src/ui/app/components/SummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="bg-white flex flex-col flex-nowrap border border-solid border-gray-45 rounded-2xl overflow-hidden">
{header ? (
<div className="flex flex-row flex-nowrap items-center justify-center uppercase bg-gray-40 px-3.75 py-3">
<Text
variant="captionSmall"
weight="semibold"
color="steel-darker"
truncate
>
{header}
</Text>
</div>
) : null}
<div
className={cx(
'flex-1 flex flex-col items-stretch flex-nowrap px-4',
minimalPadding ? 'py-2' : 'pb-5 pt-4'
)}
>
{body}
</div>
{footer ? (
<div className="p-4 pt-3 border-t border-solid border-gray-40">
{footer}
</div>
) : null}
</div>
);
}
81 changes: 81 additions & 0 deletions apps/wallet/src/ui/app/components/WalletListSelect.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<SummaryCard
header={title}
body={
<ul className="flex flex-col items-stretch flex-1 p-0 m-0 self-stretch list-none">
{accounts.map(({ address }) => (
<li
key={address}
onClick={() => {
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);
}}
>
<WalletListSelectItem
address={address}
selected={values.includes(address)}
/>
</li>
))}
</ul>
}
footer={
<div className="flex flex-row flex-nowrap self-stretch justify-between">
<div>
<Link
color="heroDark"
weight="medium"
text="Select all"
onClick={() =>
onChange(accounts.map(({ address }) => address))
}
/>
</div>
<div>
<Link
color="heroDark"
weight="medium"
text="New account"
loading={deriveNextAccount.isLoading}
onClick={() => deriveNextAccount.mutate()}
/>
</div>
</div>
}
minimalPadding
/>
);
}
38 changes: 38 additions & 0 deletions apps/wallet/src/ui/app/components/WalletListSelectItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={cx(
'transition flex flex-row flex-nowrap items-center gap-3 py-2 cursor-pointer',
'hover:text-steel-dark',
selected ? 'text-steel-dark' : 'text-steel'
)}
>
<CheckFill16
className={cx(
selected ? 'text-success' : 'text-gray-50',
'transition text-base font-bold'
)}
/>
<Text mono variant="body" weight="semibold">
{formatAddress(address)}
</Text>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 (
<MenuLayout title="Accounts" back={backUrl}>
<div className="flex flex-col gap-3">
Expand Down
89 changes: 39 additions & 50 deletions apps/wallet/src/ui/app/components/user-approve-container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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({
Expand All @@ -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<MouseEventHandler<HTMLButtonElement>>(
async (e) => {
const handleOnResponse = useCallback(
async (allowed: boolean) => {
setSubmitting(true);
const allowed = e.currentTarget.dataset.allow === 'true';
await onSubmit(allowed);
setSubmitting(false);
},
Expand Down Expand Up @@ -70,56 +72,43 @@ function UserApproveContainer({
</ExternalLink>
</div>
</div>
<div className={st.cardFooter}>
<div className={st.label}>Your address</div>
<AccountAddress
showLink={false}
mode="normal"
copyable
className={st.address}
/>
</div>
{!addressHidden ? (
<div className={st.cardFooter}>
<div className={st.label}>Your address</div>
<AccountAddress
showLink={false}
mode="normal"
copyable
className={st.address}
/>
</div>
) : null}
</div>
<div className={st.children}>{children}</div>
</div>
<div className={st.actionsContainer}>
<div className={cl(st.actions, isWarning && st.flipActions)}>
<button
type="button"
data-allow="false"
onClick={handleOnResponse}
className={cl(
st.button,
isWarning
? st.reject
: isConnect
? st.cancel
: st.reject
)}
disabled={submitting}
>
{rejectTitle}
</button>
<button
type="button"
className={cl(
st.button,
isWarning ? st.cancel : st.approve,
submitting && st.loading
)}
data-allow="true"
onClick={handleOnResponse}
<Button
size="tall"
variant="warning"
onClick={() => {
handleOnResponse(false);
}}
disabled={submitting}
>
<span>
{submitting ? (
<LoadingIndicator color="inherit" />
) : (
approveTitle
)}
</span>
{isWarning && <Icon icon="arrow-right" />}
</button>
text={rejectTitle}
/>
<Button
// recreate the button when changing the variant to avoid animating to the new styles
key={`approve_${isWarning}`}
size="tall"
variant={isWarning ? 'secondary' : 'primary'}
onClick={() => {
handleOnResponse(true);
}}
disabled={approveDisabled}
loading={submitting}
text={approveTitle}
/>
</div>
</div>
</div>
Expand Down
23 changes: 23 additions & 0 deletions apps/wallet/src/ui/app/hooks/useDeriveNextAccountMutation.ts
Original file line number Diff line number Diff line change
@@ -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');
},
});
}
1 change: 1 addition & 0 deletions apps/wallet/src/ui/app/pages/layout/Layout.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@
background-size: cover;
width: 100%;
min-height: 100vh;
height: 100vh;
}
}
Loading

0 comments on commit a221c81

Please sign in to comment.