Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
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'
|