feat(#INJI-42): [Kiruthika/Bhargavi] show error overlay if saving fails while downloading VC

Issue Link: https://mosip.atlassian.net/browse/INJI-42
This commit is contained in:
KiruthikaJeyashankar
2023-05-18 16:19:19 +05:30
parent 4f11bee78e
commit cd6f1be1d4
12 changed files with 462 additions and 169 deletions

View File

@@ -17,7 +17,11 @@ import {
selectContext,
selectTag,
selectEmptyWalletBindingId,
selectStoreError,
selectIsSavingFailedInIdle,
} from '../machines/vcItem';
import { VcItemEvents } from '../machines/vcItem';
import { MessageOverlay } from '../components/MessageOverlay';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { RotatingIcon } from './RotatingIcon';
@@ -26,6 +30,7 @@ import { useTranslation } from 'react-i18next';
import { VcItemTags } from './VcItemTags';
import VerifiedIcon from './VerifiedIcon';
import { getLocalizedField } from '../i18n';
import { selectVcLabel } from '../machines/settings';
const getDetails = (arg1, arg2, verifiableCredential) => {
if (arg1 === 'Status') {
@@ -115,6 +120,7 @@ const WalletUnverified: React.FC = () => {
export const VcItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext);
const { t } = useTranslation('VcDetails');
const { t: commonTranslate } = useTranslation('common');
const machine = useRef(
createVcItemMachine(
appService.getSnapshot().context.serviceRefs,
@@ -126,6 +132,18 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const settingsService = appService.children.get('settings');
const storeError = useSelector(service, selectStoreError);
const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle);
const vcLabel = useSelector(settingsService, selectVcLabel);
const DISMISS = () => service.send(VcItemEvents.DISMISS());
let storeErrorTranslationPath = 'errors.savingFailed';
const isDiskFullError =
storeError?.message?.match('No space left on device') != null;
if (isDiskFullError) {
storeErrorTranslationPath = 'errors.diskFullError';
}
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = verifiableCredential?.credentialSubject.UIN;
@@ -139,158 +157,178 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const tag = useSelector(service, selectTag);
return (
<Pressable
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
borderRadius={4}
<React.Fragment>
<Pressable
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
!verifiableCredential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
<Column>
<Text
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="bold"
size="smaller">
{t('idType')}
</Text>
<Text
weight="bold"
color={Theme.Colors.Details}
size="smaller"
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
borderRadius={4}
style={
!verifiableCredential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
<Column>
<Text
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="bold"
size="smaller">
{t('idType')}
</Text>
<Text
weight="bold"
color={Theme.Colors.Details}
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{t('nationalCard')}
</Text>
</Column>
<Image
source={Theme.MosipLogo}
style={Theme.Styles.logo}
resizeMethod="auto"
/>
</Row>
<Row
crossAlign="center"
margin="5 0 0 0"
style={
!verifiableCredential ? Theme.Styles.loadingContainer : null
}>
<Column
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
? Theme.Styles.loadingContainer
: Theme.Styles.closeDetails
}>
{t('nationalCard')}
</Text>
</Column>
<Image
source={Theme.MosipLogo}
style={Theme.Styles.logo}
resizeMethod="auto"
/>
</Row>
<Row
crossAlign="center"
margin="5 0 0 0"
style={!verifiableCredential ? Theme.Styles.loadingContainer : null}>
<Column
style={
!verifiableCredential
? Theme.Styles.loadingContainer
: Theme.Styles.closeDetails
}>
<Image
source={
!verifiableCredential
? Theme.ProfileIcon
: { uri: context.credential.biometrics.face }
}
style={Theme.Styles.closeCardImage}
/>
<Image
source={
!verifiableCredential
? Theme.ProfileIcon
: { uri: context.credential.biometrics.face }
}
style={Theme.Styles.closeCardImage}
/>
<Column margin="0 0 0 25" style={{ alignItems: 'flex-start' }}>
{getDetails(t('fullName'), fullName, verifiableCredential)}
{!verifiableCredential
? getDetails(t('id'), uin || vid, verifiableCredential)
: null}
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
{getDetails(t('status'), t('valid'), verifiableCredential)}
<Column margin="0 0 0 25" style={{ alignItems: 'flex-start' }}>
{getDetails(t('fullName'), fullName, verifiableCredential)}
{!verifiableCredential
? getDetails(t('id'), uin || vid, verifiableCredential)
: null}
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
{getDetails(
t('generatedOn'),
generatedOn,
verifiableCredential
)}
{getDetails(t('status'), t('valid'), verifiableCredential)}
</Column>
</Column>
</Column>
{!verifiableCredential && (
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
<VcItemTags tag={tag} />
</ImageBackground>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<Row>
{emptyWalletBindingId ? (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
{verifiableCredential && <WalletUnverified />}
<Text
color={Theme.Colors.Details}
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
{!verifiableCredential && (
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
<VcItemTags tag={tag} />
</ImageBackground>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<Row>
{emptyWalletBindingId ? (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
{verifiableCredential && <WalletUnverified />}
<Text
color={Theme.Colors.Details}
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
<Pressable
onPress={() =>
verifiableCredential ? props.onPress(service) : null
}>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
</Row>
) : (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
<WalletVerified />
<Text
color={Theme.Colors.statusLabel}
weight="semibold"
size="smaller"
margin="10 10 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('profileAuthenticated')}></Text>
</Row>
{props.showOnlyBindedVc ? null : (
<Pressable onPress={() => props.onPress(service)}>
<Pressable
onPress={() =>
verifiableCredential ? props.onPress(service) : null
}>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
)}
</Row>
)}
</Row>
)}
</Pressable>
</Row>
) : (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
<WalletVerified />
<Text
color={Theme.Colors.statusLabel}
weight="semibold"
size="smaller"
margin="10 10 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('profileAuthenticated')}></Text>
</Row>
{props.showOnlyBindedVc ? null : (
<Pressable onPress={() => props.onPress(service)}>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
)}
</Row>
)}
</Row>
)}
</Pressable>
<MessageOverlay
isVisible={isSavingFailedInIdle}
title={commonTranslate(storeErrorTranslationPath + '.title', {
vcLabelSingular: vcLabel.singular,
vcLabelPlural: vcLabel.plural,
})}
message={commonTranslate(storeErrorTranslationPath + '.message', {
vcLabelSingular: vcLabel.singular,
vcLabelPlural: vcLabel.plural,
})}
onBackdropPress={DISMISS}
/>
</React.Fragment>
);
};

View File

@@ -391,7 +391,15 @@
"allowAccess": "Allow access to the camera"
},
"errors": {
"genericError": "Something went wrong. Please try again after some time!"
"genericError": "Something went wrong. Please try again after some time!",
"savingFailed": {
"title": "Failed to save the {{vcLabelSingular}}",
"message": "Something went wrong while saving {{vcLabelSingular}} to the store."
},
"diskFullError": {
"title": "Failed to save the {{vcLabelSingular}}",
"message": "No more {{vcLabelPlural}} can be received or saved as App Data is full."
}
}
}
}
}

