Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Updated login and signup components to utilize AuthCard for better user experience during redirection. - Introduced AuthentikEmbedDialog for seamless integration of Authentik's identity portal within the application. - Enhanced password recovery and signup flows with dynamic theme handling and improved loading states. - Refactored existing components to streamline authentication processes and improve maintainability.
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
createJSONStorage,
|
|
type PersistStorage,
|
|
type StateStorage,
|
|
type StorageValue,
|
|
} from "zustand/middleware"
|
|
|
|
const DEFAULT_DEBOUNCE_MS = 220
|
|
|
|
/** In-memory fallback when `localStorage` is missing (SSR) or throws (private mode, etc.). */
|
|
function createMemoryStateStorage(): StateStorage {
|
|
const data = new Map<string, string>()
|
|
return {
|
|
getItem: (name) => data.get(name) ?? null,
|
|
setItem: (name, value) => {
|
|
data.set(name, value)
|
|
},
|
|
removeItem: (name) => {
|
|
data.delete(name)
|
|
},
|
|
}
|
|
}
|
|
|
|
function getPersistBackingStorage(): StateStorage {
|
|
if (typeof window === "undefined") {
|
|
return createMemoryStateStorage()
|
|
}
|
|
try {
|
|
return window.localStorage
|
|
} catch {
|
|
return createMemoryStateStorage()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JSON persist storage that debounces writes to `localStorage` so rapid
|
|
* store updates do not block the main thread on every mutation.
|
|
* Flushes pending keys on a timer, `beforeunload`, and `pagehide`.
|
|
* Uses in-memory storage during SSR or when `localStorage` is unavailable.
|
|
*/
|
|
function buildDebouncedJsonStorage(): PersistStorage<unknown> {
|
|
const base =
|
|
createJSONStorage(getPersistBackingStorage) ??
|
|
createJSONStorage(() => createMemoryStateStorage())
|
|
if (!base) {
|
|
throw new Error("[debounced-json-storage] failed to create JSON storage")
|
|
}
|
|
|
|
const pending = new Map<string, StorageValue<unknown>>()
|
|
const timers = new Map<string, ReturnType<typeof globalThis.setTimeout>>()
|
|
|
|
const flushKey = (name: string) => {
|
|
const t = timers.get(name)
|
|
if (t !== undefined) {
|
|
globalThis.clearTimeout(t)
|
|
timers.delete(name)
|
|
}
|
|
const value = pending.get(name)
|
|
if (value === undefined) return
|
|
pending.delete(name)
|
|
base.setItem(name, value)
|
|
}
|
|
|
|
const flushAll = () => {
|
|
for (const name of [...pending.keys()]) flushKey(name)
|
|
}
|
|
|
|
if (typeof window !== "undefined") {
|
|
window.addEventListener("beforeunload", flushAll)
|
|
window.addEventListener("pagehide", flushAll)
|
|
}
|
|
|
|
const flushPersistStorage = () => {
|
|
flushAll()
|
|
}
|
|
|
|
return {
|
|
getItem: (name) => base.getItem(name),
|
|
setItem: (name, value) => {
|
|
pending.set(name, value)
|
|
const existing = timers.get(name)
|
|
if (existing !== undefined) globalThis.clearTimeout(existing)
|
|
const id = globalThis.setTimeout(() => {
|
|
timers.delete(name)
|
|
flushKey(name)
|
|
}, DEFAULT_DEBOUNCE_MS)
|
|
timers.set(name, id)
|
|
},
|
|
removeItem: (name) => {
|
|
const existing = timers.get(name)
|
|
if (existing !== undefined) {
|
|
globalThis.clearTimeout(existing)
|
|
timers.delete(name)
|
|
}
|
|
pending.delete(name)
|
|
return base.removeItem(name)
|
|
},
|
|
flush: flushPersistStorage,
|
|
}
|
|
}
|
|
|
|
/** Shared instance for all zustand `persist` stores in this app. */
|
|
export const debouncedPersistJSONStorage = buildDebouncedJsonStorage()
|
|
|
|
/** Flush pending debounced persist writes (call before hard navigation on logout). */
|
|
export function flushPersistStorage() {
|
|
const storage = debouncedPersistJSONStorage as PersistStorage<unknown> & {
|
|
flush?: () => void
|
|
}
|
|
storage.flush?.()
|
|
}
|