220 lines
6.8 KiB
JavaScript
220 lines
6.8 KiB
JavaScript
import { useState, useEffect } from 'react'
|
||
import { useSearchParams, useNavigate } from 'react-router-dom'
|
||
import { verifyMagicLink, setPassword } from '../utils/api'
|
||
import { X } from 'lucide-react'
|
||
import './VerifyEmail.css'
|
||
|
||
export default function VerifyEmail() {
|
||
const [searchParams] = useSearchParams()
|
||
const navigate = useNavigate()
|
||
const token = searchParams.get('token')
|
||
|
||
const [loading, setLoading] = useState(true)
|
||
const [requiresPassword, setRequiresPassword] = useState(false)
|
||
const [error, setError] = useState('')
|
||
const [formData, setFormData] = useState({
|
||
password: '',
|
||
confirmPassword: '',
|
||
username: '',
|
||
firstName: ''
|
||
})
|
||
const [submitting, setSubmitting] = useState(false)
|
||
|
||
useEffect(() => {
|
||
if (!token) {
|
||
setError('Токен не указан')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
const checkToken = async () => {
|
||
try {
|
||
const result = await verifyMagicLink(token)
|
||
if (result.requiresPassword) {
|
||
setRequiresPassword(true)
|
||
} else {
|
||
// Уже авторизован, перенаправляем
|
||
window.location.href = '/feed'
|
||
}
|
||
} catch (err) {
|
||
setError(err.response?.data?.error || 'Неверная или устаревшая ссылка')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
checkToken()
|
||
}, [token])
|
||
|
||
const handleSubmit = async (e) => {
|
||
e.preventDefault()
|
||
setError('')
|
||
|
||
// Валидация
|
||
if (!formData.password || formData.password.length < 8) {
|
||
setError('Пароль должен быть не менее 8 символов')
|
||
return
|
||
}
|
||
|
||
if (formData.password !== formData.confirmPassword) {
|
||
setError('Пароли не совпадают')
|
||
return
|
||
}
|
||
|
||
if (!formData.username || formData.username.trim().length < 3) {
|
||
setError('Юзернейм должен быть не менее 3 символов')
|
||
return
|
||
}
|
||
|
||
if (!formData.firstName || formData.firstName.trim().length < 1) {
|
||
setError('Никнейм обязателен')
|
||
return
|
||
}
|
||
|
||
try {
|
||
setSubmitting(true)
|
||
await setPassword(
|
||
token,
|
||
formData.password,
|
||
formData.username.trim().toLowerCase(),
|
||
formData.firstName.trim()
|
||
)
|
||
|
||
// Успешная регистрация, перенаправляем
|
||
window.location.href = '/feed'
|
||
} catch (err) {
|
||
setError(err.response?.data?.error || 'Ошибка регистрации')
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="verify-email-page">
|
||
<div className="verify-email-container">
|
||
<div className="spinner" />
|
||
<p>Проверка ссылки...</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error && !requiresPassword) {
|
||
return (
|
||
<div className="verify-email-page">
|
||
<div className="verify-email-container">
|
||
<div className="error-state">
|
||
<h2>Ошибка</h2>
|
||
<p>{error}</p>
|
||
<button className="btn-primary" onClick={() => navigate('/feed')}>
|
||
На главную
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (requiresPassword) {
|
||
return (
|
||
<div className="verify-email-page">
|
||
<div className="verify-email-container">
|
||
<div className="verify-email-card">
|
||
<h2>Завершение регистрации</h2>
|
||
<p className="verify-email-subtitle">Установите пароль и заполните данные профиля</p>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
<div className="form-group">
|
||
<label>Юзернейм *</label>
|
||
<input
|
||
type="text"
|
||
className="form-input"
|
||
placeholder="username"
|
||
value={formData.username}
|
||
onChange={e => {
|
||
setFormData({ ...formData, username: e.target.value.replace(/[^a-z0-9_]/gi, '').toLowerCase() })
|
||
setError('')
|
||
}}
|
||
disabled={submitting}
|
||
required
|
||
minLength={3}
|
||
maxLength={20}
|
||
/>
|
||
<p className="form-hint">Только латинские буквы, цифры и _. Нельзя изменить после регистрации</p>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>Никнейм *</label>
|
||
<input
|
||
type="text"
|
||
className="form-input"
|
||
placeholder="Ваше имя"
|
||
value={formData.firstName}
|
||
onChange={e => {
|
||
setFormData({ ...formData, firstName: e.target.value })
|
||
setError('')
|
||
}}
|
||
disabled={submitting}
|
||
required
|
||
maxLength={50}
|
||
/>
|
||
<p className="form-hint">Можно изменить в настройках профиля</p>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>Пароль *</label>
|
||
<input
|
||
type="password"
|
||
className="form-input"
|
||
placeholder="Минимум 8 символов"
|
||
value={formData.password}
|
||
onChange={e => {
|
||
setFormData({ ...formData, password: e.target.value })
|
||
setError('')
|
||
}}
|
||
disabled={submitting}
|
||
required
|
||
minLength={8}
|
||
maxLength={24}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>Подтвердите пароль *</label>
|
||
<input
|
||
type="password"
|
||
className="form-input"
|
||
placeholder="Повторите пароль"
|
||
value={formData.confirmPassword}
|
||
onChange={e => {
|
||
setFormData({ ...formData, confirmPassword: e.target.value })
|
||
setError('')
|
||
}}
|
||
disabled={submitting}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="error-message">{error}</div>
|
||
)}
|
||
|
||
<button
|
||
type="submit"
|
||
className="btn-primary"
|
||
disabled={submitting || !formData.password || !formData.username || !formData.firstName}
|
||
>
|
||
{submitting ? 'Регистрация...' : 'Завершить регистрацию'}
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|