source: chapterx-frontend/src/pages/auth/LoginPage.tsx@ b62cefc

main
Last change on this file since b62cefc was b62cefc, checked in by kikisrbinoska <srbinoskakristina07@…>, 4 months ago

Added frontend and imporoved AI Suggestions service

  • Property mode set to 100644
File size: 6.0 KB
Line 
1import React, { useState } from 'react'
2import { useNavigate, Link } from 'react-router-dom'
3import { Eye, EyeOff } from 'lucide-react'
4import logo from '../../assets/chapterX-removebg-preview.png'
5import { useAuthStore } from '../../store/authStore'
6import { useUIStore } from '../../store/uiStore'
7import { Button } from '../../components/ui/Button'
8import { Avatar } from '../../components/ui/Avatar'
9import { RoleBadge } from '../../components/ui/Badge'
10
11const quickUsers = [
12 { username: 'admin_alex', name: 'Alex Admin', role: 'admin' as const },
13 { username: 'elena_writes', name: 'Elena Dimitrova', role: 'writer' as const },
14 { username: 'boris_writer', name: 'Boris Nikolov', role: 'writer' as const },
15 { username: 'sara_reader', name: 'Sara Petkovska', role: 'regular' as const },
16]
17
18export const LoginPage: React.FC = () => {
19 const navigate = useNavigate()
20 const { login, switchUser } = useAuthStore()
21 const { addToast } = useUIStore()
22 const [email, setEmail] = useState('')
23 const [password, setPassword] = useState('')
24 const [showPw, setShowPw] = useState(false)
25 const [loading, setLoading] = useState(false)
26 const [errors, setErrors] = useState<{ email?: string; password?: string }>({})
27
28 const validate = () => {
29 const e: typeof errors = {}
30 if (!email.trim()) e.email = 'Email or username is required'
31 if (!password.trim()) e.password = 'Password is required'
32 setErrors(e)
33 return Object.keys(e).length === 0
34 }
35
36 const handleSubmit = async (ev: React.FormEvent) => {
37 ev.preventDefault()
38 if (!validate()) return
39 setLoading(true)
40 try {
41 await login(email, password)
42 addToast('Welcome back!')
43 navigate('/')
44 } catch (err: any) {
45 addToast(err.message || 'Login failed', 'error')
46 } finally {
47 setLoading(false)
48 }
49 }
50
51 const handleQuickLogin = (username: string) => {
52 const { allUsers } = useAuthStore.getState()
53 const user = allUsers.find(u => u.username === username)
54 if (user) {
55 switchUser(user.user_id)
56 addToast(`Signed in as ${user.name}`)
57 navigate('/')
58 }
59 }
60
61 return (
62 <div className="min-h-[80vh] flex items-center justify-center px-4 py-12">
63 <div className="w-full max-w-md">
64 {/* Logo */}
65 <div className="text-center mb-8">
66 <Link to="/" className="inline-flex items-center gap-2 mb-6">
67 <img src={logo} alt="ChapterX" className="h-20 w-20 object-contain" />
68 </Link>
69 <h1 className="font-serif text-2xl font-bold text-white">Welcome back</h1>
70 <p className="text-slate-400 text-sm mt-2">Sign in to your account</p>
71 </div>
72
73 {/* Quick login */}
74 <div className="mb-6">
75 <p className="text-xs text-slate-500 text-center mb-3">Quick demo login</p>
76 <div className="grid grid-cols-2 gap-2">
77 {quickUsers.map(u => (
78 <button
79 key={u.username}
80 onClick={() => handleQuickLogin(u.username)}
81 className="flex items-center gap-2 p-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 hover:bg-slate-700/50 transition-all group"
82 >
83 <Avatar name={u.name} size="sm" />
84 <div className="text-left min-w-0">
85 <p className="text-white text-xs font-medium truncate">{u.name}</p>
86 <RoleBadge role={u.role} />
87 </div>
88 </button>
89 ))}
90 </div>
91 </div>
92
93 <div className="flex items-center gap-3 mb-6">
94 <div className="flex-1 h-px bg-slate-700" />
95 <span className="text-slate-600 text-xs">or sign in with email</span>
96 <div className="flex-1 h-px bg-slate-700" />
97 </div>
98
99 {/* Form */}
100 <form onSubmit={handleSubmit} className="space-y-4">
101 <div>
102 <label className="block text-sm text-slate-400 mb-1.5">Email or Username</label>
103 <input
104 type="text"
105 value={email}
106 onChange={e => { setEmail(e.target.value); setErrors(p => ({ ...p, email: '' })) }}
107 placeholder="you@example.com"
108 className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition-colors ${
109 errors.email ? 'border-rose-500' : 'border-slate-700'
110 }`}
111 />
112 {errors.email && <p className="text-rose-400 text-xs mt-1">{errors.email}</p>}
113 </div>
114 <div>
115 <label className="block text-sm text-slate-400 mb-1.5">Password</label>
116 <div className="relative">
117 <input
118 type={showPw ? 'text' : 'password'}
119 value={password}
120 onChange={e => { setPassword(e.target.value); setErrors(p => ({ ...p, password: '' })) }}
121 placeholder="••••••••"
122 className={`w-full px-4 py-3 pr-12 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition-colors ${
123 errors.password ? 'border-rose-500' : 'border-slate-700'
124 }`}
125 />
126 <button
127 type="button"
128 onClick={() => setShowPw(!showPw)}
129 className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300"
130 >
131 {showPw ? <EyeOff size={16} /> : <Eye size={16} />}
132 </button>
133 </div>
134 {errors.password && <p className="text-rose-400 text-xs mt-1">{errors.password}</p>}
135 </div>
136
137 <Button type="submit" className="w-full" size="lg" loading={loading}>
138 Sign In
139 </Button>
140 </form>
141
142 <p className="text-center text-slate-500 text-sm mt-6">
143 Don't have an account?{' '}
144 <Link to="/register" className="text-indigo-400 hover:text-indigo-300">
145 Create one
146 </Link>
147 </p>
148 </div>
149 </div>
150 )
151}
Note: See TracBrowser for help on using the repository browser.