371 lines
11 KiB
TypeScript
371 lines
11 KiB
TypeScript
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
import {
|
|
Address,
|
|
DOCUMENT_TYPE,
|
|
IDocument,
|
|
IPhoneSources,
|
|
PhoneNumber,
|
|
PhoneNumberSource,
|
|
TDocumentObj,
|
|
} from '../../screens/caseDetails/interface';
|
|
import { getPrefixBase64Image, LocalStorageKeys, MimeType } from '../../common/Constants';
|
|
import NetInfo from '@react-native-community/netinfo';
|
|
import Clipboard from '@react-native-clipboard/clipboard';
|
|
import { useWindowDimensions } from 'react-native';
|
|
import { GlobalDocumentMap } from '../../../App';
|
|
import { GenericType } from '../../common/GenericTypes';
|
|
import { CaseDetail } from '../../screens/caseDetails/interface';
|
|
import { logError } from './errorUtils';
|
|
import packageJson from '../../../package.json';
|
|
import { CaseAllocationType } from '../../screens/allCases/interface';
|
|
import { RouteProp } from '@react-navigation/native';
|
|
import crashlytics from '@react-native-firebase/crashlytics';
|
|
import { deflate } from 'react-native-gzip';
|
|
import { IGeolocationCoordinate } from '../../types/addressGeolocation.types';
|
|
|
|
const fs = ReactNativeBlobUtil.fs;
|
|
|
|
const RespHeaderContentTypeKey = 'Content-Type';
|
|
const DEFAULT_TEXT = '--';
|
|
const MAX_SHEET_HEIGHT_PERCENTAGE = 50;
|
|
|
|
export const RELATIVE_PATH_PREFIX = 'file://';
|
|
|
|
export const decideLoadingState = (textData: string): boolean => {
|
|
if (!textData) {
|
|
return true;
|
|
}
|
|
if (textData.includes('NaN') || textData.includes('undefined')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export const getCommunicationAddress = (address: Address) => {
|
|
if (!address) {
|
|
return '';
|
|
}
|
|
const { houseNumber, lineOne, lineTwo, locality, street, city, state, pinCode } = address;
|
|
return [houseNumber, lineOne, lineTwo, locality, street, city, state, pinCode]
|
|
.filter((element) => element)
|
|
.join(', ');
|
|
};
|
|
|
|
export const storeImageLocallyReturnRelativePath = async (imagePath: string) => {
|
|
if (!imagePath) return;
|
|
|
|
return ReactNativeBlobUtil.config({
|
|
fileCache: true,
|
|
})
|
|
.fetch('GET', imagePath)
|
|
.then((resp) => {
|
|
return RELATIVE_PATH_PREFIX + resp.path();
|
|
});
|
|
};
|
|
|
|
export const getBase64FromUrl = async (imagePath: string) => {
|
|
let contentType: MimeType;
|
|
return ReactNativeBlobUtil.config({
|
|
fileCache: true,
|
|
})
|
|
.fetch('GET', imagePath)
|
|
.then((resp) => {
|
|
if (resp) {
|
|
imagePath = resp.path();
|
|
contentType = resp.respInfo?.headers?.[RespHeaderContentTypeKey];
|
|
return resp.readFile('base64');
|
|
}
|
|
})
|
|
.then((base64) => {
|
|
fs.unlink(imagePath);
|
|
if (contentType) {
|
|
return `${getPrefixBase64Image(contentType)}${base64}`;
|
|
}
|
|
return;
|
|
});
|
|
};
|
|
|
|
export const getNetInfo = async () => {
|
|
const netInfo = await NetInfo.fetch();
|
|
return netInfo;
|
|
};
|
|
|
|
export const copyToClipboard = (text: string) => {
|
|
Clipboard.setString(text);
|
|
};
|
|
|
|
export const clearClipboard = () => {
|
|
Clipboard.setString('');
|
|
};
|
|
|
|
export const fetchCopiedText = async () => {
|
|
const text = await Clipboard.getString();
|
|
return text;
|
|
};
|
|
|
|
export const setAsyncStorageItem = async (key: string, value: any) => {
|
|
try {
|
|
const stringifiedValue = JSON.stringify(value);
|
|
await AsyncStorage.setItem(key, stringifiedValue);
|
|
} catch (err) {
|
|
console.error('JSON stringify errored', err);
|
|
}
|
|
return;
|
|
};
|
|
|
|
export const clearAllAsyncStorage = async () => {
|
|
try {
|
|
await AsyncStorage.clear();
|
|
} catch (err) {
|
|
// todo: add click-stream event
|
|
logError(err as Error, 'Error while cleaning AsyncStorage');
|
|
console.error('Error while cleaning AsyncStorage');
|
|
}
|
|
return;
|
|
};
|
|
|
|
export const sanitizeString = (str = '') => {
|
|
return str?.trim() || DEFAULT_TEXT;
|
|
};
|
|
|
|
export function toTileCase(text: string): string {
|
|
return text?.[0]?.toUpperCase() + text?.substring(1)?.toLowerCase() || text || '';
|
|
}
|
|
|
|
export const memoize = <T = any>(fn: Func<T>) => {
|
|
const cache = new Map();
|
|
const cached = function (this: any, val: T) {
|
|
return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
|
|
};
|
|
cached.cache = cache;
|
|
return cached;
|
|
};
|
|
|
|
export function getAddressString(address?: Address): string {
|
|
if (!address) {
|
|
return 'Address not found';
|
|
}
|
|
const addressFirstLine = [address.pinCode, address.city].filter(Boolean).join(', ');
|
|
const presentationAddressList = [
|
|
addressFirstLine,
|
|
address.lineOne,
|
|
address.lineTwo,
|
|
toTileCase(address.source),
|
|
];
|
|
return presentationAddressList.filter(Boolean).join(' \n ');
|
|
}
|
|
|
|
export function getPhoneNumberString(phoneNumber?: PhoneNumber): string {
|
|
if (!phoneNumber) {
|
|
return 'PhoneNumber not found';
|
|
}
|
|
return `${phoneNumber?.number} ${getPhoneSourceString(phoneNumber?.sources)}`;
|
|
}
|
|
|
|
export const getPhoneSourceString = (sources: IPhoneSources[]) => {
|
|
if (sources?.length) {
|
|
return `(${sources.map((source) => source.text).join(', ')})`;
|
|
}
|
|
return '';
|
|
};
|
|
|
|
export const getPrimaryPhoneNumber = (phoneNumbers: PhoneNumber[] | null) => {
|
|
if (!phoneNumbers?.length) {
|
|
return '';
|
|
}
|
|
const index = phoneNumbers.findIndex((number) => {
|
|
const { sources } = number;
|
|
const isPrimaryNumber = sources.some((source) => source.type === PhoneNumberSource.PRIMARY);
|
|
return isPrimaryNumber;
|
|
});
|
|
if (index !== -1) {
|
|
return phoneNumbers[2]?.number;
|
|
}
|
|
return phoneNumbers[0]?.number;
|
|
};
|
|
|
|
export const getDynamicBottomSheetHeightPercentageFn = (headerOffset = 100, rowHeight = 50) => {
|
|
const SCREEN_HEIGHT = useWindowDimensions().height;
|
|
|
|
return (rowLength = 0) => {
|
|
const dynamicHeight = ((rowLength * rowHeight + headerOffset) / SCREEN_HEIGHT) * 100;
|
|
|
|
return Math.min(dynamicHeight, MAX_SHEET_HEIGHT_PERCENTAGE);
|
|
};
|
|
};
|
|
|
|
export const saveToGlobalDocumentMap = async (caseId: string, data: TDocumentObj) => {
|
|
if (!caseId) return;
|
|
|
|
GlobalDocumentMap[caseId] = data;
|
|
await setAsyncStorageItem(LocalStorageKeys.GLOBAL_DOCUMENT_MAP, GlobalDocumentMap);
|
|
};
|
|
|
|
export const allSettled = (promises: Promise<GenericType>[]) =>
|
|
Promise.all(
|
|
promises.map((p) =>
|
|
p
|
|
.then((value) => ({ status: 'fulfilled', value }))
|
|
.catch((value) => ({ status: 'rejected', value }))
|
|
)
|
|
);
|
|
|
|
export function isNullOrUndefined(val: any): boolean {
|
|
return val === undefined || val === null;
|
|
}
|
|
|
|
export const getLoanAccountNumber = (caseDetail: CaseDetail) => {
|
|
const { loanAccountNumber, loanDetails } = caseDetail ?? {};
|
|
return loanAccountNumber ?? loanDetails?.loanAccountNumber ?? '';
|
|
};
|
|
|
|
export function getAppVersion(): string {
|
|
return packageJson.version;
|
|
}
|
|
|
|
export const getDocumentList = (caseDetails: CaseDetail) => {
|
|
return caseDetails.caseType === CaseAllocationType.ADDRESS_VERIFICATION_CASE
|
|
? caseDetails.customerInfo?.documents
|
|
: caseDetails.documents;
|
|
};
|
|
|
|
export const findDocumentByDocumentType = (
|
|
documentList: IDocument[] = [],
|
|
documentType: DOCUMENT_TYPE
|
|
) => {
|
|
return documentList?.find((documentItem) => documentItem.type === documentType);
|
|
};
|
|
|
|
export const checkS3Url = async (url: string): Promise<boolean> => {
|
|
try {
|
|
const response = await fetch(url, { method: 'HEAD' });
|
|
return response.ok;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export function debounce(func: any, timeout = 1000) {
|
|
let timer: number;
|
|
return (...args: any) => {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
func.apply(this, args);
|
|
}, timeout);
|
|
};
|
|
}
|
|
|
|
function memoizeValue<T extends (...args: any[]) => any>(func: T): T {
|
|
const cache: Record<string, any> = {};
|
|
return function (...args: Parameters<T>): ReturnType<T> {
|
|
const key = JSON.stringify(args);
|
|
if (cache[key]) {
|
|
return cache[key];
|
|
}
|
|
const result = func.apply(this, args);
|
|
cache[key] = result;
|
|
return result;
|
|
} as T;
|
|
}
|
|
|
|
export const getKeyByValue = memoizeValue(function (obj: Record<string, string>, value: string) {
|
|
for (let key in obj) {
|
|
const regex = /\{.*?\}/g;
|
|
const str = obj[key].replace(regex, '');
|
|
if (value.includes(str)) {
|
|
return key;
|
|
}
|
|
}
|
|
});
|
|
|
|
export const getEventNameFromAPIKey = (apiKey: string, isSuccess?: boolean) => {
|
|
return `FA_${apiKey}_${isSuccess ? 'SUCCESS' : 'FAILED'}`;
|
|
};
|
|
|
|
// takes params string like state=123&code=12456
|
|
export const getParamsObject = (paramsString: string) => {
|
|
const paramsArray = paramsString.split('&');
|
|
let pair = null,
|
|
data: Record<string, string> = {};
|
|
paramsArray.forEach((keyValueString) => {
|
|
pair = keyValueString.split('=');
|
|
if (pair?.length === 2) {
|
|
data[pair[0]] = pair[1];
|
|
}
|
|
});
|
|
return data;
|
|
};
|
|
|
|
export const isTimeDifferenceWithinRange = (time: string, rangeInMinutes: number): boolean => {
|
|
const providedTime = new Date(time).getTime();
|
|
const currentTime = new Date().getTime();
|
|
const timeDifferenceInMinutes = Math.abs(Math.round((currentTime - providedTime) / 1000 / 60));
|
|
return timeDifferenceInMinutes <= rangeInMinutes;
|
|
};
|
|
|
|
export const getScreenFocusListenerObj = ({ route }: { route: RouteProp<GenericType> }) => ({
|
|
focus: () => {
|
|
crashlytics().log(JSON.stringify(route));
|
|
},
|
|
});
|
|
|
|
export const convertTo24HourFormat = (time: string) => {
|
|
const [hourMinuteText, period] = time.split(' ');
|
|
let [hour, minutes] = hourMinuteText.split(':');
|
|
if (period === 'PM' && hour !== '12') {
|
|
hour = String(parseInt(hour, 10) + 12);
|
|
} else if (period === 'AM' && hour === '12') {
|
|
hour = '00';
|
|
}
|
|
return `${hour}:${minutes}`;
|
|
};
|
|
|
|
export const getGzipData = async (data: string) => {
|
|
try {
|
|
const compressed = await deflate(data);
|
|
return compressed;
|
|
} catch (_err) {
|
|
logError(_err as Error);
|
|
}
|
|
};
|
|
|
|
export const getMaxByPropFromList = (arr: GenericType, prop: string) => {
|
|
const mappedArray = arr.map((x: GenericType) => x[prop]);
|
|
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);
|
|
};
|
|
|
|
function deg2rad(deg: number) {
|
|
return deg * (Math.PI / 180);
|
|
}
|
|
|
|
export function getDistanceFromLatLonInKm(
|
|
latLong1: IGeolocationCoordinate,
|
|
latLong2: IGeolocationCoordinate
|
|
) {
|
|
if (!latLong1.latitude || !latLong1.longitude || !latLong2.latitude || !latLong2.longitude)
|
|
return NaN;
|
|
|
|
const EARTH_RADIUS = 6371;
|
|
const deltaLat = deg2rad(latLong2.latitude - latLong1.latitude);
|
|
const deltaLon = deg2rad(latLong2.longitude - latLong1.longitude);
|
|
const intermediateResult =
|
|
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
|
Math.cos(deg2rad(latLong1.latitude)) *
|
|
Math.cos(deg2rad(latLong2.latitude)) *
|
|
Math.sin(deltaLon / 2) *
|
|
Math.sin(deltaLon / 2);
|
|
const distance = 2 * Math.atan2(Math.sqrt(intermediateResult), Math.sqrt(1 - intermediateResult));
|
|
return EARTH_RADIUS * distance;
|
|
}
|