@@ -48,25 +48,16 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
rome-api-deploy:
|
rome-api-deploy:
|
||||||
@@ -185,7 +176,7 @@ jobs:
|
|||||||
app-beta-build:
|
app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: xlarge
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -200,6 +191,7 @@ jobs:
|
|||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-beta/"
|
to: "s3://imex-online-beta/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
rome-app-beta-build:
|
rome-app-beta-build:
|
||||||
@@ -251,31 +243,23 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
- run: npm run build:test
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test/"
|
to: "s3://imex-online-test/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
test-app-beta-build:
|
test-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
machine: true
|
||||||
|
resource_class: snaptsoft/dell
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -291,6 +275,7 @@ jobs:
|
|||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test-beta/"
|
to: "s3://imex-online-test-beta/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
rome-test-app-beta-build:
|
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=https://db.romeonline.io/v1/graphql
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
||||||
REACT_APP_GA_CODE=231103507
|
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
|
// craco.config.js
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
|
||||||
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
||||||
const {theme} = require('antd/lib');
|
const {theme} = require('antd/lib');
|
||||||
|
|
||||||
@@ -10,52 +9,31 @@ const {defaultAlgorithm, defaultSeed} = theme;
|
|||||||
const mapToken = defaultAlgorithm(defaultSeed);
|
const mapToken = defaultAlgorithm(defaultSeed);
|
||||||
const v4Token = convertLegacyToken(mapToken);
|
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 = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
// {
|
||||||
plugin: SentryWebpackPlugin,
|
// plugin: SentryWebpackPlugin,
|
||||||
options: {
|
// options: {
|
||||||
// sentry-cli configuration
|
// // sentry-cli configuration
|
||||||
authToken:
|
// authToken:
|
||||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||||
org: "snapt-software",
|
// org: "snapt-software",
|
||||||
project: "rome-online",
|
// project: "rome-online",
|
||||||
release: process.env.REACT_APP_GIT_SHA,
|
// release: process.env.REACT_APP_GIT_SHA,
|
||||||
|
//
|
||||||
// webpack-specific configuration
|
// // webpack-specific configuration
|
||||||
include: ".",
|
// include: ".",
|
||||||
ignore: ["node_modules", "webpack.config.js"],
|
// ignore: ["node_modules", "webpack.config.js"],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
options: {
|
options: {
|
||||||
lessLoaderOptions: {
|
lessLoaderOptions: {
|
||||||
lessOptions: {
|
lessOptions: {
|
||||||
modifyVars: {
|
modifyVars: {...v4Token},
|
||||||
...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 }",
|
|
||||||
},
|
|
||||||
javascriptEnabled: true,
|
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": {
|
"dependencies": {
|
||||||
"@ant-design/compatible": "^5.1.2",
|
"@ant-design/compatible": "^5.1.2",
|
||||||
"@ant-design/pro-layout": "^7.17.16",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@apollo/client": "^3.8.10",
|
"@apollo/client": "^3.9.0",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.1.0",
|
"@craco/craco": "^7.1.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.2.1",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.1.0",
|
||||||
"@sentry/react": "^7.93.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
"@sentry/tracing": "^7.93.0",
|
"@sentry/react": "^7.99.0",
|
||||||
|
"@sentry/tracing": "^7.99.0",
|
||||||
"@splitsoftware/splitio-react": "^1.11.0",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"antd": "^5.12.8",
|
"antd": "^5.13.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"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",
|
"craco-less": "^3.0.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.1",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.7.2",
|
"firebase": "^10.7.2",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.7.16",
|
"i18next": "^23.8.1",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"jsoneditor": "^10.0.0",
|
"jsoneditor": "^10.0.0",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"jsreport-browser-client-dist": "^1.3.0",
|
||||||
"libphonenumber-js": "^1.10.53",
|
"libphonenumber-js": "^1.10.54",
|
||||||
"logrocket": "^7.0.0",
|
"logrocket": "^7.0.0",
|
||||||
"markerjs2": "^2.31.4",
|
"markerjs2": "^2.32.0",
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.0",
|
||||||
"phone": "^3.1.42",
|
"phone": "^3.1.42",
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
@@ -44,17 +46,18 @@
|
|||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-big-calendar": "^1.8.6",
|
"react-big-calendar": "^1.8.7",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.0.1",
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.0",
|
"react-grid-gallery": "^1.0.0",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^14.0.0",
|
"react-i18next": "^14.0.1",
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-intersection-observer": "^9.5.3",
|
"react-intersection-observer": "^9.5.3",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
"react-number-format": "^5.1.4",
|
"react-number-format": "^5.1.4",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
@@ -63,7 +66,7 @@
|
|||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-sublime-video": "^0.2.5",
|
"react-sublime-video": "^0.2.5",
|
||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
"recharts": "^2.10.4",
|
"recharts": "^2.11.0",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
@@ -74,7 +77,7 @@
|
|||||||
"styled-components": "^6.1.8",
|
"styled-components": "^6.1.8",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"web-vitals": "^3.5.1",
|
"web-vitals": "^3.5.2",
|
||||||
"workbox-core": "^7.0.0",
|
"workbox-core": "^7.0.0",
|
||||||
"workbox-expiration": "^7.0.0",
|
"workbox-expiration": "^7.0.0",
|
||||||
"workbox-navigation-preload": "^7.0.0",
|
"workbox-navigation-preload": "^7.0.0",
|
||||||
@@ -86,13 +89,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "craco start",
|
"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: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!'",
|
"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",
|
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"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": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -118,9 +123,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@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",
|
"@testing-library/cypress": "^10.0.1",
|
||||||
"cypress": "^13.6.3",
|
"cypress": "^13.6.4",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
|
|||||||
@@ -1,51 +1,53 @@
|
|||||||
import { ApolloProvider } from "@apollo/client";
|
import {ApolloProvider} from "@apollo/client";
|
||||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react';
|
||||||
import { ConfigProvider } from "antd";
|
import {ConfigProvider} from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
import dayjs from "../utils/day";
|
import dayjs from "../utils/day";
|
||||||
import 'dayjs/locale/en';
|
import 'dayjs/locale/en';
|
||||||
import React from "react";
|
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 GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
|
import themeProvider from "./themeProvider";
|
||||||
|
|
||||||
dayjs.locale("en");
|
dayjs.locale("en");
|
||||||
|
|
||||||
export const factory = SplitSdk({
|
const config = {
|
||||||
core: {
|
core: {
|
||||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||||
key: "anon",
|
key: "anon",
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
export const factory = SplitSdk(config);
|
||||||
|
|
||||||
export default function AppContainer() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
function AppContainer() {
|
||||||
<ApolloProvider client={client}>
|
const {t} = useTranslation();
|
||||||
<ConfigProvider
|
|
||||||
//componentSize="small"
|
return (
|
||||||
input={{ autoComplete: "new-password" }}
|
<ApolloProvider client={client}>
|
||||||
locale={enLocale}
|
<ConfigProvider
|
||||||
theme={{
|
//componentSize="small"
|
||||||
token: {
|
input={{autoComplete: "new-password"}}
|
||||||
colorPrimary: "#326ade",
|
locale={enLocale}
|
||||||
colorInfo: "#326ade"
|
theme={themeProvider}
|
||||||
},
|
form={{
|
||||||
}}
|
validateMessages: {
|
||||||
form={{
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
validateMessages: {
|
required: t("general.validation.required", {label: "${label}"}),
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
},
|
||||||
required: t("general.validation.required", { label: "${label}" }),
|
}}
|
||||||
},
|
>
|
||||||
}}
|
<GlobalLoadingBar/>
|
||||||
>
|
<SplitFactoryProvider factory={factory}>
|
||||||
<GlobalLoadingBar />
|
<App/>
|
||||||
<SplitFactory factory={factory}>
|
</SplitFactoryProvider>
|
||||||
<App />
|
</ConfigProvider>
|
||||||
</SplitFactory>
|
</ApolloProvider>
|
||||||
</ConfigProvider>
|
);
|
||||||
</ApolloProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {Route, Routes} from "react-router-dom";
|
|||||||
import {createStructuredSelector} from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||||
|
|
||||||
//Component Imports
|
//Component Imports
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
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 {setOnline} from "../redux/application/application.actions";
|
||||||
import {selectOnline} from "../redux/application/application.selectors";
|
import {selectOnline} from "../redux/application/application.selectors";
|
||||||
import {checkUserSession} from "../redux/user/user.actions";
|
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 PrivateRoute from "../components/PrivateRoute";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
import handleBeta from "../utils/betaHandler";
|
import handleBeta from "../utils/betaHandler";
|
||||||
|
import Eula from "../components/eula/eula.component";
|
||||||
|
|
||||||
const ResetPassword = lazy(() =>
|
const ResetPassword = lazy(() =>
|
||||||
import("../pages/reset-password/reset-password.component")
|
import("../pages/reset-password/reset-password.component")
|
||||||
@@ -35,14 +37,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
currentEula: selectCurrentEula
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
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 client = useSplitClient().client;
|
||||||
const [listenersAdded, setListenersAdded] = useState(false)
|
const [listenersAdded, setListenersAdded] = useState(false)
|
||||||
const {t} = useTranslation();
|
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
|
// Any route that is not assigned and matched will default to the Landing Page component
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<LoadingSpinner message="Rome Online"/>}>
|
<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="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
|
||||||
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
|
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
|
||||||
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></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 path="*" element={<ManagePage/>}/>
|
||||||
</Route>
|
</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 path="*" element={<TechPageContainer/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
|
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
border-bottom: 1px solid #74695c !important;
|
border-bottom: 1px solid #74695c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark .ant-menu-item:hover {
|
|
||||||
background-color: #1890ff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
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 {
|
.job-line-manual {
|
||||||
color: tomato;
|
color: tomato;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.ant-table-column-sort {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||||
background-color: #f4f4f4;
|
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 {Button, Col, Collapse, Result, Row, Space} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {withTranslation} from "react-i18next";
|
import { withTranslation } from "react-i18next";
|
||||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
import {connect} from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {createStructuredSelector} from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
import {
|
||||||
|
selectBodyshop,
|
||||||
|
selectCurrentUser,
|
||||||
|
} from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -135,7 +138,6 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default Sentry.withErrorBoundary(
|
||||||
mapStateToProps,
|
connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))
|
||||||
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";
|
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 ref = useRef();
|
||||||
|
|
||||||
const handleChange = (newDate) => {
|
const handleChange = (newDate) => {
|
||||||
@@ -87,9 +96,13 @@ export function FormDatePicker({bodyshop, value, onChange, onBlur, onlyFuture, i
|
|||||||
onBlur={onBlur || handleBlur}
|
onBlur={onBlur || handleBlur}
|
||||||
showToday={false}
|
showToday={false}
|
||||||
disabledTime
|
disabledTime
|
||||||
{...(onlyFuture && {
|
disabledDate={(d) => {
|
||||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d),
|
if (onlyToday) {
|
||||||
})}
|
return !dayjs().isSame(d, 'day');
|
||||||
|
} else if (onlyFuture) {
|
||||||
|
return dayjs().subtract(1, "day").isAfter(d);
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, {useCallback, useEffect, useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import moment from "moment";
|
import day from '../../utils/day';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {Badge, Card, Space, Table, Tag} from 'antd';
|
import {Badge, Card, Space, Table, Tag} from 'antd';
|
||||||
import {gql, useQuery} from "@apollo/client";
|
import {gql, useQuery} from "@apollo/client";
|
||||||
@@ -69,7 +69,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
|
|||||||
dataIndex: 'start',
|
dataIndex: 'start',
|
||||||
key: 'start',
|
key: 'start',
|
||||||
render: (text) => DateTimeFormatterFunction(text),
|
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'),
|
title: t('job_lifecycle.columns.relative_start'),
|
||||||
@@ -87,7 +87,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
|
|||||||
}
|
}
|
||||||
return isEmpty(a.end) ? 1 : -1;
|
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)
|
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'}}>
|
style={{marginTop: '10px'}}>
|
||||||
<div>
|
<div>
|
||||||
{lifecycleData.durations.summations.map((key) => (
|
{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
|
<div
|
||||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
|||||||
@@ -1,54 +1,54 @@
|
|||||||
import {DownCircleFilled} from "@ant-design/icons";
|
import { DownCircleFilled } from "@ant-design/icons";
|
||||||
import {useMutation} from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import {Button, Dropdown, notification} from "antd";
|
import { Button, Dropdown, notification } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {connect} from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {createStructuredSelector} from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {UPDATE_JOB_STATUS} from "../../graphql/jobs.queries";
|
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({jobid, operation}) =>
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
dispatch(insertAuditTrail({jobid, operation})),
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
||||||
|
|
||||||
export function JobsAdminStatus({insertAuditTrail, bodyshop, job}) {
|
export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
||||||
const {t} = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
||||||
const updateJobStatus = (status) => {
|
const updateJobStatus = (status) => {
|
||||||
mutationUpdateJobstatus({
|
mutationUpdateJobstatus({
|
||||||
variables: {jobId: job.id, status: status},
|
variables: { jobId: job.id, status: status },
|
||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
notification["success"]({message: t("jobs.successes.save")});
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
||||||
});
|
});
|
||||||
// refetch();
|
// refetch();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
notification["error"]({message: t("jobs.errors.saving")});
|
notification["error"]({ message: t("jobs.errors.saving") });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusMenu = {
|
const statusMenu = {
|
||||||
items: bodyshop.md_ro_statuses.statuses.map((item) => ({
|
items: bodyshop.md_ro_statuses.statuses.map((item) => ({
|
||||||
key: item,
|
key: item,
|
||||||
label: item,
|
label: item,
|
||||||
})),
|
})),
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
updateJobStatus(e.key);
|
updateJobStatus(e.key);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -56,7 +56,7 @@ export function JobsAdminStatus({insertAuditTrail, bodyshop, job}) {
|
|||||||
<Button shape="round">
|
<Button shape="round">
|
||||||
<span>{job.status}</span>
|
<span>{job.status}</span>
|
||||||
|
|
||||||
<DownCircleFilled/>
|
<DownCircleFilled />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function JobAdminMarkReexport({
|
|||||||
const result = await markJobExported({
|
const result = await markJobExported({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
date_exported: moment(),
|
date_exported: dayjs(),
|
||||||
default_exported: bodyshop.md_ro_statuses.default_exported,
|
default_exported: bodyshop.md_ro_statuses.default_exported,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -732,12 +732,7 @@ export function JobsDetailHeaderActions({
|
|||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: 'cccontract',
|
key: 'cccontract',
|
||||||
disabled: jobRO || !job.converted,
|
disabled: jobRO || !job.converted,
|
||||||
label: <Link
|
label: <Link state={{jobId: job.id}} to='/manage/courtesycars/contracts/new'>
|
||||||
to={{
|
|
||||||
pathname: "/manage/courtesycars/contracts/new",
|
|
||||||
state: {jobId: job.id},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("menus.jobsactions.newcccontract")}
|
{t("menus.jobsactions.newcccontract")}
|
||||||
</Link>
|
</Link>
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,8 +42,13 @@ export function JobsDetailRatesTaxes({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
formItems.push(Space({children: section, wrap: true}));
|
formItems.push(<>
|
||||||
formItems.push(<Divider/>);
|
<Space wrap>
|
||||||
|
{section}
|
||||||
|
</Space>
|
||||||
|
<Divider/>
|
||||||
|
</>)
|
||||||
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||||
|
|||||||
@@ -220,12 +220,12 @@ export function PayableExportAll({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bodyshop.pbs_serialnumber)
|
if (bodyshop.pbs_serialnumber)
|
||||||
return (
|
return (
|
||||||
<Link to={{state: {billids}, pathname: `/manage/dmsap`}}>
|
<Link to='/manage/dmsap' state={{ billids }}>
|
||||||
<Button>{t("jobs.actions.export")}</Button>
|
<Button>{t("jobs.actions.export")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||||
|
|||||||
@@ -213,12 +213,12 @@ export function PayableExportButton({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bodyshop.pbs_serialnumber)
|
if (bodyshop.pbs_serialnumber)
|
||||||
return (
|
return (
|
||||||
<Link to={{state: {billids: [billId]}, pathname: `/manage/dmsap`}}>
|
<Link to='/manage/dmsap' state={{billids: [billId]}}>
|
||||||
<Button>{t("jobs.actions.export")}</Button>
|
<Button>{t("jobs.actions.export")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||||
|
|||||||
@@ -106,36 +106,36 @@ export function ProductionBoardKanbanComponent({
|
|||||||
|
|
||||||
const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null;
|
const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null;
|
||||||
|
|
||||||
let movedCardNewKanbanParent;
|
let movedCardNewKanbanParent;
|
||||||
if (movedCardWillBeFirst) {
|
if (movedCardWillBeFirst) {
|
||||||
//console.log("==> New Card is first.");
|
//console.log("==> New Card is first.");
|
||||||
movedCardNewKanbanParent = "-1";
|
movedCardNewKanbanParent = "-1";
|
||||||
} else if (movedCardWillBeLast) {
|
} else if (movedCardWillBeLast) {
|
||||||
// console.log("==> New Card is last.");
|
// console.log("==> New Card is last.");
|
||||||
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
|
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
|
||||||
} else if (!!newChildCard) {
|
} else if (!!newChildCard) {
|
||||||
// console.log("==> New Card is somewhere in the middle");
|
// console.log("==> New Card is somewhere in the middle");
|
||||||
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("==> !!!!!!Couldn't find a parent.!!!! <==");
|
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||||
}
|
}
|
||||||
const newChildCardNewParent = newChildCard ? card.id : null;
|
const newChildCardNewParent = newChildCard ? card.id : null;
|
||||||
const update = await client.mutate({
|
const update = await client.mutate({
|
||||||
mutation: generate_UPDATE_JOB_KANBAN(
|
mutation: generate_UPDATE_JOB_KANBAN(
|
||||||
oldChildCard ? oldChildCard.id : null,
|
oldChildCard ? oldChildCard.id : null,
|
||||||
oldChildCardNewParent,
|
oldChildCardNewParent,
|
||||||
card.id,
|
card.id,
|
||||||
movedCardNewKanbanParent,
|
movedCardNewKanbanParent,
|
||||||
destination.toColumnId,
|
destination.toColumnId,
|
||||||
newChildCard ? newChildCard.id : null,
|
newChildCard ? newChildCard.id : null,
|
||||||
newChildCardNewParent
|
newChildCardNewParent
|
||||||
),
|
),
|
||||||
// TODO: optimisticResponse
|
// TODO: optimisticResponse
|
||||||
});
|
});
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: card.id,
|
jobid: card.id,
|
||||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (update.errors) {
|
if (update.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -146,30 +146,30 @@ export function ProductionBoardKanbanComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalHrs = data
|
const totalHrs = data
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, val) =>
|
(acc, val) =>
|
||||||
acc +
|
acc +
|
||||||
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||||
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
const totalLAB = data
|
const totalLAB = data
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
const totalLAR = data
|
const totalLAR = data
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
.slice(-1)[0];
|
.slice(-1)[0];
|
||||||
|
|
||||||
const standardSizes = {
|
const standardSizes = {
|
||||||
xs: "250",
|
xs: "250",
|
||||||
|
|||||||
@@ -344,15 +344,16 @@ export function ScoreboardTimeTicketsStats({bodyshop}) {
|
|||||||
|
|
||||||
const jobData = {};
|
const jobData = {};
|
||||||
|
|
||||||
data.jobs.forEach((job) => {
|
const dataJobs = data.jobs.map((job) => ({
|
||||||
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
...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)
|
.reduce((acc, val) => acc + val.tthrs, 0)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
|
|
||||||
jobData.count = data.jobs.length.toFixed(0);
|
jobData.count = dataJobs.length.toFixed(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fixed: ret,
|
fixed: ret,
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { onError } from "@apollo/client/link/error";
|
import { onError } from "@apollo/client/link/error";
|
||||||
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
const errorLink = onError(
|
const errorLink = onError(
|
||||||
({ graphQLErrors, networkError, operation, forward }) => {
|
({ graphQLErrors, networkError, operation, forward }) => {
|
||||||
if (graphQLErrors)
|
if (graphQLErrors) {
|
||||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||||
console.log(
|
console.log(
|
||||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||||
)
|
);
|
||||||
);
|
Sentry.captureException({ message, locations, path });
|
||||||
|
});
|
||||||
|
}
|
||||||
if (networkError)
|
if (networkError)
|
||||||
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
||||||
console.log(operation.getContext());
|
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`
|
export const QUERY_BODYSHOP = gql`
|
||||||
query QUERY_BODYSHOP {
|
query QUERY_BODYSHOP {
|
||||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
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`
|
export const UPSERT_USER = gql`
|
||||||
mutation UPSERT_USER($authEmail: String!, $authToken: String!) {
|
mutation UPSERT_USER($authEmail: String!, $authToken: String!) {
|
||||||
insert_users(
|
insert_users(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import reportWebVitals from "./reportWebVitals";
|
|||||||
import "./translations/i18n";
|
import "./translations/i18n";
|
||||||
import "./utils/CleanAxios";
|
import "./utils/CleanAxios";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
|
|
||||||
//import { BrowserTracing } from "@sentry/tracing";
|
//import { BrowserTracing } from "@sentry/tracing";
|
||||||
//import "antd/dist/antd.css";
|
//import "antd/dist/antd.css";
|
||||||
// import "antd/dist/antd.less";
|
// import "antd/dist/antd.less";
|
||||||
@@ -25,24 +26,20 @@ if (process.env.NODE_ENV !== "development") {
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: "https://a6acc91c073e414196014b8484627a61@o492140.ingest.sentry.io/4504561071161344",
|
dsn: "https://a6acc91c073e414196014b8484627a61@o492140.ingest.sentry.io/4504561071161344",
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
|
"ResizeObserver loop",
|
||||||
|
"Module specifier, 'fs' does not start",
|
||||||
|
"Module specifier, 'zlib' does not start with",
|
||||||
],
|
],
|
||||||
integrations: [
|
integrations: [
|
||||||
// new BrowserTracing(),
|
Sentry.replayIntegration({
|
||||||
// new Sentry.Integrations.Breadcrumbs({ console: true }),
|
maskAllText: false,
|
||||||
// new Sentry.Replay(),
|
blockAllMedia: true,
|
||||||
|
}),
|
||||||
|
new Sentry.BrowserTracing(),
|
||||||
],
|
],
|
||||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
tracesSampleRate: 1.0,
|
||||||
// in development and sample at a lower rate in production
|
replaysOnErrorSampleRate: 1.0,
|
||||||
// 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,
|
|
||||||
environment: process.env.NODE_ENV,
|
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();
|
await refetch();
|
||||||
form.setFieldsValue(transormJobToForm(job));
|
form.setFieldsValue(transformJobToForm(job));
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -277,7 +277,7 @@ export function JobsDetailPage({
|
|||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
autoComplete={"off"}
|
autoComplete={"off"}
|
||||||
initialValues={transormJobToForm(job)}
|
initialValues={transformJobToForm(job)}
|
||||||
>
|
>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
// onBack={() => window.history.back()}
|
// onBack={() => window.history.back()}
|
||||||
@@ -383,25 +383,23 @@ export function JobsDetailPage({
|
|||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
||||||
|
|
||||||
const transormJobToForm = (job) => {
|
const transformJobToForm = (job) => {
|
||||||
Object.keys(job.parts_tax_rates).forEach((parttype) => {
|
const transformedJob = { ...job };
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => {
|
||||||
...job,
|
acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => {
|
||||||
loss_date: job.loss_date ? dayjs(job.loss_date) : null,
|
if (key.includes("tx_in")) {
|
||||||
date_estimated: job.date_estimated ? dayjs(job.date_estimated) : null,
|
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,
|
type: UserActionTypes.SET_AUTH_LEVEL,
|
||||||
payload: authlevel,
|
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 = {
|
const INITIAL_STATE = {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
authorized: null,
|
authorized: null,
|
||||||
|
eulaIsAccepted: false,
|
||||||
//language: "en-US"
|
//language: "en-US"
|
||||||
},
|
},
|
||||||
bodyshop: null,
|
bodyshop: null,
|
||||||
@@ -17,6 +18,7 @@ const INITIAL_STATE = {
|
|||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
authLevel: 0,
|
authLevel: 0,
|
||||||
|
currentEula: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userReducer = (state = INITIAL_STATE, action) => {
|
const userReducer = (state = INITIAL_STATE, action) => {
|
||||||
@@ -63,11 +65,19 @@ const userReducer = (state = INITIAL_STATE, action) => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
case UserActionTypes.EULA_ACCEPTED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentUser:{...state.currentUser, eulaIsAccepted: true},
|
||||||
|
currentEula: null,
|
||||||
|
};
|
||||||
case UserActionTypes.SIGN_IN_SUCCESS:
|
case UserActionTypes.SIGN_IN_SUCCESS:
|
||||||
|
const{ currentEula,...currentUser} = action.payload
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
currentUser: action.payload,
|
currentUser: currentUser,
|
||||||
|
currentEula,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
case UserActionTypes.SIGN_OUT_SUCCESS:
|
case UserActionTypes.SIGN_OUT_SUCCESS:
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ import {
|
|||||||
validatePasswordResetSuccess,
|
validatePasswordResetSuccess,
|
||||||
} from "./user.actions";
|
} from "./user.actions";
|
||||||
import UserActionTypes from "./user.types";
|
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();
|
const fpPromise = FingerprintJS.load();
|
||||||
|
|
||||||
@@ -73,6 +76,8 @@ export function* signInWithEmail({ payload: { email, password } }) {
|
|||||||
export function* onCheckUserSession() {
|
export function* onCheckUserSession() {
|
||||||
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
|
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function* isUserAuthenticated() {
|
export function* isUserAuthenticated() {
|
||||||
try {
|
try {
|
||||||
logImEXEvent("redux_auth_check");
|
logImEXEvent("redux_auth_check");
|
||||||
@@ -85,6 +90,15 @@ export function* isUserAuthenticated() {
|
|||||||
|
|
||||||
LogRocket.identify(user.email);
|
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(
|
yield put(
|
||||||
signInSuccess({
|
signInSuccess({
|
||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
@@ -92,6 +106,8 @@ export function* isUserAuthenticated() {
|
|||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
photoURL: user.photoURL,
|
photoURL: user.photoURL,
|
||||||
authorized: true,
|
authorized: true,
|
||||||
|
eulaIsAccepted,
|
||||||
|
currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -36,3 +36,8 @@ export const selectLoginLoading = createSelector(
|
|||||||
[selectUser],
|
[selectUser],
|
||||||
(user) => user.loginLoading
|
(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_START: "CHECK_ACTION_CODE_START",
|
||||||
CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS",
|
CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS",
|
||||||
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
|
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
|
||||||
|
SET_CURRENT_EULA: "SET_CURRENT_EULA",
|
||||||
|
EULA_ACCEPTED : "EULA_ACCEPTED",
|
||||||
};
|
};
|
||||||
export default UserActionTypes;
|
export default UserActionTypes;
|
||||||
|
|||||||
@@ -947,6 +947,41 @@
|
|||||||
"updated": "Document updated successfully. "
|
"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": {
|
"emails": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"notsent": "Email not sent. Error encountered while sending {{message}}"
|
"notsent": "Email not sent. Error encountered while sending {{message}}"
|
||||||
|
|||||||
@@ -947,6 +947,41 @@
|
|||||||
"updated": ""
|
"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": {
|
"emails": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}"
|
"notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}"
|
||||||
|
|||||||
@@ -947,6 +947,41 @@
|
|||||||
"updated": ""
|
"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": {
|
"emails": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}"
|
"notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function DateTimeFormatter(props) {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
export function DateTimeFormatterFunction(date) {
|
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) {
|
export function TimeFormatter(props) {
|
||||||
return props.children
|
return props.children
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import apolloLogger from "apollo-link-logger";
|
|||||||
//import axios from "axios";
|
//import axios from "axios";
|
||||||
import { auth } from "../firebase/firebase.utils";
|
import { auth } from "../firebase/firebase.utils";
|
||||||
import errorLink from "../graphql/apollo-error-handling";
|
import errorLink from "../graphql/apollo-error-handling";
|
||||||
|
import { SentryLink } from "apollo-link-sentry";
|
||||||
|
|
||||||
//import { store } from "../redux/store";
|
//import { store } from "../redux/store";
|
||||||
const httpLink = new HttpLink({
|
const httpLink = new HttpLink({
|
||||||
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
|
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
|
||||||
@@ -105,18 +107,30 @@ const link = split(
|
|||||||
const authLink = setContext((_, { headers }) => {
|
const authLink = setContext((_, { headers }) => {
|
||||||
return (
|
return (
|
||||||
auth.currentUser &&
|
auth.currentUser &&
|
||||||
auth.currentUser.getIdToken().then((token) => {
|
auth.currentUser
|
||||||
if (token) {
|
.getIdToken()
|
||||||
return {
|
.then((token) => {
|
||||||
headers: {
|
if (token) {
|
||||||
...headers,
|
return {
|
||||||
authorization: token ? `Bearer ${token}` : "",
|
headers: {
|
||||||
},
|
...headers,
|
||||||
};
|
authorization: token ? `Bearer ${token}` : "",
|
||||||
} else {
|
},
|
||||||
|
};
|
||||||
|
} 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 };
|
return { headers };
|
||||||
}
|
})
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -138,8 +152,10 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
middlewares.push(
|
middlewares.push(
|
||||||
roundTripLink.concat(
|
new SentryLink().concat(
|
||||||
retryLink.concat(errorLink.concat(authLink.concat(link)))
|
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 toArray from 'dayjs/plugin/toArray';
|
||||||
import toObject from 'dayjs/plugin/toObject';
|
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(toObject);
|
||||||
dayjs.extend(toArray);
|
dayjs.extend(toArray);
|
||||||
dayjs.extend(objectSupport);
|
dayjs.extend(objectSupport);
|
||||||
@@ -46,7 +41,6 @@ dayjs.extend(isToday);
|
|||||||
dayjs.extend(localeData);
|
dayjs.extend(localeData);
|
||||||
dayjs.extend(quarterOfYear);
|
dayjs.extend(quarterOfYear);
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
// dayjs.extend(preParsePostFormat); // TODO: This should not be needed
|
|
||||||
dayjs.extend(isLeapYear);
|
dayjs.extend(isLeapYear);
|
||||||
dayjs.extend(isoWeeksInYear);
|
dayjs.extend(isoWeeksInYear);
|
||||||
dayjs.extend(isoWeek);
|
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"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.490.0",
|
"@aws-sdk/client-secrets-manager": "^3.503.0",
|
||||||
"@aws-sdk/client-ses": "^3.490.0",
|
"@aws-sdk/client-ses": "^3.503.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.490.0",
|
"@aws-sdk/credential-provider-node": "^3.503.0",
|
||||||
"@opensearch-project/opensearch": "^2.5.0",
|
"@opensearch-project/opensearch": "^2.5.0",
|
||||||
"aws4": "^1.12.0",
|
"aws4": "^1.12.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"cloudinary": "^1.41.2",
|
"cloudinary": "^2.0.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"firebase-admin": "^12.0.0",
|
"firebase-admin": "^12.0.0",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"soap": "^1.0.0",
|
"soap": "^1.0.0",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"ssh2-sftp-client": "^9.1.0",
|
"ssh2-sftp-client": "^10.0.3",
|
||||||
"stripe": "^14.11.0",
|
"stripe": "^14.11.0",
|
||||||
"twilio": "^4.20.0",
|
"twilio": "^4.20.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user