Merge pull request 'login setup' (#1) from feature/authentication into develop

Reviewed-on: DigiMantra/digiev_frontend#1
This commit is contained in:
Mohit kalshan 2025-01-21 07:38:43 +00:00
commit f67c9c17bc
16 changed files with 403 additions and 285 deletions

View file

@ -1,6 +1,8 @@
{ {
"trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": false, "useTabs": true,
"singleQuote": false "semi": true,
"singleQuote": false,
"bracketSpacing": true
} }

View file

@ -32,6 +32,7 @@
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-cookie": "^7.2.2",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
@ -68,8 +69,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.5", "@types/node": "^22.10.5",
"@types/react": "^19.0.3", "@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2", "@types/react-dom": "^19.0.2",
"typescript": "^5.7.2" "typescript": "^5.7.3"
} }
} }

View file

@ -95,6 +95,9 @@ importers:
react: react:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.0.0 version: 19.0.0
react-cookie:
specifier: ^7.2.2
version: 7.2.2(react@19.0.0)
react-dom: react-dom:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.0.0(react@19.0.0) version: 19.0.0(react@19.0.0)
@ -127,13 +130,13 @@ importers:
specifier: ^22.10.5 specifier: ^22.10.5
version: 22.10.5 version: 22.10.5
'@types/react': '@types/react':
specifier: ^19.0.3 specifier: ^19.0.4
version: 19.0.4 version: 19.0.4
'@types/react-dom': '@types/react-dom':
specifier: ^19.0.2 specifier: ^19.0.2
version: 19.0.2(@types/react@19.0.4) version: 19.0.2(@types/react@19.0.4)
typescript: typescript:
specifier: ^5.7.2 specifier: ^5.7.3
version: 5.7.3 version: 5.7.3
packages: packages:
@ -1598,6 +1601,9 @@ packages:
'@types/graceful-fs@4.1.9': '@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
'@types/hoist-non-react-statics@3.3.6':
resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==}
'@types/html-minifier-terser@6.1.0': '@types/html-minifier-terser@6.1.0':
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
@ -2339,6 +2345,10 @@ packages:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
cookie@1.0.2: cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -4915,6 +4925,11 @@ packages:
resolution: {integrity: sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==} resolution: {integrity: sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==}
engines: {node: '>=14'} engines: {node: '>=14'}
react-cookie@7.2.2:
resolution: {integrity: sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg==}
peerDependencies:
react: '>= 16.3.0'
react-dev-utils@12.0.1: react-dev-utils@12.0.1:
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -5742,6 +5757,9 @@ packages:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'} engines: {node: '>=8'}
universal-cookie@7.2.2:
resolution: {integrity: sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==}
universalify@0.2.0: universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
@ -7883,6 +7901,11 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.10.5 '@types/node': 22.10.5
'@types/hoist-non-react-statics@3.3.6':
dependencies:
'@types/react': 19.0.4
hoist-non-react-statics: 3.3.2
'@types/html-minifier-terser@6.1.0': {} '@types/html-minifier-terser@6.1.0': {}
'@types/http-errors@2.0.4': {} '@types/http-errors@2.0.4': {}
@ -8744,6 +8767,8 @@ snapshots:
cookie@0.7.1: {} cookie@0.7.1: {}
cookie@0.7.2: {}
cookie@1.0.2: {} cookie@1.0.2: {}
core-js-compat@3.40.0: core-js-compat@3.40.0:
@ -11735,6 +11760,13 @@ snapshots:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
whatwg-fetch: 3.6.20 whatwg-fetch: 3.6.20
react-cookie@7.2.2(react@19.0.0):
dependencies:
'@types/hoist-non-react-statics': 3.3.6
hoist-non-react-statics: 3.3.2
react: 19.0.0
universal-cookie: 7.2.2
react-dev-utils@12.0.1(eslint@8.57.1)(typescript@5.7.3)(webpack@5.97.1): react-dev-utils@12.0.1(eslint@8.57.1)(typescript@5.7.3)(webpack@5.97.1):
dependencies: dependencies:
'@babel/code-frame': 7.26.2 '@babel/code-frame': 7.26.2
@ -12745,6 +12777,11 @@ snapshots:
dependencies: dependencies:
crypto-random-string: 2.0.0 crypto-random-string: 2.0.0
universal-cookie@7.2.2:
dependencies:
'@types/cookie': 0.6.0
cookie: 0.7.2
universalify@0.2.0: {} universalify@0.2.0: {}
universalify@2.0.1: {} universalify@2.0.1: {}

