From 9f239d161abac62d10736df655cd758d6dae7dba Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 30 May 2024 17:06:59 +0530 Subject: [PATCH 01/13] TP-66615 | Agent Id Card V1 --- src/action/profileActions.ts | 80 +++++++++------- src/common/BlockerScreen.tsx | 14 ++- .../useFCM/notificationHelperFunctions.ts | 32 +++++++ src/hooks/useFetchDocument.ts | 4 +- src/reducer/profileSlice.ts | 30 ++++-- src/screens/AgentIdCard/IdCard/IdCard.tsx | 94 +++++++++++++++++++ .../AgentIdCard/IdCard/IdCardCreationFlow.tsx | 92 ++++++++++++++++++ .../AgentIdCard/IdCard/ReviewIdCard.tsx | 70 ++++++++++++++ .../IdCardStatus/IdCardApprovalPending.tsx | 67 +++++++++++++ .../IdCardStatus/IdCardApproved.tsx | 82 ++++++++++++++++ .../IdCardStatus/IdCardRejected.tsx | 64 +++++++++++++ src/screens/AgentIdCard/index.tsx | 57 +++++++++++ src/screens/AgentIdCard/utils/index.tsx | 27 ++++++ src/screens/Profile/AgentIdCard.tsx | 3 + src/screens/Profile/ProfileStack.tsx | 70 ++++++-------- src/screens/Profile/ViewIdCardCta.tsx | 4 +- src/screens/allCases/CaseItemAvatar.tsx | 6 +- src/screens/auth/AuthRouter.tsx | 19 +++- src/screens/auth/ProtectedRouter.tsx | 4 +- 19 files changed, 723 insertions(+), 96 deletions(-) create mode 100644 src/screens/AgentIdCard/IdCard/IdCard.tsx create mode 100644 src/screens/AgentIdCard/IdCard/IdCardCreationFlow.tsx create mode 100644 src/screens/AgentIdCard/IdCard/ReviewIdCard.tsx create mode 100644 src/screens/AgentIdCard/IdCardStatus/IdCardApprovalPending.tsx create mode 100644 src/screens/AgentIdCard/IdCardStatus/IdCardApproved.tsx create mode 100644 src/screens/AgentIdCard/IdCardStatus/IdCardRejected.tsx create mode 100644 src/screens/AgentIdCard/index.tsx create mode 100644 src/screens/AgentIdCard/utils/index.tsx diff --git a/src/action/profileActions.ts b/src/action/profileActions.ts index 89e43bc2..964c60f0 100644 --- a/src/action/profileActions.ts +++ b/src/action/profileActions.ts @@ -1,9 +1,12 @@ +import { isFunction } from '@components/utlis/commonFunctions'; import { toast } from '../../RN-UI-LIB/src/components/toast'; import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper'; import { logError } from '../components/utlis/errorUtils'; import { ImageApprovalStatus, setApprovalStatus, + setImageUri, + setIsLoading, setIsUploadingImage, setOriginalImageDetails, } from '../reducer/profileSlice'; @@ -28,42 +31,55 @@ export const uploadImageId = (agentId: string, uri: string) => (dispatch: AppDis .then(() => { toast({ type: 'info', text1: ToastMessages.IMAGE_UPLOAD_SUCCESS }); dispatch(setApprovalStatus(ImageApprovalStatus.PENDING)); + dispatch(setImageUri('')); }) .finally(() => { dispatch(setIsUploadingImage(false)); }); }; -export const getSelfieDocument = () => (dispatch: AppDispatch) => { - const url = getApiUrl(ApiKeys.GET_DOCUMENTS, {}, { type: 'SELFIE' }); - axiosInstance - .get(url) - .then((res) => { - if (res.data?.documents?.[0]) { +export const getSelfieDocument = + (setLoading = false, callbackFn?: any) => + (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.GET_DOCUMENTS, {}, { type: 'SELFIE' }); + if (setLoading) dispatch(setIsLoading(true)); + axiosInstance + .get(url) + .then((res) => { const { documents, agencyName, agencyCode } = res.data; - const { originalDocumentUri, optimizedDocumentUri, updatedAt, approvalStatus } = - documents?.[0] || {}; - dispatch( - setOriginalImageDetails({ - originalImageUri: originalDocumentUri || '', - validationDate: updatedAt || '', - approvalStatus: approvalStatus || ImageApprovalStatus.REJECTED, - agencyName: agencyName || '', - agencyCode: agencyCode || '', - optimizedImageUri: optimizedDocumentUri || '', - }) - ); - } else { - dispatch( - setOriginalImageDetails({ - originalImageUri: '', - validationDate: '', - approvalStatus: ImageApprovalStatus.REJECTED, - }) - ); - } - }) - .catch((err) => { - logError(err as Error, 'Error while fetching selfie document'); - }); -}; + if (res.data?.documents?.[0]) { + const { originalDocumentUri, optimizedDocumentUri, approvalStatus, rejectionReason } = + documents?.[0] || {}; + dispatch( + setOriginalImageDetails({ + originalImageUri: originalDocumentUri || '', + approvalStatus: approvalStatus || ImageApprovalStatus.NOT_INITIATED, + agencyName: agencyName || '', + agencyCode: agencyCode || '', + optimizedImageUri: optimizedDocumentUri || '', + rejectionReason: rejectionReason || '', + }) + ); + + if (approvalStatus === ImageApprovalStatus.APPROVED && isFunction(callbackFn)) { + callbackFn(); + } + } else { + dispatch( + setOriginalImageDetails({ + originalImageUri: '', + agencyName: agencyName || '', + agencyCode: agencyCode || '', + rejectionReason: '', + approvalStatus: ImageApprovalStatus.NOT_INITIATED, + }) + ); + } + }) + .catch((err) => { + logError(err as Error, 'Error while fetching selfie document'); + }) + .finally(() => { + if (setLoading) dispatch(setIsLoading(false)); + }); + }; diff --git a/src/common/BlockerScreen.tsx b/src/common/BlockerScreen.tsx index fb10f4d4..3e1d22de 100644 --- a/src/common/BlockerScreen.tsx +++ b/src/common/BlockerScreen.tsx @@ -19,6 +19,9 @@ import { addClickstreamEvent } from '@services/clickstreamEventService'; import { setBlacklistedAppsInstalledData } from '@reducers/blacklistedAppsInstalledSlice'; import perf from '@react-native-firebase/perf'; import { GLOBAL } from '@constants/Global'; +import AgentIdCardFlow from '@screens/AgentIdCard'; +import { IdCardBlockStatus } from '@reducers/profileSlice'; +import FullScreenLoader from '@rn-ui-lib/components/FullScreenLoader'; interface IBlockerScreen { children?: ReactNode; @@ -35,7 +38,8 @@ const BlockerScreen = (props: IBlockerScreen) => { (state) => state.foregroundService ); const { isWifiOrCellularOn, appState } = useAppSelector((state) => state.metadata); - + const approvalStatus = useAppSelector((state) => state.profile?.approvalStatus); + const isLoading = useAppSelector((state) => state.profile?.isLoading); const [shouldUpdate, setShouldUpdate] = useState(); const [showActionBtnLoader, setShowActionBtnLoader] = useState(false); @@ -215,6 +219,14 @@ const BlockerScreen = (props: IBlockerScreen) => { }); return ; } + if (IdCardBlockStatus[approvalStatus as keyof typeof IdCardBlockStatus]) { + return ( + <> + + + + ); + } return <>{props.children}; }; diff --git a/src/hooks/useFCM/notificationHelperFunctions.ts b/src/hooks/useFCM/notificationHelperFunctions.ts index cf0b8b7a..7cedcb6d 100644 --- a/src/hooks/useFCM/notificationHelperFunctions.ts +++ b/src/hooks/useFCM/notificationHelperFunctions.ts @@ -39,6 +39,8 @@ export enum PushNotificationTypes { BOT_REMINDER_CALLBACK_NOTIFICATION = 'REQUESTED_CALLBACK_GEN_AI_BOT_FIELD_SCHEDULED_NOTIFICATION_TEMPLATE', BOT_PROMISED_TO_PAY_NOTIFICATION = 'PROMISED_TO_PAY_GEN_AI_BOT_FIELD_SCHEDULED_NOTIFICATION_TEMPLATE', BOT_REVISIT_NOTIFICATION = 'REVISIT_GEN_AI_BOT_SCHEDULED_NOTIFICATION_TEMPLATE', + ID_CARD_APPROVED = 'ID_CARD_APPROVED', + ID_CARD_REJECTED = 'ID_CARD_REJECTED', } type NotificationContent = (notification: INotification) => { @@ -122,6 +124,34 @@ const getPaymentFailedNotificationContent = (notification: INotification) => { return { title, body, actions, data, defaultPressAction }; }; +// ID Card approved notification content +const getIdCardApprovedNotificationContent = (notification: INotification) => { + const title = 'ID card approved!'; + const body = `Your ID card has been approved. Head over to profile to checkout your ID card`; + const defaultPressAction = actionContentMap[NotificationAction.DEFAULT].pressAction; + const actions = [] as AndroidAction[]; + const data = { + templateId: notification?.template?.id, + notificationId: notification?.id, + deepLinks: {}, + }; + return { title, body, actions, data, defaultPressAction }; +}; + +// ID Card rejected notification content +const getIdCardRejectedNotificationContent = (notification: INotification) => { + const title = 'ID card rejected!'; + const body = `Your ID card has been rejected. Please upload your selfie again for the ID card`; + const defaultPressAction = actionContentMap[NotificationAction.DEFAULT].pressAction; + const actions = [] as AndroidAction[]; + const data = { + templateId: notification?.template?.id, + notificationId: notification?.id, + deepLinks: {}, + }; + return { title, body, actions, data, defaultPressAction }; +}; + const getBotReminderNotificationContent = (notification: INotification) => { const { template } = notification || {}; const templateName = template?.templateName; @@ -220,6 +250,8 @@ export const notificationContentMap: Record = { [PushNotificationTypes.BOT_REMINDER_CALLBACK_NOTIFICATION]: getBotReminderNotificationContent, [PushNotificationTypes.BOT_PROMISED_TO_PAY_NOTIFICATION]: getBotReminderNotificationContent, [PushNotificationTypes.BOT_REVISIT_NOTIFICATION]: getBotReminderNotificationContent, + [PushNotificationTypes.ID_CARD_APPROVED]: getIdCardApprovedNotificationContent, + [PushNotificationTypes.ID_CARD_REJECTED]: getIdCardRejectedNotificationContent, }; const notificationDismissed = (notificationEvent: Event, isBgHandler = false) => { diff --git a/src/hooks/useFetchDocument.ts b/src/hooks/useFetchDocument.ts index 13866da2..57da51bd 100644 --- a/src/hooks/useFetchDocument.ts +++ b/src/hooks/useFetchDocument.ts @@ -108,8 +108,8 @@ const useFetchDocument = ( const signedRequestPayload: ISignedRequest = [ { documentReferenceId: referenceId, - caseId: '' + caseDetailObj.caseId, - caseType: caseDetailObj.caseType, + caseId: '' + caseDetailObj?.caseId, + caseType: caseDetailObj?.caseType, unSignedUri: unSignedUri }, ]; diff --git a/src/reducer/profileSlice.ts b/src/reducer/profileSlice.ts index f9f36293..c5fa2599 100644 --- a/src/reducer/profileSlice.ts +++ b/src/reducer/profileSlice.ts @@ -2,21 +2,31 @@ import { createSlice } from '@reduxjs/toolkit'; export enum ImageApprovalStatus { NOT_INITIATED = 'NOT_INITIATED', + INVALIDATED = 'INVALIDATED', APPROVED = 'APPROVED', PENDING = 'PENDING', REJECTED = 'REJECTED', } +export const IdCardBlockStatus = { + [ImageApprovalStatus.REJECTED]: true, + [ImageApprovalStatus.INVALIDATED]: true, + [ImageApprovalStatus.NOT_INITIATED]: true, + [ImageApprovalStatus.PENDING]: true, +}; + const initialState = { isUploadingImage: false, imageUri: '', + rejectionReason: '', originalImageUri: '', - validationDate: '', showReviewImageModal: false, - approvalStatus: ImageApprovalStatus.NOT_INITIATED, + approvalStatus: '', agencyName: '', agencyCode: '', optimizedImageUri: '', + showApprovedModal: false, + isLoading: false, }; export const profileSlice = createSlice({ @@ -30,24 +40,20 @@ export const profileSlice = createSlice({ const { originalImageUri, optimizedImageUri, - validationDate, approvalStatus, agencyName, agencyCode, + rejectionReason, } = action.payload; state.originalImageUri = originalImageUri; - state.validationDate = validationDate; state.approvalStatus = approvalStatus; state.agencyName = agencyName; state.agencyCode = agencyCode; state.optimizedImageUri = optimizedImageUri; + state.rejectionReason = rejectionReason; }, setIsUploadingImage: (state, action) => { state.isUploadingImage = action.payload; - if (!action.payload) { - state.showReviewImageModal = false; - state.imageUri = ''; - } }, setShowReviewImageModal: (state, action) => { state.showReviewImageModal = action.payload; @@ -55,6 +61,12 @@ export const profileSlice = createSlice({ setApprovalStatus: (state, action) => { state.approvalStatus = action.payload; }, + setShowApprovedModal: (state, action) => { + state.showApprovedModal = action.payload; + }, + setIsLoading: (state, action) => { + state.isLoading = action.payload; + }, resetProfileData: () => initialState, }, }); @@ -66,6 +78,8 @@ export const { setOriginalImageDetails, setApprovalStatus, resetProfileData, + setShowApprovedModal, + setIsLoading, } = profileSlice.actions; export default profileSlice.reducer; diff --git a/src/screens/AgentIdCard/IdCard/IdCard.tsx b/src/screens/AgentIdCard/IdCard/IdCard.tsx new file mode 100644 index 00000000..a62c73fa --- /dev/null +++ b/src/screens/AgentIdCard/IdCard/IdCard.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { COLORS } from '@rn-ui-lib/colors'; +import Avatar from '@rn-ui-lib/components/Avatar'; +import Text from '@rn-ui-lib/components/Text'; +import NaviLogoWithTextIcon from '@rn-ui-lib/icons/NaviLogoWithTextIcon'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import dayjs from 'dayjs'; +import { StyleSheet, View } from 'react-native'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { useAppSelector } from '@hooks'; +import { RootState } from '@store'; +import { NAVI_AGENCY_CODE } from '@common/Constants'; +import { DATE_MONTH_YEAR_DATE_FORMAT } from '@rn-ui-lib/utils/dates'; +dayjs.extend(advancedFormat); + +const IdCard = () => { + const imageUri = useAppSelector((state) => state.profile.imageUri); + const agencyName = useAppSelector((state: RootState) => state.profile.agencyName); + const agencyCode = useAppSelector((state: RootState) => state.profile.agencyCode); + const emailId = useAppSelector((state) => state.user.user?.emailId); + const phoneNumber = useAppSelector((state) => state.user.user?.phoneNumber); + const agentName = useAppSelector((state) => state.user.user?.name!!); + + const isExternalAgency = agencyName && agencyCode !== NAVI_AGENCY_CODE; + + return ( + + + {isExternalAgency ? ( + <> + + in association with + + + {agencyName} + + + ) : null} + + Valid as of {dayjs(new Date()).format(DATE_MONTH_YEAR_DATE_FORMAT)} + + + + + + Debt Management Executive + + + {agentName} + + + {emailId ? `${emailId} |` : null} {phoneNumber} + + + Emp ID{' '} + + 2345 + + + + ); +}; + +const styles = StyleSheet.create({ + greyColor: { + color: COLORS.TEXT.GREY, + }, + agentDetails: { + fontSize: 12, + color: COLORS.BORDER.SECONDARY, + }, + employeeId: { + fontSize: 12, + color: COLORS.TEXT.DARK_BLUE, + }, +}); + +export default IdCard; diff --git a/src/screens/AgentIdCard/IdCard/IdCardCreationFlow.tsx b/src/screens/AgentIdCard/IdCard/IdCardCreationFlow.tsx new file mode 100644 index 00000000..2860f9cc --- /dev/null +++ b/src/screens/AgentIdCard/IdCard/IdCardCreationFlow.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { COLORS } from '@rn-ui-lib/colors'; +import Button from '@rn-ui-lib/components/Button'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { StyleSheet, View } from 'react-native'; +import { useAppDispatch } from '@hooks'; +import GenerateIdCardIcon from '@rn-ui-lib/icons/GenerateIdCardIcon'; +import { handleIdCardImageCapture } from '../utils'; + +const IdCardCreationFlow = () => { + const dispatch = useAppDispatch(); + + const handleImageCapture = () => { + dispatch(handleIdCardImageCapture()); + }; + return ( + + + Setup digital ID card to access the app + + Guidelines for generating the ID card + + + + + Make sure the picture quality is clear + + + + Try to take the photo on a white background + + + + +