@sanity/sdk-react 3.0.0-rc.1 → 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 (85) 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 +2186 -2774
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3953 -1951
  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 +19 -19
  12. package/src/_exports/dashboard.ts +11 -0
  13. package/src/_exports/sdk-react.ts +11 -18
  14. package/src/components/SDKProvider.test.tsx +5 -5
  15. package/src/components/SanityApp.tsx +2 -3
  16. package/src/components/auth/AuthBoundary.test.tsx +1 -19
  17. package/src/components/auth/AuthBoundary.tsx +7 -6
  18. package/src/components/auth/LoginError.tsx +2 -3
  19. package/src/context/ComlinkTokenRefresh.tsx +3 -5
  20. package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
  21. package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
  22. package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
  23. package/src/context/OrganizationResourcesProvider.tsx +2 -2
  24. package/src/context/ResourceProvider.test.tsx +21 -0
  25. package/src/context/ResourceProvider.tsx +4 -1
  26. package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
  27. package/src/hooks/agent/agentActions.test.tsx +1 -1
  28. package/src/hooks/agent/agentActions.ts +1 -1
  29. package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
  30. package/src/hooks/comlink/useFrameConnection.ts +1 -1
  31. package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
  32. package/src/hooks/comlink/useWindowConnection.ts +3 -4
  33. package/src/hooks/comments/useCommentActions.test.tsx +150 -0
  34. package/src/hooks/comments/useCommentActions.ts +109 -0
  35. package/src/hooks/comments/useCommentList.ts +79 -0
  36. package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
  37. package/src/hooks/comments/useCommentThreads.ts +73 -0
  38. package/src/hooks/comments/useComments.test.tsx +242 -0
  39. package/src/hooks/comments/useComments.ts +59 -0
  40. package/src/hooks/context/useSanityInstance.test.tsx +2 -54
  41. package/src/hooks/context/useSanityInstance.ts +2 -24
  42. package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +2 -2
  43. package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
  44. package/src/hooks/dashboard/useFavorite.ts +34 -0
  45. package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
  46. package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
  47. package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
  48. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
  49. package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
  50. package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
  51. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
  52. package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
  53. package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
  54. package/src/hooks/dashboard/useWindowTitle.ts +1 -1
  55. package/src/hooks/datasets/useDatasets.test.tsx +29 -22
  56. package/src/hooks/datasets/useDatasets.ts +31 -53
  57. package/src/hooks/documents/useDocuments.ts +2 -7
  58. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +0 -27
  59. package/src/hooks/helpers/useNormalizedResourceOptions.ts +14 -25
  60. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +2 -7
  61. package/src/hooks/presence/usePresence.ts +14 -1
  62. package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
  63. package/src/hooks/presence/usePresenceForDocument.ts +104 -0
  64. package/src/hooks/presence/useReportPresence.test.tsx +202 -0
  65. package/src/hooks/presence/useReportPresence.ts +170 -0
  66. package/src/hooks/preview/useDocumentPreview.tsx +2 -6
  67. package/src/hooks/projects/useProjects.ts +0 -8
  68. package/src/hooks/query/useQuery.ts +3 -8
  69. package/src/hooks/releases/useActiveReleases.ts +2 -2
  70. package/src/hooks/releases/useAllReleases.ts +2 -2
  71. package/src/hooks/users/useUser.ts +2 -8
  72. package/src/hooks/users/useUsers.ts +1 -2
  73. package/src/hooks/applications/useCreateUserApplication.test-d.ts +0 -14
  74. package/src/hooks/applications/useCreateUserApplication.ts +0 -11
  75. package/src/hooks/applications/useDeleteUserApplication.test-d.ts +0 -14
  76. package/src/hooks/applications/useDeleteUserApplication.ts +0 -11
  77. package/src/hooks/applications/useUpdateUserApplication.test-d.ts +0 -14
  78. package/src/hooks/applications/useUpdateUserApplication.ts +0 -11
  79. package/src/hooks/applications/useUserApplication.test-d.ts +0 -11
  80. package/src/hooks/applications/useUserApplication.ts +0 -14
  81. package/src/hooks/applications/useUserApplications.test-d.ts +0 -11
  82. package/src/hooks/applications/useUserApplications.ts +0 -14
  83. package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
  84. package/src/hooks/dashboard/useManageFavorite.ts +0 -173
  85. /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
