feat: staff invite flow — email invitation instead of manual password
- POST /api/hotels/:slug/users: password now optional - with password → create active user (email_confirmed=true) - without password → create inactive user, send invite email with 7-day token - POST /api/auth/accept-invite: validates token, sets password, activates account, returns JWT for auto-login - Migration 083: invite_token + invite_expires columns on users - email.ts: sendInviteEmail() with branded HTML template - AcceptInvitePage at /invite/:token — set password form, auto-login on success - AuthContext: loginWithToken() for programmatic session set - UserModal: password field optional for new users, hint about invite email Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -308,7 +308,7 @@ function UserModal({
|
||||
if (!form.lastName.trim()) e.lastName = 'Введите фамилию'
|
||||
if (!form.email.trim()) e.email = 'Введите email'
|
||||
if (!form.position.trim()) e.position = 'Введите должность'
|
||||
if (isNew && password.length < 6) e.password = 'Минимум 6 символов'
|
||||
if (isNew && password.length > 0 && password.length < 6) e.password = 'Минимум 6 символов'
|
||||
setErrors(e)
|
||||
return Object.keys(e).length === 0
|
||||
}
|
||||
@@ -477,13 +477,18 @@ function UserModal({
|
||||
{/* Password */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
|
||||
{isNew ? 'Пароль *' : 'Новый пароль (оставьте пустым, чтобы не менять)'}
|
||||
{isNew ? 'Пароль' : 'Новый пароль (оставьте пустым, чтобы не менять)'}
|
||||
</label>
|
||||
{isNew && (
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-2">
|
||||
Оставьте пустым — сотрудник получит приглашение на email и сам задаст пароль
|
||||
</p>
|
||||
)}
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showPass ? 'text' : 'password'}
|
||||
className={cn('input pr-10', errors.password && 'border-red-400')}
|
||||
placeholder="••••••••"
|
||||
placeholder={isNew ? 'Оставьте пустым для отправки приглашения' : '••••••••'}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user