diff --git a/packages/browser-wallet/src/assets/svgX/checkmark.svg b/packages/browser-wallet/src/assets/svgX/checkmark.svg new file mode 100644 index 000000000..e4db7bbc7 --- /dev/null +++ b/packages/browser-wallet/src/assets/svgX/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.scss b/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.scss index d5cae019d..b5005fb30 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.scss +++ b/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.scss @@ -25,6 +25,37 @@ 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; + } + } + + .gap-16 { + gap: rem(16px); + } + + .width-16 { + width: rem(16px); + } + + .width-12 { + width: rem(12px); + } + .button__icon { gap: rem(16px); diff --git a/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.tsx b/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.tsx index a81d2bd9d..d95223644 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/Accounts/Accounts.tsx @@ -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'; @@ -13,34 +15,151 @@ 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 { displaySplitAddress, 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', - }, -]; +type EditableAccountNameProps = { + currentName: string; + fallbackName: string; + onNewName: (newName: string) => void; +}; -export default function Accounts() { +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) => { + setEditedName(event.target.value); + }; + const onKeyUp = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + event.preventDefault(); + onComplete(); + } + }; + if (isEditingName) { + return ( + <> + + + +
+ } + onClick={onComplete} + /> + } onClick={onAbort} /> +
+ + ); + } + return ( + <> + {displayName} + } 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 = displaySplitAddress(credential.address); + const accountName = credential.credName !== '' ? credential.credName : fallbackName; + const { address } = credential; + const ccdBalance = + accountInfo === undefined ? 'Loading' : displayAsCcd(accountInfo.accountAmount.microCcdAmount, false); + const onNewAccountName = (newName: string) => setAccount({ credName: newName }); + return ( + + + + + + {address} + copyToClipboard(address)} icon={} /> + + + {t('ccdBalance')} + {ccdBalance} + + + {t('connectedSites')} + } + leftLabel + /> + + + {t('privateKey')} + } + leftLabel + /> + + + {t('attachedTo')} + } + leftLabel + /> + + + ); +} + +export default function Accounts() { + const { t } = useTranslation('x', { keyPrefix: 'accounts' }); + const accounts = useAtomValue(credentialsAtom); return ( @@ -49,55 +168,8 @@ export default function Accounts() { } /> - {ACCOUNT_LIST.map(({ account, address, balance, attached }) => ( - - - {account} - } /> - - - {address} - copyToClipboard(address)} - icon={} - /> - - - {t('totalBalance')} - {balance} USD - - - {t('connectedSites')} - } - leftLabel - /> - - - {t('privateKey')} - } - leftLabel - /> - - - {t('attachedTo')} - } - leftLabel - /> - - + {accounts.map((item) => ( + ))} diff --git a/packages/browser-wallet/src/popup/popupX/pages/Accounts/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/Accounts/i18n/en.ts index 9fb69e2de..96e7d0f0f 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/Accounts/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/Accounts/i18n/en.ts @@ -1,6 +1,6 @@ const t = { accounts: 'Accounts', - totalBalance: 'Total Balance', + ccdBalance: 'CCD Balance', connectedSites: 'Connected sites', seeList: 'See list', privateKey: 'Private key', diff --git a/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts b/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts index 18d4e2816..10abf351d 100644 --- a/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts +++ b/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts @@ -10,15 +10,19 @@ import { isIdentityOfCredential } from '@shared/utils/identity-helpers'; import { getNextUnused } from '@shared/utils/number-helpers'; import { useDecryptedSeedPhrase } from './seed-phrase-helpers'; +/** Format an account address for display by showing the 4 first and last characters in the base58check representation. */ +export function displaySplitAddress(address: string) { + return `${address.slice(0, 4)}...${address.slice(-4)}`; +} + export const displayNameOrSplitAddress = (account: WalletCredential | undefined) => { const { credName, address } = account || { address: '' }; - return credName || `${address.slice(0, 4)}...${address.slice(address.length - 4)}`; + return credName || displaySplitAddress(address); }; export const displayNameAndSplitAddress = (account: WalletCredential | undefined) => { const { credName, address } = account || { address: '' }; - const splitAddress = `${address.slice(0, 4)}...${address.slice(address.length - 4)}`; - return `${credName ? `${credName} / ` : ''}${splitAddress}`; + return `${credName ? `${credName} / ` : ''}${displaySplitAddress(address)}`; }; export function useIdentityOf(cred?: WalletCredential) { @@ -51,9 +55,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) => setAccounts( - accounts.map((account) => (account.address === accountAddress ? { ...account, ...update } : account)) + accounts.map((account) => + account.address === accountAddress ? ({ ...account, ...update } as WalletCredential) : account + ) ); return setAccount;