mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 05:27:57 -05:00
[INJIMOB-3581] add revocation and reverification logic (#2117)
* [INJIMOB-3581] add revocation and reverification logic Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com> * [INJIMOB-3581] refactor readable array conversion to a shared utility Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com> --------- Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ export type ActivityLogType =
|
||||
| 'WALLET_BINDING_SUCCESSFULL'
|
||||
| 'WALLET_BINDING_FAILURE'
|
||||
| 'VC_REMOVED'
|
||||
| 'VC_STATUS_CHANGED'
|
||||
| 'TAMPERED_VC_REMOVED';
|
||||
|
||||
export interface ActivityLog {
|
||||
@@ -35,6 +36,7 @@ export class VCActivityLog implements ActivityLog {
|
||||
type: ActivityLogType;
|
||||
issuer: string;
|
||||
flow: string;
|
||||
vcStatus?: string;
|
||||
|
||||
constructor({
|
||||
id = '',
|
||||
@@ -46,6 +48,7 @@ export class VCActivityLog implements ActivityLog {
|
||||
issuer = '',
|
||||
credentialConfigurationId = '',
|
||||
flow = VCItemContainerFlowType.VC_SHARE,
|
||||
vcStatus = '',
|
||||
} = {}) {
|
||||
this.id = id;
|
||||
this.idType = idType;
|
||||
@@ -56,17 +59,23 @@ export class VCActivityLog implements ActivityLog {
|
||||
this.issuer = issuer;
|
||||
this.credentialConfigurationId = credentialConfigurationId;
|
||||
this.flow = flow;
|
||||
this.vcStatus = vcStatus;
|
||||
}
|
||||
|
||||
getActionText(t: TFunction, wellknown: Object | undefined) {
|
||||
const formattedVcStatus = this.vcStatus ? `.${this.vcStatus}` : '';
|
||||
if (!!this.credentialConfigurationId && wellknown) {
|
||||
const cardType = getCredentialTypeFromWellKnown(
|
||||
wellknown,
|
||||
this.credentialConfigurationId,
|
||||
);
|
||||
return `${t(this.type, {idType: cardType})}`;
|
||||
return `${t(this.type + formattedVcStatus, {
|
||||
idType: cardType,
|
||||
vcStatus: this.vcStatus,
|
||||
})}`;
|
||||
}
|
||||
return `${t(this.type, {idType: ''})}`;
|
||||
|
||||
return `${t(this.type + formattedVcStatus, {idType: '', vcStatus: this.vcStatus})}`;
|
||||
}
|
||||
|
||||
static getLogFromObject(data: Object): VCActivityLog {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {useSettingsScreen} from '../screens/Settings/SettingScreenController';
|
||||
export const BannerNotificationContainer: React.FC<
|
||||
BannerNotificationContainerProps
|
||||
> = props => {
|
||||
const {showVerificationStatusBanner = true} = props;
|
||||
const { showVerificationStatusBanner = true } = props;
|
||||
const scanScreenController = useScanScreen();
|
||||
const settingsScreenController = useSettingsScreen(props);
|
||||
const showQuickShareSuccessBanner =
|
||||
@@ -23,9 +23,11 @@ export const BannerNotificationContainer: React.FC<
|
||||
|
||||
const bannerNotificationController = UseBannerNotification();
|
||||
const WalletBindingSuccess = bannerNotificationController.isBindingSuccess;
|
||||
const {t} = useTranslation('BannerNotification');
|
||||
const reverificationSuccessObject = bannerNotificationController.isReverificationSuccess;
|
||||
const reverificationFailureObject = bannerNotificationController.isReverificationFailed;
|
||||
const { t } = useTranslation('BannerNotification');
|
||||
const rt = useTranslation('RequestScreen').t;
|
||||
const verificationStatus = bannerNotificationController.verificationStatus;
|
||||
const verificationStatus = bannerNotificationController.verificationStatus || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -69,6 +71,20 @@ export const BannerNotificationContainer: React.FC<
|
||||
</View>
|
||||
)}
|
||||
|
||||
{reverificationSuccessObject.status && (
|
||||
<View style={Theme.BannerStyles.topBanner}>
|
||||
<BannerNotification
|
||||
type={BannerStatusType.SUCCESS}
|
||||
message={t(`reverifiedSuccessfully.${reverificationSuccessObject.statusValue}`, { vcType: reverificationSuccessObject.vcType })}
|
||||
onClosePress={
|
||||
bannerNotificationController.RESET_REVIRIFICATION_SUCCESS
|
||||
}
|
||||
key={'reverifiedSuccessfullyPopup'}
|
||||
testId={'reverifiedSuccessfullyPopup'}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{showQuickShareSuccessBanner && (
|
||||
<View style={Theme.BannerStyles.topBanner}>
|
||||
<BannerNotification
|
||||
@@ -101,18 +117,6 @@ export const BannerNotificationContainer: React.FC<
|
||||
/>
|
||||
)}
|
||||
|
||||
{verificationStatus !== null && showVerificationStatusBanner && (
|
||||
<BannerNotification
|
||||
type={verificationStatus.statusType}
|
||||
message={t(`VcVerificationBanner:${verificationStatus?.statusType}`, {
|
||||
vcDetails: `${verificationStatus.vcType} ${verificationStatus.vcNumber}`,
|
||||
})}
|
||||
onClosePress={bannerNotificationController.RESET_VERIFICATION_STATUS}
|
||||
key={'reVerificationInProgress'}
|
||||
testId={'reVerificationInProgress'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{bannerNotificationController.isDownloadingFailed && (
|
||||
<BannerNotification
|
||||
type={BannerStatusType.ERROR}
|
||||
@@ -122,6 +126,16 @@ export const BannerNotificationContainer: React.FC<
|
||||
testId={'downloadingVcFailedPopup'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{reverificationFailureObject.status && (
|
||||
<BannerNotification
|
||||
type={BannerStatusType.ERROR}
|
||||
message={t(`reverificationFailed.${reverificationFailureObject.statusValue}`, { vcType: reverificationFailureObject.vcType })}
|
||||
onClosePress={bannerNotificationController.RESET_REVERIFICATION_FAILURE}
|
||||
key={'reverificationFailedPopup'}
|
||||
testId={'reverificationFailedPopup'}
|
||||
/>
|
||||
)}
|
||||
{bannerNotificationController.isDownloadingSuccess && (
|
||||
<BannerNotification
|
||||
type={BannerStatusType.SUCCESS}
|
||||
@@ -137,6 +151,8 @@ export const BannerNotificationContainer: React.FC<
|
||||
|
||||
export type vcVerificationBannerDetails = {
|
||||
statusType: BannerStatus;
|
||||
isRevoked: boolean;
|
||||
isExpired: boolean;
|
||||
vcType: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import {VcMetaEvents} from '../machines/VerifiableCredential/VCMetaMachine/VCMet
|
||||
import {
|
||||
selectIsDownloadingFailed,
|
||||
selectIsDownloadingSuccess,
|
||||
selectIsReverificationFailure,
|
||||
selectIsReverificationSuccess,
|
||||
selectWalletBindingSuccess,
|
||||
} from '../machines/VerifiableCredential/VCMetaMachine/VCMetaSelectors';
|
||||
import {selectVerificationStatus} from '../machines/VerifiableCredential/VCItemMachine/VCItemSelectors';
|
||||
@@ -27,6 +29,8 @@ export const UseBannerNotification = () => {
|
||||
isBiometricUnlock: useSelector(settingsService, selectIsBiometricUnlock),
|
||||
isDownloadingSuccess: useSelector(vcMetaService, selectIsDownloadingSuccess),
|
||||
isDownloadingFailed: useSelector(vcMetaService, selectIsDownloadingFailed),
|
||||
isReverificationSuccess: useSelector(vcMetaService,selectIsReverificationSuccess),
|
||||
isReverificationFailed: useSelector(vcMetaService, selectIsReverificationFailure),
|
||||
DISMISS: () => {
|
||||
settingsService.send(SettingsEvents.DISMISS());
|
||||
},
|
||||
@@ -40,5 +44,11 @@ export const UseBannerNotification = () => {
|
||||
RESET_DOWNLOADING_SUCCESS: () => {
|
||||
vcMetaService.send(VcMetaEvents.RESET_DOWNLOADING_SUCCESS());
|
||||
},
|
||||
RESET_REVIRIFICATION_SUCCESS: () => {
|
||||
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_SUCCESS());
|
||||
},
|
||||
RESET_REVERIFICATION_FAILURE: () => {
|
||||
vcMetaService.send(VcMetaEvents.RESET_REVERIFY_VC_FAILED());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import {Icon, ListItem, Overlay} from 'react-native-elements';
|
||||
import {Theme} from '../components/ui/styleUtils';
|
||||
import {Column, Row, Text} from '../components/ui';
|
||||
import {View} from 'react-native';
|
||||
import {useKebabPopUp} from './KebabPopUpController';
|
||||
import {ActorRefFrom} from 'xstate';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
import {VCMetadata} from '../shared/VCMetadata';
|
||||
import { Icon, ListItem, Overlay } from 'react-native-elements';
|
||||
import { Theme } from '../components/ui/styleUtils';
|
||||
import { Column, Row, Text } from '../components/ui';
|
||||
import { View } from 'react-native';
|
||||
import { useKebabPopUp } from './KebabPopUpController';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import { VCMetadata } from '../shared/VCMetadata';
|
||||
import testIDProps from '../shared/commonUtil';
|
||||
import {getKebabMenuOptions} from './kebabMenuUtils';
|
||||
import {VCItemMachine} from '../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
|
||||
import { getKebabMenuOptions } from './kebabMenuUtils';
|
||||
import { VCItemMachine } from '../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
|
||||
|
||||
export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
|
||||
const controller = useKebabPopUp(props);
|
||||
const {t} = useTranslation('HomeScreenKebabPopUp');
|
||||
const { t } = useTranslation('HomeScreenKebabPopUp');
|
||||
|
||||
return (
|
||||
<Column>
|
||||
@@ -52,14 +52,14 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
|
||||
|
||||
<FlatList
|
||||
data={getKebabMenuOptions(props)}
|
||||
renderItem={({item}) => (
|
||||
renderItem={({ item }) => (
|
||||
<ListItem topDivider onPress={item.onPress}>
|
||||
<Row crossAlign="center" style={{flex: 1}}>
|
||||
<View style={{width: 25, alignItems: 'center'}}>
|
||||
<Row crossAlign="center" style={{ flex: 1 }}>
|
||||
<View style={{ width: 25, alignItems: 'center' }}>
|
||||
{item.icon}
|
||||
</View>
|
||||
<Text
|
||||
style={{fontFamily: 'Inter_600SemiBold'}}
|
||||
style={{ fontFamily: 'Inter_600SemiBold' }}
|
||||
color={
|
||||
item.testID === 'removeFromWallet'
|
||||
? Theme.Colors.warningText
|
||||
@@ -69,6 +69,7 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
|
||||
margin="0 0 0 10">
|
||||
{item.label}
|
||||
</Text>
|
||||
{item.label === t('reverify') && (<View style={Theme.KebabPopUpStyles.new}><Text color='white' weight='bold' style={{ fontSize: 10 }}>{t('new')}</Text></View>)}
|
||||
</Row>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
@@ -71,6 +71,7 @@ export function useKebabPopUp(props) {
|
||||
DISMISS: () => service.send(VCItemEvents.DISMISS()),
|
||||
CANCEL: () => service.send(VCItemEvents.CANCEL()),
|
||||
SHOW_ACTIVITY: () => service.send(VCItemEvents.SHOW_ACTIVITY()),
|
||||
REVERIFY_VC: () => service.send(VCItemEvents.REVERIFY_VC()),
|
||||
INPUT_OTP: (otp: string) => service.send(VCItemEvents.INPUT_OTP(otp)),
|
||||
RESEND_OTP: () => service.send(VCItemEvents.RESEND_OTP()),
|
||||
GOTO_SCANSCREEN: () => {
|
||||
|
||||
@@ -1,73 +1,123 @@
|
||||
import React, {ReactElement} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
import {Dimensions, StyleSheet} from 'react-native';
|
||||
import {LinearProgress, Overlay} from 'react-native-elements';
|
||||
import {Button, Column, Text} from './ui';
|
||||
import {Theme} from './ui/styleUtils';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import { LinearProgress, Overlay } from 'react-native-elements';
|
||||
import { Button, Column, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
import Svg, { Mask, Rect } from 'react-native-svg';
|
||||
|
||||
export const MessageOverlay: React.FC<MessageOverlayProps> = props => {
|
||||
const {t} = useTranslation('common');
|
||||
const { t } = useTranslation('common');
|
||||
const style = StyleSheet.create({
|
||||
customHeight: {
|
||||
minHeight: props.minHeight ? props.minHeight : props.progress ? 100 : 170,
|
||||
},
|
||||
});
|
||||
|
||||
const isHighlightMode = props.overlayMode === 'highlight';
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
overlayStyle={Theme.MessageOverlayStyles.overlay}
|
||||
fullScreen={isHighlightMode}
|
||||
backdropStyle={{ backgroundColor: isHighlightMode ? 'transparent' : 'rgba(0,0,0,0.6)' }}
|
||||
overlayStyle={
|
||||
isHighlightMode
|
||||
? { backgroundColor: 'transparent', padding: 0, margin: 0 }
|
||||
: Theme.MessageOverlayStyles.overlay
|
||||
}
|
||||
onShow={props.onShow}
|
||||
onBackdropPress={props.onBackdropPress}>
|
||||
<Column
|
||||
testID={props.testID}
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={[Theme.MessageOverlayStyles.popupOverLay, style.customHeight]}>
|
||||
<Column padding="21" crossAlign="center">
|
||||
{props.title && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Title'}
|
||||
style={{paddingTop: 3}}
|
||||
align="center"
|
||||
weight="bold"
|
||||
margin="0 0 10 0"
|
||||
color={Theme.Colors.Details}>
|
||||
{props.title}
|
||||
</Text>
|
||||
{isHighlightMode ? (
|
||||
<View
|
||||
style={{ flex: 1 }}
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderRelease={props.onBackdropPress}
|
||||
>
|
||||
{props.cardLayout && (
|
||||
<>
|
||||
<Svg
|
||||
style={StyleSheet.absoluteFill}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<Mask id="hole">
|
||||
<Rect width="100%" height="100%" fill="white" />
|
||||
<Rect
|
||||
x={props.cardLayout.x - 10}
|
||||
y={props.cardLayout.y - 10}
|
||||
width={props.cardLayout.width + 20}
|
||||
height={props.cardLayout.height + 20}
|
||||
rx={12}
|
||||
fill="black"
|
||||
/>
|
||||
</Mask>
|
||||
<Rect
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="rgba(0,0,0,0.65)"
|
||||
mask="url(#hole)"
|
||||
/>
|
||||
</Svg>
|
||||
</>
|
||||
)}
|
||||
{props.message && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Message'}
|
||||
align="center"
|
||||
weight="semibold"
|
||||
size="small"
|
||||
margin="10 0 12 0"
|
||||
color={Theme.Colors.Details}>
|
||||
{props.message}
|
||||
</Text>
|
||||
)}
|
||||
{props.progress && <Progress progress={props.progress} />}
|
||||
{props.hint && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Hint'}
|
||||
size="smaller"
|
||||
color={Theme.Colors.textLabel}
|
||||
margin={[4, 0, 0, 0]}>
|
||||
{props.hint}
|
||||
</Text>
|
||||
)}
|
||||
{props.children}
|
||||
</Column>
|
||||
{!props.children && props.onButtonPress ? (
|
||||
<Button
|
||||
testID="cancel"
|
||||
type="gradient"
|
||||
title={props.buttonText ? t(props.buttonText) : t('cancel')}
|
||||
onPress={props.onButtonPress}
|
||||
styles={Theme.MessageOverlayStyles.button}
|
||||
/>
|
||||
) : null}
|
||||
</Column>
|
||||
</View>
|
||||
)
|
||||
: (
|
||||
(props.title || props.message || props.children) && (
|
||||
<Column
|
||||
testID={props.testID}
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
style={[
|
||||
Theme.MessageOverlayStyles.popupOverLay,
|
||||
style.customHeight,
|
||||
]}>
|
||||
<Column padding="21" crossAlign="center">
|
||||
{props.title && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Title'}
|
||||
style={{ paddingTop: 3 }}
|
||||
align="center"
|
||||
weight="bold"
|
||||
margin="0 0 10 0"
|
||||
color={Theme.Colors.Details}>
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.message && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Message'}
|
||||
align="center"
|
||||
weight="semibold"
|
||||
size="small"
|
||||
margin="10 0 12 0"
|
||||
color={Theme.Colors.Details}>
|
||||
{props.message}
|
||||
</Text>
|
||||
)}
|
||||
{props.progress && <Progress progress={props.progress} />}
|
||||
{props.hint && (
|
||||
<Text
|
||||
testID={props.testID && props.testID + 'Hint'}
|
||||
size="smaller"
|
||||
color={Theme.Colors.textLabel}
|
||||
margin={[4, 0, 0, 0]}>
|
||||
{props.hint}
|
||||
</Text>
|
||||
)}
|
||||
{props.children}
|
||||
</Column>
|
||||
{!props.children && props.onButtonPress ? (
|
||||
<Button
|
||||
testID="cancel"
|
||||
type="gradient"
|
||||
title={props.buttonText ? t(props.buttonText) : t('cancel')}
|
||||
onPress={props.onButtonPress}
|
||||
styles={Theme.MessageOverlayStyles.button}
|
||||
/>
|
||||
) : null}
|
||||
</Column>
|
||||
)
|
||||
)}
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
@@ -79,7 +129,7 @@ export const ErrorMessageOverlay: React.FC<ErrorMessageOverlayProps> = ({
|
||||
onDismiss,
|
||||
translationPath,
|
||||
}) => {
|
||||
const {t} = useTranslation(translationPath);
|
||||
const { t } = useTranslation(translationPath);
|
||||
|
||||
return (
|
||||
<MessageOverlay
|
||||
@@ -100,6 +150,7 @@ export interface ErrorMessageOverlayProps {
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
|
||||
const Progress: React.FC<MessageOverlayProps> = props => {
|
||||
return typeof props.progress === 'boolean' ? (
|
||||
props.progress && (
|
||||
@@ -113,6 +164,7 @@ const Progress: React.FC<MessageOverlayProps> = props => {
|
||||
export interface MessageOverlayProps {
|
||||
testID?: string;
|
||||
isVisible: boolean;
|
||||
overlayMode?: 'popup' | 'highlight';
|
||||
title?: string;
|
||||
buttonText?: string;
|
||||
message?: string;
|
||||
@@ -126,6 +178,13 @@ export interface MessageOverlayProps {
|
||||
onShow?: () => void;
|
||||
minHeight?: number | string | undefined;
|
||||
children?: ReactElement<any, any>;
|
||||
cardLayout?: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
type: 'success' | 'failure';
|
||||
};
|
||||
}
|
||||
|
||||
export interface VCSharingErrorStatusProps {
|
||||
|
||||
@@ -3,14 +3,18 @@ import {View} from 'react-native';
|
||||
import {Icon} from 'react-native-elements';
|
||||
import {Theme} from './ui/styleUtils';
|
||||
|
||||
const PendingIcon: React.FC = () => {
|
||||
interface PendingIconProps {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const PendingIcon: React.FC<PendingIconProps> = (props) => {
|
||||
return (
|
||||
<View style={Theme.Styles.verificationStatusIconContainer}>
|
||||
<View style={Theme.Styles.verificationStatusIconInner}>
|
||||
<Icon
|
||||
name="alert-circle"
|
||||
type="material-community"
|
||||
color={Theme.Colors.PendingIcon}
|
||||
color={props.color}
|
||||
size={12}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
selectWalletBindingResponse,
|
||||
selectVerifiableCredentialData,
|
||||
selectCredential,
|
||||
isReverifyingVc,
|
||||
} from '../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors';
|
||||
import {useInterpret, useSelector} from '@xstate/react';
|
||||
import {
|
||||
@@ -49,6 +50,7 @@ export function useVcItemController(vcMetadata: VCMetadata) {
|
||||
VCItemService,
|
||||
selectIsSavingFailedInIdle,
|
||||
),
|
||||
isReverifyingVc: useSelector(VCItemService, isReverifyingVc),
|
||||
storeErrorTranslationPath: 'errors.savingFailed',
|
||||
generatedOn: useSelector(VCItemService, selectGeneratedOn),
|
||||
isTourGuide: useSelector(authService, selectIsTourGuide),
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {Pressable} from 'react-native';
|
||||
import {ActorRefFrom} from 'xstate';
|
||||
import {ErrorMessageOverlay} from '../../MessageOverlay';
|
||||
import {Theme} from '../../ui/styleUtils';
|
||||
import {VCMetadata} from '../../../shared/VCMetadata';
|
||||
import {format} from 'date-fns';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import { ErrorMessageOverlay, MessageOverlay } from '../../MessageOverlay';
|
||||
import { Theme } from '../../ui/styleUtils';
|
||||
import { VCMetadata } from '../../../shared/VCMetadata';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import {VCCardSkeleton} from '../common/VCCardSkeleton';
|
||||
import {VCCardViewContent} from './VCCardViewContent';
|
||||
import {useVcItemController} from '../VCItemController';
|
||||
import {getCredentialIssuersWellKnownConfig} from '../../../shared/openId4VCI/Utils';
|
||||
import {CARD_VIEW_DEFAULT_FIELDS, isVCLoaded} from '../common/VCUtils';
|
||||
import {VCItemMachine} from '../../../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {Copilot} from '../../ui/Copilot';
|
||||
import {VCProcessor} from '../common/VCProcessor';
|
||||
import { VCCardSkeleton } from '../common/VCCardSkeleton';
|
||||
import { VCCardViewContent } from './VCCardViewContent';
|
||||
import { useVcItemController } from '../VCItemController';
|
||||
import { getCredentialIssuersWellKnownConfig } from '../../../shared/openId4VCI/Utils';
|
||||
import { CARD_VIEW_DEFAULT_FIELDS, isVCLoaded } from '../common/VCUtils';
|
||||
import { VCItemMachine } from '../../../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Copilot } from '../../ui/Copilot';
|
||||
import { VCProcessor } from '../common/VCProcessor';
|
||||
|
||||
export const VCCardView: React.FC<VCItemProps> = ({
|
||||
vcMetadata,
|
||||
@@ -28,15 +28,17 @@ export const VCCardView: React.FC<VCItemProps> = ({
|
||||
isInitialLaunch = false,
|
||||
isTopCard = false,
|
||||
onDisclosuresChange,
|
||||
onMeasured,
|
||||
}) => {
|
||||
const controller = useVcItemController(vcMetadata);
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const cardRef = useRef<View>(null);
|
||||
|
||||
const service = controller.VCItemService;
|
||||
const verifiableCredentialData = controller.verifiableCredentialData;
|
||||
const generatedOn = -controller.generatedOn;
|
||||
|
||||
let formattedDate =
|
||||
const formattedDate =
|
||||
generatedOn && format(new Date(generatedOn), 'MM/dd/yyyy');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,6 +49,21 @@ export const VCCardView: React.FC<VCItemProps> = ({
|
||||
const [wellknown, setWellknown] = useState(null);
|
||||
const [vc, setVc] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (onMeasured && cardRef.current) {
|
||||
const handle = requestAnimationFrame(() => {
|
||||
cardRef.current?.measureInWindow((x, y, width, height) => {
|
||||
if (width > 0 && height > 0) {
|
||||
onMeasured({ x, y, width, height });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return () => cancelAnimationFrame(handle);
|
||||
}
|
||||
}, [onMeasured]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
async function loadVc() {
|
||||
if (!isDownloading) {
|
||||
@@ -82,7 +99,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
|
||||
setFields(response.fields);
|
||||
})
|
||||
.catch(error => {
|
||||
setWellknown({fallback: 'true'});
|
||||
setWellknown({ fallback: 'true' });
|
||||
console.error(
|
||||
'Error occurred while fetching wellknown for viewing VC ',
|
||||
error,
|
||||
@@ -126,8 +143,15 @@ export const VCCardView: React.FC<VCItemProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<MessageOverlay
|
||||
progress={true}
|
||||
title={t('In Progress')}
|
||||
isVisible={controller.isReverifyingVc}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
ref={cardRef}
|
||||
accessible={false}
|
||||
onPress={() => onPress(service)}
|
||||
style={
|
||||
@@ -145,7 +169,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
|
||||
onDismiss={controller.DISMISS}
|
||||
translationPath={'VcDetails'}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -162,4 +186,5 @@ export interface VCItemProps {
|
||||
isInitialLaunch?: boolean;
|
||||
isTopCard?: boolean;
|
||||
onDisclosuresChange?: (paths: string[]) => void;
|
||||
onMeasured?: (rect: { x: number; y: number; width: number; height: number }) => void;
|
||||
}
|
||||
@@ -244,6 +244,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({
|
||||
<VCVerification
|
||||
vcMetadata={verifiableCredentialData?.vcMetadata}
|
||||
display={wellknownDisplayProperty}
|
||||
showLastChecked={false}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
@@ -301,7 +302,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({
|
||||
<Column padding="8 0">
|
||||
<View style={{ paddingHorizontal: 6, marginTop: 8 }}>
|
||||
<View
|
||||
style={{...Theme.Styles.horizontalSeparator, marginBottom: 12 }}
|
||||
style={{ ...Theme.Styles.horizontalSeparator, marginBottom: 12 }}
|
||||
/>
|
||||
<Column>
|
||||
<Text
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {Dimensions, View} from 'react-native';
|
||||
import {Column, Row, Text} from '../../ui';
|
||||
import {CustomTooltip} from '../../ui/ToolTip';
|
||||
import {Theme} from '../../ui/styleUtils';
|
||||
import { Dimensions, View } from 'react-native';
|
||||
import { Column, Row, Text } from '../../ui';
|
||||
import { CustomTooltip } from '../../ui/ToolTip';
|
||||
import { Theme } from '../../ui/styleUtils';
|
||||
import React from 'react';
|
||||
import {SvgImage} from '../../ui/svg';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import { SvgImage } from '../../ui/svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import { STATUS_FIELD_NAME } from './VCUtils';
|
||||
import { StatusTooltipContent } from './VcStatustooTip';
|
||||
|
||||
export const VCItemFieldName = ({
|
||||
fieldName,
|
||||
@@ -18,7 +20,7 @@ export const VCItemFieldName = ({
|
||||
fieldNameColor?: string;
|
||||
isDisclosed?: boolean;
|
||||
}) => {
|
||||
const {t} = useTranslation('ViewVcModal');
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
return (
|
||||
<Row>
|
||||
{fieldName && (
|
||||
@@ -26,64 +28,24 @@ export const VCItemFieldName = ({
|
||||
testID={`${testID}Title`}
|
||||
color={textColor}
|
||||
style={Theme.Styles.fieldItemTitle}>
|
||||
{fieldName}
|
||||
{fieldName === STATUS_FIELD_NAME ? t('VcDetails:status') : fieldName}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{fieldName == t('VcDetails:status') && (
|
||||
{fieldName == STATUS_FIELD_NAME && (
|
||||
<CustomTooltip
|
||||
testID="statusToolTip"
|
||||
width={Dimensions.get('screen').width * 0.8}
|
||||
height={Dimensions.get('screen').height * 0.28}
|
||||
triggerComponent={SvgImage.info()}
|
||||
triggerComponentStyles={{marginLeft: 2, marginTop: 2}}
|
||||
triggerComponentStyles={{ marginLeft: 2, marginTop: 2 }}
|
||||
toolTipContent={
|
||||
<Column align="flex-start">
|
||||
<View style={{marginBottom: 20}}>
|
||||
<Text weight="semibold">
|
||||
{t('statusToolTipContent.valid.title')}
|
||||
</Text>
|
||||
<Text
|
||||
weight="regular"
|
||||
style={[
|
||||
Theme.Styles.tooltipContentDescription,
|
||||
{marginTop: 3},
|
||||
]}>
|
||||
{t('statusToolTipContent.valid.description')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{marginBottom: 20}}>
|
||||
<Text weight="semibold">
|
||||
{t('statusToolTipContent.pending.title')}
|
||||
</Text>
|
||||
<Text
|
||||
weight="regular"
|
||||
style={[
|
||||
Theme.Styles.tooltipContentDescription,
|
||||
{marginTop: 3},
|
||||
]}>
|
||||
{t('statusToolTipContent.pending.description')}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text weight="semibold">
|
||||
{t('statusToolTipContent.expired.title')}
|
||||
</Text>
|
||||
<Text
|
||||
weight="regular"
|
||||
style={[
|
||||
Theme.Styles.tooltipContentDescription,
|
||||
{marginTop: 3},
|
||||
]}>
|
||||
{t('statusToolTipContent.expired.description')}
|
||||
</Text>
|
||||
</View>
|
||||
</Column>
|
||||
<StatusTooltipContent />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isDisclosed && (
|
||||
<Icon name="share-square-o" size={10} color="#666" style={{marginLeft:5, marginTop:3}} />
|
||||
<Icon name="share-square-o" size={10} color="#666" style={{ marginLeft: 5, marginTop: 3 }} />
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
@@ -94,19 +56,28 @@ export const VCItemFieldValue = ({
|
||||
testID,
|
||||
fieldValueColor: textColor = Theme.Colors.Details,
|
||||
}: {
|
||||
fieldValue: string;
|
||||
fieldValue: any;
|
||||
testID: string;
|
||||
fieldValueColor?: string;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
if (React.isValidElement(fieldValue)) {
|
||||
|
||||
return (
|
||||
<View
|
||||
testID={`${testID}Value`}
|
||||
color={textColor}
|
||||
style={Theme.Styles.fieldItemValue}>
|
||||
>
|
||||
{fieldValue}
|
||||
</Text>
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
testID={`${testID}Value`}
|
||||
color={textColor}
|
||||
style={Theme.Styles.fieldItemValue}>
|
||||
{fieldValue}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ export const DETAIL_VIEW_DEFAULT_FIELDS = [
|
||||
'address',
|
||||
];
|
||||
|
||||
export const STATUS_FIELD_NAME = 'Status';
|
||||
export const VC_STATUS_KEYS = ['valid', 'pending', 'expired', 'revoked'];
|
||||
|
||||
//todo UIN & VID to be removed once we get the fields in the wellknown endpoint
|
||||
export const CARD_VIEW_ADD_ON_FIELDS = ['UIN', 'VID'];
|
||||
export const DETAIL_VIEW_ADD_ON_FIELDS = [
|
||||
|
||||
30
components/VC/common/VcStatustooTip.tsx
Normal file
30
components/VC/common/VcStatustooTip.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Column } from "../../ui";
|
||||
import { Theme } from "../../ui/styleUtils";
|
||||
import { Text } from "../../ui";
|
||||
import { VC_STATUS_KEYS } from "./VCUtils";
|
||||
|
||||
export const StatusTooltipContent = () => {
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
|
||||
return (
|
||||
<Column align="center" style={{marginTop:20}}>
|
||||
{VC_STATUS_KEYS.map(key => (
|
||||
<View key={key} style={{ marginBottom: 20 }}>
|
||||
<Text weight="semibold">{t(`statusToolTipContent.${key}.title`)}</Text>
|
||||
<Text
|
||||
weight="regular"
|
||||
style={[
|
||||
Theme.Styles.tooltipContentDescription,
|
||||
{ marginTop: 3 },
|
||||
]}>
|
||||
{t(`statusToolTipContent.${key}.description`)}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,53 +1,83 @@
|
||||
import testIDProps from '../shared/commonUtil';
|
||||
import {Display} from './VC/common/VCUtils';
|
||||
import VerifiedIcon from './VerifiedIcon';
|
||||
import {Row, Text} from './ui';
|
||||
import {Theme} from './ui/styleUtils';
|
||||
import React from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import { View } from 'react-native';
|
||||
import testIDProps from '../shared/commonUtil';
|
||||
import { Display } from './VC/common/VCUtils';
|
||||
import VerifiedIcon from './VerifiedIcon';
|
||||
import PendingIcon from './PendingIcon';
|
||||
import {VCMetadata} from '../shared/VCMetadata';
|
||||
import { Row, Text } from './ui';
|
||||
import { Theme } from './ui/styleUtils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VCMetadata } from '../shared/VCMetadata';
|
||||
|
||||
export const VCVerification: React.FC<VCVerificationProps> = ({
|
||||
vcMetadata,
|
||||
display,
|
||||
showLastChecked = true,
|
||||
}) => {
|
||||
const {t} = useTranslation('VcDetails');
|
||||
const statusText = vcMetadata.isVerified
|
||||
? vcMetadata.isExpired
|
||||
? t('expired')
|
||||
: t('valid')
|
||||
: t('pending');
|
||||
const { t } = useTranslation('VcDetails');
|
||||
|
||||
let statusText: string;
|
||||
let statusIcon: JSX.Element;
|
||||
|
||||
if (vcMetadata.isVerified) {
|
||||
if (vcMetadata.isRevoked) {
|
||||
statusText = t('revoked');
|
||||
statusIcon = <PendingIcon color="brown" />;
|
||||
} else if (vcMetadata.isExpired) {
|
||||
statusText = t('expired');
|
||||
statusIcon = <PendingIcon color="red" />;
|
||||
} else {
|
||||
statusText = t('valid');
|
||||
statusIcon = <VerifiedIcon />;
|
||||
}
|
||||
} else {
|
||||
statusText = t('pending');
|
||||
statusIcon = <PendingIcon color="orange" />;
|
||||
}
|
||||
|
||||
const statusIcon = vcMetadata.isVerified ? (
|
||||
vcMetadata.isExpired ? (
|
||||
<PendingIcon />
|
||||
) : (
|
||||
<VerifiedIcon />
|
||||
)
|
||||
) : (
|
||||
<PendingIcon />
|
||||
);
|
||||
return (
|
||||
<Row
|
||||
{...testIDProps('verified')}
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<React.Fragment>
|
||||
{statusIcon}
|
||||
<View
|
||||
{...testIDProps('verified')}
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
paddingVertical: 6,
|
||||
}}>
|
||||
|
||||
{/* First Row: Status Icon + Text */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{statusIcon}
|
||||
<Text
|
||||
testID="verificationStatus"
|
||||
color={display.getTextColor(Theme.Colors.Details)}
|
||||
style={Theme.Styles.verificationStatus}>
|
||||
{statusText}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{showLastChecked && vcMetadata.lastKnownStatusTimestamp && (
|
||||
<View style={{ marginTop: 4 }}>
|
||||
<Text
|
||||
testID="verificationStatus"
|
||||
testID='lastCheckedLabel'
|
||||
color={display.getTextColor(Theme.Colors.Details)}
|
||||
style={Theme.Styles.verificationStatus}>
|
||||
{statusText}
|
||||
style={[Theme.Styles.verificationStatus, { fontFamily: 'Inter_400' }]}>
|
||||
{t('lastChecked')}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
</Row>
|
||||
);
|
||||
<Text
|
||||
testID="lastKnownStatusTimestamp"
|
||||
color={display.getTextColor(Theme.Colors.Details)}
|
||||
style={[Theme.Styles.verificationStatus,{ fontFamily: 'Inter_400' }]}>
|
||||
{new Date(vcMetadata.lastKnownStatusTimestamp).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export interface VCVerificationProps {
|
||||
vcMetadata: VCMetadata;
|
||||
display: Display;
|
||||
showLastChecked?: boolean;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ export const getKebabMenuOptions = props => {
|
||||
onPress: controller.SHOW_ACTIVITY,
|
||||
testID: 'viewActivityLog',
|
||||
},
|
||||
{
|
||||
label: t('reverify'),
|
||||
icon: SvgImage.ReverifyIcon(),
|
||||
onPress: controller.REVERIFY_VC,
|
||||
testID: 'reverify',
|
||||
},
|
||||
{
|
||||
label: t('removeFromWallet'),
|
||||
icon: SvgImage.outlinedDeleteIcon(),
|
||||
|
||||
@@ -60,6 +60,7 @@ import QuestionIcon from '../../assets/questionIcon.svg';
|
||||
import CopyIcon from '../../assets/file_copy.svg';
|
||||
import StarIcon from '../../assets/credentialRegestryStar.svg';
|
||||
import SelectedCheckBox from '../../assets/Selected_Check_Box.svg';
|
||||
import ReverifyIcon from '../../assets/Reverify.svg';
|
||||
export class SvgImage {
|
||||
static selectedCheckBox() {
|
||||
return <SelectedCheckBox />;
|
||||
@@ -248,6 +249,12 @@ export class SvgImage {
|
||||
);
|
||||
}
|
||||
|
||||
static ReverifyIcon() {
|
||||
return (
|
||||
<ReverifyIcon/>
|
||||
)
|
||||
}
|
||||
|
||||
static OutlinedPinIcon() {
|
||||
return <OutlinedPinIcon {...testIDProps('outlinedPinIcon')} />;
|
||||
}
|
||||
|
||||
@@ -769,24 +769,24 @@ export const DefaultTheme = {
|
||||
flex: 1,
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
horizontalSeparator:{
|
||||
horizontalSeparator: {
|
||||
height: 1,
|
||||
backgroundColor: '#DADADA',
|
||||
},
|
||||
disclosureTitle:{
|
||||
disclosureTitle: {
|
||||
fontFamily: 'Inter_700Bold',
|
||||
fontSize: 15,
|
||||
color: Colors.Black,
|
||||
},
|
||||
disclosureSubtitle:{
|
||||
disclosureSubtitle: {
|
||||
fontSize: 13,
|
||||
color: '#747474',
|
||||
marginTop: 4,
|
||||
},
|
||||
disclosureSelectButton:{
|
||||
disclosureSelectButton: {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter_700Bold',
|
||||
}
|
||||
},
|
||||
}),
|
||||
BannerStyles: StyleSheet.create({
|
||||
container: {
|
||||
@@ -1191,7 +1191,7 @@ export const DefaultTheme = {
|
||||
borderColor: Colors.Orange,
|
||||
borderRadius: 30,
|
||||
},
|
||||
sharedSuccessfullyVerifierInfo:{
|
||||
sharedSuccessfullyVerifierInfo: {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderRadius: 16,
|
||||
@@ -1205,7 +1205,7 @@ export const DefaultTheme = {
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
marginRight: 12,
|
||||
}
|
||||
},
|
||||
}),
|
||||
AppMetaDataStyles: StyleSheet.create({
|
||||
buttonContainer: {
|
||||
@@ -1366,6 +1366,15 @@ export const DefaultTheme = {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
new: {
|
||||
height: 20,
|
||||
width: 'auto',
|
||||
backgroundColor: '#FF5300',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
kebabHeaderStyle: {
|
||||
justifyContent: 'space-between',
|
||||
fontFamily: 'Inter_700Bold',
|
||||
@@ -2168,14 +2177,14 @@ export const DefaultTheme = {
|
||||
color: '#973C00',
|
||||
marginBottom: 5,
|
||||
},
|
||||
noteDescriptionText:{
|
||||
noteDescriptionText: {
|
||||
fontSize: 13,
|
||||
color: '#973C00',
|
||||
fontFamily: 'Inter_400Regular',
|
||||
lineHeight: 18,
|
||||
textAlign: 'left',
|
||||
marginLeft: -25
|
||||
}
|
||||
marginLeft: -25,
|
||||
},
|
||||
}),
|
||||
DisclosureInfo: StyleSheet.create({
|
||||
view: {
|
||||
|
||||
@@ -1372,6 +1372,15 @@ export const PurpleTheme = {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
new: {
|
||||
height: 20,
|
||||
width: 'auto',
|
||||
backgroundColor: '#FF5300',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
kebabHeaderStyle: {
|
||||
justifyContent: 'space-between',
|
||||
fontFamily: 'Inter_700Bold',
|
||||
|
||||
Reference in New Issue
Block a user