lock unlock VC

This commit is contained in:
Danica Erediano
2022-04-19 18:40:26 +08:00
parent 82dc109e50
commit 4a94d868e5
6 changed files with 154 additions and 95 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useRef } from 'react';
import { FlatList, Pressable, View } from 'react-native'; import { FlatList, Pressable, View } from 'react-native';
import { Popable } from 'react-native-popable'; import { Popable } from 'react-native-popable';
import { Text } from 'react-native-elements'; import { Text } from 'react-native-elements';
@@ -7,7 +7,12 @@ import { Row } from './ui';
import { Colors } from './ui/styleUtils'; import { Colors } from './ui/styleUtils';
export const DropdownIcon: React.FC<DropdownProps> = (props) => { export const DropdownIcon: React.FC<DropdownProps> = (props) => {
//const [visible, setVisible] = useState(false); const popover = useRef(null);
const handleOnPress = (item: Item) => {
popover.current?.hide();
item.onPress();
};
const renderItem = ({ item }) => { const renderItem = ({ item }) => {
return ( return (
@@ -20,7 +25,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
borderBottomWidth: 1, borderBottomWidth: 1,
}}> }}>
<Pressable <Pressable
onPress={item.onPress} onPress={() => handleOnPress(item)}
style={{ paddingTop: 8, paddingBottom: 8 }}> style={{ paddingTop: 8, paddingBottom: 8 }}>
<Row> <Row>
<Icon <Icon
@@ -40,6 +45,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
<View> <View>
<Popable <Popable
position="bottom" position="bottom"
ref={popover}
backgroundColor={Colors.White} backgroundColor={Colors.White}
style={{ top: 10, left: -20, minWidth: 120, elevation: 1 }} style={{ top: 10, left: -20, minWidth: 120, elevation: 1 }}
content={ content={
@@ -56,12 +62,12 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
</View> </View>
); );
}; };
// interface item { interface Item {
// label: string; label: string;
// onSelect: () => void; onPress?: () => void;
// } }
interface DropdownProps { interface DropdownProps {
icon: string; icon: string;
items?: any; items: Item[];
} }

View File

