Skip to content
Open
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
325 changes: 325 additions & 0 deletions components/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
import React, { useState } from 'react';
import { Mail, Lock, User, ArrowRight, Loader2, Eye, EyeOff, ArrowLeft } from 'lucide-react';

type AuthView = 'login' | 'register' | 'forgot-password' | 'reset-sent';

const Auth: React.FC = () => {
const [authView, setAuthView] = useState<AuthView>('login');
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
confirmPassword: ''
});

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
if (authView === 'forgot-password') {
setAuthView('reset-sent');
}
}, 1500);
};
Comment on lines +17 to +26
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form submission handler doesn't perform any validation. For the register view, it should validate that the password meets the stated requirement of "at least 8 characters" and that password and confirmPassword match before proceeding. For the login view, it should validate the email format. The lack of validation could lead to poor user experience and security issues.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Authentication handlers are incomplete placeholders.

The handleSubmit function only simulates a delay and handles the forgot-password view transition. Login and register submissions don't perform any actual authentication or validation—form data is collected but never used.

If this is intentional scaffolding, consider adding a TODO comment. Otherwise, integrate actual authentication logic or callbacks.

Would you like me to suggest a pattern for handling auth with callbacks/props or integration with an auth provider?

🤖 Prompt for AI Agents
In components/Auth.tsx around lines 17 to 26, handleSubmit is only a simulated
delay and only transitions the forgot-password view; it doesn't use form data,
perform validation, or call any authentication logic. Replace the placeholder
with real behavior: collect and validate the form values, setIsLoading(true),
then based on authView call the appropriate async handler (e.g. props.onLogin,
props.onRegister, or props.onForgotPassword) or an auth provider API, await the
result, handle success (change view or redirect) and errors (set error state),
and finally setIsLoading(false); if this is intentionally scaffolding instead,
add a clear TODO comment explaining expected handlers and props to be
implemented.


const renderLogin = () => (
<div className="w-full max-w-md mx-auto animate-fadeIn">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-white mb-2">Welcome Back</h1>
<p className="text-gray-400">Sign in to continue to StellarMail</p>
</div>

<div className="bg-[#1a1a1a] rounded-xl border border-gray-800 p-8">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Email Address</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail size={18} className="text-gray-500" />
</div>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full pl-10 pr-4 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="alex@stellar.io"
required
/>
</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Password</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock size={18} className="text-gray-500" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full pl-10 pr-12 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="••••••••"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The password visibility toggle button is missing an aria-label attribute. This would help screen reader users understand the button's purpose. Consider adding aria-label="Toggle password visibility" or similar text.

Suggested change
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
aria-label="Toggle password visibility"

Copilot uses AI. Check for mistakes.
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>

<div className="flex items-center justify-between text-sm">
<label className="flex items-center text-gray-400 cursor-pointer hover:text-white">
<input type="checkbox" className="mr-2 rounded border-gray-700 bg-gray-950" />
Remember me
</label>
Comment on lines +78 to +82
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

"Remember me" checkbox state is not tracked.

The checkbox is rendered but its value isn't stored in state or used anywhere. Either add state to track it or remove the checkbox if not needed.

🤖 Prompt for AI Agents
In components/Auth.tsx around lines 78 to 82, the "Remember me" checkbox is
rendered but its checked value isn't tracked; add a React state hook (e.g.,
useState<boolean>) to hold the remember flag, bind the checkbox's checked prop
to that state, add an onChange handler to toggle/update the state, and ensure
the form submission or login handler reads that state (or persist it to
localStorage/cookie as intended); alternatively, if the feature is not required,
remove the checkbox markup to avoid dead UI.

<button
type="button"
onClick={() => setAuthView('forgot-password')}
className="text-primary-400 hover:text-primary-300 font-medium"
>
Forgot password?
</button>
</div>

<button
type="submit"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 bg-primary-600 hover:bg-primary-500 text-white font-medium py-3 rounded-lg transition-colors shadow-lg shadow-primary-900/50 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<>
<Loader2 size={18} className="animate-spin" />
Signing in...
</>
) : (
<>
Sign In
<ArrowRight size={18} />
</>
)}
</button>
</form>

<div className="mt-6 text-center text-sm text-gray-400">
Don't have an account?{' '}
<button
onClick={() => setAuthView('register')}
className="text-primary-400 hover:text-primary-300 font-medium"
>
Create one
</button>
</div>
</div>
</div>
);

