feat(login): add password visibility toggle and confirm password field

This commit is contained in:
2025-11-18 11:15:09 +08:00
parent fd640631fc
commit fc0b7b2cc9
4 changed files with 77 additions and 4 deletions

View File

@@ -169,6 +169,7 @@ export interface RegisterFormData {
email: string;
password: string;
nickname: string;
confirmPassword: string;
}
/**
@@ -315,7 +316,8 @@ export const useLoginForm = () => {
defaultValues: {
email: '',
password: '',
nickname: ''
nickname: '',
confirmPassword: ''
}
});
@@ -395,6 +397,13 @@ export const useLoginForm = () => {
value: 8,
message: t('setting.passwordMinLength')
}
},
confirmPassword: {
required: t('confirmPasswordRequired'),
validate: (value: string) => {
const pwd = registerForm.getValues('password');
return value === pwd || t('setting.passwordMismatch');
}
}
}
};

View File

@@ -258,6 +258,7 @@ export default {
registerDescription: 'Glad to have you on board!',
emailLabel: 'Email',
emailPlaceholder: 'Please input email',
emailInvalid: 'Invalid email address',
passwordLabel: 'Password',
passwordPlaceholder: 'Please input password',
rememberMe: 'Remember me',

View File

@@ -240,6 +240,7 @@ export default {
registerDescription: '很高兴您加入!',
emailLabel: '邮箱',
emailPlaceholder: '请输入邮箱地址',
emailInvalid: '无效的邮箱地址',
passwordLabel: '密码',
passwordPlaceholder: '请输入密码',
rememberMe: '记住我',

View File

@@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next';
import React, { useState } from 'react';
import {
Box,
Button,
@@ -14,13 +15,20 @@ import {
CardContent,
Alert,
Tabs,
Tab
Tab,
InputAdornment,
IconButton,
} from '@mui/material';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import LanguageSwitcher from '../../components/LanguageSwitcher';
import { useLoginPage } from '../../hooks/login-hooks';
const Login = () => {
const { t } = useTranslation();
const [showLoginPassword, setShowLoginPassword] = useState(false);
const [showRegisterPassword, setShowRegisterPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const {
// 表单状态
tabValue,
@@ -110,13 +118,27 @@ const Login = () => {
<TextField
fullWidth
id="login-password"
type="password"
type={showLoginPassword ? 'text' : 'password'}
placeholder={t('login.passwordPlaceholder')}
autoComplete="current-password"
{...loginForm.register('password', loginValidation.password)}
error={!!loginForm.formState.errors.password}
helperText={loginForm.formState.errors.password?.message}
sx={{ mb: 2 }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label={showLoginPassword ? 'Hide password' : 'Show password'}
onClick={() => setShowLoginPassword((v) => !v)}
onMouseDown={(e) => e.preventDefault()}
edge="end"
>
{showLoginPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
@@ -175,13 +197,53 @@ const Login = () => {
<TextField
fullWidth
id="register-password"
type="password"
type={showRegisterPassword ? 'text' : 'password'}
placeholder={t('login.passwordPlaceholder')}
autoComplete="new-password"
{...registerForm.register('password', registerValidation.password)}
error={!!registerForm.formState.errors.password}
helperText={registerForm.formState.errors.password?.message}
sx={{ mb: 2 }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label={showRegisterPassword ? 'Hide password' : 'Show password'}
onClick={() => setShowRegisterPassword((v) => !v)}
onMouseDown={(e) => e.preventDefault()}
edge="end"
>
{showRegisterPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
<TextField
fullWidth
id="register-confirm-password"
type={showConfirmPassword ? 'text' : 'password'}
placeholder={t('confirmPassword')}
autoComplete="new-password"
{...registerForm.register('confirmPassword', registerValidation.confirmPassword)}
error={!!registerForm.formState.errors.confirmPassword}
helperText={registerForm.formState.errors.confirmPassword?.message}
sx={{ mb: 2 }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label={showConfirmPassword ? 'Hide password' : 'Show password'}
onClick={() => setShowConfirmPassword((v) => !v)}
onMouseDown={(e) => e.preventDefault()}
edge="end"
>
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
<Button