View file

@ -10,34 +10,12 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. <title>DigiEV - Eco-friendly Charge</title>
-->
<title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body> </body>
</html> </html>

View file

@ -1,12 +1,28 @@
import { BrowserRouter as Router} from 'react-router-dom'; import AppRouter from "./router";
import AppRouter from './router'; import { useSelector } from "react-redux";
import { useMatch, useNavigate, useSearchParams } from "react-router-dom";
import { useEffect } from "react";
import { RootState } from "./redux/store";
import { withCookies, ReactCookieProps } from "react-cookie";
function App() { const App: React.FC<ReactCookieProps> = ({ cookies }) => {
return ( const navigate = useNavigate();
<Router> const isPanel = useMatch("/auth/login");
<AppRouter /> const isCookiePresent = !!cookies?.get("authToken");
</Router> console.log("cookies present:", isCookiePresent);
const [searchParams] = useSearchParams();
const isAuthenticated = useSelector(
(state: RootState) => state.authReducer.isAuthenticated
); );
}
export default App; useEffect(() => {
if (isPanel && isAuthenticated) {
navigate("/panel/dashboard");
}
}, [isPanel, isAuthenticated, searchParams]);
return <AppRouter />;
};
export default withCookies(App);

View file

@ -11,6 +11,9 @@ import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded';
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'; import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded';
import MenuButton from '../MenuButton'; import MenuButton from '../MenuButton';
import { Avatar } from '@mui/material'; import { Avatar } from '@mui/material';
import { useDispatch } from 'react-redux';
import { logoutUser } from '../../redux/slices/authSlice';
import { useCookies } from 'react-cookie';
const MenuItem = styled(MuiMenuItem)({ const MenuItem = styled(MuiMenuItem)({
margin: '2px 0', margin: '2px 0',
@ -19,12 +22,20 @@ const MenuItem = styled(MuiMenuItem)({
export default function OptionsMenu({ avatar }: { avatar?: boolean }) { export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const dispatch = useDispatch();
const [cookies, setCookie, removeCookie] = useCookies(['authToken']);
const handleClick = (event: React.MouseEvent<HTMLElement>) => { const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
const handleLogout = () => {
dispatch(logoutUser({ removeCookie }));
console.log('click')
handleClose();
};
return ( return (
<React.Fragment> <React.Fragment>
<MenuButton <MenuButton
@ -70,7 +81,7 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
<MenuItem onClick={handleClose}>Settings</MenuItem> <MenuItem onClick={handleClose}>Settings</MenuItem>
<Divider /> <Divider />
<MenuItem <MenuItem
onClick={handleClose} onClick={handleLogout}
sx={{ sx={{
[`& .${listItemIconClasses.root}`]: { [`& .${listItemIconClasses.root}`]: {
ml: 'auto', ml: 'auto',

View file

@ -1,39 +1,22 @@
import { intersection, uniq } from 'lodash';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { checkUserAuth } from '../redux/slices/authSlice'; import { checkUserAuth } from '../redux/slices/authSlice';
const useAuth = (caps, { matchAllCaps = false }) => { const useAuth = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isAuthorized, setIsAuthorized] = React.useState(false); const [isAuthorized, setIsAuthorized] = React.useState(false);
const { isAuthenticated } = useSelector((state) => ({
const { isAuthenticated, userCapabilities } = useSelector((state) => ({
isAuthenticated: state.authReducer.isAuthenticated, isAuthenticated: state.authReducer.isAuthenticated,
userCapabilities: state.authReducer.userCapabilities,
})); }));
const requiredCaps = React.useMemo(
() => uniq(Array.isArray(caps) ? caps : [caps]),
[caps]
);
React.useEffect(() => { React.useEffect(() => {
const userMatchedCaps = intersection(userCapabilities, requiredCaps); if (isAuthenticated) {
let isUserAuthorized = matchAllCaps
? userMatchedCaps?.length === requiredCaps?.length
: userMatchedCaps?.length > 0;
if (requiredCaps.length === 0) {
isUserAuthorized = true;
}
// if (isAuthenticated === null) {
if (isAuthenticated === null || false || undefined) {
dispatch(checkUserAuth()); dispatch(checkUserAuth());
} else { } else {
setIsAuthorized(isAuthenticated && isUserAuthorized); setIsAuthorized(false);
} }
}, [dispatch, isAuthenticated, requiredCaps, userCapabilities]); }, [dispatch, isAuthenticated]);
return { isAuthenticated, isAuthorized }; return { isAuthenticated, isAuthorized };
}; };

View file

@ -1,29 +1,32 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom/client'; import ReactDOM from "react-dom/client";
import './index.css'; import "./index.css";
import reportWebVitals from './reportWebVitals'; import App from "./App";
import App from './App'; import { Provider } from "react-redux";
import { Provider } from 'react-redux'; import store from "./redux/store";
import store from './redux/store/store.ts'; import { Slide, ToastContainer } from "react-toastify";
import { Slide, ToastContainer } from 'react-toastify'; import { BrowserRouter as Router } from "react-router-dom";
import { CookiesProvider } from "react-cookie";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<CookiesProvider defaultSetOptions={{ path: "/" }}>
<Provider store={store}> <Provider store={store}>
<Router>
<App /> <App />
</Router>
<ToastContainer <ToastContainer
autoClose={2000} autoClose={2000}
hideProgressBar hideProgressBar
theme="dark" theme="dark"
transition={Slide} transition={Slide}
toastStyle={{ border: '1px solid dimgray' }} toastStyle={{ border: "1px solid dimgray" }}
/> />
</Provider> </Provider>
</CookiesProvider>
</React.StrictMode> </React.StrictMode>
); );
// 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();

View file

@ -1,16 +1,36 @@
import axios from 'axios'; import axios from "axios";
import { Cookies } from "react-cookie";
const cookies = new Cookies();
const http = axios.create({ const http = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL, baseURL: process.env.REACT_APP_BACKEND_URL,
}); });
console.log(process.env.REACT_APP_BACKEND_URL);
http.interceptors.request.use((config) => { http.interceptors.request.use((config) => {
const authToken = localStorage.getItem('authToken'); const authToken = cookies.get("authToken");
console.log(authToken);
if (authToken) { if (authToken) {
config.headers.Authorization = authToken; config.headers.Authorization = authToken;
} }
return config; return config;
}); });
http.interceptors.response.use(
(response) => response,
(error) => {
const isCookiePresent = cookies.get("authToken");
console.log(isCookiePresent,"jkk")
if (
error.response &&
isCookiePresent &&
(error.response.status === 403 || error.response.status === 401)
) {
cookies.remove("authToken", { path: "/" });
window.location.href = "/";
}
return Promise.reject(error);
}
);
export default http; export default http;

View file

@ -0,0 +1,9 @@
import React from 'react'
function NotFoundPage() {
return (
<div>NotFoundPage</div>
)
}
export default NotFoundPage;

View file

@ -19,7 +19,7 @@ const categoryRows = [
]; ];
export default function Vehicles() { export default function Vehicles() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null); const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm(); const { reset } = useForm();

11
src/redux/reducers.ts Normal file
View file

@ -0,0 +1,11 @@
import { combineReducers } from "@reduxjs/toolkit";
import authReducer from "./slices/authSlice";
const rootReducer = combineReducers({
authReducer,
});
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

View file

@ -1,21 +1,37 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit" import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import axios from "axios" import axios from "axios";
import http from "../../lib/https" import http from "../../lib/https";
import { toast } from "react-toastify" import { toast } from "react-toastify";
import { Cookies } from "react-cookie";
const cookies = new Cookies();
// Define types for state // Define types for state
interface User { interface User {
id: string id: string;
email: string email: string;
} }
interface AuthState { interface AuthState {
user: User | null user: User | null;
isAuthenticated: boolean isAuthenticated: boolean;
isLoading: boolean isLoading: boolean;
error: string | null error: string | null;
} }
export const checkUserAuth = createAsyncThunk<
boolean,
void,
{ rejectValue: any }
>("application/checkUserAuth", async (_, thunkAPI) => {
try {
const isCookiePresent = cookies.get("authToken");
if (!isCookiePresent) return thunkAPI.rejectWithValue(null);
return thunkAPI.fulfillWithValue(true);
} catch (error) {
console.log(error);
return thunkAPI.rejectWithValue(error);
}
});
// Async thunk for login // Async thunk for login
export const loginUser = createAsyncThunk< export const loginUser = createAsyncThunk<
User, User,
@ -23,16 +39,16 @@ export const loginUser = createAsyncThunk<
{ rejectValue: string } { rejectValue: string }
>("auth/login", async ({ email, password }, { rejectWithValue }) => { >("auth/login", async ({ email, password }, { rejectWithValue }) => {
try { try {
const response = await http.post("admin/login", { email, password }) const response = await http.post("admin/login", { email, password });
localStorage.setItem("authToken", response.data?.data?.token) // Save token cookies.set("authToken", response.data?.data?.token);
toast.success(response.data?.message) toast.success(response.data?.message);
return response.data return response.data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
) );
} }
}) });
// Async thunk for register // Async thunk for register
export const registerUser = createAsyncThunk< export const registerUser = createAsyncThunk<
@ -44,75 +60,101 @@ export const registerUser = createAsyncThunk<
const response = await axios.post( const response = await axios.post(
"https://health-digi.dmlabs.in/auth/register", "https://health-digi.dmlabs.in/auth/register",
data data
) );
return response.data return response.data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
) );
} }
}) });
export const logoutUser = createAsyncThunk<
void,
{ removeCookie: any },
{ rejectValue: string }
>("auth/logout", async ({ removeCookie }, { rejectWithValue }) => {
try {
removeCookie("authToken", { path: "/auth" });
toast.success("You have been logged out successfully.");
} catch (error: any) {
return rejectWithValue(
error.response?.data?.message || "Failed to log out."
);
}
});
const initialState: AuthState = { const initialState: AuthState = {
user: null, user: null,
isAuthenticated: false, isAuthenticated: false,
isLoading: false, isLoading: false,
error: null, error: null,
} };
const authSlice = createSlice({ const authSlice = createSlice({
name: "auth", name: "auth",
initialState, initialState,
reducers: { reducers: {
logout: (state) => { logout: (state) => {
state.user = null state.user = null;
state.isAuthenticated = false state.isAuthenticated = false;
}, },
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
// Login // Login
.addCase(loginUser.pending, (state) => { .addCase(loginUser.pending, (state) => {
state.isLoading = true state.isLoading = true;
state.error = null state.error = null;
}) })
.addCase( .addCase(
loginUser.fulfilled, loginUser.fulfilled,
(state, action: PayloadAction<User>) => { (state, action: PayloadAction<User>) => {
state.isLoading = false state.isLoading = false;
state.isAuthenticated = true state.isAuthenticated = true;
state.user = action.payload state.user = action.payload;
} }
) )
.addCase( .addCase(
loginUser.rejected, loginUser.rejected,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string | undefined>) => {
state.isLoading = false state.isLoading = false;
state.error = action.payload || "An error occurred" state.error = action.payload || "An error occurred";
} }
) )
// Register // Register
.addCase(registerUser.pending, (state) => { .addCase(registerUser.pending, (state) => {
state.isLoading = true state.isLoading = true;
state.error = null state.error = null;
}) })
.addCase( .addCase(
registerUser.fulfilled, registerUser.fulfilled,
(state, action: PayloadAction<User>) => { (state, action: PayloadAction<User>) => {
state.isLoading = false state.isLoading = false;
state.isAuthenticated = true state.isAuthenticated = true;
state.user = action.payload state.user = action.payload;
} }
) )
.addCase( .addCase(
registerUser.rejected, registerUser.rejected,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string | undefined>) => {
state.isLoading = false state.isLoading = false;
state.error = action.payload || "An error occurred" state.error = action.payload || "An error occurred";
} }
) )
}, // Logout
.addCase(logoutUser.fulfilled, (state) => {
state.user = null;
state.isAuthenticated = false;
}) })
.addCase(
logoutUser.rejected,
(state, action: PayloadAction<string | undefined>) => {
state.error =
action.payload || "An error occurred during logout";
}
);
},
});
export const { logout } = authSlice.actions export default authSlice.reducer;
export default authSlice.reducer

