@@ -48,25 +48,16 @@ jobs:
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
command: npm i
|
||||
|
||||
- run: yarn run build
|
||||
- run: npm run build
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
rome-api-deploy:
|
||||
@@ -185,7 +176,7 @@ jobs:
|
||||
app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
resource_class: large
|
||||
resource_class: xlarge
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
@@ -200,6 +191,7 @@ jobs:
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-beta/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
rome-app-beta-build:
|
||||
@@ -251,31 +243,23 @@ jobs:
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
command: npm i
|
||||
|
||||
- run: yarn run build:test
|
||||
- run: npm run build:test
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-test/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
test-app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
resource_class: large
|
||||
machine: true
|
||||
resource_class: snaptsoft/dell
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
@@ -291,6 +275,7 @@ jobs:
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-test-beta/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
rome-test-app-beta-build:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
GENERATE_SOURCEMAP=true
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
|
||||
3
client/.gitignore
vendored
Normal file
3
client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
@@ -1,7 +1,6 @@
|
||||
// craco.config.js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
||||
const {theme} = require('antd/lib');
|
||||
|
||||
@@ -10,52 +9,31 @@ const {defaultAlgorithm, defaultSeed} = theme;
|
||||
const mapToken = defaultAlgorithm(defaultSeed);
|
||||
const v4Token = convertLegacyToken(mapToken);
|
||||
|
||||
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: SentryWebpackPlugin,
|
||||
options: {
|
||||
// sentry-cli configuration
|
||||
authToken:
|
||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
org: "snapt-software",
|
||||
project: "rome-online",
|
||||
release: process.env.REACT_APP_GIT_SHA,
|
||||
|
||||
// webpack-specific configuration
|
||||
include: ".",
|
||||
ignore: ["node_modules", "webpack.config.js"],
|
||||
},
|
||||
},
|
||||
// {
|
||||
// plugin: SentryWebpackPlugin,
|
||||
// options: {
|
||||
// // sentry-cli configuration
|
||||
// authToken:
|
||||
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
// org: "snapt-software",
|
||||
// project: "rome-online",
|
||||
// release: process.env.REACT_APP_GIT_SHA,
|
||||
//
|
||||
// // webpack-specific configuration
|
||||
// include: ".",
|
||||
// ignore: ["node_modules", "webpack.config.js"],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {
|
||||
...v4Token,
|
||||
// TODO: This will no longer work in AntD 5.0
|
||||
...(process.env.NODE_ENV === "development"
|
||||
? {"colorPrimary": "#B22234"}
|
||||
: {
|
||||
//"@primary-color": "#1DA57A"
|
||||
}),
|
||||
// "@primary-color": " #1890ff", // primary color for all components
|
||||
// "@link-color": "#1890ff", // link color
|
||||
// "@success-color": "#52c41a", // success state color
|
||||
// "@warning-color": "#faad14", // warning state color
|
||||
// "@error-color": "#f5222d", // error state color
|
||||
// "@font-size-base": "14px", // major text font size
|
||||
// " @heading-color": "rgba(0, 0, 0, 0.85)", // heading text color
|
||||
// "@text-color": "rgba(0, 0, 0, 0.65)", // major text color
|
||||
// "@text-color-secondary": "rgba(0, 0, 0, 0.45)", // secondary text color
|
||||
// "@disabled-color": "rgba(0, 0, 0, 0.25)", // disable state color
|
||||
// "@border-radius-base": "2px", // major border radius
|
||||
// "@border-color-base": "#d9d9d9", // major border color
|
||||
// "@box-shadow-base":
|
||||
// "0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers }",
|
||||
},
|
||||
modifyVars: {...v4Token},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
1761
client/package-lock.json
generated
1761
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,36 +6,38 @@
|
||||
"dependencies": {
|
||||
"@ant-design/compatible": "^5.1.2",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"@apollo/client": "^3.8.10",
|
||||
"@apollo/client": "^3.9.0",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.2.1",
|
||||
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"@sentry/react": "^7.93.0",
|
||||
"@sentry/tracing": "^7.93.0",
|
||||
"@reduxjs/toolkit": "^2.1.0",
|
||||
"@sentry/cli": "^2.27.0",
|
||||
"@sentry/react": "^7.99.0",
|
||||
"@sentry/tracing": "^7.99.0",
|
||||
"@splitsoftware/splitio-react": "^1.11.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"antd": "^5.12.8",
|
||||
"antd": "^5.13.3",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"axios": "^1.6.5",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"axios": "^1.6.7",
|
||||
"craco-less": "^3.0.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"dayjs-business-days2": "^1.2.2",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.7.2",
|
||||
"graphql": "^16.6.0",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next": "^23.8.1",
|
||||
"i18next-browser-languagedetector": "^7.0.2",
|
||||
"jsoneditor": "^10.0.0",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.10.53",
|
||||
"libphonenumber-js": "^1.10.54",
|
||||
"logrocket": "^7.0.0",
|
||||
"markerjs2": "^2.31.4",
|
||||
"markerjs2": "^2.32.0",
|
||||
"normalize-url": "^8.0.0",
|
||||
"phone": "^3.1.42",
|
||||
"preval.macro": "^5.0.0",
|
||||
@@ -44,17 +46,18 @@
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^18.2.0",
|
||||
"react-big-calendar": "^1.8.6",
|
||||
"react-big-calendar": "^1.8.7",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.0.1",
|
||||
"react-cookie": "^7.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.0",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-i18next": "^14.0.1",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.5.3",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.1.4",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-resizable": "^3.0.5",
|
||||
@@ -63,7 +66,7 @@
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"recharts": "^2.10.4",
|
||||
"recharts": "^2.11.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
@@ -74,7 +77,7 @@
|
||||
"styled-components": "^6.1.8",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"web-vitals": "^3.5.1",
|
||||
"web-vitals": "^3.5.2",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-navigation-preload": "^7.0.0",
|
||||
@@ -86,13 +89,15 @@
|
||||
"scripts": {
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "craco start",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build && npm run sentry:sourcemaps",
|
||||
"build:test": "env-cmd -f .env.test npm run build",
|
||||
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||
"test": "cypress open",
|
||||
"eject": "react-scripts eject",
|
||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
|
||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||
"eulaize": "node src/utils/eulaize.js",
|
||||
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -118,9 +123,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@sentry/webpack-plugin": "^2.10.2",
|
||||
"@sentry/webpack-plugin": "^2.10.3",
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"cypress": "^13.6.3",
|
||||
"cypress": "^13.6.4",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
|
||||
@@ -1,51 +1,53 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import {ApolloProvider} from "@apollo/client";
|
||||
import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react';
|
||||
import {ConfigProvider} from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import 'dayjs/locale/en';
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import themeProvider from "./themeProvider";
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
export const factory = SplitSdk({
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
});
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
};
|
||||
export const factory = SplitSdk(config);
|
||||
|
||||
export default function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
//componentSize="small"
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "#326ade",
|
||||
colorInfo: "#326ade"
|
||||
},
|
||||
}}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", { label: "${label}" }),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactory factory={factory}>
|
||||
<App />
|
||||
</SplitFactory>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
function AppContainer() {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
//componentSize="small"
|
||||
input={{autoComplete: "new-password"}}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", {label: "${label}"}),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar/>
|
||||
<SplitFactoryProvider factory={factory}>
|
||||
<App/>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.withProfiler(AppContainer);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {Route, Routes} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
|
||||
//Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
@@ -16,10 +17,11 @@ import TechPageContainer from "../pages/tech/tech.page.container";
|
||||
import {setOnline} from "../redux/application/application.actions";
|
||||
import {selectOnline} from "../redux/application/application.selectors";
|
||||
import {checkUserSession} from "../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||
import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
|
||||
const ResetPassword = lazy(() =>
|
||||
import("../pages/reset-password/reset-password.component")
|
||||
@@ -35,14 +37,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentEula: selectCurrentEula
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
});
|
||||
|
||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
|
||||
|
||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
|
||||
const client = useSplitClient().client;
|
||||
const [listenersAdded, setListenersAdded] = useState(false)
|
||||
const {t} = useTranslation();
|
||||
@@ -121,6 +123,10 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline}
|
||||
/>
|
||||
);
|
||||
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula/>
|
||||
}
|
||||
|
||||
// Any route that is not assigned and matched will default to the Landing Page component
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner message="Rome Online"/>}>
|
||||
@@ -131,10 +137,12 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline}
|
||||
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
|
||||
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
|
||||
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
|
||||
<Route path="/manage/*" element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="/manage/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<ManagePage/>}/>
|
||||
</Route>
|
||||
<Route path="/tech/*" element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="/tech/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<TechPageContainer/>}/>
|
||||
</Route>
|
||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
border-bottom: 1px solid #74695c !important;
|
||||
}
|
||||
|
||||
.ant-menu-dark .ant-menu-item:hover {
|
||||
background-color: #1890ff !important;
|
||||
}
|
||||
|
||||
.imex-table-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -151,23 +147,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #e7f3ff !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
||||
background: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
color: tomato;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
td.ant-table-column-sort {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
|
||||
61
client/src/App/themeProvider.js
Normal file
61
client/src/App/themeProvider.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import {defaultsDeep} from "lodash";
|
||||
import {theme} from "antd";
|
||||
|
||||
const {defaultAlgorithm, darkAlgorithm} = theme;
|
||||
|
||||
let isDarkMode = false;
|
||||
|
||||
/**
|
||||
* Default theme
|
||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||
*/
|
||||
const defaultTheme = {
|
||||
components: {
|
||||
Table: {
|
||||
rowHoverBg: '#e7f3ff',
|
||||
rowSelectedBg: '#e6f7ff',
|
||||
headerSortHoverBg: 'transparent',
|
||||
},
|
||||
Menu: {
|
||||
darkItemHoverBg: '#1677ff',
|
||||
itemHoverBg: '#1677ff',
|
||||
horizontalItemHoverBg: '#1677ff',
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: "#326ade",
|
||||
colorInfo: "#326ade"
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Development theme
|
||||
* @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}}
|
||||
*/
|
||||
const devTheme = {
|
||||
components: {
|
||||
Menu: {
|
||||
darkItemHoverBg: '#a51d1d',
|
||||
itemHoverBg: '#a51d1d',
|
||||
horizontalItemHoverBg: '#a51d1d',
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#a51d1d'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Production theme
|
||||
* @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}}
|
||||
*/
|
||||
const prodTheme = {};
|
||||
|
||||
const currentTheme = process.env.NODE_ENV === "development" ? devTheme
|
||||
: prodTheme;
|
||||
|
||||
const finaltheme = {
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
...defaultsDeep(currentTheme, defaultTheme)
|
||||
}
|
||||
export default finaltheme;
|
||||
@@ -1,11 +1,14 @@
|
||||
import {Button, Col, Collapse, Result, Row, Space} from "antd";
|
||||
import React from "react";
|
||||
import {withTranslation} from "react-i18next";
|
||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -135,7 +138,6 @@ class ErrorBoundary extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation()(ErrorBoundary));
|
||||
export default Sentry.withErrorBoundary(
|
||||
connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))
|
||||
);
|
||||
|
||||
250
client/src/components/eula/eula.component.jsx
Normal file
250
client/src/components/eula/eula.component.jsx
Normal file
@@ -0,0 +1,250 @@
|
||||
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||
import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row} from "antd";
|
||||
import Markdown from "react-markdown";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectCurrentEula, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||
import {connect} from "react-redux";
|
||||
import {FormDatePicker} from "../form-date-picker/form-date-picker.component";
|
||||
import {INSERT_EULA_ACCEPTANCE} from "../../graphql/user.queries";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {acceptEula} from "../../redux/user/user.actions";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import day from '../../utils/day';
|
||||
|
||||
import './eula.styles.scss';
|
||||
|
||||
const Eula = ({currentEula, currentUser, acceptEula}) => {
|
||||
const [formReady, setFormReady] = useState(false);
|
||||
const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false);
|
||||
const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE);
|
||||
const [form] = Form.useForm();
|
||||
const markdownCardRef = useRef(null);
|
||||
const {t} = useTranslation();
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
const handleScroll = useCallback((e) => {
|
||||
const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight;
|
||||
if (bottom && !hasEverScrolledToBottom) {
|
||||
setHasEverScrolledToBottom(true);
|
||||
} else if (e.target.scrollHeight <= e.target.clientHeight && !hasEverScrolledToBottom) {
|
||||
setHasEverScrolledToBottom(true);
|
||||
}
|
||||
}, [hasEverScrolledToBottom, setHasEverScrolledToBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
handleScroll({target: markdownCardRef.current});
|
||||
}, [handleScroll]);
|
||||
|
||||
const handleChange = useCallback(() => {
|
||||
form.validateFields({validateOnly: true})
|
||||
.then(() => setFormReady(hasEverScrolledToBottom))
|
||||
.catch(() => setFormReady(false));
|
||||
}, [form, hasEverScrolledToBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
handleChange();
|
||||
}, [handleChange, hasEverScrolledToBottom, form]);
|
||||
|
||||
const onFinish = async ({acceptTerms, ...formValues}) => {
|
||||
const eulaId = currentEula.id;
|
||||
const useremail = currentUser.email;
|
||||
|
||||
try {
|
||||
const {accepted_terms, ...otherFormValues} = formValues;
|
||||
|
||||
// Trim the values of the fields before submitting
|
||||
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||
acc[key] = typeof value === 'string' ? value.trim() : value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await insertEulaAcceptance({
|
||||
variables: {
|
||||
eulaAcceptance: {
|
||||
eulaid: eulaId,
|
||||
useremail,
|
||||
...otherFormValues,
|
||||
...trimmedFormValues,
|
||||
date_accepted: new Date(),
|
||||
}
|
||||
}
|
||||
});
|
||||
acceptEula();
|
||||
} catch (err) {
|
||||
api.error({
|
||||
message: t('eula.errors.acceptance.message'),
|
||||
description: t('eula.errors.acceptance.description'),
|
||||
placement: 'bottomRight',
|
||||
duration: 5000,
|
||||
|
||||
});
|
||||
console.log(`${t('eula.errors.acceptance.message')}`);
|
||||
console.dir({
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<Modal
|
||||
title={t('eula.titles.modal')}
|
||||
className='eula-modal'
|
||||
width={'100vh'}
|
||||
open={currentEula}
|
||||
footer={() => (
|
||||
<Button
|
||||
className='eula-accept-button'
|
||||
form='tosForm'
|
||||
type="primary"
|
||||
size='large'
|
||||
htmlType="submit"
|
||||
disabled={!formReady}
|
||||
children={t('eula.buttons.accept')}
|
||||
/>
|
||||
)}
|
||||
closable={false}
|
||||
>
|
||||
<Card type='inner' className='eula-markdown-card' onScroll={handleScroll} ref={markdownCardRef}>
|
||||
<div id='markdowndiv' className='eula-markdown-div'>
|
||||
<Markdown children={currentEula?.content?.replace(/\\n|\\r|\\n\\r|\\r\\n/g, '\n')}/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<EulaFormComponent form={form} handleChange={handleChange} onFinish={onFinish} t={t}/>
|
||||
|
||||
{!hasEverScrolledToBottom && (
|
||||
<Card className='eula-never-scrolled' type='inner'>
|
||||
<h3>{t('eula.content.never_scrolled')}</h3>
|
||||
</Card>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EulaFormComponent = ({form, handleChange, onFinish, t}) => (
|
||||
<Card type='inner' title={t('eula.titles.upper_card')} style={{marginTop: '10px'}}>
|
||||
<Form id='tosForm' onChange={handleChange} onFinish={onFinish} form={form}>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.first_name')}
|
||||
name="first_name"
|
||||
rules={[{
|
||||
required: true,
|
||||
validator: (_, value) =>
|
||||
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.first_name'))),
|
||||
},]}
|
||||
>
|
||||
<Input placeholder={t('eula.labels.first_name')}
|
||||
aria-label={t('eula.labels.first_name')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.last_name')}
|
||||
name="last_name"
|
||||
rules={[{
|
||||
required: true,
|
||||
validator: (_, value) =>
|
||||
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.last_name'))),
|
||||
}]}
|
||||
>
|
||||
<Input placeholder={t('eula.labels.last_name')}
|
||||
aria-label={t('eula.labels.last_name')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.business_name')}
|
||||
name="business_name"
|
||||
rules={[{
|
||||
required: true,
|
||||
validator: (_, value) =>
|
||||
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.business_name'))),
|
||||
}]}
|
||||
>
|
||||
<Input placeholder={t('eula.labels.business_name')} aria-label={t('eula.labels.business_name')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.phone_number')}
|
||||
name="phone_number"
|
||||
rules={[
|
||||
{
|
||||
pattern: /^(\+\d{1,2}\s?)?1?-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/,
|
||||
message: t('eula.messages.phone_number'),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t('eula.labels.phone_number')}
|
||||
aria-label={t('eula.labels.phone_number')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.address')}
|
||||
name="address"
|
||||
>
|
||||
<Input placeholder={t('eula.labels.address')} aria-label={t('eula.labels.address')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label={t('eula.labels.date_accepted')}
|
||||
name="date_accepted"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (day(value).isSame(day(), 'day')) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t('eula.messages.date_accepted')));
|
||||
}
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker onChange={handleChange} onlyToday aria-label={t('eula.labels.date_accepted')}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={24}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="accepted_terms"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) =>
|
||||
value ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.accepted_terms'))),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox aria-label={t('eula.labels.accepted_terms')}>{t('eula.labels.accepted_terms')}</Checkbox>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEula: selectCurrentEula,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
acceptEula: () => dispatch(acceptEula()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Eula);
|
||||
21
client/src/components/eula/eula.styles.scss
Normal file
21
client/src/components/eula/eula.styles.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
.eula-modal {
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.eula-markdown-card {
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
.eula-markdown-div {
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
.eula-never-scrolled {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.eula-accept-button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -16,7 +16,16 @@ export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
|
||||
|
||||
const dateFormat = "MM/DD/YYYY";
|
||||
|
||||
export function FormDatePicker({bodyshop, value, onChange, onBlur, onlyFuture, isDateOnly = true, ...restProps}) {
|
||||
export function FormDatePicker({
|
||||
bodyshop,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
|
||||
const handleChange = (newDate) => {
|
||||
@@ -87,9 +96,13 @@ export function FormDatePicker({bodyshop, value, onChange, onBlur, onlyFuture, i
|
||||
onBlur={onBlur || handleBlur}
|
||||
showToday={false}
|
||||
disabledTime
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d),
|
||||
})}
|
||||
disabledDate={(d) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(d, 'day');
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(d);
|
||||
}
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import moment from "moment";
|
||||
import day from '../../utils/day';
|
||||
import axios from 'axios';
|
||||
import {Badge, Card, Space, Table, Tag} from 'antd';
|
||||
import {gql, useQuery} from "@apollo/client";
|
||||
@@ -69,7 +69,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
|
||||
dataIndex: 'start',
|
||||
key: 'start',
|
||||
render: (text) => DateTimeFormatterFunction(text),
|
||||
sorter: (a, b) => moment(a.start).unix() - moment(b.start).unix(),
|
||||
sorter: (a, b) => day(a.start).unix() - day(b.start).unix(),
|
||||
},
|
||||
{
|
||||
title: t('job_lifecycle.columns.relative_start'),
|
||||
@@ -87,7 +87,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
|
||||
}
|
||||
return isEmpty(a.end) ? 1 : -1;
|
||||
}
|
||||
return moment(a.end).unix() - moment(b.end).unix();
|
||||
return day(a.end).unix() - day(b.end).unix();
|
||||
},
|
||||
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
|
||||
},
|
||||
@@ -179,7 +179,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
|
||||
style={{marginTop: '10px'}}>
|
||||
<div>
|
||||
{lifecycleData.durations.summations.map((key) => (
|
||||
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||
<Tag key={key.status} color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||
<div
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import {DownCircleFilled} from "@ant-design/icons";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Dropdown, notification} from "antd";
|
||||
import { DownCircleFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Dropdown, notification } from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {UPDATE_JOB_STATUS} from "../../graphql/jobs.queries";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
||||
|
||||
export function JobsAdminStatus({insertAuditTrail, bodyshop, job}) {
|
||||
const {t} = useTranslation();
|
||||
export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
const updateJobStatus = (status) => {
|
||||
mutationUpdateJobstatus({
|
||||
variables: {jobId: job.id, status: status},
|
||||
})
|
||||
.then((r) => {
|
||||
notification["success"]({message: t("jobs.successes.save")});
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
||||
});
|
||||
// refetch();
|
||||
})
|
||||
.catch((error) => {
|
||||
notification["error"]({message: t("jobs.errors.saving")});
|
||||
});
|
||||
};
|
||||
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
const updateJobStatus = (status) => {
|
||||
mutationUpdateJobstatus({
|
||||
variables: { jobId: job.id, status: status },
|
||||
})
|
||||
.then((r) => {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
||||
});
|
||||
// refetch();
|
||||
})
|
||||
.catch((error) => {
|
||||
notification["error"]({ message: t("jobs.errors.saving") });
|
||||
});
|
||||
};
|
||||
|
||||
const statusMenu = {
|
||||
items: bodyshop.md_ro_statuses.statuses.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
})),
|
||||
onClick: (e) => {
|
||||
updateJobStatus(e.key);
|
||||
},
|
||||
}
|
||||
const statusMenu = {
|
||||
items: bodyshop.md_ro_statuses.statuses.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
})),
|
||||
onClick: (e) => {
|
||||
updateJobStatus(e.key);
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -56,7 +56,7 @@ export function JobsAdminStatus({insertAuditTrail, bodyshop, job}) {
|
||||
<Button shape="round">
|
||||
<span>{job.status}</span>
|
||||
|
||||
<DownCircleFilled/>
|
||||
<DownCircleFilled />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function JobAdminMarkReexport({
|
||||
const result = await markJobExported({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
date_exported: moment(),
|
||||
date_exported: dayjs(),
|
||||
default_exported: bodyshop.md_ro_statuses.default_exported,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -732,12 +732,7 @@ export function JobsDetailHeaderActions({
|
||||
menuItems.push({
|
||||
key: 'cccontract',
|
||||
disabled: jobRO || !job.converted,
|
||||
label: <Link
|
||||
to={{
|
||||
pathname: "/manage/courtesycars/contracts/new",
|
||||
state: {jobId: job.id},
|
||||
}}
|
||||
>
|
||||
label: <Link state={{jobId: job.id}} to='/manage/courtesycars/contracts/new'>
|
||||
{t("menus.jobsactions.newcccontract")}
|
||||
</Link>
|
||||
});
|
||||
|
||||
@@ -42,8 +42,13 @@ export function JobsDetailRatesTaxes({
|
||||
})
|
||||
);
|
||||
}
|
||||
formItems.push(Space({children: section, wrap: true}));
|
||||
formItems.push(<Divider/>);
|
||||
formItems.push(<>
|
||||
<Space wrap>
|
||||
{section}
|
||||
</Space>
|
||||
<Divider/>
|
||||
</>)
|
||||
|
||||
}
|
||||
return (
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
|
||||
@@ -220,12 +220,12 @@ export function PayableExportAll({
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (bodyshop.pbs_serialnumber)
|
||||
return (
|
||||
<Link to={{state: {billids}, pathname: `/manage/dmsap`}}>
|
||||
<Button>{t("jobs.actions.export")}</Button>
|
||||
</Link>
|
||||
);
|
||||
if (bodyshop.pbs_serialnumber)
|
||||
return (
|
||||
<Link to='/manage/dmsap' state={{ billids }}>
|
||||
<Button>{t("jobs.actions.export")}</Button>
|
||||
</Link>
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
|
||||
@@ -213,12 +213,12 @@ export function PayableExportButton({
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (bodyshop.pbs_serialnumber)
|
||||
return (
|
||||
<Link to={{state: {billids: [billId]}, pathname: `/manage/dmsap`}}>
|
||||
<Button>{t("jobs.actions.export")}</Button>
|
||||
</Link>
|
||||
);
|
||||
if (bodyshop.pbs_serialnumber)
|
||||
return (
|
||||
<Link to='/manage/dmsap' state={{billids: [billId]}}>
|
||||
<Button>{t("jobs.actions.export")}</Button>
|
||||
</Link>
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
|
||||
@@ -106,36 +106,36 @@ export function ProductionBoardKanbanComponent({
|
||||
|
||||
const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null;
|
||||
|
||||
let movedCardNewKanbanParent;
|
||||
if (movedCardWillBeFirst) {
|
||||
//console.log("==> New Card is first.");
|
||||
movedCardNewKanbanParent = "-1";
|
||||
} else if (movedCardWillBeLast) {
|
||||
// console.log("==> New Card is last.");
|
||||
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
|
||||
} else if (!!newChildCard) {
|
||||
// console.log("==> New Card is somewhere in the middle");
|
||||
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
||||
} else {
|
||||
throw new Error("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||
}
|
||||
const newChildCardNewParent = newChildCard ? card.id : null;
|
||||
const update = await client.mutate({
|
||||
mutation: generate_UPDATE_JOB_KANBAN(
|
||||
oldChildCard ? oldChildCard.id : null,
|
||||
oldChildCardNewParent,
|
||||
card.id,
|
||||
movedCardNewKanbanParent,
|
||||
destination.toColumnId,
|
||||
newChildCard ? newChildCard.id : null,
|
||||
newChildCardNewParent
|
||||
),
|
||||
// TODO: optimisticResponse
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: card.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
||||
});
|
||||
let movedCardNewKanbanParent;
|
||||
if (movedCardWillBeFirst) {
|
||||
//console.log("==> New Card is first.");
|
||||
movedCardNewKanbanParent = "-1";
|
||||
} else if (movedCardWillBeLast) {
|
||||
// console.log("==> New Card is last.");
|
||||
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
|
||||
} else if (!!newChildCard) {
|
||||
// console.log("==> New Card is somewhere in the middle");
|
||||
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
||||
} else {
|
||||
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||
}
|
||||
const newChildCardNewParent = newChildCard ? card.id : null;
|
||||
const update = await client.mutate({
|
||||
mutation: generate_UPDATE_JOB_KANBAN(
|
||||
oldChildCard ? oldChildCard.id : null,
|
||||
oldChildCardNewParent,
|
||||
card.id,
|
||||
movedCardNewKanbanParent,
|
||||
destination.toColumnId,
|
||||
newChildCard ? newChildCard.id : null,
|
||||
newChildCardNewParent
|
||||
),
|
||||
// TODO: optimisticResponse
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: card.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
||||
});
|
||||
|
||||
if (update.errors) {
|
||||
notification["error"]({
|
||||
@@ -146,30 +146,30 @@ export function ProductionBoardKanbanComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const totalHrs = data
|
||||
.reduce(
|
||||
(acc, val) =>
|
||||
acc +
|
||||
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAB = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAR = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
const totalHrs = data
|
||||
.reduce(
|
||||
(acc, val) =>
|
||||
acc +
|
||||
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAB = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAR = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
|
||||
const standardSizes = {
|
||||
xs: "250",
|
||||
|
||||
@@ -344,15 +344,16 @@ export function ScoreboardTimeTicketsStats({bodyshop}) {
|
||||
|
||||
const jobData = {};
|
||||
|
||||
data.jobs.forEach((job) => {
|
||||
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||
});
|
||||
const dataJobs = data.jobs.map((job) => ({
|
||||
...job,
|
||||
tthrs: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
}));
|
||||
|
||||
jobData.tthrs = data.jobs
|
||||
jobData.tthrs = dataJobs
|
||||
.reduce((acc, val) => acc + val.tthrs, 0)
|
||||
.toFixed(1);
|
||||
|
||||
jobData.count = data.jobs.length.toFixed(0);
|
||||
jobData.count = dataJobs.length.toFixed(0);
|
||||
|
||||
return {
|
||||
fixed: ret,
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { onError } from "@apollo/client/link/error";
|
||||
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors)
|
||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||
console.log(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
)
|
||||
);
|
||||
);
|
||||
Sentry.captureException({ message, locations, path });
|
||||
});
|
||||
}
|
||||
if (networkError)
|
||||
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
||||
console.log(operation.getContext());
|
||||
return forward(operation);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,21 @@ export const INTROSPECTION = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_EULA = gql`
|
||||
query QUERY_EULA($now: timestamptz!) {
|
||||
eulas(where: {effective_date: {_lte: $now}, _or: [{end_date: {_is_null: true}}, {end_date: {_gt: $now}}]}) {
|
||||
id
|
||||
content
|
||||
|
||||
eula_acceptances {
|
||||
id
|
||||
date_accepted
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_BODYSHOP = gql`
|
||||
query QUERY_BODYSHOP {
|
||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||
|
||||
@@ -31,6 +31,14 @@ export const UPDATE_ASSOCIATION = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_EULA_ACCEPTANCE = gql`
|
||||
mutation INSERT_EULA_ACCEPTANCE($eulaAcceptance:eula_acceptances_insert_input!) {
|
||||
insert_eula_acceptances_one(object: $eulaAcceptance){
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPSERT_USER = gql`
|
||||
mutation UPSERT_USER($authEmail: String!, $authToken: String!) {
|
||||
insert_users(
|
||||
|
||||
@@ -13,6 +13,7 @@ import reportWebVitals from "./reportWebVitals";
|
||||
import "./translations/i18n";
|
||||
import "./utils/CleanAxios";
|
||||
import { ConfigProvider } from "antd";
|
||||
|
||||
//import { BrowserTracing } from "@sentry/tracing";
|
||||
//import "antd/dist/antd.css";
|
||||
// import "antd/dist/antd.less";
|
||||
@@ -25,24 +26,20 @@ if (process.env.NODE_ENV !== "development") {
|
||||
Sentry.init({
|
||||
dsn: "https://a6acc91c073e414196014b8484627a61@o492140.ingest.sentry.io/4504561071161344",
|
||||
ignoreErrors: [
|
||||
|
||||
"ResizeObserver loop",
|
||||
"Module specifier, 'fs' does not start",
|
||||
"Module specifier, 'zlib' does not start with",
|
||||
],
|
||||
integrations: [
|
||||
// new BrowserTracing(),
|
||||
// new Sentry.Integrations.Breadcrumbs({ console: true }),
|
||||
// new Sentry.Replay(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: false,
|
||||
blockAllMedia: true,
|
||||
}),
|
||||
new Sentry.BrowserTracing(),
|
||||
],
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
// replaysSessionSampleRate: 0.1,
|
||||
// // If the entire session is not sampled, use the below sample rate to sample
|
||||
// // sessions when an error occurs.
|
||||
// replaysOnErrorSampleRate: 1.0,
|
||||
tracesSampleRate: 1.0,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
environment: process.env.NODE_ENV,
|
||||
// tracesSampleRate: 0.2,
|
||||
// We recommend adjusting this value in production, or using tracesSampler
|
||||
// for finer control
|
||||
// tracesSampleRate: 0.5,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ export function JobsDetailPage({
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transormJobToForm(job));
|
||||
form.setFieldsValue(transformJobToForm(job));
|
||||
form.resetFields();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -277,7 +277,7 @@ export function JobsDetailPage({
|
||||
onFinish={handleFinish}
|
||||
{...formItemLayout}
|
||||
autoComplete={"off"}
|
||||
initialValues={transormJobToForm(job)}
|
||||
initialValues={transformJobToForm(job)}
|
||||
>
|
||||
<PageHeader
|
||||
// onBack={() => window.history.back()}
|
||||
@@ -383,25 +383,23 @@ export function JobsDetailPage({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
||||
|
||||
const transormJobToForm = (job) => {
|
||||
Object.keys(job.parts_tax_rates).forEach((parttype) => {
|
||||
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
|
||||
if (key.includes("tx_in")) {
|
||||
if (
|
||||
job.parts_tax_rates[parttype][key] === "Y" ||
|
||||
job.parts_tax_rates[parttype][key] === true
|
||||
) {
|
||||
job.parts_tax_rates[parttype][key] = true;
|
||||
} else {
|
||||
job.parts_tax_rates[parttype][key] = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
const transformJobToForm = (job) => {
|
||||
const transformedJob = { ...job };
|
||||
|
||||
return {
|
||||
...job,
|
||||
loss_date: job.loss_date ? dayjs(job.loss_date) : null,
|
||||
date_estimated: job.date_estimated ? dayjs(job.date_estimated) : null,
|
||||
};
|
||||
transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => {
|
||||
acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => {
|
||||
if (key.includes("tx_in")) {
|
||||
innerAcc[key] = transformedJob.parts_tax_rates[parttype][key] === "Y" || transformedJob.parts_tax_rates[parttype][key] === true;
|
||||
} else {
|
||||
innerAcc[key] = transformedJob.parts_tax_rates[parttype][key];
|
||||
}
|
||||
return innerAcc;
|
||||
}, {});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
transformedJob.loss_date = transformedJob.loss_date ? dayjs(transformedJob.loss_date) : null;
|
||||
transformedJob.date_estimated = transformedJob.date_estimated ? dayjs(transformedJob.date_estimated) : null;
|
||||
|
||||
return transformedJob;
|
||||
};
|
||||
@@ -109,3 +109,13 @@ export const setAuthlevel = (authlevel) => ({
|
||||
type: UserActionTypes.SET_AUTH_LEVEL,
|
||||
payload: authlevel,
|
||||
});
|
||||
|
||||
export const setCurrentEula = (eula) => ({
|
||||
type: UserActionTypes.SET_CURRENT_EULA,
|
||||
payload: eula,
|
||||
});
|
||||
|
||||
export const acceptEula = () => ({
|
||||
type: UserActionTypes.EULA_ACCEPTED,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import UserActionTypes from "./user.types";
|
||||
const INITIAL_STATE = {
|
||||
currentUser: {
|
||||
authorized: null,
|
||||
eulaIsAccepted: false,
|
||||
//language: "en-US"
|
||||
},
|
||||
bodyshop: null,
|
||||
@@ -17,6 +18,7 @@ const INITIAL_STATE = {
|
||||
loading: false,
|
||||
},
|
||||
authLevel: 0,
|
||||
currentEula: null,
|
||||
};
|
||||
|
||||
const userReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -63,11 +65,19 @@ const userReducer = (state = INITIAL_STATE, action) => {
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
case UserActionTypes.EULA_ACCEPTED:
|
||||
return {
|
||||
...state,
|
||||
currentUser:{...state.currentUser, eulaIsAccepted: true},
|
||||
currentEula: null,
|
||||
};
|
||||
case UserActionTypes.SIGN_IN_SUCCESS:
|
||||
const{ currentEula,...currentUser} = action.payload
|
||||
return {
|
||||
...state,
|
||||
loginLoading: false,
|
||||
currentUser: action.payload,
|
||||
currentUser: currentUser,
|
||||
currentEula,
|
||||
error: null,
|
||||
};
|
||||
case UserActionTypes.SIGN_OUT_SUCCESS:
|
||||
|
||||
@@ -43,6 +43,9 @@ import {
|
||||
validatePasswordResetSuccess,
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import {QUERY_EULA} from "../../graphql/bodyshop.queries";
|
||||
import day from "../../utils/day";
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -73,6 +76,8 @@ export function* signInWithEmail({ payload: { email, password } }) {
|
||||
export function* onCheckUserSession() {
|
||||
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
|
||||
}
|
||||
|
||||
|
||||
export function* isUserAuthenticated() {
|
||||
try {
|
||||
logImEXEvent("redux_auth_check");
|
||||
@@ -85,6 +90,15 @@ export function* isUserAuthenticated() {
|
||||
|
||||
LogRocket.identify(user.email);
|
||||
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
variables: {
|
||||
now: day()
|
||||
},
|
||||
});
|
||||
|
||||
const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0;
|
||||
|
||||
yield put(
|
||||
signInSuccess({
|
||||
uid: user.uid,
|
||||
@@ -92,6 +106,8 @@ export function* isUserAuthenticated() {
|
||||
displayName: user.displayName,
|
||||
photoURL: user.photoURL,
|
||||
authorized: true,
|
||||
eulaIsAccepted,
|
||||
currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0],
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -36,3 +36,8 @@ export const selectLoginLoading = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.loginLoading
|
||||
);
|
||||
|
||||
export const selectCurrentEula = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.currentEula
|
||||
);
|
||||
|
||||
@@ -32,5 +32,7 @@ const UserActionTypes = {
|
||||
CHECK_ACTION_CODE_START: "CHECK_ACTION_CODE_START",
|
||||
CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS",
|
||||
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
|
||||
SET_CURRENT_EULA: "SET_CURRENT_EULA",
|
||||
EULA_ACCEPTED : "EULA_ACCEPTED",
|
||||
};
|
||||
export default UserActionTypes;
|
||||
|
||||
@@ -947,6 +947,41 @@
|
||||
"updated": "Document updated successfully. "
|
||||
}
|
||||
},
|
||||
"eula": {
|
||||
"titles": {
|
||||
"modal": "Terms and Conditions",
|
||||
"upper_card": "Acknowledgement"
|
||||
},
|
||||
"messages": {
|
||||
"first_name": "Please enter your first name.",
|
||||
"last_name": "Please enter your last name.",
|
||||
"business_name": "Please enter your legal business name.",
|
||||
"phone_number": "Please enter your phone number.",
|
||||
"date_accepted": "Please enter Today's Date.",
|
||||
"accepted_terms": "Please accept the terms and conditions of this agreement."
|
||||
},
|
||||
"buttons": {
|
||||
"accept": "Accept EULA"
|
||||
},
|
||||
"labels": {
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"business_name": "Legal Business Name",
|
||||
"phone_number": "Phone Number",
|
||||
"address": "Address",
|
||||
"date_accepted": "Date Accepted",
|
||||
"accepted_terms": "I accept the terms and conditions of this agreement."
|
||||
},
|
||||
"content": {
|
||||
"never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting."
|
||||
},
|
||||
"errors": {
|
||||
"acceptance": {
|
||||
"message": "Eula Acceptance Error",
|
||||
"description": "Something went wrong while accepting the EULA. Please try again."
|
||||
}
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Email not sent. Error encountered while sending {{message}}"
|
||||
|
||||
@@ -947,6 +947,41 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"eula": {
|
||||
"titles": {
|
||||
"modal": "Terms and Conditions",
|
||||
"upper_card": "Acknowledgement"
|
||||
},
|
||||
"messages": {
|
||||
"first_name": "Please enter your first name.",
|
||||
"last_name": "Please enter your last name.",
|
||||
"business_name": "Please enter your legal business name.",
|
||||
"phone_number": "Please enter your phone number.",
|
||||
"date_accepted": "Please enter Today's Date.",
|
||||
"accepted_terms": "Please accept the terms and conditions of this agreement."
|
||||
},
|
||||
"buttons": {
|
||||
"accept": "Accept EULA"
|
||||
},
|
||||
"labels": {
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"business_name": "Legal Business Name",
|
||||
"phone_number": "Phone Number",
|
||||
"address": "Address",
|
||||
"date_accepted": "Date Accepted",
|
||||
"accepted_terms": "I accept the terms and conditions of this agreement."
|
||||
},
|
||||
"content": {
|
||||
"never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting."
|
||||
},
|
||||
"errors": {
|
||||
"acceptance": {
|
||||
"message": "Eula Acceptance Error",
|
||||
"description": "Something went wrong while accepting the EULA. Please try again."
|
||||
}
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}"
|
||||
|
||||
@@ -947,6 +947,41 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"eula": {
|
||||
"titles": {
|
||||
"modal": "Terms and Conditions",
|
||||
"upper_card": "Acknowledgement"
|
||||
},
|
||||
"messages": {
|
||||
"first_name": "Please enter your first name.",
|
||||
"last_name": "Please enter your last name.",
|
||||
"business_name": "Please enter your legal business name.",
|
||||
"phone_number": "Please enter your phone number.",
|
||||
"date_accepted": "Please enter Today's Date.",
|
||||
"accepted_terms": "Please accept the terms and conditions of this agreement."
|
||||
},
|
||||
"buttons": {
|
||||
"accept": "Accept EULA"
|
||||
},
|
||||
"labels": {
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"business_name": "Legal Business Name",
|
||||
"phone_number": "Phone Number",
|
||||
"address": "Address",
|
||||
"date_accepted": "Date Accepted",
|
||||
"accepted_terms": "I accept the terms and conditions of this agreement."
|
||||
},
|
||||
"content": {
|
||||
"never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting."
|
||||
},
|
||||
"errors": {
|
||||
"acceptance": {
|
||||
"message": "Eula Acceptance Error",
|
||||
"description": "Something went wrong while accepting the EULA. Please try again."
|
||||
}
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}"
|
||||
|
||||
@@ -18,7 +18,7 @@ export function DateTimeFormatter(props) {
|
||||
: null;
|
||||
}
|
||||
export function DateTimeFormatterFunction(date) {
|
||||
return moment(date).format("MM/DD/YYYY hh:mm a");
|
||||
return dayjs(date).format("MM/DD/YYYY hh:mm a");
|
||||
}
|
||||
export function TimeFormatter(props) {
|
||||
return props.children
|
||||
|
||||
@@ -12,6 +12,8 @@ import apolloLogger from "apollo-link-logger";
|
||||
//import axios from "axios";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import errorLink from "../graphql/apollo-error-handling";
|
||||
import { SentryLink } from "apollo-link-sentry";
|
||||
|
||||
//import { store } from "../redux/store";
|
||||
const httpLink = new HttpLink({
|
||||
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
|
||||
@@ -105,18 +107,30 @@ const link = split(
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
return (
|
||||
auth.currentUser &&
|
||||
auth.currentUser.getIdToken().then((token) => {
|
||||
if (token) {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : "",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
auth.currentUser
|
||||
.getIdToken()
|
||||
.then((token) => {
|
||||
if (token) {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : "",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
"Authentication error. Unable to add authorization token because it was empty."
|
||||
);
|
||||
return { headers };
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"Authentication error. Unable to add authorization token.",
|
||||
error.message
|
||||
);
|
||||
return { headers };
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -138,8 +152,10 @@ if (process.env.NODE_ENV === "development") {
|
||||
}
|
||||
|
||||
middlewares.push(
|
||||
roundTripLink.concat(
|
||||
retryLink.concat(errorLink.concat(authLink.concat(link)))
|
||||
new SentryLink().concat(
|
||||
roundTripLink.concat(
|
||||
retryLink.concat(errorLink.concat(authLink.concat(link)))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -32,11 +32,6 @@ import objectSupport from 'dayjs/plugin/objectSupport';
|
||||
import toArray from 'dayjs/plugin/toArray';
|
||||
import toObject from 'dayjs/plugin/toObject';
|
||||
|
||||
// import badMutable from 'dayjs/plugin/badMutable';
|
||||
// import preParsePostFormat from 'dayjs/plugin/preParsePostFormat';
|
||||
|
||||
|
||||
// dayjs.extend(badMutable); // TODO: Client Update - This is not advised, scoreboard page
|
||||
dayjs.extend(toObject);
|
||||
dayjs.extend(toArray);
|
||||
dayjs.extend(objectSupport);
|
||||
@@ -46,7 +41,6 @@ dayjs.extend(isToday);
|
||||
dayjs.extend(localeData);
|
||||
dayjs.extend(quarterOfYear);
|
||||
dayjs.extend(localizedFormat);
|
||||
// dayjs.extend(preParsePostFormat); // TODO: This should not be needed
|
||||
dayjs.extend(isLeapYear);
|
||||
dayjs.extend(isoWeeksInYear);
|
||||
dayjs.extend(isoWeek);
|
||||
|
||||
16
client/src/utils/eulaize.js
Normal file
16
client/src/utils/eulaize.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
const filename = process.argv[2];
|
||||
|
||||
fs.readFile(filename, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(`Error reading file ${filename}:`, err);
|
||||
return;
|
||||
}
|
||||
const filteredData = JSON.stringify(data);
|
||||
console.log('Select the content between the quotes below and paste it into the EULA Content field in the EULA Content table in the database.')
|
||||
console.log('--------------------------------------------------')
|
||||
console.log(filteredData);
|
||||
console.log('--------------------------------------------------')
|
||||
});
|
||||
1669
package-lock.json
generated
1669
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -18,21 +18,21 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.490.0",
|
||||
"@aws-sdk/client-ses": "^3.490.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.490.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.503.0",
|
||||
"@aws-sdk/client-ses": "^3.503.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.503.0",
|
||||
"@opensearch-project/opensearch": "^2.5.0",
|
||||
"aws4": "^1.12.0",
|
||||
"axios": "^1.6.5",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cloudinary": "^1.41.2",
|
||||
"cloudinary": "^2.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "2.8.5",
|
||||
"csrf": "^3.1.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"express": "^4.18.2",
|
||||
"firebase-admin": "^12.0.0",
|
||||
"graphql": "^16.8.1",
|
||||
@@ -54,7 +54,7 @@
|
||||
"rimraf": "^5.0.5",
|
||||
"soap": "^1.0.0",
|
||||
"socket.io": "^4.7.4",
|
||||
"ssh2-sftp-client": "^9.1.0",
|
||||
"ssh2-sftp-client": "^10.0.3",
|
||||
"stripe": "^14.11.0",
|
||||
"twilio": "^4.20.0",
|
||||
"uuid": "^9.0.1",
|
||||
|
||||
Reference in New Issue
Block a user