Skip to content

Commit

Permalink
Connect id-cards page and factor out useEditableValue hook
Browse files Browse the repository at this point in the history
  • Loading branch information
limemloh committed Oct 28, 2024
1 parent bf10667 commit f71c44f
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const relativeRoutes = {
settings: {
path: 'settings',
idCards: {
path: 'idCards',
path: 'id-cards',
},
about: {
path: 'about',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ChangeEvent, KeyboardEvent, useState } from 'react';
import React 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';
Expand All @@ -21,71 +21,7 @@ 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';

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>
<div className="row gap-16">
<Button.Icon
className="transparent"
icon={<Checkmark className="width-12" />}
onClick={onComplete}
/>
<Button.Icon className="transparent" icon={<Close className="width-16" />} onClick={onAbort} />
</div>
</>
);
}
return (
<>
<Text.Main>{displayName}</Text.Main>
<Button.Icon className="transparent" icon={<Pencil />} onClick={onEdit} />
</>
);
}
import useEditableValue from '@popup/popupX/shared/EditableValue';

type AccountListItemProps = {
credential: WalletCredential;
Expand All @@ -108,14 +44,28 @@ function AccountListItem({ credential }: AccountListItemProps) {
const ccdBalance =
accountInfo === undefined ? 'Loading' : displayAsCcd(accountInfo.accountAmount.microCcdAmount, false);
const onNewAccountName = (newName: string) => setAccount({ credName: newName });
const editable = useEditableValue(accountName, fallbackName, onNewAccountName);

return (
<Card key={accountName}>
<Card.Row>
<EditableAccountName
currentName={accountName}
onNewName={onNewAccountName}
fallbackName={fallbackName}
/>
<Text.Main>{editable.value}</Text.Main>
{editable.isEditing ? (
<div className="row gap-16">
<Button.Icon
className="transparent"
icon={<Checkmark className="width-12" />}
onClick={editable.onComplete}
/>
<Button.Icon
className="transparent"
icon={<Close className="width-16" />}
onClick={editable.onAbort}
/>
</div>
) : (
<Button.Icon className="transparent" icon={<Pencil />} onClick={editable.onEdit} />
)}
</Card.Row>
<Card.Row>
<Text.Capture className="wrap-anywhere">{address}</Text.Capture>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
.id-cards-x {
.page__main {
gap: rem(16px);
}
}
105 changes: 88 additions & 17 deletions packages/browser-wallet/src/popup/popupX/pages/IdCards/IdCards.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,106 @@
import React from 'react';
import React, { useMemo } from 'react';
import Plus from '@assets/svgX/plus.svg';
import Button from '@popup/popupX/shared/Button';
import Page from '@popup/popupX/shared/Page';
import { useTranslation } from 'react-i18next';
import IdCard from '@popup/popupX/shared/IdCard';
import { identitiesAtom, identityProvidersAtom } from '@popup/store/identity';
import { useAtom, useAtomValue } from 'jotai';
import { useDisplayAttributeValue, useGetAttributeName } from '@popup/shared/utils/identity-helpers';
import { CreationStatus, ConfirmedIdentity, WalletCredential } from '@shared/storage/types';
import { AttributeKey } from '@concordium/web-sdk';
import { IdCardAccountInfo, IdCardAttributeInfo } from '@popup/popupX/shared/IdCard/IdCard';
import { credentialsAtomWithLoading } from '@popup/store/account';
import { displayNameAndSplitAddress } from '@popup/shared/utils/account-helpers';
import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext';
import { displayAsCcd } from 'wallet-common-helpers';

const rowsIdInfo: [string, string][] = [
['Identity document type', 'Drivers licence'],
['Identity document number', 'BXM680515'],
['First name', 'Lewis'],
['Last name', 'Hamilton'],
['Date of birth', '13 August 1992'],
['Identity document issuer', 'New Zeland'],
['ID valid until', '30 October 2051'],
];
function CcdBalance({ credential }: { credential: WalletCredential }) {
const accountInfo = useAccountInfo(credential);
const balance =
accountInfo === undefined ? '' : displayAsCcd(accountInfo.accountAmount.microCcdAmount, false, true);
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{balance}</>;
}

function fallbackIdentityName(index: number) {
return `Identity ${index + 1}`;
}

type ConfirmedIdentityProps = { identity: ConfirmedIdentity; onNewName: (name: string) => void };

function ConfirmedIdCard({ identity, onNewName }: ConfirmedIdentityProps) {
const displayAttribute = useDisplayAttributeValue();
const getAttributeName = useGetAttributeName();
const providers = useAtomValue(identityProvidersAtom);
const credentials = useAtomValue(credentialsAtomWithLoading);
const provider = providers.find((p) => p.ipInfo.ipIdentity === identity.providerIndex);
const providerName = provider?.ipInfo.ipDescription.name ?? 'Unknown';
const rowsIdInfo: IdCardAttributeInfo[] = useMemo(
() =>
Object.entries(identity.idObject.value.attributeList.chosenAttributes).map(([key, value]) => ({
key: getAttributeName(key as AttributeKey),
value: displayAttribute(key, value),
})),
[identity]
);
const rowsConnectedAccounts = useMemo(() => {
const connectedAccounts = credentials.value.flatMap((cred): IdCardAccountInfo[] =>
cred.identityIndex !== identity.index
? []
: [
{
address: displayNameAndSplitAddress(cred),
amount: <CcdBalance credential={cred} />,
},
]
);
return connectedAccounts.length === 0 ? undefined : connectedAccounts;
}, [credentials, identity]);
return (
<IdCard
identityName={identity.name}
onNewName={onNewName}
identityNameFallback={fallbackIdentityName(identity.index)}
idProviderName={providerName}
rowsIdInfo={rowsIdInfo}
rowsConnectedAccounts={rowsConnectedAccounts}
/>
);
}

const rowsConnectedAccounts: [string, string][] = [
['Accout 1 / 6gk...Fk7o', '4,227.38 USD'],
['Accout 2 / tt2...50eo', '1,195.41 USD'],
['Accout 3 / bnh...JJ76', '123.38 USD'],
['Accout 4 / rijf...8h7T', '7,200.41 USD'],
];
export default function IdCards() {
const { t } = useTranslation('x', { keyPrefix: 'idCards' });
const [identities, setIdentities] = useAtom(identitiesAtom);
const onNewName = (index: number) => (newName: string) => {
const identitiesClone = [...identities];
identitiesClone[index] = { ...identities[index], name: newName };
setIdentities(identitiesClone);
};
return (
<Page className="id-cards-x">
<Page.Top heading={t('idCards')}>
<Button.Icon icon={<Plus />} />
</Page.Top>
<Page.Main>
<IdCard rowsIdInfo={rowsIdInfo} rowsConnectedAccounts={rowsConnectedAccounts} />
{identities.map((id, index) => {
switch (id.status) {
case CreationStatus.Confirmed:
return (
<ConfirmedIdCard
identity={id}
key={`${id.providerIndex}:${id.index}`}
onNewName={onNewName(index)}
/>
);
case CreationStatus.Pending:
return null;
case CreationStatus.Rejected:
return null;
default:
return <>Unsupported</>;
}
})}
</Page.Main>
</Page>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import Page from '@popup/popupX/shared/Page';
import Text from '@popup/popupX/shared/Text';
import { useTranslation } from 'react-i18next';
import IdCard from '@popup/popupX/shared/IdCard';
import { IdCardAttributeInfo } from '@popup/popupX/shared/IdCard/IdCard';

const rowsIdInfo: [string, string][] = [
['Identity document type', 'Drivers licence'],
['Identity document number', 'BXM680515'],
const rowsIdInfo: IdCardAttributeInfo[] = [
{ key: 'Identity document type', value: 'Drivers licence' },
{ key: 'Identity document number', value: 'BXM680515' },
];

export default function IdSubmitted() {
Expand All @@ -20,7 +21,7 @@ export default function IdSubmitted() {
<Page.Top heading={t('yourId')} />
<Page.Main>
<Text.Capture>{t('idSubmitInfo')}</Text.Capture>
<IdCard rowsIdInfo={rowsIdInfo} />
<IdCard rowsIdInfo={rowsIdInfo} idProviderName="TODO" identityName="TODO" />
</Page.Main>
<Page.Footer>
<Button.Main label={t('done')} onClick={() => navToNext()} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { ReactNode } from 'react';
import React from 'react';
import ArrowRight from '@assets/svgX/arrow-right.svg';
import Button from '@popup/popupX/shared/Button';
import Page from '@popup/popupX/shared/Page';
import Text from '@popup/popupX/shared/Text';
import { useTranslation } from 'react-i18next';
import IdCard from '@popup/popupX/shared/IdCard';
import { IdCardAttributeInfo } from '@popup/popupX/shared/IdCard/IdCard';

function AccountLink({ account, balance }: { account: string; balance: string }) {
return (
Expand All @@ -16,9 +17,9 @@ function AccountLink({ account, balance }: { account: string; balance: string })
);
}

const rowsIdInfo: [string, ReactNode][] = [
['', <AccountLink account="Accout 1 / 6gk...k7o" balance="1,285,700 CCD" />],
['', <AccountLink account="Accout 2 / tt2...50eo" balance="90,800 CCD" />],
const rowsIdInfo: IdCardAttributeInfo[] = [
{ key: '', value: <AccountLink account="Accout 1 / 6gk...k7o" balance="1,285,700 CCD" /> },
{ key: '', value: <AccountLink account="Accout 2 / tt2...50eo" balance="90,800 CCD" /> },
];

export default function RestoreResult() {
Expand All @@ -29,7 +30,7 @@ export default function RestoreResult() {
<Page.Top heading={t('result')} />
<Page.Main>
<Text.Capture>{t('recoveredIds')}</Text.Capture>
<IdCard rowsIdInfo={rowsIdInfo} />
<IdCard rowsIdInfo={rowsIdInfo} idProviderName="TODO" identityName="TODO" />
</Page.Main>
<Page.Footer>
<Button.Main label={t('continue')} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
input.editable-value-x {
background: none;
color: inherit;
border: none;
font-weight: inherit;
font-size: inherit;
font-family: inherit;
padding: 0;
margin: 0;

&:focus {
outline: none;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { ChangeEvent, useState, KeyboardEvent, useCallback } from 'react';

export default function useEditableValue(current: string, fallback: string, onNewValue: (newValue: string) => void) {
const [isEditing, setIsEditing] = useState(false);
const [edited, setEdited] = useState(current);
// Using edited instead of currentValue to avoid flickering after completing.
const displayName = edited === '' ? fallback : edited;
const onAbort = useCallback(() => {
setIsEditing(false);
setEdited(current);
}, []);
const onComplete = useCallback(() => {
onNewValue(edited.trim());
setIsEditing(false);
}, []);
const onEdit = useCallback(() => {
setEdited(current);
setIsEditing(true);
}, []);
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setEdited(event.target.value);
};
const onKeyUp = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
onComplete();
}
};
const value = isEditing ? (
<input
className="editable-value-x"
autoFocus
maxLength={25}
value={edited}
placeholder={fallback}
onChange={onInputChange}
onKeyUp={onKeyUp}
/>
) : (
displayName
);
return { value, isEditing, onAbort, onComplete, onEdit };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './EditableValue';
Loading

0 comments on commit f71c44f

Please sign in to comment.