Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/validators/authValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
),
});
Expand Down
21 changes: 10 additions & 11 deletions src/components/__test__/Navbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down
4 changes: 4 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<StrictMode>
Expand Down
4 changes: 3 additions & 1 deletion src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
19 changes: 10 additions & 9 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand All @@ -64,27 +64,28 @@ 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"
: "";
Comment on lines +67 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep change-time username validation in sync with submit validation.

Lines 67-69 now accept digits and underscores, but the live validation at Line 42 still rejects them with "Only letters are allowed". That leaves usernames like user_1 showing an error while typing even though submit accepts them.

Suggested fix
     if (name === "username") {
       if (!value.trim()) {
         errorMessage = "Username is required";
-      } else if (!/^[A-Za-z\s]+$/.test(value)) {
-        errorMessage = "Only letters are allowed";
+      } else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
+        errorMessage = "Username can only contain letters, numbers, and underscores";
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
: !/^[a-zA-Z0-9_]+$/.test(formData.username)
? "Username can only contain letters, numbers, and underscores"
: "";
if (name === "username") {
if (!value.trim()) {
errorMessage = "Username is required";
} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
errorMessage = "Username can only contain letters, numbers, and underscores";
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Signup/Signup.tsx` around lines 67 - 69, The live (change-time)
username validation in the Signup component is out of sync with the submit-time
validation: update the change-time check that references formData.username so it
uses the same regex /^[a-zA-Z0-9_]+$/ and update the corresponding error message
from "Only letters are allowed" to "Username can only contain letters, numbers,
and underscores" (or otherwise match the submit-time message) so both
validations accept digits and underscores consistently.

const emailError = !formData.email.trim()
? "Email is required"
: !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email.trim())
? "Enter a valid email"
: "";
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;
}
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

Expand Down