@@ -1,379 +0,0 @@
1
- import {type Message} from '@sanity/comlink'
2
- import {type FavoriteStatusResponse, getFavoritesState, resolveFavoritesState} from '@sanity/sdk'
3
- import {act, renderHook} from '@testing-library/react'
4
- import {BehaviorSubject} from 'rxjs'
5
- import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
6
-
7
- import {ResourceProvider} from '../../context/ResourceProvider'
8
- import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection'
9
- import {useManageFavorite} from './useManageFavorite'
10
-
11
- type MockFetch = (type: string, data?: unknown, options?: unknown) => Promise<{success: boolean}>
12
- type MockSendMessage = (type: string, data?: unknown) => void
13
-
14
- vi.mock(import('@sanity/sdk'), async (importOriginal) => {
15
- const actual = await importOriginal()
16
- return {
17
- ...actual,
18
- getFavoritesState: vi.fn(),
19
- resolveFavoritesState: vi.fn(),
20
- }
21
- })
22
-
23
- vi.mock('../comlink/useWindowConnection', () => ({
24
- useWindowConnection: vi.fn(),
25
- }))
26
-
27
- describe('useManageFavorite', () => {
28
- let favoriteStatusSubject: BehaviorSubject<FavoriteStatusResponse>
29
- let mockFetch: Mock<MockFetch>
30
- let mockSendMessage: Mock<MockSendMessage>
31
-
32
- const mockDocumentHandle = {
33
- documentId: 'mock-id',
34
- documentType: 'mock-type',
35
- resourceType: 'studio' as const,
36
- }
37
-
38
- beforeEach(() => {
39
- favoriteStatusSubject = new BehaviorSubject<FavoriteStatusResponse>({isFavorited: false})
40
-
41
- // Mock getFavoritesState
42
- vi.mocked(getFavoritesState).mockImplementation(() => ({
43
- subscribe: (callback?: () => void) => {
44
- if (!callback) return () => {}
45
-
46
- const subscription = favoriteStatusSubject.subscribe(() => callback())
47
- callback() // Initial call
48
- return () => subscription.unsubscribe()
49
- },
50
- getCurrent: () => favoriteStatusSubject.getValue(),
51
- observable: favoriteStatusSubject.asObservable(),
52
- }))
53
-
54
- // Mock resolveFavoritesState
55
- vi.mocked(resolveFavoritesState).mockImplementation(async () => {
56
- const newValue = {isFavorited: !favoriteStatusSubject.getValue().isFavorited}
57
- favoriteStatusSubject.next(newValue)
58
- return newValue
59
- })
60
-
61
- // Mock useWindowConnection
62
- mockFetch = vi.fn<MockFetch>().mockResolvedValue({success: true})
63
- mockSendMessage = vi.fn<MockSendMessage>()
64
- vi.mocked(useWindowConnection).mockImplementation(
65
- () =>
66
- ({
67
- fetch: (type: string, data?: unknown, options: unknown = {}) =>
68
- mockFetch(type, data, options),
69
- sendMessage: mockSendMessage,
70
- }) as unknown as WindowConnection<Message>,
71
- )
72
- })
73
-
74
- afterEach(() => {
75
- favoriteStatusSubject.complete()
76
- vi.clearAllMocks()
77
- })
78
-
79
- it('should initialize with default states', () => {
80
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
81
- wrapper: ({children}) => (
82
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
83
- {children}
84
- </ResourceProvider>
85
- ),
86
- })
87
-
88
- expect(result.current.isFavorited).toBe(false)
89
- })
90
-
91
- it('should handle favorite action and update state', async () => {
92
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
93
- wrapper: ({children}) => (
94
- <ResourceProvider projectId="test" dataset="test" fallback={null}>
95
- {children}
96
- </ResourceProvider>
97
- ),
98
- })
99
-
100
- expect(result.current.isFavorited).toBe(false)
101
-
102
- await act(async () => {
103
- await result.current.favorite()
104
- })
105
-
106
- expect(mockFetch).toHaveBeenCalledWith(
107
- 'dashboard/v1/events/favorite/mutate',
108
- {
109
- document: {
110
- id: 'mock-id',
111
- type: 'mock-type',
112
- resource: {
113
- id: 'test.test',
114
- type: 'studio',
115
- },
116
- },
117
- eventType: 'added',
118
- },
119
- // empty options object (from useWindowConnection)
120
- {},
121
- )
122
- expect(resolveFavoritesState).toHaveBeenCalled()
123
- expect(result.current.isFavorited).toBe(true)
124
- })
125
-
126
- it('should handle unfavorite action and update state', async () => {
127
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
128
- wrapper: ({children}) => (
129
- <ResourceProvider projectId="test" dataset="test" fallback={null}>
130
- {children}
131
- </ResourceProvider>
132
- ),
133
- })
134
-
135
- // Set initial state to favorited
136
- await act(async () => {
137
- favoriteStatusSubject.next({isFavorited: true})
138
- })
139
-
140
- expect(result.current.isFavorited).toBe(true)
141
-
142
- await act(async () => {
143
- await result.current.unfavorite()
144
- })
145
-
146
- expect(mockFetch).toHaveBeenCalledWith(
147
- 'dashboard/v1/events/favorite/mutate',
148
- {
149
- document: {
150
- id: 'mock-id',
151
- type: 'mock-type',
152
- resource: {
153
- id: 'test.test',
154
- type: 'studio',
155
- },
156
- },
157
- eventType: 'removed',
158
- },
159
- {},
160
- )
161
- expect(resolveFavoritesState).toHaveBeenCalled()
162
- expect(result.current.isFavorited).toBe(false)
163
- })
164
-
165
- it('should not update state if favorite action fails', async () => {
166
- mockFetch.mockResolvedValueOnce({success: false})
167
-
168
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
169
- wrapper: ({children}) => (
170
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
171
- {children}
172
- </ResourceProvider>
173
- ),
174
- })
175
-
176
- expect(result.current.isFavorited).toBe(false)
177
-
178
- await act(async () => {
179
- await result.current.favorite()
180
- })
181
-
182
- expect(resolveFavoritesState).not.toHaveBeenCalled()
183
- expect(result.current.isFavorited).toBe(false)
184
- })
185
-
186
- it('should throw error during favorite/unfavorite actions', async () => {
187
- const errorMessage = 'Failed to update favorite status'
188
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
189
-
190
- mockFetch.mockImplementation(() => {
191
- throw new Error(errorMessage)
192
- })
193
-
194
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
195
- wrapper: ({children}) => (
196
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
197
- {children}
198
- </ResourceProvider>
199
- ),
200
- })
201
-
202
- await act(async () => {
203
- await expect(result.current.favorite()).rejects.toThrow(errorMessage)
204
- })
205
-
206
- expect(resolveFavoritesState).not.toHaveBeenCalled()
207
- expect(result.current.isFavorited).toBe(false)
208
-
209
- await act(async () => {
210
- await expect(result.current.unfavorite()).rejects.toThrow(errorMessage)
211
- })
212
-
213
- expect(resolveFavoritesState).not.toHaveBeenCalled()
214
- consoleErrorSpy.mockRestore()
215
- })
216
-
217
- it('should throw error when studio resource is missing projectId or dataset', () => {
218
- const mockDocumentHandleWithoutProjectId = {
219
- documentId: 'mock-id',
220
- documentType: 'mock-type',
221
- resourceType: 'studio' as const,
222
- }
223
-
224
- expect(() =>
225
- renderHook(() => useManageFavorite(mockDocumentHandleWithoutProjectId), {
226
- wrapper: ({children}) => (
227
- <ResourceProvider projectId={undefined} dataset={undefined} fallback={null}>
228
- {children}
229
- </ResourceProvider>
230
- ),
231
- }),
232
- ).toThrow('projectId and dataset are required for studio resources')
233
- })
234
-
235
- it('should throw error when resourceId is missing for non-studio resources', () => {
236
- const mockMediaDocumentHandle = {
237
- documentId: 'mock-id',
238
- documentType: 'mock-type',
239
- resourceType: 'media-library' as const,
240
- resourceId: undefined,
241
- }
242
-
243
- expect(() =>
244
- renderHook(() => useManageFavorite(mockMediaDocumentHandle), {
245
- wrapper: ({children}) => (
246
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
247
- {children}
248
- </ResourceProvider>
249
- ),
250
- }),
251
- ).toThrow('resourceId is required for media-library and canvas resources')
252
- })
253
-
254
- it('should include schemaName in payload when provided', async () => {
255
- const mockDocumentHandleWithSchema = {
256
- ...mockDocumentHandle,
257
- schemaName: 'testSchema',
258
- }
259
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema), {
260
- wrapper: ({children}) => (
261
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
262
- {children}
263
- </ResourceProvider>
264
- ),
265
- })
266
-
267
- await act(async () => {
268
- await result.current.favorite()
269
- })
270
-
271
- expect(mockFetch).toHaveBeenCalledWith(
272
- 'dashboard/v1/events/favorite/mutate',
273
- {
274
- document: {
275
- id: 'mock-id',
276
- type: 'mock-type',
277
- resource: {
278
- id: 'test-project.test-dataset',
279
- type: 'studio',
280
- schemaName: 'testSchema',
281
- },
282
- },
283
- eventType: 'added',
284
- },
285
- {},
286
- )
287
- })
288
-
289
- it('should default isFavorited to false if state is undefined', () => {
290
- // Mock getFavoritesState to return undefined for getCurrent
291
- vi.mocked(getFavoritesState).mockImplementation(() => ({
292
- subscribe: (callback?: () => void) => {
293
- if (!callback) return () => {}
294
- callback()
295
- return () => {}
296
- },
297
- getCurrent: () => undefined,
298
- observable: favoriteStatusSubject.asObservable(),
299
- }))
300
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
301
- wrapper: ({children}) => (
302
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
303
- {children}
304
- </ResourceProvider>
305
- ),
306
- })
307
- expect(result.current.isFavorited).toBe(false)
308
- })
309
-
310
- it('should do nothing if fetch is missing', async () => {
311
- vi.mocked(useWindowConnection).mockReturnValue({
312
- fetch: undefined,
313
- sendMessage: mockSendMessage,
314
- } as unknown as WindowConnection<Message>)
315
- const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
316
- wrapper: ({children}) => (
317
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
318
- {children}
319
- </ResourceProvider>
320
- ),
321
- })
322
- await act(async () => {
323
- await result.current.favorite()
324
- await result.current.unfavorite()
325
- })
326
- expect(mockFetch).not.toHaveBeenCalled()
327
- })
328
-
329
- it('should do nothing if documentId is missing', async () => {
330
- const handle = {...mockDocumentHandle, documentId: undefined}
331
- // @ts-expect-error -- no access to ManageFavorite props type
332
- const {result} = renderHook(() => useManageFavorite(handle), {
333
- wrapper: ({children}) => (
334
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
335
- {children}
336
- </ResourceProvider>
337
- ),
338
- })
339
- await act(async () => {
340
- await result.current.favorite()
341
- await result.current.unfavorite()
342
- })
343
- expect(mockFetch).not.toHaveBeenCalled()
344
- })
345
-
346
- it('should do nothing if documentType is missing', async () => {
347
- const handle = {...mockDocumentHandle, documentType: undefined}
348
- // @ts-expect-error -- no access to ManageFavorite props type
349
- const {result} = renderHook(() => useManageFavorite(handle), {
350
- wrapper: ({children}) => (
351
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
352
- {children}
353
- </ResourceProvider>
354
- ),
355
- })
356
- await act(async () => {
357
- await result.current.favorite()
358
- await result.current.unfavorite()
359
- })
360
- expect(mockFetch).not.toHaveBeenCalled()
361
- })
362
-
363
- it('should do nothing if resourceType is missing', async () => {
364
- const handle = {...mockDocumentHandle, resourceType: undefined, resourceId: 'studio'}
365
- // @ts-expect-error -- no access to ManageFavorite props type
366
- const {result} = renderHook(() => useManageFavorite(handle), {
367
- wrapper: ({children}) => (
368
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
369
- {children}
370
- </ResourceProvider>
371
- ),
372
- })
373
- await act(async () => {
374
- await result.current.favorite()
375
- await result.current.unfavorite()
376
- })
377
- expect(mockFetch).not.toHaveBeenCalled()
378
- })
379
- })
@@ -1,173 +0,0 @@
1
- import {
2
- type CanvasResource,
3
- type Events,
4
- type MediaResource,
5
- SDK_CHANNEL_NAME,
6
- SDK_NODE_NAME,
7
- type StudioResource,
8
- } from '@sanity/message-protocol'
9
- import {
10
- type DocumentHandle,
11
- type FavoriteStatusResponse,
12
- type FrameMessage,
13
- getFavoritesState,
14
- resolveFavoritesState,
15
- } from '@sanity/sdk'
16
- import {useCallback, useMemo, useSyncExternalStore} from 'react'
17
-
18
- import {useWindowConnection} from '../comlink/useWindowConnection'
19
- import {useSanityInstance} from '../context/useSanityInstance'
20
-
21
- interface ManageFavorite extends FavoriteStatusResponse {
22
- favorite: () => Promise<void>
23
- unfavorite: () => Promise<void>
24
- isFavorited: boolean
25
- }
26
-
27
- interface UseManageFavoriteProps extends DocumentHandle {
28
- resourceId?: string
29
- resourceType: StudioResource['type'] | MediaResource['type'] | CanvasResource['type']
30
- /**
31
- * The name of the schema collection this document belongs to.
32
- * Typically is the name of the workspace when used in the context of a studio.
33
- */
34
- schemaName?: string
35
- }
36
-
37
- /**
38
- * @internal
39
- *
40
- * This hook provides functionality to add and remove documents from favorites,
41
- * and tracks the current favorite status of the document.
42
- * @param documentHandle - The document handle containing document ID and type, like `{_id: '123', _type: 'book'}`
43
- * @returns An object containing:
44
- * - `favorite` - Function to add document to favorites
45
- * - `unfavorite` - Function to remove document from favorites
46
- * - `isFavorited` - Boolean indicating if document is currently favorited
47
- *
48
- * @example
49
- * ```tsx
50
- * function FavoriteButton(props: DocumentActionProps) {
51
- * const {documentId, documentType} = props
52
- * const {favorite, unfavorite, isFavorited} = useManageFavorite({
53
- * documentId,
54
- * documentType
55
- * })
56
- *
57
- * return (
58
- * <Button
59
- * onClick={() => isFavorited ? unfavorite() : favorite()}
60
- * text={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
61
- * />
62
- * )
63
- * }
64
- *
65
- * // Wrap the component with Suspense since the hook may suspend
66
- * function MyDocumentAction(props: DocumentActionProps) {
67
- * return (
68
- * <Suspense
69
- * fallback={
70
- * <Button
71
- * text="Loading..."
72
- * disabled
73
- * />
74
- * }
75
- * >
76
- * <FavoriteButton {...props} />
77
- * </Suspense>
78
- * )
79
- * }
80
- * ```
81
- */
82
- export function useManageFavorite({
83
- documentId,
84
- documentType,
85
- projectId: paramProjectId,
86
- dataset: paramDataset,
87
- resourceId: paramResourceId,
88
- resourceType,
89
- schemaName,
90
- }: UseManageFavoriteProps): ManageFavorite {
91
- const {fetch} = useWindowConnection<Events.FavoriteMessage, FrameMessage>({
92
- name: SDK_NODE_NAME,
93
- connectTo: SDK_CHANNEL_NAME,
94
- })
95
- const instance = useSanityInstance()
96
- const {config} = instance
97
- const instanceProjectId = config?.projectId
98
- const instanceDataset = config?.dataset
99
- const projectId = paramProjectId ?? instanceProjectId
100
- const dataset = paramDataset ?? instanceDataset
101
-
102
- if (resourceType === 'studio' && (!projectId || !dataset)) {
103
- throw new Error('projectId and dataset are required for studio resources')
104
- }
105
- // Compute the final resourceId
106
- const resourceId =
107
- resourceType === 'studio' && !paramResourceId ? `${projectId}.${dataset}` : paramResourceId
108
-
109
- if (!resourceId) {
110
- throw new Error('resourceId is required for media-library and canvas resources')
111
- }
112
-
113
- // used for favoriteStore functions like getFavoritesState and resolveFavoritesState
114
- const context = useMemo(
115
- () => ({
116
- documentId,
117
- documentType,
118
- resourceId,
119
- resourceType,
120
- schemaName,
121
- }),
122
- [documentId, documentType, resourceId, resourceType, schemaName],
123
- )
124
-
125
- // Get favorite status using StateSource
126
- const favoriteState = getFavoritesState(instance, context)
127
- const state = useSyncExternalStore(favoriteState.subscribe, favoriteState.getCurrent)
128
-
129
- const isFavorited = state?.isFavorited ?? false
130
-
131
- const handleFavoriteAction = useCallback(
132
- async (action: 'added' | 'removed') => {
133
- if (!fetch || !documentId || !documentType || !resourceType) return
134
-
135
- try {
136
- const payload = {
137
- eventType: action,
138
- document: {
139
- id: documentId,
140
- type: documentType,
141
- resource: {
142
- ...{
143
- id: resourceId,
144
- type: resourceType,
145
- },
146
- ...(schemaName ? {schemaName} : {}),
147
- },
148
- },
149
- }
150
-
151
- const res = await fetch<{success: boolean}>('dashboard/v1/events/favorite/mutate', payload)
152
- if (res.success) {
153
- // Force a re-fetch of the favorite status after successful mutation
154
- await resolveFavoritesState(instance, context)
155
- }
156
- } catch (err) {
157
- // eslint-disable-next-line no-console
158
- console.error(`Failed to ${action === 'added' ? 'favorite' : 'unfavorite'} document:`, err)
159
- throw err
160
- }
161
- },
162
- [fetch, documentId, documentType, resourceId, resourceType, schemaName, instance, context],
163
- )
164
-
165
- const favorite = useCallback(() => handleFavoriteAction('added'), [handleFavoriteAction])
166
- const unfavorite = useCallback(() => handleFavoriteAction('removed'), [handleFavoriteAction])
167
-
168
- return {
169
- favorite,
170
- unfavorite,
171
- isFavorited,
172
- }
173
- }