diff --git a/packages/browser-wallet/CHANGELOG.md b/packages/browser-wallet/CHANGELOG.md index 5c3d1cd4f..eb82050c5 100644 --- a/packages/browser-wallet/CHANGELOG.md +++ b/packages/browser-wallet/CHANGELOG.md @@ -4,6 +4,7 @@ - Remove check for redirectUri when launching identity issuance. This check was causing issues with an upcoming identity provider and seems to provide no value. - Increased padding for QR code background. In dark mode, QR code not blending with background. +- Added new option to edit account name. Name saved in local storage. Changed name displayed across all BrowserWallet. ## 1.5.1 diff --git a/packages/browser-wallet/src/assets/svg/edit-secondary.svg b/packages/browser-wallet/src/assets/svg/edit-secondary.svg new file mode 100644 index 000000000..59d3a1c36 --- /dev/null +++ b/packages/browser-wallet/src/assets/svg/edit-secondary.svg @@ -0,0 +1 @@ + diff --git a/packages/browser-wallet/src/background/credential-deployment.ts b/packages/browser-wallet/src/background/credential-deployment.ts index 717609aa9..97ce6c848 100644 --- a/packages/browser-wallet/src/background/credential-deployment.ts +++ b/packages/browser-wallet/src/background/credential-deployment.ts @@ -46,6 +46,7 @@ async function createAndSendCredential(credIn: CredentialInput): Promise { + const setAccount = useWritableSelectedAccount(account.address); + const [isEditing, setIsEditing] = useState(false); + const [name, setName] = useState(displayNameOrSplitAddress(account)); + + const handleSubmitName = useCallback(() => { + if (isEditing) { + setAccount({ credName: name } as WalletCredential); + setIsEditing(false); + } + }, [name]); + + const keyHandlerEnter: KeyboardEventHandler = (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSubmitName(); + } + }; + + return [ + () => ( +
+ {isEditing ? ( + e.stopPropagation()} + autoFocus + maxLength={ACCOUNT_NAME_MAX_LENGTH} + fixedWidth={ACCOUNT_NAME_INPUT_WIDTH} + /> + ) : ( + displayNameOrSplitAddress(account) + )} +
+ ), + () => ( + { + e.stopPropagation(); + e.preventDefault(); + handleSubmitName(); + setIsEditing(!isEditing); + }} + > + {isEditing ? : } + + ), + ]; +}; + export type Account = { address: string }; type ItemProps = { @@ -36,6 +100,7 @@ function BakerOrDelegatorIcon({ accountInfo, className }: { accountInfo: Account function AccountListItem({ account, checked, selected }: ItemProps) { const accountInfo = useAccountInfo(account); + const [EditableName, EditNameIcon] = useEditableAccountName(account); const totalBalance = useMemo( () => accountInfo?.accountAmount?.microCcdAmount || 0n, [accountInfo?.accountAmount.microCcdAmount] @@ -46,9 +111,7 @@ function AccountListItem({ account, checked, selected }: ItemProps) {
- {/* TODO add account name */} - {displaySplitAddress(account.address)}{' '} - {selected && } + {selected && }
{accountInfo && } e.stopPropagation()} tabIndex={-1} /> +
{identityName}
{displayAsCcd(totalBalance)}
@@ -86,7 +150,7 @@ const AccountList = forwardRef(({ className, onSelect }, getKey={(a) => a.address} newText={t('accountList.new')} ref={ref} - searchableKeys={['address']} + searchableKeys={['address', 'credName']} > {(a, checked) => } diff --git a/packages/browser-wallet/src/popup/page-layouts/MainLayout/EntityList/EntityList.scss b/packages/browser-wallet/src/popup/page-layouts/MainLayout/EntityList/EntityList.scss index e522eae18..98cbfad99 100644 --- a/packages/browser-wallet/src/popup/page-layouts/MainLayout/EntityList/EntityList.scss +++ b/packages/browser-wallet/src/popup/page-layouts/MainLayout/EntityList/EntityList.scss @@ -120,4 +120,18 @@ $entity-list-top-height: rem(25px); fill: $color-text; } } + + &__edit { + width: rem(18px); + height: rem(18px); + top: rem(15px); + + path { + fill: $color-text; + } + + svg { + transform: scale(1.2); + } + } } diff --git a/packages/browser-wallet/src/popup/pages/Account/AccountDetails/AccountDetails.tsx b/packages/browser-wallet/src/popup/pages/Account/AccountDetails/AccountDetails.tsx index 85c5501b8..42ee785cb 100644 --- a/packages/browser-wallet/src/popup/pages/Account/AccountDetails/AccountDetails.tsx +++ b/packages/browser-wallet/src/popup/pages/Account/AccountDetails/AccountDetails.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { displayAsCcd, getPublicAccountAmounts, PublicAccountAmounts } from 'wallet-common-helpers'; import { useTranslation } from 'react-i18next'; -import { displaySplitAddress, useIdentityName } from '@popup/shared/utils/account-helpers'; +import { displayNameOrSplitAddress, useIdentityName } from '@popup/shared/utils/account-helpers'; import VerifiedIcon from '@assets/svg/verified-stamp.svg'; import { CreationStatus, WalletCredential } from '@shared/storage/types'; import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext'; @@ -68,7 +68,7 @@ export default function AccountDetails({ expanded, account, className }: Props) return (
-
{displaySplitAddress(account.address)}
+
{displayNameOrSplitAddress(account)}
{identityName}
diff --git a/packages/browser-wallet/src/popup/pages/Allowlist/AllowlistEntryView.tsx b/packages/browser-wallet/src/popup/pages/Allowlist/AllowlistEntryView.tsx index d8df80839..2edced699 100644 --- a/packages/browser-wallet/src/popup/pages/Allowlist/AllowlistEntryView.tsx +++ b/packages/browser-wallet/src/popup/pages/Allowlist/AllowlistEntryView.tsx @@ -1,5 +1,5 @@ import AccountInfoListenerContextProvider, { useAccountInfo } from '@popup/shared/AccountInfoListenerContext'; -import { displaySplitAddress, useIdentityName } from '@popup/shared/utils/account-helpers'; +import { displayNameOrSplitAddress, useIdentityName } from '@popup/shared/utils/account-helpers'; import React, { useMemo, useState } from 'react'; import { Checkbox } from '@popup/shared/Form/Checkbox'; import { WalletCredential } from '@shared/storage/types'; @@ -25,7 +25,7 @@ function AccountListItem({ account, checked, onToggleChecked }: ItemProps) { return (
-
{displaySplitAddress(account.address)}
+
{displayNameOrSplitAddress(account)}
{ diff --git a/packages/browser-wallet/src/popup/pages/ConnectionRequest/ConnectionRequest.tsx b/packages/browser-wallet/src/popup/pages/ConnectionRequest/ConnectionRequest.tsx index 868325a20..70fa38f33 100644 --- a/packages/browser-wallet/src/popup/pages/ConnectionRequest/ConnectionRequest.tsx +++ b/packages/browser-wallet/src/popup/pages/ConnectionRequest/ConnectionRequest.tsx @@ -1,6 +1,6 @@ import { absoluteRoutes } from '@popup/constants/routes'; import { fullscreenPromptContext } from '@popup/page-layouts/FullscreenPromptLayout'; -import { selectedAccountAtom, storedAllowlistAtom } from '@popup/store/account'; +import { selectedCredentialAtom, storedAllowlistAtom } from '@popup/store/account'; import { sessionPasscodeAtom } from '@popup/store/settings'; import { useAtom, useAtomValue } from 'jotai'; import React, { useContext, useEffect, useState } from 'react'; @@ -9,7 +9,7 @@ import { useLocation, Navigate } from 'react-router-dom'; import ExternalRequestLayout from '@popup/page-layouts/ExternalRequestLayout'; import Button from '@popup/shared/Button'; import { displayUrl } from '@popup/shared/utils/string-helpers'; -import { displaySplitAddress } from '@popup/shared/utils/account-helpers'; +import { displayNameOrSplitAddress } from '@popup/shared/utils/account-helpers'; import { handleAllowlistEntryUpdate } from '../Allowlist/util'; type Props = { @@ -21,7 +21,7 @@ export default function ConnectionRequest({ onAllow, onReject }: Props) { const { state } = useLocation(); const { t } = useTranslation('connectionRequest'); const { onClose, withClose } = useContext(fullscreenPromptContext); - const selectedAccount = useAtomValue(selectedAccountAtom); + const selectedAccount = useAtomValue(selectedCredentialAtom); const [allowlistLoading, setAllowlist] = useAtom(storedAllowlistAtom); const allowlist = allowlistLoading.value; const passcode = useAtomValue(sessionPasscodeAtom); @@ -56,7 +56,7 @@ export default function ConnectionRequest({ onAllow, onReject }: Props) {
{t('waiting')}
-

{t('title', { dApp: urlDisplay, account: displaySplitAddress(selectedAccount) })}

+

{t('title', { dApp: urlDisplay, account: displayNameOrSplitAddress(selectedAccount) })}

{t('descriptionP1', { dApp: urlDisplay })}

{t('descriptionP2')}

@@ -69,7 +69,7 @@ export default function ConnectionRequest({ onAllow, onReject }: Props) { disabled={connectButtonDisabled} onClick={() => { setConnectButtonDisabled(true); - connectAccount(selectedAccount, new URL(url).origin).then(withClose(onAllow)); + connectAccount(selectedAccount.address, new URL(url).origin).then(withClose(onAllow)); }} > {t('actions.connect')} diff --git a/packages/browser-wallet/src/popup/pages/Recovery/RecoveryFinish.tsx b/packages/browser-wallet/src/popup/pages/Recovery/RecoveryFinish.tsx index eee3b88e9..117dabcaf 100644 --- a/packages/browser-wallet/src/popup/pages/Recovery/RecoveryFinish.tsx +++ b/packages/browser-wallet/src/popup/pages/Recovery/RecoveryFinish.tsx @@ -7,7 +7,7 @@ import { identitiesAtom } from '@popup/store/identity'; import Button from '@popup/shared/Button'; import { absoluteRoutes } from '@popup/constants/routes'; import { noOp, displayAsCcd } from 'wallet-common-helpers'; -import { displaySplitAddress } from '@popup/shared/utils/account-helpers'; +import { displayNameOrSplitAddress } from '@popup/shared/utils/account-helpers'; import { IdentityIdentifier, BackgroundResponseStatus, RecoveryBackgroundResponse } from '@shared/utils/types'; import { fullscreenPromptContext } from '@popup/page-layouts/FullscreenPromptLayout'; import PageHeader from '@popup/shared/PageHeader'; @@ -69,7 +69,7 @@ export function DisplaySuccess({ added }: Props) {

{addedAccounts.filter(isIdentityOfCredential(identity)).map((cred) => (
-

{displaySplitAddress(cred.address)}

+

{displayNameOrSplitAddress(cred)}

{displayAsCcd( added.accounts.find((pair) => pair.address === cred.address)?.balance || diff --git a/packages/browser-wallet/src/popup/pages/Web3ProofRequest/AccountStatement.tsx b/packages/browser-wallet/src/popup/pages/Web3ProofRequest/AccountStatement.tsx index 8e7e53f00..d20fafcdb 100644 --- a/packages/browser-wallet/src/popup/pages/Web3ProofRequest/AccountStatement.tsx +++ b/packages/browser-wallet/src/popup/pages/Web3ProofRequest/AccountStatement.tsx @@ -6,7 +6,7 @@ import { RevealStatementV2, StatementTypes, } from '@concordium/web-sdk'; -import { displaySplitAddress, useIdentityName, useIdentityOf } from '@popup/shared/utils/account-helpers'; +import { displayNameOrSplitAddress, useIdentityName, useIdentityOf } from '@popup/shared/utils/account-helpers'; import { useDisplayAttributeValue } from '@popup/shared/utils/identity-helpers'; import { ConfirmedIdentity, WalletCredential } from '@shared/storage/types'; import { getCredentialIdFromSubjectDID } from '@shared/utils/verifiable-credential-helpers'; @@ -29,7 +29,7 @@ export function DisplayAccount({ option }: { option: WalletCredential }) { return (

-
{displaySplitAddress(option.address)}
+
{displayNameOrSplitAddress(option)}
{identityName}
diff --git a/packages/browser-wallet/src/popup/shared/Form/InlineInput/InlineInput.tsx b/packages/browser-wallet/src/popup/shared/Form/InlineInput/InlineInput.tsx index 9cb831022..b2b2981bb 100644 --- a/packages/browser-wallet/src/popup/shared/Form/InlineInput/InlineInput.tsx +++ b/packages/browser-wallet/src/popup/shared/Form/InlineInput/InlineInput.tsx @@ -5,11 +5,15 @@ import { scaleFieldWidth } from '../../utils/html-helpers'; import { CommonFieldProps, RequiredControlledFieldProps } from '../common/types'; import { makeControlled } from '../common/utils'; -type Props = Pick, 'type' | 'className' | 'autoFocus'> & +type Props = Pick< + InputHTMLAttributes, + 'type' | 'className' | 'autoFocus' | 'onKeyUp' | 'onMouseUp' | 'maxLength' +> & RequiredControlledFieldProps & CommonFieldProps & { fallbackValue?: string; fallbackOnError?: boolean; + fixedWidth?: number; }; export function InlineInput({ @@ -20,6 +24,7 @@ export function InlineInput({ fallbackOnError = false, onChange = noOp, onBlur = noOp, + fixedWidth, error, ...props }: Props) { @@ -27,7 +32,9 @@ export function InlineInput({ const [innerValue, setInnerValue] = useState(value ?? fallbackValue); useLayoutEffect(() => { - scaleFieldWidth(ref.current); + if (!fixedWidth) { + scaleFieldWidth(ref.current); + } }, [innerValue]); useUpdateEffect(() => { @@ -53,7 +60,7 @@ export function InlineInput({ autoComplete="off" spellCheck="false" {...props} - style={{ width: 6 }} // To prevent initial UI jitter. + style={{ width: fixedWidth || 6 }} // To prevent initial UI jitter. /> ); } 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 cb7e49383..6d0564e0b 100644 --- a/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts +++ b/packages/browser-wallet/src/popup/shared/utils/account-helpers.ts @@ -1,6 +1,6 @@ -import { credentialsAtom, selectedAccountAtom } from '@popup/store/account'; +import { credentialsAtom, selectedAccountAtom, writableCredentialAtom } from '@popup/store/account'; import { networkConfigurationAtom } from '@popup/store/settings'; -import { useAtomValue } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import { useEffect, useMemo, useState } from 'react'; import { identitiesAtom } from '@popup/store/identity'; import { AccountInfo, ConcordiumHdWallet } from '@concordium/web-sdk'; @@ -10,7 +10,10 @@ import { isIdentityOfCredential } from '@shared/utils/identity-helpers'; import { getNextUnused } from '@shared/utils/number-helpers'; import { useDecryptedSeedPhrase } from './seed-phrase-helpers'; -export const displaySplitAddress = (address: string) => `${address.slice(0, 4)}...${address.slice(address.length - 4)}`; +export const displayNameOrSplitAddress = (account: WalletCredential | undefined) => { + const { credName, address } = account || { address: '' }; + return credName || `${address.slice(0, 4)}...${address.slice(address.length - 4)}`; +}; export function useIdentityOf(cred?: WalletCredential) { const identities = useAtomValue(identitiesAtom); @@ -40,6 +43,16 @@ export function useIdentityName(credential: WalletCredential, fallback?: string) return identityName; } +export function useWritableSelectedAccount(accountAddress: string) { + const [accounts, setAccounts] = useAtom(writableCredentialAtom); + const setAccount = (update: WalletCredential) => + setAccounts( + accounts.map((account) => (account.address === accountAddress ? { ...account, ...update } : account)) + ); + + return setAccount; +} + export function useCredential(accountAddress?: string) { const credentials = useAtomValue(credentialsAtom); diff --git a/packages/browser-wallet/src/popup/store/account.ts b/packages/browser-wallet/src/popup/store/account.ts index aa21acf2b..f3cfcf273 100644 --- a/packages/browser-wallet/src/popup/store/account.ts +++ b/packages/browser-wallet/src/popup/store/account.ts @@ -13,6 +13,13 @@ export const credentialsAtomWithLoading = atomWithChromeStorage v.value); +export const writableCredentialAtom = atom( + (get) => get(credentialsAtom), + async (_, set, update) => { + await set(credentialsAtomWithLoading, update); + } +); + export const storedConnectedSitesAtom = atomWithChromeStorage>( ChromeStorageKey.ConnectedSites, {}, @@ -34,6 +41,12 @@ export const selectedAccountAtom = atom((get) => { + const selectedAccount = get(selectedAccountAtom); + const credentials = get(credentialsAtom); + return credentials.find((cred) => cred.address === selectedAccount); +}); + export const accountsAtom = selectAtom(credentialsAtom, (cs) => cs.map((c) => c.address)); export const accountsPerIdentityAtom = selectAtom(credentialsAtom, (cs) => { diff --git a/packages/browser-wallet/src/shared/storage/types.ts b/packages/browser-wallet/src/shared/storage/types.ts index 0c58130f1..631c2942e 100644 --- a/packages/browser-wallet/src/shared/storage/types.ts +++ b/packages/browser-wallet/src/shared/storage/types.ts @@ -147,6 +147,7 @@ export interface BaseCredential { address: string; credId: string; credNumber: number; + credName: string; status: CreationStatus; identityIndex: number; providerIndex: number;