diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index aaa4398c9..4bfad39ac 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -5186,6 +5186,27 @@ + + submitticket + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -5275,6 +5296,48 @@ + + errors + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + exceptiontitle + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + in false @@ -5663,6 +5726,27 @@ messages + + exception + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + unsavedchanges false diff --git a/client/public/firebase-messaging-sw.js b/client/public/firebase-messaging-sw.js index a0ad54337..083f63d12 100644 --- a/client/public/firebase-messaging-sw.js +++ b/client/public/firebase-messaging-sw.js @@ -3,28 +3,28 @@ importScripts( "https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js" ); -// firebase.initializeApp({ -// 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({ - 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", + 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({ +// 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", +// }); + const messaging = firebase.messaging(); //Handles Background Messages diff --git a/client/public/manifest.json b/client/public/manifest.json index 8577a90e4..d7008ba45 100644 --- a/client/public/manifest.json +++ b/client/public/manifest.json @@ -1,7 +1,7 @@ { - "short_name": "Bodyshop.app", - "name": "Bodyshop Management System", - "description": "The ultimate bodyshop management system", + "short_name": "ImEX Online", + "name": "ImEX Online", + "description": "The ultimate bodyshop management system.", "icons": [ { "src": "favicon.ico", diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 88cb569d8..0733204cd 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -10,7 +10,7 @@ import { RetryLink } from "apollo-link-retry"; import { WebSocketLink } from "apollo-link-ws"; import { getMainDefinition } from "apollo-utilities"; import LogRocket from "logrocket"; -import React, { Component } from "react"; +import React from "react"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component"; import { auth } from "../firebase/firebase.utils"; import errorLink from "../graphql/apollo-error-handling"; @@ -28,7 +28,6 @@ const wsLink = new WebSocketLink({ lazy: true, reconnect: true, connectionParams: async () => { - //const token = localStorage.getItem("token"); const token = auth.currentUser && (await auth.currentUser.getIdToken(true)); if (token) { @@ -110,26 +109,18 @@ if (process.env.NODE_ENV === "development") { middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link)))); const cache = new InMemoryCache(); + export const client = new ApolloClient({ link: ApolloLink.from(middlewares), cache, - connectToDevTools: true, + connectToDevTools: process.env.NODE_ENV !== "production", }); -export default class AppContainer extends Component { - constructor() { - super(); - this.state = { client }; - } - - render() { - const { client } = this.state; - - return ( - - - - - ); - } +export default function AppContainer() { + return ( + + + + + ); } diff --git a/client/src/App/App.css b/client/src/App/App.css deleted file mode 100644 index bc8c8182d..000000000 --- a/client/src/App/App.css +++ /dev/null @@ -1 +0,0 @@ -@import "~antd/dist/antd.css"; \ No newline at end of file diff --git a/client/src/App/App.js b/client/src/App/App.js index e071f0a85..c232f0efb 100644 --- a/client/src/App/App.js +++ b/client/src/App/App.js @@ -8,9 +8,8 @@ import ErrorBoundary from "../components/error-boundary/error-boundary.component import LoadingSpinner from "../components/loading-spinner/loading-spinner.component"; import { checkUserSession } from "../redux/user/user.actions"; import { selectCurrentUser } from "../redux/user/user.selectors"; -// import { QUERY_BODYSHOP } from "../graphql/bodyshop.queries"; import PrivateRoute from "../utils/private-route"; -import "./App.css"; +import "antd/dist/antd.css"; const LandingPage = lazy(() => import("../pages/landing/landing.page")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); @@ -27,13 +26,9 @@ const mapDispatchToProps = (dispatch) => ({ checkUserSession: () => dispatch(checkUserSession()), }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(({ checkUserSession, currentUser }) => { +export function App({ checkUserSession, currentUser }) { useEffect(() => { checkUserSession(); - return () => {}; }, [checkUserSession]); const { t } = useTranslation(); @@ -61,4 +56,6 @@ export default connect( ); -}); +} + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/client/src/components/error-boundary/error-boundary.component.jsx b/client/src/components/error-boundary/error-boundary.component.jsx index 7648ee07c..7a28e5edd 100644 --- a/client/src/components/error-boundary/error-boundary.component.jsx +++ b/client/src/components/error-boundary/error-boundary.component.jsx @@ -1,31 +1,65 @@ import React from "react"; +import { withTranslation } from "react-i18next"; +import { Result, Button, Collapse, Row, Col, Space } from "antd"; class ErrorBoundary extends React.Component { constructor() { super(); this.state = { hasErrored: false, - error: null + error: null, }; } static getDerivedStateFromError(error) { - //process the error - console.log("error", error); return { hasErrored: true, error }; } componentDidCatch(error, info) { - console.log("error", error); - console.log("info", info); + console.log("Exception Caught by Error Boundary.", error, info); } render() { + const { t } = this.props; if (this.state.hasErrored === true) { - return
Uh oh, something went wrong.
; + return ( +
+ + + + + } + /> + + + + + {JSON.stringify(this.state.error || "")} + + + + +
+ ); } else { return this.props.children; } } } -export default ErrorBoundary; +export default withTranslation()(ErrorBoundary); diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 7d5f2b84d..1a49d905f 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -1,26 +1,32 @@ import Icon, { CarFilled, + DollarCircleFilled, FileAddFilled, FileFilled, GlobalOutlined, HomeFilled, TeamOutlined, - DollarCircleFilled, } from "@ant-design/icons"; -import { Avatar, Col, Menu, Row } from "antd"; +import { Avatar, Col, Menu, Row, Layout } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { FaCalendarAlt, FaCarCrash } from "react-icons/fa"; -import { Link } from "react-router-dom"; -import UserImage from "../../assets/User.svg"; -import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component"; - import { connect } from "react-redux"; +import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; +import UserImage from "../../assets/User.svg"; import { setModalContext } from "../../redux/modals/modals.actions"; +import { signOutStart } from "../../redux/user/user.actions"; +import { + selectBodyshop, + selectCurrentUser, +} from "../../redux/user/user.selectors"; + const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, }); + const mapDispatchToProps = (dispatch) => ({ setInvoiceEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), @@ -28,12 +34,11 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setModalContext({ context: context, modal: "timeTicket" })), setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), + signOutStart: () => dispatch(signOutStart()), }); function Header({ - landingHeader, - selectedNavItem, - logo, + bodyshop, handleMenuClick, currentUser, signOutStart, @@ -42,79 +47,24 @@ function Header({ setPaymentContext, }) { const { t } = useTranslation(); - //TODO Add return ( - - {logo ? ( - - Shop Logo + + + + {bodyshop - ) : null} - - {landingHeader ? ( - - - - - - {currentUser.displayName || t("general.labels.unknown")} - - } - > - signOutStart()}> - {t("user.actions.signout")} - - - - {t("menus.currentuser.profile")} - - - - - {t("menus.currentuser.languageselector")} - - } - > - - {t("general.languages.english")} - - - {t("general.languages.french")} - - - {t("general.languages.spanish")} - - - - - ) : ( - - - + + + + {t("menus.header.home")} @@ -125,41 +75,40 @@ function Header({ {t("menus.header.jobs")} - } - > - - + }> + + {t("menus.header.schedule")} - - + + {t("menus.header.productionlist")} - - {t("menus.header.activejobs")} + + {t("menus.header.activejobs")} - - {t("menus.header.alljobs")} + + {t("menus.header.alljobs")} - - + + {t("menus.header.availablejobs")} - - + + {t("menus.header.owners")} - - + + {t("menus.header.vehicles")} @@ -172,22 +121,21 @@ function Header({ {t("menus.header.courtesycars")} - } - > - - + }> + + {t("menus.header.courtesycars-all")} - - + + {t("menus.header.courtesycars-contracts")} - - + + {t("menus.header.courtesycars-newcontract")} @@ -200,82 +148,81 @@ function Header({ {t("menus.header.accounting")} - } - > + }> { setPaymentContext({ actions: {}, context: {}, }); - }} - > + }}> {t("menus.header.enterpayment")} { setInvoiceEnterContext({ actions: {}, context: {}, }); - }} - > + }}> {t("menus.header.enterinvoices")} - - {t("menus.header.invoices")} + + {t("menus.header.invoices")} { setTimeTicketContext({ actions: {}, context: {}, }); - }} - > + }}> {t("menus.header.entertimeticket")} - - + + {t("menus.header.accounting-receivables")} - - + + {t("menus.header.accounting-payables")} - - {t("menus.header.shop_config")} + + {t("menus.header.shop_config")} - - + + {t("menus.header.shop_templates")} - - + + {t("menus.header.shop_vendors")} - - {t("menus.header.shop_csi")} + + {t("menus.header.shop_csi")} - + + + + {currentUser.displayName || t("general.labels.unknown")} - } - > + }> signOutStart()}> {t("user.actions.signout")} - + {t("menus.currentuser.profile")} @@ -299,23 +245,22 @@ function Header({ {t("menus.currentuser.languageselector")} - } - > - + }> + {t("general.languages.english")} - + {t("general.languages.french")} - + {t("general.languages.spanish")} - )} - - + + + ); } diff --git a/client/src/components/header/header.container.jsx b/client/src/components/header/header.container.jsx index 77a5feb4c..0ba66116c 100644 --- a/client/src/components/header/header.container.jsx +++ b/client/src/components/header/header.container.jsx @@ -1,35 +1,15 @@ -import React from "react"; -import HeaderComponent from "./header.component"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; import i18next from "i18next"; -import { setUserLanguage, signOutStart } from "../../redux/user/user.actions"; -import { - selectCurrentUser, - selectBodyshop -} from "../../redux/user/user.selectors"; +import React from "react"; +import { connect } from "react-redux"; +import { setUserLanguage } from "../../redux/user/user.actions"; +import HeaderComponent from "./header.component"; -const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop +const mapDispatchToProps = (dispatch) => ({ + setUserLanguage: (language) => dispatch(setUserLanguage(language)), }); -const mapDispatchToProps = dispatch => ({ - signOutStart: () => dispatch(signOutStart()), - setUserLanguage: language => dispatch(setUserLanguage(language)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(function HeaderContainer({ - landingHeader, - currentUser, - bodyshop, - signOutStart, - setUserLanguage -}) { - const handleMenuClick = e => { +export function HeaderContainer({ setUserLanguage }) { + const handleMenuClick = (e) => { if (e.item.props.actiontype === "lang-select") { i18next.changeLanguage(e.key, (err, t) => { if (err) @@ -39,14 +19,7 @@ export default connect( } }; - return ( - - ); -}); + return ; +} + +export default connect(null, mapDispatchToProps)(HeaderContainer); diff --git a/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx b/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx index 5b0e60a72..5fa6a359c 100644 --- a/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx +++ b/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx @@ -6,22 +6,20 @@ import { createStructuredSelector } from "reselect"; import { selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser + currentUser: selectCurrentUser, }); -export default connect( - mapStateToProps, - null -)(function ManageSignInButton({ currentUser }) { +export function ManageSignInButton({ currentUser }) { return currentUser.authorized ? ( - + Manage ) : ( - + Sign In ); -}); +} +export default connect(mapStateToProps, null)(ManageSignInButton); diff --git a/client/src/firebase/firebase.utils.js b/client/src/firebase/firebase.utils.js index d3bc91fe0..46feef523 100644 --- a/client/src/firebase/firebase.utils.js +++ b/client/src/firebase/firebase.utils.js @@ -4,6 +4,7 @@ import "firebase/auth"; import "firebase/database"; import "firebase/analytics"; import "firebase/messaging"; +import { store } from "../redux/store"; const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG); firebase.initializeApp(config); @@ -45,3 +46,17 @@ try { } export { messaging }; + +export const logImEXEvent = async (eventName, additionalParams) => { + const state = store.getState(); + const eventParams = { + shop: + (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || + null, + user: + (state.user && state.user.currentUser && state.user.currentUser.email) || + null, + ...additionalParams, + }; + analytics.logEvent(eventName, eventParams); +}; diff --git a/client/src/pages/landing/landing.page.jsx b/client/src/pages/landing/landing.page.jsx index ba6914ed5..a7c04163d 100644 --- a/client/src/pages/landing/landing.page.jsx +++ b/client/src/pages/landing/landing.page.jsx @@ -1,19 +1,13 @@ +import { Layout, Typography } from "antd"; import React from "react"; -import { Typography, Layout } from "antd"; - -import HeaderContainer from "../../components/header/header.container"; +import ManageSignInButton from "../../components/manage-sign-in-button/manage-sign-in-button.component"; export default function LandingPage() { - const { Header, Content } = Layout; return ( - -
- -
+ + - - ImEX.Online - + ImEX.Online ); } diff --git a/client/src/pages/manage-root/manage-root.page.component.jsx b/client/src/pages/manage-root/manage-root.page.component.jsx index 9cecd0e9c..28daf94c2 100644 --- a/client/src/pages/manage-root/manage-root.page.component.jsx +++ b/client/src/pages/manage-root/manage-root.page.component.jsx @@ -1,7 +1,7 @@ import React from "react"; import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component"; import Test from "../../components/_test/test.component"; -import { analytics } from "../../firebase/firebase.utils"; +import { analytics, logImEXEvent } from "../../firebase/firebase.utils"; export default function ManageRootPageComponent() { //const client = useApolloClient(); @@ -10,34 +10,26 @@ export default function ManageRootPageComponent() { + - { - // - // Send an Email in new Window - // - } ); } diff --git a/client/src/pages/manage-root/manage-root.page.container.jsx b/client/src/pages/manage-root/manage-root.page.container.jsx index ffab7ffd0..c8627fc7e 100644 --- a/client/src/pages/manage-root/manage-root.page.container.jsx +++ b/client/src/pages/manage-root/manage-root.page.container.jsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import ManageRootPageComponent from "./manage-root.page.component"; import { useTranslation } from "react-i18next"; + export default function ManageRootPageContainer() { const { t } = useTranslation(); useEffect(() => { diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 91725ce21..9b9667899 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -128,7 +128,6 @@ const stripePromise = new Promise((resolve, reject) => { const mapStateToProps = createStructuredSelector({ conflict: selectInstanceConflict, }); -const mapDispatchToProps = (dispatch) => ({}); export function Manage({ match, conflict }) { const { t } = useTranslation(); @@ -139,186 +138,172 @@ export function Manage({ match, conflict }) { return ( -
- -
- - - - - {conflict ? ( - - ) : ( - - } - > - - - - - - - - + + + + + + {conflict ? ( + + ) : ( + + }> + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + - - - + + + + + + + + + + + + + + + + + + + + )} + + - - - - - - - - - - - - - - - - - - - - - - - )} - - - @@ -327,4 +312,4 @@ export function Manage({ match, conflict }) {
); } -export default connect(mapStateToProps, mapDispatchToProps)(Manage); +export default connect(mapStateToProps, null)(Manage); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 3a8435fac..840300590 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -349,13 +349,16 @@ "reset": "Reset to original.", "save": "Save", "saveandnew": "Save and New", - "submit": "Submit" + "submit": "Submit", + "submitticket": "Submit a Support Ticket" }, "labels": { "actions": "Actions", "areyousure": "Are you sure?", "barcode": "Barcode", "email": "Email", + "errors": "Errors", + "exceptiontitle": "An error has occurred.", "in": "In", "instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.", "instanceconflictitle": "Your account is being used elsewhere.", @@ -378,6 +381,7 @@ "spanish": "Spanish" }, "messages": { + "exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.", "unsavedchanges": "You have unsaved changes." }, "validation": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 8ec75bea0..4c7aee184 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -349,13 +349,16 @@ "reset": "Restablecer a original.", "save": "Salvar", "saveandnew": "", - "submit": "" + "submit": "", + "submitticket": "" }, "labels": { "actions": "Comportamiento", "areyousure": "", "barcode": "código de barras", "email": "", + "errors": "", + "exceptiontitle": "", "in": "en", "instanceconflictext": "", "instanceconflictitle": "", @@ -378,6 +381,7 @@ "spanish": "español" }, "messages": { + "exception": "", "unsavedchanges": "Usted tiene cambios no guardados." }, "validation": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 787815d96..0fce3ad8c 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -349,13 +349,16 @@ "reset": "Rétablir l'original.", "save": "sauvegarder", "saveandnew": "", - "submit": "" + "submit": "", + "submitticket": "" }, "labels": { "actions": "actes", "areyousure": "", "barcode": "code à barre", "email": "", + "errors": "", + "exceptiontitle": "", "in": "dans", "instanceconflictext": "", "instanceconflictitle": "", @@ -378,6 +381,7 @@ "spanish": "Espanol" }, "messages": { + "exception": "", "unsavedchanges": "Vous avez des changements non enregistrés." }, "validation": {