@sanity/sdk-react 3.0.0-rc.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/_exports/dashboard.d.ts +209 -0
  2. package/dist/_exports/dashboard.d.ts.map +1 -0
  3. package/dist/_exports/dashboard.js +278 -0
  4. package/dist/_exports/dashboard.js.map +1 -0
  5. package/dist/index.d.ts +86 -261
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +134 -532
  8. package/dist/index.js.map +1 -1
  9. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js +177 -0
  10. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map +1 -0
  11. package/package.json +9 -8
  12. package/src/_exports/dashboard.ts +11 -0
  13. package/src/_exports/sdk-react.ts +2 -12
  14. package/src/components/SDKProvider.test.tsx +5 -5
  15. package/src/components/auth/AuthBoundary.tsx +5 -5
  16. package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
  17. package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
  18. package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
  19. package/src/context/OrganizationResourcesProvider.tsx +2 -2
  20. package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
  21. package/src/hooks/comlink/useWindowConnection.ts +1 -1
  22. package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +1 -1
  23. package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
  24. package/src/hooks/dashboard/useFavorite.ts +34 -0
  25. package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
  26. package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
  27. package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
  28. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
  29. package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
  30. package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
  31. package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
  32. package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
  33. package/src/hooks/dashboard/useWindowTitle.ts +1 -1
  34. package/src/hooks/datasets/useDatasets.test.tsx +29 -22
  35. package/src/hooks/datasets/useDatasets.ts +31 -53
  36. package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
  37. package/src/hooks/dashboard/useManageFavorite.ts +0 -173
  38. /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
