diff --git a/package.json b/package.json index 605a54ad85..1b0296b1df 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", + "@reduxjs/toolkit": "^2.12.0", "@tanstack/react-query": "4.42.0", "@tanstack/react-query-devtools": "4.42.0", "@uirouter/react": "^1.0.8", @@ -55,6 +56,7 @@ "react-dom": "^18.3.1", "react-error-boundary": "^6.0.0", "react-moment": "^1.1.3", + "react-redux": "^9.3.0", "sass": "^1.62.1", "swagger-ui-react": "^4.1.3", "yup": "^0.32.11", diff --git a/src/app/api/axios.jsx b/src/app/api/axios.jsx index ef6c036313..e27dc92301 100755 --- a/src/app/api/axios.jsx +++ b/src/app/api/axios.jsx @@ -1,18 +1,20 @@ import React, { createContext, useContext, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import Axios from 'axios'; -import { applyAuthTokenInterceptor, getAccessToken } from 'axios-jwt'; +import { applyAuthTokenInterceptor, clearAuthTokens, getAccessToken } from 'axios-jwt'; import { element } from 'prop-types'; import { useSnackbar } from 'notistack'; +import { setLoginState, setUser } from 'components/login/userInfo.slice'; import { getAngularService } from 'services/angular-react-helper'; -import { UserContext } from 'shared/contexts'; const AxiosContext = createContext(); function AxiosProvider({ children }) { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const dispatch = useDispatch(); const authService = getAngularService('authService'); const { enqueueSnackbar } = useSnackbar(); - const { setLoginWidgetState } = useContext(UserContext); const axios = useMemo(() => { const ax = Axios.create({ @@ -25,14 +27,17 @@ function AxiosProvider({ children }) { const requestRefresh = (refreshToken) => { const { cognitoId } = JSON.parse(localStorage.getItem('ngStorage-currentUser')); const headers = { - 'API-Key': '12909a978483dfb8ecd0596c98ae9094', + 'API-Key': apiKey, }; if (cognitoId) { // Notice that this is the global axios instance, not the axiosInstance! <-- important return Axios.post('rest/auth/refresh-token', { refreshToken, cognitoId }, { headers }) .then((response) => response.data.accessToken) .catch(() => { - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); + dispatch(setUser({})); + clearAuthTokens(); + localStorage.removeItem('ngStorage-currentUser'); authService.logout(); }); } @@ -46,7 +51,7 @@ function AxiosProvider({ children }) { const updated = { ...config, }; - updated.headers['API-Key'] = '12909a978483dfb8ecd0596c98ae9094'; + updated.headers['API-Key'] = apiKey; let accessToken = ''; accessToken = await getAccessToken(); if (accessToken) { @@ -77,7 +82,7 @@ function AxiosProvider({ children }) { }, (error) => { if (error?.response?.data === 'Invalid authentication token.' && authService.hasAnyRole(['chpl-admin', 'chpl-onc', 'chpl-onc-acb', 'chpl-cms-staff', 'chpl-developer'])) { - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); authService.logout(); } return Promise.reject(error); diff --git a/src/app/app-wrapper.jsx b/src/app/app-wrapper.jsx index 7dc07bb314..d8c0a6eb27 100755 --- a/src/app/app-wrapper.jsx +++ b/src/app/app-wrapper.jsx @@ -5,8 +5,11 @@ import { ThemeProvider, makeStyles, } from '@material-ui/core'; -import { bool, node } from 'prop-types'; import { CookiesProvider } from 'react-cookie'; +import { Provider } from 'react-redux'; +import { bool, node } from 'prop-types'; + +import store from './store'; import { AnalyticsProvider, HashProvider } from 'shared/contexts'; import ApiWrapper from 'api/api-wrapper'; @@ -38,40 +41,42 @@ const useStyles = makeStyles({ function AppWrapper({ children, showQueryTools = DEVELOPER_MODE }) { const classes = useStyles(); return ( - - - - - - - - - - - -
- -
- {children} + + + + + + + + + + + + +
+ +
+ {children} +
+
- -
- - - - - - - - - - - + + + + + + + + + + + + ); } diff --git a/src/app/components/attestation/attestation-wizard-section-3.jsx b/src/app/components/attestation/attestation-wizard-section-3.jsx index 9cb20ddaf5..c9b1357108 100755 --- a/src/app/components/attestation/attestation-wizard-section-3.jsx +++ b/src/app/components/attestation/attestation-wizard-section-3.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Button, Card, @@ -10,6 +10,7 @@ import { } from '@material-ui/core'; import BorderColorIcon from '@material-ui/icons/BorderColor'; import Moment from 'react-moment'; +import { useSelector } from 'react-redux'; import { bool, func, @@ -17,7 +18,7 @@ import { import { ChplTextField } from 'components/util'; import { eventTrack } from 'services/analytics.service'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { useAnalyticsContext } from 'shared/contexts'; import { developer as developerPropType } from 'shared/prop-types'; import { utilStyles } from 'themes'; @@ -57,8 +58,8 @@ const useStyles = makeStyles({ }); function ChplAttestationWizardSection3({ developer, isSubmitting = false, dispatch }) { + const user = useSelector((state) => state.userInfo.user); const { analytics } = useAnalyticsContext(); - const { user } = useContext(UserContext); const [signature, setSignature] = useState(''); const classes = useStyles(); diff --git a/src/app/components/browser/browser-compared-widget.jsx b/src/app/components/browser/browser-compared-widget.jsx index 8159dc4c86..fac68d11eb 100755 --- a/src/app/components/browser/browser-compared-widget.jsx +++ b/src/app/components/browser/browser-compared-widget.jsx @@ -1,14 +1,14 @@ -import { useContext, useEffect } from 'react'; +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; -import { BrowserContext } from 'shared/contexts'; +import { pushPreviouslyCompared } from 'components/browser/browserInfo.slice'; import { listing as listingPropType } from 'shared/prop-types'; -function ChplBrowserComparedWidget(props) { - const { listing } = props; - const { addToCompared } = useContext(BrowserContext); +function ChplBrowserComparedWidget({ listing }) { + const dispatch = useDispatch(); useEffect(() => { - addToCompared(listing); + dispatch(pushPreviouslyCompared(listing)); }, []); return null; diff --git a/src/app/components/browser/browser-viewed-widget.jsx b/src/app/components/browser/browser-viewed-widget.jsx index ebe862fbcd..ff7f93ae27 100755 --- a/src/app/components/browser/browser-viewed-widget.jsx +++ b/src/app/components/browser/browser-viewed-widget.jsx @@ -1,14 +1,14 @@ -import { useContext, useEffect } from 'react'; +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; -import { BrowserContext } from 'shared/contexts'; +import { pushPreviouslyViewed } from 'components/browser/browserInfo.slice'; import { listing as listingPropType } from 'shared/prop-types'; -function ChplBrowserViewedWidget(props) { - const { listing } = props; - const { addToViewed } = useContext(BrowserContext); +function ChplBrowserViewedWidget({ listing }) { + const dispatch = useDispatch(); useEffect(() => { - addToViewed(listing); + dispatch(pushPreviouslyViewed(listing)); }, []); return null; diff --git a/src/app/components/browser/browserInfo.slice.js b/src/app/components/browser/browserInfo.slice.js new file mode 100755 index 0000000000..522ec75b2f --- /dev/null +++ b/src/app/components/browser/browserInfo.slice.js @@ -0,0 +1,35 @@ +/* eslint-disable no-param-reassign */ +import { createSlice } from '@reduxjs/toolkit'; + +export const browserInfoSlice = createSlice({ + name: 'browserInfo', + initialState: { + api: '/rest', + apiKey: window.__env?.API_KEY ?? '12909a978483dfb8ecd0596c98ae9094', + previouslyCompared: JSON.parse(localStorage.getItem('ngStorage-previouslyCompared')) ?? [], // temporary use of localstorage until redux store is truly global + previouslyViewed: JSON.parse(localStorage.getItem('ngStorage-previouslyViewed')) ?? [], // temporary use of localstorage until redux store is truly global + }, + reducers: { + pushPreviouslyCompared: (state, action) => { + state.previouslyCompared = [ + action.payload.id, + ...state.previouslyCompared.filter((id) => id !== action.payload.id), + ].slice(0, 20); + localStorage.setItem('ngStorage-previouslyCompared', JSON.stringify(state.previouslyCompared)); // temporary until redux store is truly global + }, + pushPreviouslyViewed: (state, action) => { + state.previouslyViewed = [ + action.payload.id, + ...state.previouslyViewed.filter((id) => id !== action.payload.id), + ].slice(0, 20); + localStorage.setItem('ngStorage-previouslyViewed', JSON.stringify(state.previouslyViewed)); // temporary until redux store is truly global + }, + }, +}); + +export const { + pushPreviouslyCompared, + pushPreviouslyViewed, +} = browserInfoSlice.actions; + +export default browserInfoSlice.reducer; diff --git a/src/app/components/demographics/demographics-wizard-section-2.jsx b/src/app/components/demographics/demographics-wizard-section-2.jsx index f247cb0f66..438895593a 100755 --- a/src/app/components/demographics/demographics-wizard-section-2.jsx +++ b/src/app/components/demographics/demographics-wizard-section-2.jsx @@ -14,6 +14,7 @@ import { } from '@material-ui/core'; import BorderColorIcon from '@material-ui/icons/BorderColor'; import Moment from 'react-moment'; +import { useSelector } from 'react-redux'; import { bool, func } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -22,7 +23,7 @@ import ChplUrlChecker from 'components/url-checker/url-checker'; import UrlCheckerContext from 'components/url-checker/url-checker-context'; import { ChplTextField } from 'components/util'; import { eventTrack } from 'services/analytics.service'; -import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; +import { DeveloperContext, useAnalyticsContext } from 'shared/contexts'; import { utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -126,9 +127,9 @@ const getEditField = ({ ); function ChplDemographicsWizardSection2({ isSubmitting = false, dispatch }) { + const user = useSelector((state) => state.userInfo.user); const { developer } = useContext(DeveloperContext); const { analytics } = useAnalyticsContext(); - const { user } = useContext(UserContext); const { url, setUrl } = useContext(UrlCheckerContext); const classes = useStyles(); let formik; diff --git a/src/app/components/listing/details/additional-information/additional-information.jsx b/src/app/components/listing/details/additional-information/additional-information.jsx index 5292aa400f..2fb04b9c16 100644 --- a/src/app/components/listing/details/additional-information/additional-information.jsx +++ b/src/app/components/listing/details/additional-information/additional-information.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Card, @@ -8,11 +8,11 @@ import { ListItem, Typography, } from '@material-ui/core'; +import { useSelector } from 'react-redux'; import { bool } from 'prop-types'; import ChplIcsFamily from 'components/listing/details/ics-family/ics-family'; import { ChplLink } from 'components/util'; -import { UserContext } from 'shared/contexts'; import { getDisplayDateFormat } from 'services/date-util'; import { listing as listingType } from 'shared/prop-types/listing'; @@ -43,7 +43,7 @@ const getRelatives = (source, user, isParent, listings) => listings.map((listing function ChplAdditionalInformation({ isConfirming = false, listing }) { const [currentPi, setCurrentPi] = useState(undefined); - const { user } = useContext(UserContext); + const user = useSelector((state) => state.userInfo.user); useEffect(() => { if (listing.promotingInteroperabilityUserHistory?.length > 0) { diff --git a/src/app/components/listing/details/compliance/compliance.jsx b/src/app/components/listing/details/compliance/compliance.jsx index 03db9a9a30..a5b6df1f97 100644 --- a/src/app/components/listing/details/compliance/compliance.jsx +++ b/src/app/components/listing/details/compliance/compliance.jsx @@ -4,6 +4,7 @@ import { Button, } from '@material-ui/core'; import PlayCircleFilledWhiteOutlinedIcon from '@material-ui/icons/PlayCircleFilledWhiteOutlined'; +import { useSelector } from 'react-redux'; import { arrayOf, bool, func } from 'prop-types'; import ChplDirectReviews from './direct-reviews'; @@ -23,8 +24,9 @@ function ChplCompliance({ initialSurveillance, dispatch = () => {}, }) { + const user = useSelector((state) => state.userInfo.user); const { listing } = useContext(ListingContext); - const { hasAnyRole, user } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const [surveillance, setSurveillance] = useState([]); const [icsSurveillance, setIcsSurveillance] = useState([]); diff --git a/src/app/components/listing/details/compliance/surveillance.jsx b/src/app/components/listing/details/compliance/surveillance.jsx index 8a504204fc..659dc508b1 100644 --- a/src/app/components/listing/details/compliance/surveillance.jsx +++ b/src/app/components/listing/details/compliance/surveillance.jsx @@ -12,6 +12,7 @@ import { } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditIcon from '@material-ui/icons/Edit'; +import { useSelector } from 'react-redux'; import { arrayOf, bool, func } from 'prop-types'; import { getDataDisplay } from './compliance.services'; @@ -131,9 +132,10 @@ function ChplSurveillance({ ics = false, dispatch = () => {}, }) { + const user = useSelector((state) => state.userInfo.user); const { analytics } = useAnalyticsContext(); const { listing } = useContext(ListingContext); - const { hasAnyRole, user } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const [surveillance, setSurveillance] = useState([]); const [expanded, setExpanded] = useState(false); const classes = useStyles(); diff --git a/src/app/components/listing/details/criteria/criterion-details-view.jsx b/src/app/components/listing/details/criteria/criterion-details-view.jsx index 00a43fedd2..20001ba92c 100644 --- a/src/app/components/listing/details/criteria/criterion-details-view.jsx +++ b/src/app/components/listing/details/criteria/criterion-details-view.jsx @@ -16,6 +16,7 @@ import { makeStyles, } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; +import { useSelector } from 'react-redux'; import { arrayOf } from 'prop-types'; import ChplCodeSetIndicator from './code-set-indicator/code-set-indicator'; @@ -27,7 +28,7 @@ import { ChplTooltip, ChplUpdateIndicator, } from 'components/util'; -import { ListingContext, UserContext } from 'shared/contexts'; +import { ListingContext } from 'shared/contexts'; import { accessibilityStandard, certificationResult, @@ -56,7 +57,7 @@ function ChplCriterionDetailsView({ qmsStandards, accessibilityStandards, }) { - const { user } = useContext(UserContext); + const user = useSelector((state) => state.userInfo.user); const { listing } = useContext(ListingContext); const classes = useStyles(); diff --git a/src/app/components/listing/details/listing-information/listing-information.jsx b/src/app/components/listing/details/listing-information/listing-information.jsx index 37a9bf8da3..8bdae8d2c4 100755 --- a/src/app/components/listing/details/listing-information/listing-information.jsx +++ b/src/app/components/listing/details/listing-information/listing-information.jsx @@ -10,6 +10,7 @@ import { Typography, makeStyles, } from '@material-ui/core'; +import { useSelector } from 'react-redux'; import { ChplLink } from 'components/util'; import { getDisplayDateFormat } from 'services/date-util'; @@ -40,8 +41,9 @@ const useStyles = makeStyles({ }); function ChplListingInformation({ listing: initialListing }) { + const user = useSelector((state) => state.userInfo.user); const { hti5ErdIsOn } = useContext(FlagContext); - const { hasAnyRole, user } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const [listing, setListing] = useState(undefined); const classes = useStyles(); diff --git a/src/app/components/listing/details/sed/sed.jsx b/src/app/components/listing/details/sed/sed.jsx index 7c0c94b15f..f5b4db3487 100755 --- a/src/app/components/listing/details/sed/sed.jsx +++ b/src/app/components/listing/details/sed/sed.jsx @@ -14,6 +14,7 @@ import { Typography, makeStyles, } from '@material-ui/core'; +import { useSelector } from 'react-redux'; import ChplSedDownload from './sed-download'; import ChplSedTaskView from './sed-task-view'; @@ -21,7 +22,7 @@ import ChplSedTaskView from './sed-task-view'; import { ChplLink } from 'components/util'; import { sortCriteria } from 'services/criteria.service'; import { getDisplayDateFormat } from 'services/date-util'; -import { FlagContext, UserContext } from 'shared/contexts'; +import { FlagContext } from 'shared/contexts'; import { listing as listingType } from 'shared/prop-types/listing'; import { theme } from 'themes'; @@ -66,8 +67,8 @@ function ChplSed({ listing }) { sedReportFileLocation, sedTestingEndDay, } = listing; + const user = useSelector((state) => state.userInfo.user); const { hti5ErdIsOn } = useContext(FlagContext); - const { user } = useContext(UserContext); const [hasSed, setHasSed] = useState(false); const classes = useStyles(); diff --git a/src/app/components/listing/upload-listing.jsx b/src/app/components/listing/upload-listing.jsx index cb6195244d..04db1c85f1 100755 --- a/src/app/components/listing/upload-listing.jsx +++ b/src/app/components/listing/upload-listing.jsx @@ -8,6 +8,7 @@ import { import CloudUploadOutlinedIcon from '@material-ui/icons/CloudUploadOutlined'; import DeleteIcon from '@material-ui/icons/Delete'; import DoneIcon from '@material-ui/icons/Done'; +import { useSelector } from 'react-redux'; import { func, number } from 'prop-types'; import { getAngularService } from 'services/angular-react-helper'; @@ -57,7 +58,8 @@ function ChplUploadListing({ setWarnings, setDiff, }) { - const API = getAngularService('API'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const Upload = getAngularService('Upload'); const authService = getAngularService('authService'); const [file, setFile] = useState(undefined); @@ -86,7 +88,7 @@ function ChplUploadListing({ url: `${API}/listings/upload/${id}`, headers: { Authorization: `Bearer ${authService.getToken()}`, - 'API-Key': authService.getApiKey(), + 'API-Key': apiKey, }, data: { file, diff --git a/src/app/components/login/admin-menu.jsx b/src/app/components/login/admin-menu.jsx index 6f284206b5..5b7adb1b97 100644 --- a/src/app/components/login/admin-menu.jsx +++ b/src/app/components/login/admin-menu.jsx @@ -7,12 +7,14 @@ import { } from '@material-ui/core'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import { useDispatch, useSelector } from 'react-redux'; import { func } from 'prop-types'; import ChplAdminMenuLinkItem from './navigation/admin-menu-link-item'; import ChplAdminMenuSection from './navigation/admin-menu-section'; import sectionConfigs from './navigation/admin-menu-data'; +import { setLoginState } from 'components/login/userInfo.slice'; import { eventTrack } from 'services/analytics.service'; import { FlagContext, UserContext, useAnalyticsContext } from 'shared/contexts'; import { palette } from 'themes'; @@ -35,13 +37,13 @@ const useStyles = makeStyles({ }); function ChplAdminMenu({ onClose = () => {} }) { + const dispatch = useDispatch(); + const user = useSelector((state) => state.userInfo.user); const { analytics } = useAnalyticsContext(); const { isOn } = useContext(FlagContext); const { hasAnyRole, logout, - setLoginWidgetState, - user, } = useContext(UserContext); const [activeConfigs, setActiveConfigs] = useState([]); const [openSection, setOpenSection] = useState(null); @@ -53,7 +55,7 @@ function ChplAdminMenu({ onClose = () => {} }) { key: 'developers', title: 'Developers', roles: ['chpl-developer'], - items: user?.organizations + items: [...user?.organizations] .sort((a, b) => (a.name < b.name ? -1 : 1)) .map((d) => ({ key: d.id, @@ -80,7 +82,7 @@ function ChplAdminMenu({ onClose = () => {} }) { event: 'Change Password', category: 'Authentication', }); - setLoginWidgetState('CHANGEPASSWORD'); + dispatch(setLoginState('CHANGEPASSWORD')); }; return ( diff --git a/src/app/components/login/components/change-password.jsx b/src/app/components/login/components/change-password.jsx index f29bb8d664..b7db3360a5 100755 --- a/src/app/components/login/components/change-password.jsx +++ b/src/app/components/login/components/change-password.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Button, Card, @@ -9,6 +9,7 @@ import { } from '@material-ui/core'; import ClearIcon from '@material-ui/icons/Clear'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import { useDispatch, useSelector } from 'react-redux'; import { useFormik } from 'formik'; import * as yup from 'yup'; import { useSnackbar } from 'notistack'; @@ -16,8 +17,9 @@ import { useSnackbar } from 'notistack'; import PasswordStrengthMeter from './password-strength-meter'; import { usePostChangePassword } from 'api/auth'; +import { setLoginState } from 'components/login/userInfo.slice'; import { eventTrack } from 'services/analytics.service'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { useAnalyticsContext } from 'shared/contexts'; import { ChplTextField } from 'components/util'; import { palette } from 'themes'; @@ -53,7 +55,8 @@ const validationSchema = yup.object({ }); function ChplChangePassword() { - const { user, setLoginWidgetState } = useContext(UserContext); + const dispatch = useDispatch(); + const user = useSelector((state) => state.userInfo.user); const { analytics } = useAnalyticsContext(); const { enqueueSnackbar } = useSnackbar(); const { mutate } = usePostChangePassword(); @@ -75,7 +78,7 @@ function ChplChangePassword() { event: 'Cancel Password Change', category: 'Authentication', }); - setLoginWidgetState('LOGGEDIN'); + dispatch(setLoginState('LOGGEDIN')); }; const changePassword = () => { @@ -91,7 +94,7 @@ function ChplChangePassword() { }); const body = 'Password successfully changed'; enqueueSnackbar(body, { variant: 'success' }); - setLoginWidgetState('LOGGEDIN'); + dispatch(setLoginState('LOGGEDIN')); }, onError: () => { const body = 'Error. Please check your credentials or contact the administrator'; diff --git a/src/app/components/login/components/force-change-password.jsx b/src/app/components/login/components/force-change-password.jsx index 0032af1277..67a0de6538 100755 --- a/src/app/components/login/components/force-change-password.jsx +++ b/src/app/components/login/components/force-change-password.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Button, Card, @@ -8,6 +8,7 @@ import { makeStyles, } from '@material-ui/core'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import { useSelector, useDispatch } from 'react-redux'; import { func, string } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -18,9 +19,10 @@ import { setAuthTokens } from 'axios-jwt'; import PasswordStrengthMeter from './password-strength-meter'; import { usePostNewPasswordRequired } from 'api/auth'; +import { setUser } from 'components/login/userInfo.slice'; import { getAngularService } from 'services/angular-react-helper'; import { eventTrack } from 'services/analytics.service'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { useAnalyticsContext } from 'shared/contexts'; import { ChplTextField } from 'components/util'; import { palette } from 'themes'; @@ -58,7 +60,8 @@ const validationSchema = yup.object({ function ChplForceChangePassword({ dispatch, sessionId, userName }) { const $rootScope = getAngularService('$rootScope'); const authService = getAngularService('authService'); - const { user, setUser } = useContext(UserContext); + const newDispatch = useDispatch(); + const user = useSelector((state) => state.userInfo.value); const [, setCookie] = useCookies(['cognito_id', 'refresh_token']); const { analytics } = useAnalyticsContext(); const { enqueueSnackbar } = useSnackbar(); @@ -96,7 +99,7 @@ function ChplForceChangePassword({ dispatch, sessionId, userName }) { accessToken: response.accessToken, refreshToken: response.refreshToken, }); - setUser(response.user); + newDispatch(setUser({ user: response.user })); authService.saveCurrentUser(response.user); eventTrack({ ...analytics, diff --git a/src/app/components/login/components/forgot-password.jsx b/src/app/components/login/components/forgot-password.jsx index 80aa67eeb5..bb6431f94c 100755 --- a/src/app/components/login/components/forgot-password.jsx +++ b/src/app/components/login/components/forgot-password.jsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { Button, Card, @@ -8,15 +8,17 @@ import { } from '@material-ui/core'; import ClearIcon from '@material-ui/icons/Clear'; import SendIcon from '@material-ui/icons/Send'; +import { useDispatch } from 'react-redux'; import { string } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; import { useSnackbar } from 'notistack'; import { usePostForgotPassword } from 'api/auth'; -import { eventTrack } from 'services/analytics.service'; +import { setLoginState } from 'components/login/userInfo.slice'; import { ChplTextField } from 'components/util'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { eventTrack } from 'services/analytics.service'; +import { useAnalyticsContext } from 'shared/contexts'; import { palette } from 'themes'; const useStyles = makeStyles({ @@ -38,8 +40,8 @@ const validationSchema = yup.object({ }); function ChplForgotPassword({ userName }) { + const dispatch = useDispatch(); const { analytics } = useAnalyticsContext(); - const { setLoginWidgetState } = useContext(UserContext); const { enqueueSnackbar } = useSnackbar(); const { mutate } = usePostForgotPassword(); @@ -54,7 +56,7 @@ function ChplForgotPassword({ userName }) { category: 'Authentication', }); e.stopPropagation(); - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); }; const catchEnter = (e, target) => { @@ -73,7 +75,7 @@ function ChplForgotPassword({ userName }) { onSuccess: () => { const body = `Forgotten password email sent to ${formik.values.email}; please check your email`; enqueueSnackbar(body, { variant: 'success' }); - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); formik.resetForm(); }, onError: () => { diff --git a/src/app/components/login/components/logged-in.jsx b/src/app/components/login/components/logged-in.jsx index 22ee31c35d..9a37b7e93b 100755 --- a/src/app/components/login/components/logged-in.jsx +++ b/src/app/components/login/components/logged-in.jsx @@ -8,7 +8,9 @@ import { } from '@material-ui/core'; import CreateIcon from '@material-ui/icons/Create'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import { useDispatch, useSelector } from 'react-redux'; +import { setLoginState } from 'components/login/userInfo.slice'; import { eventTrack } from 'services/analytics.service'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; import { palette } from 'themes'; @@ -26,7 +28,9 @@ const useStyles = makeStyles({ }); function ChplLoggedIn() { - const { logout, setLoginWidgetState, user } = useContext(UserContext); + const dispatch = useDispatch(); + const user = useSelector((state) => state.userInfo.user); + const { logout } = useContext(UserContext); const { analytics } = useAnalyticsContext(); const classes = useStyles(); @@ -37,7 +41,7 @@ function ChplLoggedIn() { event: 'Change Password', category: 'Authentication', }); - setLoginWidgetState('CHANGEPASSWORD'); + dispatch(setLoginState('CHANGEPASSWORD')); }; return ( diff --git a/src/app/components/login/components/reset-forgotten-password.jsx b/src/app/components/login/components/reset-forgotten-password.jsx index 379ab60b28..ec2566c0e1 100755 --- a/src/app/components/login/components/reset-forgotten-password.jsx +++ b/src/app/components/login/components/reset-forgotten-password.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Button, Card, @@ -8,6 +8,7 @@ import { makeStyles, } from '@material-ui/core'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import { useDispatch } from 'react-redux'; import { string } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -16,9 +17,10 @@ import { useSnackbar } from 'notistack'; import PasswordStrengthMeter from './password-strength-meter'; import { usePostSetForgottenPassword } from 'api/auth'; +import { setLoginState } from 'components/login/userInfo.slice'; import { ChplTextField } from 'components/util'; import { eventTrack } from 'services/analytics.service'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { useAnalyticsContext } from 'shared/contexts'; import { palette } from 'themes'; const zxcvbn = require('zxcvbn'); @@ -53,8 +55,8 @@ const validationSchema = yup.object({ }); function ChplResetForgottenPassword({ uuid }) { + const dispatch = useDispatch(); const { analytics } = useAnalyticsContext(); - const { setLoginWidgetState } = useContext(UserContext); const { enqueueSnackbar } = useSnackbar(); const { mutate } = usePostSetForgottenPassword(); const [passwordMessages, setPasswordMessages] = useState([]); @@ -82,7 +84,7 @@ function ChplResetForgottenPassword({ uuid }) { category: 'Authentication', }); enqueueSnackbar(body, { variant: 'success' }); - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); }, onError: () => { const body = 'Error. Please check your credentials or contact the administrator'; diff --git a/src/app/components/login/components/signin.jsx b/src/app/components/login/components/signin.jsx index 9111ecd0ca..927e66db0a 100755 --- a/src/app/components/login/components/signin.jsx +++ b/src/app/components/login/components/signin.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Button, Card, @@ -10,6 +10,7 @@ import { } from '@material-ui/core'; import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import { useDispatch } from 'react-redux'; import { func } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -18,10 +19,11 @@ import { setAuthTokens } from 'axios-jwt'; import { useCookies } from 'react-cookie'; import { usePostLogin } from 'api/auth'; +import { setUser } from 'components/login/userInfo.slice'; import { ChplTextField } from 'components/util'; import { getAngularService } from 'services/angular-react-helper'; import { eventTrack } from 'services/analytics.service'; -import { UserContext, useAnalyticsContext } from 'shared/contexts'; +import { useAnalyticsContext } from 'shared/contexts'; import { palette, utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -49,7 +51,7 @@ const validationSchema = yup.object({ function ChplSignin({ dispatch }) { const $rootScope = getAngularService('$rootScope'); const authService = getAngularService('authService'); - const { setUser } = useContext(UserContext); + const newDispatch = useDispatch(); const [, setCookie] = useCookies(['cognito_id', 'refresh_token']); const { analytics } = useAnalyticsContext(); const { enqueueSnackbar } = useSnackbar(); @@ -97,7 +99,7 @@ function ChplSignin({ dispatch }) { }); setCookie('cognito_id', response.user.cognitoId); setCookie('refresh_token', response.refreshToken); - setUser(response.user); + newDispatch(setUser({ user: response.user })); authService.saveCurrentUser(response.user); formik.resetForm(); $rootScope.$broadcast('loggedIn'); diff --git a/src/app/components/login/login.jsx b/src/app/components/login/login.jsx index 136cc01714..2452edf00e 100755 --- a/src/app/components/login/login.jsx +++ b/src/app/components/login/login.jsx @@ -1,5 +1,6 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { func, string } from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; import ChplChangePassword from './components/change-password'; import ChplForceChangePassword from './components/force-change-password'; @@ -8,19 +9,20 @@ import ChplLoggedIn from './components/logged-in'; import ChplResetForgottenPassword from './components/reset-forgotten-password'; import ChplSignin from './components/signin'; -import { UserContext } from 'shared/contexts'; +import { setLoginState } from 'components/login/userInfo.slice'; function ChplLogin({ dispatch = () => {}, uuid = '', }) { - const { loginWidgetState, setLoginWidgetState } = useContext(UserContext); + const loginState = useSelector((state) => state.userInfo.loginState); + const reduxDispatch = useDispatch(); const [sessionId, setSessionId] = useState(''); const [userName, setUserName] = useState(''); useEffect(() => { if (uuid) { - setLoginWidgetState('RESETFORGOTTENPASSWORD'); + reduxDispatch(setLoginState('RESETFORGOTTENPASSWORD')); } }, [uuid]); @@ -30,14 +32,14 @@ function ChplLogin({ setUserName(payload.userName); setSessionId(payload.sessionId); dispatch('forceChangePassword'); - setLoginWidgetState('FORCECHANGEPASSWORD'); + reduxDispatch(setLoginState('FORCECHANGEPASSWORD')); break; case 'forgotPassword': setUserName(payload?.userName ?? ''); - setLoginWidgetState('FORGOTPASSWORD'); + reduxDispatch(setLoginState('FORGOTPASSWORD')); break; case 'loggedIn': - setLoginWidgetState('LOGGEDIN'); + reduxDispatch(setLoginState('LOGGEDIN')); dispatch('loggedIn'); break; default: @@ -45,7 +47,7 @@ function ChplLogin({ } }; - switch (loginWidgetState) { + switch (loginState) { case 'CHANGEPASSWORD': return ( diff --git a/src/app/components/login/toggle.jsx b/src/app/components/login/toggle.jsx index 0e3f6786f1..a2e9ed5aaf 100755 --- a/src/app/components/login/toggle.jsx +++ b/src/app/components/login/toggle.jsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Button, @@ -11,13 +11,14 @@ import { } from '@material-ui/core'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import CloseIcon from '@material-ui/icons/Close'; +import { useDispatch, useSelector } from 'react-redux'; import { func } from 'prop-types'; import { getAccessToken } from 'axios-jwt'; import ChplLogin from './login'; import ChplAdminMenu from './admin-menu'; -import { UserContext } from 'shared/contexts'; +import { setLoginState } from 'components/login/userInfo.slice'; import { theme, palette } from 'themes'; const useStyles = makeStyles({ @@ -78,19 +79,27 @@ const useStyles = makeStyles({ }); function ChplToggle({ dispatch = () => {} }) { - const { loginWidgetState, setLoginWidgetState, user } = useContext(UserContext); + const reduxDispatch = useDispatch(); + const loginState = useSelector((state) => state.userInfo.loginState); + const user = useSelector((state) => state.userInfo.user); const [anchor, setAnchor] = useState(null); const [loginPopoverOpen, setLoginPopoverOpen] = useState(false); const [adminDrawerOpen, setAdminDrawerOpen] = useState(false); - const [title, setTitle] = useState(''); const classes = useStyles(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isToggleOpen = isMobile ? adminDrawerOpen : loginPopoverOpen; useEffect(() => { - getAccessToken().then((token) => (token ? setLoginWidgetState('LOGGEDIN') : setLoginWidgetState('SIGNIN'))); + getAccessToken().then((token) => (token ? reduxDispatch(setLoginState('LOGGEDIN')) : reduxDispatch(setLoginState('SIGNIN')))); }, []); + const getTitle = () => { + if (user?.fullName) { + return (user.fullName); + } + return ('Administrator login'); + }; + const handleClick = (e) => { if (isMobile) { setAdminDrawerOpen(true); @@ -116,14 +125,6 @@ function ChplToggle({ dispatch = () => {} }) { } }; - useEffect(() => { - if (user?.fullName) { - setTitle(user.fullName); - } else { - setTitle('Administrator login'); - } - }, [user]); - return ( <> {} }) { id: 'admin-login-paper', }} > - { loginWidgetState === 'LOGGEDIN' ? ( + { loginState === 'LOGGEDIN' ? ( ) : (
@@ -179,7 +180,7 @@ function ChplToggle({ dispatch = () => {} }) { - { loginWidgetState === 'LOGGEDIN' ? ( + { loginState === 'LOGGEDIN' ? ( ) : (
diff --git a/src/app/components/login/user-wrapper.jsx b/src/app/components/login/user-wrapper.jsx index 9a3142aa83..41f5b383fa 100755 --- a/src/app/components/login/user-wrapper.jsx +++ b/src/app/components/login/user-wrapper.jsx @@ -1,9 +1,11 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useCookies } from 'react-cookie'; import { node } from 'prop-types'; import { clearAuthTokens } from 'axios-jwt'; -import { useCookies } from 'react-cookie'; import ChplLogin from './login'; +import { setLoginState, setUser } from './userInfo.slice'; import { usePostLogout } from 'api/auth'; import { eventTrack } from 'services/analytics.service'; @@ -13,15 +15,15 @@ import { UserContext, useAnalyticsContext } from 'shared/contexts'; function UserWrapper({ children = }) { const $rootScope = getAngularService('$rootScope'); const authService = getAngularService('authService'); + const user = useSelector((state) => state.userInfo.user); + const dispatch = useDispatch(); const { analytics } = useAnalyticsContext(); const postLogout = usePostLogout(); - const [loginWidgetState, setLoginWidgetState] = useState('SIGNIN'); - const [user, setUser] = useState({}); const [, , removeCookie] = useCookies(['cognito_id', 'refresh_token']); useEffect(() => { const update = () => { - setUser(authService.getCurrentUser()); + dispatch(setUser({ user: authService.getCurrentUser() })); }; update(); const deregisterLoginWatcher = $rootScope.$on('loggedIn', update); @@ -40,8 +42,7 @@ function UserWrapper({ children = }) { }; const hasAuthorityOn = (organization) => user?.organizations - .filter((org) => org.id === organization.id) - .length > 0; + .some((org) => org.id === organization.id); const logout = (e) => { e.stopPropagation(); @@ -55,13 +56,13 @@ function UserWrapper({ children = }) { email: user.email, }); } - setUser({}); + dispatch(setUser({})); removeCookie('cognito_id'); removeCookie('refresh_token'); localStorage.removeItem('ngStorage-jwtToken'); localStorage.removeItem('ngStorage-refreshToken'); localStorage.removeItem('ngStorage-currentUser'); - setLoginWidgetState('SIGNIN'); + dispatch(setLoginState('SIGNIN')); clearAuthTokens(); $rootScope.$broadcast('loggedOut'); }; @@ -69,11 +70,7 @@ function UserWrapper({ children = }) { const userState = { hasAnyRole, hasAuthorityOn, - loginWidgetState, logout, - setLoginWidgetState, - setUser, - user, }; return ( diff --git a/src/app/components/login/userInfo.slice.js b/src/app/components/login/userInfo.slice.js new file mode 100755 index 0000000000..cb90a533e3 --- /dev/null +++ b/src/app/components/login/userInfo.slice.js @@ -0,0 +1,25 @@ +/* eslint-disable no-param-reassign */ +import { createSlice } from '@reduxjs/toolkit'; + +export const userInfoSlice = createSlice({ + name: 'userInfo', + initialState: { + loginState: 'SIGNIN', + user: { }, + }, + reducers: { + setLoginState: (state, action) => { + state.loginState = action.payload; + }, + setUser: (state, action) => { + state.user = action.payload.user; + }, + }, +}); + +export const { + setLoginState, + setUser, +} = userInfoSlice.actions; + +export default userInfoSlice.reducer; diff --git a/src/app/components/real-world-testing/rwt-results-wizard-section-3.jsx b/src/app/components/real-world-testing/rwt-results-wizard-section-3.jsx index ee6081e6e6..122737b31d 100755 --- a/src/app/components/real-world-testing/rwt-results-wizard-section-3.jsx +++ b/src/app/components/real-world-testing/rwt-results-wizard-section-3.jsx @@ -10,6 +10,7 @@ import { makeStyles, } from '@material-ui/core'; import BorderColorIcon from '@material-ui/icons/BorderColor'; +import { useSelector } from 'react-redux'; import Moment from 'react-moment'; import { bool, @@ -19,7 +20,7 @@ import { import ChplUrlChecker from 'components/url-checker/url-checker'; import UrlCheckerContext from 'components/url-checker/url-checker-context'; import { eventTrack } from 'services/analytics.service'; -import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; +import { DeveloperContext, useAnalyticsContext } from 'shared/contexts'; import { utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -61,9 +62,9 @@ const useStyles = makeStyles({ }); function ChplRwtResultsWizardSection3({ isSubmitting = false, dispatch }) { + const user = useSelector((state) => state.userInfo.user); const { developer } = useContext(DeveloperContext); const { analytics } = useAnalyticsContext(); - const { user } = useContext(UserContext); const { url, setUrl } = useContext(UrlCheckerContext); const classes = useStyles(); diff --git a/src/app/components/sbul/sbul-wizard-section-3.jsx b/src/app/components/sbul/sbul-wizard-section-3.jsx index c893cf0cd9..bd0ce3b039 100755 --- a/src/app/components/sbul/sbul-wizard-section-3.jsx +++ b/src/app/components/sbul/sbul-wizard-section-3.jsx @@ -11,6 +11,7 @@ import { } from '@material-ui/core'; import BorderColorIcon from '@material-ui/icons/BorderColor'; import Moment from 'react-moment'; +import { useSelector } from 'react-redux'; import { bool, func, @@ -19,7 +20,7 @@ import { import ChplUrlChecker from 'components/url-checker/url-checker'; import UrlCheckerContext from 'components/url-checker/url-checker-context'; import { eventTrack } from 'services/analytics.service'; -import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; +import { DeveloperContext, useAnalyticsContext } from 'shared/contexts'; import { utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -61,9 +62,9 @@ const useStyles = makeStyles({ }); function ChplSbulWizardSection3({ isSubmitting = false, dispatch }) { + const user = useSelector((state) => state.userInfo.user); const { developer } = useContext(DeveloperContext); const { analytics } = useAnalyticsContext(); - const { user } = useContext(UserContext); const { url, setUrl } = useContext(UrlCheckerContext); const classes = useStyles(); diff --git a/src/app/components/subscriptions/subscribe.jsx b/src/app/components/subscriptions/subscribe.jsx index 330e0579ed..2184e09b12 100755 --- a/src/app/components/subscriptions/subscribe.jsx +++ b/src/app/components/subscriptions/subscribe.jsx @@ -8,6 +8,7 @@ import { } from '@material-ui/core'; import SendIcon from '@material-ui/icons/Send'; import SubscriptionsTwoToneIcon from '@material-ui/icons/SubscriptionsTwoTone'; +import { useSelector } from 'react-redux'; import { number } from 'prop-types'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -16,7 +17,7 @@ import { useSnackbar } from 'notistack'; import { usePostSubscription } from 'api/subscriptions'; import { ChplTextField } from 'components/util'; import { eventTrack } from 'services/analytics.service'; -import { ListingContext, UserContext } from 'shared/contexts'; +import { ListingContext } from 'shared/contexts'; const validationSchema = yup.object({ email: yup.string() @@ -25,11 +26,11 @@ const validationSchema = yup.object({ }); function ChplSubscribe({ subscribedObjectTypeId, subscribedObjectId }) { + const user = useSelector((state) => state.userInfo.user); const { enqueueSnackbar } = useSnackbar(); const postSubscription = usePostSubscription(); const [isSubscribing, setIsSubscribing] = useState(false); const { listing } = useContext(ListingContext); - const { user } = useContext(UserContext); let formik; const subscribe = () => { diff --git a/src/app/components/surveillance/complaints/complaints.jsx b/src/app/components/surveillance/complaints/complaints.jsx index 073f0f526c..b6e6c33de3 100755 --- a/src/app/components/surveillance/complaints/complaints.jsx +++ b/src/app/components/surveillance/complaints/complaints.jsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; -import { arrayOf, bool, string } from 'prop-types'; import { makeStyles } from '@material-ui/core'; +import { arrayOf, bool, string } from 'prop-types'; +import { useSelector } from 'react-redux'; import ChplComplaintsView from './complaints-view'; @@ -115,9 +116,15 @@ const staticFilters = [{ ], }]; -function ChplComplaints({ bonusQuery: initialBonusQuery, canAdd, disallowedFilters: initialDisallowedFilters, canEdit = true }) { +function ChplComplaints({ + bonusQuery: initialBonusQuery, + canAdd, + disallowedFilters: initialDisallowedFilters, + canEdit = true, +}) { + const user = useSelector((state) => state.userInfo.user); const { analytics } = useAnalyticsContext(); - const { hasAnyRole, user } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const [bonusQuery, setBonusQuery] = useState(''); const [disallowedFilters, setDisallowedFilters] = useState([]); const [filters, setFilters] = useState(staticFilters); diff --git a/src/app/components/upload/upload-listings.jsx b/src/app/components/upload/upload-listings.jsx index 1c988a99f9..481f03f95d 100755 --- a/src/app/components/upload/upload-listings.jsx +++ b/src/app/components/upload/upload-listings.jsx @@ -11,6 +11,7 @@ import { import CloudUploadOutlinedIcon from '@material-ui/icons/CloudUploadOutlined'; import DeleteIcon from '@material-ui/icons/Delete'; import DoneIcon from '@material-ui/icons/Done'; +import { useSelector } from 'react-redux'; import { useSnackbar } from 'notistack'; import { getAngularService } from 'services/angular-react-helper'; @@ -55,7 +56,8 @@ const useStyles = makeStyles({ }); function ChplUploadListings() { - const API = getAngularService('API'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const Upload = getAngularService('Upload'); const authService = getAngularService('authService'); const { enqueueSnackbar } = useSnackbar(); @@ -78,7 +80,7 @@ function ChplUploadListings() { url: `${API}/listings/upload`, headers: { Authorization: `Bearer ${authService.getToken()}`, - 'API-Key': authService.getApiKey(), + 'API-Key': apiKey, }, data: { file, diff --git a/src/app/components/upload/upload-promoting-interoperability.jsx b/src/app/components/upload/upload-promoting-interoperability.jsx index 48ae96a79a..ec8314b184 100755 --- a/src/app/components/upload/upload-promoting-interoperability.jsx +++ b/src/app/components/upload/upload-promoting-interoperability.jsx @@ -11,6 +11,7 @@ import { import CloudUploadOutlinedIcon from '@material-ui/icons/CloudUploadOutlined'; import DeleteIcon from '@material-ui/icons/Delete'; import DoneIcon from '@material-ui/icons/Done'; +import { useSelector } from 'react-redux'; import { useFormik } from 'formik'; import * as yup from 'yup'; import { useSnackbar } from 'notistack'; @@ -63,9 +64,10 @@ const validationSchema = yup.object({ }); function ChplUploadPromotingInteroperability() { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const [file, setFile] = useState(undefined); const [ele, setEle] = useState(undefined); - const API = getAngularService('API'); const Upload = getAngularService('Upload'); const authService = getAngularService('authService'); const { enqueueSnackbar } = useSnackbar(); @@ -87,7 +89,7 @@ function ChplUploadPromotingInteroperability() { url: `${API}/promoting-interoperability/upload`, headers: { Authorization: `Bearer ${authService.getToken()}`, - 'API-Key': authService.getApiKey(), + 'API-Key': apiKey, }, data: { file, diff --git a/src/app/components/upload/upload-real-world-testing.jsx b/src/app/components/upload/upload-real-world-testing.jsx index d53ac4558b..70ccc22ba4 100644 --- a/src/app/components/upload/upload-real-world-testing.jsx +++ b/src/app/components/upload/upload-real-world-testing.jsx @@ -11,6 +11,7 @@ import { import CloudUploadOutlinedIcon from '@material-ui/icons/CloudUploadOutlined'; import DeleteIcon from '@material-ui/icons/Delete'; import DoneIcon from '@material-ui/icons/Done'; +import { useSelector } from 'react-redux'; import { useSnackbar } from 'notistack'; import { getAngularService } from 'services/angular-react-helper'; @@ -55,7 +56,8 @@ const useStyles = makeStyles({ }); function ChplUploadRealWorldTesting() { - const API = getAngularService('API'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const Upload = getAngularService('Upload'); const authService = getAngularService('authService'); const { enqueueSnackbar } = useSnackbar(); @@ -78,7 +80,7 @@ function ChplUploadRealWorldTesting() { url: `${API}/real-world-testing/upload`, headers: { Authorization: `Bearer ${authService.getToken()}`, - 'API-Key': authService.getApiKey(), + 'API-Key': apiKey, }, data: { file, diff --git a/src/app/components/util/chpl-page-body.jsx b/src/app/components/util/chpl-page-body.jsx index afeac775d5..7bd6e7cb7c 100644 --- a/src/app/components/util/chpl-page-body.jsx +++ b/src/app/components/util/chpl-page-body.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import { - Box, - Container, - makeStyles, - } from '@material-ui/core'; +import { + Box, + Container, + makeStyles, +} from '@material-ui/core'; import { node, oneOf } from 'prop-types'; import { palette, theme } from 'themes'; diff --git a/src/app/index.constants.js b/src/app/index.constants.js deleted file mode 100644 index d76a0fb80c..0000000000 --- a/src/app/index.constants.js +++ /dev/null @@ -1,9 +0,0 @@ -(function() { - 'use strict'; - angular.module('chpl.constants', []) - .constant('API', '/rest') - .constant('CACHE_TIMEOUT', 60) - .constant('CACHE_REFRESH_TIMEOUT', 300) - .constant('RELOAD_TIMEOUT', 3000) - .constant('SPLIT_PRIMARY', '☺'); -})(); diff --git a/src/app/index.js b/src/app/index.js index 2394705088..4762074d6e 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -43,8 +43,6 @@ importAll( require.context('./', true, /^.*\/.*\.scss$/), ); -require('./index.constants'); - const dependencies = [ 'angular-loading-bar', 'ngAnimate', @@ -69,7 +67,6 @@ const dependencies = [ 'chpl.compliance-dashboard', 'chpl.search', 'chpl.components', - 'chpl.constants', 'chpl.registration', 'chpl.shared', ]; diff --git a/src/app/pages/administration/administration.module.js b/src/app/pages/administration/administration.module.js index 3178937c5a..7bbc271f10 100644 --- a/src/app/pages/administration/administration.module.js +++ b/src/app/pages/administration/administration.module.js @@ -12,7 +12,6 @@ import { reactToAngularComponent } from 'services/angular-react-helper'; angular .module('chpl.administration', [ 'angular-confirm', - 'chpl.constants', 'chpl.services', 'ngIdle', 'ngFileUpload', diff --git a/src/app/pages/listing/listing.jsx b/src/app/pages/listing/listing.jsx index 0dcf6ac127..d9a089f885 100755 --- a/src/app/pages/listing/listing.jsx +++ b/src/app/pages/listing/listing.jsx @@ -14,6 +14,7 @@ import { Star, StarOutline, } from '@material-ui/icons'; +import { useSelector } from 'react-redux'; import { number, oneOfType, string } from 'prop-types'; import ChplListingEdit from './listing-edit'; @@ -60,10 +61,12 @@ const useStyles = makeStyles({ }); function ChplListingPage({ id }) { - const API = getAngularService('API'); - const { getApiKey, getToken } = getAngularService('authService'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); + const user = useSelector((state) => state.userInfo.user); + const { getToken } = getAngularService('authService'); const { analytics } = useAnalyticsContext(); - const { hasAnyRole, user } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const { data, isLoading, isSuccess } = useFetchListing({ id }); const [activeSurveillance, setActiveSurveillance] = useState(undefined); const [isEditing, setIsEditing] = useState(false); @@ -99,7 +102,7 @@ function ChplListingPage({ id }) { ...analyticsData.analytics, event: 'Download Original CSV', }); - const downloadLink = `${API}/listings/${listing.id}/uploaded-file?api_key=${getApiKey()}&authorization=Bearer%20${getToken()}`; + const downloadLink = `${API}/listings/${listing.id}/uploaded-file?api_key=${apiKey}&authorization=Bearer%20${getToken()}`; window.open(downloadLink); }; @@ -108,7 +111,7 @@ function ChplListingPage({ id }) { ...analyticsData.analytics, event: 'Download Current CSV', }); - const downloadLink = `${API}/certified_products/${listing.id}/download?api_key=${getApiKey()}&authorization=Bearer%20${getToken()}`; + const downloadLink = `${API}/certified_products/${listing.id}/download?api_key=${apiKey}&authorization=Bearer%20${getToken()}`; window.open(downloadLink); }; diff --git a/src/app/pages/organizations/developers/developers-view.jsx b/src/app/pages/organizations/developers/developers-view.jsx index 1382bfdc53..b6b9967c76 100755 --- a/src/app/pages/organizations/developers/developers-view.jsx +++ b/src/app/pages/organizations/developers/developers-view.jsx @@ -12,6 +12,7 @@ import { makeStyles, } from '@material-ui/core'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; +import { useSelector } from 'react-redux'; import ChplMessaging from './messaging/messaging'; @@ -81,9 +82,10 @@ const useStyles = makeStyles({ }); function ChplDevelopersView() { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const storageKey = 'storageKey-developersView'; - const API = getAngularService('API'); - const { getApiKey, getToken } = getAngularService('authService'); + const { getToken } = getAngularService('authService'); const { analytics } = useAnalyticsContext(); const { hasAnyRole } = useContext(UserContext); const { dispatch, queryString } = useFilterContext(); @@ -138,7 +140,7 @@ function ChplDevelopersView() { event: 'Download Developers', label: recordCount, }); - let url = `${API}/developers/search/download?api_key=${getApiKey()}&${queryString()}`; + let url = `${API}/developers/search/download?api_key=${apiKey}&${queryString()}`; if (hasAnyRole(['chpl-admin', 'chpl-onc', 'chpl-onc-acb'])) { url += `&authorization=Bearer%20${getToken()}`; } diff --git a/src/app/pages/registration/register-user.jsx b/src/app/pages/registration/register-user.jsx index 184167188b..76892636c6 100755 --- a/src/app/pages/registration/register-user.jsx +++ b/src/app/pages/registration/register-user.jsx @@ -6,11 +6,13 @@ import { Typography, makeStyles, } from '@material-ui/core'; +import { useDispatch } from 'react-redux'; import { string } from 'prop-types'; import { useSnackbar } from 'notistack'; import { usePostAuthorizeUser, usePostCreateInvitedUser } from 'api/users'; import ChplLogin from 'components/login/login'; +import { setUser } from 'components/login/userInfo.slice'; import ChplUserCreate from 'components/registration/user-create'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; @@ -45,8 +47,9 @@ const useStyles = makeStyles({ function ChplRegisterUser({ hash }) { const $state = getAngularService('$state'); const authService = getAngularService('authService'); + const dispatch = useDispatch(); const { analytics } = useAnalyticsContext(); - const { hasAnyRole, setUser } = useContext(UserContext); + const { hasAnyRole } = useContext(UserContext); const { enqueueSnackbar } = useSnackbar(); const [state, setState] = useState('create'); const [loginComponentState, setLoginComponentState] = useState('SIGNIN'); @@ -70,7 +73,7 @@ function ChplRegisterUser({ hash }) { enqueueSnackbar('Success: Your new permissions have been added', { variant: 'success', }); - setUser(response.data); + dispatch(setUser(response.data)); authService.saveCurrentUser(response.data); $state.go('administration'); }, diff --git a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx index 9faa9b51d5..f2d8e52d09 100755 --- a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx +++ b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx @@ -11,6 +11,7 @@ import { makeStyles, } from '@material-ui/core'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; +import { useSelector } from 'react-redux'; import { useFetchQuestionableActivity } from 'api/questionable-activity'; import ChplQuestionableActivityDetails from 'components/activity/questionable-activity-details'; @@ -82,8 +83,9 @@ const useStyles = makeStyles({ }); function ChplQuestionableActivityView() { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const storageKey = 'storageKey-questionableActivity'; - const API = getAngularService('API'); const authService = getAngularService('authService'); const { analytics } = useAnalyticsContext(); const [activities, setActivities] = useState([]); @@ -123,7 +125,7 @@ function ChplQuestionableActivityView() { }, [data?.recordCount, pageNumber, data?.results?.length]); useEffect(() => { - setDownloadLink(`${API}/questionable-activity/download?api_key=${authService.getApiKey()}&authorization=Bearer%20${authService.getToken()}`); + setDownloadLink(`${API}/questionable-activity/download?api_key=${apiKey}&authorization=Bearer%20${authService.getToken()}`); }, [API, authService]); /* eslint object-curly-newline: ["error", { "minProperties": 5, "consistent": true }] */ diff --git a/src/app/pages/resources/api/api.jsx b/src/app/pages/resources/api/api.jsx index e345ba7fad..a737fc8235 100755 --- a/src/app/pages/resources/api/api.jsx +++ b/src/app/pages/resources/api/api.jsx @@ -12,6 +12,7 @@ import { makeStyles, } from '@material-ui/core'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; +import { useSelector } from 'react-redux'; import SwaggerUI from 'swagger-ui-react'; import { @@ -90,8 +91,9 @@ const allOptions = [ ]; function ChplResourcesApi() { - const API = getAngularService('API'); - const { getApiKey, getToken } = getAngularService('authService'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); + const { getToken } = getAngularService('authService'); const analytics = { ...useAnalyticsContext().analytics, category: 'CHPL API', @@ -104,14 +106,14 @@ function ChplResourcesApi() { useEffect(() => { const data = { - 'Active products': { data: `${API}/listings/download?listingType=active&api_key=${getApiKey()}&format=json`, label: 'Active products' }, - 'Inactive products': { data: `${API}/listings/download?listingType=inactive&api_key=${getApiKey()}&format=json`, label: 'Inactive products' }, - '2014 edition products': { data: `${API}/listings/download?listingType=2014&api_key=${getApiKey()}&format=json`, label: '2014 edition products' }, - '2011 edition products': { data: `${API}/listings/download?listingType=2011&api_key=${getApiKey()}&format=json`, label: '2011 edition products' }, + 'Active products': { data: `${API}/listings/download?listingType=active&api_key=${apiKey}&format=json`, label: 'Active products' }, + 'Inactive products': { data: `${API}/listings/download?listingType=inactive&api_key=${apiKey}&format=json`, label: 'Inactive products' }, + '2014 edition products': { data: `${API}/listings/download?listingType=2014&api_key=${apiKey}&format=json`, label: '2014 edition products' }, + '2011 edition products': { data: `${API}/listings/download?listingType=2011&api_key=${apiKey}&format=json`, label: '2011 edition products' }, }; setFiles(data); setDownloadOptions(() => allOptions); - }, [API, getApiKey, getToken]); + }, [API, getToken]); const downloadFile = (type) => { if (selectedOption) { diff --git a/src/app/pages/resources/download/download.jsx b/src/app/pages/resources/download/download.jsx index fa6381bd78..906576f8b5 100755 --- a/src/app/pages/resources/download/download.jsx +++ b/src/app/pages/resources/download/download.jsx @@ -14,6 +14,7 @@ import { import CodeIcon from '@material-ui/icons/Code'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import { useSelector } from 'react-redux'; import { ChplLink, @@ -87,11 +88,9 @@ const allOptions = [ ]; function ChplResourcesDownload() { - const API = getAngularService('API'); - const { - getApiKey, - getToken, - } = getAngularService('authService'); + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); + const { getToken } = getAngularService('authService'); const analytics = { ...useAnalyticsContext().analytics, category: 'Download the CHPL', @@ -104,15 +103,15 @@ function ChplResourcesDownload() { useEffect(() => { const data = { - 'Active products summary': { data: `${API}/listings/download?listingType=active&api_key=${getApiKey()}&format=csv`, definition: `${API}/listings/download?listingType=active&api_key=${getApiKey()}&format=csv&definition=true`, label: 'Active products' }, - 'Inactive products summary': { data: `${API}/listings/download?listingType=inactive&api_key=${getApiKey()}&format=csv`, definition: `${API}/listings/download?listingType=inactive&api_key=${getApiKey()}&format=csv&definition=true`, label: 'Inactive products' }, - '2014 edition summary': { data: `${API}/listings/download?listingType=2014&api_key=${getApiKey()}&format=csv`, definition: `${API}/listings/download?listingType=2014&api_key=${getApiKey()}&format=csv&definition=true`, label: '2014 products' }, - 'SVAP Summary': { data: `${API}/svap/download?api_key=${getApiKey()}`, definition: `${API}/svap/download?api_key=${getApiKey()}&definition=true`, label: 'SVAP Summary' }, - 'Service Base URL List': { data: `${API}/service-base-url-list/download?api_key=${getApiKey()}`, label: 'Service Base URL List' }, - 'Surveillance (Basic)': { data: `${API}/surveillance/download?api_key=${getApiKey()}&type=basic&authorization=Bearer%20${getToken()}`, definition: `${API}/surveillance/download?api_key=${getApiKey()}&type=basic&definition=true&authorization=Bearer%20${getToken()}`, label: 'Surveillance (Basic)' }, - 'Surveillance Activity': { data: `${API}/surveillance/download?api_key=${getApiKey()}&type=all`, definition: `${API}/surveillance/download?api_key=${getApiKey()}&type=all&definition=true`, label: 'Surveillance' }, - 'Surveillance Non-Conformities': { data: `${API}/surveillance/download?api_key=${getApiKey()}`, definition: `${API}/surveillance/download?api_key=${getApiKey()}&definition=true`, label: 'Surveillance Non-Conformities' }, - 'Direct Review Activity': { data: `${API}/developers/direct-reviews/download?api_key=${getApiKey()}`, definition: `${API}/developers/direct-reviews/download?api_key=${getApiKey()}&definition=true`, label: 'Direct Review Activity' }, + 'Active products summary': { data: `${API}/listings/download?listingType=active&api_key=${apiKey}&format=csv`, definition: `${API}/listings/download?listingType=active&api_key=${apiKey}&format=csv&definition=true`, label: 'Active products' }, + 'Inactive products summary': { data: `${API}/listings/download?listingType=inactive&api_key=${apiKey}&format=csv`, definition: `${API}/listings/download?listingType=inactive&api_key=${apiKey}&format=csv&definition=true`, label: 'Inactive products' }, + '2014 edition summary': { data: `${API}/listings/download?listingType=2014&api_key=${apiKey}&format=csv`, definition: `${API}/listings/download?listingType=2014&api_key=${apiKey}&format=csv&definition=true`, label: '2014 products' }, + 'SVAP Summary': { data: `${API}/svap/download?api_key=${apiKey}`, definition: `${API}/svap/download?api_key=${apiKey}&definition=true`, label: 'SVAP Summary' }, + 'Service Base URL List': { data: `${API}/service-base-url-list/download?api_key=${apiKey}`, label: 'Service Base URL List' }, + 'Surveillance (Basic)': { data: `${API}/surveillance/download?api_key=${apiKey}&type=basic&authorization=Bearer%20${getToken()}`, definition: `${API}/surveillance/download?api_key=${apiKey}&type=basic&definition=true&authorization=Bearer%20${getToken()}`, label: 'Surveillance (Basic)' }, + 'Surveillance Activity': { data: `${API}/surveillance/download?api_key=${apiKey}&type=all`, definition: `${API}/surveillance/download?api_key=${apiKey}&type=all&definition=true`, label: 'Surveillance' }, + 'Surveillance Non-Conformities': { data: `${API}/surveillance/download?api_key=${apiKey}`, definition: `${API}/surveillance/download?api_key=${apiKey}&definition=true`, label: 'Surveillance Non-Conformities' }, + 'Direct Review Activity': { data: `${API}/developers/direct-reviews/download?api_key=${apiKey}`, definition: `${API}/developers/direct-reviews/download?api_key=${apiKey}&definition=true`, label: 'Direct Review Activity' }, }; setFiles(data); setDownloadOptions(() => allOptions.filter((option) => { @@ -121,7 +120,7 @@ function ChplResourcesDownload() { } return true; })); - }, [API, getApiKey, getToken, hasAnyRole]); + }, [API, getToken, hasAnyRole]); const downloadFile = (type) => { if (selectedOption) { diff --git a/src/app/pages/resources/resources.module.js b/src/app/pages/resources/resources.module.js index e81e4b6cb5..43f558c2d2 100755 --- a/src/app/pages/resources/resources.module.js +++ b/src/app/pages/resources/resources.module.js @@ -9,7 +9,6 @@ import ChplResourcesOverview from './overview/overview-wrapper'; angular .module('chpl.resources', [ - 'chpl.constants', 'chpl.services', ]) .component('chplCmsLookupWrapperBridge', reactToAngularComponent(ChplCmsLookupWrapper)) diff --git a/src/app/pages/search/listings/listings.jsx b/src/app/pages/search/listings/listings.jsx index 592c9eccb4..6623dc207f 100755 --- a/src/app/pages/search/listings/listings.jsx +++ b/src/app/pages/search/listings/listings.jsx @@ -1,4 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import ChplListingsView from './listings-view'; @@ -15,7 +16,7 @@ import { standards, } from 'components/filter/filters'; import { getRadioValueEntry } from 'components/filter/filters/value-entries'; -import { AnalyticsContext, BrowserContext, useAnalyticsContext } from 'shared/contexts'; +import { AnalyticsContext, useAnalyticsContext } from 'shared/contexts'; import { useLocalStorage } from 'services/storage.service'; const staticFilters = [ @@ -46,7 +47,8 @@ const staticFilters = [ }]; function ChplListingsPage() { - const { getPreviouslyCompared, getPreviouslyViewed } = useContext(BrowserContext); + const previouslyCompared = useSelector((state) => state.browserInfo.previouslyCompared); + const previouslyViewed = useSelector((state) => state.browserInfo.previouslyViewed); const { analytics } = useAnalyticsContext(); const [filters, setFilters] = useState(staticFilters); const [favorites] = useLocalStorage('favorites', []); @@ -67,7 +69,7 @@ function ChplListingsPage() { getValueDisplay, getLongValueDisplay: getValueDisplay, })); - }, [getPreviouslyCompared, getPreviouslyViewed, favorites]); + }, [previouslyCompared, previouslyViewed, favorites]); useEffect(() => { if (acbQuery.isLoading || !acbQuery.isSuccess) { @@ -148,9 +150,9 @@ function ChplListingsPage() { getValueDisplay = (value) => { switch (value.value) { case 'Previously Compared': - return `${value.value} (${getPreviouslyCompared().length})`; + return `${value.value} (${previouslyCompared.length})`; case 'Previously Viewed': - return `${value.value} (${getPreviouslyViewed().length})`; + return `${value.value} (${previouslyViewed.length})`; case 'Favorites': return `${value.value} (${favorites.length})`; default: @@ -160,14 +162,14 @@ function ChplListingsPage() { getQuery = (state) => { const value = state.values[0]?.value; - if (value === 'Previously Compared' && getPreviouslyCompared().length > 0) { - return `listingIds=${getPreviouslyCompared().sort((a, b) => (a < b ? -1 : 1)).join(',')}`; + if (value === 'Previously Compared' && previouslyCompared.length > 0) { + return `listingIds=${[...previouslyCompared].sort((a, b) => (a < b ? -1 : 1)).join(',')}`; } - if (value === 'Previously Viewed' && getPreviouslyViewed().length > 0) { - return `listingIds=${getPreviouslyViewed().sort((a, b) => (a < b ? -1 : 1)).join(',')}`; + if (value === 'Previously Viewed' && previouslyViewed.length > 0) { + return `listingIds=${[...previouslyViewed].sort((a, b) => (a < b ? -1 : 1)).join(',')}`; } if (value === 'Favorites' && favorites.length > 0) { - return `listingIds=${favorites.map((fav) => fav.id).sort((a, b) => (a < b ? -1 : 1)).join(',')}`; + return `listingIds=${[...favorites].map((fav) => fav.id).sort((a, b) => (a < b ? -1 : 1)).join(',')}`; } return null; }; diff --git a/src/app/pages/search/sed/sed-view.jsx b/src/app/pages/search/sed/sed-view.jsx index b5b35de25b..95da2d9b2b 100755 --- a/src/app/pages/search/sed/sed-view.jsx +++ b/src/app/pages/search/sed/sed-view.jsx @@ -3,6 +3,7 @@ import { Box, Typography, } from '@material-ui/core'; +import { useSelector } from 'react-redux'; import { useFetchListings } from 'api/search'; import ChplActionButton from 'components/action-widget/action-button'; @@ -25,7 +26,6 @@ import { useFilterContext, } from 'components/filter'; import { eventTrack } from 'services/analytics.service'; -import { getAngularService } from 'services/angular-react-helper'; import { getDisplayDateFormat } from 'services/date-util'; import { getStatusIcon } from 'services/listing.service'; import { useSessionStorage as useStorage } from 'services/storage.service'; @@ -40,9 +40,9 @@ const sortOptions = [ ]; function ChplSedSearchView() { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const storageKey = 'storageKey-sedView'; - const API = getAngularService('API'); - const authService = getAngularService('authService'); const { analytics } = useAnalyticsContext(); const [downloadLink, setDownloadLink] = useState(''); const [listings, setListings] = useState([]); @@ -81,8 +81,8 @@ function ChplSedSearchView() { }, [data?.recordCount, pageNumber, data?.results?.length]); useEffect(() => { - setDownloadLink(`${API}/certified_products/sed_details?api_key=${authService.getApiKey()}`); - }, [API, authService]); + setDownloadLink(`${API}/certified_products/sed_details?api_key=${apiKey}`); + }, [API]); const handleSort = (property, orderDirection) => { eventTrack({ diff --git a/src/app/pages/search/svap/svap-view.jsx b/src/app/pages/search/svap/svap-view.jsx index b045185163..d8a13c3940 100755 --- a/src/app/pages/search/svap/svap-view.jsx +++ b/src/app/pages/search/svap/svap-view.jsx @@ -3,6 +3,7 @@ import { Box, Typography, } from '@material-ui/core'; +import { useSelector } from 'react-redux'; import { useFetchListings } from 'api/search'; import { useFetchSvaps } from 'api/standards'; @@ -25,7 +26,6 @@ import { useFilterContext, } from 'components/filter'; import { eventTrack } from 'services/analytics.service'; -import { getAngularService } from 'services/angular-react-helper'; import { sortCriteria } from 'services/criteria.service'; import { getDisplayDateFormat } from 'services/date-util'; import { getStatusIcon } from 'services/listing.service'; @@ -71,9 +71,9 @@ const parseSvap = ({ svaps }, data) => { }; function ChplSvapSearchView() { + const apiKey = useSelector((state) => state.browserInfo.apiKey); + const API = useSelector((state) => state.browserInfo.api); const storageKey = 'storageKey-svapView'; - const API = getAngularService('API'); - const authService = getAngularService('authService'); const { analytics } = useAnalyticsContext(); const { hasAnyRole } = useContext(UserContext); const [downloadLink, setDownloadLink] = useState(''); @@ -123,8 +123,8 @@ function ChplSvapSearchView() { }, [svapQuery.data, svapQuery.isLoading, svapQuery.isSuccess]); useEffect(() => { - setDownloadLink(`${API}/svap/download?api_key=${authService.getApiKey()}`); - }, [API, authService]); + setDownloadLink(`${API}/svap/download?api_key=${apiKey}`); + }, [API]); const handleSort = (property, orderDirection) => { eventTrack({ diff --git a/src/app/services/login.service.js b/src/app/services/login.service.js index a45edd6c5b..a94173d8e5 100644 --- a/src/app/services/login.service.js +++ b/src/app/services/login.service.js @@ -6,9 +6,8 @@ import { clearAuthTokens } from 'axios-jwt'; /** @ngInclude */ /** @ngInject */ - function authService($injector, $log, $rootScope, $window, API_KEY) { + function authService($injector, $log, $rootScope) { const service = { - getApiKey, getCurrentUser, getToken, hasAnyRole, @@ -21,10 +20,6 @@ import { clearAuthTokens } from 'axios-jwt'; /// ///////////////////////////////////////////////////////////////////// - function getApiKey() { - return API_KEY; - } - function getCurrentUser() { return JSON.parse(localStorage.getItem('ngStorage-currentUser')); } diff --git a/src/app/services/network.service.js b/src/app/services/network.service.js index 72053144df..0c265702d0 100644 --- a/src/app/services/network.service.js +++ b/src/app/services/network.service.js @@ -1,10 +1,9 @@ export default class NetworkService { - constructor($http, $q, API) { + constructor($http, $q) { 'ngInject'; this.$http = $http; this.$q = $q; - this.API = API; } logout(logoutRequest) { @@ -16,7 +15,7 @@ export default class NetworkService { */ apiPOST(endpoint, postObject) { - return this.$http.post(this.API + endpoint, postObject) + return this.$http.post(`/rest${endpoint}`, postObject) .then((response) => { if (angular.isObject(response.data)) { return response.data; diff --git a/src/app/services/services.module.js b/src/app/services/services.module.js index 5bfa0dafc7..fdc72b4cc7 100644 --- a/src/app/services/services.module.js +++ b/src/app/services/services.module.js @@ -1,7 +1,5 @@ export default angular .module('chpl.services', [ 'cfp.loadingBar', - 'chpl.constants', 'ngFileSaver', - ]) - .constant('API_KEY', '12909a978483dfb8ecd0596c98ae9094'); + ]); diff --git a/src/app/shared/certificationResultConformanceMethod.js b/src/app/shared/certificationResultConformanceMethod.js deleted file mode 100755 index 1022eda527..0000000000 --- a/src/app/shared/certificationResultConformanceMethod.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultConformanceMethod', function () { - var CertificationResultConformanceMethod = function (conformanceMethod, version) { - return { - 'conformanceMethod': conformanceMethod, - 'conformanceMethodVersion': version, - }; - }; - - // Return a reference to the function - return CertificationResultConformanceMethod; - }); -})(); diff --git a/src/app/shared/certificationResultOptionalStandard.js b/src/app/shared/certificationResultOptionalStandard.js deleted file mode 100755 index 357e90caa0..0000000000 --- a/src/app/shared/certificationResultOptionalStandard.js +++ /dev/null @@ -1,11 +0,0 @@ -(() => { - angular.module('chpl.shared') - .factory('CertificationResultOptionalStandard', () => { - const CertificationResultOptionalStandard = (optionalStandard) => ({ - optionalStandard, - }); - - // Return a reference to the function - return CertificationResultOptionalStandard; - }); -})(); diff --git a/src/app/shared/certificationResultSvap.js b/src/app/shared/certificationResultSvap.js deleted file mode 100644 index ed687fb362..0000000000 --- a/src/app/shared/certificationResultSvap.js +++ /dev/null @@ -1,18 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultSvap', function () { - var CertificationResultSvap = function (svap) { - return { - 'svapId': svap.svapId, - 'regulatoryTextCitation': svap.regulatoryTextCitation, - 'approvedStandardVersion': svap.approvedStandardVersion, - 'replaced': svap.replaced, - }; - }; - - // Return a reference to the function - return CertificationResultSvap; - }); -})(); diff --git a/src/app/shared/certificationResultTestData.js b/src/app/shared/certificationResultTestData.js deleted file mode 100644 index ec0ed8f153..0000000000 --- a/src/app/shared/certificationResultTestData.js +++ /dev/null @@ -1,17 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultTestData', function () { - var CertificationResultTestData = function (testData, version, alteration) { - return { - 'testData': testData, - 'version': version, - 'alteration': alteration, - }; - }; - - // Return a reference to the function - return CertificationResultTestData; - }); -})(); diff --git a/src/app/shared/certificationResultTestProcedure.js b/src/app/shared/certificationResultTestProcedure.js deleted file mode 100644 index 7b41d66221..0000000000 --- a/src/app/shared/certificationResultTestProcedure.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultTestProcedure', function () { - var CertificationResultTestProcedure = function (testProcedure, version) { - return { - 'testProcedure': testProcedure, - 'testProcedureVersion': version, - }; - }; - - // Return a reference to the function - return CertificationResultTestProcedure; - }); -})(); diff --git a/src/app/shared/certificationResultTestStandard.js b/src/app/shared/certificationResultTestStandard.js deleted file mode 100644 index 0c3555418b..0000000000 --- a/src/app/shared/certificationResultTestStandard.js +++ /dev/null @@ -1,17 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultTestStandard', function () { - var CertificationResultTestStandard = function (testStandard) { - return { - 'description': testStandard.description, - 'testStandardName': testStandard.name, - 'testStandardId': testStandard.id, - }; - }; - - // Return a reference to the function - return CertificationResultTestStandard; - }); -})(); diff --git a/src/app/shared/certificationResultTestTool.js b/src/app/shared/certificationResultTestTool.js deleted file mode 100644 index e759f6b205..0000000000 --- a/src/app/shared/certificationResultTestTool.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultTestTool', function () { - var CertificationResultTestTool = function (testTool, version) { - return { - testTool, - version: version, - }; - }; - - // Return a reference to the function - return CertificationResultTestTool; - }); -})(); diff --git a/src/app/shared/certificationResultsFunctionalitiesTested.js b/src/app/shared/certificationResultsFunctionalitiesTested.js deleted file mode 100644 index 640fae43e8..0000000000 --- a/src/app/shared/certificationResultsFunctionalitiesTested.js +++ /dev/null @@ -1,19 +0,0 @@ -(function () { - 'use strict'; - - angular.module('chpl.shared') - .factory('CertificationResultFunctionalitiesTested', function () { - var CertificationResultFunctionalitiesTested = function (functionalitiesTested) { - return { - functionalityTested: { - 'value': functionalitiesTested.value, - 'regulatoryTextCitation': functionalitiesTested.regulatoryTextCitation, - 'id': functionalitiesTested.id, - }, - }; - }; - - // Return a reference to the function - return CertificationResultFunctionalitiesTested; - }); -})(); diff --git a/src/app/shared/contexts/analytics-context.jsx b/src/app/shared/contexts/analytics-context.jsx index 53ec3ec18c..c6258c1ff2 100755 --- a/src/app/shared/contexts/analytics-context.jsx +++ b/src/app/shared/contexts/analytics-context.jsx @@ -1,6 +1,5 @@ import React, { createContext, useContext } from 'react'; - -import UserContext from './user-context'; +import { useSelector } from 'react-redux'; const AnalyticsContext = createContext({ analytics: {}, @@ -8,7 +7,7 @@ const AnalyticsContext = createContext({ AnalyticsContext.displayName = 'analytics-context'; function AnalyticsProvider(props) { - const { user } = useContext(UserContext); + const user = useSelector((state) => state.userInfo.user); const data = { analytics: { diff --git a/src/app/shared/contexts/user-context.js b/src/app/shared/contexts/user-context.js index 696448d43f..6276f8ee44 100755 --- a/src/app/shared/contexts/user-context.js +++ b/src/app/shared/contexts/user-context.js @@ -3,10 +3,6 @@ import { createContext } from 'react'; const UserContext = createContext({ hasAnyRole: () => false, hasAuthorityOn: () => false, - loginWidgetState: 'SIGNIN', - setLoginWidgetState: () => {}, - setUser: () => {}, - user: {}, }); UserContext.displayName = 'user-information'; diff --git a/src/app/store.js b/src/app/store.js new file mode 100755 index 0000000000..769a29c375 --- /dev/null +++ b/src/app/store.js @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import browserInfoReducer from 'components/browser/browserInfo.slice'; +import userInfoReducer from 'components/login/userInfo.slice'; + +const store = configureStore({ + reducer: { + browserInfo: browserInfoReducer, + userInfo: userInfoReducer, + }, +}); + +export default store; diff --git a/yarn.lock b/yarn.lock index 1ed378cfbc..79736d5840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2594,6 +2594,42 @@ __metadata: languageName: node linkType: hard +"@reduxjs/toolkit@npm:^2.12.0": + version: 2.12.0 + resolution: "@reduxjs/toolkit@npm:2.12.0" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^11.0.0" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 10/1ea2b2f5df9558d8dab0fc8fdf03638228dd852ca62d948f7f670efd10eb2a6f1c5f7a495e21600fec0823486aad28bae17e0a1965b8ab949587c1172aba5e56 + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 + languageName: node + linkType: hard + +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10/7084f875d322792f2e0a5904009434c8374b9345b09ba89828b68fd56fa3c2b366d35bf340d9e8c72736ef01793c2f70d350c372ed79845dc3566c58d34b4b51 + languageName: node + linkType: hard + "@tanstack/match-sorter-utils@npm:^8.7.0": version: 8.19.4 resolution: "@tanstack/match-sorter-utils@npm:8.19.4" @@ -2934,6 +2970,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/use-sync-external-store@npm:0.0.6" + checksum: 10/a95ce330668501ad9b1c5b7f2b14872ad201e552a0e567787b8f1588b22c7040c7c3d80f142cbb9f92d13c4ea41c46af57a20f2af4edf27f224d352abcfe4049 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 20.2.1 resolution: "@types/yargs-parser@npm:20.2.1" @@ -5152,6 +5195,7 @@ __metadata: "@material-ui/core": "npm:^4.12.4" "@material-ui/icons": "npm:^4.11.3" "@material-ui/lab": "npm:^4.0.0-alpha.61" + "@reduxjs/toolkit": "npm:^2.12.0" "@tanstack/react-query": "npm:4.42.0" "@tanstack/react-query-devtools": "npm:4.42.0" "@uirouter/react": "npm:^1.0.8" @@ -5222,6 +5266,7 @@ __metadata: react-dom: "npm:^18.3.1" react-error-boundary: "npm:^6.0.0" react-moment: "npm:^1.1.3" + react-redux: "npm:^9.3.0" react-test-renderer: "npm:^17.0.2" sass: "npm:^1.62.1" sass-loader: "npm:^7.3.1" @@ -9225,6 +9270,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^11.0.0": + version: 11.1.15 + resolution: "immer@npm:11.1.15" + checksum: 10/c108961db7ad3a1d23d5fa57fa28c6f5fd424f497cf454c9e6898700f7fe31eb951777e0eb91a4615dfc5e8cb50268d9e309651c482c7be48c1baf98acf53f2e + languageName: node + linkType: hard + "immutable@npm:^3.7.4, immutable@npm:^3.7.6, immutable@npm:^3.x.x": version: 3.8.2 resolution: "immutable@npm:3.8.2" @@ -14360,6 +14412,25 @@ __metadata: languageName: node linkType: hard +"react-redux@npm:^9.3.0": + version: 9.3.0 + resolution: "react-redux@npm:9.3.0" + dependencies: + "@types/use-sync-external-store": "npm:^0.0.6" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + "@types/react": ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + redux: + optional: true + checksum: 10/3384e98e382ddb2f2a8f35bc7145fabd60f2fb0ccd999df5587bd926de9590f52cd3a29c4a01fe191d710a9c8f9d7293efff8d5fc32debc84de25c911b70fc45 + languageName: node + linkType: hard + "react-shallow-renderer@npm:^16.13.1": version: 16.14.1 resolution: "react-shallow-renderer@npm:16.14.1" @@ -14576,6 +14647,15 @@ __metadata: languageName: node linkType: hard +"redux-thunk@npm:^3.1.0": + version: 3.1.0 + resolution: "redux-thunk@npm:3.1.0" + peerDependencies: + redux: ^5.0.0 + checksum: 10/38c563db5f0bbec90d2e65cc27f3c870c1b6102e0c071258734fac41cb0e51d31d894125815c2f4133b20aff231f51f028ad99bccc05a7e3249f1a5d5a959ed3 + languageName: node + linkType: hard + "redux@npm:^4.0.0, redux@npm:^4.1.2": version: 4.2.0 resolution: "redux@npm:4.2.0" @@ -14585,6 +14665,13 @@ __metadata: languageName: node linkType: hard +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10/a373f9ed65693ead58bea5ef61c1d6bef39da9f2706db3be6f84815f3a1283230ecd1184efb1b3daa7f807d8211b0181564ca8f336fc6ee0b1e2fa0ba06737c2 + languageName: node + linkType: hard + "refractor@npm:^3.6.0": version: 3.6.0 resolution: "refractor@npm:3.6.0" @@ -14912,6 +14999,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.1.0": + version: 5.2.0 + resolution: "reselect@npm:5.2.0" + checksum: 10/e53d37a35f84132682b0a819d942ff7debea90fe126f4bb5eef0f21b1f48f45dec89d27990f2ef5865f5e05d64bb88fb41e4341eb276674aee6f1f7f7362c3e8 + languageName: node + linkType: hard + "resolve-cwd@npm:^2.0.0": version: 2.0.0 resolution: "resolve-cwd@npm:2.0.0" @@ -17099,7 +17193,7 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": +"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.4.0": version: 1.6.0 resolution: "use-sync-external-store@npm:1.6.0" peerDependencies: