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:
2026-04-20 20:11:53 +03:00
parent 153e1d72d6
commit a05bc3c48e
9 changed files with 331 additions and 13 deletions

View File

@@ -37,6 +37,7 @@ import { DiscountsPage } from './pages/DiscountsPage'
import { GuestReviewPage } from './pages/GuestReviewPage'
import { GuestRoomServicePage } from './pages/GuestRoomServicePage'
import { ResetPasswordPage } from './pages/ResetPasswordPage'
import { AcceptInvitePage } from './pages/AcceptInvitePage'
import { TvWelcomePage } from './pages/TvWelcomePage'
import { TechnicalPage } from './pages/TechnicalPage'
import { SchedulePage } from './pages/SchedulePage'
@@ -86,6 +87,7 @@ export default function App() {
{/* Public */}
<Route path="/login" element={<LoginPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route path="/invite/:token" element={<AcceptInvitePage />} />
<Route path="/review/:slug" element={<GuestReviewPage />} />
<Route path="/room-service/:slug" element={<GuestRoomServicePage />} />
<Route path="/:slug/pay" element={<PayDepositPage />} />