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:
2026-03-20 17:01:55 +03:00
parent 84cfca500c
commit aa4a1fdb39

View File

@@ -9,11 +9,6 @@ import { useTheme } from '../contexts/ThemeContext'
import { cn } from '../lib/utils'
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'
const LEGAL_TYPES: { id: LegalType; label: string }[] = [
@@ -197,25 +192,6 @@ function LoginForm({ onSwitch, onForgot }: { onSwitch: () => void; onForgot: ()
</button>
</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">
Нет аккаунта?{' '}
<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 [password, setPassword] = useState('')
const [showPass, setShowPass] = useState(false)
const [agreed, setAgreed] = useState(false)
const [showTerms, setShowTerms] = useState(false)
const [loading, setLoading] = useState(false)
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'
if (password.length < 8) err.password = 'Минимум 8 символов'
else if (!/[a-zA-Zа-яА-ЯёЁ]/.test(password)) err.password = 'Пароль должен содержать хотя бы одну букву'
if (!agreed) err.agreed = 'Необходимо принять пользовательское соглашение'
setErrors(err)
if (Object.keys(err).length > 0) return
setLoading(true)
@@ -457,6 +436,33 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
)}
</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 && (
<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}
@@ -477,6 +483,53 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) {
Войти
</button>
</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>
)}
</>
)
}