View file

@ -1,13 +1,10 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import authReducer from '../slices/authSlice.ts'
const store = configureStore({
reducer: {
auth: authReducer,
},
});
import rootReducer from './reducers';
export const store = configureStore({
reducer: rootReducer,
})
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;
export default store; export default store;

View file

@ -1,22 +1,29 @@
import { Routes as BaseRoutes, Navigate, Route } from 'react-router-dom'; import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom";
// import useAuth from "./hooks/useAuth"; import { Suspense } from "react";
import React, { Suspense } from 'react'; import LoadingComponent from "./components/Loading";
import LoadingComponent from './components/Loading'; import DashboardLayout from "./layouts/DashboardLayout";
import DashboardLayout from './layouts/DashboardLayout'; import Login from "./pages/Auth/Login";
import Login from './pages/Auth/Login'; import SignUp from "./pages/Auth/SignUp";
import SignUp from './pages/Auth/SignUp'; import Dashboard from "./pages/Dashboard";
import Dashboard from './pages/Dashboard'; import Vehicles from "./pages/Vehicles";
import Vehicles from './pages/Vechiles'; import { useSelector } from "react-redux";
import { RootState } from "./redux/reducers";
import { useCookies } from "react-cookie";
function ProtectedRoute({ interface ProtectedRouteProps {
caps, component: JSX.Element;
component, cookies?: { get: (key: string) => string | null };
}: { }
caps: string[]; function ProtectedRoute({ component }: ProtectedRouteProps): JSX.Element {
component: React.ReactNode; const [cookies] = useCookies(["authToken"]);
}) { const isCookiePresent = !!cookies?.authToken;
if (!localStorage.getItem('authToken')) const isAuthenticated = useSelector(
return <Navigate to={`/auth/login`} replace />; (state: RootState) => state.authReducer.isAuthenticated
);
if (!isAuthenticated && !isCookiePresent) {
return <Navigate to="/auth/login" replace />;
}
return component; return component;
} }
@ -39,11 +46,11 @@ export default function AppRouter() {
<Route path="/panel" element={<DashboardLayout />}> <Route path="/panel" element={<DashboardLayout />}>
<Route <Route
path="dashboard" path="dashboard"
element={<ProtectedRoute caps={[]} component={<Dashboard />} />} element={<ProtectedRoute component={<Dashboard />} />}
/> />
<Route <Route
path="vehicles" path="vehicles"
element={<ProtectedRoute caps={[]} component={<Vehicles />} />} element={<ProtectedRoute component={<Vehicles />} />}
/> />
<Route path="*" element={<>404</>} /> <Route path="*" element={<>404</>} />
</Route> </Route>

View file

@ -10,10 +10,11 @@
], ],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"jsx": "react-jsx",
"types": ["react", "react-dom"],
"lib": ["dom", "dom.iterable", "esnext"],
"paths": { "paths": {
"@/*": [ "@/*": ["./src/*"]
"./src/*"
]
} }
} }
} }