LoginPage: remove demo accounts hint, add terms agreement checkbox on register
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,11 +9,6 @@ import { useTheme } from '../contexts/ThemeContext'
|
|||||||
import { cn } from '../lib/utils'
|
import { cn } from '../lib/utils'
|
||||||
import { api, ApiError } from '../lib/api'
|
import { api, ApiError } from '../lib/api'
|
||||||
|
|
||||||
const DEMO_EMAILS = [
|
|
||||||
'manager@grand-palace.ru',
|
|
||||||
'cleaner@grand-palace.ru',
|
|
||||||
'admin@hotelsync.io',
|
|
||||||
]
|
|
||||||
|
|
||||||
type LegalType = 'ooo' | 'ip' | 'ao' | 'pao' | 'other'
|
type LegalType = 'ooo' | 'ip' | 'ao' | 'pao' | 'other'
|
||||||
const LEGAL_TYPES: { id: LegalType; label: string }[] = [
|
const LEGAL_TYPES: { id: LegalType; label: string }[] = [
|
||||||
@@ -197,25 +192,6 @@ function LoginForm({ onSwitch, onForgot }: { onSwitch: () => void; onForgot: ()
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Demo hint */}
|
|
||||||
<div className="mt-5 p-3 rounded-lg bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
|
|
||||||
<p className="text-xs font-medium text-slate-500 dark:text-slate-400 mb-2">
|
|
||||||
Демо-аккаунты (пароль: <code className="bg-slate-100 dark:bg-slate-700 px-1 rounded">demo</code>):
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
{DEMO_EMAILS.map(e => (
|
|
||||||
<button key={e} onClick={() => { setEmail(e); setPassword('demo'); setError('') }}
|
|
||||||
className={cn(
|
|
||||||
'text-left text-xs px-2 py-1 rounded transition-colors',
|
|
||||||
email === e
|
|
||||||
? 'text-brand-700 dark:text-brand-300 bg-brand-50 dark:bg-brand-900/30 font-medium'
|
|
||||||
: 'text-slate-500 dark:text-slate-400 hover:text-brand-600 dark:hover:text-brand-400',
|
|
||||||
)}
|
|
||||||
>{e}</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-6 text-center text-sm text-slate-500 dark:text-slate-400">
|
<p className="mt-6 text-center text-sm text-slate-500 dark:text-slate-400">
|
||||||
Нет аккаунта?{' '}
|
Нет аккаунта?{' '}
|
||||||
<button onClick={onSwitch} className="text-brand-600 dark:text-brand-400 font-medium hover:underline">
|
<button onClick={onSwitch} className="text-brand-600 dark:text-brand-400 font-medium hover:underline">
|
||||||
@@ -319,6 +295,8 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
|
|||||||
const [phone, setPhone] = useState('')
|
const [phone, setPhone] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [showPass, setShowPass] = useState(false)
|
const [showPass, setShowPass] = useState(false)
|
||||||
|
const [agreed, setAgreed] = useState(false)
|
||||||
|
const [showTerms, setShowTerms] = useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({})
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
@@ -331,6 +309,7 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
|
|||||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) err.email = 'Некорректный email'
|
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) err.email = 'Некорректный email'
|
||||||
if (password.length < 8) err.password = 'Минимум 8 символов'
|
if (password.length < 8) err.password = 'Минимум 8 символов'
|
||||||
else if (!/[a-zA-Zа-яА-ЯёЁ]/.test(password)) err.password = 'Пароль должен содержать хотя бы одну букву'
|
else if (!/[a-zA-Zа-яА-ЯёЁ]/.test(password)) err.password = 'Пароль должен содержать хотя бы одну букву'
|
||||||
|
if (!agreed) err.agreed = 'Необходимо принять пользовательское соглашение'
|
||||||
setErrors(err)
|
setErrors(err)
|
||||||
if (Object.keys(err).length > 0) return
|
if (Object.keys(err).length > 0) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -457,6 +436,33 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
|
|||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
{/* Согласие с пользовательским соглашением */}
|
||||||
|
<div>
|
||||||
|
<label className="flex items-start gap-2.5 cursor-pointer select-none">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={agreed}
|
||||||
|
onChange={e => { setAgreed(e.target.checked); setErrors(prev => ({ ...prev, agreed: '' })) }}
|
||||||
|
className="mt-0.5 w-4 h-4 rounded border-slate-300 dark:border-slate-600 text-brand-600 focus:ring-brand-500 cursor-pointer shrink-0"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Я согласен с{' '}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowTerms(true)}
|
||||||
|
className="text-brand-600 dark:text-brand-400 hover:underline font-medium"
|
||||||
|
>
|
||||||
|
пользовательским соглашением
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{errors.agreed && (
|
||||||
|
<p className="mt-1 text-xs text-red-600 dark:text-red-400 flex items-center gap-1">
|
||||||
|
<AlertCircle size={11} /> {errors.agreed}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{errors.general && (
|
{errors.general && (
|
||||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 text-sm">
|
<div className="flex items-center gap-2 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 text-sm">
|
||||||
<AlertCircle size={15} /> {errors.general}
|
<AlertCircle size={15} /> {errors.general}
|
||||||
@@ -477,6 +483,53 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
|
|||||||
Войти
|
Войти
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Модальное окно — пользовательское соглашение */}
|
||||||
|
{showTerms && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
|
||||||
|
onClick={() => setShowTerms(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-full max-w-lg bg-white dark:bg-slate-800 rounded-2xl shadow-xl flex flex-col max-h-[80vh]"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-200 dark:border-slate-700">
|
||||||
|
<h3 className="text-base font-semibold text-slate-900 dark:text-slate-100">Пользовательское соглашение</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTerms(false)}
|
||||||
|
className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 text-xl leading-none"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto px-6 py-4 text-sm text-slate-600 dark:text-slate-400 space-y-3 leading-relaxed">
|
||||||
|
<p className="text-xs text-slate-400 dark:text-slate-500">Редакция от 1 января 2026 г.</p>
|
||||||
|
<p>Настоящее Пользовательское соглашение регулирует отношения между ООО «ХотелСинк» (далее — Сервис) и пользователем, зарегистрировавшим учётную запись.</p>
|
||||||
|
<p><strong className="text-slate-700 dark:text-slate-300">1. Предмет соглашения.</strong> Сервис предоставляет доступ к облачной PMS-системе HotelSync для управления гостиничными операциями.</p>
|
||||||
|
<p><strong className="text-slate-700 dark:text-slate-300">2. Регистрация.</strong> Пользователь обязуется предоставить достоверные сведения при регистрации и нести ответственность за сохранность учётных данных.</p>
|
||||||
|
<p><strong className="text-slate-700 dark:text-slate-300">3. Персональные данные.</strong> Обработка персональных данных осуществляется в соответствии с Федеральным законом № 152-ФЗ «О персональных данных».</p>
|
||||||
|
<p><strong className="text-slate-700 dark:text-slate-300">4. Ограничение ответственности.</strong> Сервис не несёт ответственности за прямые или косвенные убытки, возникшие вследствие использования или невозможности использования платформы.</p>
|
||||||
|
<p><strong className="text-slate-700 dark:text-slate-300">5. Изменение условий.</strong> Сервис вправе изменять условия соглашения, уведомив пользователей не менее чем за 7 дней.</p>
|
||||||
|
<p className="text-xs text-slate-400 dark:text-slate-500">По вопросам: support@hotelsync.ru</p>
|
||||||
|
</div>
|
||||||
|
<div className="px-6 py-4 border-t border-slate-200 dark:border-slate-700 flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => { setAgreed(true); setShowTerms(false); setErrors(prev => ({ ...prev, agreed: '' })) }}
|
||||||
|
className="btn-primary flex-1 justify-center py-2"
|
||||||
|
>
|
||||||
|
Принять и закрыть
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTerms(false)}
|
||||||
|
className="btn-secondary flex-1 justify-center py-2"
|
||||||
|
>
|
||||||
|
Закрыть
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user