From dc6cd88e8c606e0f7140717314a32261ffc7a2b3 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 21 Feb 2025 13:54:56 -0800 Subject: [PATCH] IO-3092 Add imgproxy uploads and update expo version. --- app.json | 15 +- .../data-label/data-label.component.jsx | 2 +- .../job-documents/job-documents.component.jsx | 114 ++++++++--- .../loading-display.component.jsx | 1 + .../screen-media-browser.component.jsx | 88 ++++---- .../upload-progress-local.component.jsx} | 0 .../upload-progress.component.jsx | 98 ++++----- eas.json | 3 +- env.js | 62 +++--- package.json | 8 +- upgrades.md | 3 + util/document-upload.utility.js | 190 +++++++++++++++++- 12 files changed, 410 insertions(+), 174 deletions(-) rename components/{local-upload-progress/local-upload-progress.component.jsx => upload-progress-local/upload-progress-local.component.jsx} (100%) create mode 100644 upgrades.md diff --git a/app.json b/app.json index f4a10a9..6830cc2 100644 --- a/app.json +++ b/app.json @@ -9,12 +9,10 @@ "projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b" } }, + "runtimeVersion": "appVersion", "orientation": "default", "icon": "./assets/logo192noa.png", - "platforms": [ - "ios", - "android" - ], + "platforms": ["ios", "android"], "ios": { "supportsTablet": true, "bundleIdentifier": "com.imex.imexmobile", @@ -47,9 +45,7 @@ "fallbackToCacheTimeout": 0, "url": "https://u.expo.dev/ffe01f3a-d507-4698-82cd-da1f1cad450b" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "web": { "favicon": "./assets/logo192noa.png", "config": { @@ -85,9 +81,6 @@ ], "expo-localization", "expo-font" - ], - "runtimeVersion": { - "policy": "appVersion" - } + ] } } diff --git a/components/data-label/data-label.component.jsx b/components/data-label/data-label.component.jsx index ce655d8..6f0bdff 100644 --- a/components/data-label/data-label.component.jsx +++ b/components/data-label/data-label.component.jsx @@ -16,7 +16,7 @@ export default function DataLabelComponent({ ); const { key, ...rest } = restProps; return ( - + {label} {theContent} diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx index ef692b5..4466328 100644 --- a/components/job-documents/job-documents.component.jsx +++ b/components/job-documents/job-documents.component.jsx @@ -10,38 +10,93 @@ import { import env from "../../env"; import { DetermineFileType } from "../../util/document-upload.utility"; import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component"; -export default function JobDocumentsComponent({ job, loading, refetch }) { - const [previewVisible, setPreviewVisible] = useState(false); +import { useEffect } from "react"; +import axios from "axios"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(JobDocumentsComponent); + +export function JobDocumentsComponent({ bodyshop, job, loading, refetch }) { + const [previewVisible, setPreviewVisible] = useState(false); + const [fullphotos, setFullPhotos] = useState([]); const [imgIndex, setImgIndex] = useState(0); const onRefresh = async () => { return refetch(); }; - const fullphotos = useMemo( - () => - job.documents.map((doc, idx) => { - return { - id: idx, - videoUrl: - DetermineFileType(doc.type) === "video" && GenerateSrcUrl(doc), - source: - DetermineFileType(doc.type) === "video" - ? { uri: GenerateThumbUrl(doc) } - : { uri: GenerateSrcUrl(doc) }, - url: - DetermineFileType(doc.type) === "video" - ? GenerateThumbUrl(doc) - : GenerateSrcUrl(doc), - uri: - DetermineFileType(doc.type) === "video" - ? GenerateThumbUrl(doc) - : GenerateSrcUrl(doc), - thumbUrl: GenerateThumbUrl(doc), - }; - }), - [job.documents] - ); + useEffect(() => { + async function getPhotos() { + if (bodyshop.localmediatoken === "imgproxy") { + const result = await axios.post( + `${env.API_URL}/media/imgproxy/thumbnails`, + { + jobid: job.id, + } + ); + + setFullPhotos( + result.data.map((doc, idx) => { + return { + id: idx, + videoUrl: + DetermineFileType(doc.type) === "video" && + doc.originalUrlViaProxyPath, + source: + DetermineFileType(doc.type) === "video" + ? { uri: doc.thumbnailUrl } + : { uri: doc.originalUrl }, + url: + DetermineFileType(doc.type) === "video" + ? doc.thumbnailUrl + : doc.originalUrl, + uri: + DetermineFileType(doc.type) === "video" + ? doc.originalUrlViaProxyPath + : doc.originalUrl, + thumbUrl: doc.thumbnailUrl, + }; + }) + ); + } else { + setFullPhotos( + job.documents.map((doc, idx) => { + return { + id: idx, + videoUrl: + DetermineFileType(doc.type) === "video" && GenerateSrcUrl(doc), + source: + DetermineFileType(doc.type) === "video" + ? { uri: GenerateThumbUrl(doc) } + : { uri: GenerateSrcUrl(doc) }, + url: + DetermineFileType(doc.type) === "video" + ? GenerateThumbUrl(doc) + : GenerateSrcUrl(doc), + uri: + DetermineFileType(doc.type) === "video" + ? GenerateThumbUrl(doc) + : GenerateSrcUrl(doc), + thumbUrl: GenerateThumbUrl(doc), + }; + }) + ); + } + } + + getPhotos(); + }, [job.documents]); return ( @@ -49,7 +104,7 @@ export default function JobDocumentsComponent({ job, loading, refetch }) { refreshControl={ } - data={job.documents} + data={fullphotos} numColumns={4} style={{ flex: 1 }} keyExtractor={(item) => item.id} @@ -65,14 +120,15 @@ export default function JobDocumentsComponent({ job, loading, refetch }) { style={{ flex: 1 }} resizeMode="cover" source={{ - uri: GenerateThumbUrl(object.item), + uri: object.item.thumbUrl, aspectRatio: 1, }} /> )} /> - {job.documents.length} + + {fullphotos.length} diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx index daf1206..a55fada 100644 --- a/components/screen-media-browser/screen-media-browser.component.jsx +++ b/components/screen-media-browser/screen-media-browser.component.jsx @@ -1,23 +1,23 @@ -import { Ionicons } from '@expo/vector-icons'; -import { AssetsSelector } from 'expo-images-picker'; -import { MediaType } from 'expo-media-library'; -import React, { useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { StyleSheet, Text, View } from 'react-native'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { logImEXEvent } from '../../firebase/firebase.analytics'; -import { toggleDeleteAfterUpload } from '../../redux/app/app.actions'; +import { Ionicons } from "@expo/vector-icons"; +import { AssetsSelector } from "expo-images-picker"; +import { MediaType } from "expo-media-library"; +import React, { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StyleSheet, Text, View } from "react-native"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; +import { toggleDeleteAfterUpload } from "../../redux/app/app.actions"; import { selectCurrentCameraJobId, selectDeleteAfterUpload, -} from '../../redux/app/app.selectors'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import CameraSelectJob from '../camera-select-job/camera-select-job.component'; -import JobSpaceAvailable from '../job-space-available/job-space-available.component'; -import LocalUploadProgress from '../local-upload-progress/local-upload-progress.component'; -import UploadDeleteSwitch from '../upload-delete-switch/upload-delete-switch.component'; -import UploadProgress from '../upload-progress/upload-progress.component'; +} from "../../redux/app/app.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import CameraSelectJob from "../camera-select-job/camera-select-job.component"; +import JobSpaceAvailable from "../job-space-available/job-space-available.component"; +import UploadProgressLocal from "../upload-progress-local/upload-progress-local.component"; +import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component"; +import UploadProgress from "../upload-progress/upload-progress.component"; const mapStateToProps = createStructuredSelector({ selectedCameraJobId: selectCurrentCameraJobId, @@ -43,18 +43,18 @@ export function ImageBrowserScreen({ }, []); const onDone = (data) => { - logImEXEvent('imexmobile_upload_documents', { count: data.length }); + logImEXEvent("imexmobile_upload_documents", { count: data.length }); if (data.length !== 0) setUploads(data); }; const widgetErrors = useMemo( () => ({ - errorTextColor: 'black', + errorTextColor: "black", errorMessages: { - hasErrorWithPermissions: 'Please Allow media gallery permissions.', - hasErrorWithLoading: 'There was an error while loading images.', - hasErrorWithResizing: 'There was an error while loading images.', - hasNoAssets: 'No images found.', + hasErrorWithPermissions: "Please Allow media gallery permissions.", + hasErrorWithLoading: "There was an error while loading images.", + hasErrorWithResizing: "There was an error while loading images.", + hasNoAssets: "No images found.", }, }), [] @@ -78,28 +78,28 @@ export function ImageBrowserScreen({ width: 50, compress: 0.7, base64: false, - saveTo: 'jpeg', + saveTo: "jpeg", }), [] ); const _textStyle = { - color: 'white', + color: "white", }; const _buttonStyle = { - backgroundColor: 'orange', + backgroundColor: "orange", borderRadius: 5, }; const widgetNavigator = useMemo( () => ({ Texts: { - finish: t('mediabrowser.actions.upload'), - back: t('mediabrowser.actions.refresh'), - selected: 'selected', + finish: t("mediabrowser.actions.upload"), + back: t("mediabrowser.actions.refresh"), + selected: "selected", }, - midTextColor: 'black', + midTextColor: "black", minSelection: 1, buttonTextStyle: styles.textStyle, buttonStyle: styles.buttonStyle, @@ -114,20 +114,20 @@ export function ImageBrowserScreen({ const widgetStyles = useMemo( () => ({ margin: 2, - bgColor: 'white', - spinnerColor: 'blue', + bgColor: "white", + spinnerColor: "blue", widgetWidth: 99, videoIcon: { Component: Ionicons, - iconName: 'videocam', - color: 'white', + iconName: "videocam", + color: "white", size: 20, }, selectedIcon: { Component: Ionicons, - iconName: 'checkmark-circle-outline', - color: 'white', - bg: 'rgba(35,35,35, 0.75)', + iconName: "checkmark-circle-outline", + color: "white", + bg: "rgba(35,35,35, 0.75)", size: 32, }, }), @@ -139,7 +139,7 @@ export function ImageBrowserScreen({ {bodyshop.uselocalmediaserver ? ( - {t('mediabrowser.labels.localserver', { + {t("mediabrowser.labels.localserver", { url: bodyshop.localmediaserverhttp, })} @@ -161,11 +161,11 @@ export function ImageBrowserScreen({ - {t('mediabrowser.labels.selectjobassetselector')} + {t("mediabrowser.labels.selectjobassetselector")} )} {selectedCameraJobId && ( @@ -179,7 +179,7 @@ export function ImageBrowserScreen({ /> )} {bodyshop.uselocalmediaserver ? ( - f.id)); } } catch (error) { - console.log('Unable to delete picture.', error); + console.log("Unable to delete picture.", error); Sentry.Native.captureException(error); } } filesToDelete = []; Toast.show({ - type: 'success', + type: "success", text1: ` Upload completed.`, // // text2: duration, @@ -269,22 +268,23 @@ export function UploadProgress({ }, { bodyshop: bodyshop, - jobId: selectedCameraJobId !== 'temp' ? selectedCameraJobId : null, + jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null, uploaded_by: currentUser.email, photo: p, - } + }, + bodyshop.localmediatoken === "imgproxy" ? "imgproxy" : "" //Choose which destination to use. ); }; return ( { - Alert.alert('Cancel?', 'Do you want to abort the upload?', [ + Alert.alert("Cancel?", "Do you want to abort the upload?", [ { - text: 'Yes', + text: "Yes", onPress: () => { setUploads(null); setProgress({ @@ -299,7 +299,7 @@ export function UploadProgress({ }); }, }, - { text: 'No' }, + { text: "No" }, ]); }} > @@ -314,13 +314,13 @@ export function UploadProgress({ {`${formatBytes( @@ -362,19 +362,19 @@ export function UploadProgress({ } const styles = StyleSheet.create({ modalContainer: { - display: 'flex', + display: "flex", flex: 1, - justifyContent: 'center', + justifyContent: "center", }, modal: { //flex: 1, - display: 'flex', + display: "flex", marginLeft: 20, marginRight: 20, - backgroundColor: 'white', + backgroundColor: "white", borderRadius: 20, padding: 18, - shadowColor: '#000', + shadowColor: "#000", shadowOffset: { width: 0, height: 2, @@ -384,14 +384,14 @@ const styles = StyleSheet.create({ elevation: 5, }, centeredView: { - justifyContent: 'center', - alignItems: 'center', + justifyContent: "center", + alignItems: "center", marginTop: 22, }, progressItem: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', + display: "flex", + flexDirection: "row", + alignItems: "center", marginBottom: 12, marginLeft: 12, marginRight: 12, diff --git a/eas.json b/eas.json index 9197d1f..010e85f 100644 --- a/eas.json +++ b/eas.json @@ -7,7 +7,8 @@ "developmentClient": true, "channel": "test", "distribution": "internal", - "ios": { "simulator": true } + "ios": { //"simulator": true + } }, "test": { "channel": "test" diff --git a/env.js b/env.js index 59ad636..9e39d65 100644 --- a/env.js +++ b/env.js @@ -1,50 +1,50 @@ -import * as Updates from 'expo-updates'; +import * as Updates from "expo-updates"; const ENV = { test: { - API_URL: 'https://api.test.imex.online', - uri: 'https://db.test.bodyshop.app/v1/graphql', - wsuri: 'wss://db.test.bodyshop.app/v1/graphql', + API_URL: "https://api.test.imex.online", + uri: "https://db.test.bodyshop.app/v1/graphql", + wsuri: "wss://db.test.bodyshop.app/v1/graphql", REACT_APP_CLOUDINARY_ENDPOINT_API: - 'https://api.cloudinary.com/v1_1/bodyshop', - REACT_APP_CLOUDINARY_ENDPOINT: 'https://res.cloudinary.com/bodyshop', - REACT_APP_CLOUDINARY_API_KEY: '473322739956866', - REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS: 'c_fill,h_250,w_250', + "https://api.cloudinary.com/v1_1/bodyshop", + REACT_APP_CLOUDINARY_ENDPOINT: "https://res.cloudinary.com/bodyshop", + REACT_APP_CLOUDINARY_API_KEY: "473322739956866", + REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS: "c_fill,h_250,w_250", firebase: { - apiKey: 'AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c', - authDomain: 'imex-test.firebaseapp.com', - projectId: 'imex-test', - storageBucket: 'imex-test.appspot.com', - messagingSenderId: '991923618608', - appId: '1:991923618608:web:633437569cdad78299bef5', - measurementId: 'G-TW0XLZEH18', + apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", + authDomain: "imex-test.firebaseapp.com", + projectId: "imex-test", + storageBucket: "imex-test.appspot.com", + messagingSenderId: "991923618608", + appId: "1:991923618608:web:633437569cdad78299bef5", + measurementId: "G-TW0XLZEH18", }, }, prod: { - API_URL: 'https://api.imex.online', - uri: 'https://db.imex.online/v1/graphql', - wsuri: 'wss://db.imex.online/v1/graphql', + API_URL: "https://api.imex.online", + uri: "https://db.imex.online/v1/graphql", + wsuri: "wss://db.imex.online/v1/graphql", REACT_APP_CLOUDINARY_ENDPOINT_API: - 'https://api.cloudinary.com/v1_1/bodyshop', - REACT_APP_CLOUDINARY_ENDPOINT: 'https://res.cloudinary.com/bodyshop', - REACT_APP_CLOUDINARY_API_KEY: '473322739956866', - REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS: 'c_fill,h_250,w_250', + "https://api.cloudinary.com/v1_1/bodyshop", + REACT_APP_CLOUDINARY_ENDPOINT: "https://res.cloudinary.com/bodyshop", + REACT_APP_CLOUDINARY_API_KEY: "473322739956866", + REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS: "c_fill,h_250,w_250", firebase: { - apiKey: 'AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU', - authDomain: 'imex-prod.firebaseapp.com', - databaseURL: 'https://imex-prod.firebaseio.com', - projectId: 'imex-prod', - storageBucket: 'imex-prod.appspot.com', - messagingSenderId: '253497221485', - appId: '1:253497221485:web:3c81c483b94db84b227a64', - measurementId: 'G-NTWBKG2L0M', + apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU", + authDomain: "imex-prod.firebaseapp.com", + databaseURL: "https://imex-prod.firebaseio.com", + projectId: "imex-prod", + storageBucket: "imex-prod.appspot.com", + messagingSenderId: "253497221485", + appId: "1:253497221485:web:3c81c483b94db84b227a64", + measurementId: "G-NTWBKG2L0M", }, }, }; function getEnvVars() { - if (Updates.channel !== 'production') return ENV.test; + if (Updates.channel !== "production") return ENV.test; else return ENV.prod; } diff --git a/package.json b/package.json index 2949629..f2f04c4 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "main": "node_modules/expo/AppEntry.js", "scripts": { "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "eject": "expo eject", "release:test": "expo publish --release-channel test", @@ -95,5 +95,7 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-native": "^5.0.0" }, - "private": true + "private": true, + "name": "imexmobile", + "version": "1.0.0" } diff --git a/upgrades.md b/upgrades.md new file mode 100644 index 0000000..5404dbc --- /dev/null +++ b/upgrades.md @@ -0,0 +1,3 @@ +upgrade to expo router +investigate image pickers +change splash to explo-splash-screen diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js index 46c03db..1134c0e 100644 --- a/util/document-upload.utility.js +++ b/util/document-upload.utility.js @@ -1,11 +1,10 @@ +import * as Sentry from "@sentry/react-native"; import axios from "axios"; +import * as MediaLibrary from "expo-media-library"; import env from "../env"; import { client } from "../graphql/client"; import { INSERT_NEW_DOCUMENT } from "../graphql/documents.queries"; import { axiosAuthInterceptorId } from "./CleanAxios"; -import * as MediaLibrary from "expo-media-library"; -import { gql } from "@apollo/client"; -import * as Sentry from '@sentry/react-native'; //Context: currentUserEmail, bodyshop, jobid, invoiceid @@ -13,7 +12,55 @@ import * as Sentry from '@sentry/react-native'; var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); -export const handleUpload = async (ev, context) => { +export const handleUpload = async (ev, context, destination) => { + const { mediaId, onError, onSuccess, onProgress } = ev; + const { bodyshop, jobId } = context; + + const imageData = await MediaLibrary.getAssetInfoAsync(mediaId); + const newFile = await ( + await fetch(imageData.localUri || imageData.uri) + ).blob(); + let extension = imageData.localUri.split(".").pop(); + + let key = + destination === "imgproxy" + ? `${bodyshop.id}/${jobId}/${replaceAccents( + imageData.filename || imageData.uri.split("/").pop() + ).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.${extension}` + : `${bodyshop.id}/${jobId}/${( + imageData.filename || imageData.uri.split("/").pop() + ).replace(/\.[^/.]+$/, "")}-${new Date().getTime()}`; + + const res = + destination === "imgproxy" + ? await uploadToImgproxy( + key, + mediaId, + imageData, + extension, + newFile.type, //Filetype + newFile, //File + onError, + onSuccess, + onProgress, + context + ) + : await uploadToCloudinary( + key, + mediaId, + imageData, + extension, + newFile.type, //Filetype + newFile, //File + onError, + onSuccess, + onProgress, + context + ); + return res; +}; + +export const handleUploadImgproxy = async (ev, context) => { const { mediaId, onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; @@ -26,7 +73,7 @@ export const handleUpload = async (ev, context) => { imageData.filename || imageData.uri.split("/").pop() ).replace(/\.[^/.]+$/, "")}-${new Date().getTime()}`; - const res = await uploadToCloudinary( + const res = await uploadToImgproxy( key, mediaId, imageData, @@ -41,6 +88,110 @@ export const handleUpload = async (ev, context) => { return res; }; +export const uploadToImgproxy = async ( + key, + mediaId, + imageData, + extension, + fileType, + file, + onError, + onSuccess, + onProgress, + context +) => { + const { bodyshop, jobId, uploaded_by } = context; + + //Get the signed url allowing us to PUT to S3. + const signedURLResponse = await axios.post( + `${env.API_URL}/media/imgproxy/sign`, + { + filenames: [key], + bodyshopid: bodyshop.id, + jobid: jobId, + } + ); + + if (signedURLResponse.status !== 200) { + console.log("Error Getting Signed URL", signedURLResponse.statusText); + if (onError) onError(signedURLResponse.statusText); + return { success: false, error: signedURLResponse.statusText }; + } + const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = + signedURLResponse.data.signedUrls[0]; + + var options = { + headers: { + "Content-Type": fileType, + "Content-Length": file.size, + }, + transformRequest: [(data) => data], //Dave had this magical solution because Axios makes no sense. + onUploadProgress: (e) => { + if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); + }, + }; + + console.log("Uploading to S3", key, preSignedUploadUrlToS3, file, options); + const formData = new FormData(); + formData.append("file", { + uri: imageData.localUri, + type: fileType, + name: file.data.name, + }); + + try { + const s3UploadResponse = await cleanAxios.put( + preSignedUploadUrlToS3, + file, + options + ); + debugger; + console.log(s3UploadResponse); + } catch (error) { + console.log("Error uploading to S3", error.message, error.stack); + Sentry.Native.captureException(error); + } + + const documentInsert = await client.mutate({ + mutation: INSERT_NEW_DOCUMENT, + variables: { + docInput: [ + { + ...(jobId ? { jobid: jobId } : {}), + uploaded_by: uploaded_by, + key: s3Key, + type: fileType, + extension: extension, + bodyshopid: bodyshop.id, + size: file.size, + ...(imageData.creationTime + ? { takenat: new Date(imageData.creationTime) } + : {}), + }, + ], + }, + }); + + if (!documentInsert.errors) { + if (onSuccess) + onSuccess({ + uid: documentInsert.data.insert_documents.returning[0].id, + name: documentInsert.data.insert_documents.returning[0].name, + status: "done", + key: documentInsert.data.insert_documents.returning[0].key, + }); + } else { + if (onError) onError(JSON.stringify(documentInsert.errors)); + return { + success: false, + error: JSON.stringify(documentInsert.errors), + mediaId, + }; + } + + return { success: true, mediaId }; +}; + export const uploadToCloudinary = async ( key, mediaId, @@ -198,3 +349,32 @@ export function formatBytes(a, b = 2) { ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d] ); } + +function replaceAccents(str) { + // Verifies if the String has accents and replace them + if (str.search(/[\xC0-\xFF]/g) > -1) { + str = str + .replace(/[\xC0-\xC5]/g, "A") + .replace(/[\xC6]/g, "AE") + .replace(/[\xC7]/g, "C") + .replace(/[\xC8-\xCB]/g, "E") + .replace(/[\xCC-\xCF]/g, "I") + .replace(/[\xD0]/g, "D") + .replace(/[\xD1]/g, "N") + .replace(/[\xD2-\xD6\xD8]/g, "O") + .replace(/[\xD9-\xDC]/g, "U") + .replace(/[\xDD]/g, "Y") + .replace(/[\xDE]/g, "P") + .replace(/[\xE0-\xE5]/g, "a") + .replace(/[\xE6]/g, "ae") + .replace(/[\xE7]/g, "c") + .replace(/[\xE8-\xEB]/g, "e") + .replace(/[\xEC-\xEF]/g, "i") + .replace(/[\xF1]/g, "n") + .replace(/[\xF2-\xF6\xF8]/g, "o") + .replace(/[\xF9-\xFC]/g, "u") + .replace(/[\xFE]/g, "p") + .replace(/[\xFD\xFF]/g, "y"); + } + return str; +}