Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook up list of accounts page #544

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/browser-wallet/src/assets/svgX/checkmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,32 @@
color: $color-white;
}

.wrap-anywhere {
overflow-wrap: anywhere;
}

input.editable {
background: none;
color: inherit;
border: none;
font-weight: inherit;
font-size: inherit;
font-family: inherit;
padding: 0;
margin: 0;

&:focus {
outline: none;
}
}

.button__icon {
gap: rem(16px);

svg {
width: rem(16px);
}
limemloh marked this conversation as resolved.
Show resolved Hide resolved

.label__main {
color: $color-white;
font-size: rem(14px);
Expand Down
212 changes: 141 additions & 71 deletions packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import React, { ChangeEvent, KeyboardEvent, useState } from 'react';
import Plus from '@assets/svgX/plus.svg';
import Arrows from '@assets/svgX/arrows-down-up.svg';
import MagnifyingGlass from '@assets/svgX/magnifying-glass.svg';
import Pencil from '@assets/svgX/pencil-simple.svg';
import Checkmark from '@assets/svgX/checkmark.svg';
import Close from '@assets/svgX/close.svg';
import Copy from '@assets/svgX/copy.svg';
import ArrowRight from '@assets/svgX/arrow-right.svg';
import Page from '@popup/popupX/shared/Page';
Expand All @@ -13,34 +15,149 @@ import Text from '@popup/popupX/shared/Text';
import { useNavigate } from 'react-router-dom';
import { absoluteRoutes } from '@popup/popupX/constants/routes';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { useAtomValue } from 'jotai';
import { credentialsAtom } from '@popup/store/account';
import { WalletCredential } from '@shared/storage/types';
import { useIdentityName, useWritableSelectedAccount } from '@popup/shared/utils/account-helpers';
import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext';
import { displayAsCcd } from 'wallet-common-helpers';

const ACCOUNT_LIST = [
{
account: 'Account 1',
address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
balance: '4,227.38',
attached: 'Identity 1',
},
{
account: 'Account 2',
address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
balance: '4,227.38',
attached: 'Identity 1',
},
{
account: 'Account 3',
address: 'tt2kgdygjrsqtzq2n0yrf2493p83kkfjh50eo',
balance: '1,195.41',
attached: 'Identity 2',
},
];
function fallbackAccountName(credentialNumber: number): string {
return `Account ${1 + credentialNumber}`;
}

export default function Accounts() {
type EditableAccountNameProps = {
currentName: string;
fallbackName: string;
onNewName: (newName: string) => void;
};

function EditableAccountName({ currentName, fallbackName, onNewName }: EditableAccountNameProps) {
const [isEditingName, setIsEditingName] = useState(false);
const [editedName, setEditedName] = useState(currentName);
// Using editedName instead of currentName to avoid flickering after completing.
const displayName = editedName === '' ? fallbackName : editedName;
const onAbort = () => {
setIsEditingName(false);
setEditedName(currentName);
};
const onComplete = () => {
onNewName(editedName.trim());
setIsEditingName(false);
};
const onEdit = () => {
setEditedName(currentName);
setIsEditingName(true);
};
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setEditedName(event.target.value);
};
const onKeyUp = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
onComplete();
}
};
if (isEditingName) {
return (
<>
<Text.Main>
<input
autoFocus
className="editable"
value={editedName}
placeholder={fallbackName}
onChange={onInputChange}
onKeyUp={onKeyUp}
maxLength={25}
/>
</Text.Main>
<Button.Icon className="transparent" icon={<Checkmark />} onClick={onComplete} />
<Button.Icon className="transparent" icon={<Close />} onClick={onAbort} />
</>
);
limemloh marked this conversation as resolved.
Show resolved Hide resolved
}
return (
<>
<Text.Main>{displayName}</Text.Main>
<Button.Icon className="transparent" icon={<Pencil />} onClick={onEdit} />
</>
);
}

type AccountListItemProps = {
credential: WalletCredential;
};

