diff --git a/package.json b/package.json
index daa6b1282..2f23265df 100644
--- a/package.json
+++ b/package.json
@@ -37,13 +37,16 @@
"@mui/lab": "^5.0.0-alpha.69",
"@mui/material": "^5.4.2",
"@mui/utils": "^5.4.2",
+ "@reduxjs/toolkit": "^1.8.0",
"@testing-library/jest-dom": "^5.16.2",
"apexcharts": "^3.33.1",
+ "axios": "^0.26.0",
"change-case": "^4.1.2",
"date-fns": "^2.28.0",
"formik": "^2.2.9",
"framer-motion": "^6.2.6",
"history": "^5.2.0",
+ "jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"numeral": "^2.0.6",
"prop-types": "^15.8.1",
@@ -51,8 +54,11 @@
"react-apexcharts": "^1.3.9",
"react-dom": "^17.0.2",
"react-helmet-async": "^1.2.3",
- "react-router-dom": "^6.2.1",
+ "react-redux": "^7.2.6",
+ "react-router-dom": "^6.2.2",
"react-scripts": "^5.0.0",
+ "redux-persist": "^6.0.0",
+ "reselect": "^4.1.5",
"simplebar": "^5.3.6",
"simplebar-react": "^2.3.6",
"web-vitals": "^2.1.4",
diff --git a/src/App.js b/src/App.js
index 36b6cefcc..76721fee9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,21 +1,53 @@
-// routes
-import Router from './routes'
-// theme
-import ThemeConfig from './theme'
-import GlobalStyles from './theme/globalStyles'
-// components
-import ScrollToTop from './components/ScrollToTop'
-import { BaseOptionChartStyle } from './components/charts/BaseOptionChart'
+import { Component } from 'react'
+import { connect } from 'react-redux'
+import PropTypes from 'prop-types'
+import { BrowserRouter, Switch, Route } from 'react-router-dom'
+import { initSecurity, selectAuthModel } from '@src/store'
+import { Loader } from '@src/components'
+import { MainPage } from './pages'
+import './App.scss'
-// ----------------------------------------------------------------------
+class App extends Component {
+ static propTypes = {
+ auth: PropTypes.object,
+ initSecurity: PropTypes.func,
+ authModel: PropTypes.shape({
+ loaded: PropTypes.bool,
+ loading: PropTypes.bool,
+ error: PropTypes.string,
+ payload: PropTypes.object,
+ }),
+ }
-export default function App () {
- return (
-
-
-
-
-
-
- )
+ componentDidMount () {
+ const params = new URLSearchParams(window.location.search)
+ const authCode = params.get('code')
+ this.props.initSecurity(authCode)
+ }
+
+ render () {
+ const { authModel } = this.props
+
+ return (
+
+
+
+ {
+ authModel.loading || !authModel.loaded
+ ?
+
+
+ :
+ }
+
+
+
+ )
+ }
}
+
+const mapStateToProps = state => ({ authModel: selectAuthModel(state) })
+
+const mapDispatchToProps = { initSecurity }
+
+export default connect(mapStateToProps, mapDispatchToProps)(App)
\ No newline at end of file
diff --git a/src/App.scss b/src/App.scss
new file mode 100644
index 000000000..a04e1d6cf
--- /dev/null
+++ b/src/App.scss
@@ -0,0 +1,6 @@
+.app-loading {
+ display: flex row center center;
+
+ width: 100vw;
+ height: 100vh;
+}
\ No newline at end of file
diff --git a/src/components/Loader/Loader.js b/src/components/Loader/Loader.js
new file mode 100644
index 000000000..feb1271ba
--- /dev/null
+++ b/src/components/Loader/Loader.js
@@ -0,0 +1,27 @@
+import { Component } from 'react'
+import PropTypes from 'prop-types'
+import cn from 'classnames'
+import './Loader.scss'
+
+export default class Loader extends Component {
+ static propTypes = {
+ className: PropTypes.string,
+ small: PropTypes.bool,
+ }
+
+ render () {
+ const { className } = this.props
+ /**
+ * Loader from @link {https://codemyui.com/material-design-google-loader-in-css/}
+ */
+ return (
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/components/Loader/Loader.scss b/src/components/Loader/Loader.scss
new file mode 100644
index 000000000..67a997944
--- /dev/null
+++ b/src/components/Loader/Loader.scss
@@ -0,0 +1,97 @@
+$green: #008744;
+$blue: #0057E7;
+$red: #D62D20;
+$yellow: #FFA700;
+$white: #EEE;
+
+// scaling... any units
+$width: 100px;
+
+.loader {
+ position: relative;
+ width: $width;
+ margin: 0 auto;
+
+ &::before {
+ display: block;
+ padding-top: 100%;
+ content: "";
+ }
+}
+
+.circular {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ margin: auto;
+ animation: rotate 2s linear infinite;
+ transform-origin: center center;
+}
+
+.path {
+ animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
+ stroke-dasharray: 1, 200;
+ stroke-dashoffset: 0;
+ stroke-linecap: round;
+}
+
+@keyframes rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes dash {
+ 0% {
+ stroke-dasharray: 1, 200;
+ stroke-dashoffset: 0;
+ }
+
+ 50% {
+ stroke-dasharray: 89, 200;
+ stroke-dashoffset: -35px;
+ }
+
+ 100% {
+ stroke-dasharray: 89, 200;
+ stroke-dashoffset: -124px;
+ }
+}
+
+@keyframes color {
+ 100%,
+ 0% {
+ stroke: $red;
+ }
+
+ 40% {
+ stroke: $blue;
+ }
+
+ 66% {
+ stroke: $green;
+ }
+
+ 80%,
+ 90% {
+ stroke: $yellow;
+ }
+}
+
+// demo-specific
+body {
+ background-color: $white;
+}
+
+.showbox {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 5%;
+}
\ No newline at end of file
diff --git a/src/components/index.js b/src/components/index.js
new file mode 100644
index 000000000..af45ec3ec
--- /dev/null
+++ b/src/components/index.js
@@ -0,0 +1 @@
+export { default as Loader } from './Loader/Loader'
\ No newline at end of file
diff --git a/src/config/ConfigLoader.js b/src/config/ConfigLoader.js
new file mode 100644
index 000000000..ef7b2c40c
--- /dev/null
+++ b/src/config/ConfigLoader.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import { loadConfig } from './index'
+import { remotesManager } from '@src/remotes'
+
+export default class ConfigLoader extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = { isLoaded: false }
+ }
+
+ componentDidMount = async () => {
+ const config = await loadConfig()
+
+ remotesManager.initialize(config)
+ this.setState({ isLoaded: true })
+ }
+
+ render () {
+ if(!this.state.isLoaded)
+ return this.props.loading ? this.props.loading() : null
+
+ return this.props.ready(this.state.config)
+ }
+}
\ No newline at end of file
diff --git a/src/config/Splash/Splash.js b/src/config/Splash/Splash.js
new file mode 100644
index 000000000..383a31952
--- /dev/null
+++ b/src/config/Splash/Splash.js
@@ -0,0 +1,11 @@
+import React from 'react'
+import { Loader } from '@src/components'
+import './Splash.scss'
+
+export default class Splash extends React.Component {
+ render () {
+ return
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/config/Splash/Splash.scss b/src/config/Splash/Splash.scss
new file mode 100644
index 000000000..87ff5cf23
--- /dev/null
+++ b/src/config/Splash/Splash.scss
@@ -0,0 +1,7 @@
+
+.root-splash {
+ display: flex row center center;
+
+ width: 100vw;
+ height: 100vh;
+}
\ No newline at end of file
diff --git a/src/config/index.js b/src/config/index.js
new file mode 100644
index 000000000..d96879b81
--- /dev/null
+++ b/src/config/index.js
@@ -0,0 +1,19 @@
+const config = {}
+
+const loadConfig = () => {
+ return fetch('/config.json')
+ .then(res => res.json())
+ .then((newconfig) => {
+ for(let prop in config) {
+ delete config[prop]
+ }
+ for(let prop in newconfig) {
+ config[prop] = newconfig[prop]
+ }
+
+ return config
+ })
+}
+
+export { loadConfig }
+export default config
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index b13089ead..dee2101ac 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,21 +1,33 @@
-// scroll bar
-import 'simplebar/src/simplebar.css'
-
+import React from 'react'
+import { Provider } from 'react-redux'
+import { PersistGate } from 'redux-persist/integration/react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { HelmetProvider } from 'react-helmet-async'
-//
+import { store, persistor } from '@src/store'
+import ConfigLoader from '@src/config/ConfigLoader'
+import Splash from '@src/config/Splash/Splash'
+import './index.css'
import App from './App'
+
import * as serviceWorker from './serviceWorker'
import reportWebVitals from './reportWebVitals'
-
-// ----------------------------------------------------------------------
+import 'simplebar/src/simplebar.css'
ReactDOM.render(
-
+
+
+
+ }
+ loading={() => }
+ />
+
+
+ ,
,
document.getElementById('root')
@@ -27,4 +39,4 @@ serviceWorker.unregister()
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals()
+reportWebVitals()
\ No newline at end of file
diff --git a/src/layouts/dashboard/DashboardSidebar.js b/src/layouts/dashboard/DashboardSidebar.js
index 4f11240da..12ca05931 100644
--- a/src/layouts/dashboard/DashboardSidebar.js
+++ b/src/layouts/dashboard/DashboardSidebar.js
@@ -3,7 +3,7 @@ import { useEffect } from 'react'
import { Link as RouterLink, useLocation } from 'react-router-dom'
// material
import { styled } from '@mui/material/styles'
-import { Box, Link, Button, Drawer, Typography, Avatar, Stack } from '@mui/material'
+import { Box, Link, Drawer, Typography, Avatar, Stack } from '@mui/material'
// mocks_
import account from '../../_mocks_/account'
// hooks
@@ -90,28 +90,6 @@ export default function DashboardSidebar ({ isOpenSidebar, onCloseSidebar }) {
spacing={3}
sx={{ pt: 5, borderRadius: 2, position: 'relative' }}
>
-
-
-
-
- Get more?
-
-
- From only $69
-
-
-
-
diff --git a/src/pages/MainPage/MainPage.js b/src/pages/MainPage/MainPage.js
new file mode 100644
index 000000000..da57f9436
--- /dev/null
+++ b/src/pages/MainPage/MainPage.js
@@ -0,0 +1,19 @@
+import { Component } from 'react'
+import { Router } from '@src/routes'
+import { ThemeConfig } from '@src/theme'
+import { GlobalStyles } from '@src/theme/globalStyles'
+import { ScrollToTop } from '@src/components/ScrollToTop'
+import { BaseOptionChartStyle } from '@src/components/charts/BaseOptionChart'
+
+export default class MainPage extends Component {
+ render () {
+ return (
+
+
+
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/pages/MainPage/MainPage.scss b/src/pages/MainPage/MainPage.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/remotes/index.js b/src/remotes/index.js
new file mode 100644
index 000000000..8f9a1a108
--- /dev/null
+++ b/src/remotes/index.js
@@ -0,0 +1,55 @@
+import axios from 'axios'
+
+const withAuthorization = (authorization, config = {}) => ({
+ ...config,
+ headers: {
+ ...config.headers,
+ Authorization: `Bearer ${authorization}`,
+ },
+})
+
+class RemotesManager {
+ constructor (config) {
+ this.BACKEND_AUTH = null
+ this.BACKEND_USER = null
+ this.BACKEND_DATASETS = null
+ this.BACKEND_ACTIONS = null
+ this.BACKEND_API = null
+ }
+
+ initialize (config) {
+ console.log('app config', config)
+
+ this.BACKEND_AUTH = axios.create({
+ baseURL: config.api.auth.uri,
+ timeout: 60000,
+ })
+
+ this.BACKEND_USER = axios.create({
+ baseURL: config.api.user.uri,
+ timeout: 60000,
+ })
+
+ this.BACKEND_DATASETS = axios.create({
+ baseURL: config.api.datasets.uri,
+ timeout: 60000,
+ })
+
+ this.BACKEND_ACTIONS = axios.create({
+ baseURL: config.api.actions.uri,
+ timeout: 60000,
+ })
+
+ this.BACKEND_API = axios.create({
+ baseURL: config.api.api.uri,
+ timeout: 60000,
+ })
+ }
+}
+
+const remotesManager = new RemotesManager()
+
+export {
+ remotesManager,
+ withAuthorization,
+}
\ No newline at end of file
diff --git a/src/routes.js b/src/routes.js
index c403761d3..489970cce 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -1,8 +1,8 @@
import { Navigate, useRoutes } from 'react-router-dom'
-// layouts
+
import DashboardLayout from './layouts/dashboard'
import LogoOnlyLayout from './layouts/LogoOnlyLayout'
-//
+
import Login from './pages/Login'
import Register from './pages/Register'
import DashboardApp from './pages/DashboardApp'
@@ -11,8 +11,6 @@ import Blog from './pages/Blog'
import User from './pages/User'
import NotFound from './pages/Page404'
-// ----------------------------------------------------------------------
-
export default function Router () {
return useRoutes([
{
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 000000000..7c13c546e
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,26 @@
+import { configureStore } from '@reduxjs/toolkit'
+import storage from 'redux-persist/lib/storage'
+import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist'
+
+import securityReducer from './security'
+import { refreshJwtMiddleware, notificationsMiddleware } from './middlewares'
+
+const store = configureStore({
+ reducer: {
+ security: persistReducer({ key: 'security', whitelist: ['auth'], storage }, securityReducer),
+ },
+ middleware: getDefaultMiddleware => getDefaultMiddleware(
+ {
+ serializableCheck: false,
+ ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+ },
+ )
+ .concat(refreshJwtMiddleware)
+ .concat(notificationsMiddleware)
+ ,
+})
+
+const persistor = persistStore(store)
+
+export { store, persistor }
+export * from './security'
diff --git a/src/store/middlewares.js b/src/store/middlewares.js
new file mode 100644
index 000000000..4af58d87d
--- /dev/null
+++ b/src/store/middlewares.js
@@ -0,0 +1,12 @@
+
+export const refreshJwtMiddleware = ({ dispatch, getState }) => next => (action) => {
+ return next(action)
+}
+
+export const notificationsMiddleware = ({ dispatch, getState }) => next => (action) => {
+ if (action.type.endsWith('/rejected')) {
+ console.error(action)
+ }
+
+ return next(action)
+}
\ No newline at end of file
diff --git a/src/store/security/index.js b/src/store/security/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/store/security/lib/models.js b/src/store/security/lib/models.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/store/security/lib/reducers.js b/src/store/security/lib/reducers.js
new file mode 100644
index 000000000..71c7fd6c6
--- /dev/null
+++ b/src/store/security/lib/reducers.js
@@ -0,0 +1,19 @@
+import { getAuthUri } from '@src/utils'
+
+const reducers = {
+ clearSecurity: (state) => {
+ state.user = null
+ state.roles = null
+ state.auth = null
+ },
+
+ logout: (state) => {
+ state.user = null
+ state.roles = null
+ state.auth = null
+
+ window.location.replace(getAuthUri({ redirectSuffix: window.location.pathname }))
+ },
+}
+
+export default reducers
\ No newline at end of file
diff --git a/src/store/security/lib/selectors.js b/src/store/security/lib/selectors.js
new file mode 100644
index 000000000..0ea3f40c6
--- /dev/null
+++ b/src/store/security/lib/selectors.js
@@ -0,0 +1,17 @@
+import { createSelector } from 'reselect'
+
+export const selectSecurity = state => state.security
+export const selectAuth = state => state.security.auth
+export const selectUser = state => state.security.user
+export const selectAccessToken = state => state.security.auth.access_token
+export const selectRefreshToken = state => state?.security?.auth?.refresh_token
+
+export const selectAuthModel = createSelector(
+ selectSecurity,
+ security => ({
+ loading: security.authLoading,
+ loaded: security.authLoaded,
+ error: security.authError,
+ payload: security.auth,
+ }),
+)
\ No newline at end of file
diff --git a/src/store/security/lib/thunks.js b/src/store/security/lib/thunks.js
new file mode 100644
index 000000000..2e94f58f5
--- /dev/null
+++ b/src/store/security/lib/thunks.js
@@ -0,0 +1,120 @@
+import jwt_decode from 'jwt-decode'
+import { createAsyncThunk } from '@reduxjs/toolkit'
+import { remotesManager } from '@src/remotes'
+import { getAuthUri, getAuthParams, getRefreshParams, getLogoutParams } from '@src/utils'
+import config from '@src/config'
+import { clearSecurity, logout } from '../index'
+import { selectRefreshToken } from './selectors'
+
+export const initSecurity = createAsyncThunk('security/init',
+ async (authCode, { dispatch, getState }) => {
+ const state = getState()
+ const refresh_token = selectRefreshToken(state)
+ const decoded = refresh_token && jwt_decode(refresh_token)
+
+ if (decoded && (decoded.exp * 1000 - Date.now()) > 5000) {
+ // refresh token is not expired
+ const res = await dispatch(refreshToken())
+ return res.payload
+ }
+
+ await dispatch(clearSecurity())
+ if (!authCode)
+ window.location.replace(getAuthUri({ redirectSuffix: window.location.pathname }))
+
+ const { data } = await remotesManager.BACKEND_AUTH.post('/realms/master/protocol/openid-connect/token', new URLSearchParams({
+ ...getAuthParams({ redirectSuffix: window.location.pathname }),
+ code: authCode,
+ }).toString(), {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ })
+
+ return data
+ },
+)
+
+export const refreshToken = createAsyncThunk('security/refreshToken',
+ async (arg, { dispatch, getState }) => {
+ const state = getState()
+ const refresh_token = selectRefreshToken(state)
+
+ try {
+ const { data } = await remotesManager.BACKEND_AUTH.post('/realms/master/protocol/openid-connect/token', new URLSearchParams({
+ ...getRefreshParams({ redirectSuffix: window.location.pathname }),
+ refresh_token: refresh_token,
+ }).toString(), {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ })
+ return data
+ } catch (e) {
+ console.error('can not refresh token', e)
+ dispatch(logout())
+ }
+ },
+)
+
+export const loadUser = createAsyncThunk(
+ 'security/loadUser',
+ async (arg, { dispatch, getState }) => {
+ const { data } = await remotesManager.BACKEND_USER.post('/session/set', getState().security.auth)
+ return data
+ },
+)
+
+export const logoutFromApp = createAsyncThunk('security/logoutFromApp',
+ async (arg, { dispatch, getState }) => {
+ const refresh_token = selectRefreshToken(getState())
+ const redirectParams = new URLSearchParams({
+ ...getLogoutParams(),
+ refresh_token: refresh_token,
+ }).toString()
+ await clearSecurity()
+ window.location.replace(config.api.auth.uri + '/realms/master/protocol/openid-connect/logout?' + redirectParams)
+ },
+)
+
+const extraReducers = {
+ [initSecurity.pending]: (state, action) => {
+ state.authLoading = true
+ state.authLoaded = false
+ state.authError = null
+ },
+ [initSecurity.fulfilled]: (state, action) => {
+ state.authLoading = false
+ state.authLoaded = true
+ state.authError = null
+ state.auth = action.payload
+ },
+ [initSecurity.rejected]: (state, action) => {
+ state.authLoading = false
+ state.authLoaded = false
+ state.authError = action.error
+ window.location.replace(getAuthUri({ redirectSuffix: window.location.pathname }))
+ },
+
+ [loadUser.fulfilled]: (state, action) => {
+ state.user = action.payload.user
+ state.roles = action.payload.roles
+ state.currentRole = action.payload.roles.find(r => r.is_main)
+ },
+ [loadUser.rejected]: (state, action) => {
+ console.error('loadUser.rejected', action)
+ },
+
+ [refreshToken.fulfilled]: (state, action) => {
+ state.auth = action.payload
+ },
+ [refreshToken.rejected]: (state, action) => {
+ console.error('refreshToken.rejected', action)
+ },
+
+ [logoutFromApp.fulfilled]: (state, action) => {
+
+ },
+}
+
+export default extraReducers
\ No newline at end of file
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 000000000..6e594cd8e
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,33 @@
+import config from '@src/config'
+
+export const getAuthParams = ({ redirectSuffix }) => {
+ return {
+ grant_type: 'authorization_code',
+ response_type: 'code',
+ client_id: config.api.auth.client_id,
+ redirect_uri: `${config.api.auth.redirect_uri}${redirectSuffix || ''}`,
+ }
+}
+
+export const getRefreshParams = ({ redirectSuffix }) => {
+ return {
+ grant_type: 'refresh_token',
+ client_id: config.api.auth.client_id,
+ redirect_uri: `${config.api.auth.redirect_uri}${redirectSuffix || ''}`,
+ }
+}
+
+export const getLogoutParams = () => {
+ return {
+ client_id: config.api.auth.client_id,
+ post_logout_redirect_uri: config.api.auth.redirect_uri,
+ }
+}
+
+export const getAuthUri = ({ redirectSuffix }) => {
+ return `${config.api.auth.am_uri}?${new URLSearchParams(getAuthParams({ redirectSuffix })).toString()}`
+}
+
+export const normalizeStr = str => str.toLowerCase().replace(/\s/g, '')
+
+export * from './reduxUtils'
\ No newline at end of file
diff --git a/src/utils/reduxUtils.js b/src/utils/reduxUtils.js
new file mode 100644
index 000000000..2e4285f46
--- /dev/null
+++ b/src/utils/reduxUtils.js
@@ -0,0 +1,74 @@
+import _ from 'lodash'
+
+export const modelCreator = key => ({
+ [key]: {
+ payload: null,
+ loading: false,
+ loaded: false,
+ error: null,
+ },
+})
+
+export const extraCreator = (thunk, key) => {
+ return {
+ [thunk.pending]: (state, action) => {
+ state[key].loading = true
+ state[key].loaded = false
+ state[key].error = null
+ },
+ [thunk.rejected]: (state, action) => {
+ state[key].loading = false
+ state[key].loaded = false
+ state[key].error = action.payload || action.error?.error || action.error?.message || `Error in ${thunk.rejected}`
+ },
+ [thunk.fulfilled]: (state, action) => {
+ state[key].loading = false
+ state[key].loaded = true
+ state[key].error = null
+ state[key].payload = action.payload
+ },
+ }
+}
+
+export const extraNestedCreator = (thunk, stateKey, pathBuilder) => {
+ return {
+ [thunk.pending]: (state, action) => {
+ const keys = [stateKey, ...pathBuilder(action.meta.arg)]
+ _.set(state, keys, {
+ loading: true,
+ loaded: false,
+ error: null,
+ })
+ },
+ [thunk.rejected]: (state, action) => {
+ const keys = [stateKey, ...pathBuilder(action.meta.arg)]
+ _.set(state, keys, {
+ loading: false,
+ loaded: false,
+ error: action?.payload || action.error?.error || action.error?.message || `Error in ${thunk.rejected}`,
+ })
+ },
+ [thunk.fulfilled]: (state, action) => {
+ const keys = [stateKey, ...pathBuilder(action.meta.arg)]
+ _.set(state, keys, {
+ payload: action.payload,
+ loading: false,
+ loaded: true,
+ error: null,
+ })
+ },
+ }
+}
+
+export const mapDataWithMeta = (data) => {
+ return data.rowMode === 'array'
+ ? data.data.map((e) => {
+ return e.reduce((acc, cur, index) => {
+ return {
+ ...acc,
+ [data.metaData[index].name]: cur,
+ }
+ }, {})
+ })
+ : data.data
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 03c20c3ca..16b5de0c7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1668,6 +1668,16 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
+"@reduxjs/toolkit@^1.8.0":
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.0.tgz#8ae875e481ed97e4a691aafa034f876bfd0413c4"
+ integrity sha512-cdfHWfcvLyhBUDicoFwG1u32JqvwKDxLxDd7zSmSoFw/RhYLOygIRtmaMjPRUUHmVmmAGAvquLLsKKU/677kSQ==
+ dependencies:
+ immer "^9.0.7"
+ redux "^4.1.2"
+ redux-thunk "^2.4.1"
+ reselect "^4.1.5"
+
"@rollup/plugin-babel@^5.2.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
@@ -1977,6 +1987,14 @@
dependencies:
"@types/node" "*"
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@@ -2078,6 +2096,16 @@
dependencies:
"@types/react" "*"
+"@types/react-redux@^7.1.20":
+ version "7.1.22"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.22.tgz#0eab76a37ef477cc4b53665aeaf29cb60631b72a"
+ integrity sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-transition-group@^4.4.4":
version "4.4.4"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
@@ -2698,6 +2726,13 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
+axios@^0.26.0:
+ version "0.26.0"
+ resolved "https://npm.js.bars.group/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928"
+ integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==
+ dependencies:
+ follow-redirects "^1.14.8"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -3290,7 +3325,7 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11:
+confusing-browser-globals@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
@@ -4075,30 +4110,6 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
-eslint-config-airbnb-base@^15.0.0:
- version "15.0.0"
- resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236"
- integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==
- dependencies:
- confusing-browser-globals "^1.0.10"
- object.assign "^4.1.2"
- object.entries "^1.1.5"
- semver "^6.3.0"
-
-eslint-config-airbnb@^19.0.4:
- version "19.0.4"
- resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3"
- integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==
- dependencies:
- eslint-config-airbnb-base "^15.0.0"
- object.assign "^4.1.2"
- object.entries "^1.1.5"
-
-eslint-config-prettier@^8.4.0:
- version "8.4.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.4.0.tgz#8e6d17c7436649e98c4c2189868562921ef563de"
- integrity sha512-CFotdUcMY18nGRo5KGsnNxpznzhkopOcOo0InID+sgQssPrzjvsyKZPvOgymTFeHrFuC3Tzdf2YndhXtULK9Iw==
-
eslint-config-react-app@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz#0fa96d5ec1dfb99c029b1554362ab3fa1c3757df"
@@ -4143,7 +4154,7 @@ eslint-plugin-flowtype@^8.0.3:
lodash "^4.17.21"
string-natural-compare "^3.0.1"
-eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.25.4:
+eslint-plugin-import@^2.25.3:
version "2.25.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1"
integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==
@@ -4187,19 +4198,12 @@ eslint-plugin-jsx-a11y@^6.5.1:
language-tags "^1.0.5"
minimatch "^3.0.4"
-eslint-plugin-prettier@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0"
- integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==
- dependencies:
- prettier-linter-helpers "^1.0.0"
-
eslint-plugin-react-hooks@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172"
integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==
-eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.28.0:
+eslint-plugin-react@^7.27.1:
version "7.28.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz#8f3ff450677571a659ce76efc6d80b6a525adbdf"
integrity sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==
@@ -4270,7 +4274,7 @@ eslint-webpack-plugin@^3.1.1:
normalize-path "^3.0.0"
schema-utils "^3.1.1"
-eslint@^8.3.0, eslint@^8.9.0:
+eslint@^8.3.0:
version "8.9.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.9.0.tgz#a2a8227a99599adc4342fd9b854cb8d8d6412fdb"
integrity sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==
@@ -4445,11 +4449,6 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-fast-diff@^1.1.2:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
- integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
-
fast-glob@^3.2.11, fast-glob@^3.2.9:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
@@ -4596,7 +4595,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
-follow-redirects@^1.0.0:
+follow-redirects@^1.0.0, follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
@@ -5994,6 +5993,11 @@ jsonpointer@^5.0.0:
array-includes "^3.1.3"
object.assign "^4.1.2"
+jwt-decode@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
+ integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
+
kind-of@^6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
@@ -7340,18 +7344,6 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
-prettier-linter-helpers@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
- integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
- dependencies:
- fast-diff "^1.1.2"
-
-prettier@^2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
- integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
-
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@@ -7569,23 +7561,35 @@ react-is@^17.0.1, react-is@^17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+react-redux@^7.2.6:
+ version "7.2.6"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"
+ integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
+ dependencies:
+ "@babel/runtime" "^7.15.4"
+ "@types/react-redux" "^7.1.20"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
-react-router-dom@^6.2.1:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.1.tgz#32ec81829152fbb8a7b045bf593a22eadf019bec"
- integrity sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==
+react-router-dom@^6.2.2:
+ version "6.2.2"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.2.tgz#f1a2c88365593c76b9612ae80154a13fcb72e442"
+ integrity sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ==
dependencies:
history "^5.2.0"
- react-router "6.2.1"
+ react-router "6.2.2"
-react-router@6.2.1:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.1.tgz#be2a97a6006ce1d9123c28934e604faef51448a3"
- integrity sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==
+react-router@6.2.2:
+ version "6.2.2"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.2.tgz#495e683a0c04461eeb3d705fe445d6cf42f0c249"
+ integrity sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ==
dependencies:
history "^5.2.0"
@@ -7706,6 +7710,23 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redux-persist@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
+ integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
+
+redux-thunk@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
+ integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
+
+redux@^4.0.0, redux@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
+ integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@@ -7803,6 +7824,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+reselect@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6"
+ integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==
+
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"