From ef0199a455cbd3e9ae506ec3eb97c85462f557e5 Mon Sep 17 00:00:00 2001 From: Aman Chaturvedi Date: Thu, 29 Jun 2023 14:15:25 +0530 Subject: [PATCH 01/10] TP-31863 | ID Card maker flow --- src/action/profileActions.ts | 52 ++++++++++ src/assets/icons/IDCardIcon.tsx | 23 ++++ src/components/utlis/apiHelper.ts | 4 + src/reducer/profileSlice.ts | 43 ++++++++ src/screens/Profile/AgentIdCard.tsx | 44 ++++++++ src/screens/Profile/IDCardImageCapture.tsx | 104 +++++++++++++++++++ src/screens/Profile/ReviewIDImage.tsx | 57 ++++++++++ src/screens/Profile/index.tsx | 92 +++++++++++++--- src/screens/allCases/index.tsx | 4 + src/screens/caseDetails/CaseDetailHeader.tsx | 11 +- src/store/store.ts | 2 + 11 files changed, 418 insertions(+), 18 deletions(-) create mode 100644 src/action/profileActions.ts create mode 100644 src/assets/icons/IDCardIcon.tsx create mode 100644 src/reducer/profileSlice.ts create mode 100644 src/screens/Profile/AgentIdCard.tsx create mode 100644 src/screens/Profile/IDCardImageCapture.tsx create mode 100644 src/screens/Profile/ReviewIDImage.tsx diff --git a/src/action/profileActions.ts b/src/action/profileActions.ts new file mode 100644 index 00000000..6662c0c0 --- /dev/null +++ b/src/action/profileActions.ts @@ -0,0 +1,52 @@ +import { toast } from '../../RN-UI-LIB/src/components/toast'; +import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper'; +import { logError } from '../components/utlis/errorUtils'; +import { setIsUploadingImage, setOriginalImageDetails } from '../reducer/profileSlice'; +import { AppDispatch } from '../store/store'; + +export const uploadImageId = (agentId: string, uri: string) => (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.UPLOAD_IMAGE_ID); + const formData = new FormData(); + dispatch(setIsUploadingImage(true)); + formData.append('file', { + uri: uri, + type: 'image/jpeg', + name: `${agentId}_selfie.jpg`, + } as any); + axiosInstance + .post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then(() => { + toast({ type: 'info', text1: 'Your ID card has been sent for approval' }); + }) + .catch((err) => { + toast({ type: 'error', text1: `Error while uploading image id:${JSON.stringify(err)}` }); + logError(err as Error, 'Error while uploading image id'); + }) + .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?.[0]?.originalDocumentUri) { + const { originalDocumentUri, updatedAt } = res.data?.[0]; + dispatch( + setOriginalImageDetails({ + originalImageUri: originalDocumentUri, + validationDate: updatedAt, + }) + ); + } + }) + .catch((err) => { + logError(err as Error, 'Error while fetching selfie document'); + }); +}; diff --git a/src/assets/icons/IDCardIcon.tsx b/src/assets/icons/IDCardIcon.tsx new file mode 100644 index 00000000..e596522f --- /dev/null +++ b/src/assets/icons/IDCardIcon.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from '../../../RN-UI-LIB/src/Icons/types'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; + +const IDCardIcon: React.FC = ({ size = 20, fillColor = COLORS.TEXT.TEAL }) => ( + + + + + +); + +export default IDCardIcon; diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index c4770539..03d9d4a0 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -52,6 +52,8 @@ export enum ApiKeys { UPLOAD_FEEDBACK_IMAGES = 'UPLOAD_FEEDBACK_IMAGES', ORIGINAL_IMAGES = 'ORIGINAL_IMAGES', GLOBAL_CONFIG = 'GLOBAL_CONFIG', + UPLOAD_IMAGE_ID = 'UPLOAD_IMAGE_ID', + GET_DOCUMENTS = 'GET_DOCUMENTS', } export const API_URLS: Record = {} as Record; @@ -89,6 +91,8 @@ API_URLS[ApiKeys.FETCH_CASES] = '/cases/agents/{agentReferenceId}'; API_URLS[ApiKeys.GET_FORECLOSURE_AMOUNT] = '/{loanAccountNumber}/pre-closure-amount'; API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/feedback/persist-original-images'; API_URLS[ApiKeys.GLOBAL_CONFIG] = '/global-config'; +API_URLS[ApiKeys.UPLOAD_IMAGE_ID] = '/user/documents/selfie'; +API_URLS[ApiKeys.GET_DOCUMENTS] = '/user/documents'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/reducer/profileSlice.ts b/src/reducer/profileSlice.ts new file mode 100644 index 00000000..8b00a682 --- /dev/null +++ b/src/reducer/profileSlice.ts @@ -0,0 +1,43 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + isUploadingImage: false, + imageUri: '', + originalImageUri: '', + validationDate: '', + showReviewImageModal: false, +}; + +export const profileSlice = createSlice({ + name: 'profile', + initialState, + reducers: { + setImageUri: (state, action) => { + state.imageUri = action.payload; + }, + setOriginalImageDetails: (state, action) => { + const { originalImageUri, validationDate } = action.payload; + state.originalImageUri = originalImageUri; + state.validationDate = validationDate; + }, + setIsUploadingImage: (state, action) => { + state.isUploadingImage = action.payload; + if (!action.payload) { + state.showReviewImageModal = false; + state.imageUri = ''; + } + }, + setShowReviewImageModal: (state, action) => { + state.showReviewImageModal = action.payload; + }, + }, +}); + +export const { + setImageUri, + setIsUploadingImage, + setShowReviewImageModal, + setOriginalImageDetails, +} = profileSlice.actions; + +export default profileSlice.reducer; diff --git a/src/screens/Profile/AgentIdCard.tsx b/src/screens/Profile/AgentIdCard.tsx new file mode 100644 index 00000000..6eddb1fd --- /dev/null +++ b/src/screens/Profile/AgentIdCard.tsx @@ -0,0 +1,44 @@ +import { StyleSheet, View } from 'react-native'; +import React from 'react'; +import NaviLogoWithTextIcon from '../../../RN-UI-LIB/src/Icons/NaviLogoWithLabelIcon'; +import Avatar from '../../../RN-UI-LIB/src/components/Avatar'; +import { GenericStyles, SCREEN_WIDTH } from '../../../RN-UI-LIB/src/styles'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import Heading from '../../../RN-UI-LIB/src/components/Heading'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { useAppSelector } from '../../hooks'; +import { BUSINESS_DATE_FORMAT, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates'; + +const AgentIdCard = () => { + const { originalImageUri, agentName, agentPhone, validationDate } = useAppSelector((state) => ({ + originalImageUri: state.profile.originalImageUri, + agentName: state.user.user?.name!!, + agentPhone: state.user.user?.phoneNumber, + validationDate: state.profile.validationDate, + })); + return ( + + + + + + + Valid as of {dateFormat(new Date(validationDate), BUSINESS_DATE_FORMAT)} + + + {agentName} + + + {agentPhone} + + + ); +}; + +export default AgentIdCard; + +const styles = StyleSheet.create({ + greyColor: { + color: COLORS.TEXT.GREY, + }, +}); diff --git a/src/screens/Profile/IDCardImageCapture.tsx b/src/screens/Profile/IDCardImageCapture.tsx new file mode 100644 index 00000000..4a5e05ba --- /dev/null +++ b/src/screens/Profile/IDCardImageCapture.tsx @@ -0,0 +1,104 @@ +import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native'; +import React from 'react'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import { PERMISSIONS, RESULTS, request } from 'react-native-permissions'; +import { CameraOptions, launchCamera } from 'react-native-image-picker'; +import IDCardIcon from '../../assets/icons/IDCardIcon'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import ArrowRightOutlineIcon from '../../../RN-UI-LIB/src/Icons/ArrowRightOutlineIcon'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import ReviewIDImage from './ReviewIDImage'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import { setImageUri, setShowReviewImageModal } from '../../reducer/profileSlice'; + +const IDCardImageCapture = () => { + const { imageUri, showReviewImageModal } = useAppSelector((state) => state.profile); + const dispatch = useAppDispatch(); + + const handleImageCapture = () => { + request(PERMISSIONS.ANDROID.CAMERA).then(async (result) => { + if (result === RESULTS.GRANTED) { + const config: CameraOptions = { + mediaType: 'photo', + }; + config.includeExtra = true; + launchCamera(config, (response) => { + if (response?.didCancel) { + return; + } + if (response?.assets?.length) { + const { uri } = response.assets[0]; + if (uri) { + dispatch(setImageUri(uri)); + if (!imageUri) { + toggleReviewImageModal(); + } + } + } + }); + } + }); + }; + + const toggleReviewImageModal = () => { + dispatch(setShowReviewImageModal(!showReviewImageModal)); + }; + + const handleCloseModal = () => { + toggleReviewImageModal(); + dispatch(setImageUri('')); + }; + + return ( + + + + + + + + Capture your image to create an ID card for customer’s reference. + + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + iconContainer: { + backgroundColor: COLORS.BORDER.TEAL, + paddingHorizontal: 12, + justifyContent: 'center', + }, + chevron: { + marginLeft: 10, + marginTop: 6, + }, + elevation2: { + elevation: 2, + }, +}); + +export default IDCardImageCapture; diff --git a/src/screens/Profile/ReviewIDImage.tsx b/src/screens/Profile/ReviewIDImage.tsx new file mode 100644 index 00000000..71819fce --- /dev/null +++ b/src/screens/Profile/ReviewIDImage.tsx @@ -0,0 +1,57 @@ +import { StyleSheet, View } from 'react-native'; +import React from 'react'; +import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import Button from '../../../RN-UI-LIB/src/components/Button'; +import RNFastImage from '../../../RN-UI-LIB/src/components/FastImage'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import { uploadImageId } from '../../action/profileActions'; + +interface IReviewIDImage { + imageUri: string; + retakeImage: () => void; + closeModal: () => void; +} + +const ReviewIDImage: React.FC = ({ imageUri, retakeImage, closeModal }) => { + const dispatch = useAppDispatch(); + const { agentId, isUploadingImage } = useAppSelector((state) => ({ + agentId: state.user.user?.referenceId!!, + isUploadingImage: state.profile.isUploadingImage, + })); + + const handleImageUpload = () => { + dispatch(uploadImageId(agentId, imageUri)); + }; + return ( + + + + + + +