diff --git a/src/App.jsx b/src/App.jsx
index 129a9e7..541be20 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,4 +1,9 @@
import { Routes, Route } from "react-router-dom";
+import { useEffect } from "react";
+import ProtectedRoute from "./components/auth/ProtectedRoute";
+import { useDispatch } from "react-redux";
+import { checkAuthStatus } from "./store/slices/authSlice";
+// pages
import Login from "./pages/Login";
import Register from "./pages/Register";
import CreateNote from "./pages/CreateNote";
@@ -6,18 +11,58 @@ import ViewNotes from "./pages/ViewNotes";
import Navbar from "./components/Navbar";
const App = () => {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(checkAuthStatus());
+ }, [dispatch]);
+
return (
-
-
- } />
- } />
+ {/*
+ } />
+ } />
+
+ } />
+ } />
+ */}
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- } />
- } />
-
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
);
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 9bd2bc8..904c4b6 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -34,7 +34,8 @@ const Navbar = () => {
-
+ {isAuthenticated ? (
+ <>
{
Logout
-
-
+ >
+ ) : (
+ <>
{
Register
-
+ >
+ )}
diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx
index 0c71b6c..d13248a 100644
--- a/src/components/auth/ProtectedRoute.jsx
+++ b/src/components/auth/ProtectedRoute.jsx
@@ -2,7 +2,8 @@ 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);
+ const loading = status === "loading";
// Show loading state while checking authentication
if (loading) {
@@ -17,10 +18,16 @@ const ProtectedRoute = ({ children, requireAuth }) => {
}
// TODO: If route requires authentication and user is not authenticated, redirect to login
-
+
+ if (requireAuth && !isAuthenticated) {
+ return ;
+ }
//TODO: If route requires unauthenticated user and user is authenticated, redirect to notes
+ if (!requireAuth && isAuthenticated) {
+ return ;
+ }
// Otherwise, render the children
return children;
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 4f6d4f7..ba4e0fc 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -1,4 +1,48 @@
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useDispatch, useSelector } from "react-redux";
+import { useEffect } from "react";
+import { Link } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
+import { login, clearError } from "../store/slices/authSlice";
+import { loginSchema } from "../schema/authSchema";
+
const Login = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ const { isAuthenticated, loading, error } = useSelector(
+ (state) => state.auth
+ );
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const onSubmit = (data) => {
+ dispatch(login(data));
+ };
+
+ useEffect(() => {
+ if (isAuthenticated) {
+ navigate("/notes");
+ }
+ }, [isAuthenticated, navigate]);
+
+ useEffect(() => {
+ return () => {
+ dispatch(clearError());
+ };
+ }, [dispatch]);
+
return (
@@ -6,7 +50,7 @@ const Login = () => {
Login to Your Account
-
Don't have an account?{" "}
-
+
Register here
-
+
diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx
index ba6f0bd..29eb58a 100644
--- a/src/pages/Register.jsx
+++ b/src/pages/Register.jsx
@@ -1,4 +1,46 @@
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate, Link } from "react-router-dom";
+import { useEffect } from "react";
+// import { Link } from "react-router-dom";
+import { registerSchema } from "../schema/authSchema";
+import {
+ register as registerThunk,
+ clearError,
+} from "../store/slices/authSlice";
+
const Register = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ const { isAuthenticated, loading, error } = useSelector(
+ (state) => state.auth
+ );
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ reset,
+ } = useForm({
+ resolver: zodResolver(registerSchema),
+ });
+
+ const onSubmit = async (data) => {
+ const resultAction = await dispatch(registerThunk(data));
+ if (registerThunk.fulfilled.match(resultAction)) {
+ reset();
+ navigate("/notes");
+ }
+ };
+
+ useEffect(() => {
+ return () => {
+ dispatch(clearError());
+ };
+ }, [dispatch]);
+
return (
@@ -6,7 +48,7 @@ const Register = () => {
Create an Account
-
Already have an account?{" "}
-
+
Login here
-
+
diff --git a/src/store/slices/authSlice.js b/src/store/slices/authSlice.js
index 35778d0..72e9a3f 100644
--- a/src/store/slices/authSlice.js
+++ b/src/store/slices/authSlice.js
@@ -13,6 +13,12 @@ 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?.error || error.message);
+ }
}
);
@@ -24,6 +30,15 @@ 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) {
+ if (error.response?.data?.error) {
+ return rejectWithValue(error.response?.data?.error || error.message);
+ }
+ throw error;
+ }
}
);
@@ -35,6 +50,12 @@ export const register = createAsyncThunk(
// 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);
+ return response.data.user;
+ } catch (error) {
+ return rejectWithValue(error.response?.data?.error || error.message);
+ }
}
);
@@ -45,13 +66,19 @@ export const logout = createAsyncThunk(
// TODO: Implement logout functionality
// 1. Make a POST request to /auth/logout
// 2. Handle errors appropriately
+ try {
+ await axios.post(`${BASE_URL}/auth/logout`);
+ } catch (error) {
+ return rejectWithValue(error.response?.data?.error || error.message);
+ }
}
);
const initialState = {
user: null,
isAuthenticated: false,
- loading: true,
+ // loading: true,
+ status: "idle",
error: null,
};
@@ -64,11 +91,63 @@ 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(register.pending, (state) => {
+ state.status = "loading";
+ state.error = null;
+ })
+ .addCase(register.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.isAuthenticated = true;
+ state.user = action.payload;
+ state.error = null;
+ })
+ .addCase(register.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message;
+ })
+ // TODO: Add cases for logout
+ .addCase(logout.pending, (state) => {
+ state.status = "loading";
+ state.error = null;
+ })
+ .addCase(logout.fulfilled, (state) => {
+ state.status = "idle";
+ state.isAuthenticated = false;
+ state.user = null;
+ state.error = null;
+ });
},
});