From 44083140d70a15be3350025f9c3f873f17368766 Mon Sep 17 00:00:00 2001 From: abdiqadirabdikrin Date: Wed, 17 Sep 2025 01:04:05 -0700 Subject: [PATCH] completed --- src/App.jsx | 61 +++++++++++- src/components/Navbar.jsx | 13 ++- src/components/auth/ProtectedRoute.jsx | 6 ++ src/pages/Login.jsx | 54 ++++++++++- src/pages/Register.jsx | 61 +++++++++++- src/store/slices/authSlice.js | 129 ++++++++++++++++++++++++- 6 files changed, 306 insertions(+), 18 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 129a9e7..d41fc7d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,19 +4,72 @@ 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"; +import CreateNoteForm from "./components/CreateNoteForm"; +import NoteCard from "./components/NoteCard"; +import { useEffect } from "react"; +import { checkAuthStatus } from "./store/slices/authSlice"; +import { useDispatch } from "react-redux"; + + const App = () => { + + + const dispatch = useDispatch(); + // const { status } = useSelector((state) => state.auth); + + useEffect(() => { + dispatch(checkAuthStatus()); + }, [dispatch]); + + return (
- } /> - } /> + } /> + } /> + + + + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + - } /> - } />
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 9bd2bc8..a52a6b8 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -46,7 +46,10 @@ const Navbar = () => { Create - + + {isAuthenticated ? ( + <> + { Logout - + + ) : ( + <> { Register - + + )} + diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx index 0c71b6c..9983573 100644 --- a/src/components/auth/ProtectedRoute.jsx +++ b/src/components/auth/ProtectedRoute.jsx @@ -17,9 +17,15 @@ const ProtectedRoute = ({ children, requireAuth }) => { } // TODO: If route requires authentication and user is not authenticated, redirect to login + if (requireAuth && !isAuthenticated) { + return < Navigate to ="/login" replace />; + } //TODO: If route requires unauthenticated user and user is authenticated, redirect to notes + if (!requireAuth && isAuthenticated) { + return ; + } // Otherwise, render the children diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4f6d4f7..a0f0f80 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,4 +1,44 @@ +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 { login } from "../store/slices/authSlice"; +import { loginSchema } from "../schema/authSchema"; +import { Link } from "react-router-dom"; + + const Login = () => { + const dispatch = useDispatch(); + const { status, error } = useSelector((state) => state.auth); + const navigate = useNavigate(); + + //create react hook form + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(loginSchema), + }); + + + //create onSubmit function + + const onSubmit = async (data) => { + try{ + await dispatch(login(data)).unwrap(); + navigate("/view-notes"); + + }catch (error){ + console.log(error); + } +}; + + + + return (
@@ -6,7 +46,7 @@ const Login = () => { Login to Your Account -
+
+
diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx index ba6f0bd..e26a086 100644 --- a/src/pages/Register.jsx +++ b/src/pages/Register.jsx @@ -1,4 +1,39 @@ +import React from "react"; +import { useForm } from "react-hook-form"; +import { useDispatch, useSelector } from "react-redux"; +import axios from "axios"; +import { useNavigate } from "react-router-dom"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { registerUser } from "../store/slices/authSlice"; +import { registerSchema } from "../schema/authSchema"; +import { Link } from "react-router-dom"; + const Register = () => { + const dispatch = useDispatch(); + const { status, error } = useSelector((state) => state.auth); + const navigate = useNavigate(); + + //creat react hook form + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(registerSchema), + }); + + //handle form submit + const onSubmit = async (data) => { + try { + await dispatch(registerUser(data)).unwrap(); + navigate("/view-notes"); + } catch (err) { + console.error("Failed to register:", err); + } + }; + return (
@@ -6,7 +41,7 @@ const Register = () => { Create an Account - +
@@ -32,9 +73,16 @@ const Register = () => { + {errors.password && ( +

+ {errors.password.message} +

+)} +
@@ -47,9 +95,16 @@ const Register = () => { + {errors.confirmPassword && ( +

+ {errors.confirmPassword.message} +

+)} +
diff --git a/src/store/slices/authSlice.js b/src/store/slices/authSlice.js index 35778d0..d5e04a9 100644 --- a/src/store/slices/authSlice.js +++ b/src/store/slices/authSlice.js @@ -13,9 +13,20 @@ 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 + + }catch(error) { + throw error; + } + } ); + + // TODO: Implement login thunk export const login = createAsyncThunk( "auth/login", @@ -24,17 +35,38 @@ 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 + + }catch(error){ + if (error.response?.data?.error) { + throw new Error(error.response.data.error); + } + throw error; + + } } ); // TODO: Implement register thunk -export const register = createAsyncThunk( +export const registerUser = createAsyncThunk( "auth/register", 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); + return response.data + + }catch(error) { + throw error; + } + + } ); @@ -45,9 +77,29 @@ 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) { + throw error; + } + } ); +export const resetposssword = createAsyncThunk( + "auth/resetpassword", + async (email, { rejectWithValue }) => { + try{ + const response = await axios.post(`${BASE_URL}/auth/reset-password`, {email}); + return response.data + }catch(error) { + throw error; + } + } +) + const initialState = { user: null, isAuthenticated: false, @@ -64,11 +116,78 @@ 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 + // Check Auth Status + .addCase(checkAuthStatus.pending, (state) => { + state.status = "loading"; + }) + .addCase(checkAuthStatus.fulfilled, (state, action) => { + state.status = "succeeded"; + state.user = action.payload; + state.isAuthenticated = true; + state.loading = false; + state.error = null; + }) + .addCase(checkAuthStatus.rejected, (state, action) => { + state.status = "failed"; + state.user = null; + state.isAuthenticated = false; + state.loading = false; + state.error = action.error.message; + }) + + // Login + .addCase(login.pending, (state) => { + state.status = "loading"; + }) + .addCase(login.fulfilled, (state, action) => { + state.status = "succeeded"; + state.user = action.payload; + state.isAuthenticated = true; + state.loading = false; + state.error = null; + }) + .addCase(login.rejected, (state, action) => { + state.status = "failed"; + state.user = null; + state.isAuthenticated = false; + state.loading = false; + state.error = action.error.message; + }) + + // Register + .addCase(registerUser.pending, (state) => { + state.status = "loading"; + state.error = null; + }) + .addCase(registerUser.fulfilled, (state, action) => { + state.status = "succeeded"; + state.user = action.payload; + state.isAuthenticated = true; + state.error = null; + }) + .addCase(registerUser.rejected, (state, action) => { + state.status = "failed"; + state.error = action.error.message; + }) + + // Logout + .addCase(logout.pending, (state) => { + state.status = "loading"; + }) + .addCase(logout.fulfilled, (state) => { + state.status = "succeeded"; + state.user = null; + state.isAuthenticated = false; + state.loading = false; + state.error = null; + }) + .addCase(logout.rejected, (state, action) => { + state.status = "failed"; + state.error = action.error.message; + }); }, });