diff --git a/App.js b/App.js index d41b9e5..e2fa215 100644 --- a/App.js +++ b/App.js @@ -1,19 +1,27 @@ import { ApolloProvider } from "@apollo/client"; +import * as Sentry from "@sentry/react-native"; +import { SplitFactory } from "@splitsoftware/splitio-react-native"; +import "expo-asset"; +import "intl"; +import "intl/locale-data/jsonp/en"; import React from "react"; -import { MD2LightTheme as DefaultTheme, Provider as PaperProvider } from "react-native-paper"; +import { + MD2LightTheme as DefaultTheme, + Provider as PaperProvider, +} from "react-native-paper"; +import { SafeAreaProvider } from "react-native-safe-area-context"; +import Toast from "react-native-toast-message"; import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; -import * as Sentry from '@sentry/react-native'; import ScreenMainComponent from "./components/screen-main/screen-main.component"; import { logImEXEvent } from "./firebase/firebase.analytics"; import { client } from "./graphql/client"; import { persistor, store } from "./redux/store"; -import "intl"; -import "intl/locale-data/jsonp/en"; import "./translations/i18n"; -import "expo-asset"; -import Toast from "react-native-toast-message"; -import { SafeAreaProvider } from "react-native-safe-area-context"; + +import RNEventSource from "react-native-event-source"; +globalThis.EventSource = RNEventSource; + Sentry.init({ dsn: "https://8d6c3de1940a4e4f8b81cf4d2150bdea@o492140.ingest.sentry.io/5558869", enableInExpoDevelopment: true, @@ -27,7 +35,6 @@ Sentry.init({ debug: true, // Sentry will try to print out useful debugging information if something goes wrong with sending an event. Set this to `false` in production. }); - const theme = { ...DefaultTheme, colors: { diff --git a/app.json b/app.json index 6830cc2..60c692a 100644 --- a/app.json +++ b/app.json @@ -16,8 +16,11 @@ "ios": { "supportsTablet": true, "bundleIdentifier": "com.imex.imexmobile", - "buildNumber": "1", + "buildNumber": "3", "googleServicesFile": "./GoogleService-Info.plist", + "entitlements": { + "aps-environment": "development" + }, "infoPlist": { "NSPhotoLibraryUsageDescription": "Allow $(PRODUCT_NAME) to access your photos.", "NSPhotoLibraryAddUsageDescription": "Allow $(PRODUCT_NAME) to save photos.", @@ -26,7 +29,7 @@ }, "android": { "package": "com.imex.imexmobile", - "versionCode": 1100035, + "versionCode": 1100036, "googleServicesFile": "./google-services.json", "permissions": [ "android.permission.READ_EXTERNAL_STORAGE", diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx index 4466328..783db8a 100644 --- a/components/job-documents/job-documents.component.jsx +++ b/components/job-documents/job-documents.component.jsx @@ -1,4 +1,5 @@ -import React, { useMemo, useState } from "react"; +import axios from "axios"; +import React, { useEffect, useState } from "react"; import { FlatList, Image, @@ -10,12 +11,12 @@ import { import env from "../../env"; import { DetermineFileType } from "../../util/document-upload.utility"; import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component"; -import { useEffect } from "react"; -import axios from "axios"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { splitClient } from "../screen-main/screen-main.component"; + const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser bodyshop: selectBodyshop, @@ -32,13 +33,16 @@ export function JobDocumentsComponent({ bodyshop, job, loading, refetch }) { const [previewVisible, setPreviewVisible] = useState(false); const [fullphotos, setFullPhotos] = useState([]); const [imgIndex, setImgIndex] = useState(0); + + const useImgproxy = splitClient.getTreatment("Imgproxy"); + const onRefresh = async () => { return refetch(); }; useEffect(() => { async function getPhotos() { - if (bodyshop.localmediatoken === "imgproxy") { + if (useImgproxy) { const result = await axios.post( `${env.API_URL}/media/imgproxy/thumbnails`, { @@ -128,7 +132,11 @@ export function JobDocumentsComponent({ bodyshop, job, loading, refetch }) { )} /> - {fullphotos.length} + + {fullphotos.length} + { - return bodyshop.uselocalmediaserver - ? JobDocumentsLocalComponent({ - job: data.jobs_by_pk, - - bodyshop: bodyshop, - }) - : JobDocuments({ - job: data.jobs_by_pk, - loading: loading, - refetch: refetch, - }); + return bodyshop.uselocalmediaserver ? ( + + ) : ( + + ); }, notes: () => diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx index 5a9dfb2..b0acc6b 100644 --- a/components/screen-main/screen-main.component.jsx +++ b/components/screen-main/screen-main.component.jsx @@ -19,12 +19,14 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import env from "../../env"; import ScreenJobDetail from "../screen-job-detail/screen-job-detail.component"; import ScreenJobList from "../screen-job-list/screen-job-list.component"; import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.component"; import ScreenSettingsComponent from "../screen-settings/screen-settings.component"; import ScreenSignIn from "../screen-sign-in/screen-sign-in.component"; import ScreenSplash from "../screen-splash/screen-splash.component"; +import { SplitFactory } from "@splitsoftware/splitio-react-native"; const ActiveJobStack = createNativeStackNavigator(); const MoreStack = createNativeStackNavigator(); @@ -148,6 +150,8 @@ const BottomTabsNavigator = () => ( ); +export var splitClient; + export function ScreenMainComponent({ checkUserSession, currentUser, @@ -157,6 +161,16 @@ export function ScreenMainComponent({ checkUserSession(); }, [checkUserSession]); + useEffect(() => { + if (bodyshop && bodyshop.imexshopid) { + splitClient = SplitFactory({ + //debug: true, + core: { authorizationKey: env.SPLIT_API, key: bodyshop.imexshopid }, + }).client(); + splitClient.setAttribute("imexshopid", bodyshop.imexshopid); + } + }, [bodyshop]); + return ( {currentUser.authorized === null ? ( diff --git a/components/upload-progress/upload-progress.component.jsx b/components/upload-progress/upload-progress.component.jsx index 0cf774f..a8465a8 100644 --- a/components/upload-progress/upload-progress.component.jsx +++ b/components/upload-progress/upload-progress.component.jsx @@ -271,8 +271,7 @@ export function UploadProgress({ jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null, uploaded_by: currentUser.email, photo: p, - }, - bodyshop.localmediatoken === "imgproxy" ? "imgproxy" : "" //Choose which destination to use. + } ); }; diff --git a/eas.json b/eas.json index 010e85f..4bef6ef 100644 --- a/eas.json +++ b/eas.json @@ -7,8 +7,13 @@ "developmentClient": true, "channel": "test", "distribution": "internal", - "ios": { //"simulator": true - } + "ios": {} + }, + "development-simulator": { + "developmentClient": true, + "channel": "test", + "distribution": "internal", + "ios": { "simulator": true } }, "test": { "channel": "test" diff --git a/env.js b/env.js index 9e39d65..cbfc72e 100644 --- a/env.js +++ b/env.js @@ -10,6 +10,7 @@ const ENV = { 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", + SPLIT_API: "ts615lqgnmk84thn72uk18uu5pgce6e0l4rc", firebase: { apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", authDomain: "imex-test.firebaseapp.com", @@ -29,6 +30,7 @@ const ENV = { "https://api.cloudinary.com/v1_1/bodyshop", REACT_APP_CLOUDINARY_ENDPOINT: "https://res.cloudinary.com/bodyshop", REACT_APP_CLOUDINARY_API_KEY: "473322739956866", + SPLIT_API: "et9pjkik6bn67he5evpmpr1agoo7gactphgk", REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS: "c_fill,h_250,w_250", firebase: { apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU", diff --git a/graphql/bodyshop.queries.js b/graphql/bodyshop.queries.js index eaf877f..cca90d8 100644 --- a/graphql/bodyshop.queries.js +++ b/graphql/bodyshop.queries.js @@ -11,6 +11,7 @@ export const QUERY_BODYSHOP = gql` shopname features localmediatoken + imexshopid } } `; diff --git a/package-lock.json b/package-lock.json index 450db8f..18d3f05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,12 @@ { "name": "imexmobile", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "imexmobile", + "version": "1.0.0", "dependencies": { "@apollo/client": "^3.12.11", "@babel/preset-env": "7.26.8", @@ -18,6 +21,7 @@ "@react-navigation/native-stack": "^7.2.0", "@react-navigation/stack": "^7.1.1", "@sentry/react-native": "~6.3.0", + "@splitsoftware/splitio-react-native": "^1.1.0", "axios": "^1.7.9", "cloudinary-core": "^2.13.1", "dinero.js": "^1.9.1", @@ -54,6 +58,7 @@ "react-native": "0.76.7", "react-native-draggable-flatlist": "^4.0.1", "react-native-element-dropdown": "^2.12.4", + "react-native-event-source": "^1.1.0", "react-native-gesture-handler": "~2.20.2", "react-native-image-gallery": "^2.1.5", "react-native-image-viewing": "^0.2.2", @@ -6145,6 +6150,37 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@splitsoftware/splitio-commons": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.1.0.tgz", + "integrity": "sha512-7SJRBia0Pi72s76drH8kG2cVnCqkjMHMJQWJSFnG+rE/UOx9AROmuviOkY6tv6qYPJFqFQQGHGX6lXjxZhYzkw==", + "license": "Apache-2.0", + "dependencies": { + "@types/ioredis": "^4.28.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "ioredis": "^4.28.0" + }, + "peerDependenciesMeta": { + "ioredis": { + "optional": true + } + } + }, + "node_modules/@splitsoftware/splitio-react-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-react-native/-/splitio-react-native-1.1.0.tgz", + "integrity": "sha512-KaXU1KB+oDC309/rc0Wq47JbM2YOgk2EohhVwpwBK5awoKUKZyW/Ne6euzknkKdTBy3kAOJlh3wI9aMkvBAgpA==", + "license": "Apache-2.0", + "dependencies": { + "@splitsoftware/splitio-commons": "2.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -6222,6 +6258,15 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/ioredis": { + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz", + "integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -14325,6 +14370,12 @@ "react-native": "*" } }, + "node_modules/react-native-event-source": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-native-event-source/-/react-native-event-source-1.1.0.tgz", + "integrity": "sha512-CAs76IW8kTrdy0okfV2KPopm7E9TL0uNAR+SRrN7iZ/ii0zBeHWuhD4Q2F8gRKvmkrEtCZ6uwnfYL2TFmK0QZg==", + "license": "MIT" + }, "node_modules/react-native-gesture-handler": { "version": "2.20.2", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz", diff --git a/package.json b/package.json index f2f04c4..ea96a1d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@react-navigation/native-stack": "^7.2.0", "@react-navigation/stack": "^7.1.1", "@sentry/react-native": "~6.3.0", + "@splitsoftware/splitio-react-native": "^1.1.0", "axios": "^1.7.9", "cloudinary-core": "^2.13.1", "dinero.js": "^1.9.1", @@ -65,6 +66,7 @@ "react-native": "0.76.7", "react-native-draggable-flatlist": "^4.0.1", "react-native-element-dropdown": "^2.12.4", + "react-native-event-source": "^1.1.0", "react-native-gesture-handler": "~2.20.2", "react-native-image-gallery": "^2.1.5", "react-native-image-viewing": "^0.2.2", diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js index 1134c0e..02b2fb8 100644 --- a/util/document-upload.utility.js +++ b/util/document-upload.utility.js @@ -5,14 +5,14 @@ import env from "../env"; import { client } from "../graphql/client"; import { INSERT_NEW_DOCUMENT } from "../graphql/documents.queries"; import { axiosAuthInterceptorId } from "./CleanAxios"; - +import { splitClient } from "../components/screen-main/screen-main.component"; //Context: currentUserEmail, bodyshop, jobid, invoiceid //Required to prevent headers from getting set and rejected from Cloudinary. var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); -export const handleUpload = async (ev, context, destination) => { +export const handleUpload = async (ev, context) => { const { mediaId, onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; @@ -22,6 +22,10 @@ export const handleUpload = async (ev, context, destination) => { ).blob(); let extension = imageData.localUri.split(".").pop(); + //Default to Cloudinary in case of split treatment errors. + let destination = + splitClient?.getTreatment("Imgproxy") === "on" ? "imgproxy" : "cloudinary"; + let key = destination === "imgproxy" ? `${bodyshop.id}/${jobId}/${replaceAccents(