mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-21 00:48:37 +00:00
- Fix input field display issues in password reset confirmation page
* Replace `readOnly` with `disabled={true}` for proper field state
* Improve URL parameter parsing and state management
* Add proper null checks and fallback values
- Enhance user experience and error handling
* Add validation for invalid reset links
* Display appropriate error messages and placeholders
* Add debug logging for troubleshooting
* Improve button states and loading indicators
- Improve password reset form validation
* Add proper email input validation with error messages
* Enhance user feedback for empty email submissions
- Add missing English translations
* Add i18n support for new UI text strings
* Ensure proper internationalization coverage
The password reset confirmation page now correctly displays email addresses
from URL parameters and prevents user input as intended. Error handling
has been improved to provide better user guidance when reset links are
invalid or malformed.
Fixes: Password reset input fields showing empty and allowing user input
when they should display email/password and be read-only.
165 lines
5.8 KiB
JavaScript
165 lines
5.8 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { API, copy, showError, showNotice, getLogo, getSystemName } from '../../helpers';
|
|
import { useSearchParams, Link } from 'react-router-dom';
|
|
import { Button, Card, Form, Typography } from '@douyinfe/semi-ui';
|
|
import { IconMail, IconLock } from '@douyinfe/semi-icons';
|
|
import { useTranslation } from 'react-i18next';
|
|
import Background from '/example.png';
|
|
|
|
const { Text, Title } = Typography;
|
|
|
|
const PasswordResetConfirm = () => {
|
|
const { t } = useTranslation();
|
|
const [inputs, setInputs] = useState({
|
|
email: '',
|
|
token: '',
|
|
});
|
|
const { email, token } = inputs;
|
|
const isValidResetLink = email && token;
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [disableButton, setDisableButton] = useState(false);
|
|
const [countdown, setCountdown] = useState(30);
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
|
|
const logo = getLogo();
|
|
const systemName = getSystemName();
|
|
|
|
useEffect(() => {
|
|
let token = searchParams.get('token');
|
|
let email = searchParams.get('email');
|
|
setInputs({
|
|
token: token || '',
|
|
email: email || '',
|
|
});
|
|
}, [searchParams]);
|
|
|
|
useEffect(() => {
|
|
let countdownInterval = null;
|
|
if (disableButton && countdown > 0) {
|
|
countdownInterval = setInterval(() => {
|
|
setCountdown(countdown - 1);
|
|
}, 1000);
|
|
} else if (countdown === 0) {
|
|
setDisableButton(false);
|
|
setCountdown(30);
|
|
}
|
|
return () => clearInterval(countdownInterval);
|
|
}, [disableButton, countdown]);
|
|
|
|
async function handleSubmit(e) {
|
|
if (!email || !token) {
|
|
showError(t('无效的重置链接,请重新发起密码重置请求'));
|
|
return;
|
|
}
|
|
setDisableButton(true);
|
|
setLoading(true);
|
|
const res = await API.post(`/api/user/reset`, {
|
|
email,
|
|
token,
|
|
});
|
|
const { success, message } = res.data;
|
|
if (success) {
|
|
let password = res.data.data;
|
|
setNewPassword(password);
|
|
await copy(password);
|
|
showNotice(`${t('密码已重置并已复制到剪贴板')}: ${password}`);
|
|
} else {
|
|
showError(message);
|
|
}
|
|
setLoading(false);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen relative flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
|
{/* 背景图片容器 - 放大并保持居中 */}
|
|
<div
|
|
className="absolute inset-0 z-0 bg-cover bg-center scale-125 opacity-100"
|
|
style={{
|
|
backgroundImage: `url(${Background})`
|
|
}}
|
|
></div>
|
|
|
|
{/* 半透明遮罩层 */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-teal-500/30 via-blue-500/30 to-purple-500/30 backdrop-blur-sm z-0"></div>
|
|
|
|
<div className="w-full max-w-sm relative z-10">
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-full max-w-md">
|
|
<div className="flex items-center justify-center mb-6 gap-2">
|
|
<img src={logo} alt="Logo" className="h-10 rounded-full" />
|
|
<Title heading={3} className='!text-white'>{systemName}</Title>
|
|
</div>
|
|
|
|
<Card className="shadow-xl border-0 !rounded-2xl overflow-hidden">
|
|
<div className="flex justify-center pt-6 pb-2">
|
|
<Title heading={3} className="text-gray-800 dark:text-gray-200">{t('密码重置确认')}</Title>
|
|
</div>
|
|
<div className="px-2 py-8">
|
|
{!isValidResetLink && (
|
|
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
|
<Text>{t('无效的重置链接,请重新发起密码重置请求')}</Text>
|
|
</div>
|
|
)}
|
|
<Form className="space-y-3">
|
|
<Form.Input
|
|
field="email"
|
|
label={t('邮箱')}
|
|
name="email"
|
|
size="large"
|
|
className="!rounded-md"
|
|
value={email || ''}
|
|
disabled={true}
|
|
prefix={<IconMail />}
|
|
placeholder={email ? '' : t('等待获取邮箱信息...')}
|
|
/>
|
|
|
|
{newPassword && (
|
|
<Form.Input
|
|
field="newPassword"
|
|
label={t('新密码')}
|
|
name="newPassword"
|
|
size="large"
|
|
className="!rounded-md"
|
|
value={newPassword}
|
|
disabled={true}
|
|
prefix={<IconLock />}
|
|
onClick={(e) => {
|
|
e.target.select();
|
|
navigator.clipboard.writeText(newPassword);
|
|
showNotice(`${t('密码已复制到剪贴板')}: ${newPassword}`);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<div className="space-y-2 pt-2">
|
|
<Button
|
|
theme="solid"
|
|
className="w-full !rounded-full"
|
|
type="primary"
|
|
htmlType="submit"
|
|
size="large"
|
|
onClick={handleSubmit}
|
|
loading={loading}
|
|
disabled={disableButton || newPassword || !isValidResetLink}
|
|
>
|
|
{newPassword ? t('密码重置完成') : t('确认重置密码')}
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
|
|
<div className="mt-6 text-center text-sm">
|
|
<Text><Link to="/login" className="text-blue-600 hover:text-blue-800 font-medium">{t('返回登录')}</Link></Text>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PasswordResetConfirm;
|