215 lines
6.8 KiB
TypeScript
215 lines
6.8 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect, Suspense } from 'react';
|
||
import { useRouter, useSearchParams } from 'next/navigation';
|
||
import { confirmPasswordReset } from '@/api/auth';
|
||
import { getErrorMessage } from '@/lib/error-utils';
|
||
|
||
const loadMaterialComponents = async () => {
|
||
await Promise.all([
|
||
import('@material/web/textfield/filled-text-field.js'),
|
||
import('@material/web/button/filled-button.js'),
|
||
import('@material/web/button/text-button.js'),
|
||
]);
|
||
};
|
||
|
||
function ResetPasswordContent() {
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
const token = searchParams.get('token');
|
||
const [password, setPassword] = useState('');
|
||
const [confirmPassword, setConfirmPassword] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState('');
|
||
const [success, setSuccess] = useState(false);
|
||
const [componentsLoaded, setComponentsLoaded] = useState(false);
|
||
|
||
useEffect(() => {
|
||
loadMaterialComponents().then(() => setComponentsLoaded(true));
|
||
}, []);
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!token) {
|
||
setError('Отсутствует ссылка для сброса пароля. Запросите восстановление пароля снова.');
|
||
return;
|
||
}
|
||
if (password !== confirmPassword) {
|
||
setError('Пароли не совпадают');
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
setError('');
|
||
try {
|
||
await confirmPasswordReset(token, password, confirmPassword);
|
||
setSuccess(true);
|
||
} catch (err: any) {
|
||
setError(getErrorMessage(err, 'Не удалось сменить пароль. Ссылка могла устареть — запросите новую.'));
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (!componentsLoaded) {
|
||
return (
|
||
<div style={{ width: '100%', maxWidth: '400px' }}>
|
||
<h1 style={{ fontSize: '28px', fontWeight: '600', margin: '0 0 8px 0', color: '#1a1a1a' }}>
|
||
Uchill
|
||
</h1>
|
||
<p style={{ fontSize: '14px', color: '#666', marginBottom: '28px' }}>Загрузка...</p>
|
||
<div style={{ display: 'flex', justifyContent: 'center', padding: '32px' }}>
|
||
<div
|
||
style={{
|
||
width: '40px',
|
||
height: '40px',
|
||
border: '3px solid #e0e0e0',
|
||
borderTopColor: 'var(--md-sys-color-primary, #6750a4)',
|
||
borderRadius: '50%',
|
||
animation: 'spin 0.8s linear infinite',
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!token) {
|
||
return (
|
||
<div style={{ width: '100%', maxWidth: '400px' }}>
|
||
<h1 style={{ fontSize: '28px', fontWeight: '600', margin: '0 0 8px 0', color: '#1a1a1a' }}>
|
||
Uchill
|
||
</h1>
|
||
<p style={{ fontSize: '14px', color: '#666', marginBottom: '28px' }}>
|
||
Сброс пароля
|
||
</p>
|
||
<div
|
||
style={{
|
||
padding: '16px',
|
||
marginBottom: '24px',
|
||
background: '#ffebee',
|
||
color: '#c62828',
|
||
borderRadius: '12px',
|
||
fontSize: '14px',
|
||
lineHeight: '1.5',
|
||
}}
|
||
>
|
||
Отсутствует ссылка для сброса пароля. Перейдите по ссылке из письма или запросите восстановление пароля снова.
|
||
</div>
|
||
<md-filled-button onClick={() => router.push('/forgot-password')} style={{ width: '100%', height: '48px' }}>
|
||
Восстановить пароль
|
||
</md-filled-button>
|
||
<div style={{ textAlign: 'center', marginTop: '20px' }}>
|
||
<md-text-button onClick={() => router.push('/login')} style={{ fontSize: '14px' }}>
|
||
На страницу входа
|
||
</md-text-button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (success) {
|
||
return (
|
||
<div style={{ width: '100%', maxWidth: '400px' }}>
|
||
<h1 style={{ fontSize: '28px', fontWeight: '600', margin: '0 0 8px 0', color: '#1a1a1a' }}>
|
||
Uchill
|
||
</h1>
|
||
<p style={{ fontSize: '14px', color: '#666', marginBottom: '28px' }}>
|
||
Сброс пароля
|
||
</p>
|
||
<div
|
||
style={{
|
||
padding: '16px',
|
||
marginBottom: '24px',
|
||
background: '#e8f5e9',
|
||
color: '#2e7d32',
|
||
borderRadius: '12px',
|
||
fontSize: '14px',
|
||
lineHeight: '1.5',
|
||
}}
|
||
>
|
||
Пароль успешно изменён. Войдите с новым паролем.
|
||
</div>
|
||
<md-filled-button onClick={() => router.push('/login')} style={{ width: '100%', height: '48px' }}>
|
||
Войти
|
||
</md-filled-button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{ width: '100%', maxWidth: '400px' }}>
|
||
|
||
<p style={{ fontSize: '14px', color: '#666', marginBottom: '28px' }}>
|
||
Введите новый пароль
|
||
</p>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<md-filled-text-field
|
||
label="Новый пароль"
|
||
type="password"
|
||
value={password}
|
||
onInput={(e: any) => setPassword(e.target.value || '')}
|
||
required
|
||
style={{ width: '100%' }}
|
||
/>
|
||
</div>
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<md-filled-text-field
|
||
label="Подтвердите пароль"
|
||
type="password"
|
||
value={confirmPassword}
|
||
onInput={(e: any) => setConfirmPassword(e.target.value || '')}
|
||
required
|
||
style={{ width: '100%' }}
|
||
/>
|
||
</div>
|
||
|
||
{error && (
|
||
<div
|
||
style={{
|
||
padding: '12px 16px',
|
||
marginBottom: '20px',
|
||
background: '#ffebee',
|
||
color: '#c62828',
|
||
borderRadius: '12px',
|
||
fontSize: '14px',
|
||
lineHeight: '1.5',
|
||
}}
|
||
>
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<md-filled-button
|
||
type="submit"
|
||
disabled={loading}
|
||
style={{ width: '100%', height: '48px', marginBottom: '16px' }}
|
||
>
|
||
{loading ? 'Сохранение...' : 'Сохранить пароль'}
|
||
</md-filled-button>
|
||
|
||
<div style={{ textAlign: 'center', marginTop: '20px' }}>
|
||
<md-text-button onClick={() => router.push('/login')} style={{ fontSize: '14px' }}>
|
||
На страницу входа
|
||
</md-text-button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function ResetPasswordPage() {
|
||
return (
|
||
<Suspense
|
||
fallback={
|
||
<div style={{ width: '100%', maxWidth: '400px' }}>
|
||
<p style={{ fontSize: '14px', color: '#666' }}>Загрузка...</p>
|
||
</div>
|
||
}
|
||
>
|
||
<ResetPasswordContent />
|
||
</Suspense>
|
||
);
|
||
}
|