State Management
The frontend uses Zustand for global state. There are two stores.
uiStore
Manages auth state, the current organizational context, and UI flags.
Key state:
{ // Authentication isAuthenticated: boolean currentUser: { id: string; email: string; name: string; role: string } | null
// Organizational context orgId: string orgName: string orgSlug: string spaceId: string spaceName: string spaceSlug: string editionId: string editionName: string editions: EditionInfo[] liveEditionId: string | null // the edition the delivery key targets marketId: string marketName: string marketSlug: string markets: MarketInfo[]
// UI locale: string previewUrl: string isSidebarOpen: boolean selectedComponentId: string | null
// Impersonation (super_admin only) impersonateUserId: string | null
// Forces re-mount of space-dependent components contextKey: number}Persistence: The URL is the source of truth for the active context. The
slugs (orgSlug, spaceSlug, marketSlug) and locale are persisted to
localStorage using Zustand’s persist middleware; the raw ids (orgId,
spaceId, marketId, editionId) are deliberately dropped from the persisted
state and re-derived from the URL on load (see the partialize logic in
uiStore.ts). This keeps a refreshed tab from pinning a stale id that no longer
matches the URL.
Context key: When the space is switched (setSpace), contextKey is incremented. Components that should fully re-initialize on space change use this as a React key, forcing an unmount/remount.
API client integration: The lib/api.ts HTTP client reads from uiStore to automatically inject the X-Alokai-CMS-* context headers on every request.
pageStore
Manages the state of the page currently being edited.
Key state:
{ page: Page | null isDirty: boolean // unsaved changes exist
// Operations addComponent(zone, type, data): void updateComponent(id, data): void removeComponent(zone, id): void reorderComponents(zone, oldIndex, newIndex): void setZoneComponents(zone, components): void}The pageStore is reset whenever you navigate to a different page or switch locale.
HTTP client (lib/api.ts)
The API client is a thin wrapper around fetch that:
- Reads
orgId,spaceId,editionId,marketId, andimpersonateUserIdfromuiStore - Injects
X-Alokai-CMS-Organization,X-Alokai-CMS-Space,X-Alokai-CMS-Edition,X-Alokai-CMS-Market, and optionallyX-Alokai-CMS-Impersonateheaders - Handles 401 responses by clearing auth state and redirecting to login
import { api } from "@/lib/api";
const entries = await api.get("/api/entries");const entry = await api.post("/api/entries", { path: "/about", content_type: "page" });