|
|
|
@ -8,6 +8,8 @@ |
|
|
|
* The raw auth token is NEVER used as a key component. |
|
|
|
*/ |
|
|
|
|
|
|
|
import type { MarriageProfileResponse } from "@/hooks/marriage/types"; |
|
|
|
|
|
|
|
export const SCOPED_STORAGE_VERSION = 3; |
|
|
|
|
|
|
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
|
|
@ -283,3 +285,83 @@ export function extractSlugFromScopedKey(key: string): string | null { |
|
|
|
const match = key.match(/^marriage:user:\d+:sections:([^:]+):draft:v\d+$/); |
|
|
|
return match ? match[1] : null; |
|
|
|
} |
|
|
|
|
|
|
|
// ─── Scoped Marriage Profile Cache ─────────────────────────────────────
|
|
|
|
|
|
|
|
export interface ScopedMarriageProfileData { |
|
|
|
version: typeof SCOPED_STORAGE_VERSION; |
|
|
|
ownerProfileId: number; |
|
|
|
profile: MarriageProfileResponse; |
|
|
|
savedAt: number; |
|
|
|
} |
|
|
|
|
|
|
|
export function getScopedMarriageProfileKey(profileId: number): string { |
|
|
|
return `marriage:user:${profileId}:profile:v${SCOPED_STORAGE_VERSION}`; |
|
|
|
} |
|
|
|
|
|
|
|
export const LATEST_PROFILE_STORAGE_KEY = `marriage:profile:latest:v${SCOPED_STORAGE_VERSION}`; |
|
|
|
|
|
|
|
/** |
|
|
|
* Persist the latest full MarriageProfileResponse from the server. |
|
|
|
*/ |
|
|
|
export function saveScopedMarriageProfile(profile: MarriageProfileResponse): void { |
|
|
|
if (typeof window === "undefined" || !profile?.id) return; |
|
|
|
try { |
|
|
|
const payload: ScopedMarriageProfileData = { |
|
|
|
version: SCOPED_STORAGE_VERSION, |
|
|
|
ownerProfileId: profile.id, |
|
|
|
profile, |
|
|
|
savedAt: Date.now(), |
|
|
|
}; |
|
|
|
const serialized = JSON.stringify(payload); |
|
|
|
window.localStorage.setItem(getScopedMarriageProfileKey(profile.id), serialized); |
|
|
|
window.localStorage.setItem(LATEST_PROFILE_STORAGE_KEY, serialized); |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieve the persisted MarriageProfileResponse if valid. |
|
|
|
*/ |
|
|
|
export function getScopedMarriageProfile( |
|
|
|
expectedProfileId?: number, |
|
|
|
): MarriageProfileResponse | null { |
|
|
|
if (typeof window === "undefined") return null; |
|
|
|
try { |
|
|
|
const key = expectedProfileId |
|
|
|
? getScopedMarriageProfileKey(expectedProfileId) |
|
|
|
: LATEST_PROFILE_STORAGE_KEY; |
|
|
|
const raw = window.localStorage.getItem(key); |
|
|
|
if (!raw) return null; |
|
|
|
const parsed = JSON.parse(raw) as ScopedMarriageProfileData; |
|
|
|
if (!parsed || parsed.version !== SCOPED_STORAGE_VERSION || !parsed.profile) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
if (expectedProfileId && parsed.ownerProfileId !== expectedProfileId) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
return parsed.profile; |
|
|
|
} catch { |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Clear all persisted marriage profile caches (used on auth token/account change). |
|
|
|
*/ |
|
|
|
export function clearScopedMarriageProfiles(): void { |
|
|
|
if (typeof window === "undefined") return; |
|
|
|
try { |
|
|
|
window.localStorage.removeItem(LATEST_PROFILE_STORAGE_KEY); |
|
|
|
const keysToRemove: string[] = []; |
|
|
|
for (let i = 0; i < window.localStorage.length; i++) { |
|
|
|
const key = window.localStorage.key(i); |
|
|
|
if (key && key.startsWith("marriage:user:") && key.includes(":profile:")) { |
|
|
|
keysToRemove.push(key); |
|
|
|
} |
|
|
|
} |
|
|
|
for (const key of keysToRemove) { |
|
|
|
window.localStorage.removeItem(key); |
|
|
|
} |
|
|
|
} catch {} |
|
|
|
} |
|
|
|
|