diff --git a/components/VcDetails.tsx b/components/VcDetails.tsx index 2bffebc6..e71b091f 100644 --- a/components/VcDetails.tsx +++ b/components/VcDetails.tsx @@ -1,3 +1,4 @@ +import { formatDistanceToNow } from 'date-fns'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { Image } from 'react-native'; @@ -5,6 +6,7 @@ import { Icon, ListItem } from 'react-native-elements'; import { VC, CredentialSubject } from '../types/vc'; import { Column, Row, Text } from './ui'; import { Colors } from './ui/styleUtils'; +import { TextItem } from './ui/TextItem'; const VerifiedIcon: React.FC = () => { return ( @@ -42,7 +44,7 @@ export const VcDetails: React.FC = (props) => { {props.vc?.id} - + {t('status')} @@ -74,64 +76,49 @@ export const VcDetails: React.FC = (props) => { - - - {t('fullName')} - - {props.vc?.verifiableCredential.credentialSubject.fullName} - - - - - - {t('gender')} - - {getLocalizedField( - props.vc?.verifiableCredential.credentialSubject.gender - )} - - - - - - {t('dateOfBirth')} - - {props.vc?.verifiableCredential.credentialSubject.dateOfBirth} - - - - - - {t('phoneNumber')} - - {props.vc?.verifiableCredential.credentialSubject.phone} - - - - - - {t('email')} - - {props.vc?.verifiableCredential.credentialSubject.email} - - - - - - {t('address')} - - {getFullAddress(props.vc?.verifiableCredential.credentialSubject)} - - - - {Boolean(props.vc?.reason) && ( - - - {t('reasonForSharing')} - {props.vc?.reason} - - + + + + + + + {props.vc?.reason?.length > 0 && ( + + Reason(s) for sharing + )} + {props.vc?.reason?.map((reason, index) => ( + + ))} ); }; @@ -158,13 +145,13 @@ function getFullAddress(credential: CredentialSubject) { 'province', 'region', ]; + return fields .map((field) => getLocalizedField(credential[field])) .concat(credential.postalCode) .filter(Boolean) .join(', '); } - function getLocalizedField(rawField: string | LocalizedField) { try { const locales: LocalizedField[] = diff --git a/components/VidDetails.tsx b/components/VidDetails.tsx deleted file mode 100644 index 98f17f72..00000000 --- a/components/VidDetails.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { formatDistanceToNow } from 'date-fns'; -import React from 'react'; -import { Image } from 'react-native'; -import { ListItem } from 'react-native-elements'; -import { VID, VIDCredential } from '../types/vid'; -import { Column, Row, Text } from './ui'; -import { Colors } from './ui/styleUtils'; -import { TextItem } from './ui/TextItem'; - -export const VidDetails: React.FC = (props) => { - return ( - - - - - Generated - - - {new Date(props.vid?.generatedOn).toLocaleDateString()} - - - - - UIN - - - {props.vid?.uin} - - - - - Status - - - Valid - - - - {props.vid?.credential.biometrics && ( - - - - Photo - - - - - - - )} - - - - - - - {props.vid?.reason?.length > 0 && ( - - Reason(s) for sharing - - )} - {props.vid?.reason?.map((reason, index) => ( - - ))} - - ); -}; - -interface VidDetailsProps { - vid: VID; -} - -interface LocalizedField { - language: string; - value: string; -} - -function getFullAddress(credential: VIDCredential) { - if (!credential) { - return ''; - } - - const fields = [ - 'addressLine1', - 'addressLine2', - 'addressLine3', - 'city', - 'province', - ]; - return ( - fields.map((field) => getLocalizedField(credential[field])).join(', ') + - ', ' + - credential.postalCode - ); -} - -function getLocalizedField(rawField: string) { - try { - const locales: LocalizedField[] = JSON.parse(rawField); - // TODO: language switching - return locales.find((locale) => locale.language === 'eng').value; - } catch (e) { - return ''; - } -} diff --git a/machines/vc.ts b/machines/vc.ts index ed1db207..0fa0b4d3 100644 --- a/machines/vc.ts +++ b/machines/vc.ts @@ -37,145 +37,173 @@ const model = createModel( export const VcEvents = model.events; -export const vcMachine = model.createMachine( - { - tsTypes: {} as import('./vc.typegen').Typegen0, - schema: { - context: model.initialContext, - events: {} as EventFrom, - }, - id: 'vc', - initial: 'init', - states: { - init: { - initial: 'myVcs', - states: { - myVcs: { - entry: ['loadMyVcs'], - on: { - STORE_RESPONSE: { - target: 'receivedVcs', - actions: ['setMyVcs'], - }, - }, - }, - receivedVcs: { - entry: ['loadReceivedVcs'], - on: { - STORE_RESPONSE: { - target: '#ready', - actions: ['setReceivedVcs'], - }, - }, - }, - }, +export const vcMachine = + /** @xstate-layout N4IgpgJg5mDOIC5QDcDGA6AlgO0wF3QFsBPANVVgGIBlAFQHkAlAUQH0XqAFegOWucSgADgHtY+TCOyCQAD0QBmABwAmdAEYALCoCsANgAMOgJx6dAdnNmANCGKIlBvemWbLKvcc06FVgL5+tmhYuAQATmCoYJjIkORUdExsHNx8AkggouJ4ktIZ8gjKalq65qrmmup6SpqatvYI6k6a6HoKmko6OkpK5sb9AUEYEQCGEMREZBRYEAA2YJQsAGIcABKsALIAmqykAMLUMlkSUjIF6uom6JV6ujrqxo7qZfWITRUamgpOKubdWuYVIMQMFRuNJvF0BEAGYRWAACxwUBoDBY7GYXF4-COYhOeVA50u5nQxh8fyKCm6uleCFMxKayjaBgUCjaFmBoLAYwmESiMTi00wcwWyzW6L2zAAkqRmAARXYHHHZXJnN7qXwacwGAyVJymJTqGnqlnoToqFS+XzqA1ODnDLng3nRWIQSEwuGI7DIxJolJY9LCXE5U75NV6dToJy+YwGXrGFRuI0WiNKUnqvS3LwGcwKO1Qh3ESgAcWYtHFUpl8v2hwyx2D+LkbxUplangzPVumhjdTsiEMBg0XWb6rp1vMebBhZLZf2rEltGYGyVeNVjQuEeZvi1zO1Nl7CH7g50w4Uo56E4LlFnAEFZbK5cv66vjAoI1ahyz9OSaYfLsfjCOejmGOF7cleeysLK9AAOo8AAMvQt4PrWQYqqGjTHgokbxhaKgPF46gqEoP7akeug1H0x7aKB4zgeW0rIYGyohgSfa6C4NRVO0mh6DxlQ0n8OitE0mhOD4OpuLmwLYCIEBwDIwQ4PgEIUI+aGsYU1ToBY0baM2Si3LxP5WJGNTNgYeFUgMgQghgSnhJEzoCvAKHMQ2BTtPSRg6o4omVBUNIAcUqgKCoTg9DqJh5vZaksY2hQWho3kdDqOrPD2DSGFhsbaAZDyAjxNETCQkJCvMsXuYgjzOOafxakorJuBaRoWUJ3wWRUHQ+AZehFSpsD5rCcCelAFWriOKbtg1XUmC8+79MYpras2Zj9H0CZ9SVqmuSu6GmGotUWLGjWAgoNKpi0DIhfoFhmFJQz5ty+Z8i6pXCmNe2dBoZj3BZlw1KyRqvhGGYWGtxhlAZSh9U6-KutM7rDUiH0aSOag+I8zQ-N4SYGd9VIPIRzZETDjlw-EKPxaYEZVF0TSWQDehGv9pqkgYENVJcvhFZTBQvvSP30-9XxM-ulgDldvjVA15qaHavOIM2i1NDoPmpf5GWIMe9JDjotSGFm6gBAEQA */ + model.createMachine( + { + tsTypes: {} as import('./vc.typegen').Typegen0, + schema: { + context: model.initialContext, + events: {} as EventFrom, }, - ready: { - id: 'ready', - entry: [sendParent('READY')], - on: { - GET_RECEIVED_VCS: { - actions: ['getReceivedVcsResponse'], - }, - GET_VC_ITEM: { - actions: ['getVcItemResponse'], - }, - VC_ADDED: { - actions: ['prependToMyVcs'], - }, - VC_DOWNLOADED: { - actions: ['setDownloadedVc'], - }, - VC_RECEIVED: { - actions: ['prependToReceivedVcs'], - }, - }, - type: 'parallel', - states: { - myVcs: { - initial: 'idle', - states: { - idle: { - on: { - REFRESH_MY_VCS: 'refreshing', + id: 'vc', + initial: 'init', + states: { + init: { + initial: 'myVcs', + states: { + myVcs: { + entry: 'loadMyVcs', + on: { + STORE_RESPONSE: { + actions: 'setMyVcs', + target: 'receivedVcs', }, }, - refreshing: { - entry: ['loadMyVcs'], - on: { - STORE_RESPONSE: { - target: 'idle', - actions: ['setMyVcs'], + }, + receivedVcs: { + entry: 'loadReceivedVcs', + on: { + STORE_RESPONSE: { + actions: 'setReceivedVcs', + target: '#vc.ready', + }, + }, + }, + }, + }, + ready: { + entry: sendParent('READY'), + type: 'parallel', + states: { + myVcs: { + initial: 'idle', + states: { + idle: { + on: { + REFRESH_MY_VCS: { + target: 'refreshing', + }, + }, + }, + refreshing: { + entry: 'loadMyVcs', + on: { + STORE_RESPONSE: { + actions: 'setMyVcs', + target: 'idle', + }, + }, + }, + }, + }, + receivedVcs: { + initial: 'idle', + states: { + idle: { + on: { + REFRESH_RECEIVED_VCS: { + target: 'refreshing', + }, + }, + }, + refreshing: { + entry: 'loadReceivedVcs', + on: { + STORE_RESPONSE: { + actions: 'setReceivedVcs', + target: 'idle', + }, }, }, }, }, }, - receivedVcs: { - initial: 'idle', - states: { - idle: { - on: { - REFRESH_RECEIVED_VCS: 'refreshing', - }, - }, - refreshing: { - entry: ['loadReceivedVcs'], - on: { - STORE_RESPONSE: { - target: 'idle', - actions: ['setReceivedVcs'], - }, - }, - }, + on: { + GET_RECEIVED_VCS: { + actions: 'getReceivedVcsResponse', }, + GET_VC_ITEM: { + actions: 'getVcItemResponse', + }, + VC_ADDED: { + actions: 'prependToMyVcs', + }, + VC_DOWNLOADED: { + actions: 'setDownloadedVc', + }, + VC_RECEIVED: [ + { + actions: 'moveExistingVcToTop', + cond: 'hasExistingReceivedVc', + }, + { + actions: 'prependToReceivedVcs', + }, + ], }, }, }, }, - }, - { - actions: { - getReceivedVcsResponse: respond((context) => ({ - type: 'VC_RESPONSE', - response: context.receivedVcs, - })), + { + actions: { + getReceivedVcsResponse: respond((context) => ({ + type: 'VC_RESPONSE', + response: context.receivedVcs, + })), - getVcItemResponse: respond((context, event) => { - const vc = context.vcs[event.vcKey]; - return VcItemEvents.GET_VC_RESPONSE(vc); - }), + getVcItemResponse: respond((context, event) => { + const vc = context.vcs[event.vcKey]; + return VcItemEvents.GET_VC_RESPONSE(vc); + }), - loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), { - to: (context) => context.serviceRefs.store, - }), + loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), { + to: (context) => context.serviceRefs.store, + }), - loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), { - to: (context) => context.serviceRefs.store, - }), + loadReceivedVcs: send(StoreEvents.GET(RECEIVED_VCS_STORE_KEY), { + to: (context) => context.serviceRefs.store, + }), - setMyVcs: model.assign({ - myVcs: (_context, event) => (event.response || []) as string[], - }), + setMyVcs: model.assign({ + myVcs: (_context, event) => (event.response || []) as string[], + }), - setReceivedVcs: model.assign({ - receivedVcs: (_context, event) => (event.response || []) as string[], - }), + setReceivedVcs: model.assign({ + receivedVcs: (_context, event) => (event.response || []) as string[], + }), - setDownloadedVc: (context, event) => { - context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc; + setDownloadedVc: (context, event) => { + context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc; + }, + + prependToMyVcs: model.assign({ + myVcs: (context, event) => [event.vcKey, ...context.myVcs], + }), + + prependToReceivedVcs: model.assign({ + receivedVcs: (context, event) => [ + event.vcKey, + ...context.receivedVcs, + ], + }), + + moveExistingVcToTop: model.assign({ + receivedVcs: (context, event) => { + return [ + event.vcKey, + ...context.receivedVcs.filter((value) => value === event.vcKey), + ]; + }, + }), }, - prependToMyVcs: model.assign({ - myVcs: (context, event) => [event.vcKey, ...context.myVcs], - }), - - prependToReceivedVcs: model.assign({ - receivedVcs: (context, event) => [event.vcKey, ...context.receivedVcs], - }), - }, - } -); + guards: { + hasExistingReceivedVc: (context, event) => + context.receivedVcs.includes(event.vcKey), + }, + } + ); export function createVcMachine(serviceRefs: AppServices) { return vcMachine.withContext({ diff --git a/machines/vc.typegen.ts b/machines/vc.typegen.ts index 00c29778..57f7d90e 100644 --- a/machines/vc.typegen.ts +++ b/machines/vc.typegen.ts @@ -9,6 +9,7 @@ export interface Typegen0 { getVcItemResponse: 'GET_VC_ITEM'; prependToMyVcs: 'VC_ADDED'; setDownloadedVc: 'VC_DOWNLOADED'; + moveExistingVcToTop: 'VC_RECEIVED'; prependToReceivedVcs: 'VC_RECEIVED'; loadMyVcs: 'REFRESH_MY_VCS'; loadReceivedVcs: 'STORE_RESPONSE' | 'REFRESH_RECEIVED_VCS'; @@ -24,7 +25,9 @@ export interface Typegen0 { delays: never; }; 'eventsCausingServices': {}; - 'eventsCausingGuards': {}; + 'eventsCausingGuards': { + hasExistingReceivedVc: 'VC_RECEIVED'; + }; 'eventsCausingDelays': {}; 'matchesStates': | 'init' diff --git a/screens/Home/ViewVcModal.tsx b/screens/Home/ViewVcModal.tsx index 70acd1aa..db41938b 100644 --- a/screens/Home/ViewVcModal.tsx +++ b/screens/Home/ViewVcModal.tsx @@ -51,6 +51,7 @@ export const ViewVcModal: React.FC = (props) => { title={t('requestingOtp')} hasProgress /> + {controller.reAuthenticating !== '' && controller.reAuthenticating == 'passcode' && (