import type { UserRole } from '@/types' import { APP_ROUTES } from '@/constants/routes' export const ADMIN_ROUTE_PREFIXES = [ APP_ROUTES.DASHBOARD, '/users', APP_ROUTES.MANAGE_EVENTS, '/payments', '/bookings', '/settlements', '/withdrawal-requests', '/bank-accounts', APP_ROUTES.EVENT_CATEGORIES, APP_ROUTES.BLOG_ARTICLES, '/guest-lists', '/reviews', '/cities', '/identity-verifications', '/notifications', '/sms-messages', '/notification-rules', '/manual-notifications', '/push-deliveries', '/user-reports', '/contact-messages', '/chat-oversight', '/admin', '/discount-codes', '/wallet-deposits', '/audit-logs', '/support-tickets', '/provinces', ] as const export const isAdminOnlyRoute = (pathname: string) => { if (pathname === '/events') return true return ADMIN_ROUTE_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)) } /** Backoffice: only `/auth` is reachable without a session. */ export const isPublicBrowseRoute = (pathname: string) => pathname === '/auth' || pathname.startsWith('/auth/') /** Backoffice is never search-indexable. */ export const isIndexableRoute = (_pathname: string) => false /** * Unauthenticated requests always go to admin `/auth` (never consumer AuthGate). */ export const buildGuestLoginRedirect = (requestUrl: string, pathname: string, search = ''): URL => { const returnPath = `${pathname}${search}` const loginUrl = new URL('/auth', requestUrl) if (returnPath !== '/' && !returnPath.startsWith('/auth')) { loginUrl.searchParams.set('redirect', returnPath) } return loginUrl } export const getDefaultPostLoginPath = (_role?: UserRole | null) => APP_ROUTES.DASHBOARD export const getSafeInternalRedirect = (value: string | null | undefined, fallback: string): string => { if (!value || !value.startsWith('/') || value.startsWith('//')) return fallback try { const base = 'https://internal.ghabilee.invalid' const url = new URL(value, base) if (url.origin !== base) return fallback return `${url.pathname}${url.search}${url.hash}` } catch { return fallback } } interface AccessTokenRoutingHint { role?: UserRole sub?: unknown } const decodeAccessTokenRoutingHint = (accessToken?: string): AccessTokenRoutingHint | null => { if (!accessToken) return null try { const payload = accessToken.split('.')[1] if (!payload) return null return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as AccessTokenRoutingHint } catch { return null } } export const getRoleFromAccessToken = (accessToken?: string): UserRole | null => { const decoded = decodeAccessTokenRoutingHint(accessToken) return decoded?.role === 'admin' ? 'admin' : decoded?.role === 'user' ? 'user' : null } /** Unverified JWT subject hint for viewer-scoped client cache keys only. */ export const getViewerIdFromAccessToken = (accessToken?: string): string | null => { const subject = decodeAccessTokenRoutingHint(accessToken)?.sub return typeof subject === 'string' && subject.length > 0 ? subject : null } // Kept for shared axios/auth code that still references these symbols. export const CONSUMER_AUTH_QUERY = 'auth' export const CONSUMER_SESSION_EXPIRED_EVENT = 'ghabilee:consumer-session-expired' export function isConsumerAccessTokenExpired(user: { AccessTokenExpireTime?: number | null } | null | undefined): boolean { return user?.AccessTokenExpireTime != null && user.AccessTokenExpireTime <= Date.now() } export const isConsumerShellRoute = (_pathname: string) => false export { isLegacyEventDetailRoute } from '@/lib/routing/eventRoute'