12 KiB
Zustand State Management
Last Verified: January 20, 2026
Version: 1.8.4
Framework: Zustand 4 with persist middleware
Store Architecture
All stores in /frontend/src/store/ use Zustand with TypeScript.
Key Patterns:
persistmiddleware for localStorage persistence- Async actions for API calls
- Selectors for derived state
Available Stores (11 total):
| Store | File | Purpose |
|---|---|---|
| Auth | authStore.ts |
Authentication, user session, account |
| Site | siteStore.ts |
Site selection/management |
| Sector | sectorStore.ts |
Sector management within sites |
| Planner | plannerStore.ts |
Planner module state |
| Billing | billingStore.ts |
Credits, billing info |
| Notification | notificationStore.ts |
App notifications |
| Settings | settingsStore.ts |
User preferences |
| Module | moduleStore.ts |
Module enable/disable state |
| Column Visibility | columnVisibilityStore.ts |
Table column preferences |
| Page Size | pageSizeStore.ts |
Table pagination preferences |
| Onboarding | onboardingStore.ts |
Onboarding wizard state |
Auth Store (authStore.ts)
Purpose: User authentication and session management
interface AuthState {
user: User | null;
account: Account | null;
isAuthenticated: boolean;
accessToken: string | null;
refreshToken: string | null;
isLoading: boolean;
error: string | null;
}
interface AuthActions {
login(email: string, password: string): Promise<void>;
logout(): void;
register(data: RegisterData): Promise<void>;
refreshAccessToken(): Promise<void>;
fetchUser(): Promise<void>;
updateUser(data: UserUpdate): Promise<void>;
}
Persistence: accessToken, refreshToken in localStorage
Usage:
const { user, login, logout, isAuthenticated } = useAuthStore();
Site Store (siteStore.ts)
Purpose: Site selection and management
interface SiteState {
sites: Site[];
currentSite: Site | null;
isLoading: boolean;
error: string | null;
}
interface SiteActions {
fetchSites(): Promise<void>;
createSite(data: SiteCreate): Promise<Site>;
updateSite(id: string, data: SiteUpdate): Promise<Site>;
deleteSite(id: string): Promise<void>;
setCurrentSite(site: Site): void;
}
Persistence: currentSite.id in localStorage
Auto-selection: If no site selected and sites exist, auto-selects first site
Sector Store (sectorStore.ts)
Purpose: Sector selection and management within sites
interface SectorState {
sectors: Sector[];
currentSector: Sector | null;
isLoading: boolean;
error: string | null;
}
interface SectorActions {
fetchSectors(siteId: string): Promise<void>;
createSector(data: SectorCreate): Promise<Sector>;
updateSector(id: string, data: SectorUpdate): Promise<Sector>;
deleteSector(id: string): Promise<void>;
setCurrentSector(sector: Sector): void;
}
Persistence: currentSector.id in localStorage
Dependency: Reloads when currentSite changes
Module Store (moduleStore.ts)
Purpose: Track which modules are enabled/disabled
interface ModuleState {
modules: ModuleSettings;
isLoading: boolean;
error: string | null;
}
interface ModuleSettings {
planner_enabled: boolean;
writer_enabled: boolean;
linker_enabled: boolean;
optimizer_enabled: boolean;
automation_enabled: boolean;
integration_enabled: boolean;
publisher_enabled: boolean;
}
interface ModuleActions {
fetchModules(): Promise<void>;
updateModules(settings: Partial<ModuleSettings>): Promise<void>;
isModuleEnabled(module: ModuleName): boolean;
}
Usage:
const { isModuleEnabled } = useModuleStore();
if (isModuleEnabled('planner')) { /* show planner */ }
Currently used for: Sidebar visibility only
Billing Store (billingStore.ts)
Purpose: Credit balance and usage tracking
interface BillingState {
balance: CreditBalance | null;
usage: CreditUsage[];
limits: PlanLimits | null;
isLoading: boolean;
error: string | null;
}
interface CreditBalance {
ideaCredits: number;
contentCredits: number;
imageCredits: number;
optimizationCredits: number;
}
interface BillingActions {
fetchBalance(): Promise<void>;
fetchUsage(period?: string): Promise<void>;
fetchLimits(): Promise<void>;
}
Refresh triggers:
- After content generation
- After image generation
- After optimization (when implemented)
Planner Store (plannerStore.ts)
Purpose: Keywords, clusters, and content ideas state
interface PlannerState {
keywords: Keyword[];
clusters: Cluster[];
ideas: ContentIdea[];
selectedKeywords: string[];
filters: KeywordFilters;
isLoading: boolean;
error: string | null;
}
interface PlannerActions {
fetchKeywords(siteId: string, sectorId?: string): Promise<void>;
createKeyword(data: KeywordCreate): Promise<Keyword>;
bulkDeleteKeywords(ids: string[]): Promise<void>;
autoCluster(keywordIds: string[]): Promise<void>;
generateIdeas(clusterId: string): Promise<void>;
setFilters(filters: Partial<KeywordFilters>): void;
selectKeywords(ids: string[]): void;
}
Writer Store (writerStore.ts)
Purpose: Tasks and content management
interface WriterState {
tasks: Task[];
content: Content[];
currentContent: Content | null;
filters: TaskFilters;
isLoading: boolean;
isGenerating: boolean;
error: string | null;
}
interface WriterActions {
fetchTasks(siteId: string, sectorId?: string): Promise<void>;
createTask(data: TaskCreate): Promise<Task>;
generateContent(taskId: string): Promise<Content>;
fetchContent(contentId: string): Promise<Content>;
updateContent(id: string, data: ContentUpdate): Promise<Content>;
generateImages(contentId: string): Promise<void>;
publishToWordPress(contentId: string): Promise<void>;
}
Generation state: isGenerating tracks active AI operations
Automation Store (automationStore.ts)
Purpose: Automation pipeline state and control
interface AutomationState {
config: AutomationConfig | null;
currentRun: AutomationRun | null;
pipeline: PipelineOverview | null;
history: AutomationRun[];
logs: AutomationLog[];
isLoading: boolean;
error: string | null;
}
interface AutomationActions {
fetchConfig(siteId: string): Promise<void>;
updateConfig(data: ConfigUpdate): Promise<void>;
startRun(siteId: string): Promise<void>;
pauseRun(runId: string): Promise<void>;
resumeRun(runId: string): Promise<void>;
cancelRun(runId: string): Promise<void>;
fetchPipeline(siteId: string): Promise<void>;
fetchLogs(runId: string): Promise<void>;
}
Polling: Active runs trigger status polling every 5 seconds
Integration Store (integrationStore.ts)
Purpose: WordPress integration management
interface IntegrationState {
integrations: SiteIntegration[];
currentIntegration: SiteIntegration | null;
syncStatus: SyncStatus | null;
isLoading: boolean;
isSyncing: boolean;
error: string | null;
}
interface IntegrationActions {
fetchIntegrations(siteId: string): Promise<void>;
createIntegration(data: IntegrationCreate): Promise<SiteIntegration>;
testConnection(id: string): Promise<TestResult>;
triggerSync(id: string): Promise<void>;
fetchSyncStatus(id: string): Promise<SyncStatus>;
}
UI Store (uiStore.ts)
Purpose: UI state (sidebar, modals, notifications)
interface UIState {
sidebarOpen: boolean;
sidebarCollapsed: boolean;
theme: 'light' | 'dark' | 'system';
notifications: Notification[];
}
interface UIActions {
toggleSidebar(): void;
collapseSidebar(): void;
setTheme(theme: Theme): void;
addNotification(notification: Notification): void;
removeNotification(id: string): void;
}
Persistence: theme, sidebarCollapsed in localStorage
Notification Store (notificationStore.ts) - v1.2.0
Purpose: In-app notifications for AI task completions and system events
type NotificationType = 'success' | 'error' | 'warning' | 'info';
type NotificationCategory = 'ai_task' | 'system' | 'info';
interface Notification {
id: string;
apiId?: number; // Server ID for synced notifications
type: NotificationType;
category: NotificationCategory;
title: string;
message: string;
timestamp: Date;
read: boolean;
actionLabel?: string;
actionHref?: string;
metadata?: {
taskId?: string;
functionName?: string;
count?: number;
credits?: number;
};
}
interface NotificationStore {
notifications: Notification[];
unreadCount: number;
isLoading: boolean;
lastFetched: Date | null;
// Actions
addNotification(notification: Omit<Notification, 'id' | 'timestamp' | 'read'>): void;
markAsRead(id: string): void;
markAllAsRead(): void;
removeNotification(id: string): void;
clearAll(): void;
fetchFromAPI(): Promise<void>;
syncWithAPI(): Promise<void>;
}
Features:
- In-memory queue for optimistic UI updates
- API sync for persistent notifications
- Auto-dismissal with configurable timeout
- Read/unread state tracking
- Category-based filtering (ai_task, system, info)
API Integration: Uses notifications.api.ts service:
fetchNotifications()- List with paginationfetchUnreadCount()- Unread countmarkNotificationRead()- Mark single as readmarkAllNotificationsRead()- Mark all as readdeleteNotification()- Delete notification
Usage:
const { notifications, unreadCount, addNotification, markAsRead } = useNotificationStore();
// Add AI task completion notification
addNotification({
type: 'success',
category: 'ai_task',
title: 'Content Generated',
message: 'Generated 5 articles successfully',
metadata: { count: 5, credits: 250 }
});
Store Dependencies
authStore
│
└── siteStore (loads after auth)
│
└── sectorStore (loads when site changes)
│
├── plannerStore (scoped to site/sector)
├── writerStore (scoped to site/sector)
├── automationStore (scoped to site)
└── integrationStore (scoped to site)
notificationStore (global, polls/syncs independently)
moduleStore (global, loads once per account)
billingStore (global, loads once per account)
onboardingStore (global, syncs with UserSettings backend)
uiStore (local only, no API)
Onboarding Store (onboardingStore.ts) (v1.3.2)
Purpose: Manages onboarding wizard and guide screen state
interface OnboardingState {
isGuideDismissed: boolean;
isGuideVisible: boolean;
isLoading: boolean;
lastSyncedAt: Date | null;
}
interface OnboardingActions {
dismissGuide(): Promise<void>;
showGuide(): void;
toggleGuide(): void;
loadFromBackend(): Promise<void>;
syncToBackend(dismissed: boolean): Promise<void>;
}
Persistence:
- Local:
onboarding-storagein localStorage - Backend:
UserSettingswith keyworkflow_guide_dismissed
Features:
- Cross-device sync via backend UserSettings
- Rate-limited sync (max every 5 minutes)
- Graceful fallback if backend unavailable
Usage:
const { isGuideVisible, dismissGuide, showGuide } = useOnboardingStore();
// Show onboarding wizard
if (isGuideVisible) {
return <OnboardingWizard onDismiss={dismissGuide} />;
}
// Re-show guide button
<Button onClick={showGuide}>Show Guide</Button>
Store Files Location
frontend/src/store/
├── authStore.ts
├── siteStore.ts
├── sectorStore.ts
├── moduleStore.ts
├── billingStore.ts
├── notificationStore.ts # v1.2.0
├── onboardingStore.ts
├── pageSizeStore.ts
├── plannerStore.ts
├── writerStore.ts
├── automationStore.ts
├── integrationStore.ts
├── uiStore.ts
└── index.ts (re-exports)
Best Practices
- Always scope to site/sector when fetching module data
- Check module enabled before showing features
- Handle loading states in UI components
- Clear store on logout to prevent data leaks
- Use selectors for derived/filtered data
Planned Changes
| Item | Description | Priority |
|---|---|---|
Add linkerStore |
State for internal linking results | Medium |
Add optimizerStore |
State for optimization results | Medium |
| Better error typing | Typed error codes per store | Low |
| DevTools integration | Zustand devtools middleware | Low |