diff --git a/components/DropdownIcon.tsx b/components/DropdownIcon.tsx index 2f8d125d..8702b7b3 100644 --- a/components/DropdownIcon.tsx +++ b/components/DropdownIcon.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { FlatList, Pressable, View } from 'react-native'; import { Popable } from 'react-native-popable'; import { Text } from 'react-native-elements'; @@ -7,7 +7,12 @@ import { Row } from './ui'; import { Colors } from './ui/styleUtils'; export const DropdownIcon: React.FC = (props) => { - //const [visible, setVisible] = useState(false); + const popover = useRef(null); + + const handleOnPress = (item: Item) => { + popover.current?.hide(); + item.onPress(); + }; const renderItem = ({ item }) => { return ( @@ -20,7 +25,7 @@ export const DropdownIcon: React.FC = (props) => { borderBottomWidth: 1, }}> handleOnPress(item)} style={{ paddingTop: 8, paddingBottom: 8 }}> = (props) => { = (props) => { ); }; -// interface item { -// label: string; -// onSelect: () => void; -// } +interface Item { + label: string; + onPress?: () => void; +} interface DropdownProps { icon: string; - items?: any; + items: Item[]; } diff --git a/machines/vcItem.ts b/machines/vcItem.ts index e030462d..1ada1793 100644 --- a/machines/vcItem.ts +++ b/machines/vcItem.ts @@ -1,4 +1,5 @@ import { assign, ErrorPlatformEvent, EventFrom, send, StateFrom } from 'xstate'; +import { log } from 'xstate/lib/actions'; import { createModel } from 'xstate/lib/model'; import { VC_ITEM_STORE_KEY } from '../shared/constants'; import { AppServices } from '../shared/GlobalContext'; @@ -25,8 +26,11 @@ const model = createModel( requestId: '', isVerified: false, lastVerifiedOn: null, + locked: false, otp: '', otpError: '', + idError: '', + transactionId: '', }, { events: { @@ -40,7 +44,8 @@ const model = createModel( DOWNLOAD_READY: () => ({}), GET_VC_RESPONSE: (vc: VC) => ({ vc }), VERIFY: () => ({}), - LOCKING_VC: () => ({}), + LOCK_VC: () => ({}), + UNLOCK_VC: () => ({}), INPUT_OTP: (otp: string) => ({ otp }), LOCK: (value: boolean) => ({ value }), }, @@ -144,7 +149,14 @@ export const vcItemMachine = VERIFY: { target: 'verifyingCredential', }, - LOCKING_VC: 'lockingVc', + LOCK_VC: { + actions: ['setTransactionId'], + target: 'requestingOtp', + }, + UNLOCK_VC: { + actions: ['setTransactionId'], + target: 'requestingOtp', + }, }, }, editingTag: { @@ -196,44 +208,33 @@ export const vcItemMachine = }, ], }, - // invalid: { - // entry: ['focusInput'], - // on: { - // INPUT_ID: { - // target: 'idle', - // actions: ['setId', 'clearIdError'], - // }, - // VALIDATE_INPUT: [ - // { cond: 'isEmptyId', target: '.empty' }, - // { - // cond: 'isWrongIdFormat', - // target: '.format', - // }, - // { target: 'requestingOtp' }, - // ], - // }, - // states: { - // empty: { - // entry: ['setIdErrorEmpty'], - // }, - // format: { - // entry: ['setIdErrorWrongFormat'], - // }, - // backend: {}, - // }, - // }, + invalid: { + states: { + empty: { + entry: [log('UPDATE_SERVICE_URL received')], + }, + backend: {}, + }, + on: { + INPUT_OTP: { + target: 'requestingLock', + actions: ['setOtp'], + }, + DISMISS: 'idle', + }, + }, requestingOtp: { invoke: { src: 'requestOtp', onDone: '#acceptingOtpInput', onError: { - target: 'idle', - actions: ['setIdError'], + target: 'invalid.empty', }, }, }, acceptingOtpInput: { id: 'acceptingOtpInput', + entry: ['clearOtp'], on: { INPUT_OTP: { target: 'requestingLock', @@ -242,21 +243,12 @@ export const vcItemMachine = DISMISS: 'idle', }, }, - lockingVc: { - on: { - DISMISS: 'idle', - INPUT_OTP: { - target: 'requestingLock', - actions: ['setOtp'], - }, - }, - }, requestingLock: { invoke: { src: 'requestLock', onDone: { - target: 'lockVc', - actions: ['lockVc'], + target: 'lockingVc', + actions: ['setLock'], }, onError: [ { @@ -266,20 +258,12 @@ export const vcItemMachine = ], }, }, - lockVc: { + lockingVc: { entry: ['storeLock'], on: { STORE_RESPONSE: 'idle', }, }, - storingVcLock: { - entry: 'storeVc', - on: { - STORE_RESPONSE: { - target: 'idle', - }, - }, - }, }, }, { @@ -363,6 +347,10 @@ export const vcItemMachine = }; }), + setTransactionId: model.assign({ + transactionId: () => String(new Date().valueOf()).substring(3, 13), + }), + setOtp: model.assign({ otp: (_, event) => event.otp, }), @@ -372,6 +360,12 @@ export const vcItemMachine = (event as ErrorPlatformEvent).data.message, }), + clearOtp: assign({ otp: '' }), + + setLock: model.assign({ + locked: (context) => !context.locked, + }), + storeLock: send( (context) => { const { serviceRefs, ...data } = context; @@ -448,25 +442,32 @@ export const vcItemMachine = return verifyCredential(context.verifiableCredential); }, - // requestOtp: async (context) => { - // const response = await request('POST', '/req/otp', { - // individualId: context.id, - // individualIdType: context.idType, - // otpChannel: ['EMAIL', 'PHONE'], - // transactionID: context.transactionId, - // }); - // return response; - // }, + requestOtp: async (context) => { + try { + return await request('POST', '/req/otp', { + individualId: context.id, + individualIdType: context.idType, + otpChannel: ['EMAIL', 'PHONE'], + transactionID: context.transactionId, + }); + } catch (error) { + console.error(error); + } + }, requestLock: async (context) => { - const response = await request('POST', '/req/auth-lock', { - individualId: context.id, - individualIdType: context.idType, - otp: context.otp, - //transactionID: context.transactionId, - authType: ['bio'], - }); - return response.response.requestId; + try { + return await request('POST', '/req/auth-lock', { + individualId: context.id, + individualIdType: context.idType, + otp: context.otp, + transactionID: context.transactionId, + authType: ['bio'], + ...(context.locked && { unlockForSeconds: '120' }), + }); + } catch (error) { + console.log(error); + } }, }, @@ -533,3 +534,19 @@ export function selectVerifiableCredential(state: State) { export function selectIsEditingTag(state: State) { return state.matches('editingTag'); } + +export function selectOtpError(state: State) { + return state.context.otpError; +} + +export function selectIsLockingVc(state: State) { + return state.matches('lockingVc'); +} + +export function selectIsAcceptingOtpInput(state: State) { + return state.matches('acceptingOtpInput'); +} + +export function selectIsRequestingOtp(state: State) { + return state.matches('requestingOtp'); +} diff --git a/machines/vcItem.typegen.ts b/machines/vcItem.typegen.ts index 8d92017b..d823838f 100644 --- a/machines/vcItem.typegen.ts +++ b/machines/vcItem.typegen.ts @@ -15,18 +15,20 @@ export interface Typegen0 { | 'CREDENTIAL_DOWNLOADED' | 'done.invoke.vc-item.verifyingCredential:invocation[0]'; logDownloaded: 'CREDENTIAL_DOWNLOADED'; + setTransactionId: 'LOCK_VC' | 'UNLOCK_VC'; setTag: 'SAVE_TAG'; markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]'; logError: 'error.platform.vc-item.verifyingCredential:invocation[0]'; - setIdError: 'error.platform.vc-item.requestingOtp:invocation[0]'; setOtp: 'INPUT_OTP'; - lockVc: 'done.invoke.vc-item.requestingLock:invocation[0]'; + setLock: 'done.invoke.vc-item.requestingLock:invocation[0]'; setOtpError: 'error.platform.vc-item.requestingLock:invocation[0]'; requestVcContext: 'xstate.init'; requestStoredContext: 'GET_VC_RESPONSE'; storeTag: 'SAVE_TAG'; + clearOtp: + | 'done.invoke.vc-item.requestingOtp:invocation[0]' + | 'error.platform.vc-item.requestingLock:invocation[0]'; storeLock: 'done.invoke.vc-item.requestingLock:invocation[0]'; - storeVc: 'xstate.init'; }; 'internalEvents': { 'done.invoke.vc-item.verifyingCredential:invocation[0]': { @@ -38,10 +40,6 @@ export interface Typegen0 { type: 'error.platform.vc-item.verifyingCredential:invocation[0]'; data: unknown; }; - 'error.platform.vc-item.requestingOtp:invocation[0]': { - type: 'error.platform.vc-item.requestingOtp:invocation[0]'; - data: unknown; - }; 'done.invoke.vc-item.requestingLock:invocation[0]': { type: 'done.invoke.vc-item.requestingLock:invocation[0]'; data: unknown; @@ -51,6 +49,15 @@ export interface Typegen0 { type: 'error.platform.vc-item.requestingLock:invocation[0]'; data: unknown; }; + 'error.platform.vc-item.requestingOtp:invocation[0]': { + type: 'error.platform.vc-item.requestingOtp:invocation[0]'; + data: unknown; + }; + 'done.invoke.vc-item.requestingOtp:invocation[0]': { + type: 'done.invoke.vc-item.requestingOtp:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; '': { type: '' }; 'xstate.init': { type: 'xstate.init' }; 'done.invoke.checkStatus': { @@ -80,8 +87,8 @@ export interface Typegen0 { requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]'; }; 'missingImplementations': { - actions: 'logError' | 'setIdError' | 'lockVc' | 'storeVc'; - services: 'requestOtp'; + actions: 'logError'; + services: never; guards: never; delays: never; }; @@ -89,7 +96,7 @@ export interface Typegen0 { checkStatus: 'xstate.init'; downloadCredential: 'DOWNLOAD_READY'; verifyCredential: 'VERIFY' | ''; - requestOtp: 'xstate.init'; + requestOtp: 'LOCK_VC' | 'UNLOCK_VC'; requestLock: 'INPUT_OTP'; }; 'eventsCausingGuards': { @@ -108,12 +115,16 @@ export interface Typegen0 { | 'storingTag' | 'verifyingCredential' | 'checkingVerificationStatus' + | 'invalid' + | 'invalid.empty' + | 'invalid.backend' | 'requestingOtp' | 'acceptingOtpInput' - | 'lockingVc' | 'requestingLock' - | 'lockVc' - | 'storingVcLock' - | { checkingServerData?: 'checkingStatus' | 'downloadingCredential' }; + | 'lockingVc' + | { + checkingServerData?: 'checkingStatus' | 'downloadingCredential'; + invalid?: 'empty' | 'backend'; + }; 'tags': never; } diff --git a/screens/Home/ViewVcModal.tsx b/screens/Home/ViewVcModal.tsx index e5abe565..c22cc3cf 100644 --- a/screens/Home/ViewVcModal.tsx +++ b/screens/Home/ViewVcModal.tsx @@ -5,14 +5,24 @@ import { Modal } from '../../components/ui/Modal'; import { Colors } from '../../components/ui/styleUtils'; import { VcDetails } from '../../components/VcDetails'; import { DropdownIcon } from '../../components/DropdownIcon'; +import { MessageOverlay } from '../../components/MessageOverlay'; +import { OtpVerificationModal } from './MyVcs/OtpVerificationModal'; import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController'; export const ViewVcModal: React.FC = (props) => { const controller = useViewVcModal(props); + const lockVc = () => { + if (controller.vc.locked) { + controller.UNLOCK_VC(); + } else { + controller.LOCK_VC(); + } + }; + const DATA = [ { - label: 'Lock', + label: controller.vc.locked ? 'Unlock' : 'Lock', icon: 'lock-outline', onPress: () => lockVc(), }, @@ -31,10 +41,6 @@ export const ViewVcModal: React.FC = (props) => { }, ]; - const lockVc = () => { - controller.LOCKING_VC(); - }; - return ( = (props) => { onDismiss={controller.DISMISS} onSave={controller.SAVE_TAG} /> + + + + ); }; diff --git a/screens/Home/ViewVcModalController.ts b/screens/Home/ViewVcModalController.ts index 4e72fdd1..d99bf632 100644 --- a/screens/Home/ViewVcModalController.ts +++ b/screens/Home/ViewVcModalController.ts @@ -3,8 +3,10 @@ import { ActorRefFrom } from 'xstate'; import { ModalProps } from '../../components/ui/Modal'; import { selectOtpError, + selectIsAcceptingOtpInput, selectIsEditingTag, selectIsLockingVc, + selectIsRequestingOtp, selectVc, VcItemEvents, vcItemMachine, @@ -17,12 +19,15 @@ export function useViewVcModal({ vcItemActor }: ViewVcModalProps) { isEditingTag: useSelector(vcItemActor, selectIsEditingTag), selectIsLockingVc: useSelector(vcItemActor, selectIsLockingVc), + isAcceptingOtpInput: useSelector(vcItemActor, selectIsAcceptingOtpInput), + isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp), EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()), SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)), DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()), LOCK: (value: boolean) => vcItemActor.send(VcItemEvents.LOCK(value)), - LOCKING_VC: () => vcItemActor.send(VcItemEvents.LOCKING_VC()), + LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()), + UNLOCK_VC: () => vcItemActor.send(VcItemEvents.UNLOCK_VC()), INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)), }; } diff --git a/types/vc.ts b/types/vc.ts index 1cdcda26..25474f85 100644 --- a/types/vc.ts +++ b/types/vc.ts @@ -9,6 +9,7 @@ export interface VC { isVerified: boolean; lastVerifiedOn: number; reason?: string; + locked: boolean; } export type VcIdType = 'UIN' | 'VID';