// Resource store — example domain entity. // Backed by localStorage today, but written so each call is a single function // you can swap with `api.get/post/put/del` once you have a real backend. import { useEffect, useSyncExternalStore } from "react" export type Resource = { id: string name: string status: "active" | "paused" | "archived" owner: string createdAt: number updatedAt: number } const STORAGE_KEY = "crema.resources" const CHANGE_EVENT = "crema:resources-change" function newId() { return `r-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}` } function readFromStorage(): Resource[] { if (typeof window === "undefined") return [] try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return [] const parsed = JSON.parse(raw) if (!Array.isArray(parsed)) return [] return parsed.filter( (r): r is Resource => r && typeof r.id === "string" && typeof r.name === "string" && ["active", "paused", "archived"].includes(r.status) && typeof r.owner === "string" && typeof r.createdAt === "number" && typeof r.updatedAt === "number", ) } catch { return [] } } function write(items: Resource[]) { if (typeof window === "undefined") return try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)) window.dispatchEvent(new CustomEvent(CHANGE_EVENT)) } catch { /* quota */ } } // CRUD — these mirror what `api.get/post/put/del` would look like. export function listResources(): Resource[] { return readFromStorage() } export function createResource(input: { name: string owner: string status?: Resource["status"] }): Resource { const now = Date.now() const r: Resource = { id: newId(), name: input.name, owner: input.owner, status: input.status ?? "active", createdAt: now, updatedAt: now, } write([r, ...readFromStorage()]) return r } export function updateResource( id: string, patch: Partial>, ): Resource | null { const items = readFromStorage() let updated: Resource | null = null const next = items.map((r) => { if (r.id !== id) return r updated = { ...r, ...patch, updatedAt: Date.now() } return updated }) if (updated) write(next) return updated } export function deleteResource(id: string) { write(readFromStorage().filter((r) => r.id !== id)) } let cached: Resource[] | null = null function subscribe(cb: () => void) { const onChange = () => { cached = null cb() } window.addEventListener(CHANGE_EVENT, onChange) window.addEventListener("storage", (e) => { if (e.key === STORAGE_KEY) onChange() }) return () => window.removeEventListener(CHANGE_EVENT, onChange) } function getSnapshot(): Resource[] { if (!cached) cached = readFromStorage() return cached } function getServerSnapshot(): Resource[] { return [] } export function useResources(): Resource[] { const v = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) useEffect(() => { cached = null }, []) return v } /** Seed a few rows on first load so the table isn't empty. */ export function seedResourcesIfEmpty() { if (typeof window === "undefined") return if (localStorage.getItem(STORAGE_KEY)) return const now = Date.now() const seed: Resource[] = [ { id: newId(), name: "Acme dashboard", status: "active", owner: "Atlas", createdAt: now - 86_400_000 * 3, updatedAt: now - 3600_000, }, { id: newId(), name: "Onboarding pipeline", status: "paused", owner: "Forge", createdAt: now - 86_400_000 * 7, updatedAt: now - 86_400_000, }, { id: newId(), name: "Q1 report draft", status: "archived", owner: "Inkwell", createdAt: now - 86_400_000 * 30, updatedAt: now - 86_400_000 * 14, }, ] write(seed) }