diff --git a/backend/validators/authValidator.js b/backend/validators/authValidator.js index ab4dac07..926e699c 100644 --- a/backend/validators/authValidator.js +++ b/backend/validators/authValidator.js @@ -18,7 +18,7 @@ const signupSchema = z.object({ .min(8, "Password must be at least 8 characters long") .max(100, "Password must be at most 100 characters long") .regex( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/, + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, 'Password must contain uppercase, lowercase, number, and special character' ), }); diff --git a/src/components/__test__/Navbar.test.tsx b/src/components/__test__/Navbar.test.tsx index 780b9bb3..5aef36fc 100644 --- a/src/components/__test__/Navbar.test.tsx +++ b/src/components/__test__/Navbar.test.tsx @@ -51,31 +51,30 @@ describe('Navbar', () => { // --- Mobile menu --- it('mobile menu is hidden by default', () => { renderNavbar() - expect(screen.queryByText('About')).not.toBeInTheDocument() + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1) }) it('opens mobile menu when hamburger is clicked', () => { renderNavbar() - const hamburger = screen.getAllByRole('button')[1] // second button = hamburger + const hamburger = screen.getByRole('button', { name: /toggle menu/i }) fireEvent.click(hamburger) - expect(screen.getByText('About')).toBeInTheDocument() - expect(screen.getByText('Contact')).toBeInTheDocument() + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(2) }) it('closes mobile menu when a nav link is clicked', () => { renderNavbar() - const hamburger = screen.getAllByRole('button')[1] + const hamburger = screen.getByRole('button', { name: /toggle menu/i }) fireEvent.click(hamburger) // open - const homeLinks = screen.getAllByRole('link', { name: /home/i }) - fireEvent.click(homeLinks[homeLinks.length - 1]) // click the mobile one - expect(screen.queryByText('About')).not.toBeInTheDocument() // closed + const trackerLinks = screen.getAllByRole('link', { name: /^tracker$/i }) + fireEvent.click(trackerLinks[trackerLinks.length - 1]) // click the mobile one + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1) // closed }) it('calls toggleTheme from the mobile menu button', () => { const { toggleTheme } = renderNavbar('dark') - const hamburger = screen.getAllByRole('button')[1] - fireEvent.click(hamburger) - fireEvent.click(screen.getByText(/light/i)) + // The mobile theme button is the first button inside md:hidden (which is button at index 1 of all buttons) + const mobileThemeBtn = screen.getAllByRole('button', { name: /toggle theme/i })[1] + fireEvent.click(mobileThemeBtn) expect(toggleTheme).toHaveBeenCalledTimes(1) }) diff --git a/src/main.tsx b/src/main.tsx index 4c5b79dd..23e1511c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,10 @@ import App from "./App.tsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; import ThemeWrapper from "./context/ThemeContext.tsx"; +import axios from "axios"; + +axios.defaults.withCredentials = true; + createRoot(document.getElementById("root")!).render( diff --git a/src/pages/Login/Login.tsx b/src/pages/Login/Login.tsx index 92b7073e..7d1acbfe 100644 --- a/src/pages/Login/Login.tsx +++ b/src/pages/Login/Login.tsx @@ -30,7 +30,9 @@ const Login: React.FC = () => { setIsLoading(true); try { - const response = await axios.post(`${backendUrl}/api/auth/login`, formData); + const response = await axios.post(`${backendUrl}/api/auth/login`, formData, { + withCredentials: true, + }); setMessage(response.data.message); if (response.data.message === 'Login successful') { diff --git a/src/pages/Signup/Signup.tsx b/src/pages/Signup/Signup.tsx index 2ac51dcc..5c161e78 100644 --- a/src/pages/Signup/Signup.tsx +++ b/src/pages/Signup/Signup.tsx @@ -53,8 +53,8 @@ const SignUp: React.FC = () => { if (name === "password") { if (!value.trim()) { errorMessage = "Password is required"; - } else if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(value)) { - errorMessage = "Password must be 8+ characters with letters and numbers"; + } else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(value)) { + errorMessage = "Password must contain uppercase, lowercase, number, and special character"; } } setErrors((prev) => ({ ...prev, [name]: errorMessage })); @@ -64,9 +64,9 @@ const SignUp: React.FC = () => { e.preventDefault(); const usernameError = !formData.username.trim() ? "Username is required" - : !/^[A-Za-z\s]+$/.test(formData.username) - ? "Only letters are allowed" - : ""; + : !/^[a-zA-Z0-9_]+$/.test(formData.username) + ? "Username can only contain letters, numbers, and underscores" + : ""; const emailError = !formData.email.trim() ? "Email is required" : !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email.trim()) @@ -74,9 +74,9 @@ const SignUp: React.FC = () => { : ""; const passwordError = !formData.password.trim() ? "Password is required" - : !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(formData.password) - ? "Password must be 8+ characters with letters and numbers" - : ""; + : !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(formData.password) + ? "Password must contain uppercase, lowercase, number, and special character" + : ""; if (usernameError || emailError || passwordError) { setErrors({ username: usernameError, email: emailError, password: passwordError }); return; @@ -84,7 +84,8 @@ const SignUp: React.FC = () => { setIsLoading(true); try { const response = await axios.post(`${backendUrl}/api/auth/signup`, - formData // Include cookies for session + formData, + { withCredentials: true } ); setMessage(response.data.message); // Show success message from backend