@@ -1,4 +1,5 @@
import { assign, ErrorPlatformEvent, EventFrom, send, StateFrom } from 'xstate'; import { assign, ErrorPlatformEvent, EventFrom, send, StateFrom } from 'xstate';
import { log } from 'xstate/lib/actions';
import { createModel } from 'xstate/lib/model'; import { createModel } from 'xstate/lib/model';
import { VC_ITEM_STORE_KEY } from '../shared/constants'; import { VC_ITEM_STORE_KEY } from '../shared/constants';
import { AppServices } from '../shared/GlobalContext'; import { AppServices } from '../shared/GlobalContext';
@@ -25,8 +26,11 @@ const model = createModel(
requestId: '', requestId: '',
isVerified: false, isVerified: false,
lastVerifiedOn: null, lastVerifiedOn: null,
locked: false,
otp: '', otp: '',
otpError: '', otpError: '',
idError: '',
transactionId: '',
}, },
{ {
events: { events: {
@@ -40,7 +44,8 @@ const model = createModel(
DOWNLOAD_READY: () => ({}), DOWNLOAD_READY: () => ({}),
GET_VC_RESPONSE: (vc: VC) => ({ vc }), GET_VC_RESPONSE: (vc: VC) => ({ vc }),
VERIFY: () => ({}), VERIFY: () => ({}),
LOCKING_VC: () => ({}), LOCK_VC: () => ({}),
UNLOCK_VC: () => ({}),
INPUT_OTP: (otp: string) => ({ otp }), INPUT_OTP: (otp: string) => ({ otp }),
LOCK: (value: boolean) => ({ value }), LOCK: (value: boolean) => ({ value }),
}, },
@@ -144,7 +149,14 @@ export const vcItemMachine =
VERIFY: { VERIFY: {
target: 'verifyingCredential', target: 'verifyingCredential',
}, },
LOCKING_VC: 'lockingVc', LOCK_VC: {
actions: ['setTransactionId'],
target: 'requestingOtp',
},
UNLOCK_VC: {
actions: ['setTransactionId'],
target: 'requestingOtp',
},
}, },
}, },
editingTag: { editingTag: {
@@ -196,44 +208,33 @@ export const vcItemMachine =
}, },
], ],
}, },
// invalid: { invalid: {
// entry: ['focusInput'], states: {
// on: { empty: {
// INPUT_ID: { entry: [log('UPDATE_SERVICE_URL received')],
// target: 'idle', },
// actions: ['setId', 'clearIdError'], backend: {},
// }, },
// VALIDATE_INPUT: [ on: {
// { cond: 'isEmptyId', target: '.empty' }, INPUT_OTP: {
// { target: 'requestingLock',
// cond: 'isWrongIdFormat', actions: ['setOtp'],
// target: '.format', },
// }, DISMISS: 'idle',
// { target: 'requestingOtp' }, },
// ], },
// },
// states: {
// empty: {
// entry: ['setIdErrorEmpty'],
// },
// format: {
// entry: ['setIdErrorWrongFormat'],
// },
// backend: {},
// },
// },
requestingOtp: { requestingOtp: {
invoke: { invoke: {
src: 'requestOtp', src: 'requestOtp',
onDone: '#acceptingOtpInput', onDone: '#acceptingOtpInput',
onError: { onError: {
target: 'idle', target: 'invalid.empty',
actions: ['setIdError'],
}, },
}, },
}, },
acceptingOtpInput: { acceptingOtpInput: {
id: 'acceptingOtpInput', id: 'acceptingOtpInput',
entry: ['clearOtp'],
on: { on: {
INPUT_OTP: { INPUT_OTP: {
target: 'requestingLock', target: 'requestingLock',
@@ -242,21 +243,12 @@ export const vcItemMachine =
DISMISS: 'idle', DISMISS: 'idle',
}, },
}, },
lockingVc: {
on: {
DISMISS: 'idle',
INPUT_OTP: {
target: 'requestingLock',
actions: ['setOtp'],
},
},
},
requestingLock: { requestingLock: {
invoke: { invoke: {
src: 'requestLock', src: 'requestLock',
onDone: { onDone: {
target: 'lockVc', target: 'lockingVc',
actions: ['lockVc'], actions: ['setLock'],
}, },
onError: [ onError: [
{ {
@@ -266,20 +258,12 @@ export const vcItemMachine =
], ],
}, },
}, },
lockVc: { lockingVc: {
entry: ['storeLock'], entry: ['storeLock'],
on: { on: {
STORE_RESPONSE: 'idle', 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({ setOtp: model.assign({
otp: (_, event) => event.otp, otp: (_, event) => event.otp,
}), }),
@@ -372,6 +360,12 @@ export const vcItemMachine =
(event as ErrorPlatformEvent).data.message, (event as ErrorPlatformEvent).data.message,
}), }),
clearOtp: assign({ otp: '' }),
setLock: model.assign({
locked: (context) => !context.locked,
}),
storeLock: send( storeLock: send(
(context) => { (context) => {
const { serviceRefs, ...data } = context; const { serviceRefs, ...data } = context;
@@ -448,25 +442,32 @@ export const vcItemMachine =
return verifyCredential(context.verifiableCredential); return verifyCredential(context.verifiableCredential);
}, },
// requestOtp: async (context) => { requestOtp: async (context) => {
// const response = await request('POST', '/req/otp', { try {
// individualId: context.id, return await request('POST', '/req/otp', {
// individualIdType: context.idType, individualId: context.id,
// otpChannel: ['EMAIL', 'PHONE'], individualIdType: context.idType,
// transactionID: context.transactionId, otpChannel: ['EMAIL', 'PHONE'],
// }); transactionID: context.transactionId,
// return response; });
// }, } catch (error) {
console.error(error);
}
},
requestLock: async (context) => { requestLock: async (context) => {
const response = await request('POST', '/req/auth-lock', { try {
individualId: context.id, return await request('POST', '/req/auth-lock', {
individualIdType: context.idType, individualId: context.id,
otp: context.otp, individualIdType: context.idType,
//transactionID: context.transactionId, otp: context.otp,
authType: ['bio'], transactionID: context.transactionId,
}); authType: ['bio'],
return response.response.requestId; ...(context.locked && { unlockForSeconds: '120' }),
});
} catch (error) {
console.log(error);
}
}, },
}, },
@@ -533,3 +534,19 @@ export function selectVerifiableCredential(state: State) {
export function selectIsEditingTag(state: State) { export function selectIsEditingTag(state: State) {
return state.matches('editingTag'); 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');
}

View File

@@ -15,18 +15,20 @@ export interface Typegen0 {
| 'CREDENTIAL_DOWNLOADED' | 'CREDENTIAL_DOWNLOADED'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'; | 'done.invoke.vc-item.verifyingCredential:invocation[0]';
logDownloaded: 'CREDENTIAL_DOWNLOADED'; logDownloaded: 'CREDENTIAL_DOWNLOADED';
setTransactionId: 'LOCK_VC' | 'UNLOCK_VC';
setTag: 'SAVE_TAG'; setTag: 'SAVE_TAG';
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]'; markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
logError: 'error.platform.vc-item.verifyingCredential:invocation[0]'; logError: 'error.platform.vc-item.verifyingCredential:invocation[0]';
setIdError: 'error.platform.vc-item.requestingOtp:invocation[0]';
setOtp: 'INPUT_OTP'; 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]'; setOtpError: 'error.platform.vc-item.requestingLock:invocation[0]';
requestVcContext: 'xstate.init'; requestVcContext: 'xstate.init';
requestStoredContext: 'GET_VC_RESPONSE'; requestStoredContext: 'GET_VC_RESPONSE';
storeTag: 'SAVE_TAG'; 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]'; storeLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
storeVc: 'xstate.init';
}; };
'internalEvents': { 'internalEvents': {
'done.invoke.vc-item.verifyingCredential:invocation[0]': { 'done.invoke.vc-item.verifyingCredential:invocation[0]': {
@@ -38,10 +40,6 @@ export interface Typegen0 {
type: 'error.platform.vc-item.verifyingCredential:invocation[0]'; type: 'error.platform.vc-item.verifyingCredential:invocation[0]';
data: unknown; 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]': { 'done.invoke.vc-item.requestingLock:invocation[0]': {
type: 'done.invoke.vc-item.requestingLock:invocation[0]'; type: 'done.invoke.vc-item.requestingLock:invocation[0]';
data: unknown; data: unknown;
@@ -51,6 +49,15 @@ export interface Typegen0 {
type: 'error.platform.vc-item.requestingLock:invocation[0]'; type: 'error.platform.vc-item.requestingLock:invocation[0]';
data: unknown; 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: '' }; '': { type: '' };
'xstate.init': { type: 'xstate.init' }; 'xstate.init': { type: 'xstate.init' };
'done.invoke.checkStatus': { 'done.invoke.checkStatus': {
@@ -80,8 +87,8 @@ export interface Typegen0 {
requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]'; requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
}; };
'missingImplementations': { 'missingImplementations': {
actions: 'logError' | 'setIdError' | 'lockVc' | 'storeVc'; actions: 'logError';
services: 'requestOtp'; services: never;
guards: never; guards: never;
delays: never; delays: never;
}; };
@@ -89,7 +96,7 @@ export interface Typegen0 {
checkStatus: 'xstate.init'; checkStatus: 'xstate.init';
downloadCredential: 'DOWNLOAD_READY'; downloadCredential: 'DOWNLOAD_READY';
verifyCredential: 'VERIFY' | ''; verifyCredential: 'VERIFY' | '';
requestOtp: 'xstate.init'; requestOtp: 'LOCK_VC' | 'UNLOCK_VC';
requestLock: 'INPUT_OTP'; requestLock: 'INPUT_OTP';
}; };
'eventsCausingGuards': { 'eventsCausingGuards': {
@@ -108,12 +115,16 @@ export interface Typegen0 {
| 'storingTag' | 'storingTag'
| 'verifyingCredential' | 'verifyingCredential'
| 'checkingVerificationStatus' | 'checkingVerificationStatus'
| 'invalid'
| 'invalid.empty'
| 'invalid.backend'
| 'requestingOtp' | 'requestingOtp'
| 'acceptingOtpInput' | 'acceptingOtpInput'
| 'lockingVc'
| 'requestingLock' | 'requestingLock'
| 'lockVc' | 'lockingVc'
| 'storingVcLock' | {
| { checkingServerData?: 'checkingStatus' | 'downloadingCredential' }; checkingServerData?: 'checkingStatus' | 'downloadingCredential';
invalid?: 'empty' | 'backend';
};
'tags': never; 'tags': never;
} }

View File

@@ -5,14 +5,24 @@ import { Modal } from '../../components/ui/Modal';
import { Colors } from '../../components/ui/styleUtils'; import { Colors } from '../../components/ui/styleUtils';
import { VcDetails } from '../../components/VcDetails'; import { VcDetails } from '../../components/VcDetails';
import { DropdownIcon } from '../../components/DropdownIcon'; import { DropdownIcon } from '../../components/DropdownIcon';
import { MessageOverlay } from '../../components/MessageOverlay';
import { OtpVerificationModal } from './MyVcs/OtpVerificationModal';
import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController'; import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => { export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
const controller = useViewVcModal(props); const controller = useViewVcModal(props);
const lockVc = () => {
if (controller.vc.locked) {
controller.UNLOCK_VC();
} else {
controller.LOCK_VC();
}
};
const DATA = [ const DATA = [
{ {
label: 'Lock', label: controller.vc.locked ? 'Unlock' : 'Lock',
icon: 'lock-outline', icon: 'lock-outline',
onPress: () => lockVc(), onPress: () => lockVc(),
}, },
@@ -31,10 +41,6 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
}, },
]; ];
const lockVc = () => {
controller.LOCKING_VC();
};
return ( return (
<Modal <Modal
isVisible={props.isVisible} isVisible={props.isVisible}
@@ -55,6 +61,19 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
onDismiss={controller.DISMISS} onDismiss={controller.DISMISS}
onSave={controller.SAVE_TAG} onSave={controller.SAVE_TAG}
/> />
<OtpVerificationModal
isVisible={controller.isAcceptingOtpInput}
onDismiss={controller.DISMISS}
onInputDone={controller.INPUT_OTP}
error={controller.otpError}
/>
<MessageOverlay
isVisible={controller.isRequestingOtp}
title="Requesting OTP..."
hasProgress
/>
</Modal> </Modal>
); );
}; };

