Skip to content

Commit d412816

Browse files
safe1ineclaude
andcommitted
Improve login page UX
- Save and restore credentials in localStorage per login type (user/manager) - Add password visibility toggle button - Exclude "forgot password" link from tab order Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9348044 commit d412816

File tree

1 file changed

+71
-22
lines changed

1 file changed

+71
-22
lines changed

frontend/src/pages/login.tsx

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import { toast } from "sonner"
2323
import { apiRequest } from "@/utils/requestUtils"
2424
import { Link, useNavigate } from "react-router-dom"
2525
import { captchaChallenge } from "@/utils/common"
26+
import { Eye, EyeOff } from "lucide-react"
27+
28+
const USER_STORAGE_KEY = 'login_user'
29+
const MANAGER_STORAGE_KEY = 'login_manager'
2630

2731
export default function LoginPage({
2832
className,
@@ -33,14 +37,35 @@ export default function LoginPage({
3337
const [teamManagerEmail, setTeamManagerEmail] = React.useState('')
3438
const [teamManagerPassword, setTeamManagerPassword] = React.useState('')
3539
const [logging, setLogging] = React.useState(false)
40+
const [showUserPassword, setShowUserPassword] = React.useState(false)
41+
const [showManagerPassword, setShowManagerPassword] = React.useState(false)
3642
const navigate = useNavigate()
3743

44+
React.useEffect(() => {
45+
try {
46+
const savedUser = localStorage.getItem(USER_STORAGE_KEY)
47+
if (savedUser) {
48+
const { email, password } = JSON.parse(savedUser)
49+
if (email) setUserEmail(email)
50+
if (password) setUserPassword(password)
51+
}
52+
const savedManager = localStorage.getItem(MANAGER_STORAGE_KEY)
53+
if (savedManager) {
54+
const { email, password } = JSON.parse(savedManager)
55+
if (email) setTeamManagerEmail(email)
56+
if (password) setTeamManagerPassword(password)
57+
}
58+
} catch {
59+
// ignore
60+
}
61+
}, [])
62+
3863
const handleUserLogin = async () => {
3964
if (userEmail.trim() === '' || userPassword.trim() === '') {
4065
toast.error('请输入账号和密码')
4166
return
4267
}
43-
68+
4469
setLogging(true)
4570

4671
const token = await captchaChallenge();
@@ -51,6 +76,7 @@ export default function LoginPage({
5176
captcha_token: token,
5277
}, [], (resp) => {
5378
if (resp.code === 0) {
79+
localStorage.setItem(USER_STORAGE_KEY, JSON.stringify({ email: userEmail.trim(), password: userPassword.trim() }))
5480
navigate('/console/')
5581
} else {
5682
toast.error('登录失败,请重试')
@@ -67,18 +93,19 @@ export default function LoginPage({
6793
toast.error('请输入账号和密码')
6894
return
6995
}
70-
96+
7197
setLogging(true)
7298

7399
const token = await captchaChallenge();
74100
if (token) {
75-
101+
76102
await apiRequest('v1TeamsUsersLoginCreate', {
77103
email: teamManagerEmail.trim(),
78104
password: teamManagerPassword.trim(),
79105
captcha_token: token,
80106
}, [], (resp) => {
81107
if (resp.code === 0) {
108+
localStorage.setItem(MANAGER_STORAGE_KEY, JSON.stringify({ email: teamManagerEmail.trim(), password: teamManagerPassword.trim() }))
82109
navigate('/manager/')
83110
} else {
84111
toast.error('登录失败,请重试')
@@ -124,19 +151,30 @@ export default function LoginPage({
124151
<Field>
125152
<div className="flex flex-row items-center justify-between">
126153
<FieldLabel htmlFor="user-password">密码</FieldLabel>
127-
<Link to="/findpassword" className="text-sm text-muted-foreground hover:underline">
154+
<Link to="/findpassword" tabIndex={-1} className="text-sm text-muted-foreground hover:underline">
128155
找回密码
129156
</Link>
130157
</div>
131-
<Input
132-
value={userPassword}
133-
placeholder="************"
134-
onChange={(e) => setUserPassword(e.target.value)}
135-
id="user-password"
136-
type="password"
137-
required
138-
disabled={logging}
139-
/>
158+
<div className="relative">
159+
<Input
160+
value={userPassword}
161+
placeholder="************"
162+
onChange={(e) => setUserPassword(e.target.value)}
163+
id="user-password"
164+
type={showUserPassword ? "text" : "password"}
165+
required
166+
disabled={logging}
167+
className="pr-9"
168+
/>
169+
<button
170+
type="button"
171+
tabIndex={-1}
172+
onClick={() => setShowUserPassword(v => !v)}
173+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
174+
>
175+
{showUserPassword ? <EyeOff size={16} /> : <Eye size={16} />}
176+
</button>
177+
</div>
140178
</Field>
141179
<Field>
142180
<Button type="submit" disabled={logging} variant="outline">
@@ -173,15 +211,26 @@ export default function LoginPage({
173211
</Field>
174212
<Field>
175213
<FieldLabel htmlFor="password">密码</FieldLabel>
176-
<Input
177-
id="password"
178-
placeholder="************"
179-
type="password"
180-
required
181-
disabled={logging}
182-
value={teamManagerPassword}
183-
onChange={(e) => setTeamManagerPassword(e.target.value)}
184-
/>
214+
<div className="relative">
215+
<Input
216+
id="password"
217+
placeholder="************"
218+
type={showManagerPassword ? "text" : "password"}
219+
required
220+
disabled={logging}
221+
value={teamManagerPassword}
222+
onChange={(e) => setTeamManagerPassword(e.target.value)}
223+
className="pr-9"
224+
/>
225+
<button
226+
type="button"
227+
tabIndex={-1}
228+
onClick={() => setShowManagerPassword(v => !v)}
229+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
230+
>
231+
{showManagerPassword ? <EyeOff size={16} /> : <Eye size={16} />}
232+
</button>
233+
</div>
185234
</Field>
186235
<Field>
187236
<Button type="submit" disabled={logging}>

0 commit comments

Comments
 (0)