const renderRegister = () => (
<div className="w-full max-w-md mx-auto animate-fadeIn">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-white mb-2">Create Account</h1>
<p className="text-gray-400">Start your email marketing journey</p>
</div>

<div className="bg-[#1a1a1a] rounded-xl border border-gray-800 p-8">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Full Name</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<User size={18} className="text-gray-500" />
</div>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full pl-10 pr-4 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="Alex Morgan"
required
/>
</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Email Address</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail size={18} className="text-gray-500" />
</div>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full pl-10 pr-4 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="alex@stellar.io"
required
/>
</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Password</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock size={18} className="text-gray-500" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full pl-10 pr-12 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="••••••••"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the login form, this password visibility toggle button is missing an aria-label attribute for screen reader accessibility.

Suggested change
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white"
aria-label={showPassword ? "Hide password" : "Show password"}

Copilot uses AI. Check for mistakes.
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
<p className="mt-1 text-xs text-gray-500">Must be at least 8 characters</p>
</div>

<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Confirm Password</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock size={18} className="text-gray-500" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
className="w-full pl-10 pr-4 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="••••••••"
required
/>
</div>
</div>
Comment on lines +189 to +207
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Password validation is displayed but not enforced.

The UI shows "Must be at least 8 characters" (line 189), and there's a confirmPassword field, but neither constraint is validated before submission. Users could submit weak passwords or mismatched confirmations.

Add validation in handleSubmit for the register flow:

 const handleSubmit = async (e: React.FormEvent) => {
   e.preventDefault();
+  if (authView === 'register') {
+    if (formData.password.length < 8) {
+      // Show error: password too short
+      return;
+    }
+    if (formData.password !== formData.confirmPassword) {
+      // Show error: passwords don't match
+      return;
+    }
+  }
   setIsLoading(true);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/Auth.tsx around lines 189 to 207, the form shows a password rule
and confirmPassword field but there is no enforcement in handleSubmit for the
register flow; update handleSubmit to validate that formData.password is at
least 8 characters and that formData.confirmPassword matches formData.password,
prevent submission if validations fail, and set/display appropriate error
state/messages (e.g., setError or formErrors) so the user sees why submission
was blocked; ensure early return on validation failure and only proceed with the
registration request when checks pass.


<button
type="submit"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 bg-primary-600 hover:bg-primary-500 text-white font-medium py-3 rounded-lg transition-colors shadow-lg shadow-primary-900/50 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<>
<Loader2 size={18} className="animate-spin" />
Creating account...
</>
) : (
<>
Create Account
<ArrowRight size={18} />
</>
)}
</button>
</form>

<div className="mt-6 text-center text-sm text-gray-400">
Already have an account?{' '}
<button
onClick={() => setAuthView('login')}
className="text-primary-400 hover:text-primary-300 font-medium"
>
Sign in
</button>
</div>
</div>
</div>
);

const renderForgotPassword = () => (
<div className="w-full max-w-md mx-auto animate-fadeIn">
<button
onClick={() => setAuthView('login')}
className="flex items-center gap-2 text-gray-400 hover:text-white mb-6 transition-colors"
>
<ArrowLeft size={18} />
Back to login
</button>

<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-white mb-2">Forgot Password?</h1>
<p className="text-gray-400">We'll send you a reset link</p>
</div>

<div className="bg-[#1a1a1a] rounded-xl border border-gray-800 p-8">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">Email Address</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail size={18} className="text-gray-500" />
</div>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full pl-10 pr-4 py-3 bg-gray-950 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 transition-colors"
placeholder="alex@stellar.io"
required
/>
</div>
</div>

<button
type="submit"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 bg-primary-600 hover:bg-primary-500 text-white font-medium py-3 rounded-lg transition-colors shadow-lg shadow-primary-900/50 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<>
<Loader2 size={18} className="animate-spin" />
Sending...
</>
) : (
<>
Send Reset Link
<ArrowRight size={18} />
</>
)}
</button>
</form>
</div>
</div>
);

const renderResetSent = () => (
<div className="w-full max-w-md mx-auto animate-fadeIn text-center">
<div className="w-16 h-16 bg-primary-500/20 rounded-full flex items-center justify-center mx-auto mb-6">
<Mail size={32} className="text-primary-400" />
</div>
<h1 className="text-3xl font-bold text-white mb-2">Check Your Email</h1>
<p className="text-gray-400 mb-8">
We've sent a password reset link to <strong className="text-white">{formData.email}</strong>
</p>
<button
onClick={() => setAuthView('login')}
className="text-primary-400 hover:text-primary-300 font-medium"
>
Return to login
</button>
</div>
);

return (
<div className="min-h-screen flex items-center justify-center p-4">
{authView === 'login' && renderLogin()}
{authView === 'register' && renderRegister()}
{authView === 'forgot-password' && renderForgotPassword()}
{authView === 'reset-sent' && renderResetSent()}
</div>
);
};

export default Auth;
Loading