Skip to content

Commit

Permalink
Merge pull request #556 from Concordium/x-id-cards
Browse files Browse the repository at this point in the history
Hook up IdCards page with actual data
  • Loading branch information
limemloh authored Nov 1, 2024
2 parents 7d0971d + 5de695d commit f117137
Show file tree
Hide file tree
Showing 13 changed files with 243 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);
}
}
107 changes: 90 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,108 @@
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 { compareAttributes, 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)
.sort(([left], [right]) => compareAttributes(left, right))
.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);
}, [current]);
const onComplete = useCallback(() => {
onNewValue(edited.trim());
setIsEditing(false);
}, [edited]);
const onEdit = useCallback(() => {
setEdited(current);
setIsEditing(true);
}, [current]);
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 f117137

Please sign in to comment.