Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 9 additions & 3 deletions frontend/src/components/PasswordInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,34 @@ const EyeSlashedIcon = () => (
);


const PasswordInput = ({ value, onChange, placeholder = "Password", id = "password", error }) => {
const PasswordInput = ({ value, onChange, placeholder = "Password", id = "password", error, className }) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);

const togglePasswordVisibility = () => {
setIsPasswordVisible(prevState => !prevState);
};

// Default classes if no className is provided
const defaultClasses = "w-full px-4 py-2 pr-10 mt-2 border rounded-md focus:outline-none focus:ring-1";
const errorClasses = error ? 'border-red-500 focus:ring-red-500' : 'focus:ring-blue-600 dark:border-gray-600';
const inputClasses = className || `${defaultClasses} ${errorClasses}`;

return (
<div className="relative">
<input
type={isPasswordVisible ? 'text' : 'password'}
placeholder={placeholder}
className={`w-full px-4 py-2 pr-10 mt-2 border rounded-md focus:outline-none focus:ring-1 ${error ? 'border-red-500 focus:ring-red-500' : 'focus:ring-blue-600 dark:border-gray-600'}`}
className={`${inputClasses} pr-10`}
id={id}
value={value}
onChange={onChange}
required
/>
<button
type="button"
className="absolute inset-y-0 right-0 top-1 flex items-center pr-3 text-gray-400 hover:text-gray-600"
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-200"
onClick={togglePasswordVisibility}
aria-label={isPasswordVisible ? "Hide password" : "Show password"}
>
{isPasswordVisible ? <EyeSlashedIcon /> : <EyeIcon />}
</button>
Expand Down
133 changes: 107 additions & 26 deletions frontend/src/pages/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import useAuth from '../hooks/useAuth';
import PasswordInput from '../components/PasswordInput';
import { HiMail, HiArrowRight } from 'react-icons/hi';

/* // Demo user credentials
const DEMO_EMAIL = 'test@example.com';
Expand All @@ -11,17 +12,21 @@ export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [serverError, setServerError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { login } = useAuth();

const handleSubmit = async (e) => {
e.preventDefault();
setServerError(''); // Clear previous server errors
setIsLoading(true);
try {
await login(email, password);
// Toast handled globally in AuthContext
} catch (error) {
setServerError(error.message);
// Toast handled globally in AuthContext
} finally {
setIsLoading(false);
}
};

Expand All @@ -31,41 +36,117 @@ export default function LoginPage() {
}; */

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900 px-4">

<Link to="/" className="text-4xl font-bold text-blue-600 dark:text-blue-400 font-montserrat mb-8 transition-all duration-500 hover:scale-105 hover:drop-shadow-lg hover:text-blue-500 dark:hover:text-blue-300 cursor-pointer" title="Go to home">
<div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 px-4 py-8">
{/* Logo/Brand */}
<Link
to="/"
className="text-5xl font-bold bg-gradient-to-r from-blue-600 to-blue-500 dark:from-blue-400 dark:to-blue-300 bg-clip-text text-transparent mb-12 transition-all duration-500 hover:scale-105 hover:drop-shadow-2xl cursor-pointer animate-fade-in"
title="Go to home"
>
Paisable
</Link>

<div className="px-8 py-6 text-left bg-white dark:bg-gray-800 shadow-lg rounded-lg w-full max-w-md">
<h3 className="text-2xl font-bold text-center text-gray-800 dark:text-gray-200">Login to your account</h3>
{serverError && <p className="text-center text-red-500 bg-red-100 dark:bg-red-900/50 p-2 rounded-md my-4">{serverError}</p>}
<form onSubmit={handleSubmit}>
<div className="mt-4">
<div>
<label className="block text-gray-700 dark:text-gray-300" htmlFor="email">Email</label>
<input type="email" placeholder="Email" className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600 dark:border-gray-600" id="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
</div>
<div className="mt-4">
<label className="block text-gray-700 dark:text-gray-300" htmlFor="password">Password</label>
<PasswordInput value={password} onChange={(e) => setPassword(e.target.value)} />
{/* Login Card */}
<div className="px-8 py-8 text-left bg-white dark:bg-gray-800 shadow-2xl rounded-2xl w-full max-w-md border border-gray-200 dark:border-gray-700 backdrop-blur-sm transform transition-all duration-300 hover:shadow-3xl">
{/* Header */}
<div className="text-center mb-8">
<h3 className="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-2">Welcome Back</h3>
<p className="text-gray-600 dark:text-gray-400 text-sm">Sign in to continue to your account</p>
</div>

{/* Error Message */}
{serverError && (
<div className="mb-6 p-4 rounded-lg bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 animate-shake">
<p className="text-sm text-red-600 dark:text-red-400 text-center font-medium">{serverError}</p>
</div>
)}

{/* Form */}
<form onSubmit={handleSubmit} className="space-y-6">
{/* Email Field */}
<div>
<label className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2" htmlFor="email">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<HiMail className="h-5 w-5 text-gray-400 dark:text-gray-500" />
</div>
<input
type="email"
placeholder="you@example.com"
className="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-50 dark:bg-gray-700/50 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="flex flex-col gap-4 mt-4">
<button type="submit" className="w-full px-6 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700">Login</button>
{/*<button
type="button"
onClick={handleFillDemoCredentials}
className="w-full px-6 py-2 text-blue-600 dark:text-blue-400 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600"
>
Fill Demo Credentials
</button>*/}
</div>

{/* Password Field */}
<div>
<label className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2" htmlFor="password">
Password
</label>
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-50 dark:bg-gray-700/50 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
/>
</div>

{/* Submit Button */}
<button
type="submit"
disabled={isLoading}
className="w-full px-6 py-3 text-white font-semibold bg-gradient-to-r from-blue-600 to-blue-500 rounded-lg hover:from-blue-700 hover:to-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transform transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none flex items-center justify-center gap-2"
>
{isLoading ? (
<>
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Signing in...
</>
) : (
<>
Sign In
<HiArrowRight className="h-5 w-5" />
</>
)}
</button>

{/* Divider */}
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300 dark:border-gray-600"></div>
</div>
<div className="mt-6 text-gray-600 dark:text-gray-400">
Don't have an account? <Link to="/register" className="text-blue-600 hover:underline ml-2">Register</Link>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">
New to Paisable?
</span>
</div>
</div>

{/* Register Link */}
<div className="text-center">
<Link
to="/register"
className="inline-flex items-center gap-1 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-semibold transition-colors duration-200"
>
Create an account
<HiArrowRight className="h-4 w-4" />
</Link>
</div>
</form>
</div>

{/* Footer */}
<p className="mt-8 text-sm text-gray-500 dark:text-gray-400 text-center">
Protected by industry-standard security
</p>
</div>
);
}
Loading