Skip to content
Closed
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
344 changes: 344 additions & 0 deletions backend/bun.lock

Large diffs are not rendered by default.

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"];

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
2 changes: 1 addition & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
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 @@ -112,7 +112,7 @@ export const useGitHubData = (
const shouldFetchPrs =
activeTab === 'pr' || activeTab === 'both';

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

if (shouldFetchIssues) {
requests.push(
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Login: React.FC = () => {
<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">
<div className="relative w-full max-w-md p-10 sm:px-6">
{/* Branding */}
<div 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">
Expand Down
136 changes: 102 additions & 34 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
);
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 @@ -130,18 +147,30 @@ const SignUp: React.FC = () => {
/>
</div>

<div className="relative w-full max-w-md px-4 sm:px-6">
<div className="relative w-full max-w-md p-10 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" />
<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 @@ -169,39 +197,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 +275,22 @@ 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"}`}>
<div className="text-center mt-8 pb-8">
<p
className={`${mode === "dark" ? "text-slate-500" : "text-gray-600"} text-sm`}
>
Already have an account?
<Link
to="/login"
className="ml-1 text-purple-400 hover:text-purple-300 transition-colors duration-300"
>
Sign in here
</Link>
</p>
Expand Down