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">
<head>
<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="theme-color" content="#000000" />
<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",
"antd": "^4.6.6",
"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-retry": "^2.2.16",
"apollo-link-ws": "^1.0.20",
"axios": "^0.20.0",
"codemirror": "^5.58.1",
"codemirror-graphql": "^0.12.2",
@@ -45,7 +41,6 @@
"react-email-editor": "^1.1.1",
"react-ga": "^3.1.2",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.1.1",
"react-i18next": "^11.7.3",
"react-icons": "^3.11.0",
"react-moment": "^1.0.0",
@@ -70,7 +65,8 @@
"start": "react-scripts start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` react-scripts build",
"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": {
"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">
<head>
<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="theme-color" content="#002366" />
<meta
@@ -10,7 +10,7 @@
content="Web site created using create-react-app"
/>
<!-- <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

View File

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

View File

@@ -1,153 +1,16 @@
import { ApolloProvider } from "@apollo/react-common";
import { ConfigProvider } from "antd";
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 moment from "moment";
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";
import client from "../utils/GraphQLClient";
import App from "./App";
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");
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() {
return (
<ApolloProvider client={client}>

View File

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

View File

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

View File

@@ -4,9 +4,9 @@ import { Button, notification, Popconfirm } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { cleanAxios } from "../../App/App.container";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_DOCUMENT } from "../../graphql/documents.queries";
import cleanAxios from "../../utils/CleanAxios";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
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;
}
.rbc-time-view .rbc-allday-cell {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
import "firebase/analytics";
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
import "firebase/database";
import "firebase/analytics";
import "firebase/firestore";
import "firebase/messaging";
import { store } from "../redux/store";
//import { store } from "../redux/store";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
firebase.initializeApp(config);
@@ -48,14 +48,14 @@ try {
export { messaging };
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState();
// const state = stateProp || 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,
// 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

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

View File

@@ -1,6 +1,6 @@
import { all, takeLatest, call, put } from "redux-saga/effects";
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 {
scheduleLoadFailure,

View File

@@ -1,7 +1,7 @@
import axios from "axios";
import phone from "phone";
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 {
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 { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries";
import axios from "axios";
import { client } from "../App/App.container";
import client from "../utils/GraphQLClient";
export default async function RenderTemplate(templateObject, bodyshop) {
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,
//tag: "1234", image: "/logo192.png",
badge: "/logo240.png",
badge: "/logo192.png",
//badge: "/badge-icon.png",
},
},