Merged in development (pull request #16)

Test CI for Production.
This commit is contained in:
Patrick Fic
2020-10-07 17:28:44 +00:00
32 changed files with 428 additions and 15172 deletions

View File

@@ -0,0 +1,53 @@
####################################################################################################
#### Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
####
#### Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
#### except in compliance with the License. A copy of the License is located at
####
#### http://aws.amazon.com/apache2.0/
####
#### or in the "license" file accompanying this file. This file is distributed on an "AS IS"
#### BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#### License for the specific language governing permissions and limitations under the License.
####################################################################################################
####################################################################################################
#### This configuration file adds a listener to the Application Load Balancer for port 443, this new listener
#### requires the ARN of a public website certificate create residing in the certificate manager service.
#### The configuration file also modifies the default port 80 listener attached to an Application Load Balancer
#### to automatically redirect incoming connections on HTTP to HTTPS.
#### This will not work with an environment using the load balancer type Classic or Network.
#### Do not use this configuration file if a listener has already been created for port 443 from the console.
####################################################################################################
Resources:
AWSEBV2LoadBalancerListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: redirect
RedirectConfig:
Protocol: HTTPS
Port: '443'
Host: '#{host}'
Path: '/#{path}'
Query: '#{query}'
StatusCode: HTTP_301
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 80
Protocol: HTTP
AWSEBV2LoadBalancerListenerHTTPS:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
Certificates:
- CertificateArn: arn:aws:acm:ca-central-1:714144183158:certificate/c6a0fcde-b959-4aee-afc6-934e27c4962b
DefaultActions:
- Type: forward
TargetGroupArn:
Ref: AWSEBV2LoadBalancerTargetGroup
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 443
Protocol: HTTPS

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta

13
client/madge-graph.svg Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.44.1 (20200629.0846)
-->
<!-- Title: G Pages: 1 -->
<svg width="43pt" height="43pt"
viewBox="0.00 0.00 43.20 43.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(21.6 21.6)">
<title>G</title>
<polygon fill="#111111" stroke="transparent" points="-21.6,21.6 -21.6,-21.6 21.6,-21.6 21.6,21.6 -21.6,21.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@@ -11,11 +11,7 @@
"@tinymce/tinymce-react": "^3.7.0", "@tinymce/tinymce-react": "^3.7.0",
"antd": "^4.6.6", "antd": "^4.6.6",
"apollo-boost": "^0.4.9", "apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20",
"apollo-link-error": "^1.1.13",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.0",
"apollo-link-retry": "^2.2.16",
"apollo-link-ws": "^1.0.20",
"axios": "^0.20.0", "axios": "^0.20.0",
"codemirror": "^5.58.1", "codemirror": "^5.58.1",
"codemirror-graphql": "^0.12.2", "codemirror-graphql": "^0.12.2",
@@ -45,7 +41,6 @@
"react-email-editor": "^1.1.1", "react-email-editor": "^1.1.1",
"react-ga": "^3.1.2", "react-ga": "^3.1.2",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.1.1",
"react-i18next": "^11.7.3", "react-i18next": "^11.7.3",
"react-icons": "^3.11.0", "react-icons": "^3.11.0",
"react-moment": "^1.0.0", "react-moment": "^1.0.0",
@@ -70,7 +65,8 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` react-scripts build", "build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"

BIN
client/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#002366" /> <meta name="theme-color" content="#002366" />
<meta <meta
@@ -10,7 +10,7 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> --> <!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<link rel="apple-touch-icon" href="logo240.png" /> <link rel="apple-touch-icon" href="logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a

View File

@@ -9,7 +9,7 @@
"type": "image/x-icon" "type": "image/x-icon"
}, },
{ {
"src": "logo240.png", "src": "logo192.png",
"type": "image/png", "type": "image/png",
"sizes": "192x192" "sizes": "192x192"
}, },

View File

@@ -1,153 +1,16 @@
import { ApolloProvider } from "@apollo/react-common"; import { ApolloProvider } from "@apollo/react-common";
import { ConfigProvider } from "antd"; import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US"; import enLocale from "antd/es/locale/en_US";
import { ApolloLink } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from "apollo-link";
import { setContext } from "apollo-link-context";
import { HttpLink } from "apollo-link-http";
import apolloLogger from "apollo-link-logger";
import { RetryLink } from "apollo-link-retry";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import axios from "axios";
import LogRocket from "logrocket"; import LogRocket from "logrocket";
import moment from "moment"; import moment from "moment";
import React 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 client from "../utils/GraphQLClient";
import errorLink from "../graphql/apollo-error-handling";
import App from "./App"; import App from "./App";
moment.locale("en-US"); moment.locale("en-US");
export const axiosAuthInterceptorId = axios.interceptors.request.use(
async (config) => {
if (!config.headers.Authorization) {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => Promise.reject(error)
);
export const cleanAxios = axios.create();
cleanAxios.interceptors.request.eject(axiosAuthInterceptorId);
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp"); if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
});
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT_WS,
options: {
lazy: true,
reconnect: true,
connectionParams: async () => {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
},
};
}
},
},
});
const subscriptionMiddleware = {
applyMiddleware: async (options, next) => {
options.authToken =
auth.currentUser && (await auth.currentUser.getIdToken(true));
next();
},
};
wsLink.subscriptionClient.use([subscriptionMiddleware]);
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
// console.log(
// "##Intercepted GQL Transaction : " +
// definition.operation +
// "|" +
// definition.name.value +
// "##",
// query
// );
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const authLink = setContext((_, { headers }) => {
return (
auth.currentUser &&
auth.currentUser.getIdToken().then((token) => {
if (token) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
} else {
return { headers };
}
})
);
});
const retryLink = new RetryLink({
delay: {
initial: 500,
max: 5,
jitter: true,
},
attempts: {
max: 5,
retryIf: (error, _operation) => !!error,
},
});
const middlewares = [];
if (process.env.NODE_ENV === "development") {
middlewares.push(apolloLogger);
}
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
const cache = new InMemoryCache({});
export const client = new ApolloClient({
link: ApolloLink.from(middlewares),
cache,
connectToDevTools: process.env.NODE_ENV !== "production",
defaultOptions: {
query: {
fetchPolicy: "network-only",
},
watchQuery: {
fetchPolicy: "network-only",
},
},
});
export default function AppContainer() { export default function AppContainer() {
return ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>

View File

@@ -1,4 +1,5 @@
//Global Styles. //Global Styles.
@import "react-big-calendar/lib/sass/styles";
.imex-table-header { .imex-table-header {
display: flex; display: flex;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,185 +1,185 @@
import Icon from "@ant-design/icons"; // import Icon from "@ant-design/icons";
import { Button, Dropdown, Menu, notification } from "antd"; // import { Button, Dropdown, Menu, notification } from "antd";
import React, { useState } from "react"; // import React, { useState } from "react";
import { useMutation, useQuery } from "react-apollo"; // import { useMutation, useQuery } from "react-apollo";
import { Responsive, WidthProvider } from "react-grid-layout"; // import { Responsive, WidthProvider } from "react-grid-layout";
import { useTranslation } from "react-i18next"; // import { useTranslation } from "react-i18next";
import { MdClose } from "react-icons/md"; // import { MdClose } from "react-icons/md";
import { connect } from "react-redux"; // import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; // import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; // import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_DASHBOARD_DETAILS } from "../../graphql/bodyshop.queries"; // import { QUERY_DASHBOARD_DETAILS } from "../../graphql/bodyshop.queries";
import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries"; // import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries";
import { // import {
selectBodyshop, // selectBodyshop,
selectCurrentUser, // selectCurrentUser,
} from "../../redux/user/user.selectors"; // } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; // import AlertComponent from "../alert/alert.component";
import DashboardMonthlyRevenueGraph from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component"; // import DashboardMonthlyRevenueGraph from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
import DashboardProjectedMonthlySales from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component"; // import DashboardProjectedMonthlySales from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component"; // import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component";
import DashboardTotalProductionHours from "../dashboard-components/total-production-hours/total-production-hours.component"; // import DashboardTotalProductionHours from "../dashboard-components/total-production-hours/total-production-hours.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; // import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
//Combination of the following: // //Combination of the following:
// /node_modules/react-grid-layout/css/styles.css // // /node_modules/react-grid-layout/css/styles.css
// /node_modules/react-resizable/css/styles.css // // /node_modules/react-resizable/css/styles.css
import "./dashboard-grid.styles.css"; // import "./dashboard-grid.styles.css";
import "./dashboard-grid.styles.scss"; // import "./dashboard-grid.styles.scss";
const ResponsiveReactGridLayout = WidthProvider(Responsive); // const ResponsiveReactGridLayout = WidthProvider(Responsive);
const mapStateToProps = createStructuredSelector({ // const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, // currentUser: selectCurrentUser,
bodyshop: selectBodyshop, // bodyshop: selectBodyshop,
}); // });
const mapDispatchToProps = (dispatch) => ({ // const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) // //setUserLanguage: language => dispatch(setUserLanguage(language))
}); // });
export function DashboardGridComponent({ currentUser, bodyshop }) { // export function DashboardGridComponent({ currentUser, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_DASHBOARD_DETAILS); // const { loading, error, data } = useQuery(QUERY_DASHBOARD_DETAILS);
const { t } = useTranslation(); // const { t } = useTranslation();
const [state, setState] = useState({ // const [state, setState] = useState({
layout: bodyshop.associations[0].user.dashboardlayout || [ // layout: bodyshop.associations[0].user.dashboardlayout || [
{ i: "ProductionDollars", x: 0, y: 0, w: 2, h: 2 }, // { i: "ProductionDollars", x: 0, y: 0, w: 2, h: 2 },
// { i: "ProductionHours", x: 2, y: 0, w: 2, h: 2 }, // // { i: "ProductionHours", x: 2, y: 0, w: 2, h: 2 },
], // ],
}); // });
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT); // const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
const handleLayoutChange = async (newLayout) => { // const handleLayoutChange = async (newLayout) => {
logImEXEvent("dashboard_change_layout"); // logImEXEvent("dashboard_change_layout");
setState({ ...state, layout: newLayout }); // setState({ ...state, layout: newLayout });
const result = await updateLayout({ // const result = await updateLayout({
variables: { email: currentUser.email, layout: newLayout }, // variables: { email: currentUser.email, layout: newLayout },
}); // });
if (!!result.errors) { // if (!!result.errors) {
notification["error"]({ // notification["error"]({
message: t("dashboard.errors.updatinglayout", { // message: t("dashboard.errors.updatinglayout", {
message: JSON.stringify(result.errors), // message: JSON.stringify(result.errors),
}), // }),
}); // });
} // }
}; // };
const handleRemoveComponent = (key) => { // const handleRemoveComponent = (key) => {
logImEXEvent("dashboard_remove_component", { name: key }); // logImEXEvent("dashboard_remove_component", { name: key });
const idxToRemove = state.layout.findIndex((i) => i.i === key); // const idxToRemove = state.layout.findIndex((i) => i.i === key);
const newLayout = state.layout; // const newLayout = state.layout;
newLayout.splice(idxToRemove, 1); // newLayout.splice(idxToRemove, 1);
handleLayoutChange(newLayout); // handleLayoutChange(newLayout);
}; // };
const handleAddComponent = (e) => { // const handleAddComponent = (e) => {
logImEXEvent("dashboard_add_component", { name: e }); // logImEXEvent("dashboard_add_component", { name: e });
handleLayoutChange([ // handleLayoutChange([
...state.layout, // ...state.layout,
{ // {
i: e.key, // i: e.key,
x: (state.layout.length * 2) % (state.cols || 12), // x: (state.layout.length * 2) % (state.cols || 12),
y: Infinity, // puts it at the bottom // y: Infinity, // puts it at the bottom
w: componentList[e.key].w || 2, // w: componentList[e.key].w || 2,
h: componentList[e.key].h || 2, // h: componentList[e.key].h || 2,
}, // },
]); // ]);
}; // };
const onBreakpointChange = (breakpoint, cols) => { // const onBreakpointChange = (breakpoint, cols) => {
setState({ ...state, breakpoint: breakpoint, cols: cols }); // setState({ ...state, breakpoint: breakpoint, cols: cols });
}; // };
const existingLayoutKeys = state.layout.map((i) => i.i); // const existingLayoutKeys = state.layout.map((i) => i.i);
const addComponentOverlay = ( // const addComponentOverlay = (
<Menu onClick={handleAddComponent}> // <Menu onClick={handleAddComponent}>
{Object.keys(componentList).map((key) => ( // {Object.keys(componentList).map((key) => (
<Menu.Item // <Menu.Item
key={key} // key={key}
value={key} // value={key}
disabled={existingLayoutKeys.includes(key)} // disabled={existingLayoutKeys.includes(key)}
> // >
{componentList[key].label} // {componentList[key].label}
</Menu.Item> // </Menu.Item>
))} // ))}
</Menu> // </Menu>
); // );
if (error) return <AlertComponent message={error.message} type="error" />; // if (error) return <AlertComponent message={error.message} type="error" />;
return ( // return (
<div> // <div>
<Dropdown overlay={addComponentOverlay} trigger={["click"]}> // <Dropdown overlay={addComponentOverlay} trigger={["click"]}>
<Button>{t("dashboard.actions.addcomponent")}</Button> // <Button>{t("dashboard.actions.addcomponent")}</Button>
</Dropdown> // </Dropdown>
<ResponsiveReactGridLayout // <ResponsiveReactGridLayout
className="layout" // className="layout"
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} // breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} // cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
width="100%" // width="100%"
onLayoutChange={handleLayoutChange} // onLayoutChange={handleLayoutChange}
onBreakpointChange={onBreakpointChange} // onBreakpointChange={onBreakpointChange}
> // >
{state.layout.map((item, index) => { // {state.layout.map((item, index) => {
const TheComponent = componentList[item.i].component; // const TheComponent = componentList[item.i].component;
return ( // return (
<div key={item.i} data-grid={item}> // <div key={item.i} data-grid={item}>
<LoadingSkeleton loading={loading}> // <LoadingSkeleton loading={loading}>
<Icon // <Icon
component={MdClose} // component={MdClose}
key={item.i} // key={item.i}
style={{ // style={{
position: "absolute", // position: "absolute",
zIndex: "2", // zIndex: "2",
right: ".25rem", // right: ".25rem",
top: ".25rem", // top: ".25rem",
cursor: "pointer", // cursor: "pointer",
}} // }}
onClick={() => handleRemoveComponent(item.i)} // onClick={() => handleRemoveComponent(item.i)}
/> // />
<TheComponent // <TheComponent
className="dashboard-card" // className="dashboard-card"
size="small" // size="small"
style={{ height: "100%", width: "100%" }} // style={{ height: "100%", width: "100%" }}
/> // />
</LoadingSkeleton> // </LoadingSkeleton>
</div> // </div>
); // );
})} // })}
</ResponsiveReactGridLayout> // </ResponsiveReactGridLayout>
</div> // </div>
); // );
} // }
export default connect( // export default connect(
mapStateToProps, // mapStateToProps,
mapDispatchToProps // mapDispatchToProps
)(DashboardGridComponent); // )(DashboardGridComponent);
const componentList = { // const componentList = {
ProductionDollars: { // ProductionDollars: {
label: "Production Dollars", // label: "Production Dollars",
component: DashboardTotalProductionDollars, // component: DashboardTotalProductionDollars,
w: 2, // w: 2,
h: 1, // h: 1,
}, // },
ProductionHours: { // ProductionHours: {
label: "Production Hours", // label: "Production Hours",
component: DashboardTotalProductionHours, // component: DashboardTotalProductionHours,
w: 2, // w: 2,
h: 1, // h: 1,
}, // },
ProjectedMonthlySales: { // ProjectedMonthlySales: {
label: "Projected Monthly Sales", // label: "Projected Monthly Sales",
component: DashboardProjectedMonthlySales, // component: DashboardProjectedMonthlySales,
w: 2, // w: 2,
h: 1, // h: 1,
}, // },
MonthlyRevenueGraph: { // MonthlyRevenueGraph: {
label: "Monthly Sales Graph", // label: "Monthly Sales Graph",
component: DashboardMonthlyRevenueGraph, // component: DashboardMonthlyRevenueGraph,
w: 2, // w: 2,
h: 2, // h: 2,
}, // },
}; // };

View File

@@ -1,7 +1,7 @@
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import i18n from "i18next"; import i18n from "i18next";
import { axiosAuthInterceptorId, client } from "../../App/App.container"; import client, { axiosAuthInterceptorId } from "../../utils/CleanAxios";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
//Context: currentUserEmail, bodyshop, jobid, invoiceid //Context: currentUserEmail, bodyshop, jobid, invoiceid

View File

@@ -4,9 +4,9 @@ import { Button, notification, Popconfirm } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { cleanAxios } from "../../App/App.container";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_DOCUMENT } from "../../graphql/documents.queries"; import { DELETE_DOCUMENT } from "../../graphql/documents.queries";
import cleanAxios from "../../utils/CleanAxios";
//Context: currentUserEmail, bodyshop, jobid, invoiceid //Context: currentUserEmail, bodyshop, jobid, invoiceid
export default function JobsDocumentsDeleteButton({ export default function JobsDocumentsDeleteButton({

View File

@@ -1,6 +1,4 @@
@import "react-big-calendar/lib/sass/styles"; Ø.rbc-time-header-cell-single-day {
.rbc-time-header-cell-single-day {
display: unset; display: unset;
} }
.rbc-time-view .rbc-allday-cell { .rbc-time-view .rbc-allday-cell {

View File

@@ -14,7 +14,7 @@ import {
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { client } from "../../App/App.container"; import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser

View File

@@ -8,7 +8,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { client } from "../../App/App.container"; import client from "../../utils/GraphQLClient";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { displayTemplateInWindowNoprint } from "../../utils/RenderTemplate"; import { displayTemplateInWindowNoprint } from "../../utils/RenderTemplate";

View File

@@ -7,15 +7,15 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, Redirect, useLocation } from "react-router-dom"; import { Link, Redirect, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ImEXOnlineLogo from "../../assets/logo240.png"; import ImEXOnlineLogo from "../../assets/logo192.png";
import { UPSERT_USER } from "../../graphql/user.queries"; import { UPSERT_USER } from "../../graphql/user.queries";
import { import {
emailSignInStart, emailSignInStart,
sendPasswordReset sendPasswordReset,
} from "../../redux/user/user.actions"; } from "../../redux/user/user.actions";
import { import {
selectCurrentUser, selectCurrentUser,
selectSignInError selectSignInError,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import "./sign-in-form.styles.scss"; import "./sign-in-form.styles.scss";
@@ -66,37 +66,39 @@ export function SignInComponent({
return <Redirect to={redirect || "/manage"} />; return <Redirect to={redirect || "/manage"} />;
return ( return (
<div className='login-container'> <div className="login-container">
<div className='login-logo-container'> <div className="login-logo-container">
<img src={ImEXOnlineLogo} height='100' width='100' alt='ImEX Online' /> <img src={ImEXOnlineLogo} height="100" width="100" alt="ImEX Online" />
<Typography.Title>{t("titles.app")}</Typography.Title> <Typography.Title>{t("titles.app")}</Typography.Title>
</div> </div>
<Form onFinish={handleFinish} form={form} size='large'> <Form onFinish={handleFinish} form={form} size="large">
<Form.Item <Form.Item
name='email' name="email"
rules={[ rules={[
{ required: true, message: t("general.validation.required") }, { required: true, message: t("general.validation.required") },
]}> ]}
>
<Input <Input
prefix={<UserOutlined />} prefix={<UserOutlined />}
placeholder={t("general.labels.username")} placeholder={t("general.labels.username")}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name='password' name="password"
rules={[ rules={[
{ required: true, message: t("general.validation.required") }, { required: true, message: t("general.validation.required") },
]}> ]}
>
<Input <Input
prefix={<LockOutlined />} prefix={<LockOutlined />}
type='password' type="password"
placeholder={t("general.labels.password")} placeholder={t("general.labels.password")}
/> />
</Form.Item> </Form.Item>
{signInError ? ( {signInError ? (
<AlertComponent type='error' message={signInError.message} /> <AlertComponent type="error" message={signInError.message} />
) : null} ) : null}
<Button className='login-btn' type='primary' htmlType='submit'> <Button className="login-btn" type="primary" htmlType="submit">
{t("general.actions.login")} {t("general.actions.login")}
</Button> </Button>
</Form> </Form>

View File

@@ -3,7 +3,7 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ImEXOnlineLogo from "../../assets/logo240.png"; import ImEXOnlineLogo from "../../assets/logo192.png";
import { sendPasswordReset } from "../../redux/user/user.actions"; import { sendPasswordReset } from "../../redux/user/user.actions";
import { selectPasswordReset } from "../../redux/user/user.selectors"; import { selectPasswordReset } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ImEXOnlineLogo from "../../assets/logo240.png"; import ImEXOnlineLogo from "../../assets/logo192.png";
import { validatePasswordResetStart } from "../../redux/user/user.actions"; import { validatePasswordResetStart } from "../../redux/user/user.actions";
import { selectPasswordReset } from "../../redux/user/user.selectors"; import { selectPasswordReset } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";

View File

@@ -1,10 +1,10 @@
import "firebase/analytics";
import firebase from "firebase/app"; import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth"; import "firebase/auth";
import "firebase/database"; import "firebase/database";
import "firebase/analytics"; import "firebase/firestore";
import "firebase/messaging"; import "firebase/messaging";
import { store } from "../redux/store"; //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);
@@ -48,14 +48,14 @@ try {
export { messaging }; export { messaging };
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState(); // const state = stateProp || store.getState();
const eventParams = { const eventParams = {
shop: // shop:
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) || // (state.user && state.user.bodyshop && state.user.bodyshop.shopname) ||
null, // null,
user: // user:
(state.user && state.user.currentUser && state.user.currentUser.email) || // (state.user && state.user.currentUser && state.user.currentUser.email) ||
null, // null,
...additionalParams, ...additionalParams,
}; };
analytics.logEvent(eventName, eventParams); analytics.logEvent(eventName, eventParams);

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { client } from "../../App/App.container"; import client from "../../utils/GraphQLClient";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
import ConflictComponent from "../../components/conflict/conflict.component"; import ConflictComponent from "../../components/conflict/conflict.component";

View File

@@ -1,6 +1,6 @@
import { all, takeLatest, call, put } from "redux-saga/effects"; import { all, takeLatest, call, put } from "redux-saga/effects";
import ApplicationActionTypes from "./application.types"; import ApplicationActionTypes from "./application.types";
import { client } from "../../App/App.container"; import client from "../../utils/GraphQLClient";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries"; import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { import {
scheduleLoadFailure, scheduleLoadFailure,

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import phone from "phone"; import phone from "phone";
import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { client } from "../../App/App.container"; import client from "../../utils/GraphQLClient";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
CONVERSATION_ID_BY_PHONE, CONVERSATION_ID_BY_PHONE,

View File

@@ -0,0 +1,27 @@
import axios from "axios";
import { auth } from "../firebase/firebase.utils";
if (process.env.NODE_ENV === "prodution") {
axios.defaults.baseURL =
process.env.REACT_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
}
export const axiosAuthInterceptorId = axios.interceptors.request.use(
async (config) => {
if (!config.headers.Authorization) {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => Promise.reject(error)
);
const cleanAxios = axios.create();
cleanAxios.interceptors.request.eject(axiosAuthInterceptorId);
export default cleanAxios;

View File

@@ -0,0 +1,118 @@
import { setContext } from "@apollo/client/link/context";
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
import { RetryLink } from "@apollo/client/link/retry";
import { WebSocketLink } from "@apollo/client/link/ws";
import { ApolloLink } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from "apollo-link";
import apolloLogger from "apollo-link-logger";
import { getMainDefinition } from "apollo-utilities";
import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling";
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
});
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT_WS,
options: {
lazy: true,
reconnect: true,
connectionParams: async () => {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
},
};
}
},
},
});
const subscriptionMiddleware = {
applyMiddleware: async (options, next) => {
options.authToken =
auth.currentUser && (await auth.currentUser.getIdToken(true));
next();
},
};
wsLink.subscriptionClient.use([subscriptionMiddleware]);
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
// console.log(
// "##Intercepted GQL Transaction : " +
// definition.operation +
// "|" +
// definition.name.value +
// "##",
// query
// );
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const authLink = setContext((_, { headers }) => {
return (
auth.currentUser &&
auth.currentUser.getIdToken().then((token) => {
if (token) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
} else {
return { headers };
}
})
);
});
const retryLink = new RetryLink({
delay: {
initial: 500,
max: 5,
jitter: true,
},
attempts: {
max: 5,
retryIf: (error, _operation) => !!error,
},
});
const middlewares = [];
if (process.env.NODE_ENV === "development") {
middlewares.push(apolloLogger);
}
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
const cache = new InMemoryCache({});
export default new ApolloClient({
link: ApolloLink.from(middlewares),
cache,
connectToDevTools: process.env.NODE_ENV !== "production",
defaultOptions: {
query: {
fetchPolicy: "network-only",
},
watchQuery: {
fetchPolicy: "network-only",
},
},
});

View File

@@ -1,7 +1,7 @@
import axios from "axios";
import gql from "graphql-tag"; import gql from "graphql-tag";
import { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries"; import { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries";
import axios from "axios"; import client from "../utils/GraphQLClient";
import { client } from "../App/App.container";
export default async function RenderTemplate(templateObject, bodyshop) { export default async function RenderTemplate(templateObject, bodyshop) {
const { data: templateRecords } = await client.query({ const { data: templateRecords } = await client.query({

View File

@@ -1,170 +0,0 @@
/* eslint no-fallthrough: off */
import * as dates from "date-arithmetic";
export {
milliseconds,
seconds,
minutes,
hours,
month,
startOf,
endOf,
add,
eq,
gte,
gt,
lte,
lt,
inRange,
min,
max
} from "date-arithmetic";
const MILLI = {
seconds: 1000,
minutes: 1000 * 60,
hours: 1000 * 60 * 60,
day: 1000 * 60 * 60 * 24
};
const MONTHS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
export function monthsInYear(year) {
let date = new Date(year, 0, 1);
return MONTHS.map(i => dates.month(date, i));
}
export function firstVisibleDay(date, localizer) {
let firstOfMonth = dates.startOf(date, "month");
return dates.startOf(firstOfMonth, "week", localizer.startOfWeek());
}
export function lastVisibleDay(date, localizer) {
let endOfMonth = dates.endOf(date, "month");
return dates.endOf(endOfMonth, "week", localizer.startOfWeek());
}
export function visibleDays(date, localizer) {
let current = firstVisibleDay(date, localizer),
last = lastVisibleDay(date, localizer),
days = [];
while (dates.lte(current, last, "day")) {
days.push(current);
current = dates.add(current, 1, "day");
}
return days;
}
export function ceil(date, unit) {
let floor = dates.startOf(date, unit);
return dates.eq(floor, date) ? floor : dates.add(floor, 1, unit);
}
export function range(start, end, unit = "day") {
let current = start,
days = [];
while (dates.lte(current, end, unit)) {
days.push(current);
current = dates.add(current, 1, unit);
}
return days;
}
export function merge(date, time) {
if (time == null && date == null) return null;
if (time == null) time = new Date();
if (date == null) date = new Date();
date = dates.startOf(date, "day");
date = dates.hours(date, dates.hours(time));
date = dates.minutes(date, dates.minutes(time));
date = dates.seconds(date, dates.seconds(time));
return dates.milliseconds(date, dates.milliseconds(time));
}
export function eqTime(dateA, dateB) {
return (
dates.hours(dateA) === dates.hours(dateB) &&
dates.minutes(dateA) === dates.minutes(dateB) &&
dates.seconds(dateA) === dates.seconds(dateB)
);
}
export function isJustDate(date) {
return (
dates.hours(date) === 0 &&
dates.minutes(date) === 0 &&
dates.seconds(date) === 0 &&
dates.milliseconds(date) === 0
);
}
export function duration(start, end, unit, firstOfWeek) {
if (unit === "day") unit = "date";
return Math.abs(
dates[unit](start, undefined, firstOfWeek) -
dates[unit](end, undefined, firstOfWeek)
);
}
export function diff(dateA, dateB, unit) {
if (!unit || unit === "milliseconds") return Math.abs(+dateA - +dateB);
// the .round() handles an edge case
// with DST where the total won't be exact
// since one day in the range may be shorter/longer by an hour
return Math.round(
Math.abs(
+dates.startOf(dateA, unit) / MILLI[unit] -
+dates.startOf(dateB, unit) / MILLI[unit]
)
);
}
export function total(date, unit) {
let ms = date.getTime(),
div = 1;
switch (unit) {
case "week":
div *= 7;
case "day":
div *= 24;
case "hours":
div *= 60;
case "minutes":
div *= 60;
case "seconds":
div *= 1000;
}
return ms / div;
}
export function week(date) {
var d = new Date(date);
d.setHours(0, 0, 0);
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
return Math.ceil(((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7 + 1) / 7);
}
export function today() {
return dates.startOf(new Date(), "day");
}
export function yesterday() {
return dates.add(dates.startOf(new Date(), "day"), -1, "day");
}
export function tomorrow() {
return dates.add(dates.startOf(new Date(), "day"), 1, "day");
}

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@ exports.testResponse = async (req, res) => {
// renotify: true, // renotify: true,
//tag: "1234", image: "/logo192.png", //tag: "1234", image: "/logo192.png",
badge: "/logo240.png", badge: "/logo192.png",
//badge: "/badge-icon.png", //badge: "/badge-icon.png",
}, },
}, },