TP-0 | Merge branch 'master' of github.cmd.navi-tech.in:medici/Address-Verification-App into feat/TP-31697

This commit is contained in:
Aman Chaturvedi
2023-06-21 16:37:42 +05:30
15 changed files with 204 additions and 26 deletions

View File

@@ -25,6 +25,7 @@ import foregroundService from '../services/foregroundServices/foreground.service
import { loggedOutCurrentUser } from '../hooks/useFirestoreUpdates';
import { GenericFunctionArgs, GenericType } from '../common/GenericTypes';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { resetConfig } from '../reducer/configSlice';
export interface GenerateOTPPayload {
phoneNumber: string;
@@ -196,6 +197,7 @@ export const handleLogout = () => async (dispatch: AppDispatch) => {
})
);
dispatch(resetCasesData());
dispatch(resetConfig());
} catch (err) {
logError(err as Error, 'Logout clear session details error');
}

View File

@@ -0,0 +1,28 @@
import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
import { AppDispatch } from '../store/store';
import { logError } from '../components/utlis/errorUtils';
import { setConfigData, setLoading } from '../reducer/configSlice';
export const getConfigData = () => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.GLOBAL_CONFIG);
dispatch(setLoading(true));
axiosInstance
.get(url, {
headers: {
donotHandleError: true,
},
})
.then((response) => {
if (response?.data) {
dispatch(setConfigData(response.data));
return;
}
throw response;
})
.catch((err) => {
logError(err);
})
.finally(() => {
dispatch(setLoading(false));
});
};

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
function QuestionMarkIcon(props) {
return (
<Svg
width={16}
height={16}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<Path
d="M8 13a.964.964 0 00.708-.292A.964.964 0 009 12a.964.964 0 00-.292-.708A.964.964 0 008 11a.964.964 0 00-.708.292A.964.964 0 007 12c0 .278.097.514.292.708.194.195.43.292.708.292zm-.75-3.188h1.52c0-.513.046-.878.136-1.093.09-.216.31-.49.656-.823.487-.472.823-.875 1.01-1.208.188-.334.282-.702.282-1.105 0-.764-.26-1.385-.781-1.864C9.553 3.239 8.889 3 8.083 3c-.708 0-1.323.188-1.843.563-.521.374-.886.881-1.094 1.52l1.354.563c.125-.39.323-.691.594-.906.27-.216.587-.323.948-.323.389 0 .708.11.958.333.25.222.375.514.375.875 0 .32-.108.604-.323.854a7.88 7.88 0 01-.719.73c-.486.444-.788.808-.906 1.093-.118.285-.177.788-.177 1.51zM8 16a7.796 7.796 0 01-3.104-.625 8.064 8.064 0 01-2.552-1.719 8.063 8.063 0 01-1.719-2.552A7.797 7.797 0 010 8c0-1.111.208-2.15.625-3.115a8.064 8.064 0 014.27-4.26A7.797 7.797 0 018 0c1.111 0 2.15.208 3.115.625a8.095 8.095 0 014.26 4.26C15.792 5.851 16 6.89 16 8a7.796 7.796 0 01-.625 3.104 8.063 8.063 0 01-4.26 4.271A7.774 7.774 0 018 16z"
fill="#fff"
/>
</Svg>
);
}
export default QuestionMarkIcon;

View File

