Added better error boundary page. Started some CSS and layout refactoring. **Major Changes**.

This commit is contained in:
Patrick Fic
2020-06-10 17:52:38 -07:00
parent 52849e1af7
commit afbec7d79e
18 changed files with 475 additions and 455 deletions

View File

@@ -5186,6 +5186,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>submitticket</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -5275,6 +5296,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>errors</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>exceptiontitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>in</name> <name>in</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -5663,6 +5726,27 @@
<folder_node> <folder_node>
<name>messages</name> <name>messages</name>
<children> <children>
<concept_node>
<name>exception</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>unsavedchanges</name> <name>unsavedchanges</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -3,28 +3,28 @@ importScripts(
"https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js" "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({ firebase.initializeApp({
apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc", apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-dev.firebaseapp.com", authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-dev.firebaseio.com", databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-dev", projectId: "imex-prod",
storageBucket: "imex-dev.appspot.com", storageBucket: "imex-prod.appspot.com",
messagingSenderId: "759548147434", messagingSenderId: "253497221485",
appId: "1:759548147434:web:e8239868a48ceb36700993", appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-K5XRBVVB4S", 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(); const messaging = firebase.messaging();
//Handles Background Messages //Handles Background Messages

View File

@@ -1,7 +1,7 @@
{ {
"short_name": "Bodyshop.app", "short_name": "ImEX Online",
"name": "Bodyshop Management System", "name": "ImEX Online",
"description": "The ultimate bodyshop management system", "description": "The ultimate bodyshop management system.",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",

View File

@@ -10,7 +10,7 @@ import { RetryLink } from "apollo-link-retry";
import { WebSocketLink } from "apollo-link-ws"; import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities"; import { getMainDefinition } from "apollo-utilities";
import LogRocket from "logrocket"; 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 GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling"; import errorLink from "../graphql/apollo-error-handling";
@@ -28,7 +28,6 @@ const wsLink = new WebSocketLink({
lazy: true, lazy: true,
reconnect: true, reconnect: true,
connectionParams: async () => { connectionParams: async () => {
//const token = localStorage.getItem("token");
const token = const token =
auth.currentUser && (await auth.currentUser.getIdToken(true)); auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) { if (token) {
@@ -110,26 +109,18 @@ if (process.env.NODE_ENV === "development") {
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link)))); middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
const cache = new InMemoryCache(); const cache = new InMemoryCache();
export const client = new ApolloClient({ export const client = new ApolloClient({
link: ApolloLink.from(middlewares), link: ApolloLink.from(middlewares),
cache, cache,
connectToDevTools: true, connectToDevTools: process.env.NODE_ENV !== "production",
}); });
export default class AppContainer extends Component { export default function AppContainer() {
constructor() { return (
super(); <ApolloProvider client={client}>
this.state = { client }; <GlobalLoadingBar />
} <App />
</ApolloProvider>
render() { );
const { client } = this.state;
return (
<ApolloProvider client={client}>
<GlobalLoadingBar />
<App />
</ApolloProvider>
);
}
} }

View File

@@ -1 +0,0 @@
@import "~antd/dist/antd.css";

View File

@@ -8,9 +8,8 @@ import ErrorBoundary from "../components/error-boundary/error-boundary.component
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
import { checkUserSession } from "../redux/user/user.actions"; import { checkUserSession } from "../redux/user/user.actions";
import { selectCurrentUser } from "../redux/user/user.selectors"; import { selectCurrentUser } from "../redux/user/user.selectors";
// import { QUERY_BODYSHOP } from "../graphql/bodyshop.queries";
import PrivateRoute from "../utils/private-route"; import PrivateRoute from "../utils/private-route";
import "./App.css"; import "antd/dist/antd.css";
const LandingPage = lazy(() => import("../pages/landing/landing.page")); const LandingPage = lazy(() => import("../pages/landing/landing.page"));
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
@@ -27,13 +26,9 @@ const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()), checkUserSession: () => dispatch(checkUserSession()),
}); });
export default connect( export function App({ checkUserSession, currentUser }) {
mapStateToProps,
mapDispatchToProps
)(({ checkUserSession, currentUser }) => {
useEffect(() => { useEffect(() => {
checkUserSession(); checkUserSession();
return () => {};
}, [checkUserSession]); }, [checkUserSession]);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -61,4 +56,6 @@ export default connect(
</Switch> </Switch>
</div> </div>
); );
}); }
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@@ -1,31 +1,65 @@
import React from "react"; import React from "react";
import { withTranslation } from "react-i18next";
import { Result, Button, Collapse, Row, Col, Space } from "antd";
class ErrorBoundary extends React.Component { class ErrorBoundary extends React.Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
hasErrored: false, hasErrored: false,
error: null error: null,
}; };
} }
static getDerivedStateFromError(error) { static getDerivedStateFromError(error) {
//process the error
console.log("error", error);
return { hasErrored: true, error }; return { hasErrored: true, error };
} }
componentDidCatch(error, info) { componentDidCatch(error, info) {
console.log("error", error); console.log("Exception Caught by Error Boundary.", error, info);
console.log("info", info);
} }
render() { render() {
const { t } = this.props;
if (this.state.hasErrored === true) { if (this.state.hasErrored === true) {
return <div>Uh oh, something went wrong.</div>; return (
<div>
<Result
status='500'
title={t("general.labels.exceptiontitle")}
subTitle={t("general.messages.exception")}
extra={
<Space>
<Button
type='primary'
onClick={() => {
window.location.reload();
}}>
{t("general.actions.refresh")}
</Button>
<Button
onClick={() => {
alert("Not implemented yet.");
}}>
{t("general.actions.submitticket")}
</Button>
</Space>
}
/>
<Row>
<Col offset={6} span={12}>
<Collapse bordered={false}>
<Collapse.Panel header={t("general.labels.errors")}>
{JSON.stringify(this.state.error || "")}
</Collapse.Panel>
</Collapse>
</Col>
</Row>
</div>
);
} else { } else {
return this.props.children; return this.props.children;
} }
} }
} }
export default ErrorBoundary; export default withTranslation()(ErrorBoundary);

