diff --git a/client/package.json b/client/package.json index d780f0d69..d447ae552 100644 --- a/client/package.json +++ b/client/package.json @@ -62,6 +62,7 @@ "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", @@ -99,6 +100,7 @@ "buildcra": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build", "test": "cypress open", "eject": "react-scripts eject", + "eulaize": "node src/utils/eulaize.js", "madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ." }, "eslintConfig": { diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index d858ce36a..caa5b94f2 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -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"; @@ -20,6 +21,7 @@ import {selectBodyshop, 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") @@ -42,7 +44,6 @@ const mapDispatchToProps = (dispatch) => ({ }); export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) { - const client = useSplitClient().client; const [listenersAdded, setListenersAdded] = useState(false) const {t} = useTranslation(); @@ -121,6 +122,10 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline} /> ); + if (!currentUser.eulaIsAccepted) { + return + } + // Any route that is not assigned and matched will default to the Landing Page component return ( }> @@ -131,10 +136,12 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline} }/> }/> }/> - }> + }> }/> - }> + }> }/> }> diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index 8eab784c9..c49788f4d 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -147,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; diff --git a/client/src/App/themeProvider.js b/client/src/App/themeProvider.js index 9bf301d61..a35278ea2 100644 --- a/client/src/App/themeProvider.js +++ b/client/src/App/themeProvider.js @@ -1,4 +1,9 @@ import {defaultsDeep} from "lodash"; +import {theme} from "antd"; + +const {defaultAlgorithm, darkAlgorithm} = theme; + +let isDarkMode = false; /** * Default theme @@ -6,6 +11,11 @@ import {defaultsDeep} from "lodash"; */ const defaultTheme = { components: { + Table: { + rowHoverBg: '#e7f3ff', + rowSelectedBg: '#e6f7ff', + headerSortHoverBg: 'transparent', + }, Menu: { darkItemHoverBg: '#1677ff', itemHoverBg: '#1677ff', @@ -40,7 +50,11 @@ const devTheme = { */ const prodTheme = {}; -const theme = process.env.NODE_ENV === "development" ? devTheme +const currentTheme = process.env.NODE_ENV === "development" ? devTheme : prodTheme; -export default defaultsDeep(theme, defaultTheme); \ No newline at end of file +const finaltheme = { + algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, + ...defaultsDeep(currentTheme, defaultTheme) +} +export default finaltheme; \ No newline at end of file diff --git a/client/src/components/eula/eula.component.jsx b/client/src/components/eula/eula.component.jsx new file mode 100644 index 000000000..964a0751d --- /dev/null +++ b/client/src/components/eula/eula.component.jsx @@ -0,0 +1,226 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row, Space} 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 = (e) => { + const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight; + if (bottom && !hasEverScrolledToBottom) { + setHasEverScrolledToBottom(true); + } + }; + + 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; + await insertEulaAcceptance({ + variables: { + eulaAcceptance: { + eulaid: eulaId, + useremail, + ...otherFormValues, + 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} + ( +