@@ -1,4 +1,5 @@
import { ReactNode, useEffect, useRef } from 'react';
import { NativeEventSubscription } from 'react-native';
import UnstoppableService, {
IForegroundTask,
} from '../services/foregroundServices/foreground.service';
@@ -24,7 +25,8 @@ import {
import { getSyncCaseIds } from '../components/utlis/firebaseFallbackUtils';
import { syncCasesByFallback } from '../reducer/allCasesSlice';
import { MILLISECONDS_IN_A_MINUTE } from '../../RN-UI-LIB/src/utlis/common';
import { VisitPlanStatus, setLockData } from '../reducer/userSlice';
import { setLockData } from '../reducer/userSlice';
import { getConfigData } from '../action/configActions';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
@@ -155,6 +157,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
// App comes to foreground from background
if (nextAppState === 'active') {
handleGetCaseSyncStatus();
dispatch(getConfigData());
UnstoppableService.start(tasks);
if (bgTrackingTimeoutId.current) {
clearTimeout(bgTrackingTimeoutId.current);
@@ -179,6 +182,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
return;
}
await handleGetCaseSyncStatus();
dispatch(getConfigData());
if (LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) {
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
if (updatedDetails?.cases?.length) {
@@ -189,14 +193,18 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
}, []);
useEffect(() => {
let appStateSubscription: NativeEventSubscription;
if (isOnline) {
AppState.addEventListener('change', handleAppStateChange);
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
UnstoppableService.start(tasks);
} else {
if (UnstoppableService.isRunning()) {
UnstoppableService.stopAll();
}
}
return () => {
appStateSubscription?.remove();
};
}, [isOnline]);
useIsLocationEnabled();

View File

@@ -51,6 +51,7 @@ export enum ApiKeys {
GET_FORECLOSURE_AMOUNT = 'GET_FORECLOSURE_AMOUNT',
UPLOAD_FEEDBACK_IMAGES = 'UPLOAD_FEEDBACK_IMAGES',
ORIGINAL_IMAGES = 'ORIGINAL_IMAGES',
GLOBAL_CONFIG = 'GLOBAL_CONFIG',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -87,6 +88,7 @@ API_URLS[ApiKeys.CASES_SEND_ID] = '/cases/sync';
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';
export const API_STATUS_CODE = {
OK: 200,

View File

@@ -0,0 +1,36 @@
import { createSlice } from '@reduxjs/toolkit';
interface IConfigData {
supportLink: '';
}
interface IConfigSlice {
data: IConfigData;
loading: boolean;
}
export const initialConfigData = {
supportLink: '',
} as IConfigData;
const initialState = {
data: initialConfigData,
loading: false,
} as IConfigSlice;
const ConfigSlice = createSlice({
name: 'config',
initialState,
reducers: {
setConfigData: (state, action) => {
state.data = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
resetConfig: () => initialState,
},
});
export const { setConfigData, setLoading, resetConfig } = ConfigSlice.actions;
export default ConfigSlice.reducer;

View File

@@ -1,5 +1,13 @@
import React, { useState } from 'react';
import { Alert, Pressable, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
import {
Alert,
Linking,
Pressable,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
import Text from '../../../RN-UI-LIB/src/components/Text';
import LogoutIcon from '../../../RN-UI-LIB/src/Icons/LogoutIcon';
@@ -22,15 +30,22 @@ import { useFocusEffect } from '@react-navigation/native';
import { CaseDetail } from '../caseDetails/interface';
import CaseItem from '../allCases/CaseItem';
import { IUserRole } from '../../reducer/userSlice';
import QuestionMarkIcon from '../../assets/icons/QuestionMarkIcon';
const Profile: React.FC = () => {
const [buttonPressedCount, setButtonPressedCount] = useState(0);
const dispatch = useAppDispatch();
const { phoneNumber, name, roles } = useAppSelector((state: RootState) => state.user.user!!);
const { completedList: completeCasesList, caseDetails } = useAppSelector(
(state) => state.allCases
);
const {
completedList: completeCasesList,
caseDetails,
supportLink,
} = useAppSelector((state: RootState) => ({
completedList: state.allCases.completedList,
caseDetails: state.allCases.caseDetails,
supportLink: state.config.data?.supportLink,
}));
useFocusEffect(
React.useCallback(() => {
@@ -80,9 +95,33 @@ const Profile: React.FC = () => {
});
};
const helpButtonClickHandler = () => Linking.openURL(supportLink);
return (
<View style={GenericStyles.fill}>
<NavigationHeader title={name} subTitle={phoneNumber} showAvatarIcon />
<NavigationHeader
title={name}
subTitle={phoneNumber}
showAvatarIcon
rightActionable={
supportLink ? (
<View style={[GenericStyles.row, GenericStyles.fill, GenericStyles.alignStart]}>
<TouchableOpacity
activeOpacity={0.7}
onPress={helpButtonClickHandler}
style={[GenericStyles.row, GenericStyles.centerAlignedRow]}
>
<QuestionMarkIcon />
<Text style={[GenericStyles.whiteText, styles.helpText, GenericStyles.fw500]}>
Help
</Text>
</TouchableOpacity>
</View>
) : (
false
)
}
/>
<ScrollView>
<View
style={[
@@ -157,6 +196,9 @@ const Profile: React.FC = () => {
export default Profile;
const styles = StyleSheet.create({
helpText: {
paddingLeft: 6,
},
logoutContainer: {
paddingVertical: 16,
paddingHorizontal: 20,

View File

@@ -12,7 +12,7 @@ import {
import { BUSINESS_DATE_FORMAT, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { RootState } from '../../store/store';
import Tag from '../../../RN-UI-LIB/src/components/Tag';
import Tag, { TagVariant } from '../../../RN-UI-LIB/src/components/Tag';
import { CaseAllocationType, TaskTitleUIMapping } from '../allCases/interface';
import { updatePreDefinedCaseFormJourney } from '../../reducer/caseReducer';
import { getCollectionFeedbackOnAddressPreDefinedJourney } from '../../components/utlis/addressGeolocationUtils';
@@ -147,7 +147,7 @@ const AddressItem = ({
</Text>
{isGroupedAddress && contactabilityStatus ? (
<View style={GenericStyles.mv8}>
<Tag variant="yellow" text={contactabilityStatus} />
<Tag variant={TagVariant.yellow} text={contactabilityStatus} />
</View>
) : null}
<Text

View File

@@ -50,6 +50,7 @@ const CaseItem: React.FC<ICaseItemProps> = ({
caseDetailObj?.interactionStatus,
caseDetailObj?.caseVerdict,
caseDetailObj?.imageUri,
caseDetailObj?.totalOverdueAmount,
JSON.stringify(caseDetailObj?.currentTask),
caseDetailObj?.addressString,
]);

View File

@@ -24,15 +24,15 @@ import {
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import Tag from '../../../RN-UI-LIB/src/components/Tag';
import Tag, { TagVariant } from '../../../RN-UI-LIB/src/components/Tag';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import OnboardingIcon from '../../../RN-UI-LIB/src/Icons/OnboardingIcon';
import CollectionsIcon from '../../../RN-UI-LIB/src/Icons/CollectionsIcon';
import RoundCheckIcon from '../../../RN-UI-LIB/src/Icons/RoundCheckIcon';
import { getDocumentList } from '../../components/utlis/commonFunctions';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from './constants';
import { VisitPlanStatus } from '../../reducer/userSlice';
import { PaymentStatus } from '../caseDetails/interface';
interface IListItem {
caseListItemDetailObj: ICaseItemCaseDetailObj;
@@ -41,6 +41,19 @@ interface IListItem {
shouldBatchAvatar?: boolean;
}
const paymentStatusMapping: Record<
PaymentStatus,
{ label: PaymentStatus | string; variant: TagVariant }
> = {
[PaymentStatus.Paid]: { label: PaymentStatus.Paid, variant: TagVariant.success },
[PaymentStatus['Partially Paid']]: {
label: PaymentStatus['Partially Paid'],
variant: TagVariant.yellow,
},
[PaymentStatus.Unpaid]: { label: PaymentStatus.Unpaid, variant: TagVariant.alert },
[PaymentStatus.Closed]: { label: PaymentStatus.Closed, variant: TagVariant.error },
};
const ListItem: React.FC<IListItem> = (props) => {
const { caseListItemDetailObj, isCompleted, isTodoItem, shouldBatchAvatar } = props;
const {
@@ -49,13 +62,14 @@ const ListItem: React.FC<IListItem> = (props) => {
caseStatus,
caseType,
collectionTag,
pos,
paymentStatus,
dpdBucket,
pinRank,
isSynced,
isNewlyAdded,
interactionStatus,
caseVerdict,
totalOverdueAmount,
} = caseListItemDetailObj;
const isCollectionCaseType = caseType === CaseAllocationType.COLLECTION_CASE;
@@ -185,19 +199,26 @@ const ListItem: React.FC<IListItem> = (props) => {
{isCollectionCaseType ? (
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
<Tag
variant="violet"
variant={TagVariant.violet}
text={CaseTypeMap[CaseAllocationType.COLLECTION_CASE]}
tagIcon={<CollectionsIcon />}
/>
{paymentStatus ? (
<View style={[GenericStyles.ml8]}>
<Tag
variant={paymentStatusMapping[paymentStatus]?.variant || TagVariant.alert}
text={(paymentStatusMapping[paymentStatus]?.label || paymentStatus) as string}
/>
</View>
) : null}
{collectionTag ? (
<View style={[GenericStyles.ml8]}>
<Tag variant="gray" text={collectionTag} />
<Tag variant={TagVariant.gray} text={collectionTag} />
</View>
) : null}
</View>
) : (
<Tag
variant="teal"
variant={TagVariant.teal}
text={CaseTypeMap[CaseAllocationType.ADDRESS_VERIFICATION_CASE]}
tagIcon={<OnboardingIcon />}
/>
@@ -222,7 +243,7 @@ const ListItem: React.FC<IListItem> = (props) => {
{isCollectionCaseType ? (
<View>
<Text small style={[styles.caseStatusText, styles.borderTop]} bold>
POS {formatAmount(pos, false)}
Total due {formatAmount(totalOverdueAmount, false)}
{' '}DPD bucket {dpdBucket}
</Text>
{caseInteractionStatus ? (

View File

@@ -156,6 +156,13 @@ export enum FeedbackStatus {
NOT_ATTEMPTED = 'NOT_ATTEMPTED',
}
export enum PaymentStatus {
'Paid' = 'Paid',
'Partially Paid' = 'Partially Paid',
'Unpaid' = 'Unpaid',
'Closed' = 'Closed',
}
export interface CaseDetail {
id: string;
allocationReferenceId?: string;
@@ -199,6 +206,7 @@ export interface CaseDetail {
offlineCaseKey?: string; // using for the collection case to handle multiple offline feedbacks
imageReferenceId?: string;
collectionTag?: 'Fresh' | 'Stab';
paymentStatus?: PaymentStatus;
disbursementAmount?: number;
imageUri?: string;
feedbackStatus?: FeedbackStatus;

View File

@@ -9,7 +9,7 @@ import { BUSINESS_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates';
import { EmiSelectedTab } from './EmiScheduleTab';
import { row } from './constants';
import EmiBreakupBottomSheet from './EmiBreakupBottomSheet';
import Tag from '../../../RN-UI-LIB/src/components/Tag';
import Tag, { TagVariant } from '../../../RN-UI-LIB/src/components/Tag';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import { getNumberWithRankSuffix } from '../../../RN-UI-LIB/src/utlis/common';
@@ -70,7 +70,7 @@ const EmiScheduleItem: React.FC<IEmiScheduleItem> = ({
{emiItem.status === 'UNPAID' ? (
<>
{selectedTab === EmiSelectedTab.ALL ? (
<Tag variant="error" text="Unpaid" />
<Tag variant={TagVariant.error} text="Unpaid" />
) : (
<Button
title="Breakup"
@@ -87,7 +87,7 @@ const EmiScheduleItem: React.FC<IEmiScheduleItem> = ({
{emiItem.status === 'PAID' ? (
<>
{selectedTab === EmiSelectedTab.ALL ? (
<Tag variant="success" text="Paid" />
<Tag variant={TagVariant.success} text="Paid" />
) : (
<Text light style={[GenericStyles.fontSize12]}>
Paid on:{' '}
@@ -105,9 +105,13 @@ const EmiScheduleItem: React.FC<IEmiScheduleItem> = ({
)}
</>
) : null}
{emiItem.status === 'SCHEDULED' ? <Tag variant="gray" text="Scheduled" /> : null}
{emiItem.status === 'SCHEDULED' ? (
<Tag variant={TagVariant.gray} text="Scheduled" />
) : null}
{emiItem.status === 'CANCELLED' ? <Tag variant="yellow" text="Cancelled" /> : null}
{emiItem.status === 'CANCELLED' ? (
<Tag variant={TagVariant.yellow} text="Cancelled" />
) : null}
</View>
</View>
<EmiBreakupBottomSheet

View File

@@ -28,6 +28,7 @@ import notificationsSlice from '../reducer/notificationsSlice';
import MetadataSlice from '../reducer/metadataSlice';
import foregroundServiceSlice from '../reducer/foregroundServiceSlice';
import feedbackImagesSlice from '../reducer/feedbackImagesSlice';
import configSlice from '../reducer/configSlice';
const rootReducer = combineReducers({
case: caseReducer,
@@ -46,6 +47,7 @@ const rootReducer = combineReducers({
metadata: MetadataSlice,
foregroundService: foregroundServiceSlice,
feedbackImages: feedbackImagesSlice,
config: configSlice,
});
const persistConfig = {
@@ -64,6 +66,7 @@ const persistConfig = {
'feedbackHistory',
'address',
'feedbackImages',
'config',
],
blackList: ['case', 'filters'],
};

View File

@@ -1,3 +1,4 @@
import { TagVariant } from '../../RN-UI-LIB/src/components/Tag';
import { COLORS } from '../../RN-UI-LIB/src/styles/colors';
export enum REPAYMENT_STATUS {
@@ -20,17 +21,17 @@ export const RepaymentStatusTagging: IRepaymentStatusTagging = {
[REPAYMENT_STATUS.FAILURE]: {
label: 'Failed',
iconColor: COLORS.TEXT.RED,
variant: 'error',
variant: TagVariant.error,
},
[REPAYMENT_STATUS.FAILED]: {
label: 'Failed',
iconColor: COLORS.TEXT.RED,
variant: 'error',
variant: TagVariant.error,
},
[REPAYMENT_STATUS.UNKNOWN]: {
label: 'Unknown',
iconColor: COLORS.TEXT.LIGHT,
variant: 'gray',
variant: TagVariant.gray,
},
};