function AccountListItem({ credential }: AccountListItemProps) {
const { t } = useTranslation('x', { keyPrefix: 'accounts' });
const nav = useNavigate();
const navToPrivateKey = () => nav(absoluteRoutes.settings.accounts.privateKey.path);
const navToConnectedSites = () => nav(absoluteRoutes.settings.accounts.connectedSites.path);
const navToIdCards = () => nav(absoluteRoutes.settings.idCards.path);
const identityName = useIdentityName(credential);
const accountInfo = useAccountInfo(credential);
const setAccount = useWritableSelectedAccount(credential.address);
const fallbackName = fallbackAccountName(credential.credNumber);
const accountName = credential.credName !== '' ? credential.credName : fallbackName;
soerenbf marked this conversation as resolved.
Show resolved Hide resolved
const { address } = credential;
const ccdBalance =
accountInfo === undefined ? 'Loading' : displayAsCcd(accountInfo.accountAmount.microCcdAmount, false);
const onNewAccountName = (newName: string) => setAccount({ credName: newName });
return (
<Card key={accountName}>
<Card.Row>
<EditableAccountName
currentName={accountName}
onNewName={onNewAccountName}
fallbackName={fallbackName}
/>
</Card.Row>
<Card.Row>
<Text.Capture className="wrap-anywhere">{address}</Text.Capture>
<Button.Icon className="transparent" onClick={() => copyToClipboard(address)} icon={<Copy />} />
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('ccdBalance')}</Text.MainRegular>
<Text.MainMedium>{ccdBalance}</Text.MainMedium>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('connectedSites')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToConnectedSites}
label={t('seeList')}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('privateKey')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToPrivateKey}
label={t('export')}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('attachedTo')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToIdCards}
label={identityName}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
</Card>
);
}

export default function Accounts() {
const { t } = useTranslation('x', { keyPrefix: 'accounts' });
const accounts = useAtomValue(credentialsAtom);
return (
<Page className="accounts-x">
<Page.Top heading={t('accounts')}>
Expand All @@ -49,55 +166,8 @@ export default function Accounts() {
<Button.Icon icon={<Plus />} />
</Page.Top>
<Page.Main>
{ACCOUNT_LIST.map(({ account, address, balance, attached }) => (
<Card key={account}>
<Card.Row>
<Text.Main>{account}</Text.Main>
<Button.Icon className="transparent" icon={<Pencil />} />
</Card.Row>
<Card.Row>
<Text.Capture>{address}</Text.Capture>
<Button.Icon
className="transparent"
onClick={() => copyToClipboard(address)}
icon={<Copy />}
/>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('totalBalance')}</Text.MainRegular>
<Text.MainMedium>{balance} USD</Text.MainMedium>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('connectedSites')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToConnectedSites}
label={t('seeList')}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('privateKey')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToPrivateKey}
label={t('export')}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('attachedTo')}</Text.MainRegular>
<Button.IconText
className="transparent"
onClick={navToIdCards}
label={attached}
icon={<ArrowRight />}
leftLabel
/>
</Card.Row>
</Card>
{accounts.map((item) => (
<AccountListItem credential={item} key={item.address} />
))}
</Page.Main>
</Page>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const t = {
accounts: 'Accounts',
totalBalance: 'Total Balance',
ccdBalance: 'CCD Balance',
connectedSites: 'Connected sites',
seeList: 'See list',
privateKey: 'Private key',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ export function useIdentityName(credential: WalletCredential, fallback?: string)

export function useWritableSelectedAccount(accountAddress: string) {
const [accounts, setAccounts] = useAtom(writableCredentialAtom);
const setAccount = (update: WalletCredential) =>
const setAccount = (update: Partial<WalletCredential>) =>
setAccounts(
accounts.map((account) => (account.address === accountAddress ? { ...account, ...update } : account))
accounts.map((account) =>
account.address === accountAddress ? ({ ...account, ...update } as WalletCredential) : account
)
);

return setAccount;
Expand Down
Loading