/** * Per-browser tour dismissals in localStorage, keyed by tour id. Mirrors the * announcement dismissal store (browser storage is read during mount and * written during mount or the explicit dismissal handler; it is never * accessed during render). Dismissals are permanent per browser: a tour that * was dismissed never auto-starts again, but the header replay control can * always run it on demand. */ const DISMISSALS_STORAGE_KEY = "ptf:tour:dismissals"; const OPTED_OUT_STORAGE_KEY = "ptf:tours:opted-out"; export type TourDismissals = Record; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } export function readTourDismissals(now: number): TourDismissals { if (typeof window === "undefined") return {}; try { const raw = window.localStorage.getItem(DISMISSALS_STORAGE_KEY); if (!raw) return {}; const parsed: unknown = JSON.parse(raw); if (!isRecord(parsed)) return {}; const dismissals: TourDismissals = {}; for (const [key, value] of Object.entries(parsed)) { if (typeof value === "number" && Number.isFinite(value) && value > 0) { dismissals[key] = value; } } return dismissals; } catch (error) { if (error instanceof Error) return {}; throw error; } } export function writeTourDismissals(dismissals: TourDismissals, now: number): void { if (typeof window === "undefined") return; try { window.localStorage.setItem(DISMISSALS_STORAGE_KEY, JSON.stringify(dismissals)); } catch (error) { if (error instanceof Error) return; throw error; } } /** Merges a single dismissal (timestamp = dismissal time) into the stored map. */ export function storeTourDismissal(tourId: string, now: number): void { const existing = readTourDismissals(now); writeTourDismissals({ ...existing, [tourId]: now }, now); } /** True when the tour has been dismissed by this browser before `now`. */ export function isTourDismissed(tourId: string, now: number): boolean { return readTourDismissals(now)[tourId] !== undefined; } /** * Site-wide tours opt-out (per browser). When set, tours never auto-start; * the header replay control remains as the explicit opt-in. */ export function isToursOptedOut(): boolean { if (typeof window === "undefined") return false; try { return window.localStorage.getItem(OPTED_OUT_STORAGE_KEY) === "1"; } catch (error) { if (error instanceof Error) return false; throw error; } } export function setToursOptedOut(optedOut: boolean): void { if (typeof window === "undefined") return; try { if (optedOut) { window.localStorage.setItem(OPTED_OUT_STORAGE_KEY, "1"); } else { window.localStorage.removeItem(OPTED_OUT_STORAGE_KEY); } } catch (error) { if (error instanceof Error) return; throw error; } }