diff --git a/client/public/firebase-messaging-sw.js b/client/public/firebase-messaging-sw.js new file mode 100644 index 000000000..7ee3f3d98 --- /dev/null +++ b/client/public/firebase-messaging-sw.js @@ -0,0 +1,56 @@ +// Scripts for firebase and firebase messaging +importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js"); +importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js"); + +// Initialize the Firebase app in the service worker by passing the generated config +let firebaseConfig; +switch (this.location.hostname) { + case "localhost": + firebaseConfig = { + apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc", + authDomain: "imex-dev.firebaseapp.com", + databaseURL: "https://imex-dev.firebaseio.com", + projectId: "imex-dev", + storageBucket: "imex-dev.appspot.com", + messagingSenderId: "759548147434", + appId: "1:759548147434:web:e8239868a48ceb36700993", + measurementId: "G-K5XRBVVB4S", + }; + break; + case "test.imex.online": + firebaseConfig = { + apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", + authDomain: "imex-test.firebaseapp.com", + projectId: "imex-test", + storageBucket: "imex-test.appspot.com", + messagingSenderId: "991923618608", + appId: "1:991923618608:web:633437569cdad78299bef5", + // measurementId: "${config.measurementId}", + }; + break; + case "imex.online": + default: + firebaseConfig = { + 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", + }; +} + +firebase.initializeApp(firebaseConfig); + +// Retrieve firebase messaging +const messaging = firebase.messaging(); + +messaging.onBackgroundMessage(function (payload) { + // Customize notification here + const channel = new BroadcastChannel("imex-sw-messages"); + channel.postMessage(payload); + + //self.registration.showNotification(notificationTitle, notificationOptions); +}); diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 50bde585a..4b80fb38e 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -150,7 +150,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline { - history(opt.label.props.to); - }} - onClear={() => setData([])} - > + setData([])}> { @@ -157,14 +156,7 @@ export default function GlobalSearch() { if (error) return ; return ( - { - history(opt.label.props.to); - }} - > + , - // label: t("menus.header.rescueme"), - // onClick: () => { - // window.open("https://imexrescue.com/", "_blank"); - // } - // }, + ...(InstanceRenderManager({ + imex: true, + rome: false, + promanager: false + }) + ? [ + { + key: "rescue", + icon: , + label: t("menus.header.rescueme"), + onClick: () => { + window.open("https://imexrescue.com/", "_blank"); + } + } + ] + : []), ...(InstanceRenderManager({ imex: true, diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index a961c33e9..20cc88d36 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -13,6 +13,7 @@ import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-forma import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; // TODO: Client Update, this might break const timeZonesList = Intl.supportedValuesOf("timeZone"); const mapStateToProps = createStructuredSelector({ @@ -144,285 +145,289 @@ export function ShopInfoGeneral({ form, bodyshop }) { - - - - - {InstanceRenderManager({ - imex: ( - - {() => ( + null}> + + + + + {InstanceRenderManager({ + imex: ( + + {() => ( + + + + )} + + ) + })} + + + + + + 2 + 3 + + + + {() => { + return ( - + + {t("bodyshop.labels.2tiername")} + {t("bodyshop.labels.2tiersource")} + - )} - - ) - })} - - - - - - 2 - 3 - - - - {() => { - return ( + ); + }} + + + + + + + + + + + + + + {InstanceRenderManager({ + imex: ( + + + + ) + })} + + + + {InstanceRenderManager({ + imex: ( - - {t("bodyshop.labels.2tiername")} - {t("bodyshop.labels.2tiersource")} - + - ); - }} - - - - - - - - - - - - - - {InstanceRenderManager({ - imex: ( - - - - ) - })} - - - - {InstanceRenderManager({ - imex: ( - - - - ) - })} - - - - - - - - - - - - - - {ReceivableCustomFieldSelect} - - - {ReceivableCustomFieldSelect} - - - {ReceivableCustomFieldSelect} - - { - return { - required: getFieldValue("enforce_class"), + ) + })} + + + + + + + - + + + + + {ClosingPeriod.treatment === "on" && ( + <> + + + + + )} + + + null}> + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + null}> + + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - { - remove(field.name); - }} - /> - - - + + { + remove(field.name); + }} + /> + + + + + ))} + + - ))} - - - -
- ); - }} -
-
+ + ); + }} + +
+ + {(fields, { add, remove, move }) => { diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx index f26dc1938..8291c9c4f 100644 --- a/client/src/components/shop-info/shop-info.rbac.component.jsx +++ b/client/src/components/shop-info/shop-info.rbac.component.jsx @@ -1,12 +1,13 @@ +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Form, InputNumber } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -29,210 +30,219 @@ export function ShopInfoRbacComponent({ form, bodyshop }) { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {...HasFeatureAccess({ featureName: "export", bodyshop }) ? [ + + + , + + + , + + + , + + + + ]:[]} + {...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [ + + + , + + + , + + + , + + + , + + + + ]:[]} + + {...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [ + + + , + + + , + + + , + + + , + + + , + + + + ]:[]} + {...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [ + + + , + + + + ]:[]} - - - - - - + {HasFeatureAccess({ featureName: "visualboard", bodyshop }) && ( + + + + )} + - - - - - - - - - - - - - - - - - - - - - - - - + {HasFeatureAccess({ featureName: "scoreboard", bodyshop }) && ( + + + + )} + {...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [ + + + , + + + , + + + , + + + , + + + , + + + , + + + , + + + , + + + , + + + + ]:[]} - - - - - - - - - - - - - - - - - - - - + */} + {HasFeatureAccess({ featureName: "media", bodyshop }) && ( + + + + )} + { return (
- + {fields.map((field, index) => ( @@ -386,7 +386,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) { ))} - + - } - /> - ); + return ; } diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index 19991f62f..97f34ea95 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -72,10 +72,17 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { }); } - items.push({ - key: "licensing", - label: t("bodyshop.labels.licensing"), - children: + InstanceRenderManager({ + executeFunction: true, + args: [], + imex: () => { + items.push({ + key: "licensing", + label: t("bodyshop.labels.licensing"), + children: + }); + }, + rome: "USE_IMEX" }); if (HasFeatureAccess({ featureName: "csi", bodyshop })) { diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 0c5aae651..99ba30225 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -15,6 +15,7 @@ import { getToken } from "firebase/messaging"; import i18next from "i18next"; import LogRocket from "logrocket"; import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; +import { Userpilot } from "userpilot"; import { factory } from "../../App/App.container"; import { analytics, @@ -25,6 +26,10 @@ import { messaging, updateCurrentUser } from "../../firebase/firebase.utils"; +import { QUERY_EULA } from "../../graphql/bodyshop.queries"; +import client from "../../utils/GraphQLClient"; +import day from "../../utils/day"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { checkInstanceId, sendPasswordResetFailure, @@ -43,11 +48,6 @@ import { validatePasswordResetSuccess } from "./user.actions"; import UserActionTypes from "./user.types"; -import client from "../../utils/GraphQLClient"; -import { QUERY_EULA } from "../../graphql/bodyshop.queries"; -import day from "../../utils/day"; -import InstanceRenderManager from "../../utils/instanceRenderMgr"; -import { Userpilot } from "userpilot"; const fpPromise = FingerprintJS.load(); @@ -310,10 +310,41 @@ export function* SetAuthLevelFromShopDetails({ payload }) { updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false }) ); + const user = yield select((state) => state.user.currentUser); if (payload.features.singleDeviceOnly) { - const user = yield select((state) => state.user.currentUser); + if ( + !( + user.email.includes("@imex.") || + user.email.includes("@rome.") || + user.email.includes("@rometech.") || + user.email.includes("@promanager.") + ) + ) + yield put(setInstanceId(user.uid)); + } - if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) yield put(setInstanceId(user.uid)); + //For Rome, check to make sure it's not a PM shop. + try { + InstanceRenderManager({ + executeFunction: true, + args: [], + rome: () => { + if ( + payload.imexshopid.toLowerCase().startsWith("pm_") && + !( + user.email.includes("@imex.") || + user.email.includes("@rome.") || + user.email.includes("@rometech.") || + user.email.includes("@promanager.") + ) + ) { + throw new Error("You are not authorized to use this application."); + } + }, + promanager: () => {} + }); + } catch (error) { + yield put(setInstanceConflict()); } try { diff --git a/client/src/utils/fcm-handler.js b/client/src/utils/fcm-handler.js index 0b44abb49..c6284c764 100644 --- a/client/src/utils/fcm-handler.js +++ b/client/src/utils/fcm-handler.js @@ -1,4 +1,5 @@ export default async function FcmHandler({ client, payload }) { + console.log("FCM", payload); switch (payload.type) { case "messaging-inbound": client.cache.modify({ diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js index c7cc7d365..bebc05ade 100644 --- a/server/intellipay/intellipay.js +++ b/server/intellipay/intellipay.js @@ -145,19 +145,19 @@ exports.generate_payment_url = async (req, res) => { }; exports.postback = async (req, res) => { - logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body); - const { body: values } = req; - - const comment = Buffer.from(values?.comment, "base64").toString(); - - if ((!values.invoice || values.invoice === "") && !comment) { - //invoice is specified through the pay link. Comment by IO. - logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body); - res.sendStatus(200); - return; - } - try { + logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body); + const { body: values } = req; + + const comment = Buffer.from(values?.comment, "base64").toString(); + + if ((!values.invoice || values.invoice === "") && !comment) { + //invoice is specified through the pay link. Comment by IO. + logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body); + res.sendStatus(200); + return; + } + if (values.invoice) { //This is a link email that's been sent out. const job = await gqlClient.request(queries.GET_JOB_BY_PK, { diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 7617099ff..c8bcdf2a8 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -718,6 +718,7 @@ function CalculateTaxesTotals(job, otherTotals) { const taxableAmounts = { PAA: Dinero(), + PAE: Dinero(), PAN: Dinero(), PAL: Dinero(), PAR: Dinero(),