Skip to content

Commit 1cf8895

Browse files
committed
Added Redux Store and login/logout feature
1 parent b835b4b commit 1cf8895

File tree

13 files changed

+634
-391
lines changed

13 files changed

+634
-391
lines changed

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@monaco-editor/react": "^4.5.1",
14+
"@reduxjs/toolkit": "^1.9.5",
1415
"@tailwindcss/typography": "^0.5.9",
1516
"framer-motion": "^10.12.16",
1617
"react": "^18.2.0",

web/src/app/hooks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
2+
import type { RootState, AppDispatch } from "./store"
3+
4+
// Use throughout your app instead of plain `useDispatch` and `useSelector`
5+
export const useAppDispatch: () => AppDispatch = useDispatch
6+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

web/src/app/store.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
2+
import counterReducer from "@/features/counter/CounterSlice"
3+
import userReducer from "@/features/user/UserSlice"
4+
5+
export const store = configureStore({
6+
reducer: {
7+
counter: counterReducer,
8+
user: userReducer,
9+
},
10+
})
11+
12+
export type AppDispatch = typeof store.dispatch
13+
export type RootState = ReturnType<typeof store.getState>
14+
export type AppThunk<ReturnType = void> = ThunkAction<
15+
ReturnType,
16+
RootState,
17+
unknown,
18+
Action<string>
19+
>

web/src/components/Forms/LoginForm.tsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,39 @@
1-
import {MouseEvent, useRef} from "react";
1+
import { MouseEvent, useRef } from "react";
22
import Logo from "../../assets/logo";
3-
import {Link} from "react-router-dom";
3+
import { Link } from "react-router-dom";
4+
import { login } from "@/features/user/UserSlice";
5+
import { useAppDispatch } from "@/app/hooks";
6+
import { useNavigate } from "react-router-dom";
47

