From 3c483bf6260c18418df89615b081b2f1d3291d2e Mon Sep 17 00:00:00 2001 From: hani Date: Mon, 25 Aug 2025 14:43:21 +0300 Subject: [PATCH] newUpdates_week15 --- src/App.jsx | 43 +++++++-- src/components/Navbar.jsx | 13 ++- src/components/auth/ProtectedRoute.jsx | 11 ++- src/pages/Login.jsx | 42 ++++++++- src/pages/Register.jsx | 48 +++++++++- src/schema/authSchema.js | 6 +- src/store/slices/authSlice.js | 126 +++++++++++++++++++++---- 7 files changed, 249 insertions(+), 40 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 129a9e7..8ed9acf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,17 @@ import { Routes, Route } from "react-router-dom"; import Login from "./pages/Login"; -import Register from "./pages/Register"; -import CreateNote from "./pages/CreateNote"; +import Register from "./pages/Register.jsx"; import ViewNotes from "./pages/ViewNotes"; -import Navbar from "./components/Navbar"; +import CreateNote from "./pages/CreateNote.jsx"; +import Navbar from "./components/Navbar.jsx"; +import { useDispatch,useSelector } from "react-redux"; +import { useEffect } from "react"; +import { checkAuthStatus } from "./store/slices/authSlice"; +import ProtectedRoute from "./components/auth/ProtectedRoute"; + + + const App = () => { return (
@@ -12,15 +19,35 @@ const App = () => {
- } /> - } /> - } /> - } /> + + + + + } /> + + + + + } /> + + + + + } /> + + + + } /> +
); }; - + export default App; diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 9bd2bc8..2fe5fbc 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -34,7 +34,8 @@ const Navbar = () => {
- + {isAuthenticated ? ( + <> { Logout - - + + ) : ( + <> { Register - + + )}
@@ -99,4 +102,4 @@ const Navbar = () => { ); }; -export default Navbar; +export default Navbar; \ No newline at end of file diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx index 0c71b6c..b6882cb 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, loading , status } = useSelector((state) => state.auth); // Show loading state while checking authentication - if (loading) { + if ( status === "loading") { return (
@@ -17,12 +17,19 @@ 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..dde0cd0 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,4 +1,38 @@ -const Login = () => { +import ract from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import {z} from "zod"; +import { useNavigate } from 'react-router-dom'; +import {useDispatch, useSelector} from 'react-redux'; +import { login } from "../store/slices/authSlice.js"; +import { loginSchema } from '../schema/authSchema.js'; + + +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 onLogin = async (data) => { + try{ + await dispatch(login(data)).unwrap(); + navigate("/notes"); + } catch(error) { + console.log("failed to login", error); + } + } + + return (
@@ -6,7 +40,7 @@ const Login = () => { Login to Your Account -
+
); -}; +} export default Login; diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx index ba6f0bd..b02d823 100644 --- a/src/pages/Register.jsx +++ b/src/pages/Register.jsx @@ -1,4 +1,40 @@ +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 { registers } from '../store/slices/authSlice.js'; +import { registerSchema } from "../schema/authSchema.js"; + + const Register = () => { + const dispatch = useDispatch(); + const navigate = useNavigate() + const {status , error} = useSelector((state) => state.auth) + const{ + register, + handleSubmit, + formState:{errors}, + } =useForm({ + resolve: zodResolver(registerSchema), + defaultValues: { + email: "" + } + }); + + const onRegister = async (data) => { + try { + await dispatch(registers(data)).unwrap(); + console.log(register.date) + navigate('/login'); + } catch (error) { + console.log('something went wrong', error); + } + }; + + + return (
@@ -6,7 +42,7 @@ const Register = () => { Create an Account - +
@@ -32,9 +73,13 @@ const Register = () => { + {/* {errors.password && ( +

{errors.password.message}

+ )} */}
@@ -47,6 +92,7 @@ const Register = () => { diff --git a/src/schema/authSchema.js b/src/schema/authSchema.js index 9a739d6..08e8b62 100644 --- a/src/schema/authSchema.js +++ b/src/schema/authSchema.js @@ -10,11 +10,7 @@ export const registerSchema = z 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"), confirmPassword: z.string().min(1, "Please confirm your password"), }) .refine((data) => data.password === data.confirmPassword, { diff --git a/src/store/slices/authSlice.js b/src/store/slices/authSlice.js index 35778d0..892fcd5 100644 --- a/src/store/slices/authSlice.js +++ b/src/store/slices/authSlice.js @@ -9,17 +9,35 @@ axios.defaults.withCredentials = true; export const checkAuthStatus = createAsyncThunk( "auth/checkStatus", async (_, { rejectWithValue }) => { + try { + const response = await axios.get(`${BASE_URL}/auth/check`); + return response.data; + } catch (error) { + return rejectWithValue(error.response?.data || "Session expired"); + } + } +); + + + // TODO: Implement authentication status check // 1. Make a GET request to /auth/check // 2. Return the response data // 3. Handle errors appropriately - } -); + + // TODO: Implement login thunk export const login = createAsyncThunk( "auth/login", async (credentials, { rejectWithValue }) => { + try { + const response = await axios.post(`${BASE_URL}/auth/login`, credentials); + return response.data; + } catch (error) { + // Handle error appropriately + return rejectWithValue(error.response.data); + } // TODO: Implement login functionality // 1. Make a POST request to /auth/login with credentials // 2. Return the response data @@ -28,13 +46,20 @@ export const login = createAsyncThunk( ); // TODO: Implement register thunk -export const register = createAsyncThunk( +export const registers = 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); + + console.log("response",response) + return response.data; + + } catch (error) { + + return rejectWithValue(error.response.data); + } + } ); @@ -42,6 +67,14 @@ export const register = createAsyncThunk( export const logout = createAsyncThunk( "auth/logout", async (_, { rejectWithValue }) => { + try { + await axios.post(`${BASE_URL}/auth/logout`); + return true + } catch (error) { + // Handle error appropriately + return rejectWithValue(error.response.data); + + } // TODO: Implement logout functionality // 1. Make a POST request to /auth/logout // 2. Handle errors appropriately @@ -64,13 +97,74 @@ 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 + .addCase(checkAuthStatus.pending, (state) => { + state.loading = true; + }) + .addCase(checkAuthStatus.fulfilled, (state, action) => { + state.loading = false; + state.isAuthenticated = true; + state.user = action.payload; + }) + .addCase(checkAuthStatus.rejected, (state, action) => { + state.loading = false; + state.isAuthenticated = false; + state.user = null; + state.error = action.payload; + }) + + // ✅ login + .addCase(login.pending, (state) => { + state.loading = true; + }) + .addCase(login.fulfilled, (state, action) => { + state.loading = false; + state.isAuthenticated = true; + state.user = action.payload.user; + state.error = null; + -export const { clearError } = authSlice.actions; -export default authSlice.reducer; + }) + .addCase(login.rejected, (state, action) => { + state.loading = false; + state.isAuthenticated = true; + state.user = null; + state.error = action.payload; + }) + + // ✅ register + .addCase(registers.pending, (state) => { + state.loading = true; + }) + .addCase(registers.fulfilled, (state, action) => { + state.loading = false; + state.isAuthenticated = false; + state.user = action.payload.user; + state.error = null; + }) + .addCase(registers.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }) + + // ✅ logout + .addCase(logout.pending, (state) => { + state.loading = true; + }) + .addCase(logout.fulfilled, (state) => { + state.loading = false; + state.isAuthenticated = false; + state.user = null; + state.error = null; + }) + .addCase(logout.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + + } + // TODO: Add cases for checkAuthStatus + }); + + export const { clearError } = authSlice.actions; +export default authSlice.reducer; \ No newline at end of file