Files
accounted/components/dashboard/system-notice.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

59 lines
2.1 KiB
TypeScript

/**
* System notice window: a one-off, operator-set banner ("high load right
* now") shown to every signed-in user until a fixed point in time.
*
* The switch is NEXT_PUBLIC_SYSTEM_NOTICE_UNTIL, an ISO timestamp with an
* explicit offset (e.g. 2026-09-10T23:00:00+02:00). The value doubles as the
* dismiss key: closing the banner stores the timestamp in localStorage, so a
* later notice with a new timestamp shows once again while the old dismissal
* stays inert. No DB read: the notice must survive the DB being unavailable.
*/
export const SYSTEM_NOTICE_STORAGE_KEY = 'Accounted:system-notice-dismissed'
/**
* A date-time without Z or a numeric offset is parsed as the runtime's local
* time, which is UTC on Vercel and whatever the operator's laptop is locally.
* Require the offset so the deadline means the same instant everywhere.
*/
const HAS_UTC_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/i
/**
* Parse the raw env value into an epoch ms deadline. Returns null when the
* value is missing, has no UTC offset, is unparseable, or is already in the
* past, so callers render nothing without a second check.
*/
export function parseSystemNoticeUntil(
raw: string | undefined | null,
now: number = Date.now(),
): number | null {
const trimmed = raw?.trim()
if (!trimmed) return null
if (!HAS_UTC_OFFSET.test(trimmed)) return null
const until = new Date(trimmed).getTime()
if (!Number.isFinite(until)) return null
return until > now ? until : null
}
type StorageLike = Pick<Storage, 'getItem' | 'setItem'>
export function isSystemNoticeDismissed(
storage: StorageLike | null | undefined,
until: number,
): boolean {
try {
return storage?.getItem(SYSTEM_NOTICE_STORAGE_KEY) === String(until)
} catch {
return false
}
}
export function dismissSystemNotice(storage: StorageLike | null | undefined, until: number): void {
try {
storage?.setItem(SYSTEM_NOTICE_STORAGE_KEY, String(until))
} catch {
// Private mode or blocked storage: the banner closes for this page
// load and may show again next time, which is the acceptable fallback.
}
}