Skip to content
Merged
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
1,656 changes: 1,656 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Footer from "./components/Footer";
import ScrollProgressBar from "./components/ScrollProgressBar";
import { Toaster } from "react-hot-toast";
import Router from "./Routes/Router";
import ScrollToTop from "./components/ScrollToTop";

const FULLSCREEN_ROUTES = ["/signup", "/login", "/enterToken"];

Expand All @@ -13,6 +14,8 @@ function App() {

return (
<div className="relative flex flex-col min-h-screen">
<ScrollToTop />

{!isFullscreen && <ScrollProgressBar />}

{!isFullscreen && <Navbar />}
Expand Down
3 changes: 1 addition & 2 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
import { UserContext } from "../context/UserContext";
import { Moon, Sun, Menu, X, Github } from "lucide-react";
import { Moon, Sun, Menu, X} from "lucide-react";

const Navbar: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
Expand Down
14 changes: 14 additions & 0 deletions src/components/ScrollToTop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

const ScrollToTop = () => {
const location = useLocation();

useEffect(() => {
window.scrollTo(0, 0);
}, [location]);

return null;
};

export default ScrollToTop;
2 changes: 1 addition & 1 deletion src/components/__test__/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// src/components/__tests__/Navbar.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi} from 'vitest'
import { MemoryRouter } from 'react-router-dom'
import { ThemeContext } from "../../context/ThemeContext";
import Navbar from '../Navbar.tsx'
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useGitHubData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export const useGitHubData = (
const shouldFetchPrs =
activeTab === 'pr' || activeTab === 'both';

const requests: Promise<any>[] = [];
const requests: Promise<unknown>[] = [];

if (shouldFetchIssues) {
requests.push(
Expand Down
143 changes: 92 additions & 51 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -65,35 +68,49 @@ const SignUp: React.FC = () => {
const usernameError = !formData.username.trim()
? "Username is required"
: !/^[A-Za-z\s]+$/.test(formData.username)
? "Only letters are allowed"
: "";
? "Only letters are allowed"
: "";
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
);
Comment on lines +95 to 98
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether credentials are already configured globally and compare auth request patterns.
rg -n -C3 'axios\.create\(|axios\.defaults\.withCredentials|withCredentials\s*:\s*true'
rg -n -C2 '/api/auth/signup|/api/auth/login|/api/auth'

Repository: GitMetricsLab/github_tracker

Length of output: 881


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "withCredentials" -S .
rg -n "axios\.create\(" -S src backend .
rg -n "axios\.defaults\.withCredentials" -S src backend .
rg -n -C3 "Include cookies" src/pages/Signup/Signup.tsx
sed -n '70,120p' src/pages/Signup/Signup.tsx
sed -n '1,120p' src/pages/Login/Login.tsx
sed -n '1,120p' backend/server.js
ls -la src | head

Repository: GitMetricsLab/github_tracker

Length of output: 151


🏁 Script executed:

#!/bin/bash
set +euo pipefail
rg -n "withCredentials" -S src backend || true
rg -n "axios\.create\(" -S src backend || true
rg -n "axios\.defaults\.withCredentials" -S src backend || true
rg -n "axios\.defaults" -S src backend | head -n 50
sed -n '1,220p' src/pages/Signup/Signup.tsx
sed -n '1,140p' src/pages/Login/Login.tsx
fd -t f "axios*.ts" src backend -a || true
fd -t f -g "*axios*" src backend || true
rg -n "from 'axios'|require\\(['\"]axios['\"]\\)" -S src backend || true

Repository: GitMetricsLab/github_tracker

Length of output: 14360


Fix misleading cookies comment on signup axios request

src/pages/Signup/Signup.tsx’s /api/auth/signup axios.post only passes url and data; there’s no withCredentials (and no global axios credentials config found—only a withCredentials: false elsewhere). Either remove the “Include cookies for session” comment or add explicit axios config.

✅ If cookies are required for this route
 const response = await axios.post(
   `${backendUrl}/api/auth/signup`,
-  formData, // Include cookies for session
+  formData,
+  { withCredentials: true },
 );
🤖 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 95 - 98, The comment "Include
cookies for session" on the axios.post in Signup.tsx is misleading because the
call only passes url and formData; either remove that comment or explicitly
enable credentials: update the axios.post call that sends formData to include an
axios config with withCredentials: true (or ensure a global axios default is set
to withCredentials: true) so cookies are actually sent; locate the axios.post
invocation in the Signup component and apply one of these two fixes to make the
behavior and comment consistent.

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.");
} 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.");
}
} finally {
setIsLoading(false);
}
Expand Down Expand Up @@ -138,10 +155,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 +183,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 @@ -170,18 +198,19 @@ 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
id="username"
type="text"
name="username"
placeholder="Enter your username"
value={formData.username}
onChange={handleChange}
<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>
)}
Comment on lines +201 to +213
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

Add explicit labels and error associations for all form fields.

The inputs rely on placeholders only. Please add programmatic labels (label + htmlFor) and connect inline errors with aria-describedby/role="alert" so screen-reader users get field context and validation feedback reliably.

♿ Suggested pattern (apply to username/email/password)
+ <label htmlFor="signup-username" className="sr-only">Username</label>
  <input
+   id="signup-username"
    type="text"
    name="username"
    placeholder="Enter your username"
    value={formData.username}
    onChange={handleChange}
    required
+   aria-invalid={Boolean(errors.username)}
+   aria-describedby={errors.username ? "signup-username-error" : undefined}
    className="..."
  />
- {errors.username && (
-   <p className="text-red-500 text-sm mt-2">{errors.username}</p>
- )}
+ {errors.username && (
+   <p id="signup-username-error" role="alert" className="text-red-500 text-sm mt-2">
+     {errors.username}
+   </p>
+ )}

Also applies to: 220-232, 240-265

🤖 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 200 - 212, The username input (and
other fields using formData, handleChange, errors and mode) lacks a programmatic
label and proper ARIA error association; add a <label htmlFor="username"> linked
to the input's id="username", set input's aria-describedby to an id for the
error message (e.g., username-error) and ensure the error <p> has that id plus
role="alert" and aria-live="assertive" so screen readers announce validation;
repeat the same pattern for email and password fields, keeping existing props
(value={formData...}, onChange={handleChange}, required) and preserving the
mode-based className logic.

</div>

<div>
Expand All @@ -190,18 +219,19 @@ const SignUp: React.FC = () => {
<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
id="email"
type="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
<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>
Expand All @@ -210,26 +240,32 @@ const SignUp: React.FC = () => {
<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
id="password"
type={showPassword ? "text" : "password"}
name="password"
placeholder="Enter your password"
value={formData.password}
onChange={handleChange}
<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"}
<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" />}
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 @@ -242,15 +278,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