Skip to content
Open
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
126 changes: 94 additions & 32 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const SignUp: React.FC = () => {
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](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(value)) {
errorMessage = "Enter a valid GitHub username";
Comment on lines +42 to +43
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 | 🟠 Major | ⚡ Quick win

Username regex still accepts invalid GitHub usernames.

Line 42 and Line 70 allow consecutive hyphens and unlimited length, which are not GitHub-compatible. This will still pass some invalid usernames client-side.

Proposed fix
+const GITHUB_USERNAME_REGEX = /^(?=.{1,39}$)(?!.*--)[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/;
...
-      } else if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(value)) {
+      } else if (!GITHUB_USERNAME_REGEX.test(value)) {
         errorMessage = "Enter a valid GitHub username";
       }
...
-      : !/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(formData.username)
+      : !GITHUB_USERNAME_REGEX.test(formData.username)
         ? "Enter a valid GitHub username"
         : "";

Also applies to: 70-72

🤖 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 42 - 43, The current username
validation regex in Signup.tsx (the branch that tests the variable named "value"
in the Signup component's username validation logic) allows consecutive hyphens
and unlimited length; update both occurrences of that regex (the check around
the first validation block and the similar check near lines ~70–72) to a pattern
that (1) enforces a 1–39 character limit, (2) requires the username to start and
end with an alphanumeric character, (3) allows hyphens only between alphanumeric
groups, and (4) forbids consecutive hyphens (i.e., use a regex with a negative
lookahead for "--", a length check for max 39, and a structure like alnum groups
separated by single hyphens); replace the old test with that stricter pattern so
invalid GitHub usernames are rejected client-side.

}
}
if (name === "email") {
Expand All @@ -53,8 +53,11 @@ 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-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(value)
) {
errorMessage =
"Password must be 8+ characters with letters and numbers";
}
}
setErrors((prev) => ({ ...prev, [name]: errorMessage }));
Expand All @@ -64,36 +67,46 @@ 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](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(formData.username)
? "Enter a valid GitHub username"
: "";
const emailError = !formData.email.trim()
? "Email is required"
: !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email.trim())
? "Enter a valid email"
: "";
? "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-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(
formData.password,
)
? "Password must be 8+ characters with letters and numbers"
: "";
if (usernameError || emailError || passwordError) {
setErrors({ username: usernameError, email: emailError, password: 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
const response = await axios.post(
`${backendUrl}/api/auth/signup`,
formData, // Include cookies for session
);
setMessage(response.data.message); // Show success message from backend

// Navigate to login page after successful signup
if (response.data.message === 'User created successfully') {
if (response.data.message === "User created successfully") {
navigate("/login");
}
} catch (error: any) {
setMessage(error.response?.data?.message || "Something went wrong. Please try again.");
setMessage(
error.response?.data?.message ||
"Something went wrong. Please try again.",
);
Comment on lines 105 to +109
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify there are no explicit `any` catch parameters in TS/TSX files.
rg -nP --type=ts --type=tsx 'catch\s*\(\s*[A-Za-z_$][A-Za-z0-9_$]*\s*:\s*any\s*\)'

Repository: GitMetricsLab/github_tracker

Length of output: 100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact lines around the reported catch
echo "---- src/pages/Signup/Signup.tsx (around 100-115) ----"
nl -ba src/pages/Signup/Signup.tsx | sed -n '90,125p'

# Search for explicit `any` in catch parameters across the repo
echo "---- Searching for `catch (..: any)` in .ts/.tsx ----"
rg -n --glob='*.ts' --glob='*.tsx' -P 'catch\s*\(\s*[^)]*:\s*any\s*\)'

Repository: GitMetricsLab/github_tracker

Length of output: 188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/pages/Signup/Signup.tsx (around 100-115) ----"
cat -n src/pages/Signup/Signup.tsx | sed -n '90,125p'

echo "---- Searching for `catch (..: any)` in .ts/.tsx ----"
rg -n --glob='*.ts' --glob='*.tsx' -P 'catch\s*\(\s*[^)]*:\s*any\s*\)'

Repository: GitMetricsLab/github_tracker

Length of output: 1711


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/pages/Signup/Signup.tsx (around 100-115) ----"
cat -n src/pages/Signup/Signup.tsx | sed -n '100,112p'

echo "---- Searching for explicit typed `any` in catch parameters ----"
rg -n --glob='*.ts' --glob='*.tsx' -P 'catch\s*\(\s*[^)]*:\s*any\s*\)' .

echo "---- Searching ESLint for no-explicit-any rule ----"
rg -n "no-explicit-any" .eslintrc* package.json eslint.* --hidden || true

echo "---- Searching tsconfig for useUnknownInCatchVariables ----"
rg -n "useUnknownInCatchVariables" tsconfig*.json . --hidden || true

Repository: GitMetricsLab/github_tracker

Length of output: 919


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- repo root ----"
ls -a

echo "---- Searching for explicit typed any in catch parameters (.ts/.tsx) ----"
rg -n --glob='*.ts' --glob='*.tsx' -P 'catch\s*\(\s*[^)]*:\s*any\s*\)' .

echo "---- Searching for ESLint rule no-explicit-any in repo ----"
rg -n "no-explicit-any" . || true

echo "---- Searching for TS compiler option useUnknownInCatchVariables in tsconfig ----"
rg -n "useUnknownInCatchVariables" tsconfig*.json . || true

echo "---- Searching for `@typescript-eslint` in package.json ----"
cat package.json | head -n 80

Repository: GitMetricsLab/github_tracker

Length of output: 2796


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/pages/Signup/Signup.tsx imports (top) ----"
sed -n '1,60p' src/pages/Signup/Signup.tsx

echo "---- eslint.config.js (relevant excerpts) ----"
sed -n '1,200p' eslint.config.js

echo "---- tsconfig.json (relevant excerpts) ----"
cat tsconfig.json

echo "---- tsconfig.app.json (relevant excerpts) ----"
cat tsconfig.app.json

echo "---- tsconfig.node.json (relevant excerpts) ----"
cat tsconfig.node.json

Repository: GitMetricsLab/github_tracker

Length of output: 4439


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Running eslint on src/pages/Signup/Signup.tsx ----"
npx eslint src/pages/Signup/Signup.tsx

Repository: GitMetricsLab/github_tracker

Length of output: 306


Replace catch (error: any) with typed narrowing

src/pages/Signup/Signup.tsx uses catch (error: any) at line 105, and @typescript-eslint/no-explicit-any makes eslint fail with “Unexpected any. Specify a different type”.

Proposed fix
+import type { AxiosError } from "axios";
...
-    } catch (error: any) {
-      setMessage(
-        error.response?.data?.message ||
-          "Something went wrong. Please try again.",
-      );
+    } catch (error: unknown) {
+      const axiosError = error as AxiosError<{ message?: string }>;
+      setMessage(
+        axiosError.response?.data?.message ??
+          "Something went wrong. Please try again.",
+      );
     } finally {
📝 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
} catch (error: any) {
setMessage(error.response?.data?.message || "Something went wrong. Please try again.");
setMessage(
error.response?.data?.message ||
"Something went wrong. Please try again.",
);
import type { AxiosError } from "axios";
// ... other code ...
} catch (error: unknown) {
const axiosError = error as AxiosError<{ message?: string }>;
setMessage(
axiosError.response?.data?.message ??
"Something went wrong. Please try again.",
);
} finally {
🧰 Tools
🪛 ESLint

[error] 105-105: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 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 105 - 109, The catch currently uses
catch (error: any) which violates no-explicit-any; change it to catch (error:
unknown) and narrow the type before using error.response: add a type guard that
checks if error is an AxiosError (import and use axios.isAxiosError) or at
minimum if (typeof error === 'object' && error !== null && 'response' in error)
cast appropriately, then call setMessage(error.response?.data?.message ||
"Something went wrong. Please try again."); keep setMessage and the rest of the
error-handling logic unchanged.

} finally {
setIsLoading(false);
}
Expand Down Expand Up @@ -138,10 +151,22 @@ const SignUp: React.FC = () => {
className="text-center mb-10"
>
<div className="inline-flex items-center justify-center w-20 h-20 bg-white rounded-3xl mb-6 shadow-2xl transform hover:scale-105 transition-transform duration-300 overflow-hidden">
<img src="/crl-icon.png" alt="Logo" className="w-14 h-14 object-contain" />
<img
src="/crl-icon.png"
alt="Logo"
className="w-14 h-14 object-contain"
/>
</div>
<h1 className={`text-4xl font-bold mb-2 ${mode === "dark" ? "text-white" : "text-black"}`}>GitHubTracker</h1>
<p className={`text-lg font-medium ${mode === "dark" ? "text-slate-300" : "text-gray-700"}`}>Join your GitHub journey</p>
<h1
className={`text-4xl font-bold mb-2 ${mode === "dark" ? "text-white" : "text-black"}`}
>
GitHubTracker
</h1>
<p
className={`text-lg font-medium ${mode === "dark" ? "text-slate-300" : "text-gray-700"}`}
>
Join your GitHub journey
</p>
</motion.div>

<motion.div
Expand All @@ -154,7 +179,6 @@ const SignUp: React.FC = () => {
: "bg-white border-gray-200 text-black"
}`}
>

<h2
className={`text-2xl font-bold text-center mb-8 ${
mode === "dark" ? "text-white" : "text-gray-800"
Expand All @@ -169,39 +193,72 @@ const SignUp: React.FC = () => {
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<User className="h-5 w-5 text-gray-400" />
</div>
<input type="text" name="username" placeholder="Enter your username" value={formData.username} onChange={handleChange} required
<input
type="text"
name="username"
placeholder="Enter your username"
value={formData.username}
onChange={handleChange}
required
className={`w-full pl-12 pr-4 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400"}`}
/>
</div>
{errors.username && <p className="text-red-500 text-sm mt-2">{errors.username}</p>}
{errors.username && (
<p className="text-red-500 text-sm mt-2">{errors.username}</p>
)}
</div>

<div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input type="email" name="email" placeholder="Enter your email" value={formData.email} onChange={handleChange} required
<input
type="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
required
className={`w-full pl-12 pr-4 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400"}`}
/>
</div>
{errors.email && <p className="text-red-500 text-sm mt-2">{errors.email}</p>}
{errors.email && (
<p className="text-red-500 text-sm mt-2">{errors.email}</p>
)}
</div>

<div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input type={showPassword ? "text" : "password"} name="password" placeholder="Enter your password" value={formData.password} onChange={handleChange} required
<input
type={showPassword ? "text" : "password"}
name="password"
placeholder="Enter your password"
value={formData.password}
onChange={handleChange}
required
className={`w-full pl-12 pr-12 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400"}`}
/>
<button type="button" onClick={() => setShowPassword(!showPassword)} aria-label={showPassword ? "Hide password" : "Show password"} aria-pressed={showPassword}
className={`absolute inset-y-0 right-0 pr-4 flex items-center transition-colors duration-200 ${mode === "dark" ? "text-slate-400 hover:text-white" : "text-gray-500 hover:text-gray-800"}`}>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
aria-label={showPassword ? "Hide password" : "Show password"}
aria-pressed={showPassword}
className={`absolute inset-y-0 right-0 pr-4 flex items-center transition-colors duration-200 ${mode === "dark" ? "text-slate-400 hover:text-white" : "text-gray-500 hover:text-gray-800"}`}
>
{showPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
</div>
{errors.password && <p className="text-red-500 text-sm mt-2">{errors.password}</p>}
{errors.password && (
<p className="text-red-500 text-sm mt-2">{errors.password}</p>
)}
</div>

<button
Expand All @@ -214,15 +271,20 @@ const SignUp: React.FC = () => {
</form>

{message && (
<div className={`text-center mt-6 p-3 rounded-xl ${message.includes("successfully") ? "text-green-600 bg-green-100" : "text-red-600 bg-red-100"}`}>
<div
className={`text-center mt-6 p-3 rounded-xl ${message.includes("successfully") ? "text-green-600 bg-green-100" : "text-red-600 bg-red-100"}`}
>
{message}
</div>
)}

<div className="text-center mt-8">
<p className={mode === "dark" ? "text-gray-300" : "text-gray-600"}>
Already have an account?{" "}
<Link to="/login" className={`font-medium hover:underline transition-colors duration-300 ${mode === "dark" ? "text-white" : "text-black"}`}>
<Link
to="/login"
className={`font-medium hover:underline transition-colors duration-300 ${mode === "dark" ? "text-white" : "text-black"}`}
>
Sign in here
</Link>
</p>
Expand Down
Loading