@@ -0,0 +1,177 @@
1
+ import { c } from "react-compiler-runtime";
2
+ import { getDashboardOrganizationId } from "@sanity/sdk";
3
+ import { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
4
+ import { SDK_CHANNEL_NAME, SDK_NODE_NAME } from "@sanity/message-protocol";
5
+ import { getNodeState } from "@sanity/sdk/comlink";
6
+ import { filter, firstValueFrom } from "rxjs";
7
+ const SanityInstanceContext = createContext(null), useSanityInstance = () => {
8
+ let instance = useContext(SanityInstanceContext);
9
+ if (!instance) throw Error("SanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.");
10
+ return instance;
11
+ };
12
+ function createStateSourceHook(options) {
13
+ let getState = typeof options == "function" ? options : options.getState, suspense = "shouldSuspend" in options && "suspender" in options ? options : void 0;
14
+ function useHook(...t0) {
15
+ let $ = c(3), params = t0, instance = useSanityInstance();
16
+ if (suspense?.suspender && suspense?.shouldSuspend?.(instance, ...params)) throw suspense.suspender(instance, ...params);
17
+ let t1;
18
+ $[0] !== instance || $[1] !== params ? (t1 = getState(instance, ...params), $[0] = instance, $[1] = params, $[2] = t1) : t1 = $[2];
19
+ let state = t1;
20
+ return useSyncExternalStore(state.subscribe, state.getCurrent);
21
+ }
22
+ return useHook;
23
+ }
24
+ const useNodeState = createStateSourceHook({
25
+ getState: getNodeState,
26
+ shouldSuspend: (instance, nodeInput) => getNodeState(instance, nodeInput).getCurrent() === void 0,
27
+ suspender: (instance, nodeInput) => firstValueFrom(getNodeState(instance, nodeInput).observable.pipe(filter(Boolean)))
28
+ });
29
+ /**
30
+ * @internal
31
+ * Hook to wrap a Comlink node in a React hook.
32
+ * Our store functionality takes care of the lifecycle of the node,
33
+ * as well as sharing a single node between invocations if they share the same name.
34
+ *
35
+ * Generally not to be used directly, but to be used as a dependency of
36
+ * Comlink-powered hooks like `useStudioWorkspacesByProjectIdDataset`.
37
+ */
38
+ function useWindowConnection(t0) {
39
+ let $ = c(19), { name, connectTo, onMessage } = t0, t1;
40
+ $[0] !== connectTo || $[1] !== name ? (t1 = {
41
+ name,
42
+ connectTo
43
+ }, $[0] = connectTo, $[1] = name, $[2] = t1) : t1 = $[2];
44
+ let { node } = useNodeState(t1), t2;
45
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t2 = [], $[3] = t2) : t2 = $[3];
46
+ let messageUnsubscribers = useRef(t2), instance = useSanityInstance(), t3;
47
+ $[4] !== node || $[5] !== onMessage ? (t3 = () => (onMessage && Object.entries(onMessage).forEach((t4) => {
48
+ let [type, handler] = t4, messageUnsubscribe = node.on(type, handler);
49
+ messageUnsubscribe && messageUnsubscribers.current.push(messageUnsubscribe);
50
+ }), () => {
51
+ messageUnsubscribers.current.forEach(_temp), messageUnsubscribers.current = [];
52
+ }), $[4] = node, $[5] = onMessage, $[6] = t3) : t3 = $[6];
53
+ let t4;
54
+ $[7] !== instance || $[8] !== name || $[9] !== node || $[10] !== onMessage ? (t4 = [
55
+ instance,
56
+ name,
57
+ onMessage,
58
+ node
59
+ ], $[7] = instance, $[8] = name, $[9] = node, $[10] = onMessage, $[11] = t4) : t4 = $[11], useEffect(t3, t4);
60
+ let t5;
61
+ $[12] === node ? t5 = $[13] : (t5 = (type_0, data) => {
62
+ node.post(type_0, data);
63
+ }, $[12] = node, $[13] = t5);
64
+ let sendMessage = t5, t6;
65
+ $[14] === node ? t6 = $[15] : (t6 = (type_1, data_0, fetchOptions) => node.fetch(type_1, data_0, fetchOptions ?? {}), $[14] = node, $[15] = t6);
66
+ let fetch = t6, t7;
67
+ return $[16] !== fetch || $[17] !== sendMessage ? (t7 = {
68
+ sendMessage,
69
+ fetch
70
+ }, $[16] = fetch, $[17] = sendMessage, $[18] = t7) : t7 = $[18], t7;
71
+ }
72
+ function _temp(unsubscribe) {
73
+ return unsubscribe();
74
+ }
75
+ /**
76
+ * @public
77
+ *
78
+ * A React hook that retrieves the dashboard organization ID that is currently selected in the Sanity Dashboard.
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * function DashboardComponent() {
83
+ * const orgId = useOrganizationId()
84
+ *
85
+ * if (!orgId) return null
86
+ *
87
+ * return <div>Organization ID: {String(orgId)}</div>
88
+ * }
89
+ * ```
90
+ *
91
+ * @category Dashboard
92
+ * @returns The dashboard organization ID (string | undefined)
93
+ */
94
+ function useOrganizationId() {
95
+ let $ = c(2), instance = useSanityInstance(), t0;
96
+ $[0] === instance ? t0 = $[1] : (t0 = getDashboardOrganizationId(instance), $[0] = instance, $[1] = t0);
97
+ let { subscribe, getCurrent } = t0;
98
+ return useSyncExternalStore(subscribe, getCurrent);
99
+ }
100
+ /**
101
+ * Hook that fetches studio workspaces and organizes them by projectId:dataset
102
+ * @internal
103
+ *
104
+ * @example
105
+ * ```tsx
106
+ * import {useStudioWorkspacesByProjectIdDataset} from '@sanity/sdk-react'
107
+ * import {Card, Code, Button} from '@sanity/ui'
108
+ * import {Suspense} from 'react'
109
+ *
110
+ * function WorkspacesCard() {
111
+ * const {workspacesByProjectIdAndDataset, error} = useStudioWorkspacesByProjectIdDataset()
112
+ * if (error) {
113
+ * return <div>Error: {error}</div>
114
+ * }
115
+ * return (
116
+ * <Card padding={4} radius={2} shadow={1}>
117
+ * <Code language="json">
118
+ * {JSON.stringify(workspacesByProjectIdAndDataset, null, 2)}
119
+ * </Code>
120
+ * </Card>
121
+ * )
122
+ * }
123
+ *
124
+ * // Wrap the component with Suspense since the hook may suspend
125
+ * function DashboardWorkspaces() {
126
+ * return (
127
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
128
+ * <WorkspacesCard />
129
+ * </Suspense>
130
+ * )
131
+ * }
132
+ * ```
133
+ */
134
+ function useStudioWorkspacesByProjectIdDataset() {
135
+ let $ = c(8), t0;
136
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {}, $[0] = t0) : t0 = $[0];
137
+ let [workspacesByProjectIdAndDataset, setWorkspacesByProjectIdAndDataset] = useState(t0), [error, setError] = useState(null), t1;
138
+ $[1] === Symbol.for("react.memo_cache_sentinel") ? (t1 = {
139
+ name: SDK_NODE_NAME,
140
+ connectTo: SDK_CHANNEL_NAME
141
+ }, $[1] = t1) : t1 = $[1];
142
+ let { fetch } = useWindowConnection(t1), t2, t3;
143
+ $[2] === fetch ? (t2 = $[3], t3 = $[4]) : (t2 = () => {
144
+ if (!fetch) return;
145
+ let fetchWorkspaces = async function fetchWorkspaces(signal) {
146
+ try {
147
+ let data = await fetch("dashboard/v1/context", void 0, { signal }), workspaceMap = {}, noProjectIdAndDataset = [];
148
+ data.context.availableResources.forEach((resource) => {
149
+ if (resource.type !== "studio") return;
150
+ if (!resource.projectId || !resource.dataset) {
151
+ noProjectIdAndDataset.push(resource);
152
+ return;
153
+ }
154
+ let key = `${resource.projectId}:${resource.dataset}`;
155
+ workspaceMap[key] || (workspaceMap[key] = []), workspaceMap[key].push(resource);
156
+ }), noProjectIdAndDataset.length > 0 && (workspaceMap["NO_PROJECT_ID:NO_DATASET"] = noProjectIdAndDataset), setWorkspacesByProjectIdAndDataset(workspaceMap), setError(null);
157
+ } catch (t4) {
158
+ let err = t4;
159
+ if (err instanceof Error) {
160
+ if (err.name === "AbortError") return;
161
+ setError("Failed to fetch workspaces");
162
+ }
163
+ }
164
+ }, controller = new AbortController();
165
+ return fetchWorkspaces(controller.signal), () => {
166
+ controller.abort();
167
+ };
168
+ }, t3 = [fetch], $[2] = fetch, $[3] = t2, $[4] = t3), useEffect(t2, t3);
169
+ let t4;
170
+ return $[5] !== error || $[6] !== workspacesByProjectIdAndDataset ? (t4 = {
171
+ workspacesByProjectIdAndDataset,
172
+ error
173
+ }, $[5] = error, $[6] = workspacesByProjectIdAndDataset, $[7] = t4) : t4 = $[7], t4;
174
+ }
175
+ export { useSanityInstance as a, createStateSourceHook as i, useOrganizationId as n, SanityInstanceContext as o, useWindowConnection as r, useStudioWorkspacesByProjectIdDataset as t };
176
+
177
+ //# sourceMappingURL=useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js","names":["SanityInstance","createContext","SanityInstanceContext","SanityInstance","useContext","SanityInstanceContext","useSanityInstance","instance","Error","SanityConfig","SanityInstance","StateSource","useSyncExternalStore","useSanityInstance","StateSourceFactory","instance","params","TParams","TState","CreateStateSourceHookOptions","getState","shouldSuspend","suspender","Promise","getConfig","createStateSourceHook","options","suspense","undefined","useHook","t0","$","_c","t1","state","subscribe","getCurrent","MessageData","NodeInput","SanityInstance","StateSource","FrameMessage","getNodeState","NodeState","WindowMessage","useCallback","useEffect","useRef","filter","firstValueFrom","useSanityInstance","createStateSourceHook","WindowMessageHandler","event","TFrameMessage","UseWindowConnectionOptions","name","connectTo","onMessage","Record","TMessage","WindowConnection","sendMessage","type","TType","data","Extract","fetch","options","signal","AbortSignal","suppressWarnings","responseTimeout","Promise","TResponse","useNodeState","getState","instance","nodeInput","shouldSuspend","getCurrent","undefined","suspender","observable","pipe","Boolean","useWindowConnection","t0","$","_c","t1","node","t2","Symbol","for","messageUnsubscribers","t3","Object","entries","forEach","t4","handler","messageUnsubscribe","on","current","push","_temp","t5","type_0","post","t6","type_1","data_0","fetchOptions","t7","unsubscribe","getDashboardOrganizationId","useMemo","useSyncExternalStore","useSanityInstance","useOrganizationId","$","_c","instance","t0","subscribe","getCurrent","SDK_CHANNEL_NAME","SDK_NODE_NAME","useEffect","useState","useWindowConnection","DashboardResource","id","name","title","basePath","projectId","dataset","type","userApplicationId","url","WorkspacesByProjectIdDataset","key","StudioWorkspacesResult","workspacesByProjectIdAndDataset","error","useStudioWorkspacesByProjectIdDataset","$","_c","t0","Symbol","for","setWorkspacesByProjectIdAndDataset","setError","t1","connectTo","fetch","t2","t3","fetchWorkspaces","signal","data","undefined","workspaceMap","noProjectIdAndDataset","context","availableResources","forEach","resource","push","const","length","t4","err","Error","controller","AbortController","abort"],"sources":["../src/context/SanityInstanceContext.ts","../src/hooks/context/useSanityInstance.ts","../src/hooks/helpers/createStateSourceHook.tsx","../src/hooks/comlink/useWindowConnection.ts","../src/hooks/dashboard/useOrganizationId.tsx","../src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts"],"sourcesContent":["import {type SanityInstance} from '@sanity/sdk'\nimport {createContext} from 'react'\n\nexport const SanityInstanceContext = createContext<SanityInstance | null>(null)\n","import {type SanityInstance} from '@sanity/sdk'\nimport {useContext} from 'react'\n\nimport {SanityInstanceContext} from '../../context/SanityInstanceContext'\n\n/**\n * Retrieves the current Sanity instance from context\n *\n * @public\n *\n * @category Platform\n * @returns The current Sanity instance\n *\n * @remarks\n * This hook accesses the nearest Sanity instance from the React context.\n * The hook must be used within a component wrapped by a `ResourceProvider` or `SanityApp`.\n *\n * @example Get the current instance\n * ```tsx\n * const instance = useSanityInstance()\n * console.log(instance.config.projectId)\n * ```\n *\n * @throws Error if no SanityInstance is found in context\n */\nexport const useSanityInstance = (): SanityInstance => {\n const instance = useContext(SanityInstanceContext)\n\n if (!instance) {\n throw new Error(\n `SanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.`,\n )\n }\n\n return instance\n}\n","import {type SanityConfig, type SanityInstance, type StateSource} from '@sanity/sdk'\nimport {useSyncExternalStore} from 'react'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\ntype StateSourceFactory<TParams extends unknown[], TState> = (\n instance: SanityInstance,\n ...params: TParams\n) => StateSource<TState>\n\ninterface CreateStateSourceHookOptions<TParams extends unknown[], TState> {\n getState: StateSourceFactory<TParams, TState>\n shouldSuspend?: (instance: SanityInstance, ...params: TParams) => boolean\n suspender?: (instance: SanityInstance, ...params: TParams) => Promise<unknown>\n getConfig?: (...params: TParams) => SanityConfig | undefined\n}\n\nexport function createStateSourceHook<TParams extends unknown[], TState>(\n options: StateSourceFactory<TParams, TState> | CreateStateSourceHookOptions<TParams, TState>,\n): (...params: TParams) => TState {\n const getState = typeof options === 'function' ? options : options.getState\n const suspense = 'shouldSuspend' in options && 'suspender' in options ? options : undefined\n\n function useHook(...params: TParams) {\n const instance = useSanityInstance()\n\n if (suspense?.suspender && suspense?.shouldSuspend?.(instance, ...params)) {\n throw suspense.suspender(instance, ...params)\n }\n\n const state = getState(instance, ...params)\n return useSyncExternalStore(state.subscribe, state.getCurrent)\n }\n\n return useHook\n}\n","import {type MessageData, type NodeInput} from '@sanity/comlink'\nimport {type SanityInstance, type StateSource} from '@sanity/sdk'\nimport {\n type FrameMessage,\n getNodeState,\n type NodeState,\n type WindowMessage,\n} from '@sanity/sdk/comlink'\nimport {useCallback, useEffect, useRef} from 'react'\nimport {filter, firstValueFrom} from 'rxjs'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\nimport {createStateSourceHook} from '../helpers/createStateSourceHook'\n\n/**\n * @internal\n */\nexport type WindowMessageHandler<TFrameMessage extends FrameMessage> = (\n event: TFrameMessage['data'],\n) => TFrameMessage['response']\n\n/**\n * @internal\n */\nexport interface UseWindowConnectionOptions<TMessage extends FrameMessage> {\n name: string\n connectTo: string\n onMessage?: Record<TMessage['type'], WindowMessageHandler<TMessage>>\n}\n\n/**\n * @internal\n */\nexport interface WindowConnection<TMessage extends WindowMessage> {\n sendMessage: <TType extends TMessage['type']>(\n type: TType,\n data?: Extract<TMessage, {type: TType}>['data'],\n ) => void\n fetch: <TResponse>(\n type: string,\n data?: MessageData,\n options?: {\n signal?: AbortSignal\n suppressWarnings?: boolean\n responseTimeout?: number\n },\n ) => Promise<TResponse>\n}\n\nconst useNodeState = createStateSourceHook({\n getState: getNodeState as (\n instance: SanityInstance,\n nodeInput: NodeInput,\n ) => StateSource<NodeState>,\n shouldSuspend: (instance: SanityInstance, nodeInput: NodeInput) =>\n getNodeState(instance, nodeInput).getCurrent() === undefined,\n suspender: (instance: SanityInstance, nodeInput: NodeInput) => {\n return firstValueFrom(getNodeState(instance, nodeInput).observable.pipe(filter(Boolean)))\n },\n})\n\n/**\n * @internal\n * Hook to wrap a Comlink node in a React hook.\n * Our store functionality takes care of the lifecycle of the node,\n * as well as sharing a single node between invocations if they share the same name.\n *\n * Generally not to be used directly, but to be used as a dependency of\n * Comlink-powered hooks like `useStudioWorkspacesByProjectIdDataset`.\n */\nexport function useWindowConnection<\n TWindowMessage extends WindowMessage,\n TFrameMessage extends FrameMessage,\n>({\n name,\n connectTo,\n onMessage,\n}: UseWindowConnectionOptions<TFrameMessage>): WindowConnection<TWindowMessage> {\n const {node} = useNodeState({name, connectTo})\n const messageUnsubscribers = useRef<(() => void)[]>([])\n const instance = useSanityInstance()\n\n useEffect(() => {\n if (onMessage) {\n Object.entries(onMessage).forEach(([type, handler]) => {\n const messageUnsubscribe = node.on(type, handler as WindowMessageHandler<TFrameMessage>)\n if (messageUnsubscribe) {\n messageUnsubscribers.current.push(messageUnsubscribe)\n }\n })\n }\n\n return () => {\n messageUnsubscribers.current.forEach((unsubscribe) => unsubscribe())\n messageUnsubscribers.current = []\n }\n }, [instance, name, onMessage, node])\n\n const sendMessage = useCallback(\n (type: TWindowMessage['type'], data?: Extract<TWindowMessage, {type: typeof type}>['data']) => {\n node.post(type, data)\n },\n [node],\n )\n\n const fetch = useCallback(\n <TResponse>(\n type: string,\n data?: MessageData,\n fetchOptions?: {\n responseTimeout?: number\n signal?: AbortSignal\n suppressWarnings?: boolean\n },\n ): Promise<TResponse> => {\n return node.fetch(type, data, fetchOptions ?? {}) as Promise<TResponse>\n },\n [node],\n )\n return {\n sendMessage,\n fetch,\n }\n}\n","import {getDashboardOrganizationId} from '@sanity/sdk'\nimport {useMemo, useSyncExternalStore} from 'react'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\n/**\n * @public\n *\n * A React hook that retrieves the dashboard organization ID that is currently selected in the Sanity Dashboard.\n *\n * @example\n * ```tsx\n * function DashboardComponent() {\n * const orgId = useOrganizationId()\n *\n * if (!orgId) return null\n *\n * return <div>Organization ID: {String(orgId)}</div>\n * }\n * ```\n *\n * @category Dashboard\n * @returns The dashboard organization ID (string | undefined)\n */\nexport function useOrganizationId(): string | undefined {\n const instance = useSanityInstance()\n const {subscribe, getCurrent} = useMemo(() => getDashboardOrganizationId(instance), [instance])\n\n return useSyncExternalStore(subscribe, getCurrent)\n}\n","import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\nimport {useEffect, useState} from 'react'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\n\nexport interface DashboardResource {\n id: string\n name: string\n title: string\n basePath: string\n projectId: string\n dataset: string\n type: string\n userApplicationId: string\n url: string\n}\n\ninterface WorkspacesByProjectIdDataset {\n [key: `${string}:${string}`]: DashboardResource[] // key format: `${projectId}:${dataset}`\n}\n\ninterface StudioWorkspacesResult {\n workspacesByProjectIdAndDataset: WorkspacesByProjectIdDataset\n error: string | null\n}\n\n/**\n * Hook that fetches studio workspaces and organizes them by projectId:dataset\n * @internal\n *\n * @example\n * ```tsx\n * import {useStudioWorkspacesByProjectIdDataset} from '@sanity/sdk-react'\n * import {Card, Code, Button} from '@sanity/ui'\n * import {Suspense} from 'react'\n *\n * function WorkspacesCard() {\n * const {workspacesByProjectIdAndDataset, error} = useStudioWorkspacesByProjectIdDataset()\n * if (error) {\n * return <div>Error: {error}</div>\n * }\n * return (\n * <Card padding={4} radius={2} shadow={1}>\n * <Code language=\"json\">\n * {JSON.stringify(workspacesByProjectIdAndDataset, null, 2)}\n * </Code>\n * </Card>\n * )\n * }\n *\n * // Wrap the component with Suspense since the hook may suspend\n * function DashboardWorkspaces() {\n * return (\n * <Suspense fallback={<Button text=\"Loading...\" disabled />}>\n * <WorkspacesCard />\n * </Suspense>\n * )\n * }\n * ```\n */\nexport function useStudioWorkspacesByProjectIdDataset(): StudioWorkspacesResult {\n const [workspacesByProjectIdAndDataset, setWorkspacesByProjectIdAndDataset] =\n useState<WorkspacesByProjectIdDataset>({})\n const [error, setError] = useState<string | null>(null)\n\n const {fetch} = useWindowConnection({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n })\n\n // Once computed, this should probably be in a store and poll for changes\n // However, our stores are currently being refactored\n useEffect(() => {\n if (!fetch) return\n\n async function fetchWorkspaces(signal: AbortSignal) {\n try {\n const data = await fetch<{\n context: {availableResources: Array<DashboardResource>}\n }>('dashboard/v1/context', undefined, {signal})\n\n const workspaceMap: WorkspacesByProjectIdDataset = {}\n const noProjectIdAndDataset: DashboardResource[] = []\n\n data.context.availableResources.forEach((resource) => {\n if (resource.type !== 'studio') return\n if (!resource.projectId || !resource.dataset) {\n noProjectIdAndDataset.push(resource)\n return\n }\n const key = `${resource.projectId}:${resource.dataset}` as const\n if (!workspaceMap[key]) {\n workspaceMap[key] = []\n }\n workspaceMap[key].push(resource)\n })\n\n if (noProjectIdAndDataset.length > 0) {\n workspaceMap['NO_PROJECT_ID:NO_DATASET'] = noProjectIdAndDataset\n }\n\n setWorkspacesByProjectIdAndDataset(workspaceMap)\n setError(null)\n } catch (err: unknown) {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return\n }\n setError('Failed to fetch workspaces')\n }\n }\n }\n\n const controller = new AbortController()\n fetchWorkspaces(controller.signal)\n\n return () => {\n controller.abort()\n }\n }, [fetch])\n\n return {\n workspacesByProjectIdAndDataset,\n error,\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/sdk-react",
3
- "version": "3.0.0-rc.2",
3
+ "version": "3.0.0",
4
4
  "private": false,
