|
1 | 1 | import React, { useState } from 'react' |
2 | | -import { Link, useNavigate } from 'react-router-dom' |
3 | | -import robotImg from '../../../assets/login.png' |
4 | | -import React, { useState, useEffect } from 'react' |
5 | | -import { Link, useNavigate } from 'react-router-dom' |
6 | | -import robotImg from '../../../assets/robot.png' |
7 | | -import aiImg from '../../../assets/Artificial intelligence.png' |
8 | | -import apiService from '../../../core/api.service' |
| 2 | +import { useNavigate } from 'react-router-dom' |
9 | 3 |
|
10 | | -const LoginPage: React.FC = () => { |
11 | | - const navigate = useNavigate() |
12 | | - const glowStyles = ` |
13 | | - @keyframes glow { |
14 | | - 0%, 100% { filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5)); } |
15 | | - 50% { filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.8)); } |
16 | | - } |
17 | | - .logo-glow { |
18 | | - animation: glow 3s ease-in-out infinite; |
19 | | - } |
20 | | - ` |
| 4 | +interface ApiService { |
| 5 | + login: ( |
| 6 | + email: string, |
| 7 | + password: string |
| 8 | + ) => Promise<{ success: boolean; data?: unknown; error?: string }> |
| 9 | +} |
| 10 | + |
| 11 | +interface LoginPageProps { |
| 12 | + apiService: ApiService |
| 13 | +} |
| 14 | + |
| 15 | +const LoginPage: React.FC<LoginPageProps> = ({ apiService }) => { |
21 | 16 | const [email, setEmail] = useState('') |
22 | 17 | const [password, setPassword] = useState('') |
23 | | - const [rememberMe, setRememberMe] = useState(false) |
24 | | - const [loading, setLoading] = useState(false) |
25 | | - const [isLoading, setIsLoading] = useState(false) |
26 | 18 | const [error, setError] = useState('') |
27 | | - const [success, setSuccess] = useState('') |
28 | | - |
29 | | - const handleSubmit = async (e: React.FormEvent) => { |
30 | | - e.preventDefault() |
31 | | - setError('') |
32 | | - setSuccess('') |
33 | | - setLoading(true) |
34 | | - |
35 | | - try { |
36 | | - const response = await fetch('/api/v1/user/login', { |
37 | | - method: 'POST', |
38 | | - headers: { |
39 | | - 'Content-Type': 'application/json', |
40 | | - }, |
41 | | - body: JSON.stringify({ email, password }), |
42 | | - }) |
43 | | - |
44 | | - const data = await response.json() |
45 | | - |
46 | | - if (!response.ok) { |
47 | | - setError(data.message || 'Login failed. Please try again.') |
48 | | - setLoading(false) |
49 | | - return |
50 | | - } |
51 | | - |
52 | | - // Store token in localStorage |
53 | | - if (data.data?.token) { |
54 | | - localStorage.setItem('token', data.data.token) |
55 | | - localStorage.setItem('user', JSON.stringify(data.data.user || {})) |
56 | | - |
57 | | - // Store remember me preference |
58 | | - if (rememberMe) { |
59 | | - localStorage.setItem('rememberMe', 'true') |
60 | | - localStorage.setItem('savedEmail', email) |
61 | | - } |
62 | | - |
63 | | - setSuccess('Login successful! Redirecting...') |
64 | | - |
65 | | - // Redirect after brief delay |
66 | | - setTimeout(() => { |
67 | | - navigate('/dashboard') |
68 | | - }, 1000) |
69 | | - } |
70 | | - } catch (err) { |
71 | | - setError('An error occurred. Please check your connection and try again.') |
72 | | - console.error('Login error:', err) |
73 | | - } finally { |
74 | | - setLoading(false) |
75 | | - } |
76 | | - } |
77 | | - setIsLoading(true) |
| 19 | + const navigate = useNavigate() |
78 | 20 |
|
| 21 | + const handleLogin = async () => { |
79 | 22 | try { |
| 23 | + setError('') |
80 | 24 | const response = await apiService.login(email, password) |
81 | 25 |
|
82 | | - if (!response.success) { |
83 | | - throw new Error(response.message || 'Login failed. Please try again.') |
84 | | - } |
85 | | - |
86 | | - setSuccess('Login successful! Redirecting...') |
87 | | - |
88 | | - // Store token if provided |
89 | | - if (response.data?.token) { |
90 | | - localStorage.setItem('authToken', response.data.token) |
91 | | - } |
92 | | - |
93 | | - // Store user info if remember me is checked |
94 | | - if (rememberMe) { |
95 | | - localStorage.setItem('rememberedEmail', email) |
| 26 | + if (response.success) { |
| 27 | + navigate('/dashboard') |
96 | 28 | } else { |
97 | | - localStorage.removeItem('rememberedEmail') |
| 29 | + setError(response.error || 'Login failed') |
98 | 30 | } |
99 | | - |
100 | | - // Redirect to dashboard after a short delay |
101 | | - setTimeout(() => { |
102 | | - navigate('/dashboard') |
103 | | - }, 1000) |
104 | | - } catch (err: any) { |
105 | | - setError(err.message || 'An error occurred during login. Please try again.') |
106 | | - console.error('Login error:', err) |
107 | | - } finally { |
108 | | - setIsLoading(false) |
| 31 | + } catch (err) { |
| 32 | + setError('An error occurred during login') |
| 33 | + console.error(err) |
109 | 34 | } |
110 | 35 | } |
111 | 36 |
|
112 | | - // Load remembered email on component mount |
113 | | - useEffect(() => { |
114 | | - const rememberedEmail = localStorage.getItem('rememberedEmail') |
115 | | - if (rememberedEmail) { |
116 | | - setEmail(rememberedEmail) |
117 | | - setRememberMe(true) |
118 | | - } |
119 | | - }, []) |
120 | | - <img |
121 | | - src={aiImg} |
122 | | - alt="Artificial Intelligence Logo" |
123 | | - className="logo-glow w-20 sm:w-24 h-20 sm:h-24 object-cover rounded-full shadow-2xl" |
124 | | - /> |
125 | | - </div> |
126 | | - |
127 | | - <div className="w-full max-w-7xl bg-[#181818] rounded-3xl shadow-2xl overflow-hidden grid grid-cols-1 md:grid-cols-2"> |
128 | | - {/* Left Section */} |
129 | | - <div className="bg-[#181818] p-6 sm:p-8 md:p-12 lg:p-16 xl:p-20 flex flex-col justify-center"> |
130 | | - <h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-white mb-1 sm:mb-2"> |
131 | | - Welcome Back |
132 | | - </h1> |
133 | | - <p className="text-gray-400 text-xs sm:text-sm md:text-base mb-6 md:mb-8"> |
134 | | - Sign in to your account to continue |
135 | | - </p> |
136 | | - |
137 | | - <form |
138 | | - onSubmit={handleSubmit} |
139 | | - className="space-y-3 sm:space-y-4 md:space-y-5 lg:space-y-6" |
140 | | - > |
141 | | - {/* Error Message */} |
142 | | - {error && ( |
143 | | - <div className="p-3 sm:p-4 bg-red-900 bg-opacity-30 border border-red-500 rounded-lg"> |
144 | | - <p className="text-red-400 text-xs sm:text-sm">{error}</p> |
145 | | - <div className="p-3 bg-red-900 border border-red-700 rounded-lg text-red-100 text-xs sm:text-sm"> |
146 | | - {error} |
147 | | - </div> |
148 | | - )} |
149 | | - |
150 | | - {/* Success Message */} |
151 | | - {success && ( |
152 | | - <div className="p-3 sm:p-4 bg-green-900 bg-opacity-30 border border-green-500 rounded-lg"> |
153 | | - <p className="text-green-400 text-xs sm:text-sm">{success}</p> |
154 | | - <div className="p-3 bg-green-900 border border-green-700 rounded-lg text-green-100 text-xs sm:text-sm"> |
155 | | - {success} |
156 | | - </div> |
157 | | - )} |
158 | | - |
159 | | - {/* Email Input */} |
160 | | - <div> |
161 | | - <label |
162 | | - htmlFor="email" |
163 | | - className="block text-gray-200 text-xs sm:text-sm font-semibold mb-2" |
164 | | - > |
165 | | - Email |
166 | | - </label> |
167 | | - <input |
168 | | - id="email" |
169 | | - type="email" |
170 | | - value={email} |
171 | | - onChange={e => setEmail(e.target.value)} |
172 | | - placeholder="you@example.com" |
173 | | - className="w-full px-4 py-2.5 bg-[#2a2a2a] border border-gray-600 rounded-lg text-sm sm:text-base text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition disabled:opacity-50" |
174 | | - required |
175 | | - disabled={loading} |
176 | | - disabled={isLoading} |
177 | | - /> |
178 | | - </div> |
179 | | - |
180 | | - {/* Password Input */} |
181 | | - <div> |
182 | | - <label |
183 | | - htmlFor="password" |
184 | | - className="block text-gray-200 text-xs sm:text-sm font-semibold mb-2" |
185 | | - > |
186 | | - Password |
187 | | - </label> |
188 | | - <input |
189 | | - id="password" |
190 | | - type="password" |
191 | | - value={password} |
192 | | - onChange={e => setPassword(e.target.value)} |
193 | | - placeholder="••••••••" |
194 | | - className="w-full px-4 py-2.5 bg-[#2a2a2a] border border-gray-600 rounded-lg text-sm sm:text-base text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition disabled:opacity-50" |
195 | | - required |
196 | | - disabled={loading} |
197 | | - disabled={isLoading} |
198 | | - /> |
199 | | - |
200 | | - {/* Remember Me & Forgot Password */} |
201 | | - <div className="flex flex-row items-center justify-between gap-2 mt-2 sm:mt-3"> |
202 | | - <label className="flex items-center gap-2 cursor-pointer"> |
203 | | - <input |
204 | | - type="checkbox" |
205 | | - checked={rememberMe} |
206 | | - onChange={e => setRememberMe(e.target.checked)} |
207 | | - className="w-4 h-4 bg-gray-700 border border-gray-600 rounded cursor-pointer accent-gray-500 disabled:opacity-50" |
208 | | - disabled={loading} |
209 | | - className="w-4 h-4 bg-gray-700 border border-gray-600 rounded cursor-pointer accent-gray-500" |
210 | | - disabled={isLoading} |
211 | | - /> |
212 | | - <span className="text-gray-300 text-xs sm:text-sm">Remember me</span> |
213 | | - </label> |
214 | | - <Link |
215 | | - to="/forgot-password" |
216 | | - className="text-gray-300 hover:text-white hover:underline text-xs sm:text-sm transition-all duration-200" |
217 | | - > |
218 | | - Forgot password? |
219 | | - </Link> |
220 | | - </div> |
221 | | - </div> |
222 | | - |
223 | | - {/* Submit Button */} |
224 | | - <button |
225 | | - type="submit" |
226 | | - disabled={loading} |
227 | | - className="w-full bg-gray-400 hover:bg-gray-500 disabled:bg-gray-600 disabled:cursor-not-allowed text-black font-bold py-2.5 text-sm sm:text-base rounded-lg transition-colors duration-200 mt-6 sm:mt-7 md:mt-8 flex items-center justify-center gap-2" |
228 | | - > |
229 | | - {loading ? ( |
230 | | - <> |
231 | | - <span className="inline-block w-4 h-4 border-2 border-black border-t-transparent rounded-full animate-spin"></span> |
232 | | - Logging in... |
233 | | - </> |
234 | | - ) : ( |
235 | | - 'Log In' |
236 | | - )} |
237 | | - disabled={isLoading} |
238 | | - className={`w-full font-bold py-2.5 text-sm sm:text-base rounded-lg transition-colors duration-200 mt-6 sm:mt-7 md:mt-8 ${ |
239 | | - isLoading |
240 | | - ? 'bg-gray-600 text-gray-300 cursor-not-allowed' |
241 | | - : 'bg-gray-400 hover:bg-gray-500 text-black' |
242 | | - }`} |
243 | | - > |
244 | | - {isLoading ? 'Logging in...' : 'Log In'} |
245 | | - </button> |
246 | | - </form> |
| 37 | + const handleSubmit = (e: React.FormEvent) => { |
| 38 | + e.preventDefault() |
| 39 | + handleLogin() |
| 40 | + } |
247 | 41 |
|
248 | | - <p className="text-gray-400 text-xs sm:text-sm mt-4 sm:mt-5 md:mt-6 text-center"> |
249 | | - Don't have an account?{' '} |
250 | | - <Link |
251 | | - to="/register" |
252 | | - className="text-white hover:text-gray-300 hover:underline transition-all duration-200" |
253 | | - > |
254 | | - Create Account |
255 | | - </Link> |
256 | | - </p> |
| 42 | + return ( |
| 43 | + <div className="min-h-screen flex items-center justify-center bg-zinc-900"> |
| 44 | + <form |
| 45 | + onSubmit={handleSubmit} |
| 46 | + className="bg-zinc-800 p-8 rounded-lg border border-zinc-500/50" |
| 47 | + > |
| 48 | + <h2 className="text-2xl font-bold text-white mb-6">Login</h2> |
| 49 | + |
| 50 | + {error && <div className="bg-red-500/20 text-red-400 p-3 rounded mb-4">{error}</div>} |
| 51 | + |
| 52 | + <div className="mb-4"> |
| 53 | + <label className="block text-white mb-2">Email</label> |
| 54 | + <input |
| 55 | + type="email" |
| 56 | + value={email} |
| 57 | + onChange={e => setEmail(e.target.value)} |
| 58 | + className="w-full bg-zinc-700 text-white px-4 py-2 rounded" |
| 59 | + required |
| 60 | + /> |
257 | 61 | </div> |
258 | 62 |
|
259 | | - {/* Right Section */} |
260 | | - <div className="bg-gradient-to-br from-gray-700 to-gray-900 p-0 items-center justify-center hidden md:flex overflow-hidden"> |
261 | | - <img src={robotImg} alt="Robot" className="w-full h-full object-cover" /> |
| 63 | + <div className="mb-6"> |
| 64 | + <label className="block text-white mb-2">Password</label> |
| 65 | + <input |
| 66 | + type="password" |
| 67 | + value={password} |
| 68 | + onChange={e => setPassword(e.target.value)} |
| 69 | + className="w-full bg-zinc-700 text-white px-4 py-2 rounded" |
| 70 | + required |
| 71 | + /> |
262 | 72 | </div> |
263 | | - </div> |
| 73 | + |
| 74 | + <button |
| 75 | + type="submit" |
| 76 | + className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 transition-colors" |
| 77 | + > |
| 78 | + Login |
| 79 | + </button> |
| 80 | + </form> |
264 | 81 | </div> |
265 | 82 | ) |
266 | 83 | } |
|
0 commit comments