Skip to content

Commit

Permalink
Merge pull request #569 from Concordium/x-rejected-id-card
Browse files Browse the repository at this point in the history
Show rejected ID cards
  • Loading branch information
limemloh authored Nov 15, 2024
2 parents d32a5a4 + a57d5b1 commit 2d819f1
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { identitiesAtom } from '@popup/store/identity';
import { useAtom } from 'jotai';
import { CreationStatus } from '@shared/storage/types';
import { ConfirmedIdCard } from '@popup/popupX/shared/IdCard';
import { ConfirmedIdCard, RejectedIdCard } from '@popup/popupX/shared/IdCard';

export default function IdCards() {
const { t } = useTranslation('x', { keyPrefix: 'idCards' });
Expand Down Expand Up @@ -35,7 +35,13 @@ export default function IdCards() {
case CreationStatus.Pending:
return null;
case CreationStatus.Rejected:
return null;
return (
<RejectedIdCard
identity={id}
key={`${id.providerIndex}:${id.index}`}
onNewName={onNewName(index)}
/>
);
default:
return <>Unsupported</>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ 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: IdCardAttributeInfo[] = [
const rowsIdInfo = [
{ key: 'Identity document type', value: 'Drivers licence' },
{ key: 'Identity document number', value: 'BXM680515' },
];
Expand All @@ -21,7 +20,16 @@ export default function IdSubmitted() {
<Page.Top heading={t('yourId')} />
<Page.Main>
<Text.Capture>{t('idSubmitInfo')}</Text.Capture>
<IdCard rowsIdInfo={rowsIdInfo} idProviderName="TODO" identityName="TODO" />
<IdCard title="Identity 1" subtitle="Submitted to NotaBene">
<IdCard.Content>
{rowsIdInfo.map((info) => (
<IdCard.ContentRow key={info.key}>
<Text.MainRegular>{info.key}</Text.MainRegular>
<Text.MainMedium>{info.value}</Text.MainMedium>
</IdCard.ContentRow>
))}
</IdCard.Content>
</IdCard>
</Page.Main>
<Page.Footer>
<Button.Main label={t('done')} onClick={() => navToNext()} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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 @@ -17,7 +16,7 @@ function AccountLink({ account, balance }: { account: string; balance: string })
);
}

const rowsIdInfo: IdCardAttributeInfo[] = [
const rowsIdInfo = [
{ 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" /> },
];
Expand All @@ -30,7 +29,17 @@ export default function RestoreResult() {
<Page.Top heading={t('result')} />
<Page.Main>
<Text.Capture>{t('recoveredIds')}</Text.Capture>
<IdCard rowsIdInfo={rowsIdInfo} idProviderName="TODO" identityName="TODO" />

<IdCard title="Identity 1" subtitle="Verified by NotaBene">
<IdCard.Content>
{rowsIdInfo.map((info) => (
<IdCard.ContentRow key={info.key}>
<Text.MainRegular>{info.key}</Text.MainRegular>
<Text.MainMedium>{info.value}</Text.MainMedium>
</IdCard.ContentRow>
))}
</IdCard.Content>
</IdCard>
</Page.Main>
<Page.Footer>
<Button.Main label={t('continue')} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { ReactNode, useMemo } from 'react';
import { useDisplayAttributeValue, useGetAttributeName } from '@popup/shared/utils/identity-helpers';
import { identityProvidersAtom } from '@popup/store/identity';
import { useAtomValue } from 'jotai';
Expand All @@ -8,7 +8,10 @@ import { compareAttributes, displayAsCcd } from 'wallet-common-helpers';
import { credentialsAtomWithLoading } from '@popup/store/account';
import { AttributeKey } from '@concordium/web-sdk';
import { displayNameAndSplitAddress } from '@popup/shared/utils/account-helpers';
import IdCard, { IdCardAccountInfo, IdCardAttributeInfo } from './IdCard';
import { useTranslation } from 'react-i18next';
import Text from '@popup/popupX/shared/Text';
import IdCard from './IdCard';
import useEditableName from './useEditableName';

function CcdBalance({ credential }: { credential: WalletCredential }) {
const accountInfo = useAccountInfo(credential);
Expand All @@ -18,9 +21,15 @@ function CcdBalance({ credential }: { credential: WalletCredential }) {
return <>{balance}</>;
}

function fallbackIdentityName(index: number) {
return `Identity ${index + 1}`;
}
type IdCardAttributeInfo = {
key: string;
value: ReactNode;
};

type IdCardAccountInfo = {
address: string;
amount: ReactNode;
};

export type ConfirmedIdentityProps = {
/** Identity to show. */
Expand All @@ -39,12 +48,14 @@ export default function ConfirmedIdCard({
shownAttributes,
onNewName,
}: ConfirmedIdentityProps) {
const { t } = useTranslation('x', { keyPrefix: 'sharedX' });
const editable = useEditableName(identity, onNewName);
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 idProviderName = provider?.ipInfo.ipDescription.name ?? 'Unknown';
const rowsIdInfo: IdCardAttributeInfo[] = useMemo(
() =>
Object.entries(identity.idObject.value.attributeList.chosenAttributes)
Expand Down Expand Up @@ -76,12 +87,30 @@ export default function ConfirmedIdCard({
}, [credentials, identity, hideAccounts]);
return (
<IdCard
identityName={identity.name}
onNewName={onNewName}
identityNameFallback={fallbackIdentityName(identity.index)}
idProviderName={providerName}
rowsIdInfo={rowsIdInfo}
rowsConnectedAccounts={rowsConnectedAccounts}
/>
title={editable.value}
titleAction={editable.actions}
subtitle={t('idCard.verifiedBy', { idProviderName })}
>
{rowsIdInfo && (
<IdCard.Content>
{rowsIdInfo.map((info) => (
<IdCard.ContentRow key={info.key}>
<Text.MainRegular>{info.key}</Text.MainRegular>
<Text.MainMedium>{info.value}</Text.MainMedium>
</IdCard.ContentRow>
))}
</IdCard.Content>
)}
{rowsConnectedAccounts && (
<IdCard.Content>
{rowsConnectedAccounts.map((account) => (
<IdCard.ContentRow key={account.address}>
<Text.MainRegular>{account.address}</Text.MainRegular>
<Text.MainMedium>{account.amount}</Text.MainMedium>
</IdCard.ContentRow>
))}
</IdCard.Content>
)}
</IdCard>
);
}
14 changes: 14 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/IdCard/IdCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,18 @@
}
}
}

.card-x.rejected {
justify-content: center;
align-items: center;

svg.icon-rejected {
width: rem(48px);
height: rem(48px);

path {
fill: $color-red-attention;
}
}
}
}
74 changes: 13 additions & 61 deletions packages/browser-wallet/src/popup/popupX/shared/IdCard/IdCard.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,26 @@
import React, { ReactNode } from 'react';
import Card from '@popup/popupX/shared/Card';
import Text from '@popup/popupX/shared/Text';
import Button from '@popup/popupX/shared/Button';
import useEditableValue from '@popup/popupX/shared/EditableValue';
import { useTranslation } from 'react-i18next';

export type IdCardAttributeInfo = {
key: string;
value: string | ReactNode;
export type IdCardBaseProps = {
title: ReactNode;
subtitle: ReactNode;
titleAction?: ReactNode;
children?: ReactNode;
};

export type IdCardAccountInfo = {
address: string;
amount: ReactNode;
};

export type IdCardProps = {
idProviderName: string;
identityName: string;
rowsIdInfo?: IdCardAttributeInfo[];
rowsConnectedAccounts?: IdCardAccountInfo[];
onNewName?: (newName: string) => void;
identityNameFallback?: string;
};

export default function IdCard({
idProviderName,
identityName,
rowsIdInfo,
rowsConnectedAccounts,
onNewName,
identityNameFallback,
}: IdCardProps) {
const editable = useEditableValue(identityName, identityNameFallback ?? '', onNewName ?? (() => {}));
const { t } = useTranslation('x', { keyPrefix: 'sharedX' });

export default function IdCard({ title, subtitle, titleAction, children }: IdCardBaseProps) {
return (
<Card type="gradient" className="id-card-x">
<span className="title-row">
<Text.Main>{editable.value}</Text.Main>
{editable.isEditing ? (
<>
<Button.Secondary label={t('idCard.name.save')} onClick={editable.onComplete} />
<Button.Secondary label={t('idCard.name.abort')} onClick={editable.onAbort} />
</>
) : (
onNewName && <Button.Secondary label={t('idCard.name.edit')} onClick={editable.onEdit} />
)}
<Text.Main>{title}</Text.Main>
{titleAction}
</span>
<Text.Capture>{t('idCard.verifiedBy', { idProviderName })}</Text.Capture>
{rowsIdInfo && (
<Card>
{rowsIdInfo.map((info) => (
<Card.Row key={info.key}>
<Text.MainRegular>{info.key}</Text.MainRegular>
<Text.MainMedium>{info.value}</Text.MainMedium>
</Card.Row>
))}
</Card>
)}
{rowsConnectedAccounts && (
<Card>
{rowsConnectedAccounts.map((account) => (
<Card.Row key={account.address}>
<Text.MainRegular>{account.address}</Text.MainRegular>
<Text.MainMedium>{account.amount}</Text.MainMedium>
</Card.Row>
))}
</Card>
)}
<Text.Capture>{subtitle}</Text.Capture>
{children}
</Card>
);
}

IdCard.Content = Card;
IdCard.ContentRow = Card.Row;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import Close from '@assets/svgX/close.svg';
import { identityProvidersAtom } from '@popup/store/identity';
import { useAtomValue } from 'jotai';
import { RejectedIdentity } from '@shared/storage/types';
import { useTranslation } from 'react-i18next';
import Text from '@popup/popupX/shared/Text';
import IdCard from './IdCard';

export type RejectedIdentityProps = {
/** Identity to show. */
identity: RejectedIdentity;
};

export default function RejectedIdCard({ identity }: RejectedIdentityProps) {
const { t } = useTranslation('x', { keyPrefix: 'sharedX' });
const providers = useAtomValue(identityProvidersAtom);
const provider = providers.find((p) => p.ipInfo.ipIdentity === identity.providerIndex);
const idProviderName = provider?.ipInfo.ipDescription.name ?? 'Unknown';
return (
<IdCard title={identity.name} subtitle={t('idCard.rejectedBy', { idProviderName })}>
<IdCard.Content className="rejected">
<Close className="icon-rejected" />
<Text.MainRegular>{t('idCard.itentityRejected')}</Text.MainRegular>
<Text.MainMedium>{identity.error}</Text.MainMedium>
</IdCard.Content>
</IdCard>
);
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default } from './IdCard';
export { default as ConfirmedIdCard } from './ConfirmedIdCard';
export { default as RejectedIdCard } from './RejectedIdCard';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { BaseIdentity } from '@shared/storage/types';
import { useTranslation } from 'react-i18next';
import useEditableValue from '@popup/popupX/shared/EditableValue';
import Button from '@popup/popupX/shared/Button';

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

export type NewNameHandler = (name: string) => void;

export default function useEditableName(identity: BaseIdentity, onNewName?: NewNameHandler) {
const { t } = useTranslation('x', { keyPrefix: 'sharedX' });
const editable = useEditableValue(identity.name, fallbackIdentityName(identity.index), onNewName ?? (() => {}));
const editNameAction = editable.isEditing ? (
<>
<Button.Secondary label={t('idCard.name.save')} onClick={editable.onComplete} />
<Button.Secondary label={t('idCard.name.abort')} onClick={editable.onAbort} />
</>
) : (
onNewName && <Button.Secondary label={t('idCard.name.edit')} onClick={editable.onEdit} />
);
return {
value: editable.value,
actions: editNameAction,
};
}
2 changes: 2 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const t = {
abort: 'Abort',
},
verifiedBy: 'Verified by {{idProviderName}}',
rejectedBy: 'Rejected by {{idProviderName}}',
itentityRejected: 'This identity has been rejected',
},
};

Expand Down

0 comments on commit 2d819f1

Please sign in to comment.