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 { 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<DropdownProps> = (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<DropdownProps> = (props) => {
borderBottomWidth: 1,
}}>
<Pressable
onPress={item.onPress}
onPress={() => handleOnPress(item)}
style={{ paddingTop: 8, paddingBottom: 8 }}>
<Row>
<Icon
@@ -40,6 +45,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
<View>
<Popable
position="bottom"
ref={popover}
backgroundColor={Colors.White}
style={{ top: 10, left: -20, minWidth: 120, elevation: 1 }}
content={
@@ -56,12 +62,12 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
</View>
);
};
// interface item {
// label: string;
// onSelect: () => void;
// }
interface Item {
label: string;
onPress?: () => void;
}
interface DropdownProps {
icon: string;
items?: any;
items: Item[];
}

View File

@@ -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');
}

View File

@@ -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;
}

View File

@@ -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<ViewVcModalProps> = (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<ViewVcModalProps> = (props) => {
},
];
const lockVc = () => {
controller.LOCKING_VC();
};
return (
<Modal
isVisible={props.isVisible}
@@ -55,6 +61,19 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
onDismiss={controller.DISMISS}
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>
);
};

View File

@@ -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)),
};
}

View File

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