|
1 |
| -import React, { useState } from 'react'; |
2 |
| -import { Navigate, Link } from 'react-router-dom'; |
3 |
| -import { doSignInWithEmailAndPassword } from '../../firebase/auth'; |
4 |
| -import { useAuth } from '../../context/authContext/authContext'; |
5 |
| - |
6 |
| -const InputField = ({ label, type, value, onChange, required }) => ( |
7 |
| - <div> |
8 |
| - <label className="text-sm text-gray-600 font-bold">{label}</label> |
9 |
| - <input |
10 |
| - type={type} |
11 |
| - autoComplete={type === 'email' ? 'email' : 'current-password'} |
12 |
| - required={required} |
13 |
| - value={value} |
14 |
| - onChange={onChange} |
15 |
| - className="w-full mt-2 px-3 py-2 text-gray-500 bg-transparent outline-none border focus:border-indigo-600 shadow-sm rounded-lg transition duration-300" |
16 |
| - /> |
17 |
| - </div> |
18 |
| -); |
| 1 | +import { useState, useEffect } from "react"; |
| 2 | +import { useNavigate, Link } from "react-router-dom"; |
| 3 | +import { useAuth } from "../../context/authContext/authContext"; // 🔹 Import authentication context |
| 4 | +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
| 5 | +import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; |
| 6 | +import { motion } from "framer-motion"; // 🔹 Import Framer Motion for animations |
19 | 7 |
|
20 | 8 | const Login = () => {
|
21 |
| - const { userLoggedIn } = useAuth(); // Check if user is already logged in |
22 |
| - const [email, setEmail] = useState(''); |
23 |
| - const [password, setPassword] = useState(''); |
| 9 | + const { userLoggedIn, login } = useAuth(); // 🔹 Getting authentication functions |
| 10 | + const navigate = useNavigate(); |
| 11 | + const [email, setEmail] = useState(""); |
| 12 | + const [password, setPassword] = useState(""); |
24 | 13 | const [isSigningIn, setIsSigningIn] = useState(false);
|
25 |
| - const [error, setError] = useState(null); // Error state for login errors |
| 14 | + const [error, setError] = useState(null); |
| 15 | + const [showPassword, setShowPassword] = useState(false); // 🔹 State for password visibility |
| 16 | + |
| 17 | + useEffect(() => { |
| 18 | + if (userLoggedIn) { |
| 19 | + navigate("/"); // 🔹 Redirect user if already logged in |
| 20 | + } |
| 21 | + }, [userLoggedIn, navigate]); |
| 22 | + |
| 23 | + const validateForm = () => { |
| 24 | + if (!email || !password) { |
| 25 | + setError("Email and Password are required."); |
| 26 | + return false; |
| 27 | + } |
| 28 | + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| 29 | + if (!emailRegex.test(email)) { |
| 30 | + setError("Invalid email format."); |
| 31 | + return false; |
| 32 | + } |
| 33 | + if (password.length < 6) { |
| 34 | + setError("Password must be at least 6 characters."); |
| 35 | + return false; |
| 36 | + } |
| 37 | + return true; |
| 38 | + }; |
26 | 39 |
|
27 | 40 | const onSubmit = async (e) => {
|
28 | 41 | e.preventDefault();
|
29 |
| - |
30 |
| - if (!isSigningIn) { |
| 42 | + if (!isSigningIn && validateForm()) { |
31 | 43 | setIsSigningIn(true);
|
32 |
| - setError(null); // Reset error state |
| 44 | + setError(null); |
33 | 45 |
|
34 | 46 | try {
|
35 |
| - await doSignInWithEmailAndPassword(email, password); |
| 47 | + await login(email, password); // 🔹 Calling login function from auth context |
36 | 48 | } catch (error) {
|
37 |
| - setError('Invalid email or password.'); // Show custom error message |
| 49 | + setError(error.message || "Invalid email or password."); |
38 | 50 | console.error("Login error:", error.message);
|
39 | 51 | } finally {
|
40 |
| - setIsSigningIn(false); // Reset loading state |
| 52 | + setIsSigningIn(false); |
41 | 53 | }
|
42 | 54 | }
|
43 | 55 | };
|
44 | 56 |
|
45 |
| - if (userLoggedIn) { |
46 |
| - return <Navigate to="/" replace />; // Redirect to home if logged in |
47 |
| - } |
48 |
| - |
49 | 57 | return (
|
50 |
| - <div> |
51 |
| - <main className="w-full h-screen flex items-center justify-center"> |
52 |
| - <div className="w-96 text-gray-600 space-y-5 p-4 shadow-xl border rounded-xl"> |
53 |
| - <div className="text-center"> |
54 |
| - <h3 className="mt-2 text-gray-800 text-xl font-semibold sm:text-2xl">Welcome Back</h3> |
55 |
| - </div> |
56 |
| - <form onSubmit={onSubmit} className="space-y-5"> |
57 |
| - <InputField |
58 |
| - label="Email" |
59 |
| - type="email" |
60 |
| - value={email} |
61 |
| - onChange={(e) => setEmail(e.target.value)} |
62 |
| - required |
| 58 | + <main className="w-full h-screen flex items-center justify-center"> |
| 59 | + {/* 🔹 Animated Login Form */} |
| 60 | + <motion.div |
| 61 | + initial={{ opacity: 0, y: -50 }} |
| 62 | + animate={{ opacity: 1, y: 0 }} |
| 63 | + transition={{ duration: 0.6, ease: "easeOut" }} |
| 64 | + className="w-96 text-gray-600 space-y-5 p-4 shadow-xl border rounded-xl" |
| 65 | + > |
| 66 | + <div className="text-center"> |
| 67 | + <h3 className="mt-2 text-gray-800 text-xl font-semibold sm:text-2xl">Welcome Back</h3> |
| 68 | + </div> |
| 69 | + <form onSubmit={onSubmit} className="space-y-5"> |
| 70 | + <div> |
| 71 | + <label className="text-sm text-gray-600 font-bold">Email</label> |
| 72 | + <input |
| 73 | + type="email" |
| 74 | + autoComplete="email" |
| 75 | + required |
| 76 | + value={email} |
| 77 | + onChange={(e) => setEmail(e.target.value)} |
| 78 | + className="w-full mt-2 px-3 py-2 text-gray-500 bg-transparent outline-none border focus:border-indigo-600 shadow-sm rounded-lg transition duration-300" |
63 | 79 | />
|
64 |
| - <InputField |
65 |
| - label="Password" |
66 |
| - type="password" |
67 |
| - value={password} |
68 |
| - onChange={(e) => setPassword(e.target.value)} |
69 |
| - required |
| 80 | + </div> |
| 81 | + |
| 82 | + {/* 🔹 Password Field with Show/Hide Toggle */} |
| 83 | + <div className="relative"> |
| 84 | + <label className="text-sm text-gray-600 font-bold">Password</label> |
| 85 | + <input |
| 86 | + type={showPassword ? "text" : "password"} |
| 87 | + autoComplete="current-password" |
| 88 | + required |
| 89 | + value={password} |
| 90 | + onChange={(e) => setPassword(e.target.value)} |
| 91 | + className="w-full mt-2 px-3 py-2 text-gray-500 bg-transparent outline-none border focus:border-indigo-600 shadow-sm rounded-lg transition duration-300 pr-10" |
70 | 92 | />
|
| 93 | + {/* 👁 Password Visibility Toggle */} |
| 94 | + <motion.span |
| 95 | + className="absolute top-10 right-3 cursor-pointer text-gray-500 hover:text-indigo-600 transition" |
| 96 | + onClick={() => setShowPassword(!showPassword)} |
| 97 | + transition={{ duration: 0.3, ease: "easeInOut" }} |
| 98 | + > |
| 99 | + <FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} size="lg" /> |
| 100 | + </motion.span> |
| 101 | + </div> |
71 | 102 |
|
72 |
| - {error && ( |
73 |
| - <p className="text-red-500 text-sm text-center" aria-live="assertive">{error}</p> |
74 |
| - )} |
| 103 | + {error && <p className="text-red-500 text-sm text-center" aria-live="assertive">{error}</p>} |
75 | 104 |
|
76 |
| - <button |
77 |
| - type="submit" |
78 |
| - disabled={isSigningIn} |
79 |
| - className={`w-full py-2 text-white font-bold rounded-lg transition duration-300 |
80 |
| - ${isSigningIn ? 'bg-gray-400' : 'bg-indigo-600 hover:bg-indigo-700'}`} |
81 |
| - > |
82 |
| - {isSigningIn ? ( |
83 |
| - <span className="flex items-center justify-center"> |
84 |
| - <svg className="animate-spin h-5 w-5 mr-3" viewBox="0 0 24 24"> |
85 |
| - <circle className="text-indigo-200" cx="12" cy="12" r="10" strokeWidth="4" /> |
86 |
| - <path className="text-indigo-600" fill="currentColor" d="M4 12c0 1.1.9 2 2 2h1c0-2.21-1.79-4-4-4v2zm16-2c-1.1 0-2 .9-2 2h2c0-1.1-.9-2-2-2zm-2 0c0 2.21 1.79 4 4 4v-2h-1c-1.1 0-2-.9-2-2h-2zm-4 6c2.21 0 4-1.79 4-4h-2c0 1.1-.9 2-2 2v2z" /> |
87 |
| - </svg> |
88 |
| - Signing In... |
89 |
| - </span> |
90 |
| - ) : 'Sign In'} |
91 |
| - </button> |
92 |
| - </form> |
93 |
| - <p className="text-center text-sm"> |
94 |
| - Don't have an account? <Link to="/register" className="hover:underline font-bold">Sign up</Link> |
95 |
| - </p> |
96 |
| - <p className="text-center text-sm"> |
97 |
| - <Link to="/forgot-password" className="hover:underline">Forgot Password?</Link> |
98 |
| - </p> |
99 |
| - </div> |
100 |
| - </main> |
101 |
| - </div> |
| 105 | + {/* 🔹 Submit Button with Animation */} |
| 106 | + <motion.button |
| 107 | + type="submit" |
| 108 | + disabled={isSigningIn} |
| 109 | + whileHover={{ scale: 1.05 }} |
| 110 | + whileTap={{ scale: 0.95 }} |
| 111 | + className={`w-full py-2 text-white font-bold rounded-lg transition duration-300 |
| 112 | + ${isSigningIn ? "bg-gray-400" : "bg-indigo-600 hover:bg-indigo-700"}`} |
| 113 | + > |
| 114 | + {isSigningIn ? "Signing In..." : "Sign In"} |
| 115 | + </motion.button> |
| 116 | + </form> |
| 117 | + |
| 118 | + <p className="text-center text-sm"> |
| 119 | + Don't have an account? <Link to="/register" className="hover:underline font-bold">Sign up</Link> |
| 120 | + </p> |
| 121 | + <p className="text-center text-sm"> |
| 122 | + <Link to="/forgot-password" className="hover:underline">Forgot Password?</Link> |
| 123 | + </p> |
| 124 | + </motion.div> |
| 125 | + </main> |
102 | 126 | );
|
103 | 127 | };
|
104 | 128 |
|
|
0 commit comments