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
14 changes: 10 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { useLocation } from "react-router-dom";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import ScrollProgressBar from "./components/ScrollProgressBar";
import { Toaster } from "react-hot-toast";
import Router from "./Routes/Router";
import ThemeWrapper from "./context/ThemeContext";

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

function App() {
const location = useLocation();
const isFullscreen = FULLSCREEN_ROUTES.includes(location.pathname);

return (
<ThemeWrapper>
<div className="relative flex flex-col min-h-screen">
<ScrollProgressBar />
{!isFullscreen && <ScrollProgressBar />}

<Navbar />
{!isFullscreen && <Navbar />}

<main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-center">
<main className={`flex justify-center items-center ${isFullscreen ? "flex-1" : "flex-grow bg-gray-50 dark:bg-gray-800"}`}>
<Router />
</main>

<Footer />
{!isFullscreen && <Footer />}

<Toaster
position="top-center"
Expand Down
233 changes: 146 additions & 87 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React, { useState } from "react";
import React, { useState, useContext } from "react";
import axios from "axios";
import { useNavigate, Link } from "react-router-dom";
import { User, Mail, Lock } from "lucide-react";
import { motion } from "framer-motion";
import { User, Mail, Lock, Eye, EyeOff } from "lucide-react";
import { ThemeContext } from "../../context/ThemeContext";
import type { ThemeContextType } from "../../context/ThemeContext";

const backendUrl = import.meta.env.VITE_BACKEND_URL;

Expand All @@ -17,9 +20,13 @@ const SignUp: React.FC = () => {
email: "",
password: "",
});

const [message, setMessage] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [showPassword, setShowPassword] = useState<boolean>(false);

const navigate = useNavigate();
const themeContext = useContext(ThemeContext) as ThemeContextType;
const { mode } = themeContext;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
Expand All @@ -28,163 +35,215 @@ const SignUp: React.FC = () => {

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await axios.post(
`${backendUrl}/api/auth/signup`,
formData
);
setIsLoading(true);

try {
const response = await axios.post(`${backendUrl}/api/auth/signup`, formData);
setMessage(response.data.message);
Comment on lines 36 to +42
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

Prevent duplicate submit requests in handleSubmit.

Line 38 sets loading, but there’s no early guard; fast repeated Enter/click can still trigger multiple POSTs before re-render.

Suggested fix
 const handleSubmit = async (e: React.FormEvent) => {
   e.preventDefault();
+  if (isLoading) return;
   setIsLoading(true);
📝 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
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
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') {
navigate("/login");}
// // Simulate API call (replace with your actual backend integration)
// try {
// // Mock successful signup
// setMessage("Account created successfully! Redirecting to login...");
// // In your actual implementation, integrate with your backend here:
// // const response = await fetch(`${backendUrl}/api/auth/signup`, {
// // method: 'POST',
// // headers: { 'Content-Type': 'application/json' },
// // body: JSON.stringify(formData)
// // });
// setTimeout(() => {
// // Navigate to login page in your actual implementation
// console.log("Redirecting to login page...");
// }, 2000);
} catch (error) {
setMessage("Something went wrong. Please try again.");
const response = await axios.post(`${backendUrl}/api/auth/signup`, formData);
setMessage(response.data.message);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isLoading) return;
setIsLoading(true);
try {
const response = await axios.post(`${backendUrl}/api/auth/signup`, formData);
setMessage(response.data.message);
🤖 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 36 - 42, handleSubmit can be
invoked multiple times before React updates state, so add an early guard using a
mutable ref (e.g., isSubmittingRef) to block duplicate POSTs: at the top of
handleSubmit return immediately if isSubmittingRef.current is true, then set
isSubmittingRef.current = true and setIsLoading(true) before the try, and reset
isSubmittingRef.current = false (and setIsLoading(false)) in the finally block;
reference handleSubmit, isSubmittingRef, setIsLoading to locate where to
implement this.


if (response.data.message === "User created successfully") {
navigate("/login");
}
Comment on lines +44 to +46
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

Don’t gate redirect on an exact response message string.

Line 44 couples success flow to "User created successfully". Any backend wording change can leave users stuck on signup even after successful creation.

Suggested fix
-      if (response.data.message === "User created successfully") {
+      if (response.status >= 200 && response.status < 300) {
         navigate("/login");
       }
📝 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
if (response.data.message === "User created successfully") {
navigate("/login");
}
if (response.status >= 200 && response.status < 300) {
navigate("/login");
}
🤖 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 44 - 46, The redirect is currently
gated on an exact response.data.message string ("User created successfully"),
which is fragile; update the success check in the signup handler (the code that
calls navigate("/login") using the response variable) to rely on a stable
indicator such as HTTP status (e.g., response.status in the 2xx range), a
boolean success flag (response.data.success === true), or presence of a created
user id (response.data.user?.id) instead of the verbose message, and then call
navigate("/login") when that stable condition is met; also keep existing error
handling paths intact so failures still surface to the user.

} catch (error) {
setMessage("Something went wrong. Please try again.");
} catch (error: any) {
setMessage(error.response?.data?.message || "Something went wrong. Please try again.");
} finally {
setIsLoading(false);
}
};

return (
<div className="relative h-screen w-screen
bg-gradient-to-br
from-indigo-200 via-purple-200 to-pink-200
dark:from-indigo-900 dark:via-purple-800 dark:to-pink-700
flex items-center justify-center px-4 overflow-hidden">

{/* Background effects */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-40 -right-32 w-80 h-80 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
<div className="absolute -bottom-40 -left-32 w-80 h-80 bg-pink-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
<div
className={`min-h-screen h-full w-full flex items-center justify-center relative overflow-hidden ${
mode === "dark"
? "bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900"
: "bg-gradient-to-br from-slate-100 via-purple-100 to-slate-100"
}`}
>
<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">
{/* Logo */}
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-20 h-20
bg-white dark:bg-gray-200 rounded-3xl mb-6 shadow-2xl
transform hover:scale-105 transition duration-300 overflow-hidden">
<div className="relative w-full max-w-md 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>

<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
<h1
className={`text-4xl font-bold bg-clip-text text-transparent mb-2 ${
mode === "dark"
? "bg-gradient-to-r from-purple-300 via-pink-300 to-indigo-300"
: "bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600"
}`}
>
GitHubTracker
</h1>

<p className="text-purple-600 dark:text-purple-200 text-lg">
<p
className={`${mode === "dark" ? "text-slate-300" : "text-gray-700"} text-lg font-medium`}
>
Join your GitHub journey
</p>
</div>

{/* Form Card */}
<div className="bg-white/80 dark:bg-white/10 backdrop-blur-lg
rounded-3xl p-8 border border-gray-200 dark:border-white/20 shadow-2xl">

<h2 className="text-2xl font-semibold text-gray-900 dark:text-white text-center mb-8">
</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-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>

<div className="space-y-6">
{/* Username */}
<form onSubmit={handleSubmit} className="space-y-6">
<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-purple-500 dark:text-purple-300" />
<User
className={`h-5 w-5 ${mode === "dark" ? "text-purple-300" : "text-purple-400"}`}
/>
</div>
<input
type="text"
name="username"
placeholder="Enter your username"
value={formData.username}
onChange={handleChange}
autoComplete="username"
required
className="w-full pl-12 pr-4 py-4
bg-white/70 dark:bg-white/10
border border-gray-300 dark:border-white/20
rounded-2xl text-gray-900 dark:text-white
placeholder-gray-500 dark:placeholder-purple-300
focus:outline-none focus:ring-2 focus:ring-purple-400 transition"
className={`w-full pl-12 pr-4 py-4 rounded-2xl focus:outline-none transition-all ${
mode === "dark"
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
}`}
/>
</div>

{/* Email */}
<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-purple-500 dark:text-purple-300" />
<Mail
className={`h-5 w-5 ${mode === "dark" ? "text-purple-300" : "text-purple-400"}`}
/>
</div>
<input
type="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
autoComplete="email"
required
className="w-full pl-12 pr-4 py-4
bg-white/70 dark:bg-white/10
border border-gray-300 dark:border-white/20
rounded-2xl text-gray-900 dark:text-white
placeholder-gray-500 dark:placeholder-purple-300
focus:outline-none focus:ring-2 focus:ring-purple-400 transition"
className={`w-full pl-12 pr-4 py-4 rounded-2xl focus:outline-none transition-all ${
mode === "dark"
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
}`}
/>
</div>

{/* Password */}
<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-purple-500 dark:text-purple-300" />
<Lock
className={`h-5 w-5 ${mode === "dark" ? "text-purple-300" : "text-purple-400"}`}
/>
</div>
<input
type="password"
type={showPassword ? "text" : "password"}
name="password"
placeholder="Enter your password"
value={formData.password}
onChange={handleChange}
autoComplete="new-password"
required
className="w-full pl-12 pr-4 py-4
bg-white/70 dark:bg-white/10
border border-gray-300 dark:border-white/20
rounded-2xl text-gray-900 dark:text-white
placeholder-gray-500 dark:placeholder-purple-300
focus:outline-none focus:ring-2 focus:ring-purple-400 transition"
className={`w-full pl-12 pr-12 py-4 rounded-2xl focus:outline-none transition-all ${
mode === "dark"
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
}`}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className={`absolute inset-y-0 right-0 pr-4 flex items-center ${
mode === "dark" ? "text-slate-400 hover:text-white" : "text-gray-500 hover:text-gray-800"
} transition-colors duration-200`}
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>

{/* Button */}
<button
onClick={handleSubmit}
className="w-full bg-gradient-to-r from-purple-500 to-pink-500
text-white font-semibold py-4 rounded-2xl
hover:from-purple-600 hover:to-pink-600
transform hover:scale-105 transition shadow-lg"
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 disabled:opacity-50 disabled:cursor-not-allowed"
>
Create Account
{isLoading ? "Creating account..." : "Create Account"}
</button>
</div>
</form>

{/* Message */}
{message && (
<div className={`text-center mt-6 p-3 rounded-xl ${
message.includes("successfully")
? "text-green-600 bg-green-100 dark:text-green-300 dark:bg-green-500/20"
: "text-red-600 bg-red-100 dark:text-red-300 dark:bg-red-500/20"
}`}>
<div
className={`mt-6 p-4 rounded-2xl text-center text-sm font-medium ${
message.includes("successfully")
? "bg-green-500/20 text-green-300 border border-green-500/30"
: "bg-red-500/20 text-red-300 border border-red-500/30"
}`}
>
{message}
</div>
)}

{/* Footer */}
<div className="text-center mt-8">
<p className="text-gray-700 dark:text-purple-200">
Already have an account?{" "}
<Link to="/login">
<span className="text-purple-600 dark:text-purple-300 hover:text-purple-800 dark:hover:text-white font-medium">
Sign in here
</span>
<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>
</div>
</div>
</motion.div>
</div>

<div
className={`${
mode === "dark" ? "from-slate-900" : "from-slate-100"
} absolute bottom-0 left-0 w-full h-20 bg-gradient-to-t to-transparent`}
/>
</div>
);
};

export default SignUp;
export default SignUp;
Loading