- Add notificationSettings JSONB to role_permissions (migration 089)
- sendPushForNotification() — pushes only to users with the notif type enabled
- reviews.ts — push + in-app on new direct and QR reviews
- room-service.ts — use sendPushForNotification('room_service_order')
- publicWidget.ts — push + in-app on new online booking
- jobs.ts — runUnreadMessagesJob() every 15 min, deduped by in-process set
- UsersPage: NOTIFICATION_GROUPS UI in roles tab with per-type toggles
- api.ts / RolePermissionsContext: notificationSettings in types and save()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
import { createContext, useContext, useState, useEffect, useCallback, useMemo, type ReactNode } from 'react'
|
|
import { useAuth } from './AuthContext'
|
|
import { api } from '../lib/api'
|
|
|
|
// ── Default permissions for system roles (fallback if no DB data) ─────────────
|
|
|
|
export const DEFAULT_ROLE_PERMS: Record<string, string[]> = {
|
|
hotel_admin: ['*'],
|
|
manager: ['*'],
|
|
receptionist: [
|
|
'calendar', 'bookings', 'guests', 'rooms', 'availability',
|
|
'housekeeping', 'room_service', 'rental',
|
|
'pos', 'reviews', 'reports',
|
|
'documents', 'website',
|
|
],
|
|
housekeeper: ['calendar', 'housekeeping', 'maintenance', 'rooms'],
|
|
accountant: ['calendar', 'reports', 'pos', 'discounts', 'tariffs', 'pricing', 'loyalty', 'documents'],
|
|
security: ['calendar', 'bookings'],
|
|
technician: ['calendar', 'housekeeping', 'maintenance', 'rooms', 'floor_map', 'equipment', 'ttlock'],
|
|
}
|
|
|
|
// Default home pages per role (fallback)
|
|
export const DEFAULT_HOME_PAGES: Record<string, string> = {
|
|
hotel_admin: '/calendar',
|
|
manager: '/calendar',
|
|
receptionist: '/calendar',
|
|
housekeeper: '/housekeeping',
|
|
accountant: '/reports',
|
|
security: '/calendar',
|
|
technician: '/housekeeping',
|
|
}
|
|
|
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
|
|
export interface SavedRolePermission {
|
|
id: string
|
|
hotelId: string
|
|
roleKey: string
|
|
name: string
|
|
color: string
|
|
isSystem: boolean
|
|
permissions: Record<string, boolean>
|
|
homePage?: string | null
|
|
notificationSettings?: Record<string, boolean>
|
|
}
|
|
|
|
interface RolePermissionsContextValue {
|
|
/** Check if the current user has a given permission */
|
|
can: (permission: string) => boolean
|
|
/** Home page path for the current user's role */
|
|
userHomePage: string
|
|
/** Raw saved permissions for all roles (used by RolesTab editor) */
|
|
savedPermissions: SavedRolePermission[]
|
|
/** Re-fetch from API */
|
|
reload: () => Promise<void>
|
|
/** True while initial permissions are being fetched */
|
|
loading: boolean
|
|
}
|
|
|
|
// ── Context ───────────────────────────────────────────────────────────────────
|
|
|
|
const RolePermissionsContext = createContext<RolePermissionsContextValue>({
|
|
can: () => false,
|
|
userHomePage: '/calendar',
|
|
savedPermissions: [],
|
|
reload: async () => {},
|
|
loading: true,
|
|
})
|
|
|
|
export function RolePermissionsProvider({ children }: { children: ReactNode }) {
|
|
const { user } = useAuth()
|
|
const [savedPermissions, setSavedPermissions] = useState<SavedRolePermission[]>([])
|
|
// Start as true — PermissionGuard waits before rendering, preventing permission flash
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const reload = useCallback(async () => {
|
|
if (!user?.hotelSlug) {
|
|
setLoading(false)
|
|
return
|
|
}
|
|
setLoading(true)
|
|
try {
|
|
const data = await api.rolePermissions.list(user.hotelSlug)
|
|
setSavedPermissions(data)
|
|
} catch {
|
|
// silently fall back to defaults
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [user?.hotelSlug])
|
|
|
|
useEffect(() => {
|
|
reload()
|
|
}, [reload])
|
|
|
|
const can = useCallback((permission: string): boolean => {
|
|
const role = user?.role ?? 'housekeeper'
|
|
|
|
if (role === 'super_admin') return true
|
|
if (role === 'hotel_admin' || role === 'manager') return true
|
|
|
|
// Check saved permissions from DB
|
|
const saved = savedPermissions.find(rp => rp.roleKey === role)
|
|
if (saved) {
|
|
return saved.permissions[permission] === true
|
|
}
|
|
|
|
// Fall back to hardcoded defaults
|
|
const perms = DEFAULT_ROLE_PERMS[role] ?? []
|
|
return perms.includes('*') || perms.includes(permission)
|
|
}, [user?.role, savedPermissions])
|
|
|
|
const userHomePage = useMemo(() => {
|
|
const role = user?.role ?? ''
|
|
if (role === 'super_admin') return '/admin'
|
|
if (role === 'hotel_admin' || role === 'manager') return '/calendar'
|
|
|
|
// Use saved home page from DB if set
|
|
const saved = savedPermissions.find(rp => rp.roleKey === role)
|
|
if (saved?.homePage) return saved.homePage
|
|
|
|
// Fall back to role default
|
|
return DEFAULT_HOME_PAGES[role] ?? '/calendar'
|
|
}, [user?.role, savedPermissions])
|
|
|
|
return (
|
|
<RolePermissionsContext.Provider value={{ can, userHomePage, savedPermissions, reload, loading }}>
|
|
{children}
|
|
</RolePermissionsContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useRolePermissions() {
|
|
return useContext(RolePermissionsContext)
|
|
}
|