Files
hotelsync/src/App.tsx
HotelSync a05bc3c48e 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>
2026-04-20 20:11:53 +03:00

154 lines
9.8 KiB
TypeScript

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { ThemeProvider } from './contexts/ThemeContext'
import { AuthProvider } from './contexts/AuthContext'
import { ModulesProvider } from './contexts/ModulesContext'
import { AmenitiesProvider } from './contexts/AmenitiesContext'
import { BedTypesProvider } from './contexts/BedTypesContext'
import { NotificationsProvider } from './contexts/NotificationsContext'
import { AppLayout } from './layouts/AppLayout'
import { LoginPage } from './pages/LoginPage'
import { CalendarPage } from './pages/CalendarPage'
import { BookingsPage } from './pages/BookingsPage'
import { RoomsPage } from './pages/RoomsPage'
import { HousekeepingPage } from './pages/HousekeepingPage'
import { ChannelsPage } from './pages/ChannelsPage'
import { ApiDocsPage } from './pages/ApiDocsPage'
import { SettingsPage } from './pages/SettingsPage'
import { ModulesPage } from './pages/ModulesPage'
import { ReportsPage } from './pages/ReportsPage'
import { WebsitePage } from './pages/WebsitePage'
import { BookingWidgetPage } from './pages/BookingWidgetPage'
import { AvailabilityPage } from './pages/AvailabilityPage'
import { FloorMapPage } from './pages/FloorMapPage'
import { MigrationPage } from './pages/MigrationPage'
import { PosPage } from './pages/PosPage'
import { ReviewsPage } from './pages/ReviewsPage'
import { RoomServicePage } from './pages/RoomServicePage'
import { RentalPage } from './pages/RentalPage'
import { RoomCategoriesPage } from './pages/RoomCategoriesPage'
import { DocumentsPage } from './pages/DocumentsPage'
import { UsersPage } from './pages/UsersPage'
import { GuestsPage } from './pages/GuestsPage'
import { TariffsPage } from './pages/TariffsPage'
import { DynamicPricingPage } from './pages/DynamicPricingPage'
import { LoyaltyPage } from './pages/LoyaltyPage'
import { MaintenancePage } from './pages/MaintenancePage'
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'
import { BillingPage } from './pages/BillingPage'
import { EquipmentPage } from './pages/EquipmentPage'
import { WiFiPage } from './pages/WiFiPage'
import { TTLockPage } from './pages/TTLockPage'
import { ChecklistSettingsPage } from './pages/ChecklistSettingsPage'
import { MinibarSettingsPage } from './pages/MinibarSettingsPage'
import { MinibarStockPage } from './pages/MinibarStockPage'
import { PaymentSettingsPage } from './pages/PaymentSettingsPage'
import { DepositSettingsPage } from './pages/DepositSettingsPage'
import { DepositHistoryPage } from './pages/DepositHistoryPage'
import { PayDepositPage } from './pages/PayDepositPage'
import { BookingWidgetStandalonePage } from './pages/BookingWidgetStandalonePage'
import { BookingConfirmPage } from './pages/BookingConfirmPage'
import { ModuleGuard } from './components/ModuleGuard'
import { PermissionGuard } from './components/PermissionGuard'
import { RolePermissionsProvider, useRolePermissions } from './contexts/RolePermissionsContext'
import { useAuth } from './contexts/AuthContext'
// Redirects to the user's role home page after login
function HomeRedirect() {
const { user } = useAuth()
const { userHomePage, loading } = useRolePermissions()
if (!user) return <Navigate to="/login" replace />
if (loading) return null
return <Navigate to={userHomePage} replace />
}
// Shorthand: wrap element with PermissionGuard
function P({ perm, children }: { perm: string; children: React.ReactNode }) {
return <PermissionGuard permission={perm}>{children}</PermissionGuard>
}
export default function App() {
return (
<ThemeProvider>
<AuthProvider>
<ModulesProvider>
<RolePermissionsProvider>
<NotificationsProvider>
<AmenitiesProvider>
<BedTypesProvider>
<BrowserRouter>
<Routes>
{/* 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 />} />
<Route path="/:slug/book" element={<BookingWidgetStandalonePage />} />
<Route path="/booking-confirm/:id" element={<BookingConfirmPage />} />
{/* PMS routes */}
<Route element={<AppLayout />}>
<Route path="/calendar" element={<P perm="calendar"> <CalendarPage /> </P>} />
<Route path="/bookings" element={<P perm="bookings"> <BookingsPage /> </P>} />
<Route path="/guests" element={<P perm="guests"> <GuestsPage /> </P>} />
<Route path="/rooms" element={<P perm="rooms"> <RoomsPage /> </P>} />
<Route path="/room-categories" element={<P perm="rooms"> <RoomCategoriesPage /></P>} />
<Route path="/availability" element={<P perm="availability"><AvailabilityPage /></P>} />
<Route path="/floor-map" element={<P perm="floor_map"> <FloorMapPage /> </P>} />
<Route path="/housekeeping" element={<P perm="housekeeping"><ModuleGuard moduleId="housekeeping"><HousekeepingPage /></ModuleGuard></P>} />
<Route path="/technical" element={<P perm="maintenance"> <TechnicalPage /> </P>} />
<Route path="/maintenance" element={<P perm="maintenance"> <MaintenancePage /> </P>} />
<Route path="/pos" element={<P perm="pos"> <ModuleGuard moduleId="pos"><PosPage /></ModuleGuard> </P>} />
<Route path="/room-service" element={<P perm="room_service"><ModuleGuard moduleId="room-service"><RoomServicePage /></ModuleGuard></P>} />
<Route path="/reviews" element={<P perm="reviews"> <ModuleGuard moduleId="reviews"><ReviewsPage /></ModuleGuard> </P>} />
<Route path="/tariffs" element={<P perm="tariffs"> <TariffsPage /> </P>} />
<Route path="/dynamic-pricing" element={<P perm="pricing"> <DynamicPricingPage /></P>} />
<Route path="/discounts" element={<P perm="discounts"> <DiscountsPage /> </P>} />
<Route path="/rental" element={<P perm="rental"> <ModuleGuard moduleId="rental"><RentalPage /></ModuleGuard> </P>} />
<Route path="/channels" element={<P perm="channels"> <ModuleGuard moduleId="channel-manager"><ChannelsPage /></ModuleGuard></P>} />
<Route path="/website" element={<P perm="website"> <WebsitePage /> </P>} />
<Route path="/booking-widget" element={<P perm="website"> <BookingWidgetPage /></P>} />
<Route path="/reports" element={<P perm="reports"> <ReportsPage /> </P>} />
<Route path="/loyalty" element={<P perm="loyalty"> <LoyaltyPage /> </P>} />
<Route path="/users" element={<P perm="users"> <UsersPage /> </P>} />
<Route path="/schedule" element={<P perm="users"> <SchedulePage /> </P>} />
<Route path="/documents" element={<P perm="documents"> <DocumentsPage /> </P>} />
<Route path="/settings" element={<P perm="settings"> <SettingsPage /> </P>} />
<Route path="/modules" element={<P perm="settings"> <ModulesPage /> </P>} />
<Route path="/billing" element={<P perm="settings"> <BillingPage /> </P>} />
<Route path="/equipment" element={<P perm="equipment"> <EquipmentPage /> </P>} />
<Route path="/wifi" element={<P perm="wifi"> <WiFiPage /> </P>} />
<Route path="/ttlock" element={<P perm="ttlock"> <TTLockPage /> </P>} />
<Route path="/settings/checklists" element={<P perm="settings"><ChecklistSettingsPage /></P>} />
<Route path="/settings/payments" element={<P perm="settings"><PaymentSettingsPage /> </P>} />
<Route path="/settings/minibar" element={<P perm="settings"><MinibarSettingsPage /> </P>} />
<Route path="/settings/minibar/stock" element={<P perm="settings"><MinibarStockPage /> </P>} />
<Route path="/settings/deposit" element={<P perm="settings"><DepositSettingsPage /> </P>} />
<Route path="/settings/deposit/history" element={<P perm="settings"><DepositHistoryPage /> </P>} />
<Route path="/migration" element={<P perm="settings"> <MigrationPage /> </P>} />
<Route path="/api-docs" element={<P perm="settings"> <ApiDocsPage /> </P>} />
<Route path="/tv-welcome" element={<TvWelcomePage />} />
</Route>
<Route path="/" element={<HomeRedirect />} />
<Route path="*" element={<Navigate to="/login" replace />} />
</Routes>
</BrowserRouter>
</BedTypesProvider>
</AmenitiesProvider>
</NotificationsProvider>
</RolePermissionsProvider>
</ModulesProvider>
</AuthProvider>
</ThemeProvider>
)
}