admin/lib/notificationNavigation.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

41 lines
1.4 KiB
TypeScript

const INTERNAL_URL_BASE = 'https://notifications.ghabilee.invalid'
const hasControlCharacter = (value: string) =>
Array.from(value).some((character) => {
const code = character.charCodeAt(0)
return code <= 31 || code === 127
})
export const NOTIFICATIONS_RETURN_PATH = '/profile/notifications'
export const isSafeInternalNavigationPath = (value: string | null | undefined): value is string => {
if (!value || !value.startsWith('/') || value.startsWith('//') || value.includes('\\') || hasControlCharacter(value)) {
return false
}
try {
return new URL(value, INTERNAL_URL_BASE).origin === INTERNAL_URL_BASE
} catch {
return false
}
}
export const addReturnTo = (path: string, returnTo: string): string | null => {
if (!isSafeInternalNavigationPath(path) || !isSafeInternalNavigationPath(returnTo)) return null
const url = new URL(path, INTERNAL_URL_BASE)
url.searchParams.set('returnTo', returnTo)
return `${url.pathname}${url.search}${url.hash}`
}
export const buildNotificationActionPath = (actionUrl: string | null | undefined) =>
actionUrl ? addReturnTo(/^\/bookings\/[^/?#]+(?:[?#]|$)/.test(actionUrl) ? '/my-events' : actionUrl, NOTIFICATIONS_RETURN_PATH) : null
export const getSafeReturnTo = (search: string) => {
const returnTo = new URLSearchParams(search).get('returnTo')
return isSafeInternalNavigationPath(returnTo) ? returnTo : null
}