uchill/front_material/app/(auth)/reset-password/page.tsx

215 lines
6.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}