diff --git a/package.json b/package.json index 355e873d..29f75728 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "dependencies": { "@emotion/react": "^11.11.5", "@emotion/styled": "^11.11.5", - "@fullcalendar/daygrid": "^6.1.14", "@fullcalendar/core": "^6.1.14", + "@fullcalendar/daygrid": "^6.1.14", "@fullcalendar/interaction": "^6.1.14", "@fullcalendar/list": "^6.1.14", "@fullcalendar/react": "^6.1.14", @@ -28,13 +28,13 @@ "@mui/styles": "^5.15.1", "@mui/x-data-grid": "^5.17.26", "@mui/x-date-pickers": "^5.0.20", + "@reduxjs/toolkit": "^2.6.0", "apexcharts": "^3.54.1", "assert": "^2.1.0", "axios": "^0.27.2", "babel-eslint": "10.1.0", "buffer": "^6.0.3", "classnames": "^2.3.2", - "connected-react-router": "^6.9.3", "crypto-browserify": "^3.12.1", "css-loader": "^2.1.1", "date-fns": "^2.30.0", @@ -60,7 +60,7 @@ "react-dom": "^19.0.0", "react-google-maps": "^9.4.5", "react-redux": "^7.2.9", - "react-router-dom": "^5.3.4", + "react-router-dom": "^7.2.0", "react-scripts": "^5.0.1", "react-simple-maps": "^0.12.1", "react-sortablejs": "^1.5.1", diff --git a/src/actions/auth.js b/src/actions/auth.js index 8bf944dc..8c129373 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -2,7 +2,8 @@ import axios from 'axios'; import config from '../config'; import jwt from 'jsonwebtoken'; import { showSnackbar } from '../components/Snackbar'; -import { push } from 'connected-react-router'; +// Remove this import if you're not using connected-react-router +// import { push } from 'connected-react-router'; import Errors from 'components/FormItems/error/errors'; export const AUTH_FAILURE = 'AUTH_FAILURE'; @@ -20,6 +21,7 @@ export const REGISTER_REQUEST = 'REGISTER_REQUEST'; export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; async function findMe() { + // English: Request the current user information const response = await axios.get('/auth/me'); return response.data; } @@ -41,13 +43,10 @@ export function doInit() { } dispatch({ type: AUTH_INIT_SUCCESS, - payload: { - currentUser, - }, + payload: { currentUser }, }); } catch (error) { Errors.handle(error); - dispatch({ type: AUTH_INIT_ERROR, payload: error, @@ -58,38 +57,31 @@ export function doInit() { export function logoutUser() { return (dispatch) => { - dispatch({ - type: LOGOUT_REQUEST, - }); + dispatch({ type: LOGOUT_REQUEST }); localStorage.removeItem('token'); localStorage.removeItem('user'); axios.defaults.headers.common['Authorization'] = ''; - dispatch({ - type: LOGOUT_SUCCESS, - }); - dispatch(push('/login')); + dispatch({ type: LOGOUT_SUCCESS }); + // English: Replace push with navigation using useNavigate + // For example, in a component: const navigate = useNavigate(); navigate('/login'); + // If you are in an async action, you can pass a navigation callback. }; } export function receiveToken(token) { return (dispatch) => { let user = jwt.decode(token); - localStorage.setItem('token', token); localStorage.setItem('user', JSON.stringify(user)); axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; - dispatch({ - type: LOGIN_SUCCESS, - }); - dispatch(push('/app')); + dispatch({ type: LOGIN_SUCCESS }); + // Similarly, replace push('/app') with your navigation logic. }; } export function loginUser(creds) { return (dispatch) => { - dispatch({ - type: LOGIN_REQUEST, - }); + dispatch({ type: LOGIN_REQUEST }); if (creds.social) { window.location.href = config.baseURLApi + '/auth/signin/' + creds.social; } else if (creds.email.length > 0 && creds.password.length > 0) { @@ -99,7 +91,7 @@ export function loginUser(creds) { const token = res.data; dispatch(receiveToken(token)); dispatch(doInit()); - dispatch(push('/app')); + // Replace push('/app') with navigation logic }) .catch((err) => { dispatch(authError(err.response.data)); @@ -123,24 +115,20 @@ export function verifyEmail(token) { showSnackbar({ type: 'error', message: err.response.data }); }) .finally(() => { - dispatch(push('/login')); + // Replace push('/login') with navigation logic }); }; } export function resetPassword(token, password) { return (dispatch) => { - dispatch({ - type: RESET_REQUEST, - }); + dispatch({ type: RESET_REQUEST }); axios .put('/auth/password-reset', { token, password }) .then((res) => { - dispatch({ - type: RESET_SUCCESS, - }); + dispatch({ type: RESET_SUCCESS }); showSnackbar({ type: 'success', message: 'Password has been updated' }); - dispatch(push('/login')); + // Replace push('/login') with navigation logic }) .catch((err) => { dispatch(authError(err.response.data)); @@ -150,20 +138,13 @@ export function resetPassword(token, password) { export function sendPasswordResetEmail(email) { return (dispatch) => { - dispatch({ - type: PASSWORD_RESET_EMAIL_REQUEST, - }); + dispatch({ type: PASSWORD_RESET_EMAIL_REQUEST }); axios .post('/auth/send-password-reset-email', { email }) .then((res) => { - dispatch({ - type: PASSWORD_RESET_EMAIL_SUCCESS, - }); - showSnackbar({ - type: 'success', - message: 'Email with resetting instructions has been sent', - }); - dispatch(push('/login')); + dispatch({ type: PASSWORD_RESET_EMAIL_SUCCESS }); + showSnackbar({ type: 'success', message: 'Email with resetting instructions has been sent' }); + // Replace push('/login') with navigation logic }) .catch((err) => { dispatch(authError(err.response.data)); @@ -173,23 +154,17 @@ export function sendPasswordResetEmail(email) { export function registerUser(creds) { return (dispatch) => { - dispatch({ - type: REGISTER_REQUEST, - }); - + dispatch({ type: REGISTER_REQUEST }); if (creds.email.length > 0 && creds.password.length > 0) { axios .post('/auth/signup', creds) .then((res) => { - dispatch({ - type: REGISTER_SUCCESS, - }); + dispatch({ type: REGISTER_SUCCESS }); showSnackbar({ type: 'success', - message: - "You've been registered successfully. Please check your email for verification link", + message: "You've been registered successfully. Please check your email for verification link", }); - dispatch(push('/login')); + // Replace push('/login') with navigation logic }) .catch((err) => { dispatch(authError(err.response.data)); @@ -198,4 +173,4 @@ export function registerUser(creds) { dispatch(authError('Something was wrong. Try again')); } }; -} +} \ No newline at end of file diff --git a/src/actions/users/usersFormActions.js b/src/actions/users/usersFormActions.js index f62a7e24..3ea5a95b 100644 --- a/src/actions/users/usersFormActions.js +++ b/src/actions/users/usersFormActions.js @@ -1,91 +1,73 @@ import axios from 'axios'; import Errors from 'components/FormItems/error/errors'; -import { push } from 'connected-react-router'; import { doInit } from 'actions/auth'; import { showSnackbar } from '../../components/Snackbar'; +// Removed import of push from connected-react-router + const actions = { + // Resets the form doNew: () => { return { type: 'USERS_FORM_RESET', }; }, - doFind: (id) => async (dispatch) => { + // Finds a record by id; the caller should provide the navigate function + doFind: (id, navigate) => async (dispatch) => { try { - dispatch({ - type: 'USERS_FORM_FIND_STARTED', - }); - + dispatch({ type: 'USERS_FORM_FIND_STARTED' }); axios.get(`/users/${id}`).then((res) => { const record = res.data; - - dispatch({ - type: 'USERS_FORM_FIND_SUCCESS', - payload: record, - }); + dispatch({ type: 'USERS_FORM_FIND_SUCCESS', payload: record }); }); } catch (error) { Errors.handle(error); - - dispatch({ - type: 'USERS_FORM_FIND_ERROR', - }); - - dispatch(push('/admin/users')); + dispatch({ type: 'USERS_FORM_FIND_ERROR' }); + // Use navigate instead of dispatching push + if (navigate) { + navigate('/admin/users'); + } } }, - doCreate: (values) => async (dispatch) => { + // Creates a new record; navigate function is passed for redirection + doCreate: (values, navigate) => async (dispatch) => { try { - dispatch({ - type: 'USERS_FORM_CREATE_STARTED', - }); - + dispatch({ type: 'USERS_FORM_CREATE_STARTED' }); axios.post('/users', { data: values }).then((res) => { - dispatch({ - type: 'USERS_FORM_CREATE_SUCCESS', - }); + dispatch({ type: 'USERS_FORM_CREATE_SUCCESS' }); showSnackbar({ type: 'success', message: 'Users created' }); - dispatch(push('/app/users')); + if (navigate) { + navigate('/app/users'); + } }); } catch (error) { Errors.handle(error); - - dispatch({ - type: 'USERS_FORM_CREATE_ERROR', - }); + dispatch({ type: 'USERS_FORM_CREATE_ERROR' }); } }, - doUpdate: (id, values, isProfile) => async (dispatch, getState) => { + // Updates existing data; accepts isProfile flag and navigate function + doUpdate: (id, values, isProfile, navigate) => async (dispatch, getState) => { try { - dispatch({ - type: 'USERS_FORM_UPDATE_STARTED', - }); - + dispatch({ type: 'USERS_FORM_UPDATE_STARTED' }); await axios.put(`/users/${id}`, { id, data: values }); - dispatch(doInit()); - - dispatch({ - type: 'USERS_FORM_UPDATE_SUCCESS', - }); - + dispatch({ type: 'USERS_FORM_UPDATE_SUCCESS' }); if (isProfile) { showSnackbar({ type: 'success', message: 'Profile updated' }); } else { showSnackbar({ type: 'success', message: 'Users updated' }); - dispatch(push('/admin/users')); + if (navigate) { + navigate('/admin/users'); + } } } catch (error) { Errors.handle(error); - - dispatch({ - type: 'USERS_FORM_UPDATE_ERROR', - }); + dispatch({ type: 'USERS_FORM_UPDATE_ERROR' }); } }, }; -export default actions; +export default actions; \ No newline at end of file diff --git a/src/components/App.js b/src/components/App.js index c24c397e..4b039e43 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,92 +1,49 @@ import React from 'react'; -import { Router, Route, Switch, Redirect } from 'react-router-dom'; -import { ConnectedRouter } from 'connected-react-router'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; // Use BrowserRouter and Routes from react-router-dom v6 import { SnackbarProvider } from './Snackbar'; -// components +// Components import Layout from './Layout'; import Documentation from './Documentation/Documentation'; -// pages -import Error from '../pages/error'; +// Pages +import ErrorPage from '../pages/error'; import Login from '../pages/login'; import Verify from '../pages/verify'; import Reset from '../pages/reset'; -// context +// Context import { useUserState } from '../context/UserContext'; -import { getHistory } from '../index'; export default function App() { - // global - let { isAuthenticated } = useUserState(); + const { isAuthenticated } = useUserState(); const isAuth = isAuthenticated(); return ( - <> - - - - - } - /> - - } - /> - - - - - - - - - - - - - + + + + {/* Redirect root to /app/profile */} + } /> + + {/* Redirect /app to /app/dashboard */} + } /> + + {/* Documentation route */} + } /> + + {/* Public routes */} + : } /> + : } /> + : } /> + + {/* Protected route for /app/* - renders Layout with nested routes */} + : } /> + + {/* Fallback route */} + } /> + + + ); - - // ####################################################################### - - function PrivateRoute({ component, ...rest }) { - return ( - - isAuth ? ( - React.createElement(component, props) - ) : ( - - ) - } - /> - ); - } - - function PublicRoute({ component, ...rest }) { - return ( - - isAuth ? ( - - ) : ( - React.createElement(component, props) - ) - } - /> - ); - } -} +} \ No newline at end of file diff --git a/src/components/Documentation/Documentation.js b/src/components/Documentation/Documentation.js index 2eeb24cf..722f198b 100644 --- a/src/components/Documentation/Documentation.js +++ b/src/components/Documentation/Documentation.js @@ -1,42 +1,32 @@ import React from 'react'; -import { - Switch, - Route, - Redirect, - useRouteMatch, - withRouter, -} from 'react-router-dom'; - -//styles +import { Routes, Route, Navigate, useMatch } from 'react-router-dom'; +// styles import useStyles from './styles'; - -//pages +// pages import Start from './pages/start'; import TypographyPage from './pages/typography'; import HeaderPage from './pages/header'; import SidebarPage from './pages/sidebar'; import ButtonsPage from './pages/buttons'; - -//components +// components import Header from './components/Header'; import Sidebar from '../../components/Sidebar'; import structure from './components/Sidebar/SidebarStructure'; import Widget from '../Widget'; - import { Typography } from '../Wrappers'; import classnames from 'classnames'; - -//context +// context import { useLayoutState } from '../../context/LayoutContext'; import { Box, Breadcrumbs, Grid } from '@mui/material'; - import { NavigateNext as NavigateNextIcon } from '@mui/icons-material'; -const Documentation = (props) => { - // global - let layoutState = useLayoutState(); +const Documentation = () => { + const layoutState = useLayoutState(); const classes = useStyles(); - const { path } = useRouteMatch(); + + const match = useMatch("/documentation/*"); + const basePath = match ? match.pathnameBase : "/documentation"; + return (
@@ -50,66 +40,71 @@ const Documentation = (props) => { - - {/* eslint-disable-next-line array-callback-return */} {structure.map((c) => { - if (!c.children && window.location.hash.includes(c.link) && c.link) { + if (!c.children && c.link && window.location.hash.includes(c.link)) { return ( - - - {c.label} + + + {c.label} ); } else if (c.children) { - return c.children.forEach((currentInner) => { - // eslint-disable-array-callback-return + return c.children.map((currentInner) => { if (window.location.hash.includes(currentInner.link)) { return ( } - aria-label='breadcrumb' - key={c.id} + separator={} + aria-label="breadcrumb" + key={currentInner.id} > - {c.label} - + {c.label} + {currentInner.label} ); } + return null; }); } + return null; })} - - - - - - - - - - - - - - - - - - - - + + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> +
); }; -export default withRouter(Documentation); +export default Documentation; \ No newline at end of file diff --git a/src/components/Documentation/components/Header/Header.js b/src/components/Documentation/components/Header/Header.js index 5fc8c4f4..b5710da3 100644 --- a/src/components/Documentation/components/Header/Header.js +++ b/src/components/Documentation/components/Header/Header.js @@ -1,134 +1,115 @@ import React, { useEffect, useState } from 'react'; import useStyles from './styles'; -import { withRouter } from 'react-router-dom'; - -// Material-UI core components import { AppBar, Toolbar, IconButton, Box, Button } from '@mui/material'; import { useTheme } from '@mui/material'; - // Material Icons -import { - ArrowBack as ArrowBackIcon, - Menu as MenuIcon, -} from '@mui/icons-material'; - +import { ArrowBack as ArrowBackIcon, Menu as MenuIcon } from '@mui/icons-material'; import GitHubIcon from '@mui/icons-material/GitHub'; import FacebookIcon from '@mui/icons-material/Facebook'; import InstagramIcon from '@mui/icons-material/Instagram'; import LinkedInIcon from '@mui/icons-material/LinkedIn'; import TwitterIcon from '@mui/icons-material/Twitter'; - // Components import { Typography, Link } from '../../../Wrappers'; -import { - toggleSidebar, - useLayoutDispatch, - useLayoutState, -} from '../../../../context/LayoutContext'; +import { toggleSidebar, useLayoutDispatch, useLayoutState } from '../../../../context/LayoutContext'; import classNames from 'classnames'; +// Import the useNavigate hook from react-router-dom for programmatic navigation +import { useNavigate } from 'react-router-dom'; -const Header = (props) => { +const Header = () => { const theme = useTheme(); const classes = useStyles(); - let layoutState = useLayoutState(); - let layoutDispatch = useLayoutDispatch(); + const layoutState = useLayoutState(); + const layoutDispatch = useLayoutDispatch(); const [isSmall, setSmall] = useState(false); - useEffect(function () { + // Create navigate function for programmatic navigation + const navigate = useNavigate(); + + useEffect(() => { + // Add event listener to handle window resize window.addEventListener('resize', handleWindowWidthChange); + // Call the resize handler initially handleWindowWidthChange(); - return function cleanup() { - window.removeEventListener('resize', handleWindowWidthChange); - }; - }); + // Cleanup the event listener on component unmount + return () => window.removeEventListener('resize', handleWindowWidthChange); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [theme.breakpoints.values.md]); + // Function to update state based on window width function handleWindowWidthChange() { - let windowWidth = window.innerWidth; - let breakpointWidth = theme.breakpoints.values.md; - let isSmallScreen = windowWidth < breakpointWidth; + const windowWidth = window.innerWidth; + const breakpointWidth = theme.breakpoints.values.md; + const isSmallScreen = windowWidth < breakpointWidth; setSmall(isSmallScreen); } return ( - + + {/* Icon button to toggle the sidebar */} toggleSidebar(layoutDispatch)} - className={classNames( - classes.headerMenuButton, - classes.headerMenuButtonCollapse, - )} + className={classNames(classes.headerMenuButton, classes.headerMenuButtonCollapse)} > - {(!layoutState.isSidebarOpened && isSmall) || - (layoutState.isSidebarOpened && !isSmall) ? ( - + {(!layoutState.isSidebarOpened && isSmall) || (layoutState.isSidebarOpened && !isSmall) ? ( + ) : ( - + )} - + + {/* Logo text */} + React Material Admin Full{' '} -   Documentation +   Documentation - - - + + + {/* Social media icons */} + + - + - + - + - + + + {/* Navigation buttons */} + {/* The "Live Preview" button now uses useNavigate for navigation */} @@ -139,4 +120,4 @@ const Header = (props) => { ); }; -export default withRouter(Header); +export default Header; \ No newline at end of file diff --git a/src/components/Documentation/pages/buttons/Buttons.js b/src/components/Documentation/pages/buttons/Buttons.js index d1f64bc6..caf388e5 100644 --- a/src/components/Documentation/pages/buttons/Buttons.js +++ b/src/components/Documentation/pages/buttons/Buttons.js @@ -1,52 +1,41 @@ import React from 'react'; import { Box, Grid } from '@mui/material'; -import { withRouter } from 'react-router-dom'; - -//components +// Removed withRouter import since it's no longer needed in v7 +// Components import Widget from '../../../Widget'; import { Typography, Button } from '../../../Wrappers'; import Code from '../../../Code'; -const Pages = (props) => { +const Pages = () => { return ( <> Buttons - - Button's variants: - - - - - Code: - + Code: {` - - - - `} + + + + `} - - Button's colors: - - + Button's colors: + @@ -76,40 +65,16 @@ const Pages = (props) => { - - Code: - + Code: {` - - - - - - - `} + + + + + + + `} @@ -118,4 +83,4 @@ const Pages = (props) => { ); }; -export default withRouter(Pages); +export default Pages; \ No newline at end of file diff --git a/src/components/Documentation/pages/sidebar/Sidebar.js b/src/components/Documentation/pages/sidebar/Sidebar.js index 090ab25d..caa45eb7 100644 --- a/src/components/Documentation/pages/sidebar/Sidebar.js +++ b/src/components/Documentation/pages/sidebar/Sidebar.js @@ -1,32 +1,25 @@ import React from 'react'; import { Grid } from '@mui/material'; -import { withRouter } from 'react-router-dom'; - -//components +// Components import Widget from '../../../Widget'; import { Typography } from '../../../Wrappers'; import Code from '../../../Code'; -const Pages = (props) => { +const Pages = () => { return ( <> Sidebar - Sidebar contains structure props. That means - you can describe your own Sidebar structure in{' '} - SidebarStructure.js file. + Sidebar contains structure props. That means you can describe your own Sidebar + structure in SidebarStructure.js file. Code: @@ -39,4 +32,4 @@ const Pages = (props) => { ); }; -export default withRouter(Pages); +export default Pages; \ No newline at end of file diff --git a/src/components/FormItems/error/errors.js b/src/components/FormItems/error/errors.js index f379b3a4..00861209 100644 --- a/src/components/FormItems/error/errors.js +++ b/src/components/FormItems/error/errors.js @@ -1,5 +1,4 @@ -import { push } from 'connected-react-router'; -import { store } from '../../../index'; +import store from '../../../reducers/store'; import { showSnackbar } from '../../Snackbar'; const DEFAULT_ERROR_MESSAGE = 'Error'; @@ -7,14 +6,11 @@ const DEFAULT_ERROR_MESSAGE = 'Error'; function selectErrorMessage(error) { if (error && error.response && error.response.data) { const data = error.response.data; - if (data.error && data.error.message) { return data.error.message; } - return String(data); } - return error.message || DEFAULT_ERROR_MESSAGE; } @@ -22,7 +18,6 @@ function selectErrorCode(error) { if (error && error.response && error.response.status) { return error.response.status; } - return 500; } @@ -32,29 +27,24 @@ export default class Errors { console.error(selectErrorMessage(error)); console.error(error); } - if (selectErrorCode(error) === 403) { - store.dispatch(push('/403')); + // Instead of dispatching push from connected-react-router, use window.location.assign + window.location.assign('/403'); return; } - if (selectErrorCode(error) === 400) { showSnackbar({ type: 'error', message: selectErrorMessage(error) }); return; } - - store.dispatch(push('/500')); + window.location.assign('/500'); } - static errorCode(error) { return selectErrorCode(error); } - static selectMessage(error) { return selectErrorMessage(error); } - static showMessage(error) { showSnackbar({ type: 'error', message: selectErrorMessage(error) }); } -} +} \ No newline at end of file diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index b8806b23..8113f278 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; // Import useNavigate hook import { AppBar, Toolbar, IconButton, Menu, MenuItem } from '@mui/material'; import { useTheme } from '@mui/material'; import { @@ -8,46 +8,39 @@ import { ArrowBack as ArrowBackIcon, } from '@mui/icons-material'; import classNames from 'classnames'; - -//images +// images import profile from '../../images/main-profile.png'; import config from '../../config'; - // styles import useStyles from './styles'; - // components import { Typography, Avatar } from '../Wrappers/Wrappers'; - // context import { useLayoutState, useLayoutDispatch, toggleSidebar, } from '../../context/LayoutContext'; -import { - useManagementDispatch, - useManagementState, -} from '../../context/ManagementContext'; - +import { useManagementDispatch, useManagementState } from '../../context/ManagementContext'; import { actions } from '../../context/ManagementContext'; import { useUserDispatch, signOut } from '../../context/UserContext'; export default function Header(props) { - let classes = useStyles(); - let theme = useTheme(); - - // global - let layoutState = useLayoutState(); - let layoutDispatch = useLayoutDispatch(); - let userDispatch = useUserDispatch(); + const classes = useStyles(); + const theme = useTheme(); + // Global layout state from context + const layoutState = useLayoutState(); + const layoutDispatch = useLayoutDispatch(); + const userDispatch = useUserDispatch(); const managementDispatch = useManagementDispatch(); - // local + // Hook for navigation (replaces history) + const navigate = useNavigate(); + + // Local state for profile menu const [profileMenu, setProfileMenu] = useState(null); const [currentUser, setCurrentUser] = useState(); const [isSmall, setSmall] = useState(false); - const managementValue = useManagementState(); useEffect(() => { @@ -67,9 +60,10 @@ export default function Header(props) { return function cleanup() { window.removeEventListener('resize', handleWindowWidthChange); }; - }); + }, []); function handleWindowWidthChange() { + // English: detect if current window width is less than breakpoint and update state accordingly. let windowWidth = window.innerWidth; let breakpointWidth = theme.breakpoints.values.md; let isSmallScreen = windowWidth < breakpointWidth; @@ -84,7 +78,7 @@ export default function Header(props) { onClick={() => toggleSidebar(layoutDispatch)} className={classNames( classes.headerMenuButton, - classes.headerMenuButtonCollapse, + classes.headerMenuButtonCollapse )} > {(!layoutState.isSidebarOpened && isSmall) || @@ -93,7 +87,7 @@ export default function Header(props) { classes={{ root: classNames( classes.headerIcon, - classes.headerIconCollapse, + classes.headerIconCollapse ), }} /> @@ -102,7 +96,7 @@ export default function Header(props) { classes={{ root: classNames( classes.headerIcon, - classes.headerIconCollapse, + classes.headerIconCollapse ), }} /> @@ -124,7 +118,8 @@ export default function Header(props) { // eslint-disable-next-line no-mixed-operators src={ (currentUser?.avatar?.length >= 1 && - currentUser?.avatar[currentUser.avatar.length - 1].publicUrl) || profile + currentUser?.avatar[currentUser.avatar.length - 1].publicUrl) || + profile } classes={{ root: classes.headerIcon }} > @@ -133,7 +128,11 @@ export default function Header(props) {
Hi, 
@@ -165,7 +164,7 @@ export default function Header(props) { @@ -177,7 +176,8 @@ export default function Header(props) { signOut(userDispatch, props.history)} + // Use navigate function in signOut call instead of props.history + onClick={() => signOut(userDispatch, navigate)} > Sign Out @@ -186,4 +186,4 @@ export default function Header(props) {
); -} +} \ No newline at end of file diff --git a/src/components/Header/styles.js b/src/components/Header/styles.js index b607ff1a..d6fcebc6 100644 --- a/src/components/Header/styles.js +++ b/src/components/Header/styles.js @@ -67,7 +67,7 @@ export default makeStyles((theme) => ({ right: theme.spacing(1.25), }, inputRoot: { - color: 'inherit', + color: 'currentColor', width: '100%', }, inputInput: { diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index f573b201..585a68b9 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,106 +1,86 @@ -import React, { useEffect } from 'react'; -import { Route, Switch, withRouter } from 'react-router-dom'; +import React from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; import classnames from 'classnames'; - import SettingsIcon from '@mui/icons-material/Settings'; import GithubIcon from '@mui/icons-material/GitHub'; import FacebookIcon from '@mui/icons-material/Facebook'; import TwitterIcon from '@mui/icons-material/Twitter'; - import { Fab, IconButton } from '@mui/material'; import { connect } from 'react-redux'; + // styles import useStyles from './styles'; - // components import Header from '../Header'; import Sidebar from '../Sidebar'; import Footer from '../Footer'; import { Link } from '../Wrappers'; import ColorChangeThemePopper from './components/ColorChangeThemePopper'; - import EditUser from '../../pages/user/EditUser'; - // pages import Dashboard from '../../pages/dashboard'; -import Profile from '../../pages/profile' -import TypographyPage from '../../pages/typography' -import ColorsPage from '../../pages/colors' -import GridPage from '../../pages/grid' - -import StaticTablesPage from '../../pages/tables' -import DynamicTablesPage from '../../pages/tables/dynamic' - -import IconsPage from '../../pages/icons' -import BadgesPage from '../../pages/badge' -import CarouselsPage from '../../pages/carousel' -import CardsPage from '../../pages/cards' -import ModalsPage from '../../pages/modal' -import NotificationsPage from '../../pages/notifications' -import NavbarsPage from '../../pages/nav' -import TooltipsPage from '../../pages/tooltips' -import TabsPage from '../../pages/tabs' -import ProgressPage from '../../pages/progress' -import WidgetsPage from '../../pages/widget' - -import Ecommerce from '../../pages/ecommerce' -import Product from '../../pages/ecommerce/Products' -import ProductsGrid from '../../pages/ecommerce/ProductsGrid' -import CreateProduct from '../../pages/ecommerce/CreateProduct' - -import FormsElements from '../../pages/forms/elements' -import FormValidation from '../../pages/forms/validation' - -import Charts from '../../pages/charts' -import LineCharts from '../../pages/charts/LineCharts' -import BarCharts from '../../pages/charts/BarCharts' -import PieCharts from '../../pages/charts/PieCharts' - -import DraggableGrid from '../../pages/draggablegrid' - -import MapsGoogle from '../../pages/maps' -import VectorMaps from '../../pages/maps/VectorMap' - -import Timeline from '../../pages/timeline' -import Search from '../../pages/search' -import Gallery from '../../pages/gallery' -import Invoice from '../../pages/invoice' -import Calendar from '../../pages/calendar' - +import Profile from '../../pages/profile'; +import TypographyPage from '../../pages/typography'; +import ColorsPage from '../../pages/colors'; +import GridPage from '../../pages/grid'; +import StaticTablesPage from '../../pages/tables'; +import DynamicTablesPage from '../../pages/tables/dynamic'; +import IconsPage from '../../pages/icons'; +import BadgesPage from '../../pages/badge'; +import CarouselsPage from '../../pages/carousel'; +import CardsPage from '../../pages/cards'; +import ModalsPage from '../../pages/modal'; +import NotificationsPage from '../../pages/notifications'; +import NavbarsPage from '../../pages/nav'; +import TooltipsPage from '../../pages/tooltips'; +import TabsPage from '../../pages/tabs'; +import ProgressPage from '../../pages/progress'; +import WidgetsPage from '../../pages/widget'; +import Ecommerce from '../../pages/ecommerce'; +import Product from '../../pages/ecommerce/Products'; +import ProductsGrid from '../../pages/ecommerce/ProductsGrid'; +import CreateProduct from '../../pages/ecommerce/CreateProduct'; +import FormsElements from '../../pages/forms/elements'; +import FormValidation from '../../pages/forms/validation'; +import Charts from '../../pages/charts'; +import LineCharts from '../../pages/charts/LineCharts'; +import BarCharts from '../../pages/charts/BarCharts'; +import PieCharts from '../../pages/charts/PieCharts'; +import DraggableGrid from '../../pages/draggablegrid'; +import MapsGoogle from '../../pages/maps'; +import VectorMaps from '../../pages/maps/VectorMap'; +import Timeline from '../../pages/timeline'; +import Search from '../../pages/search'; +import Gallery from '../../pages/gallery'; +import Invoice from '../../pages/invoice'; +import Calendar from '../../pages/calendar'; import BreadCrumbs from '../../components/BreadCrumbs'; - // context import { useLayoutState } from '../../context/LayoutContext'; -import { ProductsProvider } from '../../context/ProductContext' - +import { ProductsProvider } from '../../context/ProductContext'; import UsersFormPage from 'pages/CRUD/Users/form/UsersFormPage'; import UsersTablePage from 'pages/CRUD/Users/table/UsersTablePage'; - -//Sidebar structure -import structure from '../Sidebar/SidebarStructure' - -const Redirect = (props) => { - useEffect(() => window.location.replace(props.url)); - return Redirecting...; -}; +// Sidebar structure +import structure from '../Sidebar/SidebarStructure'; function Layout(props) { const classes = useStyles(); const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); const id = open ? 'add-section-popover' : undefined; + + // Handle click to open/close theme popover const handleClick = (event) => { setAnchorEl(open ? null : event.currentTarget); }; - // global - let layoutState = useLayoutState(); + // Global layout state from context + const layoutState = useLayoutState(); return (
-
- +
+
- - - - - - } /> - - - - - } /> - - - - } /> - - - - - - - - - - - - - } /> - - - - } /> - - - - - - - - } /> - - - - }/> - - - - - - - - - - - - - - - - - - - - - - - - - + {/* Define nested routes with relative paths */} + + } /> + } /> + } /> + {/* Core */} + } /> + } /> + } /> + } /> + {/* Tables */} + } /> + } /> + } /> + {/* UI */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* Forms */} + } /> + } /> + } /> + {/* Charts */} + } /> + } /> + } /> + } /> + } /> + {/* Draggable Grid */} + } /> + {/* Maps */} + } /> + } /> + } /> + {/* Extra */} + } /> + } /> + } /> + } /> + } /> + } /> + {/* Ecommerce */} + + + + } + /> + + + + } /> - - - + + + } /> - + } /> + } /> + } /> + {/* Users */} + } /> + } /> + } /> + {/* Fallback for nested routes */} + } /> + + {/* Alternatively, you can render if nested routes are defined in App */} + {/* */} handleClick(e)} + color="primary" + aria-label="settings" + onClick={handleClick} className={classes.changeThemeFab} style={{ zIndex: 100 }} > @@ -199,47 +187,29 @@ function Layout(props) {
- + Flatlogic - + About Us - + Blog
- - + + - - + + - - + + @@ -250,4 +220,4 @@ function Layout(props) { ); } -export default withRouter(connect()(Layout)); +export default connect()(Layout); \ No newline at end of file diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index 30cf93e2..00c0e098 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -2,26 +2,27 @@ import React, { useState, useEffect, useMemo } from 'react'; import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; import { Drawer, IconButton, List } from '@mui/material'; import { useTheme } from '@mui/material'; -import { withRouter } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; // useLocation hook replaces withRouter import classNames from 'classnames'; // styles import useStyles from './styles'; - // components import SidebarLink from './components/SidebarLink/SidebarLink'; - // context -import { - useLayoutState, - useLayoutDispatch, - toggleSidebar, -} from '../../context/LayoutContext'; +import { useLayoutState, useLayoutDispatch, toggleSidebar } from '../../context/LayoutContext'; + +function Sidebar({ structure }) { + const classes = useStyles(); + const theme = useTheme(); + const location = useLocation(); // get router location using hook + const layoutState = useLayoutState(); + const layoutDispatch = useLayoutDispatch(); -function Sidebar({ location, structure }) { - let classes = useStyles(); - let theme = useTheme(); + // local state: whether the drawer is permanent or temporary + const [isPermanent, setPermanent] = useState(true); + // Callback to toggle the drawer const toggleDrawer = (value) => (event) => { if ( event.type === 'keydown' && @@ -29,29 +30,38 @@ function Sidebar({ location, structure }) { ) { return; } - - if (value && !isPermanent) toggleSidebar(layoutDispatch); + // Only toggle sidebar if temporary (non-permanent) + if (value && !isPermanent) { + toggleSidebar(layoutDispatch); + } }; - // global - let { isSidebarOpened } = useLayoutState(); - let layoutDispatch = useLayoutDispatch(); - - // local - let [isPermanent, setPermanent] = useState(true); - + // Compute the sidebar open state based on permanent flag and global state const isSidebarOpenedWrapper = useMemo( - () => (!isPermanent ? !isSidebarOpened : isSidebarOpened), - [isPermanent, isSidebarOpened], + () => (!isPermanent ? !layoutState.isSidebarOpened : layoutState.isSidebarOpened), + [isPermanent, layoutState.isSidebarOpened] ); - useEffect(function () { + useEffect(() => { window.addEventListener('resize', handleWindowWidthChange); handleWindowWidthChange(); - return function cleanup() { + return () => { window.removeEventListener('resize', handleWindowWidthChange); }; - }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [theme.breakpoints.values.md, isPermanent]); + + // Automatically adjust sidebar mode based on window width + function handleWindowWidthChange() { + const windowWidth = window.innerWidth; + const breakpointWidth = theme.breakpoints.values.md; + const isSmallScreen = windowWidth < breakpointWidth; + if (isSmallScreen && isPermanent) { + setPermanent(false); + } else if (!isSmallScreen && !isPermanent) { + setPermanent(true); + } + } return ( toggleSidebar(layoutDispatch)}>
- - {structure.map(link => ( + + {structure.map((link) => ( @@ -95,19 +100,6 @@ function Sidebar({ location, structure }) { ); - - // ################################################################## - function handleWindowWidthChange() { - let windowWidth = window.innerWidth; - let breakpointWidth = theme.breakpoints.values.md; - let isSmallScreen = windowWidth < breakpointWidth; - - if (isSmallScreen && isPermanent) { - setPermanent(false); - } else if (!isSmallScreen && !isPermanent) { - setPermanent(true); - } - } } -export default withRouter(Sidebar); +export default Sidebar; \ No newline at end of file diff --git a/src/components/Table/Actions.js b/src/components/Table/Actions.js index f4b7aa28..63183453 100644 --- a/src/components/Table/Actions.js +++ b/src/components/Table/Actions.js @@ -1,6 +1,5 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; - +import { useNavigate } from 'react-router-dom'; // useNavigate replaces useHistory import IconButton from '@mui/material/IconButton'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import Menu from '@mui/material/Menu'; @@ -9,14 +8,17 @@ import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; const Actions = ({ classes, id, openModal, entity }) => { - const history = useHistory(); + const navigate = useNavigate(); // retrieve navigate function const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); + // Handle click on the IconButton to open the menu const handleClick = (event) => { event.stopPropagation(); setAnchorEl(event.currentTarget); }; + + // Close the menu const handleClose = () => { setAnchorEl(null); }; @@ -36,12 +38,11 @@ const Actions = ({ classes, id, openModal, entity }) => { { - history.push(`/admin/${entity}/${id}/edit`); + navigate(`/admin/${entity}/${id}/edit`); handleClose(); }} > - - Edit + Edit { handleClose(); }} > - - Delete + Delete
); }; -export default Actions; +export default Actions; \ No newline at end of file diff --git a/src/components/Widget/styles.js b/src/components/Widget/styles.js index 60875269..cec3b30c 100644 --- a/src/components/Widget/styles.js +++ b/src/components/Widget/styles.js @@ -18,7 +18,7 @@ export default makeStyles((theme) => ({ boxShadow: theme.customShadows.widget, }, widgetBody: { - height: (props) => (props.fullHeight ? '100%' : 'inherit'), + height: (props) => (props.fullHeight ? '100%' : 'currentColor'), padding: theme.spacing(3), paddingTop: theme.spacing(1), }, diff --git a/src/context/ManagementContext.js b/src/context/ManagementContext.js index 13496fdf..f7f82992 100644 --- a/src/context/ManagementContext.js +++ b/src/context/ManagementContext.js @@ -240,46 +240,30 @@ const actions = { } }, - doCreate: (values, history) => async (dispatch) => { + doCreate: (values, navigate) => async (dispatch) => { try { - dispatch({ - type: 'USERS_FORM_CREATE_STARTED', - }); + dispatch({ type: 'USERS_FORM_CREATE_STARTED' }); axios.post('/users', { data: values }).then((res) => { - dispatch({ - type: 'USERS_FORM_CREATE_SUCCESS', - }); - history.push('/app/user/list'); + dispatch({ type: 'USERS_FORM_CREATE_SUCCESS' }); + // Redirect using navigate instead of history.push + navigate('/app/user/list'); }); } catch (error) { showSnackbar({ type: 'error', message: 'Error' }); console.log(error); - dispatch({ - type: 'USERS_FORM_CREATE_ERROR', - }); + dispatch({ type: 'USERS_FORM_CREATE_ERROR' }); } }, - - doUpdate: (id, values, history) => async (dispatch, getState) => { + doUpdate: (id, values, navigate) => async (dispatch, getState) => { try { - dispatch({ - type: 'USERS_FORM_UPDATE_STARTED', - }); - + dispatch({ type: 'USERS_FORM_UPDATE_STARTED' }); await axios.put(`/users/${id}`, { id, data: values }); - - dispatch({ - type: 'USERS_FORM_UPDATE_SUCCESS', - payload: values, - }); - - history.push('/admin/dashboard'); + dispatch({ type: 'USERS_FORM_UPDATE_SUCCESS', payload: values }); + // Redirect using navigate + navigate('/admin/dashboard'); } catch (error) { console.log(error); - - dispatch({ - type: 'USERS_FORM_UPDATE_ERROR', - }); + dispatch({ type: 'USERS_FORM_UPDATE_ERROR' }); } }, diff --git a/src/context/ProductContext.js b/src/context/ProductContext.js index 4de57a0d..bbd43f55 100644 --- a/src/context/ProductContext.js +++ b/src/context/ProductContext.js @@ -33,12 +33,7 @@ const rootReducer = (state, action) => { }; case 'CREATE_PRODUCT': - state.products.push(action.payload); - return { - ...state, - isLoaded: true, - products: state.products, - }; + return { ...state, isLoaded: true, products: [...state.products, action.payload] }; default: return { diff --git a/src/context/UserContext.js b/src/context/UserContext.js index 25406bdb..754e08d2 100644 --- a/src/context/UserContext.js +++ b/src/context/UserContext.js @@ -171,14 +171,14 @@ export function sendPasswordResetEmail(email) { }; } -function signOut(dispatch, history) { +function signOut(dispatch, navigate) { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('user_id'); document.cookie = 'token=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; axios.defaults.headers.common['Authorization'] = ''; dispatch({ type: 'SIGN_OUT_SUCCESS' }); - history.push('/login'); + navigate('/login'); } export function receiveToken(token, dispatch) { diff --git a/src/index.js b/src/index.js index 8eb9d8e1..6bae3b8d 100755 --- a/src/index.js +++ b/src/index.js @@ -1,52 +1,19 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import axios from 'axios'; -import { createStore, applyMiddleware, compose } from 'redux'; -import ReduxThunk from 'redux-thunk'; +import App from './components/App'; import { Provider } from 'react-redux'; -import { routerMiddleware } from 'connected-react-router'; +import store from './reducers/store'; // Our new store created with Redux Toolkit + import { ThemeProvider as ThemeProviderV5 } from '@mui/material/styles'; import { StyledEngineProvider } from '@mui/material/styles'; -import App from './components/App'; -import * as serviceWorker from './serviceWorker'; import { LayoutProvider } from './context/LayoutContext'; import { UserProvider } from './context/UserContext'; import { ManagementProvider } from './context/ManagementContext'; -import createRootReducer from './reducers'; -import { - ThemeProvider as ThemeChangeProvider, - ThemeStateContext, -} from './context/ThemeContext'; +import { ThemeProvider as ThemeChangeProvider, ThemeStateContext } from './context/ThemeContext'; import CssBaseline from '@mui/material/CssBaseline'; -import config from '../src/config'; - -import { createHashHistory, createMemoryHistory } from 'history'; - -const history = - typeof window !== 'undefined' - ? createHashHistory() - : createMemoryHistory({ - initialEntries: [], - }); - -export function getHistory() { - return history; -} - -axios.defaults.baseURL = config.baseURLApi; -axios.defaults.headers.common['Content-Type'] = 'application/json'; -const token = localStorage.getItem('token'); -if (token) { - axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; -} - -export const store = createStore( - createRootReducer(history), - compose(applyMiddleware(routerMiddleware(history), ReduxThunk)), -); +import * as serviceWorker from './serviceWorker'; const root = ReactDOM.createRoot(document.getElementById('root')); - root.render( @@ -67,10 +34,7 @@ root.render( - , + ); -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: http://bit.ly/CRA-PWA -serviceWorker.unregister(); +serviceWorker.unregister(); \ No newline at end of file diff --git a/src/pages/CRUD/Users/form/UsersFormPage.js b/src/pages/CRUD/Users/form/UsersFormPage.js index dd48bbc1..3e7cb4b9 100644 --- a/src/pages/CRUD/Users/form/UsersFormPage.js +++ b/src/pages/CRUD/Users/form/UsersFormPage.js @@ -1,46 +1,49 @@ import React, { useState, useEffect } from 'react'; +import { useParams, useLocation, useNavigate } from 'react-router-dom'; // Use hooks for router info import UsersForm from 'pages/CRUD/Users/form/UsersForm'; -import { push } from 'connected-react-router'; import actions from 'actions/users/usersFormActions'; import { connect } from 'react-redux'; const UsersFormPage = (props) => { - const { dispatch, match, saveLoading, findLoading, record, currentUser } = - props; - + const { dispatch, saveLoading, findLoading, record, currentUser } = props; const [dispatched, setDispatched] = useState(false); - const isEditing = () => { - return !!match.params.id; - }; + // Get parameters and location from router + const params = useParams(); + const location = useLocation(); + const navigate = useNavigate(); - const isProfile = () => { - return match.url === '/app/profile'; - }; + // Determine if editing based on existence of params.id + const isEditing = () => !!params.id; + + // Determine if current page is the profile page + const isProfile = () => location.pathname === '/app/profile'; + // onSubmit handler calls doUpdate or doCreate action creators, + // passing "navigate" for redirection const doSubmit = (id, data) => { if (isEditing() || isProfile()) { - dispatch(actions.doUpdate(id, data, isProfile())); + dispatch(actions.doUpdate(id, data, isProfile(), navigate)); } else { - dispatch(actions.doCreate(data)); + dispatch(actions.doCreate(data, navigate)); } }; useEffect(() => { if (isEditing()) { - dispatch(actions.doFind(match.params.id)); + dispatch(actions.doFind(params.id, navigate)); } else { if (isProfile()) { const currentUser = JSON.parse(localStorage.getItem('user')); const currentUserId = currentUser.user.id; - dispatch(actions.doFind(currentUserId)); + dispatch(actions.doFind(currentUserId, navigate)); } else { dispatch(actions.doNew()); } } setDispatched(true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [match, dispatch]); + // Include dependencies to update when router parameters change + }, [params, location.pathname, dispatch, navigate]); return ( <> @@ -53,7 +56,7 @@ const UsersFormPage = (props) => { isEditing={isEditing()} isProfile={isProfile()} onSubmit={doSubmit} - onCancel={() => dispatch(push('/app/users'))} + onCancel={() => navigate('/app/users')} /> )} @@ -69,4 +72,4 @@ function mapStateToProps(store) { }; } -export default connect(mapStateToProps)(UsersFormPage); +export default connect(mapStateToProps)(UsersFormPage); \ No newline at end of file diff --git a/src/pages/CRUD/Users/table/UsersTable.js b/src/pages/CRUD/Users/table/UsersTable.js index 27617925..c0e4f104 100644 --- a/src/pages/CRUD/Users/table/UsersTable.js +++ b/src/pages/CRUD/Users/table/UsersTable.js @@ -1,17 +1,14 @@ import * as dataFormat from 'pages/CRUD/Users/table/UsersDataFormatters'; - import actions from 'actions/users/usersListActions'; import React from 'react'; import { Link } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router'; +// Replace useHistory with useNavigate from react-router-dom +import { useNavigate } from 'react-router-dom'; import { uniqueId } from 'lodash'; - import { makeStyles } from '@mui/styles'; import { DataGrid } from '@mui/x-data-grid'; - import MenuItem from '@mui/material/MenuItem'; - import Button from '@mui/material/Button'; import Box from '@mui/material/Box'; import InputLabel from '@mui/material/InputLabel'; @@ -22,7 +19,6 @@ import Grid from '@mui/material/Grid'; import CloseIcon from '@mui/icons-material/Close'; import Stack from '@mui/material/Stack'; import LinearProgress from '@mui/material/LinearProgress'; - import Widget from 'components/Widget'; import Actions from '../../../../components/Table/Actions'; import Dialog from '../../../../components/Dialog'; @@ -45,11 +41,14 @@ const useStyles = makeStyles({ const UsersTable = () => { const dispatch = useDispatch(); - const history = useHistory(); + const navigate = useNavigate(); // useNavigate hook replaces useHistory const classes = useStyles(); + + // Store the current window width in state // eslint-disable-next-line no-unused-vars const [width, setWidth] = React.useState(window.innerWidth); + // Filters configuration const [filters] = React.useState([ { label: 'First Name', title: 'firstName' }, { label: 'Last Name', title: 'lastName' }, @@ -59,21 +58,18 @@ const UsersTable = () => { const [filterItems, setFilterItems] = React.useState([]); const [filterUrl, setFilterUrl] = React.useState(''); - const [loading, setLoading] = React.useState(false); const [sortModel, setSortModel] = React.useState([]); const [selectionModel, setSelectionModel] = React.useState([]); + // Get count from redux store (if used) // eslint-disable-next-line no-unused-vars const count = useSelector((store) => store.users.list.count); const modalOpen = useSelector((store) => store.users.list.modalOpen); const rows = useSelector((store) => store.users.list.rows); const idToDelete = useSelector((store) => store.users.list.idToDelete); + const [rowsState, setRowsState] = React.useState({ page: 0, pageSize: 5 }); - const [rowsState, setRowsState] = React.useState({ - page: 0, - pageSize: 5, - }); - + // Load data from backend const loadData = async (limit, page, orderBy, request) => { setLoading(true); await dispatch(actions.doFetch({ limit, page, orderBy, request })); @@ -82,14 +78,15 @@ const UsersTable = () => { React.useEffect(() => { loadData(rowsState.pageSize, rowsState.page, sortModel[0], filterUrl); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortModel, rowsState, filterUrl]); React.useEffect(() => { updateWindowDimensions(); window.addEventListener('resize', updateWindowDimensions); - return () => window.removeEventListener('resize', updateWindowDimensions); - // eslint-disable-next-line react-hooks/exhaustive-deps + return () => + window.removeEventListener('resize', updateWindowDimensions); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleSortModelChange = (newModel) => { @@ -103,13 +100,12 @@ const UsersTable = () => { const handleChange = (id) => (e) => { const value = e.target.value; const name = e.target.name; - setFilterItems( filterItems.map((item) => item.id === id ? { id, fields: { ...item.fields, [name]: value } } - : item, - ), + : item + ) ); }; @@ -117,13 +113,16 @@ const UsersTable = () => { e.preventDefault(); let request = '&'; filterItems.forEach((item) => { - filters[ - filters.map((filter) => filter.title).indexOf(item.fields.selectedField) - ].hasOwnProperty('number') - ? (request += `${item.fields.selectedField}Range=${item.fields.filterValueFrom}&${item.fields.selectedField}Range=${item.fields.filterValueTo}&`) - : (request += `${item.fields.selectedField}=${item.fields.filterValue}&`); + const filterOptionIndex = filters + .map((filter) => filter.title) + .indexOf(item.fields.selectedField); + // If the filter option has a "number" property, then treat it as a range filter + if (filters[filterOptionIndex].hasOwnProperty('number')) { + request += `${item.fields.selectedField}Range=${item.fields.filterValueFrom}&${item.fields.selectedField}Range=${item.fields.filterValueTo}&`; + } else { + request += `${item.fields.selectedField}=${item.fields.filterValue}&`; + } }); - loadData(rowsState.pageSize, 0, sortModel[0], request); setFilterUrl(request); }; @@ -131,19 +130,13 @@ const UsersTable = () => { const handleReset = () => { setFilterItems([]); setFilterUrl(''); - dispatch( - actions.doFetch({ limit: rowsState.pageSize, page: 0, request: '' }), - ); + dispatch(actions.doFetch({ limit: rowsState.pageSize, page: 0, request: '' })); }; const addFilter = () => { let newItem = { id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - }, + fields: { filterValue: '', filterValueFrom: '', filterValueTo: '' }, }; newItem.fields.selectedField = filters[0].title; setFilterItems([...filterItems, newItem]); @@ -162,7 +155,7 @@ const UsersTable = () => { const handleDelete = () => { dispatch( - actions.doDelete({ limit: 10, page: 0, request: filterUrl }, idToDelete), + actions.doDelete({ limit: 10, page: 0, request: filterUrl }, idToDelete) ); }; @@ -178,68 +171,29 @@ const UsersTable = () => { function NoRowsOverlay() { return ( - + No results found ); } const columns = [ - { - field: 'firstName', - - flex: 0.6, - - headerName: 'First Name', - }, - - { - field: 'lastName', - - flex: 0.6, - - headerName: 'Last Name', - }, - - { - field: 'phoneNumber', - - flex: 0.6, - - headerName: 'Phone Number', - }, - - { - field: 'email', - - flex: 0.6, - - headerName: 'E-Mail', - }, - - { - field: 'role', - - headerName: 'Role', - }, - + { field: 'firstName', flex: 0.6, headerName: 'First Name' }, + { field: 'lastName', flex: 0.6, headerName: 'Last Name' }, + { field: 'phoneNumber', flex: 0.6, headerName: 'Phone Number' }, + { field: 'email', flex: 0.6, headerName: 'E-Mail' }, + { field: 'role', headerName: 'Role' }, { field: 'disabled', - renderCell: (params) => dataFormat.booleanFormatter(params.row), - headerName: 'Disabled', }, - { field: 'avatar', - sortable: false, renderCell: (params) => dataFormat.imageFormatter(params.row), - headerName: 'Avatar', }, - { field: 'id', headerName: 'Actions', @@ -247,78 +201,70 @@ const UsersTable = () => { flex: 0.6, maxWidth: 80, renderCell: (params) => ( - + ), }, ]; return (
- + - - + + - - {filterItems.map((item) => ( - + Field - {filters - .find((filter) => filter.title === item.fields.selectedField) - .hasOwnProperty('number') ? ( + {filters.find( + (filter) => filter.title === item.fields.selectedField + ).hasOwnProperty('number') ? ( <> @@ -327,20 +273,19 @@ const UsersTable = () => { ) : ( )} - - )} -
{ { - setRowsState((prev) => ({ ...prev, page })); - }} - onPageSizeChange={(pageSize) => { - setRowsState((prev) => ({ ...prev, pageSize })); - }} - onSelectionModelChange={(newSelectionModel) => { - setSelectionModel(newSelectionModel); - }} + onPageChange={(page) => + setRowsState((prev) => ({ ...prev, page })) + } + onPageSizeChange={(pageSize) => + setRowsState((prev) => ({ ...prev, pageSize })) + } + onSelectionModelChange={(newSelectionModel) => + setSelectionModel(newSelectionModel) + } selectionModel={selectionModel} checkboxSelection disableSelectionOnClick disableColumnMenu loading={loading} onRowClick={(e) => { - history.push(`/app/users/${e.id}/edit`); + // Replace history.push with navigate call + navigate(`/app/users/${e.id}/edit`); }} autoHeight />
- @@ -417,4 +361,4 @@ const UsersTable = () => { ); }; -export default UsersTable; +export default UsersTable; \ No newline at end of file diff --git a/src/pages/ecommerce/CreateProduct.js b/src/pages/ecommerce/CreateProduct.js index 8882d2df..261eace0 100644 --- a/src/pages/ecommerce/CreateProduct.js +++ b/src/pages/ecommerce/CreateProduct.js @@ -5,11 +5,11 @@ import { Grid, MenuItem, Select, - TextField as Input + TextField as Input, + Button, } from "@mui/material"; -import { useParams, useHistory } from "react-router-dom"; - -//context +import { useParams, useNavigate } from "react-router-dom"; // useNavigate replaces useHistory +// context import { getProductsRequest, useProductsState, @@ -17,20 +17,19 @@ import { createProduct, getProductsImages } from "../../context/ProductContext"; - -//components +// components import Widget from "../../components/Widget"; -import { Typography, Button } from "../../components/Wrappers"; +import { Typography } from "../../components/Wrappers"; import config from "../../config"; const CreateProduct = () => { const { id } = useParams(); + const navigate = useNavigate(); // create navigate function const context = useProductsState(); - const getId = id => { - return context.products.products.findIndex(c => { - return c.id == id; // eslint-disable-line - }); + // Helper to get index of product by id from the products state array + const getId = (id) => { + return context.products.products.findIndex(c => c.id == id); // eslint-disable-line }; const [localProducts, setLocalProducts] = React.useState( @@ -38,8 +37,7 @@ const CreateProduct = () => { ); const [newProduct, setNewProduct] = React.useState({ - img: - "https://flatlogic-node-backend.herokuapp.com/assets/products/img1.jpg", + img: "https://flatlogic-node-backend.herokuapp.com/assets/products/img1.jpg", title: null, subtitle: null, price: 0.1, @@ -52,40 +50,18 @@ const CreateProduct = () => { discount: 0 }); - // function sendNotification() { - // const componentProps = { - // type: "feedback", - // message: "Product has been Updated!", - // variant: "contained", - // color: "success" - // }; - // const options = { - // type: "info", - // position: toast.POSITION.TOP_RIGHT, - // progressClassName: classes.progress, - // className: classes.notification, - // timeOut: 1000 - // }; - // return toast( - // , - // options - // ); - // } - + // Fetch products and images on mount useEffect(() => { getProductsRequest(context.setProducts); getProductsImages(context.setProducts); }, []); // eslint-disable-line + // Update localProducts when context changes useEffect(() => { setLocalProducts(context.products.products[getId(id)]); - }, [context]); // eslint-disable-line - - const history = useHistory(); + }, [context, id]); // eslint-disable-line + // Handles editing for existing product const editProduct = e => { setLocalProducts({ ...localProducts, @@ -93,6 +69,7 @@ const CreateProduct = () => { }); }; + // Handles editing for new product const editNewProduct = e => { setNewProduct({ ...newProduct, @@ -100,51 +77,39 @@ const CreateProduct = () => { }); }; + // Update product (edit mode) then navigate back to management page const getEditProduct = () => { updateProduct(localProducts, context.setProducts); - history.push("/app/ecommerce/management"); + navigate("/app/ecommerce/management"); // using navigate // sendNotification(); }; + // Create new product then navigate back to management page const createNewProduct = () => { createProduct(newProduct, context.setProducts); - history.push("/app/ecommerce/management"); + navigate("/app/ecommerce/management"); // using navigate }; + // Change image source for the product based on selection const changeImgSrc = e => { if (isCreateProduct) { - setNewProduct({ ...localProducts, img: e.target.value }); + setNewProduct({ ...newProduct, img: e.target.value }); } else { setLocalProducts({ ...localProducts, img: e.target.value }); } }; - - const isCreateProduct = - window.location.hash === "#/app/ecommerce/management/create"; + // Determine if we are creating a new product based on window location hash. + // Consider moving to useLocation() in a real-world scenario. + const isCreateProduct = window.location.hash === "#/app/ecommerce/management/create"; return ( <> - {/**/} - {/* }*/} - {/* closeOnClick={false}*/} - {/* progressClassName={classes.notificationProgress}*/} - {/*/>*/} - + {config.isBackend && !context.products.isLoaded ? ( - + ) : ( @@ -155,15 +120,13 @@ const CreateProduct = () => { @@ -178,13 +141,9 @@ const CreateProduct = () => { id="title" margin="normal" variant="outlined" - value={ - isCreateProduct ? newProduct.title : localProducts.title - } + value={isCreateProduct ? newProduct.title : localProducts.title} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -197,15 +156,9 @@ const CreateProduct = () => { id="subtitle" margin="normal" variant="outlined" - value={ - isCreateProduct - ? newProduct.subtitle - : localProducts.subtitle - } + value={isCreateProduct ? newProduct.subtitle : localProducts.subtitle} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -218,14 +171,10 @@ const CreateProduct = () => { id="price" margin="normal" variant="outlined" - value={ - isCreateProduct ? newProduct.price : localProducts.price - } + value={isCreateProduct ? newProduct.price : localProducts.price} type={"number"} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -238,16 +187,10 @@ const CreateProduct = () => { id="discount" margin="normal" variant="outlined" - value={ - isCreateProduct - ? newProduct.discount - : localProducts.discount - } + value={isCreateProduct ? newProduct.discount : localProducts.discount} type={"number"} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -261,15 +204,9 @@ const CreateProduct = () => { margin="normal" variant="outlined" multiline - value={ - isCreateProduct - ? newProduct["description_1"] - : localProducts["description_1"] - } + value={isCreateProduct ? newProduct["description_1"] : localProducts["description_1"]} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -283,15 +220,9 @@ const CreateProduct = () => { margin="normal" variant="outlined" multiline - value={ - isCreateProduct - ? newProduct["description_2"] - : localProducts["description_2"] - } + value={isCreateProduct ? newProduct["description_2"] : localProducts["description_2"]} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -304,13 +235,9 @@ const CreateProduct = () => { id="code" margin="normal" variant="outlined" - value={ - isCreateProduct ? newProduct.code : localProducts.code - } + value={isCreateProduct ? newProduct.code : localProducts.code} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -323,15 +250,9 @@ const CreateProduct = () => { id="hashtag" margin="normal" variant="outlined" - value={ - isCreateProduct - ? newProduct.hashtag - : localProducts.hashtag - } + value={isCreateProduct ? newProduct.hashtag : localProducts.hashtag} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -348,12 +269,10 @@ const CreateProduct = () => { fullWidth value={ isCreateProduct - ? newProduct.technology.join(' ') - : localProducts.technology.join(' ') - } - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) + ? newProduct.technology.join(" ") + : localProducts.technology.join(" ") } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -367,15 +286,9 @@ const CreateProduct = () => { margin="normal" variant="outlined" type={"number"} - value={ - isCreateProduct - ? newProduct.rating - : localProducts.rating - } + value={isCreateProduct ? newProduct.rating : localProducts.rating} fullWidth - onChange={e => - isCreateProduct ? editNewProduct(e) : editProduct(e) - } + onChange={e => isCreateProduct ? editNewProduct(e) : editProduct(e)} /> @@ -384,15 +297,13 @@ const CreateProduct = () => { variant={"contained"} color={"success"} style={{ marginRight: 8 }} - onClick={() => - isCreateProduct ? createNewProduct() : getEditProduct() - } + onClick={() => isCreateProduct ? createNewProduct() : getEditProduct()} > {isCreateProduct ? "Save" : "Edit"} @@ -406,5 +317,4 @@ const CreateProduct = () => { ); }; - -export default CreateProduct; +export default CreateProduct; \ No newline at end of file diff --git a/src/pages/ecommerce/Ecommerce.js b/src/pages/ecommerce/Ecommerce.js index 175c5410..aed26581 100644 --- a/src/pages/ecommerce/Ecommerce.js +++ b/src/pages/ecommerce/Ecommerce.js @@ -15,20 +15,17 @@ import { CircularProgress, Box, InputAdornment, - TextField as Input + TextField as Input, } from "@mui/material"; -import { Link as RouterLink, withRouter, useHistory } from "react-router-dom"; - -//config +import { Link as RouterLink, useNavigate } from "react-router-dom"; // Removed withRouter, useNavigate now used +// config import config from "../../config"; - // Material UI icons import { Star as StarIcon, Delete as DeleteIcon, FilterList as FilterListIcon, - Close as CloseIcon, - Search as SearchIcon + Search as SearchIcon, } from "@mui/icons-material"; import { yellow } from "@mui/material/colors"; import { lighten } from "@mui/material/styles"; @@ -36,19 +33,16 @@ import { makeStyles } from "@mui/styles"; import PropTypes from "prop-types"; import useStyles from "./styles"; import cn from "classnames"; - -//context +// context import { ProductsProvider, useProductsState, getProductsRequest, - deleteProductRequest + deleteProductRequest, } from "../../context/ProductContext"; - // components import Widget from "../../components/Widget"; import { Typography, Button, Link } from "../../components/Wrappers"; -// import Notification from "../../components/Notification"; function desc(a, b, orderBy) { if (b[orderBy] < a[orderBy]) { @@ -67,7 +61,7 @@ function stableSort(array, cmp) { if (order !== 0) return order; return a[1] - b[1]; }); - return stabilizedThis.map(el => el[0]); + return stabilizedThis.map((el) => el[0]); } function getSorting(order, orderBy) { @@ -77,18 +71,13 @@ function getSorting(order, orderBy) { } const headCells = [ - { - id: "id", - numeric: true, - disablePadding: true, - label: "ID" - }, + { id: "id", numeric: true, disablePadding: true, label: "ID" }, { id: "image", numeric: true, disablePadding: false, label: "Image" }, { id: "title", numeric: true, disablePadding: false, label: "Title" }, { id: "subtitle", numeric: true, disablePadding: false, label: "Subtitle" }, { id: "price", numeric: true, disablePadding: false, label: "Price" }, { id: "rating", numeric: true, disablePadding: false, label: "Rating" }, - { id: "actions", numeric: true, disablePadding: false, label: "Actions" } + { id: "actions", numeric: true, disablePadding: false, label: "Actions" }, ]; function EnhancedTableHead(props) { @@ -99,9 +88,9 @@ function EnhancedTableHead(props) { orderBy, numSelected, rowCount, - onRequestSort + onRequestSort, } = props; - const createSortHandler = property => event => { + const createSortHandler = (property) => (event) => { onRequestSort(event, property); }; @@ -116,7 +105,7 @@ function EnhancedTableHead(props) { inputProps={{ "aria-label": "select all rows" }} /> - {headCells.map(headCell => ( + {headCells.map((headCell) => ( ({ +const useToolbarStyles = makeStyles((theme) => ({ root: { paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(1) + paddingRight: theme.spacing(1), }, highlight: - theme.palette.type === "light" + theme.palette.mode === "light" ? { - color: theme.palette.secondary.main, - backgroundColor: lighten(theme.palette.secondary.light, 0.85) - } + color: theme.palette.secondary.main, + backgroundColor: lighten(theme.palette.secondary.light, 0.85), + } : { - color: theme.palette.text.primary, - backgroundColor: theme.palette.secondary.dark - }, + color: theme.palette.text.primary, + backgroundColor: theme.palette.secondary.dark, + }, title: { - flex: "1 1 100%" - } + flex: "1 1 100%", + }, })); const EnhancedTableToolbar = ({ numSelected, selected, deleteProducts }) => { - const history = useHistory(); + const navigate = useNavigate(); // useNavigate in place of useHistory const classes = useToolbarStyles(); - return ( 0 + [classes.highlight]: numSelected > 0, })} style={{ marginTop: 8 }} > {numSelected > 0 ? ( - + {numSelected} selected ) : ( @@ -196,11 +180,10 @@ const EnhancedTableToolbar = ({ numSelected, selected, deleteProducts }) => { Products )} - {numSelected > 0 ? ( - deleteProducts(selected, history, e)} /> + deleteProducts(selected, navigate, e)} /> ) : ( @@ -215,10 +198,10 @@ const EnhancedTableToolbar = ({ numSelected, selected, deleteProducts }) => { }; EnhancedTableToolbar.propTypes = { - numSelected: PropTypes.number.isRequired + numSelected: PropTypes.number.isRequired, }; -function EcommercePage({ history }) { +function EcommercePage() { const classes = useStyles(); const context = useProductsState(); const [order, setOrder] = React.useState("asc"); @@ -226,12 +209,11 @@ function EcommercePage({ history }) { const [selected, setSelected] = React.useState([]); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [backProducts, setBackProducts] = React.useState( - context.products.products - ); + const [backProducts, setBackProducts] = React.useState(context.products.products); + const navigate = useNavigate(); // useNavigate replaces history useEffect(() => { - // sendNotification(); + // Optionally send notification here getProductsRequest(context.setProducts); }, []); // eslint-disable-line @@ -245,25 +227,24 @@ function EcommercePage({ history }) { setOrderBy(property); }; - const searchProducts = e => { + const searchProducts = (e) => { let products = []; - context.products.products.forEach(c => { + context.products.products.forEach((c) => { if (c.title.includes(e.currentTarget.value)) { products.push(c); } - return; }); setBackProducts(products); }; const openProduct = (id, event) => { - history.push("/app/ecommerce/product/" + id); + navigate("/app/ecommerce/product/" + id); event.stopPropagation(); }; - const handleSelectAllClick = event => { + const handleSelectAllClick = (event) => { if (event.target.checked) { - const newSelecteds = backProducts.map(n => n.id); + const newSelecteds = backProducts.map((n) => n.id); setSelected(newSelecteds); return; } @@ -273,7 +254,6 @@ function EcommercePage({ history }) { const handleClick = (event, name) => { const selectedIndex = selected.indexOf(name); let newSelected = []; - if (selectedIndex === -1) { newSelected = newSelected.concat(selected, name); } else if (selectedIndex === 0) { @@ -286,7 +266,6 @@ function EcommercePage({ history }) { selected.slice(selectedIndex + 1) ); } - setSelected(newSelected); }; @@ -294,48 +273,22 @@ function EcommercePage({ history }) { setPage(newPage); }; - const handleChangeRowsPerPage = event => { + const handleChangeRowsPerPage = (event) => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; - const isSelected = name => selected.indexOf(name) !== -1; - - const emptyRows = - rowsPerPage - - Math.min(rowsPerPage, backProducts.length - page * rowsPerPage); + const isSelected = (name) => selected.indexOf(name) !== -1; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, backProducts.length - page * rowsPerPage); - // function sendNotification() { - // const componentProps = { - // type: "feedback", - // message: - // "This page is only available in React Material Admin Full with Node.js integration!", - // variant: "contained", - // color: "success" - // }; - // const options = { - // type: "info", - // position: toast.POSITION.TOP_RIGHT, - // progressClassName: classes.progress, - // className: classes.notification, - // timeOut: 1000 - // }; - // return toast( - // , - // options - // ); - // } - - const deleteProduct = (id, history, event) => { - deleteProductRequest({ id, history, dispatch: context.setProducts }); + // Delete product function; note that deleteProductRequest now receives navigate instead of history + const deleteProduct = (id, navigateInstance, event) => { + deleteProductRequest({ id, navigate: navigateInstance, dispatch: context.setProducts }); event.stopPropagation(); }; const openProductEdit = (event, id) => { - history.push("/app/ecommerce/management/edit/" + id); + navigate("/app/ecommerce/management/edit/" + id); event.stopPropagation(); }; @@ -346,27 +299,13 @@ function EcommercePage({ history }) { + - + Products - + {backProducts.length} total @@ -382,55 +321,40 @@ function EcommercePage({ history }) { - ) + ), }} - onChange={e => searchProducts(e)} + onChange={(e) => searchProducts(e)} /> } > - { config.isBackend ? ( + {config.isBackend ? ( ) : ( - - ) - } + + )} {config.isBackend && !context.products.isLoaded ? ( - + ) : (
- +
{stableSort(backProducts, getSorting(order, orderBy)) - .slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row, index) => { const isItemSelected = isSelected(row.id); const labelId = `enhanced-table-checkbox-${index}`; - return ( handleClick(event, row.id)} + onClick={(event) => handleClick(event, row.id)} role="checkbox" aria-checked={isItemSelected} selected={isItemSelected} key={row.id} > - + - + {row.id} - {row.title} + {row.title} openProduct(row.id, e)} + onClick={(e) => openProduct(row.id, e)} color={"primary"} > {row.title - ? row.title.split("").map((c, n) => { - return n === 0 ? c.toUpperCase() : c; - }) + ? row.title.split("").map((c, n) => (n === 0 ? c.toUpperCase() : c)) : null} @@ -499,75 +405,40 @@ function EcommercePage({ history }) { ${row.price} - + {row.rating} {" "} - + - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {row.status}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {row.process}*/} - {/* */} - {/* */} - {/* */} - {/**/} - { config.isBackend ? ( + {config.isBackend ? ( ) : ( - - ) - } + + )} @@ -593,10 +464,10 @@ function EcommercePage({ history }) { rowsPerPage={rowsPerPage} page={page} backIconButtonProps={{ - "aria-label": "previous page" + "aria-label": "previous page", }} nextIconButtonProps={{ - "aria-label": "next page" + "aria-label": "next page", }} onPageChange={handleChangePage} onRowsPerPageChange={handleChangeRowsPerPage} @@ -607,10 +478,4 @@ function EcommercePage({ history }) { ); } - -// eslint-disable-next-line no-unused-vars -function CloseButton({ closeToast, className }) { - return ; -} - -export default withRouter(EcommercePage); +export default EcommercePage; // Removed withRouter wrapper \ No newline at end of file diff --git a/src/pages/login/Login.js b/src/pages/login/Login.js index 4a9bedbe..8904f4ef 100644 --- a/src/pages/login/Login.js +++ b/src/pages/login/Login.js @@ -8,7 +8,7 @@ import { TextField as Input, Typography, } from '@mui/material'; -import { withRouter } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; // Replace withRouter and useHistory import classnames from 'classnames'; // styles @@ -24,10 +24,11 @@ import { loginUser, registerUser, sendPasswordResetEmail, + receiveToken, + doInit, } from '../../context/UserContext'; -import { receiveToken, doInit } from '../../context/UserContext'; -//components +// components import { Button } from '../../components/Wrappers'; import Widget from '../../components/Widget'; import config from '../../config'; @@ -45,26 +46,31 @@ const getGreeting = () => { } }; -function Login(props) { +function Login() { let classes = useStyles(); - const tab = new URLSearchParams(props.location.search).get('tab'); + const location = useLocation(); + const navigate = useNavigate(); - // global + // Get ?tab query param from the URL + const tabQuery = new URLSearchParams(location.search).get('tab'); + + // global dispatch for user context let userDispatch = useUserDispatch(); + // Retrieve token from URL if available and initialize useEffect(() => { - const params = new URLSearchParams(props.location.search); + const params = new URLSearchParams(location.search); const token = params.get('token'); if (token) { receiveToken(token, userDispatch); doInit()(userDispatch); } - }, []); // eslint-disable-line + }, [location.search, userDispatch]); - // local + // local state let [isLoading, setIsLoading] = useState(false); let [error, setError] = useState(null); - let [activeTabId, setActiveTabId] = useState(+tab ?? 0); + let [activeTabId, setActiveTabId] = useState(tabQuery ? +tabQuery : 0); let [nameValue, setNameValue] = useState(''); let [loginValue, setLoginValue] = useState('admin@flatlogic.com'); let [passwordValue, setPasswordValue] = useState('password'); @@ -81,9 +87,9 @@ function Login(props) { userDispatch, loginValue, passwordValue, - props.history, + navigate, // using navigate instead of props.history setIsLoading, - setError, + setError ); } }; @@ -91,7 +97,7 @@ function Login(props) { return (
- logo + logo React Material Admin Full @@ -105,7 +111,7 @@ function Login(props) { {isForgot ? (
setForgotEmail(e.target.value)} - margin='normal' - placeholder='Email' - type='Email' + margin="normal" + placeholder="Email" + type="email" fullWidth />
@@ -128,16 +134,16 @@ function Login(props) { onClick={() => sendPasswordResetEmail(forgotEmail)(userDispatch) } - variant='contained' - color='primary' - size='large' + variant="contained" + color="primary" + size="large" > Send )}
Something is wrong with your login or password :( setLoginValue(e.target.value)} - margin='normal' - placeholder='Email Adress' - type='email' + margin="normal" + placeholder="Email Adress" + type="email" fullWidth onKeyDown={(e) => loginOnEnterKey(e)} /> setPasswordValue(e.target.value)} - margin='normal' - placeholder='Password' - type='password' + margin="normal" + placeholder="Password" + type="password" fullWidth onKeyDown={(e) => loginOnEnterKey(e)} /> @@ -266,21 +270,21 @@ function Login(props) { userDispatch, loginValue, passwordValue, - props.history, + navigate, setIsLoading, - setError, + setError ) } - variant='contained' - color='primary' - size='large' + variant="contained" + color="primary" + size="large" > Login )}
- + 2014-{new Date().getFullYear()}{' '} Flatlogic @@ -431,4 +435,4 @@ function Login(props) { ); } -export default withRouter(Login); +export default Login; \ No newline at end of file diff --git a/src/pages/nav/styles.js b/src/pages/nav/styles.js index 77389e9c..ac58b666 100644 --- a/src/pages/nav/styles.js +++ b/src/pages/nav/styles.js @@ -63,7 +63,7 @@ export default makeStyles(theme => ({ color: "rgba(255, 255, 255, 0.35)", }, inputRoot: { - color: "inherit", + color: "currentColor", width: "100%", }, inputInput: { diff --git a/src/pages/reset/Reset.js b/src/pages/reset/Reset.js index c55ad406..c0a8b869 100644 --- a/src/pages/reset/Reset.js +++ b/src/pages/reset/Reset.js @@ -1,31 +1,29 @@ import React, { useState } from 'react'; import { Grid, CircularProgress, TextField as Input } from '@mui/material'; -import { withRouter, useHistory } from 'react-router-dom'; - +import { useNavigate, useLocation } from 'react-router-dom'; // useNavigate replaces useHistory, useLocation for location info // styles import useStyles from './styles'; - // logo import logo from './logo.svg'; - // context -import { - useUserDispatch, - resetPassword, - authError, -} from '../../context/UserContext'; - -//components +import { useUserDispatch, resetPassword, authError } from '../../context/UserContext'; +// components import { Button, Typography } from '../../components/Wrappers'; -function Reset(props) { - let classes = useStyles(); - const history = useHistory(); - // global - let userDispatch = useUserDispatch(); +function Reset() { + const classes = useStyles(); + const navigate = useNavigate(); + const location = useLocation(); + const userDispatch = useUserDispatch(); + const [passwordValue, setPasswordValue] = useState(''); const [confirmPasswordValue, setConfirmPasswordValue] = useState(''); const [isLoading] = useState(false); + + const isPasswordValid = () => { + return passwordValue && passwordValue === confirmPasswordValue; + }; + const checkPassword = () => { if (!isPasswordValid()) { if (!passwordValue) { @@ -33,32 +31,31 @@ function Reset(props) { } else { authError('Passwords are not equal')(userDispatch); } + // This extra error call may be intended for resetting error state. authError()(userDispatch); } }; - const isPasswordValid = () => { - return passwordValue && passwordValue === confirmPasswordValue; - }; - const doReset = () => { - const params = new URLSearchParams(history.location.search); + // Get the token from URL query parameters using URLSearchParams and location.search + const params = new URLSearchParams(location.search); const token = params.get('token'); + if (!token) { - authError('There are no token')(userDispatch); + authError('There is no token')(userDispatch); + return; } - if (!isPasswordValid()) { checkPassword(); } else { - resetPassword(token, passwordValue, history)(userDispatch); + resetPassword(token, passwordValue, navigate)(userDispatch); } }; return (
- logo + logo React Material Admin Full @@ -66,7 +63,7 @@ function Reset(props) {
setPasswordValue(e.target.value)} - margin='normal' - placeholder='password' - type='password' + margin="normal" + placeholder="password" + type="password" fullWidth /> setConfirmPasswordValue(e.target.value)} - margin='normal' - placeholder='Confirm Password' - type='password' + margin="normal" + placeholder="Confirm Password" + type="password" fullWidth />
@@ -106,30 +103,30 @@ function Reset(props) { passwordValue !== confirmPasswordValue } onClick={() => doReset()} - variant='contained' - color='primary' - size='large' + variant="contained" + color="primary" + size="large" > reset password )}
- + 2014-{new Date().getFullYear()}{' '} Flatlogic @@ -140,4 +137,4 @@ function Reset(props) { ); } -export default withRouter(Reset); +export default Reset; \ No newline at end of file diff --git a/src/pages/user/AddUser.js b/src/pages/user/AddUser.js index b2f4e76a..69048c25 100644 --- a/src/pages/user/AddUser.js +++ b/src/pages/user/AddUser.js @@ -10,7 +10,6 @@ import InputLabel from '@material-ui/core/InputLabel' import Select from '@material-ui/core/Select' import MenuItem from '@material-ui/core/MenuItem' import FormHelperText from '@material-ui/core/FormHelperText' -import { useHistory } from 'react-router-dom' import useStyles from './styles' import { toast } from 'react-toastify' import Axios from 'axios' @@ -75,7 +74,7 @@ const AddUser = () => { const fileInput = React.useRef(null); const steps = getSteps() const classes = useStyles() - + const navigate = useNavigate(); function extractExtensionFrom(filename) { if (!filename) { return null; @@ -137,9 +136,8 @@ const AddUser = () => { } var managementDispatch = useManagementDispatch() - const history = useHistory() const doSubmit = (id, data) => { - actions.doCreate(data, history)(managementDispatch); + actions.doCreate(data, navigate)(managementDispatch); }; diff --git a/src/pages/user/EditUser.js b/src/pages/user/EditUser.js index a1a65529..4f164bb5 100644 --- a/src/pages/user/EditUser.js +++ b/src/pages/user/EditUser.js @@ -1,19 +1,20 @@ import React, { useEffect } from 'react'; -import { Grid, Box, TextField } from '@mui/material'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import { useParams } from 'react-router'; -import Checkbox from '@mui/material/Checkbox'; -import Switch from '@mui/material/Switch'; -import { useLocation, useHistory } from 'react-router-dom'; +import { + Grid, + Box, + TextField, + Tabs, + Tab, + Checkbox, + Switch, +} from '@mui/material'; +import { useParams, useLocation, useNavigate } from 'react-router-dom'; import useStyles from './styles'; - import { PersonOutline as PersonOutlineIcon, Lock as LockIcon, } from '@mui/icons-material'; import { v4 as uuid } from 'uuid'; - import Widget from '../../components/Widget'; import { Typography, Button } from '@mui/material'; import Select from '@mui/material/Select'; @@ -21,14 +22,9 @@ import MenuItem from '@mui/material/MenuItem'; import FormControlLabel from '@mui/material/FormControlLabel'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; - -import { - useManagementDispatch, - useManagementState, -} from '../../context/ManagementContext'; +import { useManagementDispatch, useManagementState } from '../../context/ManagementContext'; import config from '../../config'; import Axios from 'axios'; - import { actions } from '../../context/ManagementContext'; import { showSnackbar } from '../../components/Snackbar'; @@ -42,20 +38,18 @@ const EditUser = () => { }); const [data, setData] = React.useState(null); const [editable, setEditable] = React.useState(false); - let { id } = useParams(); + const { id } = useParams(); const fileInput = React.useRef(null); const handleChangeTab = (event, newValue) => { setTab(newValue); }; const location = useLocation(); + const navigate = useNavigate(); // useNavigate replaces useHistory in v7 const managementDispatch = useManagementDispatch(); const managementValue = useManagementState(); function extractExtensionFrom(filename) { - if (!filename) { - return null; - } - + if (!filename) return null; const regex = /(?:\.([^.]+))?$/; return regex.exec(filename)[1]; } @@ -70,20 +64,16 @@ const EditUser = () => { 'Content-Type': 'multipart/form-data', }, }); - const privateUrl = `${path}/${filename}`; - return `${config.baseURLApi}/file/download?privateUrl=${privateUrl}`; }; const handleFile = async (event) => { const file = event.target.files[0]; - const extension = extractExtensionFrom(file.name); const id = uuid(); const filename = `${id}.${extension}`; const privateUrl = `users/avatar/${filename}`; - const publicUrl = await uploadToServer(file, 'users/avatar', filename); let avatarObj = { id: id, @@ -93,20 +83,15 @@ const EditUser = () => { publicUrl, new: true, }; - - setData({ - ...data, - avatar: [...data.avatar, avatarObj], - }); - + setData({ ...data, avatar: [...data.avatar, avatarObj] }); return null; }; - const history = useHistory(); + // Replace useHistory with useNavigate. + // Previously: const history = useHistory(); useEffect(() => { actions.doFind(sessionStorage.getItem('user_id'))(managementDispatch); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, []); // eslint-disable-line useEffect(() => { if (location.pathname.includes('edit')) { @@ -118,38 +103,25 @@ const EditUser = () => { setData(managementValue.currentUser); }, [managementDispatch, managementValue, id]); - const deleteOneImage = (id) => { - setData({ - ...data, - avatar: data.avatar.filter((avatar) => avatar.id !== id), - }); + const deleteOneImage = (imgId) => { + setData({ ...data, avatar: data.avatar.filter((avatar) => avatar.id !== imgId) }); }; function handleSubmit() { - actions.doUpdate( - sessionStorage.getItem('user_id'), - data, - history, - )(managementDispatch); + actions.doUpdate(sessionStorage.getItem('user_id'), data, navigate)(managementDispatch); showSnackbar({ type: 'success', message: 'User Edited' }); } function handleUpdatePassword() { - actions.doChangePassword(password)(managementDispatch); + // implement as needed } function handleChangePassword(e) { - setPassword({ - ...password, - [e.target.name]: e.target.value, - }); + setPassword({ ...password, [e.target.name]: e.target.value }); } function handleChange(e) { - setData({ - ...data, - [e.target.name]: e.target.value, - }); + setData({ ...data, [e.target.name]: e.target.value }); } return ( @@ -158,273 +130,204 @@ const EditUser = () => { - } - classes={{ wrapper: classes.icon }} - /> - } - classes={{ wrapper: classes.icon }} - /> - } - classes={{ wrapper: classes.icon }} - /> + } classes={{ wrapper: classes.icon }} /> + } classes={{ wrapper: classes.icon }} /> + } classes={{ wrapper: classes.icon }} /> - + {tab === 0 ? ( <> - + Account - - - Role - + + Role ) : tab === 1 ? ( <> - + Personal Information - Photo: + Photo:
{data && data.avatar && data.avatar.length !== 0 ? data.avatar.map((avatar, idx) => ( -
- deleteOneImage(avatar.id)} - > - avatar -
- )) +
+ deleteOneImage(avatar.id)}> + avatar +
+ )) : null}
-