diff --git a/package-lock.json b/package-lock.json
index b7dad75..b4b82a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.50.1",
"react-redux": "^9.2.0",
- "react-router-dom": "^6.22.1",
+ "react-router-dom": "^7.6.1",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -1108,15 +1108,6 @@
}
}
},
- "node_modules/@remix-run/router": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
- "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
@@ -2142,6 +2133,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3866,35 +3866,41 @@
}
},
"node_modules/react-router": {
- "version": "6.30.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
- "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==",
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz",
+ "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.0"
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=20.0.0"
},
"peerDependencies": {
- "react": ">=16.8"
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
}
},
"node_modules/react-router-dom": {
- "version": "6.30.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz",
- "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==",
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.1.tgz",
+ "integrity": "sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.0",
- "react-router": "6.30.0"
+ "react-router": "7.6.1"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=20.0.0"
},
"peerDependencies": {
- "react": ">=16.8",
- "react-dom": ">=16.8"
+ "react": ">=18",
+ "react-dom": ">=18"
}
},
"node_modules/read-cache": {
@@ -4066,6 +4072,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index 79c59bb..538d498 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.50.1",
"react-redux": "^9.2.0",
- "react-router-dom": "^6.22.1",
+ "react-router-dom": "^7.6.1",
"zod": "^3.22.4"
},
"devDependencies": {
diff --git a/src/App.jsx b/src/App.jsx
deleted file mode 100644
index 129a9e7..0000000
--- a/src/App.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Routes, Route } from "react-router-dom";
-import Login from "./pages/Login";
-import Register from "./pages/Register";
-import CreateNote from "./pages/CreateNote";
-import ViewNotes from "./pages/ViewNotes";
-import Navbar from "./components/Navbar";
-
-const App = () => {
- return (
-
-
-
-
-
- } />
- } />
-
- } />
- } />
-
-
-
- );
-};
-
-export default App;
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 9bd2bc8..c412a85 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -25,6 +25,8 @@ const Navbar = () => {
+
+
{
-
+
+ {isAuthenticated ? (
+ <>
+
Create
@@ -49,11 +53,10 @@ const Navbar = () => {
View All
@@ -66,15 +69,16 @@ const Navbar = () => {
Logout
-
-
+ >
+ ) : (
+ <>
+
Login
@@ -82,16 +86,16 @@ const Navbar = () => {
Register
-
+ >
+ )}
diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx
index 0c71b6c..daa7fc3 100644
--- a/src/components/auth/ProtectedRoute.jsx
+++ b/src/components/auth/ProtectedRoute.jsx
@@ -2,10 +2,10 @@ import { Navigate, useLocation } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoute = ({ children, requireAuth }) => {
- const { isAuthenticated, loading } = useSelector((state) => state.auth);
+ const { isAuthenticated, status} = useSelector((state) => state.auth);
// Show loading state while checking authentication
- if (loading) {
+ if (status === "loading") {
return (
@@ -17,9 +17,15 @@ const ProtectedRoute = ({ children, requireAuth }) => {
}
// TODO: If route requires authentication and user is not authenticated, redirect to login
+ if (requireAuth && !isAuthenticated){
+
+ }
//TODO: If route requires unauthenticated user and user is authenticated, redirect to notes
+ if (!requireAuth && isAuthenticated){
+
+ }
// Otherwise, render the children
diff --git a/src/main.jsx b/src/main.jsx
index 3d23c77..3ca2b7b 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -3,15 +3,17 @@ import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import store from "./store";
-import App from "./App.jsx";
+import App from "./store/App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
-
+
);
+
+
diff --git a/src/main.tsx b/src/main.tsx
index ea9e363..302d376 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,6 +1,6 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
-import App from './App.tsx';
+import App from './store/App.jsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 4f6d4f7..b73b6a8 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -1,12 +1,47 @@
+import React from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import {login} from "../store/slices/authSlice";
+import {loginSchema} from "../schema/authSchema";
+
const Login = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const { status, error } = useSelector((state) => state.auth);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ });
+
+ const onSubmit = async (data) => {
+ try {
+ await dispatch(login(data)).unwrap();
+ navigate("/notes");
+ } catch (err) {
+ console.error("Failed to login:", err);
+ }
+ };
+
+
return (
Login to Your Account
+ {error && (
+
+ {error}
+
+ )}
-
diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx
index ba6f0bd..c89aa2f 100644
--- a/src/pages/Register.jsx
+++ b/src/pages/Register.jsx
@@ -1,4 +1,35 @@
+import React from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import { registerUser } from "../store/slices/authSlice";
+import { registerSchema } from "../schema/authSchema";
+
+
const Register = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const { status, error } = useSelector((state) => state.auth);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(registerSchema),
+ });
+
+ const onSubmit = async (data) => {
+ try {
+ await dispatch(registerUser(data)).unwrap();
+ navigate("/login");
+ } catch (err) {
+ console.error("Failed to register:", err);
+ }
+ };
+
return (
@@ -6,7 +37,33 @@ const Register = () => {
Create an Account
-
diff --git a/src/schema/authSchema.js b/src/schema/authSchema.js
index 9a739d6..2fee15f 100644
--- a/src/schema/authSchema.js
+++ b/src/schema/authSchema.js
@@ -7,14 +7,15 @@ export const loginSchema = z.object({
export const registerSchema = z
.object({
+ name: z.string().min(1, "Full name is required"),
email: z.string().min(1, "Email is required").email("Invalid email format"),
password: z
.string()
- .min(8, "Password must be at least 8 characters")
- .regex(
- /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
- "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
- ),
+ .min(8, "Password must be at least 8 characters"),
+ // .regex(
+ // /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
+ // "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
+ // ),
confirmPassword: z.string().min(1, "Please confirm your password"),
})
.refine((data) => data.password === data.confirmPassword, {
diff --git a/src/store/App.jsx b/src/store/App.jsx
new file mode 100644
index 0000000..843102d
--- /dev/null
+++ b/src/store/App.jsx
@@ -0,0 +1,44 @@
+import { Routes, Route } from "react-router-dom";
+import Login from "../pages/Login";
+import Register from "../pages/Register";
+import CreateNote from "../pages/CreateNote";
+import ViewNotes from "../pages/ViewNotes";
+import Navbar from "../components/Navbar";
+import ProtectedRoute from "../components/auth/ProtectedRoute";
+
+const App = () => {
+ return (
+
+
+
+
+
+
+
+
+ } />
+
+
+
+ } />
+
+
+
+
+
+ } />
+
+
+
+ } />
+
+
+
+ );
+};
+
+export default App;
diff --git a/src/store/slices/authSlice.js b/src/store/slices/authSlice.js
index 35778d0..a9c48e9 100644
--- a/src/store/slices/authSlice.js
+++ b/src/store/slices/authSlice.js
@@ -13,9 +13,16 @@ export const checkAuthStatus = createAsyncThunk(
// 1. Make a GET request to /auth/check
// 2. Return the response data
// 3. Handle errors appropriately
- }
-);
-
+ try {
+ const response = await axios.get(`${BASE_URL}/auth/check`)
+ return response.data.user;
+ }
+ catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message
+ );
+ }
+ });
// TODO: Implement login thunk
export const login = createAsyncThunk(
"auth/login",
@@ -24,19 +31,36 @@ export const login = createAsyncThunk(
// 1. Make a POST request to /auth/login with credentials
// 2. Return the response data
// 3. Handle errors appropriately
- }
-);
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/login`, credentials)
+ return response.data.user;
+ }
+ catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message
+ );
+ }
+ });
// TODO: Implement register thunk
-export const register = createAsyncThunk(
- "auth/register",
+export const registerUser = createAsyncThunk(
+ "auth/registerUser",
async (userData, { rejectWithValue }) => {
// TODO: Implement registration functionality
// 1. Make a POST request to /auth/register with userData
// 2. Return the response data
// 3. Handle errors appropriately
- }
-);
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/register`, userData)
+ console.log(`${BASE_URL}/auth/register`);
+ return response.data.user;
+ }
+ catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message
+ );
+ }
+ });
// TODO: Implement logout thunk
export const logout = createAsyncThunk(
@@ -45,14 +69,23 @@ export const logout = createAsyncThunk(
// TODO: Implement logout functionality
// 1. Make a POST request to /auth/logout
// 2. Handle errors appropriately
- }
-);
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/logout`)
+ }
+ catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message
+ );
+ }
+ });
+
const initialState = {
user: null,
isAuthenticated: false,
loading: true,
error: null,
+ status:"idle",
};
const authSlice = createSlice({
@@ -64,13 +97,66 @@ const authSlice = createSlice({
},
},
extraReducers: (builder) => {
- builder;
- // TODO: Add cases for checkAuthStatus
- // TODO: Add cases for login
- // TODO: Add cases for register
- // TODO: Add cases for logout
+ builder
+ // TODO: Add cases for checkAuthStatus
+
+ .addCase(checkAuthStatus.pending, (state) => {
+ state.status = "loading";
+ })
+ .addCase(checkAuthStatus.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.isAuthenticated = true;
+ state.user = action.payload;
+ state.error = null;
+ })
+ .addCase(checkAuthStatus.rejected, (state) => {
+ state.status = "idle";
+ state.isAuthenticated = false;
+ state.user = null;
+ })
+
+ // TODO: Add cases for login
+ .addCase(login.pending, (state) => {
+ state.status = "loading";
+ state.error = null;
+ })
+ .addCase(login.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.isAuthenticated = true;
+ state.user = action.payload;
+ state.error = null;
+ })
+ .addCase(login.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.payload;
+ })
+
+ // TODO: Add cases for register
+ .addCase(registerUser.pending, (state) => {
+ state.status = "loading";
+ state.error = null;
+ })
+ .addCase(registerUser.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.isAuthenticated = true;
+ state.user = action.payload;
+ state.error = null;
+ })
+ .addCase(registerUser.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.payload;
+ })
+
+ // TODO: Add cases for logout
+ .addCase(logout.fulfilled, (state) => {
+ state.status = "idle";
+ state.isAuthenticated = false;
+ state.user = null;
+ state.error = null;
+ });
},
});
+
export const { clearError } = authSlice.actions;
export default authSlice.reducer;