diff --git a/App.tsx b/App.tsx index 5ebe0dde..122a881b 100644 --- a/App.tsx +++ b/App.tsx @@ -19,7 +19,13 @@ import FullScreenLoader from './RN-UI-LIB/src/components/FullScreenLoader'; import { toastConfigs, ToastContainer } from './RN-UI-LIB/src/components/toast'; import * as Sentry from '@sentry/browser'; -import { APM_APP_NAME, APM_BASE_URL, ENV, SENTRY_DSN } from './src/constants/config'; +import { + APM_APP_NAME, + APM_BASE_URL, + ENV, + MS_CLARITY_PROJECT_ID, + SENTRY_DSN, +} from './src/constants/config'; import { COLORS } from './RN-UI-LIB/src/styles/colors'; import codePush from 'react-native-code-push'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -31,12 +37,16 @@ import ErrorBoundary from './src/common/ErrorBoundary'; import CodePush from 'react-native-code-push'; import { TDocumentObj } from './src/screens/caseDetails/interface'; import AuthRouter from './src/screens/auth/AuthRouter'; +import { initialize } from 'react-native-clarity'; Sentry.init({ dsn: SENTRY_DSN }); -if (ENV !== 'prod') { - // mockApiServer(); +if (ENV === 'prod') { + if (MS_CLARITY_PROJECT_ID) { + initialize(MS_CLARITY_PROJECT_ID); + } } + setJsErrorHandler(); LogBox.ignoreAllLogs(); diff --git a/config/dev/config.js b/config/dev/config.js index 0b20b602..350ade2b 100644 --- a/config/dev/config.js +++ b/config/dev/config.js @@ -8,3 +8,4 @@ export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://dev-longhorn-portal.np.navi-tech.in/apm-events'; export const GOOGLE_SSO_CLIENT_ID = '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; +export const MS_CLARITY_PROJECT_ID = ''; diff --git a/config/prod/config.js b/config/prod/config.js index 8ef9052b..4cf9cadd 100644 --- a/config/prod/config.js +++ b/config/prod/config.js @@ -12,3 +12,4 @@ export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = '136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com'; +export const MS_CLARITY_PROJECT_ID = 'gobifvjiac'; diff --git a/config/qa/config.js b/config/qa/config.js index 6fa05aeb..5df41c0d 100644 --- a/config/qa/config.js +++ b/config/qa/config.js @@ -12,3 +12,4 @@ export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; +export const MS_CLARITY_PROJECT_ID = ''; diff --git a/package.json b/package.json index 6a9606c2..c9d5313d 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "react-hook-form": "7.40.0", "react-native": "0.70.6", "react-native-call-log": "2.1.2", + "react-native-clarity": "0.0.3", "react-native-code-push": "7.1.0", "react-native-contacts": "7.0.5", "react-native-date-picker": "4.2.10", diff --git a/src/action/firebaseFallbackActions.ts b/src/action/firebaseFallbackActions.ts new file mode 100644 index 00000000..6f246977 --- /dev/null +++ b/src/action/firebaseFallbackActions.ts @@ -0,0 +1,63 @@ +import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper'; +import { logError } from '../components/utlis/errorUtils'; +import { FilterResponse } from '../screens/allCases/interface'; +import { CaseDetail } from '../screens/caseDetails/interface'; + +export enum SyncStatus { + SEND_CASES = 'SEND_CASES', + FETCH_CASES = 'FETCH_CASES', + SKIP = 'SKIP', +} + +interface IFilterSync { + filterComponentList: FilterResponse[]; +} + +export interface ISyncedCases { + cases: CaseDetail[]; + filters: IFilterSync; + deletedCaseIds: string[]; + payloadCreatedAt: number; +} + +interface ICases { + caseId: string; + caseViewCreatedAt?: number; +} + +export interface ISyncCaseIdPayload { + agentId: string; + cases: ICases[]; +} + +export const getCasesSyncStatus = async (userReferenceId: string) => { + try { + const url = getApiUrl(ApiKeys.CASES_SYNC_STATUS, {}, { userReferenceId }); + const response = await axiosInstance.get(url, { headers: { donotHandleError: true } }); + return response?.data?.syncStatus; + } catch (err) { + logError(err as Error, 'Error getting sync status'); + } +}; + +export const sendSyncCaseIds = async (payload: ISyncCaseIdPayload) => { + try { + const url = getApiUrl(ApiKeys.CASES_SEND_ID); + const response = await axiosInstance.post(url, payload, { + headers: { donotHandleError: true }, + }); + return response?.data; + } catch (err) { + logError(err as Error, 'Error sending case ids sync'); + } +}; + +export const fetchCasesToSync = async (agentReferenceId: string) => { + try { + const url = getApiUrl(ApiKeys.FETCH_CASES, { agentReferenceId }); + const response = await axiosInstance.get(url, { headers: { donotHandleError: true } }); + return response?.data; + } catch (err) { + logError(err as Error, 'Error fetching cases to be synced'); + } +}; diff --git a/src/common/BlockerScreen.tsx b/src/common/BlockerScreen.tsx index 24432dfe..3a50fc9e 100644 --- a/src/common/BlockerScreen.tsx +++ b/src/common/BlockerScreen.tsx @@ -23,6 +23,8 @@ const BlockerScreen = (props: IBlockerScreen) => { const { isTimeSynced, isDeviceLocationEnabled } = useAppSelector( (state) => state.foregroundService ); + const { isWifiOrCellularOn } = useAppSelector((state) => state.metadata); + const [showActionBtnLoader, setShowActionBtnLoader] = useState(false); const dispatch = useAppDispatch(); @@ -110,6 +112,20 @@ const BlockerScreen = (props: IBlockerScreen) => { ); } + if (!isWifiOrCellularOn) { + const { heading, instructions } = BLOCKER_SCREEN_DATA.DEVICE_WIFI_OR_CELLULAR_OFF; + return ( + + ); + } + return <>{props.children}; }; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index b1439476..d02c4fb9 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -562,6 +562,13 @@ export const BLOCKER_SCREEN_DATA = { `Please retry connecting using the button below, if it doesn't automatically detect.`, ], }, + DEVICE_WIFI_OR_CELLULAR_OFF: { + heading: `Please turn on your internet`, + instructions: [ + 'Open the Settings app on your Android device.', + 'Check you wifi/cellular settings', + ], + }, }; export const SCREEN_ANIMATION_DURATION = 300; diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 2aeb83e4..1bd766ac 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -13,11 +13,23 @@ import { useAppDispatch, useAppSelector } from '../hooks'; import { dataSyncService } from '../services/dataSync.service'; import { DATA_SYNC_TIME_INTERVAL, IS_DATA_SYNC_REQUIRED } from '../constants/config'; import useIsLocationEnabled from '../hooks/useIsLocationEnabled'; +import { + ISyncCaseIdPayload, + ISyncedCases, + SyncStatus, + fetchCasesToSync, + getCasesSyncStatus, + sendSyncCaseIds, +} from '../action/firebaseFallbackActions'; +import { getSyncCaseIds } from '../components/utlis/firebaseFallbackUtils'; +import { syncCasesByFallback } from '../reducer/allCasesSlice'; +import { MILLISECONDS_IN_A_MINUTE } from '../../RN-UI-LIB/src/utlis/common'; export enum FOREGROUND_TASKS { GEOLOCATION = 'GEOLOCATION', TIME_SYNC = 'TIME_SYNC', DATA_SYNC = 'DATA_SYNC', + FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK', } interface ITrackingComponent { @@ -33,6 +45,16 @@ const TrackingComponent: React.FC = ({ children }) => { const bgTrackingTimeoutId = useRef(); const user = useAppSelector((state) => state.user); + const { + referenceId, + pendingList = [], + pinnedList = [], + } = useAppSelector((state) => ({ + referenceId: state.user.user?.referenceId!!, + pendingList: state.allCases.pendingList, + pinnedList: state.allCases.pinnedList, + })); + const handleTimeSync = async () => { try { const timestamp = await getSyncTime(); @@ -56,17 +78,44 @@ const TrackingComponent: React.FC = ({ children }) => { } }; + const handleGetCaseSyncStatus = async () => { + try { + const syncStatus = await getCasesSyncStatus(referenceId); + if (syncStatus === SyncStatus.SEND_CASES) { + const cases = getSyncCaseIds([...pendingList, ...pinnedList]); + const payload: ISyncCaseIdPayload = { + agentId: referenceId, + cases, + }; + sendSyncCaseIds(payload); + } else if (syncStatus === SyncStatus.FETCH_CASES) { + const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId); + if (updatedDetails?.cases?.length) { + dispatch(syncCasesByFallback(updatedDetails)); + } + } + } catch (e) { + logError(e as Error, 'Error during fetching case sync status'); + } + }; + const tasks: IForegroundTask[] = [ { taskId: FOREGROUND_TASKS.TIME_SYNC, task: handleTimeSync, - delay: 1000 * 60 * 5, // 5 minutes, + delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes, onLoop: true, }, { taskId: FOREGROUND_TASKS.GEOLOCATION, task: handleSendGeolocation, - delay: 1000 * 60 * 3, // 3 minutes + delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes + onLoop: true, + }, + { + taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK, + task: handleGetCaseSyncStatus, + delay: 30 * MILLISECONDS_IN_A_MINUTE, // 30 minutes onLoop: true, }, ]; diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 530736e0..bd80f7a3 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -43,6 +43,9 @@ export enum ApiKeys { DATA_SYNC_UPLOAD_COMPLETED = 'DATA_SYNC_UPLOAD_COMPLETED', IMPERSONATION = 'IMPERSONATION', TELEPHONES = 'TELEPHONES', + CASES_SYNC_STATUS = 'CASES_SYNC_STATUS', + CASES_SEND_ID = 'CASES_SEND_ID', + FETCH_CASES = 'FETCH_CASES', } export const API_URLS: Record = {} as Record; @@ -72,6 +75,9 @@ API_URLS[ApiKeys.GET_PRE_SIGNED_URL_DATA_SYNC] = '/sync-data/get-pre-signed-url' API_URLS[ApiKeys.DATA_SYNC_UPLOAD_COMPLETED] = '/sync-data/upload-completed'; API_URLS[ApiKeys.IMPERSONATION] = '/auth/impersonation'; API_URLS[ApiKeys.TELEPHONES] = '/telephones'; +API_URLS[ApiKeys.CASES_SYNC_STATUS] = '/cases/agents/sync-status'; +API_URLS[ApiKeys.CASES_SEND_ID] = '/cases/sync'; +API_URLS[ApiKeys.FETCH_CASES] = '/cases/agents/{agentReferenceId}'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/components/utlis/commonFunctions.ts b/src/components/utlis/commonFunctions.ts index 122ad899..2ffc0e2e 100644 --- a/src/components/utlis/commonFunctions.ts +++ b/src/components/utlis/commonFunctions.ts @@ -319,3 +319,13 @@ export const getMaxByPropFromList = (arr: GenericType, prop: string) => { const max = Math.max(...mappedArray); return arr.find((x: GenericType) => x[prop] == max); }; + + +export const getGoogleMapUrl = (latitude: string | number, longitude: string | number) => { + if (!latitude || !longitude) return; + return `https://www.google.com/maps/search/${latitude},+${longitude}`; +} + +export const isValidAmountEntered = (value: number) => { + return typeof value === 'number' && !isNaN(value); +}; diff --git a/src/components/utlis/firebaseFallbackUtils.ts b/src/components/utlis/firebaseFallbackUtils.ts new file mode 100644 index 00000000..ec1a7942 --- /dev/null +++ b/src/components/utlis/firebaseFallbackUtils.ts @@ -0,0 +1,8 @@ +import { ICaseItem } from '../../screens/allCases/interface'; + +export const getSyncCaseIds = (casesList: ICaseItem[] = []) => { + return casesList.map((caseItem) => ({ + caseId: caseItem.caseReferenceId, + caseViewCreatedAt: caseItem.caseViewCreatedAt, + })); +}; diff --git a/src/constants/Global.ts b/src/constants/Global.ts index 704917cf..9a6c1f1a 100644 --- a/src/constants/Global.ts +++ b/src/constants/Global.ts @@ -1,3 +1,4 @@ +import { setCustomUserId } from 'react-native-clarity'; import { isNullOrUndefined } from '../components/utlis/commonFunctions'; export enum DEVICE_TYPE_ENUM { @@ -21,6 +22,10 @@ interface IGlobalUserData { isImpersonated?: boolean; } +interface IClarityConfiguration { + customUserId?: string; +} + export const setGlobalUserData = (userData: IGlobalUserData) => { const { token, deviceId, agentId, deviceType, isImpersonated } = userData; if (!isNullOrUndefined(token)) GLOBAL.SESSION_TOKEN = `${token}`; @@ -29,3 +34,9 @@ export const setGlobalUserData = (userData: IGlobalUserData) => { if (!isNullOrUndefined(deviceType)) GLOBAL.DEVICE_TYPE = deviceType as DEVICE_TYPE_ENUM; if (!isNullOrUndefined(isImpersonated)) GLOBAL.IS_IMPERSONATED = isImpersonated ?? false; }; + +export const setMsClarityConfig = (clarityConfig: IClarityConfiguration) => { + if (clarityConfig?.customUserId) { + setCustomUserId(clarityConfig.customUserId); + } +}; diff --git a/src/constants/config.js b/src/constants/config.js index 6fa05aeb..5df41c0d 100644 --- a/src/constants/config.js +++ b/src/constants/config.js @@ -12,3 +12,4 @@ export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; +export const MS_CLARITY_PROJECT_ID = ''; diff --git a/src/hooks/useFirestoreUpdates.ts b/src/hooks/useFirestoreUpdates.ts index 9c601701..7fcd7438 100644 --- a/src/hooks/useFirestoreUpdates.ts +++ b/src/hooks/useFirestoreUpdates.ts @@ -167,14 +167,6 @@ const useFirestoreUpdates = () => { .catch((error) => { dispatch(setLoading(false)); logError(error as Error, 'Error in signInUserToFirebase'); - setGlobalUserData({ token: '', agentId: '', deviceId: '' }); - dispatch( - setAuthData({ - sessionDetails: null, - user: null, - isLoggedIn: false, - }) - ); toast({ type: 'error', text1: ToastMessages.FIRESTORE_SIGNIN_FAILED, diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index bb8f7097..1cf7b8cd 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -199,6 +199,18 @@ export const getUpdatedAVCaseDetail = ({ return updatedValue; }; +const getCaseListItem = ( + caseReferenceId: string, + pinRank?: number | null, + caseViewCreatedAt?: number +) => { + return { + caseReferenceId, + pinRank: pinRank || null, + caseViewCreatedAt, + }; +}; + const allCasesSlice = createSlice({ name: 'cases', initialState, @@ -219,7 +231,7 @@ const allCasesSlice = createSlice({ let newVisitCollectionCases: string[] = []; let removedVisitedCasesLoanIds: string[] = []; caseUpdates.forEach(({ updateType, updatedCaseDetail }) => { - const { caseType, caseReferenceId, id, pinRank } = updatedCaseDetail; + const { caseType, caseReferenceId, id, pinRank, caseViewCreatedAt } = updatedCaseDetail; const caseId = caseReferenceId || id; switch (updateType) { @@ -272,10 +284,7 @@ const allCasesSlice = createSlice({ if (pinRank && caseType === CaseAllocationType.COLLECTION_CASE) { newVisitCollectionCases.push(caseId); } - const caseListItem = { - caseReferenceId: caseId, - pinRank: pinRank || null, - }; + const caseListItem = getCaseListItem(caseId, pinRank, caseViewCreatedAt); state.casesList.unshift(caseListItem); let currentTask = null; if (caseType !== CaseAllocationType.COLLECTION_CASE) { @@ -494,6 +503,33 @@ const allCasesSlice = createSlice({ resetNewVisitedCases: (state) => { state.newVisitedCases = []; }, + syncCasesByFallback: (state, action) => { + const { cases = [], deletedCaseIds = [], payloadCreatedAt } = action.payload; + cases.forEach((caseItem: CaseDetail) => { + const { caseViewCreatedAt, caseReferenceId, isSynced, pinRank } = caseItem; + if ( + !state.caseDetails[caseReferenceId] || + (isSynced && caseViewCreatedAt && caseViewCreatedAt < payloadCreatedAt) + ) { + const caseListItem = getCaseListItem(caseReferenceId, pinRank, caseViewCreatedAt); + state.casesList.unshift(caseListItem); + state.caseDetails[caseReferenceId] = { ...caseItem, isSynced: true }; + const { pendingList, completedList, pinnedList } = getCaseListComponents( + state.casesList, + state.caseDetails + ); + state.pendingList = pendingList; + state.completedList = completedList; + state.pinnedList = pinnedList; + } + }); + deletedCaseIds.forEach((caseItem: CaseDetail) => { + const { caseViewCreatedAt, caseReferenceId } = caseItem; + if (caseViewCreatedAt && caseViewCreatedAt < payloadCreatedAt) { + delete state.caseDetails[caseReferenceId]; + } + }); + }, }, }); @@ -513,6 +549,7 @@ export const { updateCaseDetailBeforeApiCall, setVisitPlansUpdating, resetNewVisitedCases, + syncCasesByFallback, } = allCasesSlice.actions; export default allCasesSlice.reducer; diff --git a/src/reducer/metadataSlice.ts b/src/reducer/metadataSlice.ts index 0ccf05b7..71ec5d8d 100644 --- a/src/reducer/metadataSlice.ts +++ b/src/reducer/metadataSlice.ts @@ -7,11 +7,13 @@ export interface UninstallInformation { interface IMetadata { isOnline: boolean; forceUninstall: Record; + isWifiOrCellularOn: boolean; } const initialState = { isOnline: true, forceUninstall: {}, + isWifiOrCellularOn: true, } as IMetadata; const MetadataSlice = createSlice({ @@ -24,9 +26,12 @@ const MetadataSlice = createSlice({ setForceUninstallData: (state, action) => { state.forceUninstall = action.payload; }, + setIsWifiOrCellularOn: (state, action) => { + state.isWifiOrCellularOn = action.payload; + }, }, }); -export const { setIsOnline, setForceUninstallData } = MetadataSlice.actions; +export const { setIsOnline, setForceUninstallData, setIsWifiOrCellularOn } = MetadataSlice.actions; export default MetadataSlice.reducer; diff --git a/src/screens/addressGeolocation/GeolocationItem.tsx b/src/screens/addressGeolocation/GeolocationItem.tsx index 5237e5e9..fb0911a7 100644 --- a/src/screens/addressGeolocation/GeolocationItem.tsx +++ b/src/screens/addressGeolocation/GeolocationItem.tsx @@ -7,7 +7,7 @@ import { BUSINESS_TIME_FORMAT, dateFormat, } from '../../../RN-UI-LIB/src/utlis/dates'; -import { sanitizeString } from '../../components/utlis/commonFunctions'; +import { getGoogleMapUrl, sanitizeString } from '../../components/utlis/commonFunctions'; import { IGeoLocation } from '../../types/addressGeolocation.types'; import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; @@ -30,7 +30,7 @@ const GeolocationItem = ({ geolocationItem, showSeparator = true }: IGeolocation latitude: geolocationItem.latitude, longitude: geolocationItem.longitude, }); - const geolocationUrl = `http://maps.google.com/?q=${geolocationItem?.latitude},${geolocationItem?.longitude}`; + const geolocationUrl = getGoogleMapUrl(geolocationItem?.latitude, geolocationItem?.longitude); if (!geolocationUrl) return; return Linking.openURL(geolocationUrl); diff --git a/src/screens/allCases/Filters.tsx b/src/screens/allCases/Filters.tsx index 9025bf6a..e234672c 100644 --- a/src/screens/allCases/Filters.tsx +++ b/src/screens/allCases/Filters.tsx @@ -45,7 +45,14 @@ const Filters: React.FC = ({ return ( - + } diff --git a/src/screens/allCases/interface.ts b/src/screens/allCases/interface.ts index 53b52b9b..38c205c6 100644 --- a/src/screens/allCases/interface.ts +++ b/src/screens/allCases/interface.ts @@ -112,6 +112,7 @@ export const caseVerdictAndColor = { export interface ICaseItem { type?: CaseTypes; // this is for maintaining frontend pinRank: number | null; + caseViewCreatedAt?: number; updatedAt?: any; allocatedAt?: any; caseReferenceId: string; diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index 7a244738..96d597df 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'; import { RootState } from '../../store/store'; import { getUniqueId, isTablet } from 'react-native-device-info'; import { setDeviceId } from '../../reducer/userSlice'; -import { DEVICE_TYPE_ENUM, setGlobalUserData } from '../../constants/Global'; +import { DEVICE_TYPE_ENUM, setGlobalUserData, setMsClarityConfig } from '../../constants/Global'; import { registerNavigateAndDispatch } from '../../components/utlis/apiHelper'; import ProtectedRouter from './ProtectedRouter'; import useNativeButtons from '../../hooks/useNativeButton'; @@ -38,6 +38,8 @@ const AuthRouter = () => { isImpersonated: user?.isImpersonated ?? false, }); + setMsClarityConfig({ customUserId: user?.user?.phoneNumber || user?.user?.emailId }); + // Sets the dispatch for apiHelper registerNavigateAndDispatch(dispatch); diff --git a/src/screens/caseDetails/feedback/FeedbackDetailImageItem.tsx b/src/screens/caseDetails/feedback/FeedbackDetailImageItem.tsx index c833c0a5..b35953a6 100644 --- a/src/screens/caseDetails/feedback/FeedbackDetailImageItem.tsx +++ b/src/screens/caseDetails/feedback/FeedbackDetailImageItem.tsx @@ -47,7 +47,12 @@ const FeedbackDetailImageItem: React.FC = ({ image }) onPress={handleExpandImage} /> - + { latitude: latitude, longitude: longitude, }); - - if (!latitude || !longitude) return; - const geolocationUrl = `http://maps.google.com/?q=${latitude},${longitude}`; + const geolocationUrl = getGoogleMapUrl(latitude, longitude); + if (!geolocationUrl) return; return Linking.openURL(geolocationUrl); }; diff --git a/src/screens/caseDetails/interface.ts b/src/screens/caseDetails/interface.ts index 6622dcb2..917da295 100644 --- a/src/screens/caseDetails/interface.ts +++ b/src/screens/caseDetails/interface.ts @@ -181,6 +181,7 @@ export interface CaseDetail { addresses?: Address[]; currentAllocationReferenceId: string; customerReferenceId: string; + caseViewCreatedAt?: number; // collection case addressString?: string; currentOutstandingEmi?: number; diff --git a/src/screens/registerPayements/RegisterPayments.tsx b/src/screens/registerPayements/RegisterPayments.tsx index f8cb5546..40d7c103 100644 --- a/src/screens/registerPayements/RegisterPayments.tsx +++ b/src/screens/registerPayements/RegisterPayments.tsx @@ -15,6 +15,7 @@ import { copyToClipboard, getDynamicBottomSheetHeightPercentageFn, getPhoneNumberString, + isValidAmountEntered, } from '../../components/utlis/commonFunctions'; import useIsOnline from '../../hooks/useIsOnline'; import { RootState } from '../../store/store'; @@ -29,6 +30,7 @@ import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import QrCodeModal from './QrCodeModal'; import ModalWrapper from '../../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper'; import OfflineScreen from '../../common/OfflineScreen'; +import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount'; interface IRegisterForm { selectedPhoneNumber: string; @@ -116,10 +118,10 @@ const RegisterPayments: React.FC = ({ route }) => { errorMessage = 'This is a required field'; break; case 'min': - errorMessage = 'Amount should be greater than min amount'; - break; case 'max': - errorMessage = 'Amount should be less than max amount'; + errorMessage = `The entered amount should be between ${formatAmount(1)} and ${formatAmount( + maxAmount + )}`; break; } @@ -217,7 +219,12 @@ const RegisterPayments: React.FC = ({ route }) => { /> )} name="amount" - rules={{ required: true, min: 1, max: maxAmount }} + rules={{ + required: true, + min: 1, + max: maxAmount, + validate: (value) => isValidAmountEntered(Number(value)), + }} />