Skip to content
Merged
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
295 changes: 188 additions & 107 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,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 +54 to +55
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.

}
}

Expand All @@ -67,8 +67,11 @@ const SignUp: React.FC = () => {
if (name === "password") {
if (!value.trim()) {
errorMessage = "Password is required";
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(value)) {
errorMessage = "Password must contain uppercase, lowercase, number, and special character";
} else if (
!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(value)
) {
errorMessage =
"Password must be 8+ characters with letters and numbers";
}
}

Expand All @@ -80,8 +83,8 @@ const SignUp: React.FC = () => {

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"
Expand All @@ -90,30 +93,37 @@ const SignUp: React.FC = () => {
: "";
const passwordError = !formData.password.trim()
? "Password is required"
: !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(formData.password)
: !/^(?=.*[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, { withCredentials: true });
setMessage(response.data.message);
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.status === 201) {
if (response.data.message === "User created successfully") {
navigate("/login");
}
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
setMessage(error.response?.data?.message || "Something went wrong. Please try again.");
} else {
setMessage("Something went wrong. Please try again.");
}
} catch (error: any) {
setMessage(
error.response?.data?.message ||
"Something went wrong. Please try again.",
);
Comment on lines 122 to +126
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 All @@ -135,105 +145,176 @@ const SignUp: React.FC = () => {
</p>
}
>
<div className="space-y-8">
<div className="space-y-3 text-center sm:text-left">
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.22em] text-cyan-300/90">
<Sparkles className="h-3.5 w-3.5" />
New account
</div>
<div>
<h2 className={`text-3xl font-semibold tracking-tight ${mode === "dark" ? "text-white" : "text-slate-950"}`}>
Build your profile
</h2>
<p className={`mt-2 text-sm leading-6 ${mode === "dark" ? "text-slate-300" : "text-slate-600"}`}>
Use a simple, guided signup flow with instant feedback while you type.
</p>
<div className="absolute inset-0">
<div
className={`absolute -top-40 -right-40 w-96 h-96 ${
mode === "dark" ? "bg-purple-500" : "bg-purple-300"
} rounded-full blur-3xl opacity-30 animate-pulse`}
/>
<div
className={`absolute -bottom-40 -left-40 w-96 h-96 ${
mode === "dark" ? "bg-blue-500" : "bg-blue-300"
} rounded-full blur-3xl opacity-30 animate-pulse`}
/>
<div
className={`absolute top-40 left-40 w-96 h-96 ${
mode === "dark" ? "bg-pink-500" : "bg-pink-300"
} rounded-full blur-3xl opacity-30 animate-pulse`}
/>
<div
className={`absolute top-1/2 right-1/4 w-64 h-64 ${
mode === "dark" ? "bg-indigo-500" : "bg-indigo-300"
} rounded-full blur-2xl opacity-20 animate-pulse delay-1000`}
/>
</div>

<div className="relative w-full max-w-md px-4 sm:px-6">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
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"
/>
</div>
</div>

<form onSubmit={handleSubmit} className="space-y-5">
<label className="block space-y-2">
<span className={`text-sm font-medium ${mode === "dark" ? "text-slate-200" : "text-slate-700"}`}>Username</span>
<div className={`flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30"}`}>
<User className={`h-5 w-5 shrink-0 ${mode === "dark" ? "text-slate-400" : "text-slate-500"}`} />
<input
type="text"
name="username"
placeholder="Your display name"
value={formData.username}
onChange={handleChange}
required
className={`w-full bg-transparent text-sm outline-none ${mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400"}`}
/>
<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
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className={`rounded-3xl p-6 sm:p-10 shadow-2xl border ${
mode === "dark"
? "bg-white/10 backdrop-blur-xl border-white/20 text-white"
: "bg-white border-gray-200 text-black"
}`}
>
<h2
className={`text-2xl font-bold text-center mb-8 ${
mode === "dark" ? "text-white" : "text-gray-800"
}`}
>
Create Account
</h2>

<form onSubmit={handleSubmit} className="space-y-6">
<div>
<div className="relative">
<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
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>
)}
</div>
{errors.username && <p className="text-sm text-rose-600 dark:text-rose-300">{errors.username}</p>}
</label>

<label className="block space-y-2">
<span className={`text-sm font-medium ${mode === "dark" ? "text-slate-200" : "text-slate-700"}`}>Email address</span>
<div className={`flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30"}`}>
<Mail className={`h-5 w-5 shrink-0 ${mode === "dark" ? "text-slate-400" : "text-slate-500"}`} />
<input
type="email"
name="email"
placeholder="name@example.com"
value={formData.email}
onChange={handleChange}
required
className={`w-full bg-transparent text-sm outline-none ${mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400"}`}
/>
<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
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>
)}
</div>
{errors.email && <p className="text-sm text-rose-600 dark:text-rose-300">{errors.email}</p>}
</label>

<label className="block space-y-2">
<span className={`text-sm font-medium ${mode === "dark" ? "text-slate-200" : "text-slate-700"}`}>Password</span>
<div className={`flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30"}`}>
<Lock className={`h-5 w-5 shrink-0 ${mode === "dark" ? "text-slate-400" : "text-slate-500"}`} />
<input
type={showPassword ? "text" : "password"}
name="password"
placeholder="At least 8 characters"
value={formData.password}
onChange={handleChange}
required
className={`w-full bg-transparent text-sm outline-none ${mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400"}`}
/>
<button
type="button"
onClick={() => setShowPassword((previous) => !previous)}
aria-label={showPassword ? "Hide password" : "Show password"}
aria-pressed={showPassword}
className={`shrink-0 transition-colors ${mode === "dark" ? "text-slate-400 hover:text-white" : "text-slate-500 hover:text-slate-900"}`}
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
<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
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>
</div>
{errors.password && (
<p className="text-red-500 text-sm mt-2">{errors.password}</p>
)}
</div>
{errors.password && <p className="text-sm text-rose-600 dark:text-rose-300">{errors.password}</p>}
</label>

<button
type="submit"
disabled={isLoading}
className="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-cyan-500 via-blue-500 to-indigo-600 px-6 py-4 text-sm font-semibold text-white shadow-lg shadow-cyan-500/20 transition duration-300 hover:-translate-y-0.5 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-60"
>
{isLoading ? "Creating account..." : "Create account"}
{!isLoading && <ArrowRight className="h-4 w-4" />}
</button>
</form>

{message && (
<div
className={`rounded-2xl border px-4 py-3 text-sm ${
message.includes("successfully")
? "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300"
: "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300"
}`}
>
<div className="flex items-start gap-2">
<ShieldCheck className="mt-0.5 h-4 w-4 shrink-0" />
<p>{message}</p>
<button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 hover:scale-[1.02] hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? "Creating account..." : "Create Account"}
</button>
</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"}`}
>
{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"}`}
>
Sign in here
</Link>
</p>
</div>
)}
</div>
Expand Down
Loading