5
5
  "description": "Sanity SDK React toolkit for Content OS",
6
6
  "keywords": [
@@ -34,7 +34,8 @@
34
34
  "types": "./dist/index.d.ts",
35
35
  "exports": {
36
36
  ".": "./dist/index.js",
37
- "./package.json": "./package.json"
37
+ "./package.json": "./package.json",
38
+ "./dashboard": "./dist/_exports/dashboard.js"
38
39
  },
39
40
  "publishConfig": {
40
41
  "access": "public"
@@ -49,7 +50,7 @@
49
50
  "react-error-boundary": "^6.1.2",
50
51
  "rxjs": "^7.8.2",
51
52
  "xstate": "^5.31.0",
52
- "@sanity/sdk": "3.0.0-rc.2"
53
+ "@sanity/sdk": "3.0.0"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@sanity/browserslist-config": "^1.0.5",
@@ -73,15 +74,15 @@
73
74
  "typescript": "^6.0.3",
74
75
  "vite": "^8.1.5",
75
76
  "vitest": "^4.1.10",
76
- "@repo/package.bundle": "3.82.0",
77
77
  "@repo/config-eslint": "0.0.0",
78
- "@repo/config-test": "0.0.1",
79
78
  "@repo/package.config": "0.0.1",
80
- "@repo/tsconfig": "0.0.1"
79
+ "@repo/package.bundle": "3.82.0",
80
+ "@repo/tsconfig": "0.0.1",
81
+ "@repo/config-test": "0.0.1"
81
82
  },
82
83
  "peerDependencies": {
83
- "react": "^18.0.0 || ^19.0.0",
84
- "react-dom": "^18.0.0 || ^19.0.0"
84
+ "react": "^19.2.0",
85
+ "react-dom": "^19.2.0"
85
86
  },
86
87
  "browserslist": "extends @sanity/browserslist-config",
87
88
  "inlinedDependencies": {
@@ -0,0 +1,11 @@
1
+ export {
2
+ type AgentResourceContextOptions,
3
+ useAgentResourceContext,
4
+ } from '../hooks/dashboard/useAgentResourceContext'
5
+ export {useNavigate} from '../hooks/dashboard/useNavigate'
6
+ export {
7
+ type NavigateToStudioResult,
8
+ useNavigateToStudioDocument,
9
+ } from '../hooks/dashboard/useNavigateToStudioDocument'
10
+ export {useOrganizationId} from '../hooks/dashboard/useOrganizationId'
11
+ export {useWindowTitle} from '../hooks/dashboard/useWindowTitle'
@@ -21,10 +21,6 @@ export {
21
21
  useAgentTransform,
22
22
  useAgentTranslate,
23
23
  } from '../hooks/agent/agentActions'
24
- export {
25
- type AgentResourceContextOptions,
26
- useAgentResourceContext,
27
- } from '../hooks/agent/useAgentResourceContext'
28
24
  export {useApplication} from '../hooks/applications/useApplication'
29
25
  export {useApplications} from '../hooks/applications/useApplications'
30
26
  export {useDeleteApplication} from '../hooks/applications/useDeleteApplication'
@@ -32,7 +28,6 @@ export {useUpdateApplication} from '../hooks/applications/useUpdateApplication'
32
28
  export {useAuthState} from '../hooks/auth/useAuthState'
33
29
  export {useAuthToken} from '../hooks/auth/useAuthToken'
34
30
  export {useCurrentUser} from '../hooks/auth/useCurrentUser'
35
- export {useDashboardOrganizationId} from '../hooks/auth/useDashboardOrganizationId'
36
31
  export {useHandleAuthCallback} from '../hooks/auth/useHandleAuthCallback'
37
32
  export {useLoginUrl} from '../hooks/auth/useLoginUrl'
38
33
  export {useLogOut} from '../hooks/auth/useLogOut'
@@ -55,15 +50,10 @@ export {useComments, type UseCommentsResult} from '../hooks/comments/useComments
55
50
  export {useCommentThreads, type UseCommentThreadsResult} from '../hooks/comments/useCommentThreads'
56
51
  export {useResource} from '../hooks/context/useResource'
57
52
  export {useSanityInstance} from '../hooks/context/useSanityInstance'
58
- export {useDashboardNavigate} from '../hooks/dashboard/useDashboardNavigate'
59
- export {useManageFavorite} from '../hooks/dashboard/useManageFavorite'
60
- export {
61
- type NavigateToStudioResult,
62
- useNavigateToStudioDocument,
63
- } from '../hooks/dashboard/useNavigateToStudioDocument'
53
+ export {useFavorite} from '../hooks/dashboard/useFavorite'
64
54
  export {useRecordDocumentHistoryEvent} from '../hooks/dashboard/useRecordDocumentHistoryEvent'
65
55
  export {useStudioWorkspacesByProjectIdDataset} from '../hooks/dashboard/useStudioWorkspacesByProjectIdDataset'
66
- export {useWindowTitle} from '../hooks/dashboard/useWindowTitle'
56
+ export {useUpdateFavorite} from '../hooks/dashboard/useUpdateFavorite'
67
57
  export {useDatasets} from '../hooks/datasets/useDatasets'
68
58
  export {useApplyDocumentActions} from '../hooks/document/useApplyDocumentActions'
69
59
  export {type CreateDocumentOverrides, useCreateDocument} from '../hooks/document/useCreateDocument'
@@ -3,7 +3,7 @@ import {render} from '@testing-library/react'
3
3
  import React from 'react'
4
4
  import {beforeEach, describe, expect, it, vi} from 'vitest'
5
5
 
6
- import {useDashboardOrganizationId} from '../hooks/auth/useDashboardOrganizationId'
6
+ import {useOrganizationId} from '../hooks/dashboard/useOrganizationId'
7
7
  import {resolveOrgResources} from '../utils/resolveOrgResources'
8
8
  import {SDKProvider} from './SDKProvider'
9
9
 
@@ -14,8 +14,8 @@ vi.mock('../hooks/context/useSanityInstance', () => {
14
14
  return {useSanityInstance: () => instance}
15
15
  })
16
16
 
17
- vi.mock('../hooks/auth/useDashboardOrganizationId', () => ({
18
- useDashboardOrganizationId: vi.fn(),
17
+ vi.mock('../hooks/dashboard/useOrganizationId', () => ({
18
+ useOrganizationId: vi.fn(),
19
19
  }))
20
20
 
21
21
  vi.mock('../utils/resolveOrgResources', () => ({
@@ -53,13 +53,13 @@ vi.mock('./auth/AuthBoundary', () => ({
53
53
  }))
54
54
 
55
55
  const mockResolveOrgResources = vi.mocked(resolveOrgResources)
56
- const mockUseDashboardOrganizationId = vi.mocked(useDashboardOrganizationId)
56
+ const mockUseOrganizationId = vi.mocked(useOrganizationId)
57
57
 
58
58
  describe('SDKProvider', () => {
59
59
  beforeEach(() => {
60
60
  vi.clearAllMocks()
61
61
  mockResolveOrgResources.mockResolvedValue({})
62
- mockUseDashboardOrganizationId.mockReturnValue(undefined)
62
+ mockUseOrganizationId.mockReturnValue(undefined)
63
63
  })
64
64
 
65
65
  it('renders single ResourceProvider with AuthBoundary for a single config', () => {
@@ -5,8 +5,8 @@ import {useEffect, useMemo} from 'react'
5
5
  import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
6
6
 
7
7
  import {ComlinkTokenRefreshProvider} from '../../context/ComlinkTokenRefresh'
8
- import {isWorkbenchEnvironment} from '../../context/workbenchToken'
9
- import {WorkbenchTokenRefreshProvider} from '../../context/WorkbenchTokenRefresh'
8
+ import {isDashboardEnvironment} from '../../context/dashboardToken'
9
+ import {DashboardTokenRefreshProvider} from '../../context/DashboardTokenRefresh'
10
10
  import {useAuthState} from '../../hooks/auth/useAuthState'
11
11
  import {useLoginUrl} from '../../hooks/auth/useLoginUrl'
12
12
  import {useVerifyOrgProjects} from '../../hooks/auth/useVerifyOrgProjects'
@@ -144,11 +144,11 @@ export function AuthBoundary({
144
144
 
145
145
  return (
146
146
  <ComlinkTokenRefreshProvider>
147
- <WorkbenchTokenRefreshProvider>
147
+ <DashboardTokenRefreshProvider>
148
148
  <ErrorBoundary FallbackComponent={FallbackComponent} resetKeys={[sessionResetKey]}>
149
149
  <AuthSwitch {...props} />
150
150
  </ErrorBoundary>
151
- </WorkbenchTokenRefreshProvider>
151
+ </DashboardTokenRefreshProvider>
152
152
  </ComlinkTokenRefreshProvider>
153
153
  )
154
154
  }
@@ -187,7 +187,7 @@ function AuthSwitch({
187
187
  const loginUrl = useLoginUrl()
188
188
 
189
189
  useEffect(() => {
190
- if (isLoggedOut && !isInIframe() && !isStudio && !isWorkbenchEnvironment()) {
190
+ if (isLoggedOut && !isInIframe() && !isStudio && !isDashboardEnvironment()) {
191
191
  // We don't want to redirect to login if we're in the Dashboard, in studio
192
192
  // mode, or in the workbench (the OS owns the session and mints the token)
193
193
  window.location.href = loginUrl
@@ -4,13 +4,13 @@ import {of} from 'rxjs'
4
4
  import {afterEach, beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
5
5
 
6
6
  import {useAuthState} from '../hooks/auth/useAuthState'
7
- import {ResourceProvider} from './ResourceProvider'
8
7
  import {
9
- isWorkbenchEnvironment,
10
- observeWorkbenchToken,
11
- refreshWorkbenchToken,
12
- } from './workbenchToken'
13
- import {WorkbenchTokenRefreshProvider} from './WorkbenchTokenRefresh'
8
+ isDashboardEnvironment,
9
+ observeDashboardToken,
10
+ refreshDashboardToken,
11
+ } from './dashboardToken'
12
+ import {DashboardTokenRefreshProvider} from './DashboardTokenRefresh'
13
+ import {ResourceProvider} from './ResourceProvider'
14
14
 
15
15
  vi.mock('@sanity/sdk', async () => {
16
16
  const actual = await vi.importActual('@sanity/sdk')
@@ -24,28 +24,28 @@ vi.mock('../hooks/auth/useAuthState', () => ({
24
24
  useAuthState: vi.fn(),
25
25
  }))
26
26
 
27
- vi.mock('./workbenchToken', () => ({
28
- isWorkbenchEnvironment: vi.fn(() => false),
29
- observeWorkbenchToken: vi.fn(() => undefined),
30
- refreshWorkbenchToken: vi.fn(),
27
+ vi.mock('./dashboardToken', () => ({
28
+ isDashboardEnvironment: vi.fn(() => false),
29
+ observeDashboardToken: vi.fn(() => undefined),
30
+ refreshDashboardToken: vi.fn(),
31
31
  }))
32
32
 
33
33
  const mockSetAuthToken = setAuthToken as Mock
34
34
  const mockUseAuthState = useAuthState as Mock
35
- const mockIsWorkbenchEnvironment = isWorkbenchEnvironment as Mock
36
- const mockObserveWorkbenchToken = observeWorkbenchToken as Mock
37
- const mockRefreshWorkbenchToken = refreshWorkbenchToken as Mock
35
+ const mockIsDashboardEnvironment = isDashboardEnvironment as Mock
36
+ const mockObserveDashboardToken = observeDashboardToken as Mock
37
+ const mockRefreshDashboardToken = refreshDashboardToken as Mock
38
38
 
39
39
  const renderProvider = () =>
40
40
  render(
41
41
  <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
42
- <WorkbenchTokenRefreshProvider>
42
+ <DashboardTokenRefreshProvider>
43
43
  <div>Test</div>
44
- </WorkbenchTokenRefreshProvider>
44
+ </DashboardTokenRefreshProvider>
45
45
  </ResourceProvider>,
46
46
  )
47
47
 
48
- describe('WorkbenchTokenRefreshProvider', () => {
48
+ describe('DashboardTokenRefreshProvider', () => {
49
49
  beforeEach(() => {
50
50
  mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN})
51
51
  })
@@ -54,9 +54,9 @@ describe('WorkbenchTokenRefreshProvider', () => {
54
54
  vi.clearAllMocks()
55
55
  })
56
56
 
57
- describe('when not in the workbench', () => {
57
+ describe('when not in the dashboard', () => {
58
58
  it('does not subscribe to the OS token', () => {
59
- mockIsWorkbenchEnvironment.mockReturnValue(false)
59
+ mockIsDashboardEnvironment.mockReturnValue(false)
60
60
 
61
61
  act(() => {
62
62
  renderProvider()
@@ -66,23 +66,23 @@ describe('WorkbenchTokenRefreshProvider', () => {
66
66
  })
67
67
  })
68
68
 
69
- describe('when in the workbench', () => {
69
+ describe('when in the dashboard', () => {
70
70
  beforeEach(() => {
71
- mockIsWorkbenchEnvironment.mockReturnValue(true)
71
+ mockIsDashboardEnvironment.mockReturnValue(true)
72
72
  })
73
73
 
74
74
  it('mirrors the OS token into the auth store', () => {
75
- mockObserveWorkbenchToken.mockReturnValue(of('workbench-token'))
75
+ mockObserveDashboardToken.mockReturnValue(of('dashboard-token'))
76
76
 
77
77
  act(() => {
78
78
  renderProvider()
79
79
  })
80
80
 
81
- expect(mockSetAuthToken).toHaveBeenCalledWith(expect.anything(), 'workbench-token')
81
+ expect(mockSetAuthToken).toHaveBeenCalledWith(expect.anything(), 'dashboard-token')
82
82
  })
83
83
 
84
84
  it('asks the OS to reissue the token on a 401', () => {
85
- mockObserveWorkbenchToken.mockReturnValue(of('workbench-token'))
85
+ mockObserveDashboardToken.mockReturnValue(of('dashboard-token'))
86
86
 
87
87
  const {rerender} = renderProvider()
88
88
 
@@ -93,14 +93,14 @@ describe('WorkbenchTokenRefreshProvider', () => {
93
93
  act(() => {
94
94
  rerender(
95
95
  <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
96
- <WorkbenchTokenRefreshProvider>
96
+ <DashboardTokenRefreshProvider>
97
97
  <div>Test</div>
98
- </WorkbenchTokenRefreshProvider>
98
+ </DashboardTokenRefreshProvider>
99
99
  </ResourceProvider>,
100
100
  )
101
101
  })
102
102
 
103
- expect(mockRefreshWorkbenchToken).toHaveBeenCalledTimes(1)
103
+ expect(mockRefreshDashboardToken).toHaveBeenCalledTimes(1)
104
104
  })
105
105
  })
106
106
  })
@@ -5,28 +5,28 @@ import React, {type PropsWithChildren, useEffect, useRef} from 'react'
5
5
  import {useAuthState} from '../hooks/auth/useAuthState'
6
6
  import {useSanityInstance} from '../hooks/context/useSanityInstance'
7
7
  import {
8
- isWorkbenchEnvironment,
9
- observeWorkbenchToken,
10
- refreshWorkbenchToken,
11
- } from './workbenchToken'
8
+ isDashboardEnvironment,
9
+ observeDashboardToken,
10
+ refreshDashboardToken,
11
+ } from './dashboardToken'
12
12
 
13
13
  /**
14
- * Keeps the SDK auth token in sync with the workbench "OS".
14
+ * Keeps the SDK auth token in sync with the dashboard "OS".
15
15
  *
16
- * When running as a federated remote inside the workbench the OS owns the
16
+ * When running as a federated remote inside the dashboard the OS owns the
17
17
  * session, so we subscribe to its `auth.token` stream and mirror each value into
18
18
  * the auth store — a token logs us in, `null` logs us out, and later OS
19
19
  * sign-in/out propagates automatically. When a request is rejected with a 401
20
20
  * (the token expired), we ask the OS to reissue rather than tearing the session
21
21
  * down; the new token arrives back through the same subscription.
22
22
  */
23
- function WorkbenchTokenRefresh({children}: PropsWithChildren) {
23
+ function DashboardTokenRefresh({children}: PropsWithChildren) {
24
24
  const instance = useSanityInstance()
25
25
  const authState = useAuthState()
26
26
  const processed401ErrorRef = useRef<unknown | null>(null)
27
27
 
28
28
  useEffect(() => {
29
- const token$ = observeWorkbenchToken()
29
+ const token$ = observeDashboardToken()
30
30
  if (!token$) return undefined
31
31
  const subscription = token$.subscribe((token) => setAuthToken(instance, token))
32
32
  return () => subscription.unsubscribe()
@@ -38,7 +38,7 @@ function WorkbenchTokenRefresh({children}: PropsWithChildren) {
38
38
 
39
39
  if (has401Error && processed401ErrorRef.current !== authState.error) {
40
40
  processed401ErrorRef.current = authState.error
41
- refreshWorkbenchToken()
41
+ refreshDashboardToken()
42
42
  } else if (!has401Error) {
43
43
  processed401ErrorRef.current = null
44
44
  }
@@ -48,13 +48,13 @@ function WorkbenchTokenRefresh({children}: PropsWithChildren) {
48
48
  }
49
49
 
50
50
  /**
51
- * Provides workbench OS token refresh. No-op outside the workbench, where the
51
+ * Provides dashboard OS token refresh. No-op outside the dashboard, where the
52
52
  * app uses its normal auth flow.
53
- * @public
53
+ * @internal
54
54
  */
55
- export const WorkbenchTokenRefreshProvider: React.FC<PropsWithChildren> = ({children}) => {
56
- if (isWorkbenchEnvironment()) {
57
- return <WorkbenchTokenRefresh>{children}</WorkbenchTokenRefresh>
55
+ export const DashboardTokenRefreshProvider: React.FC<PropsWithChildren> = ({children}) => {
56
+ if (isDashboardEnvironment()) {
57
+ return <DashboardTokenRefresh>{children}</DashboardTokenRefresh>
58
58
  }
59
59
 
60
60
  return children
@@ -3,7 +3,7 @@ import {act, render} from '@testing-library/react'
3
3
  import {type ReactNode, Suspense, useContext, useEffect} from 'react'
4
4
  import {beforeEach, describe, expect, it, vi} from 'vitest'
5
5
 
6
- import {useDashboardOrganizationId} from '../hooks/auth/useDashboardOrganizationId'
6
+ import {useOrganizationId} from '../hooks/dashboard/useOrganizationId'
7
7
  import {resolveOrgResources} from '../utils/resolveOrgResources'
8
8
  import {OrganizationResourcesProvider} from './OrganizationResourcesProvider'
9
9
  import {ResourcesContext} from './ResourcesContext'
@@ -28,8 +28,8 @@ vi.mock('../hooks/context/useSanityInstance', () => {
28
28
  return {useSanityInstance: () => instance}
29
29
  })
30
30
 
31
- vi.mock('../hooks/auth/useDashboardOrganizationId', () => ({
32
- useDashboardOrganizationId: vi.fn(),
31
+ vi.mock('../hooks/dashboard/useOrganizationId', () => ({
32
+ useOrganizationId: vi.fn(),
33
33
  }))
34
34
 
35
35
  vi.mock('../utils/resolveOrgResources', () => ({
@@ -37,14 +37,14 @@ vi.mock('../utils/resolveOrgResources', () => ({
37
37
  }))
38
38
 
39
39
  const mockResolveOrgResources = vi.mocked(resolveOrgResources)
40
- const mockUseDashboardOrganizationId = vi.mocked(useDashboardOrganizationId)
40
+ const mockUseOrganizationId = vi.mocked(useOrganizationId)
41
41
 
42
42
  describe('OrganizationResourcesProvider', () => {
43
43
  beforeEach(() => {
44
44
  vi.clearAllMocks()
45
45
  instance = createSanityInstance()
46
46
  mockResolveOrgResources.mockResolvedValue({})
47
- mockUseDashboardOrganizationId.mockReturnValue(undefined)
47
+ mockUseOrganizationId.mockReturnValue(undefined)
48
48
  })
49
49
 
50
50
  // Captures ResourcesContext via useEffect (side-effect territory, not render)
@@ -91,7 +91,7 @@ describe('OrganizationResourcesProvider', () => {
91
91
  })
92
92
 
93
93
  it('suspends children while org resources are being fetched, then renders with resolved resources', async () => {
94
- mockUseDashboardOrganizationId.mockReturnValue('org-suspend-test')
94
+ mockUseOrganizationId.mockReturnValue('org-suspend-test')
95
95
  const {promise, resolve} = promiseWithResolvers<void>()
96
96
  mockResolveOrgResources.mockReturnValue(
97
97
  promise.then(() => ({
@@ -115,7 +115,7 @@ describe('OrganizationResourcesProvider', () => {
115
115
  })
116
116
 
117
117
  it('renders without inferred entries and logs a warning when resolveOrgResources rejects', async () => {
118
- mockUseDashboardOrganizationId.mockReturnValue('org-warn-test')
118
+ mockUseOrganizationId.mockReturnValue('org-warn-test')
119
119
  const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
120
120
  mockResolveOrgResources.mockRejectedValue(new Error('fetch failed'))
121
121
 
@@ -135,7 +135,7 @@ describe('OrganizationResourcesProvider', () => {
135
135
  })
136
136
 
137
137
  it('caches Promise references across providers for the same instance', () => {
138
- mockUseDashboardOrganizationId.mockReturnValue('org-cache-test')
138
+ mockUseOrganizationId.mockReturnValue('org-cache-test')
139
139
  render(
140
140
  <Suspense fallback={null}>
141
141
  <OrganizationResourcesProvider inferMediaLibraryAndCanvas>
@@ -168,7 +168,7 @@ describe('OrganizationResourcesProvider', () => {
168
168
  })
169
169
 
170
170
  it('explicit resources take precedence over inferred ones', async () => {
171
- mockUseDashboardOrganizationId.mockReturnValue('org-override-test')
171
+ mockUseOrganizationId.mockReturnValue('org-override-test')
172
172
  mockResolveOrgResources.mockResolvedValue({
173
173
  mediaLibrary: {mediaLibraryId: 'inferred-ml'},
174
174
  canvas: {canvasId: 'inferred-canvas'},