53
.ebextensions/eb-redirect-https.config
Normal 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
|
||||
|
||||
@@ -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
@@ -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 |
@@ -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
|
After Width: | Height: | Size: 8.7 KiB |
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo240.png",
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//Global Styles.
|
||||
@import "react-big-calendar/lib/sass/styles";
|
||||
|
||||
.imex-table-header {
|
||||
display: flex;
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
BIN
client/src/assets/logo512.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -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,
|
||||
// },
|
||||
// };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
27
client/src/utils/CleanAxios.js
Normal 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;
|
||||
118
client/src/utils/GraphQLClient.js
Normal 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",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
14645
client/yarn.lock
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||