TP-31697 | Optimized feedback image changes

This commit is contained in:
Aman Chaturvedi
2023-06-16 12:01:44 +05:30
parent 619a20296d
commit 2276681584
20 changed files with 433 additions and 371 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

@@ -1,90 +0,0 @@
import axios from 'axios';
import React from 'react';
import { View, Button, Image } from 'react-native';
import ImagePicker, { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker';
class ImageUploader extends React.Component {
state = {
image: null,
};
selectImage = () => {
const options: ImageLibraryOptions = {
mediaType: 'photo',
quality: 1,
};
launchImageLibrary(options, (response) => {
if (!response.didCancel) {
console.log('response:', response);
this.setState({ image: response.assets?.[0] });
}
});
};
uploadImage = async () => {
const { image } = this.state;
if (image) {
const formData = new FormData();
console.log('image:', image);
// try {
// const response = await fetch(image.uri);
// const blobData = await response.blob();
// formData.append('avatar', blobData, 'avatar.png');
// const result = await axios.post('https://0ac3-115-111-223-26.in.ngrok.io/upload', formData, {
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
// });
// console.log('Request successful', result.data);
// } catch (error) {
// console.error('Request failed', error);
// }
console.log('image', image);
formData.append('file[]', {
uri: image.uri,
name: 'image1.png',
type: image.type,
});
formData.append('file[]', {
uri: image.uri,
name: 'image2.png',
type: image.type,
});
formData.append('file[]', {
uri: image.uri,
name: 'image3.png',
type: image.type,
});
const response = await axios.post(
'https://8ee6-115-111-223-26.in.ngrok.io/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
}
};
render() {
const { image } = this.state;
return (
<View>
<Button title="Select Image" onPress={this.selectImage} />
{image && <Image source={{ uri: image.uri }} style={{ width: 200, height: 200 }} />}
<Button title="Upload Image" onPress={this.uploadImage} />
</View>
);
}
}
export default ImageUploader;

View File

@@ -12,9 +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 { IDocument, removeDocuments, setDocumentReferenceId } from '../reducer/allCasesSlice';
import { _map } from '../../RN-UI-LIB/src/utlis/common';
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 {
@@ -145,58 +145,42 @@ export const getCaseUnifiedData =
});
};
const uploadFeedbackImages =
(caseId: string, payload: IUploadImagePayload[]) => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.UPLOAD_FEEDBACK_IMAGES);
axiosInstance
.post(url, payload)
.then(() => {
dispatch(removeDocuments({ caseId, docs: payload }));
})
.catch((err) => {
// Add to retry queue
});
};
export const uploadImages =
(caseId: string, documents: Record<string, IDocument>, interactionId: string) =>
(caseKey: string, documents: Record<string, IDocument>, interactionReferenceId: string) =>
(dispatch: AppDispatch) => {
_map(documents, (questionKey) => {
if (!documents) {
return;
}
const fileDoc = documents[questionKey];
if (!interactionId || !fileDoc) {
if (!interactionReferenceId || !fileDoc) {
return;
}
const { fileUri } = fileDoc;
const formData = new FormData();
formData.append('images', {
formData.append(
'originalImageData',
JSON.stringify({
interactionReferenceId,
questionKey,
})
);
formData.append('image', {
uri: fileUri,
name: `image_${caseId}_${new Date().getTime()}`,
name: `image_${interactionReferenceId}`,
type: 'image/jpeg',
} as any);
const url = getApiUrl(ApiKeys.UPLOAD_IMAGES);
const url = getApiUrl(ApiKeys.UPLOAD_FEEDBACK_IMAGES);
axiosInstance
.post(url, formData, {
.put(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
donotHandleError: true,
},
timeout: 5 * MILLISECONDS_IN_A_MINUTE,
})
.then((res) => {
const originalImageDocumentReferenceId = res?.data?.referenceId;
dispatch(
setDocumentReferenceId({ caseId, questionKey, originalImageDocumentReferenceId })
);
dispatch(
uploadFeedbackImages(caseId, [
{
interactionId,
questionKey,
originalImageDocumentReferenceId,
},
])
);
dispatch(removeDocumentByQuestionKey({ caseKey, questionKey }));
})
.catch((err) => {
logError(err as Error, 'Error uploading image to document service');
@@ -204,25 +188,8 @@ export const uploadImages =
});
};
const getDocsWithAndWithoutReferenceId = (docs: Record<string, IDocument>) => {
const docsWithReferenceId: IDocument[] = [];
const docsWithoutReferenceId: IDocument[] = [];
_map(docs, (questionKey: string) => {
const docRefId = docs?.[questionKey]?.originalImageDocumentReferenceId;
if (docRefId) {
docsWithReferenceId.push(docs?.[questionKey]);
} else {
docsWithoutReferenceId.push(docs?.[questionKey]);
}
});
return {
docsWithReferenceId,
docsWithoutReferenceId,
};
};
export const uploadUnsyncedImages =
(caseId: string, docs: Record<string, IDocument>) => (dispatch: AppDispatch) => {
const { docsWithReferenceId, docsWithoutReferenceId } = getDocsWithAndWithoutReferenceId(docs);
// dispatch(uploadFeedbackImages(caseId, docs))
(caseId: string, interactionId: string, docs: Record<string, IDocument>) =>
(dispatch: AppDispatch) => {
dispatch(uploadImages(caseId, docs, interactionId));
};

View File

@@ -7,7 +7,6 @@ import {
resetTodoList,
setLoading,
setVisitPlansUpdating,
updateUnsyncedCaseDetail,
updateSingleCase,
} from '../reducer/allCasesSlice';
import {
@@ -71,12 +70,10 @@ export const postPinnedList =
export const syncCaseDetail =
(
payload: any,
updatedCaseDetail?: any,
callbacks?: {
onSuccessCB?: (data: any, actions?: any, interactionId?: string) => void;
onErrorCB?: (e: Error) => void;
},
nextActions?: any
}
) =>
(dispatch: AppDispatch) => {
const offlineImageIdList = getOfflineImageId(payload);
@@ -104,7 +101,7 @@ export const syncCaseDetail =
text1: ToastMessages.FEEDBACK_SUCCESSFUL,
});
if (callbacks?.onSuccessCB != null && typeof callbacks?.onSuccessCB === 'function') {
callbacks?.onSuccessCB(payload.data.answers, nextActions, interactionId);
callbacks?.onSuccessCB(payload.data.answers, interactionId);
}
})
.catch((e) => {
@@ -113,14 +110,6 @@ export const syncCaseDetail =
type: 'error',
text1: ToastMessages.FEEDBACK_FAILED,
});
if (updatedCaseDetail && !updatedCaseDetail.offlineCaseKey) {
dispatch(
updateUnsyncedCaseDetail({
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

@@ -15,11 +15,18 @@ 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 { addDocumentToUpload } from '../../../reducer/allCasesSlice';
import {
addIntermediateDocument,
deleteIntermediateDocument,
} from '../../../reducer/feedbackImagesSlice';
interface IOfflineImage {
idx: string;
@@ -70,7 +77,7 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
if (!fileUri) {
return;
}
dispatch(addDocumentToUpload({ caseId, fileUri, questionKey }));
dispatch(addIntermediateDocument({ caseId, fileUri, questionKey }));
};
const handleChange = async (clickedImage: IImageDetails, onChange: (...event: any[]) => void) => {
@@ -98,6 +105,11 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
await OfflineImageDAO.addImage(base64Image, uniqueId);
};
const handleImageDelete = () => {
setImageId('');
dispatch(deleteIntermediateDocument({ caseId, questionKey: questionId }));
};
// TODO : add the validator back when firestore is fixed.
return (
<View style={[GenericStyles.mt12]}>
@@ -126,7 +138,7 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
uri: props.offlineImages?.find((image) => image.idx === imageId)?.imageData || '',
}}
>
<TouchableOpacity onPress={() => setImageId('')} style={styles.deleteButton}>
<TouchableOpacity onPress={handleImageDelete} style={styles.deleteButton}>
<DeleteIcon />
</TouchableOpacity>
</ImageBackground>

View File

@@ -17,8 +17,7 @@ import useIsOnline from '../../hooks/useIsOnline';
import {
getUpdatedAVCaseDetail,
getUpdatedCollectionCaseDetail,
setDocumentInteractionId,
updateUnsyncedCaseDetail,
updateCaseDetail,
} from '../../reducer/allCasesSlice';
import { deleteInteraction, deleteJourney, updateInteraction } from '../../reducer/caseReducer';
import { CaseAllocationType } from '../../screens/allCases/interface';
@@ -43,8 +42,8 @@ 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 { isEmpty } from '../../../RN-UI-LIB/src/utlis/common';
import { GenericFunctionArgs } from '../../common/GenericTypes';
import { setDocumentInteractionId, setDocumentsToUpload } from '../../reducer/feedbackImagesSlice';
interface IWidget {
route: {
@@ -63,20 +62,26 @@ const Widget: React.FC<IWidget> = (props) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const { params } = props.route;
const { caseId, journey, handleCloseRouting } = params;
const { caseType, templateData, caseData, dataToBeValidated, docsToBeUploaded } = 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.allCases.docsToBeUploaded?.[caseId]?.documents,
};
}
);
let 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 { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name];
const sectionMap = templateData.sections;
@@ -140,19 +145,16 @@ const Widget: React.FC<IWidget> = (props) => {
setError(data);
};
// Upload original doc to document service => get docRefId
const uploadOriginalDocs = (interactionId: string) => {
if (isEmpty(docsToBeUploaded)) {
const docs = intermediateDocsToBeUploaded?.[caseId]?.documents;
if (!docs) {
return;
}
dispatch(setDocumentInteractionId({ caseId, interactionId }));
if (isOnline) {
dispatch(uploadImages(caseId, docsToBeUploaded, interactionId));
} else {
}
dispatch(setDocumentInteractionId({ caseKey: caseKey.current, interactionId }));
dispatch(uploadImages(caseKey.current, docs, interactionId));
};
const onSuccessfulSubmit = (data: any, nextActions?: any, interactionId?: string) => {
const onSuccessfulSubmit = (data: any, interactionId: string, nextActions?: any) => {
setIsSubmitting(false);
navigateToScreen(
caseType === CaseAllocationType.COLLECTION_CASE ? 'collectionCaseDetail' : 'caseDetail',
@@ -193,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({
@@ -203,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,
@@ -215,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(
updateUnsyncedCaseDetail({
caseId,
updateCaseDetail({
caseKey: caseKey.current,
updatedCaseDetail: updatedCase,
})
);
@@ -267,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(
updateUnsyncedCaseDetail({
caseId,
updateCaseDetail({
caseKey: caseId,
updatedCaseDetail: updatedCase,
})
);

View File

@@ -49,8 +49,8 @@ export enum ApiKeys {
CASES_SEND_ID = 'CASES_SEND_ID',
FETCH_CASES = 'FETCH_CASES',
GET_FORECLOSURE_AMOUNT = 'GET_FORECLOSURE_AMOUNT',
UPLOAD_IMAGES = 'UPLOAD_IMAGES',
UPLOAD_FEEDBACK_IMAGES = 'UPLOAD_FEEDBACK_IMAGES',
ORIGINAL_IMAGES = 'ORIGINAL_IMAGES',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -86,8 +86,8 @@ 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_IMAGES] = '/cases/images';
API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/cases/feedback/original-images';
API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/feedback/persist-original-images';
API_URLS[ApiKeys.ORIGINAL_IMAGES] = '/feedback/original-images';
export const API_STATUS_CODE = {
OK: 200,

View File

@@ -1,28 +0,0 @@
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// const storage = multer.diskStorage({
// destination: 'uploads/',
// filename: (req, file, cb) => {
// cb(null, file.originalname);
// },
// });
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.array('file[]'), (req, res) => {
const files = req.files;
console.log('request.file:::', files);
// if (!req.files) {
// res.status(400).json({ error: 'No image file provided' });
// } else {
// const imagePath = req.files[0].path;
// res.json({ message: 'Image uploaded successfully', imagePath });
// }
res.send('Files uploaded successfully');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});

View File

@@ -1,6 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
import { toast } from '../../RN-UI-LIB/src/components/toast';
import { _map, isEmpty } from '../../RN-UI-LIB/src/utlis/common';
import { _map } from '../../RN-UI-LIB/src/utlis/common';
import { findDocumentByDocumentType } from '../components/utlis/commonFunctions';
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes } from '../common/Constants';
import { getCurrentScreen, navigateToScreen } from '../components/utlis/navigationUtlis';
@@ -18,21 +18,9 @@ import { addClickstreamEvent } from '../services/clickstreamEventService';
import { getLoanAccountNumber } from '../components/utlis/commonFunctions';
import { getVisitedWidgetsNodeList } from '../components/form/services/forms.service';
import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.types';
import { IUploadImagePayload } from '../action/caseApiActions';
import { IAvatarUri } from '../action/caseListAction';
export type ICasesMap = { [key: string]: ICaseItem };
export interface IDocument {
questionKey: string;
originalImageDocumentReferenceId: string;
fileUri?: string;
}
interface IDocumentDetail {
interactionId: string;
documents: Record<string, IDocument>;
}
interface IAllCasesSlice {
casesList: ICaseItem[];
casesListMap: ICasesMap;
@@ -47,8 +35,6 @@ interface IAllCasesSlice {
newlyPinnedCases: number;
completedCases: number;
caseDetails: Record<string, CaseDetail>;
unsyncedCaseDetail: Record<string, CaseDetail>;
docsToBeUploaded: Record<string, IDocumentDetail>;
searchQuery: string;
isOnboarded: boolean;
visitPlansUpdating: boolean;
@@ -72,8 +58,6 @@ const initialState: IAllCasesSlice = {
newlyPinnedCases: 0,
completedCases: 0,
caseDetails: {},
unsyncedCaseDetail: {},
docsToBeUploaded: {},
searchQuery: '',
isOnboarded: false,
visitPlansUpdating: false,
@@ -406,14 +390,12 @@ const allCasesSlice = createSlice({
}
}
},
updateUnsyncedCaseDetail: (state, action) => {
const { caseId, updatedCaseDetail } = action.payload;
let caseKey: string = caseId;
updateCaseDetail: (state, action) => {
const { caseKey, updatedCaseDetail } = action.payload;
if (updatedCaseDetail.caseType === CaseAllocationType.COLLECTION_CASE) {
caseKey += '_' + Date.now();
updatedCaseDetail.offlineCaseKey = caseKey;
}
state.unsyncedCaseDetail[caseKey] = updatedCaseDetail;
state.caseDetails[caseKey] = updatedCaseDetail;
},
updateSingleCase: (state, action) => {
const { data, id, caseType, offlineCaseKey } = action.payload;
@@ -425,7 +407,7 @@ const allCasesSlice = createSlice({
isSynced: true,
};
if (offlineCaseKey) {
delete state.unsyncedCaseDetail[offlineCaseKey];
delete state.caseDetails[offlineCaseKey];
}
}
return;
@@ -583,55 +565,6 @@ const allCasesSlice = createSlice({
}
});
},
addDocumentToUpload: (state, action) => {
const { caseId, questionKey, fileUri } = action.payload;
const doc = {
questionKey,
fileUri,
originalImageDocumentReferenceId: '',
};
if (state.docsToBeUploaded?.[caseId]) {
const newDocsMap = { ...state.docsToBeUploaded[caseId].documents, doc };
state.docsToBeUploaded[caseId].documents = newDocsMap;
} else {
state.docsToBeUploaded[caseId] = {
interactionId: '',
documents: {
[questionKey]: doc,
},
};
}
},
removeDocuments: (state, action) => {
const { docs, caseId } = action.payload;
docs?.forEach((doc: IUploadImagePayload) => {
const { questionKey } = doc;
// delete respective document
if (state.docsToBeUploaded?.[caseId]?.documents?.[questionKey]) {
delete state.docsToBeUploaded?.[caseId]?.documents?.[questionKey];
}
// delete the whole object if no documents remaining
if (isEmpty(state.docsToBeUploaded?.[caseId]?.documents)) {
delete state.docsToBeUploaded?.[caseId];
}
});
},
setDocumentInteractionId: (state, action) => {
const { caseId, interactionId } = action.payload;
if (state.docsToBeUploaded[caseId]) {
state.docsToBeUploaded[caseId].interactionId = interactionId;
}
},
setDocumentReferenceId: (state, action) => {
const { caseId, questionKey, originalImageDocumentReferenceId } = action.payload;
if (state.docsToBeUploaded[caseId]) {
const doc = state.docsToBeUploaded[caseId].documents[questionKey];
state.docsToBeUploaded[caseId].documents[questionKey] = {
...doc,
originalImageDocumentReferenceId,
};
}
},
setCasesImageUri: (state, action) => {
const imageUris: IAvatarUri[] = action.payload;
imageUris.forEach(({ caseId, imageUri }) => {
@@ -651,7 +584,7 @@ export const {
deleteIntermediateTodoListItem,
setSelectedTodoListMap,
resetSelectedTodoList,
updateUnsyncedCaseDetail,
updateCaseDetail,
updateSingleCase,
updateCaseDetailsFirestore,
toggleNewlyAddedCase,
@@ -660,10 +593,6 @@ export const {
setVisitPlansUpdating,
resetNewVisitedCases,
syncCasesByFallback,
addDocumentToUpload,
setDocumentReferenceId,
removeDocuments,
setDocumentInteractionId,
setCasesImageUri,
} = allCasesSlice.actions;

View File

@@ -0,0 +1,94 @@
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]) {
const newDocsMap = { ...state.intermediateDocsToBeUploaded[caseId].documents, doc };
state.intermediateDocsToBeUploaded[caseId].documents = newDocsMap;
} 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

@@ -28,7 +28,10 @@ const FeedbackDetailImageItem: React.FC<IFeedbackDetailImageItem> = ({ image })
};
const questionText = getQuestionText(image);
const originalImageUri = image.metadata?.originalDocumentSignedUri || image.inputText;
// [TODO]: Lat long is merely for testing in UAT, will remove it once get the signoff.
const latitude = image.metadata?.image_latitude;
const longitude = image.metadata?.image_longitude;
return (
<View style={[GenericStyles.mv8]}>
<Text style={[styles.textContainer, styles.questionText]}>{questionText}</Text>
@@ -47,6 +50,9 @@ const FeedbackDetailImageItem: React.FC<IFeedbackDetailImageItem> = ({ image })
onPress={handleExpandImage}
/>
</TouchableOpacity>
<Text>
Geolocation: (Lat:{latitude}, Long:{longitude})
</Text>
<Modal
visible={isImageExpanded}
transparent
@@ -54,7 +60,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,7 +11,8 @@ import {
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CaseAllocationType } from '../allCases/interface';
import { CaseDetail } from './interface';
import { uploadUnsyncedImages } from '../../action/caseApiActions';
import { uploadImages, uploadUnsyncedImages } from '../../action/caseApiActions';
import { setDocumentInteractionId } from '../../reducer/feedbackImagesSlice';
export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any => {
const caseId = updatedCaseDetail?.id;
@@ -22,9 +23,9 @@ export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any
const interactionsHandler = () => {
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
const { unsyncedCasesDetails, docsToBeUploaded } = useAppSelector((state) => ({
unsyncedCasesDetails: state.allCases.unsyncedCaseDetail,
docsToBeUploaded: state.allCases.docsToBeUploaded,
const { allCasesDetails, docsToBeUploaded } = useAppSelector((state) => ({
allCasesDetails: state.allCases.caseDetails,
docsToBeUploaded: state.feedbackImages.docsToBeUploaded,
}));
const { templateId } = useAppSelector(
(state) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE]
@@ -36,17 +37,26 @@ 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 (!unsyncedCasesDetails) return;
if (!allCasesDetails) return;
let notSyncedCases: Array<any> = [];
// [DISCUSS]: isApiCalled not getting true anywhere.
_map(unsyncedCasesDetails, (el) => {
_map(allCasesDetails, (el) => {
if (
unsyncedCasesDetails[el]?.isSynced === false &&
unsyncedCasesDetails[el]?.isApiCalled === false &&
allCasesDetails[el]?.isSynced === false &&
allCasesDetails[el]?.isApiCalled === false &&
!inProgressCaseIds.current.includes(el)
) {
const unSyncedCase = getUnSyncedCase(unsyncedCasesDetails[el]);
const unSyncedCase = getUnSyncedCase(allCasesDetails[el]);
notSyncedCases.push(unSyncedCase);
}
});
@@ -59,18 +69,23 @@ const interactionsHandler = () => {
* inProgressCaseIds not removing the processed ID,
* this will prevent processing of failed api cases again.
*/
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),
})
);
}
}
})();
}, [unsyncedCasesDetails, isOnline]);
}, [allCasesDetails, isOnline]);
useEffect(() => {
if (!isOnline) {
@@ -81,13 +96,14 @@ const interactionsHandler = () => {
}
_map(docsToBeUploaded, (caseId) => {
const interactionId = docsToBeUploaded[caseId]?.interactionId;
// No interactionId means form is not submitted yet
if (!interactionId) {
return;
}
const doc = docsToBeUploaded[caseId].documents;
dispatch(uploadUnsyncedImages(caseId, doc));
dispatch(uploadUnsyncedImages(caseId, interactionId, doc));
});
}, [docsToBeUploaded, isOnline]);
}, [isOnline]);
return null;
};

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';
const rootReducer = combineReducers({
case: caseReducer,
@@ -44,6 +45,7 @@ const rootReducer = combineReducers({
notifications: notificationsSlice,
metadata: MetadataSlice,
foregroundService: foregroundServiceSlice,
feedbackImages: feedbackImagesSlice,
});
const persistConfig = {
@@ -61,6 +63,7 @@ const persistConfig = {
'repayments',
'feedbackHistory',
'address',
'feedbackImages',
],
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 {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

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"