View File

@@ -1,26 +1,32 @@
import Icon, { import Icon, {
CarFilled, CarFilled,
DollarCircleFilled,
FileAddFilled, FileAddFilled,
FileFilled, FileFilled,
GlobalOutlined, GlobalOutlined,
HomeFilled, HomeFilled,
TeamOutlined, TeamOutlined,
DollarCircleFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Avatar, Col, Menu, Row } from "antd"; import { Avatar, Col, Menu, Row, Layout } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaCalendarAlt, FaCarCrash } from "react-icons/fa"; 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 { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import UserImage from "../../assets/User.svg";
import { setModalContext } from "../../redux/modals/modals.actions"; 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({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: (context) => setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
@@ -28,12 +34,11 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "timeTicket" })), dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setPaymentContext: (context) => setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })), dispatch(setModalContext({ context: context, modal: "payment" })),
signOutStart: () => dispatch(signOutStart()),
}); });
function Header({ function Header({
landingHeader, bodyshop,
selectedNavItem,
logo,
handleMenuClick, handleMenuClick,
currentUser, currentUser,
signOutStart, signOutStart,
@@ -42,79 +47,24 @@ function Header({
setPaymentContext, setPaymentContext,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
//TODO Add
return ( return (
<Row type="flex" justify="space-around" align="middle"> <Layout.Header>
{logo ? ( <Row align='middle'>
<Col span={3}> <Col span={4}>
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} /> <img
alt={bodyshop ? bodyshop.shopname : "ImEX Online Logo"}
src={
bodyshop && bodyshop.logo_img_path
? bodyshop.logo_img_path
: "./logo192.png"
}
/>
</Col> </Col>
) : null} <Col span={16}>
<Col span={21}> <Menu mode='horizontal' onClick={handleMenuClick}>
{landingHeader ? ( <Menu.Item key='home'>
<Menu <Link to='/manage'>
theme="dark"
className="header"
selectedKeys={selectedNavItem}
mode="horizontal"
onClick={handleMenuClick}
>
<ManageSignInButton />
<Menu.SubMenu
title={
<div>
<Avatar
size="medium"
alt="Avatar"
src={
currentUser.photoURL ? currentUser.photoURL : UserImage
}
style={{ margin: "10px" }}
/>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}
>
<Menu.Item onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item>
<Link to="/manage/profile">
{t("menus.currentuser.profile")}
</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
</Menu>
) : (
<Menu
theme="dark"
className="header"
selectedKeys={selectedNavItem}
mode="horizontal"
onClick={handleMenuClick}
>
<Menu.Item key="home">
<Link to="/manage">
<HomeFilled /> <HomeFilled />
{t("menus.header.home")} {t("menus.header.home")}
</Link> </Link>
@@ -125,41 +75,40 @@ function Header({
<Icon component={FaCarCrash} /> <Icon component={FaCarCrash} />
<span>{t("menus.header.jobs")}</span> <span>{t("menus.header.jobs")}</span>
</span> </span>
} }>
> <Menu.Item key='schedule'>
<Menu.Item key="schedule"> <Link to='/manage/schedule'>
<Link to="/manage/schedule">
<Icon component={FaCalendarAlt} /> <Icon component={FaCalendarAlt} />
{t("menus.header.schedule")} {t("menus.header.schedule")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="production"> <Menu.Item key='production'>
<Link to="/manage/production/list"> <Link to='/manage/production/list'>
<Icon component={FaCalendarAlt} /> <Icon component={FaCalendarAlt} />
{t("menus.header.productionlist")} {t("menus.header.productionlist")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="activejobs"> <Menu.Item key='activejobs'>
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link> <Link to='/manage/jobs'>{t("menus.header.activejobs")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="alljobs"> <Menu.Item key='alljobs'>
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link> <Link to='/manage/jobs/all'>{t("menus.header.alljobs")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="availablejobs"> <Menu.Item key='availablejobs'>
<Link to="/manage/available"> <Link to='/manage/available'>
{t("menus.header.availablejobs")} {t("menus.header.availablejobs")}
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}> <Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item key="owners"> <Menu.Item key='owners'>
<Link to="/manage/owners"> <Link to='/manage/owners'>
<TeamOutlined /> <TeamOutlined />
{t("menus.header.owners")} {t("menus.header.owners")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="vehicles"> <Menu.Item key='vehicles'>
<Link to="/manage/vehicles"> <Link to='/manage/vehicles'>
<CarFilled /> <CarFilled />
{t("menus.header.vehicles")} {t("menus.header.vehicles")}
</Link> </Link>
@@ -172,22 +121,21 @@ function Header({
<CarFilled /> <CarFilled />
<span>{t("menus.header.courtesycars")}</span> <span>{t("menus.header.courtesycars")}</span>
</span> </span>
} }>
> <Menu.Item key='courtesycarsall'>
<Menu.Item key="courtesycarsall"> <Link to='/manage/courtesycars'>
<Link to="/manage/courtesycars">
<CarFilled /> <CarFilled />
{t("menus.header.courtesycars-all")} {t("menus.header.courtesycars-all")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="contracts"> <Menu.Item key='contracts'>
<Link to="/manage/courtesycars/contracts"> <Link to='/manage/courtesycars/contracts'>
<FileFilled /> <FileFilled />
{t("menus.header.courtesycars-contracts")} {t("menus.header.courtesycars-contracts")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="newcontract"> <Menu.Item key='newcontract'>
<Link to="/manage/courtesycars/contracts/new"> <Link to='/manage/courtesycars/contracts/new'>
<FileAddFilled /> <FileAddFilled />
{t("menus.header.courtesycars-newcontract")} {t("menus.header.courtesycars-newcontract")}
</Link> </Link>
@@ -200,82 +148,81 @@ function Header({
<DollarCircleFilled /> <DollarCircleFilled />
<span>{t("menus.header.accounting")}</span> <span>{t("menus.header.accounting")}</span>
</span> </span>
} }>
>
<Menu.Item <Menu.Item
key="enterpayments" key='enterpayments'
onClick={() => { onClick={() => {
setPaymentContext({ setPaymentContext({
actions: {}, actions: {},
context: {}, context: {},
}); });
}} }}>
>
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="enterinvoices" key='enterinvoices'
onClick={() => { onClick={() => {
setInvoiceEnterContext({ setInvoiceEnterContext({
actions: {}, actions: {},
context: {}, context: {},
}); });
}} }}>
>
{t("menus.header.enterinvoices")} {t("menus.header.enterinvoices")}
</Menu.Item> </Menu.Item>
<Menu.Item key="invoices"> <Menu.Item key='invoices'>
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link> <Link to='/manage/invoices'>{t("menus.header.invoices")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="entertimetickets" key='entertimetickets'
onClick={() => { onClick={() => {
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: {}, context: {},
}); });
}} }}>
>
{t("menus.header.entertimeticket")} {t("menus.header.entertimeticket")}
</Menu.Item> </Menu.Item>
<Menu.Item key="receivables"> <Menu.Item key='receivables'>
<Link to="/manage/accounting/receivables"> <Link to='/manage/accounting/receivables'>
{t("menus.header.accounting-receivables")} {t("menus.header.accounting-receivables")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="payables"> <Menu.Item key='payables'>
<Link to="/manage/accounting/payables"> <Link to='/manage/accounting/payables'>
{t("menus.header.accounting-payables")} {t("menus.header.accounting-payables")}
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}> <Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop"> <Menu.Item key='shop'>
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link> <Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-templates"> <Menu.Item key='shop-templates'>
<Link to="/manage/shop/templates"> <Link to='/manage/shop/templates'>
{t("menus.header.shop_templates")} {t("menus.header.shop_templates")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-vendors"> <Menu.Item key='shop-vendors'>
<Link to="/manage/shop/vendors"> <Link to='/manage/shop/vendors'>
{t("menus.header.shop_vendors")} {t("menus.header.shop_vendors")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-csi"> <Menu.Item key='shop-csi'>
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link> <Link to='/manage/shop/csi'>{t("menus.header.shop_csi")}</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu>
</Col>
<Col span={4}>
<Menu>
<Menu.SubMenu <Menu.SubMenu
title={ title={
<div> <div>
<Avatar <Avatar
size="medium" size='medium'
alt="Avatar" alt='Avatar'
src={ src={
currentUser.photoURL ? currentUser.photoURL : UserImage currentUser.photoURL ? currentUser.photoURL : UserImage
} }
@@ -283,13 +230,12 @@ function Header({
/> />
{currentUser.displayName || t("general.labels.unknown")} {currentUser.displayName || t("general.labels.unknown")}
</div> </div>
} }>
>
<Menu.Item onClick={() => signOutStart()}> <Menu.Item onClick={() => signOutStart()}>
{t("user.actions.signout")} {t("user.actions.signout")}
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<Link to="/manage/profile"> <Link to='/manage/profile'>
{t("menus.currentuser.profile")} {t("menus.currentuser.profile")}
</Link> </Link>
</Menu.Item> </Menu.Item>
@@ -299,23 +245,22 @@ function Header({
<GlobalOutlined /> <GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span> <span>{t("menus.currentuser.languageselector")}</span>
</span> </span>
} }>
> <Menu.Item actiontype='lang-select' key='en-US'>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")} {t("general.languages.english")}
</Menu.Item> </Menu.Item>
<Menu.Item actiontype="lang-select" key="fr-CA"> <Menu.Item actiontype='lang-select' key='fr-CA'>
{t("general.languages.french")} {t("general.languages.french")}
</Menu.Item> </Menu.Item>
<Menu.Item actiontype="lang-select" key="es-MX"> <Menu.Item actiontype='lang-select' key='es-MX'>
{t("general.languages.spanish")} {t("general.languages.spanish")}
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
)} </Col>
</Col> </Row>
</Row> </Layout.Header>
); );
} }

View File

@@ -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 i18next from "i18next";
import { setUserLanguage, signOutStart } from "../../redux/user/user.actions"; import React from "react";
import { import { connect } from "react-redux";
selectCurrentUser, import { setUserLanguage } from "../../redux/user/user.actions";
selectBodyshop import HeaderComponent from "./header.component";
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({
currentUser: selectCurrentUser, setUserLanguage: (language) => dispatch(setUserLanguage(language)),
bodyshop: selectBodyshop
}); });
const mapDispatchToProps = dispatch => ({ export function HeaderContainer({ setUserLanguage }) {
signOutStart: () => dispatch(signOutStart()), const handleMenuClick = (e) => {
setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function HeaderContainer({
landingHeader,
currentUser,
bodyshop,
signOutStart,
setUserLanguage
}) {
const handleMenuClick = e => {
if (e.item.props.actiontype === "lang-select") { if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => { i18next.changeLanguage(e.key, (err, t) => {
if (err) if (err)
@@ -39,14 +19,7 @@ export default connect(
} }
}; };
return ( return <HeaderComponent handleMenuClick={handleMenuClick} />;
<HeaderComponent }
handleMenuClick={handleMenuClick}
signOutStart={signOutStart} export default connect(null, mapDispatchToProps)(HeaderContainer);
landingHeader={landingHeader}
selectedNavItem={null}
currentUser={currentUser}
logo={bodyshop ? bodyshop.logo_img_path : null}
/>
);
});

View File

@@ -6,22 +6,20 @@ import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser currentUser: selectCurrentUser,
}); });
export default connect( export function ManageSignInButton({ currentUser }) {
mapStateToProps,
null
)(function ManageSignInButton({ currentUser }) {
return currentUser.authorized ? ( return currentUser.authorized ? (
<Link to="/manage"> <Link to='/manage'>
<BuildFilled /> <BuildFilled />
Manage Manage
</Link> </Link>
) : ( ) : (
<Link to="/signin"> <Link to='/signin'>
<LoginOutlined /> <LoginOutlined />
Sign In Sign In
</Link> </Link>
); );
}); }
export default connect(mapStateToProps, null)(ManageSignInButton);

View File

@@ -4,6 +4,7 @@ import "firebase/auth";
import "firebase/database"; import "firebase/database";
import "firebase/analytics"; import "firebase/analytics";
import "firebase/messaging"; import "firebase/messaging";
import { store } from "../redux/store";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG); const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
firebase.initializeApp(config); firebase.initializeApp(config);
@@ -45,3 +46,17 @@ try {
} }
export { messaging }; 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);
};

View File

@@ -1,19 +1,13 @@
import { Layout, Typography } from "antd";
import React from "react"; import React from "react";
import { Typography, Layout } from "antd"; import ManageSignInButton from "../../components/manage-sign-in-button/manage-sign-in-button.component";
import HeaderContainer from "../../components/header/header.container";
export default function LandingPage() { export default function LandingPage() {
const { Header, Content } = Layout;
return ( return (
<Layout style={{ minHeight: "100vh" }}> <Layout style={{ height: "100vh" }}>
<Header> <ManageSignInButton />
<HeaderContainer landingHeader />
</Header>
<Content className='content-container' style={{ padding: "0em 4em 4em" }}> <Typography.Title>ImEX.Online</Typography.Title>
<Typography.Title>ImEX.Online</Typography.Title>
</Content>
</Layout> </Layout>
); );
} }

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component"; import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";
import Test from "../../components/_test/test.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() { export default function ManageRootPageComponent() {
//const client = useApolloClient(); //const client = useApolloClient();
@@ -10,34 +10,26 @@ export default function ManageRootPageComponent() {
<Test /> <Test />
<button <button
onClick={() => { onClick={() => {
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
}}>
LogImEXEvent
</button>
<button
onClick={() => {
console.log("Things.");
analytics.logEvent("start_game", { analytics.logEvent("start_game", {
level: "10", level: "10",
difficulty: "expert", difficulty: "expert",
}); });
}} analytics.logEvent("select_content", {
> content_type: "image",
content_id: "P12453",
items: [{ name: "Kittens" }],
});
}}>
Click me to start an event Click me to start an event
</button> </button>
<DashboardGridComponent /> <DashboardGridComponent />
{
// <SendEmailButton
// MessageOptions={{
// from: {
// name: "Kavia"
// },
// to: "patrickwf@gmail.com",
// replyTo: "patrickwf@gmail.com"
// }}
// Template={PartsOrderEmail}
// QueryConfig={[
// REPORT_QUERY_PARTS_ORDER_BY_PK,
// {
// variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
// }
// ]}>
// Send an Email in new Window
// </SendEmailButton>
}
</div> </div>
); );
} }

View File

@@ -1,6 +1,7 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import ManageRootPageComponent from "./manage-root.page.component"; import ManageRootPageComponent from "./manage-root.page.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export default function ManageRootPageContainer() { export default function ManageRootPageContainer() {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {

View File

@@ -128,7 +128,6 @@ const stripePromise = new Promise((resolve, reject) => {
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict, conflict: selectInstanceConflict,
}); });
const mapDispatchToProps = (dispatch) => ({});
export function Manage({ match, conflict }) { export function Manage({ match, conflict }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -139,186 +138,172 @@ export function Manage({ match, conflict }) {
return ( return (
<Layout style={{ minHeight: "100vh" }}> <Layout style={{ minHeight: "100vh" }}>
<Header> <HeaderContainer />
<HeaderContainer />
</Header> <Content className='content-container' style={{ padding: "0em 4em 4em" }}>
<Layout> <FcmNotification />
<Content <ErrorBoundary>
className="content-container" {conflict ? (
style={{ padding: "0em 4em 4em" }} <ConflictComponent />
> ) : (
<FcmNotification /> <Suspense
<ErrorBoundary> fallback={
{conflict ? ( <LoadingSpinner message={t("general.labels.loadingapp")} />
<ConflictComponent /> }>
) : ( <BreadCrumbs />
<Suspense <EnterInvoiceModalContainer />
fallback={ <EmailOverlayContainer />
<LoadingSpinner message={t("general.labels.loadingapp")} /> <TimeTicketModalContainer />
} <PrintCenterModalContainer />
> <Elements stripe={stripePromise}>
<BreadCrumbs /> <PaymentModalContainer />
<EnterInvoiceModalContainer /> </Elements>
<EmailOverlayContainer />
<TimeTicketModalContainer /> <Route exact path={`${match.path}`} component={ManageRootPage} />
<PrintCenterModalContainer /> <Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Elements stripe={stripePromise}>
<PaymentModalContainer /> <Switch>
</Elements> <Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/all`}
component={AllJobs}
/>
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
</Switch>
<Route
exact
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<Route <Route
exact exact
path={`${match.path}`} path={`${match.path}/courtesycars/contracts`}
component={ManageRootPage} component={ContractsList}
/> />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/all`}
component={AllJobs}
/>
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
</Switch>
<Route <Route
exact exact
path={`${match.path}/courtesycars/`} path={`${match.path}/courtesycars/contracts/new`}
component={CourtesyCarsPage} component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/> />
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<Route <Route
exact exact
path={`${match.path}/courtesycars/contracts`} path={`${match.path}/courtesycars/:ccId`}
component={ContractsList} component={CourtesyCarDetailContainer}
/> />
</Switch>
<Route
exact
path={`${match.path}/profile`}
component={ProfilePage}
/>
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
<Route
exact
path={`${match.path}/shop/templates`}
component={ShopTemplates}
/>
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/shop/csi`}
component={ShopCsiPageContainer}
/>
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
<Route
exact
path={`${match.path}/accounting/payables`}
component={AccountingPayables}
/>
</Suspense>
)}
</ErrorBoundary>
</Content>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
/>
</Switch>
<Route
exact
path={`${match.path}/profile`}
component={ProfilePage}
/>
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route
exact
path={`${match.path}/shop/`}
component={ShopPage}
/>
<Route
exact
path={`${match.path}/shop/templates`}
component={ShopTemplates}
/>
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/shop/csi`}
component={ShopCsiPageContainer}
/>
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
<Route
exact
path={`${match.path}/accounting/payables`}
component={AccountingPayables}
/>
</Suspense>
)}
</ErrorBoundary>
</Content>
</Layout>
<Footer> <Footer>
<FooterComponent /> <FooterComponent />
</Footer> </Footer>
@@ -327,4 +312,4 @@ export function Manage({ match, conflict }) {
</Layout> </Layout>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(Manage); export default connect(mapStateToProps, null)(Manage);

View File

@@ -349,13 +349,16 @@
"reset": "Reset to original.", "reset": "Reset to original.",
"save": "Save", "save": "Save",
"saveandnew": "Save and New", "saveandnew": "Save and New",
"submit": "Submit" "submit": "Submit",
"submitticket": "Submit a Support Ticket"
}, },
"labels": { "labels": {
"actions": "Actions", "actions": "Actions",
"areyousure": "Are you sure?", "areyousure": "Are you sure?",
"barcode": "Barcode", "barcode": "Barcode",
"email": "Email", "email": "Email",
"errors": "Errors",
"exceptiontitle": "An error has occurred.",
"in": "In", "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.", "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.", "instanceconflictitle": "Your account is being used elsewhere.",
@@ -378,6 +381,7 @@
"spanish": "Spanish" "spanish": "Spanish"
}, },
"messages": { "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." "unsavedchanges": "You have unsaved changes."
}, },
"validation": { "validation": {

View File

@@ -349,13 +349,16 @@
"reset": "Restablecer a original.", "reset": "Restablecer a original.",
"save": "Salvar", "save": "Salvar",
"saveandnew": "", "saveandnew": "",
"submit": "" "submit": "",
"submitticket": ""
}, },
"labels": { "labels": {
"actions": "Comportamiento", "actions": "Comportamiento",
"areyousure": "", "areyousure": "",
"barcode": "código de barras", "barcode": "código de barras",
"email": "", "email": "",
"errors": "",
"exceptiontitle": "",
"in": "en", "in": "en",
"instanceconflictext": "", "instanceconflictext": "",
"instanceconflictitle": "", "instanceconflictitle": "",
@@ -378,6 +381,7 @@
"spanish": "español" "spanish": "español"
}, },
"messages": { "messages": {
"exception": "",
"unsavedchanges": "Usted tiene cambios no guardados." "unsavedchanges": "Usted tiene cambios no guardados."
}, },
"validation": { "validation": {

View File

@@ -349,13 +349,16 @@
"reset": "Rétablir l'original.", "reset": "Rétablir l'original.",
"save": "sauvegarder", "save": "sauvegarder",
"saveandnew": "", "saveandnew": "",
"submit": "" "submit": "",
"submitticket": ""
}, },
"labels": { "labels": {
"actions": "actes", "actions": "actes",
"areyousure": "", "areyousure": "",
"barcode": "code à barre", "barcode": "code à barre",
"email": "", "email": "",
"errors": "",
"exceptiontitle": "",
"in": "dans", "in": "dans",
"instanceconflictext": "", "instanceconflictext": "",
"instanceconflictitle": "", "instanceconflictitle": "",
@@ -378,6 +381,7 @@
"spanish": "Espanol" "spanish": "Espanol"
}, },
"messages": { "messages": {
"exception": "",
"unsavedchanges": "Vous avez des changements non enregistrés." "unsavedchanges": "Vous avez des changements non enregistrés."
}, },
"validation": { "validation": {