diff --git a/server/routes/auth.js b/server/routes/auth.js index fb352cc..0d24db2 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -43,17 +43,7 @@ router.post("/register", async (req, res) => { const { email, password } = req.body; const db = await getDbConnection(); - // Validate input - if (!validateEmail(email)) { - return res.status(400).json({ message: "Invalid email format" }); - } - if (!validatePassword(password)) { - return res.status(400).json({ - message: - "Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character", - }); - } - + // Check if user already exists const existingUser = await db.get("SELECT * FROM users WHERE email = ?", [ email, diff --git a/src/App.jsx b/src/App.jsx index 129a9e7..547ee26 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,19 +4,47 @@ 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 { useDispatch } from "react-redux"; +import { useEffect } from "react"; +import { checkAuthStatus } from './store/slices/authSlice' const App = () => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(checkAuthStatus()) + }, [dispatch]) + return (
- } /> - } /> + + + + } /> + + + + + } /> + + + + + } /> - } /> - } /> + + + + }/>
diff --git a/src/components/CreateNoteForm.jsx b/src/components/CreateNoteForm.jsx index 7bb495e..4732150 100644 --- a/src/components/CreateNoteForm.jsx +++ b/src/components/CreateNoteForm.jsx @@ -26,7 +26,7 @@ const CreateNoteForm = () => { }); const onSubmit = async (data) => { - const result = await dispatch(createNote(data)); + const result = await dispatch(createNote(data)).unwrap(); if (!result.error) { reset(); navigate("/notes"); diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 9bd2bc8..dce29ee 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -17,8 +17,7 @@ const Navbar = () => { const { isAuthenticated, user } = useSelector((state) => state.auth); const handleLogout = () => { - dispatch(logout()); - navigate("/login"); + dispatch(logout()).unwrap(), navigate("/login"); }; return ( @@ -34,7 +33,8 @@ const Navbar = () => {
- + {isAuthenticated ? ( + <> { View All - - - - - - Login - - - - - Register - - + + ) : ( + <> + + + Login + + + + Register + + + )}
diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx index 0c71b6c..8577b08 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,10 +17,14 @@ 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..ab6e9a6 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,4 +1,44 @@ +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from 'zod' +import { loginSchema } from "../schema/authSchema"; +import { useSelector, useDispatch } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { login as loginUser } from "../store/slices/authSlice"; +import axios from "axios"; + const Login = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { status, error } = useSelector((state) => state.auth); + + const { + register: registerFeild, + handleSubmit, + formState: {errors} + } = useForm({ + resolver: zodResolver(loginSchema), + defaultValues: { + email: "", + } + }) + + const onLogin = async (data) => { + try { + console.log(data) + const resolt = await dispatch(loginUser(data)); + console.log(resolt) + // if(loginUser.fulfilled.match(resolt)) { + // navigate('/notes') + // } else { + // console.error('login Failed', resolt.payload) + // } + } catch(error) { + console.log(error) + } + }; + + return (
@@ -6,7 +46,7 @@ const Login = () => { Login to Your Account -
+
@@ -32,6 +74,7 @@ const Login = () => { diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx index ba6f0bd..0d7a6ce 100644 --- a/src/pages/Register.jsx +++ b/src/pages/Register.jsx @@ -1,4 +1,36 @@ +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 { register as registerUser } from '../store/slices/authSlice'; +import { registerSchema } from '../schema/authSchema'; +import { useState } from 'react'; + const Register = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { status, error } = useSelector((state) => state.auth) + + const { + register, + handleSubmit, + formState: {errors} + } = useForm({ + resolver: zodResolver(registerSchema), + defaultValues: { + email: "", + }, + }) + + const onRegister = async (data) => { + try { + await dispatch(registerUser(data)).unwrap() + navigate('/login') + }catch(error) { + console.log('somthing went wrong', error) + } + }; return (
@@ -6,7 +38,7 @@ const Register = () => { Create an Account - +
@@ -32,9 +68,13 @@ const Register = () => { + {errors.password && ( +

{errors.password.message}

+ )}
@@ -47,6 +87,7 @@ const Register = () => { diff --git a/src/pages/ViewNotes.jsx b/src/pages/ViewNotes.jsx index 0b7eaca..3b7ca96 100644 --- a/src/pages/ViewNotes.jsx +++ b/src/pages/ViewNotes.jsx @@ -9,6 +9,7 @@ const ViewNotes = () => { const dispatch = useDispatch(); const { notes, loading, error } = useSelector((state) => state.notes); + console.log(notes) useEffect(() => { dispatch(fetchNotes()); }, [dispatch]); diff --git a/src/schema/authSchema.js b/src/schema/authSchema.js index 9a739d6..d842000 100644 --- a/src/schema/authSchema.js +++ b/src/schema/authSchema.js @@ -10,11 +10,11 @@ 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"), + // .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/slices/authSlice.js b/src/store/slices/authSlice.js index 35778d0..470911e 100644 --- a/src/store/slices/authSlice.js +++ b/src/store/slices/authSlice.js @@ -7,12 +7,18 @@ axios.defaults.withCredentials = true; // TODO: Implement checkAuthStatus thunk export const checkAuthStatus = createAsyncThunk( - "auth/checkStatus", + "auth/checkAuthStatus", async (_, { rejectWithValue }) => { // TODO: Implement authentication status check - // 1. Make a GET request to /auth/check - // 2. Return the response data - // 3. Handle errors appropriately + try { + // 1. Make a GET request to /auth/check + const response = await axios.get(`${BASE_URL}/auth/check`) + // 2. Return the response data + return response.data.user + }catch(err) { + // 3. Handle errors appropriately + throw err + } } ); @@ -21,9 +27,18 @@ export const login = createAsyncThunk( "auth/login", async (credentials, { rejectWithValue }) => { // TODO: Implement login functionality - // 1. Make a POST request to /auth/login with credentials - // 2. Return the response data - // 3. Handle errors appropriately + try { + // 1. Make a POST request to /auth/login with credentials + const response = await axios.post(`${BASE_URL}/auth/login`, credentials) + // 2. Return the response data + return response.data; + + }catch(error) { + // 3. Handle errors appropriately + if(error.response?.data?.error) { + throw new Error(error.response.data.error); + } + } } ); @@ -32,9 +47,21 @@ export const register = 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 { + console.log("DATA", userData) + // 1. Make a POST request to /auth/register with userData + const response = await axios.post(`${BASE_URL}/auth/register`, userData); + console.log('RESPONSE', response) + // 2. Return the response data + return response.data; + + }catch(error) { + // 3. Handle errors appropriately + if(error.response?.data?.error) { + throw new Error(error.response.data.error) + } + throw error; + } } ); @@ -43,6 +70,7 @@ export const logout = createAsyncThunk( "auth/logout", async (_, { rejectWithValue }) => { // TODO: Implement logout functionality + await axios.post(`${BASE_URL}/auth/logout`) // 1. Make a POST request to /auth/logout // 2. Handle errors appropriately } @@ -64,11 +92,60 @@ const authSlice = createSlice({ }, }, extraReducers: (builder) => { - builder; + builder // TODO: Add cases for checkAuthStatus + .addCase(checkAuthStatus.pending, (status) => { + 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, action) => { + 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.error.message; + }) + // 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 = 'adile'; + state.isAuthenticated = false; + state.user = null; + state.error = null; + }) }, });