View File

@@ -3,8 +3,10 @@ import { ActorRefFrom } from 'xstate';
import { ModalProps } from '../../components/ui/Modal'; import { ModalProps } from '../../components/ui/Modal';
import { import {
selectOtpError, selectOtpError,
selectIsAcceptingOtpInput,
selectIsEditingTag, selectIsEditingTag,
selectIsLockingVc, selectIsLockingVc,
selectIsRequestingOtp,
selectVc, selectVc,
VcItemEvents, VcItemEvents,
vcItemMachine, vcItemMachine,
@@ -17,12 +19,15 @@ export function useViewVcModal({ vcItemActor }: ViewVcModalProps) {
isEditingTag: useSelector(vcItemActor, selectIsEditingTag), isEditingTag: useSelector(vcItemActor, selectIsEditingTag),
selectIsLockingVc: useSelector(vcItemActor, selectIsLockingVc), selectIsLockingVc: useSelector(vcItemActor, selectIsLockingVc),
isAcceptingOtpInput: useSelector(vcItemActor, selectIsAcceptingOtpInput),
isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp),
EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()), EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()),
SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)), SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)),
DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()), DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()),
LOCK: (value: boolean) => vcItemActor.send(VcItemEvents.LOCK(value)), 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)), INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
}; };
} }

View File

@@ -9,6 +9,7 @@ export interface VC {
isVerified: boolean; isVerified: boolean;
lastVerifiedOn: number; lastVerifiedOn: number;
reason?: string; reason?: string;
locked: boolean;
} }
export type VcIdType = 'UIN' | 'VID'; export type VcIdType = 'UIN' | 'VID';