Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 2x 2x 94x 94x 94x 94x 94x 94x 32x 32x 94x 94x 2x 2x 94x 35x 35x 35x 35x 34x 1x 35x 94x 32x 94x 64x 94x 40x 94x 91x 94x 346x 346x 346x | /**
* Shared state for the Collections workspace shell.
* Provides collection list, rail collapse, and import dialog orchestration.
*/
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react';
import {
getCollections,
type CollectionListItem,
} from '@/services/collections.service';
import { handleApiError } from '@/lib/api';
import { isSuperadminAccount } from '@/lib/auth';
import { useAuthStore } from '@/stores/auth.store';
interface CollectionsWorkspaceContextValue {
collections: CollectionListItem[];
loading: boolean;
error: string | null;
refreshCollections: () => Promise<void>;
railCollapsed: boolean;
setRailCollapsed: (collapsed: boolean) => void;
isSuperadmin: boolean;
importDialogOpen: boolean;
setImportDialogOpen: (open: boolean) => void;
collectionNames: string[];
getListItem: (name: string) => CollectionListItem | undefined;
}
const CollectionsWorkspaceContext = createContext<CollectionsWorkspaceContextValue | null>(
null,
);
const RAIL_COLLAPSED_KEY = 'snackbase_collections_rail_collapsed';
export function CollectionsWorkspaceProvider({ children }: { children: ReactNode }) {
const { account } = useAuthStore();
// Superadmin = system account membership (backend require_superadmin), not role name.
// Superadmins are seeded with role "admin" on the system account.
const isSuperadmin = isSuperadminAccount(account);
const [collections, setCollections] = useState<CollectionListItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [railCollapsed, setRailCollapsedState] = useState(() => {
Iif (typeof window === 'undefined') return false;
return localStorage.getItem(RAIL_COLLAPSED_KEY) === 'true';
});
const [importDialogOpen, setImportDialogOpen] = useState(false);
const setRailCollapsed = useCallback((collapsed: boolean) => {
setRailCollapsedState(collapsed);
localStorage.setItem(RAIL_COLLAPSED_KEY, String(collapsed));
}, []);
const refreshCollections = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await getCollections({
page: 1,
page_size: 100,
sort_by: 'name',
sort_order: 'asc',
});
setCollections(response.items);
} catch (err) {
setError(handleApiError(err));
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void refreshCollections();
}, [refreshCollections]);
const collectionNames = useMemo(
() => collections.map((c) => c.name),
[collections],
);
const getListItem = useCallback(
(name: string) => collections.find((c) => c.name === name),
[collections],
);
const value = useMemo<CollectionsWorkspaceContextValue>(
() => ({
collections,
loading,
error,
refreshCollections,
railCollapsed,
setRailCollapsed,
isSuperadmin,
importDialogOpen,
setImportDialogOpen,
collectionNames,
getListItem,
}),
[
collections,
loading,
error,
refreshCollections,
railCollapsed,
setRailCollapsed,
isSuperadmin,
importDialogOpen,
collectionNames,
getListItem,
],
);
return (
<CollectionsWorkspaceContext.Provider value={value}>
{children}
</CollectionsWorkspaceContext.Provider>
);
}
// Context hooks are co-located with providers (standard React pattern).
// eslint-disable-next-line react-refresh/only-export-components
export function useCollectionsWorkspace() {
const ctx = useContext(CollectionsWorkspaceContext);
Iif (!ctx) {
throw new Error(
'useCollectionsWorkspace must be used within CollectionsWorkspaceProvider',
);
}
return ctx;
}
|