Phase 6: Workspaces — Widget CRUD, Manager-Check, Cross-Tenant, Zustand Store, Settings Route
Backend:
- Widget CRUD: get_widgets, create_widget, update_widget, delete_widget
- Manager role check: is_workspace_manager
- Cross-tenant validation: verify_user_same_tenant (UserTenant)
- Default workspace seeding: seed_default_workspace with 12 standard modules
- Set user default workspace: set_user_default_workspace
- Fix create_workspace default uniqueness (unset others before insert)
- Widget CRUD routes: GET/POST/PUT/DELETE /{workspace_id}/widgets
- Set-default route: POST /{workspace_id}/set-default
- Cross-tenant validation in assign_user route
Frontend:
- workspaceStore (Zustand): central state with sessionStorage persistence
- API client interceptor: X-Workspace-ID header on all requests
- useWorkspace hook refactored to use workspaceStore
- Widget API hooks: useWorkspaceWidgets, useCreateWorkspaceWidget, etc.
- useSetDefaultWorkspace hook
- Settings route: /settings/workspaces with WorkspaceManagerPage
- Settings nav item for Workspaces
Tests:
- 25 backend tests (CRUD, modules, widgets, users, manager, seeding, context, isolation)
- 12 frontend tests (workspaceStore state, visibility, persistence, reset)
- 48/48 backend tests passing
- 12/12 frontend tests passing
This commit is contained in:
@@ -30,6 +30,13 @@ export function getCsrfToken(): string | null {
|
||||
let onUnauthorized: (() => void) | null = null;
|
||||
let onValidationError: ((errors: Record<string, string[]>) => void) | null = null;
|
||||
|
||||
// Workspace context — set by workspaceStore, sent as X-Workspace-ID header
|
||||
let activeWorkspaceId: string | null = null;
|
||||
|
||||
export function setActiveWorkspaceId(id: string | null) {
|
||||
activeWorkspaceId = id;
|
||||
}
|
||||
|
||||
export function setUnauthorizedHandler(handler: () => void) {
|
||||
onUnauthorized = handler;
|
||||
}
|
||||
@@ -45,6 +52,10 @@ apiClient.interceptors.request.use(
|
||||
if (unsafe.includes(config.method?.toLowerCase() ?? '') && csrfToken) {
|
||||
config.headers['X-CSRF-Token'] = csrfToken;
|
||||
}
|
||||
// Attach X-Workspace-ID header for workspace context (per-tab)
|
||||
if (activeWorkspaceId) {
|
||||
config.headers['X-Workspace-ID'] = activeWorkspaceId;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
|
||||
Reference in New Issue
Block a user