Merge pull request #513 from medici/feat/TP-31697

TP-31697 | Feedback image optimisation changes
This commit is contained in:
Himanshu Kansal
2023-06-21 16:40:22 +05:30
committed by GitHub Enterprise
22 changed files with 539 additions and 128 deletions

View File

@@ -22,6 +22,7 @@
"prepare": "husky install"
},
"dependencies": {
"@bam.tech/react-native-image-resizer": "3.0.5",
"@cobo/apm-rum-react-native": "^0.6.0",
"@elastic/apm-rum-core": "^5.17.0",
"@nozbe/watermelondb": "0.24.0",

View File

@@ -12,6 +12,9 @@ import { GenericType } from '../common/GenericTypes';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
import { PageRouteEnum } from '../screens/auth/ProtectedRouter';
import { MILLISECONDS_IN_A_MINUTE, _map } from '../../RN-UI-LIB/src/utlis/common';
import { logError } from '../components/utlis/errorUtils';
import { IDocument, removeDocumentByQuestionKey } from '../reducer/feedbackImagesSlice';
// TODO: Need to add respective interfaces instead of any
interface IUnifiedData {
@@ -21,6 +24,12 @@ interface IUnifiedData {
repayments: Array<any>;
}
export interface IUploadImagePayload {
interactionId: string;
questionKey: string;
originalImageDocumentReferenceId: string;
}
export enum UnifiedCaseDetailsTypes {
ADDRESS_AND_GEOLOCATIONS = 'includeAddressesAndGeoLocations',
FEEDBACKS = 'includeFeedbacks',
@@ -135,3 +144,46 @@ export const getCaseUnifiedData =
dispatch(setAddressLoading({ isLoading: false, loanAccountNumbers }));
});
};
export const uploadImages =
(caseKey: string, documents: Record<string, IDocument>, interactionReferenceId: string) =>
(dispatch: AppDispatch) => {
if (!documents || !interactionReferenceId) {
return;
}
_map(documents, (questionKey, index) => {
const fileDoc = documents[questionKey];
if (!fileDoc) {
return;
}
const { fileUri } = fileDoc;
const formData = new FormData();
formData.append(
'originalImageData',
JSON.stringify({
interactionReferenceId,
questionKey,
})
);
formData.append('image', {
uri: fileUri,
name: `image_${interactionReferenceId}_${index}`,
type: 'image/jpeg',
} as any);
const url = getApiUrl(ApiKeys.UPLOAD_FEEDBACK_IMAGES);
axiosInstance
.put(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
donotHandleError: true,
},
timeout: 5 * MILLISECONDS_IN_A_MINUTE,
})
.then((res) => {
dispatch(removeDocumentByQuestionKey({ caseKey, questionKey }));
})
.catch((err) => {
logError(err as Error, 'Error uploading image to document service');
});
});
};

View File