58
const LoginForm = () => {
9+
const dispatch = useAppDispatch();
10+
const navigate = useNavigate();
611

7-
const emailRef:any = useRef();
8-
const passwordRef:any = useRef();
12+
const emailRef: any = useRef();
13+
const passwordRef: any = useRef();
914
const handleSubmit = (e: MouseEvent<HTMLElement>) => {
1015
e.preventDefault();
1116
const email = emailRef.current.value;
1217
const password = passwordRef.current.value;
13-
console.log(email, password);
18+
handleLogin(email, password);
19+
};
20+
21+
const handleLogin = async (email: string, password: string) => {
22+
const response = await fetch("http://localhost:9000/api/login", {
23+
method: "POST",
24+
headers: {
25+
"Content-Type": "application/json",
26+
},
27+
body: JSON.stringify({ email, password }),
28+
});
29+
const data = await response.json();
30+
if (data.success) {
31+
localStorage.setItem("auth-token", data.authtoken);
32+
dispatch(login());
33+
navigate("/profile");
34+
} else {
35+
alert(data.error);
36+
}
1437
};
1538

1639
return (
@@ -22,9 +45,7 @@ const LoginForm = () => {
2245
className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white"
2346
>
2447
<Logo />
25-
<span className="text-2xl font-semibold text-blue-500">
26-
Log
27-
</span>
48+
<span className="text-2xl font-semibold text-blue-500">Log</span>
2849
in
2950
</a>
3051
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">

web/src/components/Navbar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { Link } from "react-router-dom";
22
import Logo from "../assets/logo";
3+
import { useAppSelector, useAppDispatch } from "@/app/hooks";
4+
import { selectUser ,logout} from "@/features/user/UserSlice";
5+
import { useEffect } from "react";
36

47
const Navbar = () => {
8+
const user = useAppSelector(selectUser);
9+
const dispatch = useAppDispatch();
10+
useEffect(() => {
11+
console.log(user.isLogged);
12+
}, [user.isLogged]);
513
return (
614
<>
715
<header className="header z-10 sticky top-0 bg-slate-100 dark:bg-dark1 dark:text-white shadow-md flex items-center justify-between px-8 py-02">
816
{/* logo */}
9-
<Link to="/" className="md:w-3/12 w-4/12 p-2 flex justify-start my-auto">
17+
<Link to="/" className="md:w-3/12 w-6/12 p-2 flex justify-start my-auto">
1018
{/* <img className="fill-blue-400" height={50} width={30} src="/logo.svg" /> */}
1119
<Logo />
1220
<span
@@ -32,9 +40,15 @@ const Navbar = () => {
3240
</nav>
3341
{/* buttons -*/}
3442
<div className="w-6/12 md:w-3/12 flex justify-end">
43+
{!user.isLogged ? (
3544
<Link to="/login" className="dark:bg-dark3 dark:hover:bg-dark4 bg-slate-200 hover:bg-slate-300 dark:text-white rounded-lg shadow-md px-4 py-2 font-semibold">
3645
Login
3746
</Link>
47+
) : (
48+
<button onClick={()=> dispatch(logout())} className="dark:bg-dark3 dark:hover:bg-dark4 bg-slate-200 hover:bg-slate-300 dark:text-white rounded-lg shadow-md px-4 py-2 font-semibold">
49+
Logout
50+
</button>
51+
)}
3852
</div>
3953
</header>
4054
</>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// A mock function to mimic making an async request for data
2+
export function fetchCount(amount = 1) {
3+
return new Promise<{ data: number }>((resolve) =>
4+
setTimeout(() => resolve({ data: amount }), 500),
5+
)
6+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"
2+
import { RootState, AppThunk } from "../../app/store"
3+
import { fetchCount } from "./CounterAPI"
4+
5+
export interface CounterState {
6+
value: number
7+
status: "idle" | "loading" | "failed"
8+
}
9+
10+
const initialState: CounterState = {
11+
value: 0,
12+
status: "idle",
13+
}
14+
15+
// The function below is called a thunk and allows us to perform async logic. It
16+
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
17+
// will call the thunk with the `dispatch` function as the first argument. Async
18+
// code can then be executed and other actions can be dispatched. Thunks are
19+
// typically used to make async requests.
20+
// export const incrementAsync = createAsyncThunk(
21+
// "counter/fetchCount",
22+
// async (amount: number) => {
23+
// const response = await fetchCount(amount)
24+
// // The value we return becomes the `fulfilled` action payload
25+
// return response.data
26+
// },
27+
// )
28+
29+
export const counterSlice = createSlice({
30+
name: "counter",
31+
initialState,
32+
// The `reducers` field lets us define reducers and generate associated actions
33+
reducers: {
34+
increment: (state) => {
35+
// Redux Toolkit allows us to write "mutating" logic in reducers. It
36+
// doesn't actually mutate the state because it uses the Immer library,
37+
// which detects changes to a "draft state" and produces a brand new
38+
// immutable state based off those changes
39+
console.log("increment");
40+
state.value += 1
41+
},
42+
decrement: (state) => {
43+
state.value -= 1
44+
},
45+
// Use the PayloadAction type to declare the contents of `action.payload`
46+
incrementByAmount: (state, action: PayloadAction<number>) => {
47+
state.value += action.payload
48+
},
49+
},
50+
// The `extraReducers` field lets the slice handle actions defined elsewhere,
51+
// including actions generated by createAsyncThunk or in other slices.
52+
// extraReducers: (builder) => {
53+
// builder
54+
// .addCase(incrementAsync.pending, (state) => {
55+
// state.status = "loading"
56+
// })
57+
// .addCase(incrementAsync.fulfilled, (state, action) => {
58+
// state.status = "idle"
59+
// state.value += action.payload
60+
// })
61+
// .addCase(incrementAsync.rejected, (state) => {
62+
// state.status = "failed"
63+
// })
64+
// },
65+
})
66+
67+
export const { increment, decrement, incrementByAmount } = counterSlice.actions
68+
69+
// The function below is called a selector and allows us to select a value from
70+
// the state. Selectors can also be defined inline where they're used instead of
71+
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
72+
export const selectCount = (state: RootState) => state.counter.value
73+
74+
// We can also write thunks by hand, which may contain both sync and async logic.
75+
// Here's an example of conditionally dispatching actions based on current state.
76+
// export const incrementIfOdd =
77+
// (amount: number): AppThunk =>
78+
// (dispatch, getState) => {
79+
// const currentValue = selectCount(getState())
80+
// if (currentValue % 2 === 1) {
81+
// dispatch(incrementByAmount(amount))
82+
// }
83+
// }
84+
85+
export default counterSlice.reducer

web/src/features/user/UserAPI.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const fetchUser = async () => {
2+
const authToken = localStorage.getItem("auth-token");
3+
};
4+
5+
const isLoggedin = () => {
6+
const authToken = localStorage.getItem("auth-token");
7+
if(authToken==null||authToken==undefined) {
8+
return false;
9+
}
10+
if (authToken) {
11+
return true;
12+
}
13+
return false;
14+
};
15+
16+
export { fetchUser, isLoggedin };

web/src/features/user/UserSlice.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
2+
import { RootState, AppThunk } from "../../app/store";
3+
import { User } from "@/models/user.type";
4+
import { isLoggedin } from "./UserAPI";
5+
6+
const initialState: User = {
7+
_id: "",
8+
name: "",
9+
email: "",
10+
branch: "",
11+
avatar: "",
12+
registrationNumber: "",
13+
sectionCode: "",
14+
isLogged: isLoggedin(),
15+
};
16+
17+
export const userSlice = createSlice({
18+
name: "user",
19+
initialState,
20+
reducers: {
21+
setUser: (state, action: PayloadAction<User>) => {
22+
state = action.payload;
23+
},
24+
logout: (state) => {
25+
localStorage.removeItem("auth-token");
26+
state.isLogged = false;
27+
},
28+
login: (state) => {
29+
state.isLogged = true;
30+
},
31+
},
32+
extraReducers: (builder) => {
33+
}
34+
});
35+
36+
export const { setUser, logout, login } = userSlice.actions;
37+
38+
export const selectUser = (state: RootState) => state.user;
39+
40+
export default userSlice.reducer;

web/src/main.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom/client'
3-
import App from './App.tsx'
3+
import App from '@/App'
4+
import { store } from "@/app/store"
45
import './index.css'
6+
import { Provider } from "react-redux"
57

68
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
79
<React.StrictMode>
8-
<App />
10+
<Provider store={store}>
11+
<App />
12+
</Provider>
913
</React.StrictMode>,
1014
)

0 commit comments

Comments
 (0)