View File

@@ -22,6 +22,7 @@ const model = createModel(
APPEND: (key: string, value: unknown) => ({ key, value }),
PREPEND: (key: string, value: unknown) => ({ key, value }),
REMOVE: (key: string, value: string) => ({ key, value }),
REMOVE_VC_METADATA: (key: string, value: string) => ({ key, value }),
REMOVE_ITEMS: (key: string, values: string[]) => ({ key, values }),
CLEAR: () => ({}),
ERROR: (error: Error) => ({ error }),
@@ -119,6 +120,9 @@ export const storeMachine =
REMOVE: {
actions: 'forwardStoreRequest',
},
REMOVE_VC_METADATA: {
actions: 'forwardStoreRequest',
},
REMOVE_ITEMS: {
actions: 'forwardStoreRequest',
},
@@ -137,12 +141,12 @@ export const storeMachine =
],
},
STORE_ERROR: {
actions: send(
(_, event) => model.events.STORE_ERROR(event.error),
{
actions: [
send((_, event) => model.events.STORE_ERROR(event.error), {
to: (_, event) => event.requester,
}
),
}),
sendUpdate(),
],
},
},
},
@@ -214,6 +218,15 @@ export const storeMachine =
response = event.value;
break;
}
case 'REMOVE_VC_METADATA': {
await removeVCMetaData(
event.key,
event.value,
context.encryptionKey
);
response = event.value;
break;
}
case 'REMOVE_ITEMS': {
await removeItems(
event.key,
@@ -365,6 +378,26 @@ export async function removeItem(
}
}
export async function removeVCMetaData(
key: string,
value: string,
encryptionKey: string
) {
try {
const data = await Storage.getItem(key);
const decryptedData = decryptJson(encryptionKey, data);
const list = JSON.parse(decryptedData);
const newList = list.filter((vc: string) => {
return !vc.includes(value);
});
await setItem(key, newList, encryptionKey);
} catch (e) {
console.error('error remove VC metadata:', e);
throw e;
}
}
export async function removeItems(
key: string,
values: string[],

View File

@@ -1,5 +1,66 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'done.invoke._store': {
type: 'done.invoke._store';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.store.resettingStorage:invocation[0]': {
type: 'done.invoke.store.resettingStorage:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform._store': { type: 'error.platform._store'; data: unknown };
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
clear: 'done.invoke.store.resettingStorage:invocation[0]';
generateEncryptionKey: 'done.invoke.store.generatingEncryptionKey:invocation[0]';
getEncryptionKey: 'done.invoke.store.gettingEncryptionKey:invocation[0]';
store: 'done.invoke._store';
};
'missingImplementations': {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
forwardStoreRequest:
| 'APPEND'
| 'CLEAR'
| 'GET'
| 'PREPEND'
| 'REMOVE'
| 'REMOVE_ITEMS'
| 'REMOVE_VC_METADATA'
| 'SET';
notifyParent:
| 'KEY_RECEIVED'
| 'done.invoke.store.resettingStorage:invocation[0]';
setEncryptionKey: 'KEY_RECEIVED';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {};
'eventsCausingServices': {
clear: 'KEY_RECEIVED';
generateEncryptionKey: 'ERROR';
getEncryptionKey: 'xstate.init';
store: 'KEY_RECEIVED' | 'done.invoke.store.resettingStorage:invocation[0]';
};
'matchesStates':
| 'generatingEncryptionKey'
| 'gettingEncryptionKey'
| 'ready'
| 'resettingStorage';
'tags': never;
}
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {

View File

@@ -26,6 +26,7 @@ const model = createModel(
STORE_RESPONSE: (response: unknown) => ({ response }),
STORE_ERROR: (error: Error) => ({ error }),
VC_ADDED: (vcKey: string) => ({ vcKey }),
REMOVE_VC_FROM_CONTEXT: (vcKey: string) => ({ vcKey }),
VC_RECEIVED: (vcKey: string) => ({ vcKey }),
VC_DOWNLOADED: (vc: VC) => ({ vc }),
REFRESH_MY_VCS: () => ({}),
@@ -133,6 +134,9 @@ export const vcMachine =
VC_ADDED: {
actions: 'prependToMyVcs',
},
REMOVE_VC_FROM_CONTEXT: {
actions: 'removeVcFromMyVcs',
},
VC_DOWNLOADED: {
actions: 'setDownloadedVc',
},
@@ -185,6 +189,11 @@ export const vcMachine =
myVcs: (context, event) => [event.vcKey, ...context.myVcs],
}),
removeVcFromMyVcs: model.assign({
myVcs: (context, event) =>
context.myVcs.filter((vc: string) => !vc.includes(event.vcKey)),
}),
prependToReceivedVcs: model.assign({
receivedVcs: (context, event) => [
event.vcKey,

View File

@@ -53,6 +53,7 @@ const model = createModel(
walletBindingError: '',
publicKey: '',
privateKey: '',
storeError: null as Error,
},
{
events: {
@@ -75,6 +76,7 @@ const model = createModel(
ADD_WALLET_BINDING_ID: () => ({}),
CANCEL: () => ({}),
CONFIRM: () => ({}),
STORE_ERROR: (error: Error) => ({ error }),
},
}
);
@@ -183,14 +185,29 @@ export const vcItemMachine =
},
],
CREDENTIAL_DOWNLOADED: {
actions: [
'setCredential',
'storeContext',
'updateVc',
'logDownloaded',
],
actions: ['setCredential', 'storeContext'],
},
STORE_RESPONSE: {
actions: ['updateVc', 'logDownloaded'],
target: '#vc-item.checkingVerificationStatus',
},
STORE_ERROR: {
target: '#vc-item.checkingServerData.savingFailed',
},
},
},
savingFailed: {
entry: ['setStoreError', 'removeVcMetaDataFromStorage'],
initial: 'idle',
states: {
idle: {},
viewingVc: {},
},
on: {
DISMISS: {
actions: ['removeVcMetaDataFromVcMachine'],
target: '.viewingVc',
},
},
},
},
@@ -501,6 +518,36 @@ export const vcItemMachine =
},
{
actions: {
setStoreError: assign({
storeError: (_context, event) => event.error,
}),
removeVcMetaDataFromStorage: send(
(context) => {
const { serviceRefs, ...data } = context;
return StoreEvents.REMOVE_VC_METADATA(
MY_VCS_STORE_KEY,
VC_ITEM_STORE_KEY(context)
);
},
{
to: (context) => context.serviceRefs.store,
}
),
removeVcMetaDataFromVcMachine: send(
(context, _event) => {
const { serviceRefs, ...data } = context;
return {
type: 'REMOVE_VC_FROM_CONTEXT',
vcKey: VC_ITEM_STORE_KEY(context),
};
},
{
to: (context) => context.serviceRefs.vc,
}
),
setWalletBindingError: assign({
walletBindingError: (context, event) =>
i18n.t(`errors.genericError`, {
@@ -618,14 +665,16 @@ export const vcItemMachine =
}),
logDownloaded: send(
(_, event) =>
ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VC_ITEM_STORE_KEY(event.vc),
(context) => {
const { serviceRefs, ...data } = context;
return ActivityLogEvents.LOG_ACTIVITY({
_vcKey: VC_ITEM_STORE_KEY(data),
type: 'VC_DOWNLOADED',
timestamp: Date.now(),
deviceName: '',
vcLabel: event.vc.tag || event.vc.id,
}),
vcLabel: data.tag || data.id,
});
},
{
to: (context) => context.serviceRefs.activityLog,
}
@@ -1083,3 +1132,11 @@ export function isWalletBindingInProgress(state: State) {
export function isShowingBindingWarning(state: State) {
return state.matches('showBindingWarning');
}
export function selectStoreError(state: State) {
return state.context.storeError;
}
export function selectIsSavingFailedInIdle(state: State) {
return state.matches('checkingServerData.savingFailed.idle');
}

View File

@@ -75,6 +75,10 @@ export interface Typegen0 {
type: 'error.platform.vc-item.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]': {
type: 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
data: unknown;
};
'error.platform.vc-item.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item.requestingBindingOtp:invocation[0]';
data: unknown;
@@ -141,6 +145,12 @@ export interface Typegen0 {
incrementDownloadCounter: 'POLL';
logDownloaded: 'CREDENTIAL_DOWNLOADED';
logRevoked: 'STORE_RESPONSE';
logWalletBindingFailure:
| 'error.platform.vc-item.addKeyPair:invocation[0]'
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
logWalletBindingSuccess: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'xstate.init';
@@ -149,6 +159,7 @@ export interface Typegen0 {
| 'CREDENTIAL_DOWNLOADED'
| 'GET_VC_RESPONSE'
| 'STORE_RESPONSE';
setDownloadInterval: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
setMaxDownloadCount: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setOtp: 'INPUT_OTP';
@@ -159,6 +170,7 @@ export interface Typegen0 {
setPublicKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setTag: 'SAVE_TAG';
setThumbprintForWalletBindingId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
setTransactionId:
| 'INPUT_OTP'
| 'REVOKE_VC'
@@ -196,7 +208,9 @@ export interface Typegen0 {
'eventsCausingServices': {
addWalletBindnigId: 'done.invoke.vc-item.addKeyPair:invocation[0]';
checkDownloadExpiryLimit: 'STORE_RESPONSE';
checkStatus: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
checkStatus:
| 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]'
| 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
downloadCredential: 'DOWNLOAD_READY';
generateKeyPair: 'INPUT_OTP';
requestBindingOtp: 'CONFIRM';

View File

@@ -12,10 +12,19 @@ import { OnboardingOverlay } from './OnboardingOverlay';
import { useTranslation } from 'react-i18next';
import { VcItem } from '../../components/VcItem';
import { GET_INDIVIDUAL_ID } from '../../shared/constants';
import { MessageOverlay } from '../../components/MessageOverlay';
export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
const { t } = useTranslation('MyVcsTab');
const { t: commonTranslate } = useTranslation('common');
const controller = useMyVcsTab(props);
let storeErrorTranslationPath = 'errors.savingFailed';
const isDiskFullError =
controller.storeError?.message?.match('No space left on device') != null;
if (isDiskFullError) {
storeErrorTranslationPath = 'errors.diskFullError';
}
const getId = () => {
controller.DISMISS();
@@ -114,6 +123,18 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
onDone={controller.ONBOARDING_DONE}
onAddVc={controller.ADD_VC}
/>
<MessageOverlay
isVisible={controller.isSavingFailedInIdle}
title={commonTranslate(storeErrorTranslationPath + '.title', {
vcLabelSingular: controller.vcLabel.singular,
vcLabelPlural: controller.vcLabel.plural,
})}
message={commonTranslate(storeErrorTranslationPath + '.message', {
vcLabelSingular: controller.vcLabel.singular,
vcLabelPlural: controller.vcLabel.plural,
})}
onBackdropPress={controller.DISMISS}
/>
</React.Fragment>
);
};

View File

@@ -17,6 +17,8 @@ import {
selectIsOnboarding,
selectIsRequestSuccessful,
selectGetVcModal,
selectStoreError,
selectIsSavingFailedInIdle,
} from './MyVcsTabMachine';
export function useMyVcsTab(props: HomeScreenTabProps) {
@@ -36,6 +38,8 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
isRequestSuccessful: useSelector(service, selectIsRequestSuccessful),
isOnboarding: useSelector(service, selectIsOnboarding),
storeError: useSelector(service, selectStoreError),
isSavingFailedInIdle: useSelector(service, selectIsSavingFailedInIdle),
DISMISS: () => service.send(MyVcsTabEvents.DISMISS()),

View File

@@ -21,6 +21,7 @@ import { GetVcModalMachine } from './MyVcs/GetVcModalMachine';
const model = createModel(
{
serviceRefs: {} as AppServices,
storeError: null as Error,
},
{
events: {
@@ -30,6 +31,7 @@ const model = createModel(
}),
DISMISS: () => ({}),
STORE_RESPONSE: (response?: unknown) => ({ response }),
STORE_ERROR: (error: Error) => ({ error }),
ADD_VC: () => ({}),
GET_VC: () => ({}),
ONBOARDING_DONE: () => ({}),
@@ -43,6 +45,7 @@ type ViewVcEvent = EventFrom<typeof model, 'VIEW_VC'>;
export const MyVcsTabMachine = model.createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkCeA1AxrAKgQwCMA6TACzEwGsBLAOygHlaCB7PAJwjqgGUAXPHwCusAMQ8cDAEoBRAPqyeABQYA5HjIDaABgC6iUAAcWsan2otaBkAA9EANgBMAGhCpEAFidEA7PY+O2j4ArPbajo4AjPYAvjGuaFi4hCTkVNxMrBxc9PyCIuKSsgoyymoampH6SCDGpuaW1nYIjgCcABxEwa0AzK32PU492l6tru4Ike2OXT2Rkdrac5GOPq3BcQkY2PjEllmc3KIAggAip3LoAMI61UYmZhZWNc2RrTPTSyPtPtphwR4fONEO12toiNEPO8PD0PFMej52psQIkdil9mxDvRRGoAEIMY5SU4ASVUAHE5Kc1Fo9NY6o9Gi9EG8PoFhh4fn9tACgW5EMFpl1uYEFt1Wj4QsjUcliNQIAAbMAnc6XG60mr0hrPUDNLw+LprAJvcVBJbAhA+Rz2Ig9EXrH5vdrvKXbGVEOWK0ToYkyADqqtudIeWqa-JcfJakT6vh80UR-g50R6LqSu3dCqVZJkOAD6vu9SeoZaayIgN67WCCO6flC5ui-SIf3ssfmXiWPhTaOIADdqGAAO7cLCiEk8ZDEng8QMa4OFpkIJzmwJOoiOJ32VqQysjWLxFGutN4CA5KDDiCWMDu2jdliUS-HY9YZAsCB4eXT-MM7W2fnhA2tI1WhNX4enNDpWlXADNz6ewBXsKNOzdI8T2HUdx0nD9alnRkdWZKNOnaQEKy8SIAmCRE60BSJfA3S1elhYIo13LZUxSZCh0wIhYD4Fh2COCRpHkRQVHUGk7iwgscJ-C1ohtDw23k94phCSigIhCVY2CcJIj8dZEMPY8OK4ni+OxATihkKQpGkTDNTnXCZOtWEFKhKIfmCOtBmojc2gWdp7HaW0Oz3aUDJQzjkKwHghEwTA4FgAAzIR5RHCd0KnPMJK-It5h6AiiIBeCyIoiNImCCJV0Y2MBlhcJK30lIYD4cx6DPC8rxvO8iDJMA+CfF831s7Dv2aVYILLPLKxCcVYPsMC-F8KCo36OCEJCg9Gt6lrT0wVKxwnDLxLsqTRpLCaKyrGba1K9pZLBCUdw8cjejiPdaBfOBrFCwgg0kkbEAAWkcDxzXIxbm2CSHuRg+r1tY4gyAoGh6EyTETzyYR4BnP6iwCSjglXH5-IGFZ7AGJYGr2Zg0e4X7svncjzSdfU5jBNcOVu7oPEp9NFTpkN5yjJYDVgwL-McdleQmKY-2B4Gyf8ALhg2OGuyIXsBw4-n7OkqZrUtH42mBsEvFBJcFh6G14I08V1h6ZNVaQwzWswbWTs8AF-0A4CzQjYiiE+KIVkBWDIh59iXaIfs8EeegADFeO7TAAGkwAmT8BYcrSZme73fhAusdxjMnAvKvw1yRR2wqM7jeNp7H6aznSvaiID8996WEWtbQyrWN5lnKlWWLViOdqISLMGi2L4qS+U3f+hBN31KZIbmeCnAWMZSvKmYfC8XzAqepjw+dsfYDwXt45jxUIHnosnQglfK2iaIom0LfpYr1cNy01ovh0gKJ9wpcQvtwOO19IC8zAHfec-ROiWjhN0YGQQIhS2ZFWGifxHQInkvbIBNdQFX2oDfdWfZBwuxgQ5ACy8KzP3Xm-D+zIYQeEqgxAUPRqwVh5k1baWBKHSWBnWf4NoJagn8mVTm3CtpGWjrHKACd2BJ1TunLKmdpKWjrD4aMawKzQVqo4WGcQgA */
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./MyVcsTabMachine.typegen').Typegen0,
@@ -107,6 +110,22 @@ export const MyVcsTabMachine = model.createMachine(
target: 'addVcSuccessful',
actions: ['sendVcAdded'],
},
STORE_ERROR: {
actions: 'setStoreError',
target: '#MyVcsTab.addingVc.savingFailed',
},
},
},
savingFailed: {
initial: 'idle',
states: {
idle: {},
viewingVc: {},
},
on: {
DISMISS: {
target: '.viewingVc',
},
},
},
addVcSuccessful: {
@@ -164,6 +183,10 @@ export const MyVcsTabMachine = model.createMachine(
to: (context) => context.serviceRefs.vc,
}
),
setStoreError: model.assign({
storeError: (_context, event) => event.error,
}),
},
guards: {
@@ -198,3 +221,11 @@ export function selectIsOnboarding(state: State) {
export function selectIsRequestSuccessful(state: State) {
return state.matches('addingVc.addVcSuccessful');
}
export function selectStoreError(state: State) {
return state.context.storeError;
}
export function selectIsSavingFailedInIdle(state: State) {
return state.matches('addingVc.savingFailed.idle');
}

View File

@@ -18,25 +18,32 @@ export interface Typegen0 {
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
completeOnboarding: 'ADD_VC' | 'ONBOARDING_DONE';
getOnboardingStatus: 'xstate.init';
sendVcAdded: 'STORE_RESPONSE';
setStoreError: 'STORE_ERROR';
storeVcItem: 'done.invoke.AddVcModal';
viewVcFromParent: 'VIEW_VC';
};
'eventsCausingServices': {};
'eventsCausingDelays': {};
'eventsCausingGuards': {
isOnboardingDone: 'STORE_RESPONSE';
};
'eventsCausingDelays': {};
'eventsCausingServices': {
AddVcModal: 'ADD_VC' | 'done.invoke.GetVcModal';
GetVcModal: 'GET_VC';
};
'matchesStates':
| 'addingVc'
| 'addingVc.addVcSuccessful'
| 'addingVc.savingFailed'
| 'addingVc.savingFailed.idle'
| 'addingVc.savingFailed.viewingVc'
| 'addingVc.storing'
| 'addingVc.waitingForvcKey'
| 'checkingOnboardingStatus'
@@ -46,7 +53,12 @@ export interface Typegen0 {
| 'onboarding'
| 'viewingVc'
| {
addingVc?: 'addVcSuccessful' | 'storing' | 'waitingForvcKey';
addingVc?:
| 'addVcSuccessful'
| 'savingFailed'
| 'storing'
| 'waitingForvcKey'
| { savingFailed?: 'idle' | 'viewingVc' };
gettingVc?: 'waitingForvcKey';
};
'tags': never;

View File

@@ -1,13 +1,13 @@
import { MMKVLoader } from 'react-native-mmkv-storage';
import { VC_ITEM_STORE_KEY_REGEX } from './constants';
import {
DocumentDirectoryPath,
exists,
mkdir,
readFile,
unlink,
writeFile,
exists,
} from 'react-native-fs';
import { MMKVLoader } from 'react-native-mmkv-storage';
import { VC_ITEM_STORE_KEY_REGEX } from './constants';
const MMKV = new MMKVLoader().initialize();
const vcKeyRegExp = new RegExp(VC_ITEM_STORE_KEY_REGEX);
@@ -23,12 +23,17 @@ class Storage {
};
static setItem = async (key: string, data: string) => {
if (vcKeyRegExp.exec(key)) {
await mkdir(vcDirectoryPath);
const path = getFilePath(key);
return await writeFile(path, data, 'utf8');
try {
if (vcKeyRegExp.exec(key)) {
await mkdir(vcDirectoryPath);
const path = getFilePath(key);
return await writeFile(path, data, 'utf8');
}
await MMKV.setItem(key, data);
} catch (error) {
console.log('Error Occurred while saving in Storage.', error);
throw error;
}
await MMKV.setItem(key, data);
};
static clear = async () => {