@@ -7,15 +7,9 @@ import {
resetTodoList,
setLoading,
setVisitPlansUpdating,
updateCaseDetail,
updateSingleCase,
} from '../reducer/allCasesSlice';
import {
CaseAllocationType,
CaseType,
ICaseItem,
IPinnedCasesPayload,
} from '../screens/allCases/interface';
import { CaseAllocationType, ICaseItem, IPinnedCasesPayload } from '../screens/allCases/interface';
import { AppDispatch } from '../store/store';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
@@ -70,12 +64,10 @@ export const postPinnedList =
export const syncCaseDetail =
(
payload: any,
updatedCaseDetail?: any,
callbacks?: {
onSuccessCB?: (data: any, actions?: any) => void;
onSuccessCB?: (data: any, actions?: any, interactionId?: string) => void;
onErrorCB?: (e: Error) => void;
},
nextActions?: any
}
) =>
(dispatch: AppDispatch) => {
const offlineImageIdList = getOfflineImageId(payload);
@@ -88,6 +80,7 @@ export const syncCaseDetail =
})
.then((res) => {
const caseType = payload.caseType;
const interactionId = res.data?.referenceId;
dispatch(
updateSingleCase({
data: res.data,
@@ -102,7 +95,7 @@ export const syncCaseDetail =
text1: ToastMessages.FEEDBACK_SUCCESSFUL,
});
if (callbacks?.onSuccessCB != null && typeof callbacks?.onSuccessCB === 'function') {
callbacks?.onSuccessCB(payload.data.answers, nextActions);
callbacks?.onSuccessCB(payload.data.answers, interactionId);
}
})
.catch((e) => {
@@ -111,14 +104,6 @@ export const syncCaseDetail =
type: 'error',
text1: ToastMessages.FEEDBACK_FAILED,
});
if (updatedCaseDetail) {
dispatch(
updateCaseDetail({
caseId: payload.data.caseReferenceId,
updatedCaseDetail,
})
);
}
}
if (callbacks?.onErrorCB != null && typeof callbacks?.onErrorCB === 'function') {
callbacks?.onErrorCB(e);

View File

@@ -8,9 +8,10 @@ interface IExpandableImage {
imageSrc?: string;
close?: () => void;
title?: string;
fallbackImage?: string;
}
const imageHtml = (imageSrc?: string) => `
const imageHtml = (imageSrc?: string, fallbackImage?: string) => `
<html>
<head>
<title>Image Viewer</title>
@@ -24,19 +25,37 @@ const imageHtml = (imageSrc?: string) => `
justify-content: center;
align-items: center;
}
.parent {
position: relative;
top: 0;
left: 0;
}
.image1 {
position: relative;
top: 0;
left: 0;
width: calc(100vw - 32px);
max-height: calc(100vh - 32px)
}
.image2 {
position: absolute;
top: 0;
left: 0;
width: calc(100vw - 32px);
max-height: calc(100vh - 32px)
}
</style>
</head>
<body>
<div class="page pinch-zoom-parent">
<div class="pinch-zoom">
<img style="width: calc(100vw - 32px); max-height: calc(100vh - 32px)" src=${imageSrc} />
</div>
</div>
<div class="parent">
<img class="image1" src=${fallbackImage} />
<img class="image2" src=${imageSrc} />
</div>
</body>
</html>
`;
const ExpandableImage: React.FC<IExpandableImage> = ({ imageSrc, title, close }) => {
const ExpandableImage: React.FC<IExpandableImage> = ({ imageSrc, fallbackImage, title, close }) => {
return (
<View style={GenericStyles.fill}>
<NavigationHeader onBack={close} title={title} />
@@ -44,7 +63,7 @@ const ExpandableImage: React.FC<IExpandableImage> = ({ imageSrc, title, close })
scalesPageToFit={true}
bounces={false}
scrollEnabled={false}
source={{ html: imageHtml(imageSrc) }}
source={{ html: imageHtml(imageSrc, fallbackImage) }}
/>
</View>
);

View File

@@ -8,7 +8,7 @@ import { GenericObject, GenericType } from '../../common/GenericTypes';
import StarRating from '../../../RN-UI-LIB/src/components/star_rating/StarRating';
import { CaseAllocationType } from '../../screens/allCases/interface';
import { getAddressString, getPhoneNumberString, memoize } from '../utlis/commonFunctions';
import { getImageFromOfflineDb } from '../../services/casePayload.transformer';
import { getBase64ImageFromOfflineDb } from '../../services/casePayload.transformer';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
const RATING_COMPONENT = 'Rating';
@@ -61,7 +61,7 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
React.useEffect(() => {
if (answer?.type === AnswerType.image) {
(async () => {
const data = await getImageFromOfflineDb(answer.answer);
const data = await getBase64ImageFromOfflineDb(answer.answer);
setImageUrl(data);
})();
}

View File

@@ -1,26 +1,37 @@
import React, { useEffect, useState } from 'react';
import { Control, Controller } from 'react-hook-form';
import { ImageBackground, StyleSheet, TouchableOpacity, View } from 'react-native';
import PhotoUpload from '../../../../RN-UI-LIB/src/components/photoUpload/PhotoUpload';
import PhotoUpload, {
IImageDetails,
} from '../../../../RN-UI-LIB/src/components/photoUpload/PhotoUpload';
import { useSelector } from 'react-redux';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import DeleteIcon from '../../../../RN-UI-LIB/src/Icons/DeleteIcon';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
import { useAppSelector } from '../../../hooks';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { RootState } from '../../../store/store';
import { AnswerType } from '../interface';
import ErrorMessage from './ErrorMessage';
import withObservables from '@nozbe/with-observables';
import OfflineImageDAO from '../../../wmDB/dao/OfflineImageDAO';
import { CLICKSTREAM_EVENT_NAMES, PrefixJpegBase64Image } from '../../../common/Constants';
import {
CLICKSTREAM_EVENT_NAMES,
PrefixJpegBase64Image,
getPrefixBase64Image,
} from '../../../common/Constants';
import { addClickstreamEvent } from '../../../services/clickstreamEventService';
import { isQuestionMandatory, validateInput } from '../services/validation.service';
import { CaseAllocationType } from '../../../screens/allCases/interface';
import {
addIntermediateDocument,
deleteIntermediateDocument,
} from '../../../reducer/feedbackImagesSlice';
interface IOfflineImage {
idx: string;
imageData: string;
originalImageUri: string;
}
interface IImageUpload {
@@ -35,6 +46,16 @@ interface IImageUpload {
offlineImages: IOfflineImage[];
}
const getImageUri = (imageList: IOfflineImage[], imageId: string) => {
const image = imageList?.find((image) => image.idx === imageId);
let uri = '';
if (image) {
const { originalImageUri, imageData } = image;
uri = originalImageUri || imageData || '';
}
return uri;
};
const ImageUpload: React.FC<IImageUpload> = (props) => {
const { questionId, error, sectionId, caseId, journeyId, widgetId, questionType } = props;
const [imageId, setImageId] = useState('');
@@ -51,6 +72,8 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
]?.questionContext?.[questionId]?.answer
);
const dispatch = useAppDispatch();
useEffect(() => {
if (dataFromRedux) {
setImageId(dataFromRedux);
@@ -61,8 +84,20 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
return null;
}
const handleChange = async (clickedImage: string | null, onChange: (...event: any[]) => void) => {
const data = `${PrefixJpegBase64Image}${clickedImage}`;
const addOriginalFileUriToDocs = (caseId: string, fileUri: string, questionKey: string) => {
if (!fileUri) {
return;
}
dispatch(addIntermediateDocument({ caseId, fileUri, questionKey }));
};
const handleChange = async (clickedImage: IImageDetails, onChange: (...event: any[]) => void) => {
const { base64, uri = '' } = clickedImage;
addOriginalFileUriToDocs(caseId, uri, questionId);
if (!base64) {
return;
}
const base64Image = `${PrefixJpegBase64Image}${base64}`;
var uniqueId = 'id' + new Date().getTime();
setImageId(uniqueId);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, {
@@ -78,8 +113,14 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
answer: uniqueId,
type: AnswerType.image,
});
await OfflineImageDAO.addImage(data, uniqueId);
await OfflineImageDAO.addImage(base64Image, uri, uniqueId);
};
const handleImageDelete = () => {
setImageId('');
dispatch(deleteIntermediateDocument({ caseId, questionKey: questionId }));
};
// TODO : add the validator back when firestore is fixed.
return (
<View style={[GenericStyles.mt12]}>
@@ -105,10 +146,10 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
style={styles.image}
imageStyle={styles.br8}
source={{
uri: props.offlineImages?.find((image) => image.idx === imageId)?.imageData || '',
uri: getImageUri(props.offlineImages, imageId),
}}
>
<TouchableOpacity onPress={() => setImageId('')} style={styles.deleteButton}>
<TouchableOpacity onPress={handleImageDelete} style={styles.deleteButton}>
<DeleteIcon />
</TouchableOpacity>
</ImageBackground>

View File

@@ -41,7 +41,9 @@ import { CaptureGeolocation } from './services/geoLocation.service';
import Submit from './Submit';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from '../../screens/allCases/constants';
import { uploadImages } from '../../action/caseApiActions';
import { GenericFunctionArgs } from '../../common/GenericTypes';
import { setDocumentInteractionId, setDocumentsToUpload } from '../../reducer/feedbackImagesSlice';
interface IWidget {
route: {
@@ -60,13 +62,27 @@ const Widget: React.FC<IWidget> = (props) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const { params } = props.route;
const { caseId, journey, handleCloseRouting } = params;
const caseType = useAppSelector(
(state) =>
state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE
);
const caseKey = useRef<string>('');
const {
caseType,
templateData,
caseData,
dataToBeValidated,
docsToBeUploaded,
intermediateDocsToBeUploaded,
} = useAppSelector((state) => {
const caseType =
state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE;
return {
caseType,
templateData: state.case.templateData[caseType],
caseData: state.allCases.caseDetails[caseId],
dataToBeValidated: state.case.caseForm?.[caseId]?.[journey],
docsToBeUploaded: state.feedbackImages.docsToBeUploaded,
intermediateDocsToBeUploaded: state.feedbackImages.intermediateDocsToBeUploaded,
};
});
const name = getWidgetNameFromRoute(props.route.name, caseType);
const templateData: FormTemplateV1 = useAppSelector((state) => state.case.templateData[caseType]);
const caseData = useAppSelector((state) => state.allCases.caseDetails[caseId]);
const { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name];
const sectionMap = templateData.sections;
const [error, setError] = useState();
@@ -82,7 +98,6 @@ const Widget: React.FC<IWidget> = (props) => {
setIsJourneyFirstScreen(isFirst);
}, [templateData, name]);
const dataToBeValidated = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]);
const {
control,
handleSubmit,
@@ -130,7 +145,16 @@ const Widget: React.FC<IWidget> = (props) => {
setError(data);
};
const onSuccessfulSubmit = (data: any, nextActions?: any) => {
const uploadOriginalDocs = (interactionId: string) => {
const docs = intermediateDocsToBeUploaded?.[caseId]?.documents;
if (!docs) {
return;
}
dispatch(setDocumentInteractionId({ caseKey: caseKey.current, interactionId }));
dispatch(uploadImages(caseKey.current, docs, interactionId));
};
const onSuccessfulSubmit = (data: any, interactionId: string, nextActions?: any) => {
setIsSubmitting(false);
navigateToScreen(
caseType === CaseAllocationType.COLLECTION_CASE ? 'collectionCaseDetail' : 'caseDetail',
@@ -149,6 +173,9 @@ const Widget: React.FC<IWidget> = (props) => {
nextActions,
})
);
if (interactionId) {
uploadOriginalDocs(interactionId);
}
};
async function fetchLocation(): Promise<Geolocation.GeoCoordinates | undefined> {
@@ -168,6 +195,16 @@ const Widget: React.FC<IWidget> = (props) => {
});
};
const onErrorSubmit = (updatedCaseDetails: any) => {
setIsSubmitting(false);
dispatch(
updateCaseDetail({
caseKey: caseKey.current,
updatedCaseDetails,
})
);
};
const handleSubmitJourney = async (data: any, coords: Geolocation.GeoCoordinates) => {
dispatch(
updateInteraction({
@@ -178,6 +215,8 @@ const Widget: React.FC<IWidget> = (props) => {
})
);
if (caseType === CaseAllocationType.COLLECTION_CASE) {
caseKey.current = `${caseId}_${Date.now()}`;
dispatch(setDocumentsToUpload({ caseId, caseKey: caseKey.current }));
const updatedCase = getUpdatedCollectionCaseDetail({
caseData,
answer: data,
@@ -190,17 +229,16 @@ const Widget: React.FC<IWidget> = (props) => {
const unSyncedCase = getUnSyncedCase(updatedCase);
const transformedPayload = await getTransformedCollectionCaseItem(unSyncedCase);
dispatch(
syncCaseDetail(transformedPayload, updatedCase, {
onSuccessCB: onSuccessfulSubmit,
onErrorCB: () => {
setIsSubmitting(false);
},
syncCaseDetail(transformedPayload, {
onSuccessCB: (apiCaseData, interactionId: string) =>
onSuccessfulSubmit(apiCaseData, interactionId),
onErrorCB: () => onErrorSubmit(updatedCase),
})
);
} else {
dispatch(
updateCaseDetail({
caseId,
caseKey: caseKey.current,
updatedCaseDetail: updatedCase,
})
);
@@ -242,22 +280,16 @@ const Widget: React.FC<IWidget> = (props) => {
const unSyncedCase = getUnSyncedCase(updatedCase);
const transformedPayload = await getTransformedAvCase(unSyncedCase, templateId);
dispatch(
syncCaseDetail(
transformedPayload,
updatedCase,
{
onSuccessCB: onSuccessfulSubmit,
onErrorCB: () => {
setIsSubmitting(false);
},
},
nextActions
)
syncCaseDetail(transformedPayload, {
onSuccessCB: (apiCaseData, interactionId: string) =>
onSuccessfulSubmit(apiCaseData, interactionId, nextActions),
onErrorCB: () => onErrorSubmit(updatedCase),
})
);
} else {
dispatch(
updateCaseDetail({
caseId,
caseKey: caseId,
updatedCaseDetail: updatedCase,
})
);

View File

@@ -49,6 +49,8 @@ export enum ApiKeys {
CASES_SEND_ID = 'CASES_SEND_ID',
FETCH_CASES = 'FETCH_CASES',
GET_FORECLOSURE_AMOUNT = 'GET_FORECLOSURE_AMOUNT',
UPLOAD_FEEDBACK_IMAGES = 'UPLOAD_FEEDBACK_IMAGES',
ORIGINAL_IMAGES = 'ORIGINAL_IMAGES',
GLOBAL_CONFIG = 'GLOBAL_CONFIG',
}
@@ -85,6 +87,7 @@ 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}';
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 = {

View File

@@ -13,12 +13,7 @@ import {
caseVerdict,
ICaseItem,
} from '../screens/allCases/interface';
import {
CaseDetail,
CONTEXT_TASK_STATUSES,
DOCUMENT_TYPE,
IDocument,
} from '../screens/caseDetails/interface';
import { CaseDetail, CONTEXT_TASK_STATUSES, DOCUMENT_TYPE } from '../screens/caseDetails/interface';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { getLoanAccountNumber } from '../components/utlis/commonFunctions';
import { getVisitedWidgetsNodeList } from '../components/form/services/forms.service';
@@ -26,7 +21,6 @@ import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.ty
import { IAvatarUri } from '../action/caseListAction';
export type ICasesMap = { [key: string]: ICaseItem };
interface IAllCasesSlice {
casesList: ICaseItem[];
casesListMap: ICasesMap;
@@ -397,10 +391,8 @@ const allCasesSlice = createSlice({
}
},
updateCaseDetail: (state, action) => {
const { caseId, updatedCaseDetail } = action.payload;
let caseKey: string = caseId;
const { caseKey, updatedCaseDetail } = action.payload;
if (updatedCaseDetail.caseType === CaseAllocationType.COLLECTION_CASE) {
caseKey += '_' + Date.now();
updatedCaseDetail.offlineCaseKey = caseKey;
}
state.caseDetails[caseKey] = updatedCaseDetail;
@@ -480,7 +472,9 @@ const allCasesSlice = createSlice({
...state.caseDetails[computedKey],
isSynced: true,
};
delete state.caseDetails[offlineCaseKey];
if (offlineCaseKey) {
delete state.caseDetails[offlineCaseKey];
}
}
return;
}

View File

@@ -0,0 +1,93 @@
import { createSlice } from '@reduxjs/toolkit';
import { isEmpty } from '../../RN-UI-LIB/src/utlis/common';
export interface IDocument {
fileUri?: string;
}
interface IDocumentDetail {
interactionId: string;
documents: Record<string, IDocument>;
}
interface IImagesSlice {
intermediateDocsToBeUploaded: Record<string, IDocumentDetail>;
docsToBeUploaded: Record<string, IDocumentDetail>;
}
const initialState: IImagesSlice = {
intermediateDocsToBeUploaded: {},
docsToBeUploaded: {},
};
const feedbackImagesSlice = createSlice({
name: 'feedbackImages',
initialState,
reducers: {
addIntermediateDocument: (state, action) => {
const { caseId, questionKey, fileUri } = action.payload;
const doc = {
questionKey,
fileUri,
originalImageDocumentReferenceId: '',
};
if (state.intermediateDocsToBeUploaded?.[caseId]?.documents) {
state.intermediateDocsToBeUploaded[caseId].documents[questionKey] = doc;
} else {
state.intermediateDocsToBeUploaded[caseId] = {
interactionId: '',
documents: {
[questionKey]: doc,
},
};
}
},
deleteIntermediateDocument: (state, action) => {
const { caseId, questionKey } = action.payload;
// delete respective document
if (state.intermediateDocsToBeUploaded?.[caseId]?.documents?.[questionKey]) {
delete state.intermediateDocsToBeUploaded?.[caseId]?.documents?.[questionKey];
}
// delete the whole object if no documents remaining
if (isEmpty(state.intermediateDocsToBeUploaded?.[caseId]?.documents)) {
delete state.intermediateDocsToBeUploaded?.[caseId];
}
},
setDocumentsToUpload: (state, action) => {
const { caseId, caseKey } = action.payload;
if (state.intermediateDocsToBeUploaded?.[caseId]) {
state.docsToBeUploaded[caseKey] = {
...state.intermediateDocsToBeUploaded[caseId],
};
delete state.intermediateDocsToBeUploaded?.[caseId];
}
},
setDocumentInteractionId: (state, action) => {
const { caseKey, interactionId } = action.payload;
if (state.docsToBeUploaded[caseKey]) {
state.docsToBeUploaded[caseKey].interactionId = interactionId;
}
},
removeDocumentByQuestionKey: (state, action) => {
const { caseKey, questionKey } = action.payload;
// delete respective document
if (state.docsToBeUploaded[caseKey]?.documents?.[questionKey]) {
delete state.docsToBeUploaded[caseKey]?.documents?.[questionKey];
}
// delete the whole object if no documents remaining
if (isEmpty(state.docsToBeUploaded?.[caseKey]?.documents)) {
delete state.docsToBeUploaded?.[caseKey];
}
},
},
});
export const {
addIntermediateDocument,
deleteIntermediateDocument,
setDocumentsToUpload,
setDocumentInteractionId,
removeDocumentByQuestionKey,
} = feedbackImagesSlice.actions;
export default feedbackImagesSlice.reducer;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, StyleSheet, View } from 'react-native';
import Accordion from '../../../../RN-UI-LIB/src/components/accordian/Accordian';
import NavigationHeader from '../../../../RN-UI-LIB/src/components/NavigationHeader';
@@ -67,14 +67,7 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
if (!isPastFeedbackOnAddress && currentPage == 1) {
setFeedbackList(feedbackListFromCache);
return;
}
setLoading(true);
setFeedbackList([]);
const fetchFeedbacks = useCallback(() => {
const getPastFeedbackApiFn = isPastFeedbackOnAddress
? getPastFeedbacksOnAddresses
: getPastFeedbacks;
@@ -112,6 +105,16 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
});
}, [currentPage]);
useEffect(() => {
if (!isPastFeedbackOnAddress && currentPage == 1) {
setFeedbackList(feedbackListFromCache);
} else {
setLoading(true);
setFeedbackList([]);
}
fetchFeedbacks();
}, [currentPage]);
useEffect(() => {
if (!isOnline) {
setCurrentPage(1);
@@ -167,7 +170,7 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
onBack={goBack}
/>
<ScrollView
refreshControl={<RefreshControl refreshing={loading} />}
refreshControl={<RefreshControl refreshing={loading} onRefresh={fetchFeedbacks} />}
style={[GenericStyles.ph16, GenericStyles.mt16]}
ref={(x) => setRef(x)}
>

View File

@@ -28,7 +28,7 @@ const FeedbackDetailImageItem: React.FC<IFeedbackDetailImageItem> = ({ image })
};
const questionText = getQuestionText(image);
const originalImageUri = image.metadata?.originalDocumentSignedUri || image.inputText;
return (
<View style={[GenericStyles.mv8]}>
<Text style={[styles.textContainer, styles.questionText]}>{questionText}</Text>
@@ -54,7 +54,8 @@ const FeedbackDetailImageItem: React.FC<IFeedbackDetailImageItem> = ({ image })
animationType="fade"
>
<ExpandableImage
imageSrc={image.inputText}
imageSrc={originalImageUri}
fallbackImage={image.inputText}
title={questionText}
close={handleExpandedImageClose}
/>

View File

@@ -11,6 +11,8 @@ import {
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CaseAllocationType } from '../allCases/interface';
import { CaseDetail } from './interface';
import { uploadImages } from '../../action/caseApiActions';
import { setDocumentInteractionId } from '../../reducer/feedbackImagesSlice';
export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any => {
const caseId = updatedCaseDetail?.id;
@@ -21,7 +23,10 @@ export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any
const interactionsHandler = () => {
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
const allCasesDetails = useAppSelector((state) => state.allCases.caseDetails);
const { allCasesDetails, docsToBeUploaded } = useAppSelector((state) => ({
allCasesDetails: state.allCases.caseDetails,
docsToBeUploaded: state.feedbackImages.docsToBeUploaded,
}));
const { templateId } = useAppSelector(
(state) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE]
);
@@ -32,9 +37,19 @@ const interactionsHandler = () => {
const inProgressCaseIds = useRef<string[]>([]);
const handleSuccessSubmit = (caseKey: string, interactionId: string) => {
const docs = docsToBeUploaded?.[caseKey]?.documents;
if (!docs) {
return;
}
dispatch(setDocumentInteractionId({ caseKey, interactionId }));
dispatch(uploadImages(caseKey, docs, interactionId));
};
useEffect(() => {
if (!allCasesDetails) return;
let notSyncedCases: Array<any> = [];
// [DISCUSS]: isApiCalled not getting true anywhere.
_map(allCasesDetails, (el) => {
if (
allCasesDetails[el]?.isSynced === false &&
@@ -49,19 +64,44 @@ const interactionsHandler = () => {
//TODO: use batched api call
for (const caseItem of notSyncedCases) {
if (isOnline) {
inProgressCaseIds.current.push(caseItem.offlineCaseKey || caseItem.id);
const caseKey = caseItem.offlineCaseKey || caseItem.id;
inProgressCaseIds.current.push(caseKey);
let modifiedCaseItem: any;
if (caseItem?.caseType === CaseAllocationType.COLLECTION_CASE) {
modifiedCaseItem = await getTransformedCollectionCaseItem(caseItem);
} else {
modifiedCaseItem = await getTransformedAvCase(caseItem, templateId);
}
dispatch(syncCaseDetail(modifiedCaseItem));
dispatch(
syncCaseDetail(modifiedCaseItem, {
onSuccessCB: (_, interactionId) => handleSuccessSubmit(caseKey, interactionId),
})
);
}
}
})();
}, [allCasesDetails, isOnline]);
useEffect(() => {
if (!isOnline) {
return;
}
if (!docsToBeUploaded) {
return;
}
_map(docsToBeUploaded, (caseId) => {
const interactionId = docsToBeUploaded[caseId]?.interactionId;
// No interactionId means form is not submitted yet
if (!interactionId) {
return;
}
const docs = docsToBeUploaded[caseId]?.documents;
if (docs) {
dispatch(uploadImages(caseId, docs, interactionId));
}
});
}, [isOnline]);
return null;
};

View File

@@ -64,7 +64,7 @@ export const extractQuestionContext = async (answer: Answer): Promise<IQuestionC
}
if (answer.type === AnswerType.image && answer.answer?.length) {
const offlineImageId = answer.answer;
const data = await getImageFromOfflineDb(offlineImageId);
const data = await getBase64ImageFromOfflineDb(offlineImageId);
answer = {
...answer,
answer: data,
@@ -81,11 +81,16 @@ export const extractQuestionContext = async (answer: Answer): Promise<IQuestionC
return questionContexts;
};
export const getImageFromOfflineDb = async (imageId: string) => {
export const getBase64ImageFromOfflineDb = async (imageId: string) => {
let imageList = await OfflineImageDAO.getImage(imageId);
return imageList?.[0]?.imageData;
};
export const getOriginalImageFromOfflineDb = async (imageId: string) => {
let imageList = await OfflineImageDAO.getImage(imageId);
return imageList?.[0]?.originalImageUri;
};
export interface IGetTransformedCaseItem extends CaseDetail {
answer: any;
caseId: string;

View File

@@ -27,6 +27,7 @@ import feedbackHistorySlice from '../reducer/feedbackHistorySlice';
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({
@@ -45,6 +46,7 @@ const rootReducer = combineReducers({
notifications: notificationsSlice,
metadata: MetadataSlice,
foregroundService: foregroundServiceSlice,
feedbackImages: feedbackImagesSlice,
config: configSlice,
});
@@ -63,6 +65,7 @@ const persistConfig = {
'repayments',
'feedbackHistory',
'address',
'feedbackImages',
'config',
],
blackList: ['case', 'filters'],

View File

@@ -16,6 +16,11 @@ export enum OPTION_TAG {
IMAGE_UPLOAD = 'IMAGE_UPLOAD',
}
interface IImageMetadata {
image_created_at: string;
originalDocumentSignedUri: string;
}
export interface IAnswerView {
interactionId?: number;
referenceId?: string;
@@ -30,6 +35,7 @@ export interface IAnswerView {
inputDate?: string;
inputText?: string;
questionTag?: OPTION_TAG;
metadata?: IImageMetadata;
}
export enum FEEDBACK_TYPE {

View File

@@ -4,6 +4,6 @@ export enum TableName {
CLICKSTREAM_EVENTS = 'clickstream_events',
}
export const DB_VERSION = 4;
export const DB_VERSION = 5;
export const DB_NAME = 'AVAPP';

View File

@@ -6,12 +6,13 @@ const offlineImage = database.get(TableName.OFFLINE_IMAGES);
export default {
observeOfflineImage: () => offlineImage.query().observe(),
addImage: async (imageData: string, imageIdx: string) => {
addImage: async (imageData: string, originalImageUri: string, imageIdx: string) => {
try {
return await database.action(async () => {
return await offlineImage.create((image: any) => {
image.idx = imageIdx;
image.imageData = imageData;
image.originalImageUri = originalImageUri;
});
});
} catch (e) {

View File

@@ -7,6 +7,7 @@ export default class OfflineImage extends Model {
@field('idx') idx!: string;
@field('image_data') imageData!: string;
@field('original_image_uri') originalImageUri!: string;
@readonly @date('created_at') createdAt!: any;
@readonly @date('updated_at') updatedAt!: any;
}

View File

@@ -9,6 +9,7 @@ export default appSchema({
columns: [
{ name: 'idx', type: 'string' },
{ name: 'image_data', type: 'string' },
{ name: 'original_image_uri', type: 'string' },
],
}),
tableSchema({

196
yarn.lock
View File

@@ -957,6 +957,11 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@bam.tech/react-native-image-resizer@3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz#6661ba020de156268f73bdc92fbb93ef86f88a13"
integrity sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ==
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1729,48 +1734,123 @@
redux-thunk "^2.4.2"
reselect "^4.1.7"
"@sentry/browser@7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.29.0.tgz#eb162b50adec33ac49ecd3dc930bdffbfda8098e"
integrity sha512-Af+dIcntaw405Wt7myDOMGDxiszfy4aBdshrEKYbGgcfHjgXBIdF3iKlNatvl6nrOm+IOVuKgSpCLOr2hiCwzw==
"@sentry-internal/tracing@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.52.0.tgz#c4e0750ad0c3949c5bb4b59cbb708b0fef274080"
integrity sha512-o1YPcRGtC9tjeFCvWRJsbgK94zpExhzfxWaldAKvi3PuWEmPeewSdO/Q5pBIY1QonvSI+Q3gysLRcVlLYHhO5A==
dependencies:
"@sentry/core" "7.29.0"
"@sentry/replay" "7.29.0"
"@sentry/types" "7.29.0"
"@sentry/utils" "7.29.0"
"@sentry/core" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
tslib "^1.9.3"
"@sentry/core@7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.29.0.tgz#bc4b54d56cf7652598d4430cf43ea97cc069f6fe"
integrity sha512-+e9aIp2ljtT4EJq3901z6TfEVEeqZd5cWzbKEuQzPn2UO6If9+Utd7kY2Y31eQYb4QnJgZfiIEz1HonuYY6zqQ==
"@sentry/browser@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.52.0.tgz#55d266c89ed668389ff687e5cc885c27016ea85c"
integrity sha512-Sib0T24cQCqqqAhg+nZdfI7qNYGE03jiM3RbY7yG5UoycdnJzWEwrBVSzRTgg3Uya9TRTEGJ+d9vxPIU5TL7TA==
dependencies:
"@sentry/types" "7.29.0"
"@sentry/utils" "7.29.0"
"@sentry-internal/tracing" "7.52.0"
"@sentry/core" "7.52.0"
"@sentry/replay" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
tslib "^1.9.3"
"@sentry/replay@7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.29.0.tgz#75d5bb9df39e0a31994be245032c9998af62a304"
integrity sha512-Gw7HgviJQu6pX5RFQGVY38Av4qFn9otrZdwSSl/QK5hIyg6yhlh5h7U0ydZkrYYGiW6Z6SYYRpEWCJc/Wbh+ZQ==
"@sentry/cli@2.17.5":
version "2.17.5"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.17.5.tgz#d41e24893a843bcd41e14274044a7ddea9332824"
integrity sha512-0tXjLDpaKB46851EMJ6NbP0o9/gdEaDSLAyjEtXxlVO6+RyhUj6x6jDwn0vis8n/7q0AvbIjAcJrot+TbZP+WQ==
dependencies:
"@sentry/core" "7.29.0"
"@sentry/types" "7.29.0"
"@sentry/utils" "7.29.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.7"
progress "^2.0.3"
proxy-from-env "^1.1.0"
which "^2.0.2"
"@sentry/types@7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.29.0.tgz#ed829b6014ee19049035fec6af2b4fea44ff28b8"
integrity sha512-DmoEpoqHPty3VxqubS/5gxarwebHRlcBd/yuno+PS3xy++/i9YPjOWLZhU2jYs1cW68M9R6CcCOiC9f2ckJjdw==
"@sentry/utils@7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.29.0.tgz#cbf8f87dd851b0fdc7870db9c68014c321c3bab8"
integrity sha512-ICcBwTiBGK8NQA8H2BJo0JcMN6yCeKLqNKNMVampRgS6wSfSk1edvcTdhRkW3bSktIGrIPZrKskBHyMwDGF2XQ==
"@sentry/core@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.52.0.tgz#6c820ca48fe2f06bfd6b290044c96de2375f2ad4"
integrity sha512-BWdG6vCMeUeMhF4ILpxXTmw70JJvT1MGJcnv09oSupWHTmqy6I19YP6YcEyFuBL4jXPN51eCl7luIdLGJrPbOg==
dependencies:
"@sentry/types" "7.29.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
tslib "^1.9.3"
"@sentry/hub@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.52.0.tgz#ffc087d58c745d57108862faa0f701b15503dcc2"
integrity sha512-w3d8Pmp3Fx2zbbjz6hAeIbsFEkLyrUs9YTGG2y8oCoTlAtGK+AjdG+Z0H/clAZONflD/je2EmFHCI0EuXE9tEw==
dependencies:
"@sentry/core" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
tslib "^1.9.3"
"@sentry/integrations@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.52.0.tgz#632aa5e54bdfdab910a24057c2072634a2670409"
integrity sha512-tqxYzgc71XdFD8MTCsVMCPef08lPY9jULE5Zi7TzjyV2AItDRJPkixG0qjwjOGwCtN/6KKz0lGPGYU8ZDxvsbg==
dependencies:
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/react-native@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@sentry/react-native/-/react-native-5.5.0.tgz#b1283f68465b1772ad6059ebba149673cef33f2d"
integrity sha512-xrES+OAIu3HFhoQSuJjd16Hh02/mByuNoKUjF7e4WDGIiTew3aqlqeLjU7x4npmg5Vbt+ND5jR12u/NmdfArwg==
dependencies:
"@sentry/browser" "7.52.0"
"@sentry/cli" "2.17.5"
"@sentry/core" "7.52.0"
"@sentry/hub" "7.52.0"
"@sentry/integrations" "7.52.0"
"@sentry/react" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
"@sentry/react@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.52.0.tgz#d12d270ec82dea0474e69deb9112181affe7c524"
integrity sha512-VQxquyFFlvB81k7UER7tTJxjzbczNI2jqsw6nN1TVDrAIDt8/hT2x7m/M0FlWc88roBKuaMmbvzfNGWaL9abyQ==
dependencies:
"@sentry/browser" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/replay@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.52.0.tgz#4d78e88282d2c1044ea4b648a68d1b22173e810d"
integrity sha512-RRPALjDST2s7MHiMcUJ7Wo4WW7EWfUDYSG0LuhMT8DNc+ZsxQoFsLYX/yz8b3f0IUSr7xKBXP+aPeIy3jDAS2g==
dependencies:
"@sentry/core" "7.52.0"
"@sentry/types" "7.52.0"
"@sentry/utils" "7.52.0"
"@sentry/types@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.52.0.tgz#b7d5372f17355e3991cbe818ad567f3fe277cc6b"
integrity sha512-XnEWpS6P6UdP1FqbmeqhI96Iowqd2jM5R7zJ97txTdAd5NmdHHH0pODTR9NiQViA1WlsXDut7ZLxgPzC9vIcMA==
"@sentry/utils@7.52.0":
version "7.52.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.52.0.tgz#cacc36d905036ba7084c14965e964fc44239d7f0"
integrity sha512-X1NHYuqW0qpZfP731YcVe+cn36wJdAeBHPYPIkXCl4o4GePCJfH/CM/+9V9cZykNjyLrs2Xy/TavSAHNCj8j7w==
dependencies:
"@sentry/types" "7.52.0"
tslib "^1.9.3"
"@shopify/flash-list@1.4.3":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@shopify/flash-list/-/flash-list-1.4.3.tgz#b7a4fe03d64f3c5ce9646859b49b9d95307f203d"
integrity sha512-jtIReAbwWzYBV0dQ6Io9wBX+pD0C4qQFMrb5/fkEvX8PYDgBl5KRYvpfr9WLLj8CV2Jsn1X0mYOsB+ysWrI/8g==
dependencies:
recyclerlistview "4.2.0"
tslib "2.4.0"
"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
@@ -4858,6 +4938,11 @@ image-size@^0.6.0:
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.3.tgz#e7e5c65bb534bd7cdcedd6cb5166272a85f75fb2"
integrity sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immer@^9.0.16:
version "9.0.16"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198"
@@ -5977,6 +6062,13 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
dependencies:
immediate "~3.0.5"
lilconfig@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@@ -6020,6 +6112,13 @@ listr2@^5.0.7:
through "^2.3.8"
wrap-ansi "^7.0.0"
localforage@^1.8.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
dependencies:
lie "3.1.1"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -6062,7 +6161,7 @@ lodash.compact@^3.0.1:
resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5"
integrity sha512-2ozeiPi+5eBXW1CLtzjk8XQFhQOEMwwfxblqeq6EGyTxZJ1bPATqilY0e6g2SLQpP4KuMeuioBhEnWz5Pr7ICQ==
lodash.debounce@^4.0.8:
lodash.debounce@4.0.8, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
@@ -6825,6 +6924,13 @@ node-fetch@^2.2.0, node-fetch@^2.6.0:
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.7:
version "2.6.11"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25"
integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==
dependencies:
whatwg-url "^5.0.0"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -7387,6 +7493,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-polyfill@^8.1.3:
version "8.3.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
@@ -7407,7 +7518,7 @@ prompts@^2.0.1, prompts@^2.4.0:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.7.2, prop-types@^15.8.0, prop-types@^15.8.1:
prop-types@15.8.1, prop-types@^15.7.2, prop-types@^15.8.0, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7872,6 +7983,15 @@ recursive-fs@^2.1.0:
resolved "https://registry.yarnpkg.com/recursive-fs/-/recursive-fs-2.1.0.tgz#1e20cf7836b292ed81208c4817550a58ad0e15ff"
integrity sha512-oed3YruYsD52Mi16s/07eYblQOLi5dTtxpIJNdfCEJ7S5v8dDgVcycar0pRWf4IBuPMIkoctC8RTqGJzIKMNAQ==
recyclerlistview@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-4.2.0.tgz#a140149aaa470c9787a1426452651934240d69ef"
integrity sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==
dependencies:
lodash.debounce "4.0.8"
prop-types "15.8.1"
ts-object-utils "0.0.5"
redux-persist@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
@@ -9004,6 +9124,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
ts-object-utils@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/ts-object-utils/-/ts-object-utils-0.0.5.tgz#95361cdecd7e52167cfc5e634c76345e90a26077"
integrity sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==
tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
@@ -9014,6 +9139,11 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
tslib@^1.8.1, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"