@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.
- package/dist/_exports/dashboard.d.ts +209 -0
- package/dist/_exports/dashboard.d.ts.map +1 -0
- package/dist/_exports/dashboard.js +278 -0
- package/dist/_exports/dashboard.js.map +1 -0
- package/dist/index.d.ts +2186 -2774
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3953 -1951
- package/dist/index.js.map +1 -1
- package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js +177 -0
- package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map +1 -0
- package/package.json +19 -19
- package/src/_exports/dashboard.ts +11 -0
- package/src/_exports/sdk-react.ts +11 -18
- package/src/components/SDKProvider.test.tsx +5 -5
- package/src/components/SanityApp.tsx +2 -3
- package/src/components/auth/AuthBoundary.test.tsx +1 -19
- package/src/components/auth/AuthBoundary.tsx +7 -6
- package/src/components/auth/LoginError.tsx +2 -3
- package/src/context/ComlinkTokenRefresh.tsx +3 -5
- package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
- package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
- package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
- package/src/context/OrganizationResourcesProvider.tsx +2 -2
- package/src/context/ResourceProvider.test.tsx +21 -0
- package/src/context/ResourceProvider.tsx +4 -1
- package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +1 -1
- package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
- package/src/hooks/comlink/useFrameConnection.ts +1 -1
- package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
- package/src/hooks/comlink/useWindowConnection.ts +3 -4
- package/src/hooks/comments/useCommentActions.test.tsx +150 -0
- package/src/hooks/comments/useCommentActions.ts +109 -0
- package/src/hooks/comments/useCommentList.ts +79 -0
- package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
- package/src/hooks/comments/useCommentThreads.ts +73 -0
- package/src/hooks/comments/useComments.test.tsx +242 -0
- package/src/hooks/comments/useComments.ts +59 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -54
- package/src/hooks/context/useSanityInstance.ts +2 -24
- package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +2 -2
- package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
- package/src/hooks/dashboard/useFavorite.ts +34 -0
- package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
- package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
- package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
- package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
- package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
- package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
- package/src/hooks/dashboard/useWindowTitle.ts +1 -1
- package/src/hooks/datasets/useDatasets.test.tsx +29 -22
- package/src/hooks/datasets/useDatasets.ts +31 -53
- package/src/hooks/documents/useDocuments.ts +2 -7
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +0 -27
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +14 -25
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +2 -7
- package/src/hooks/presence/usePresence.ts +14 -1
- package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
- package/src/hooks/presence/usePresenceForDocument.ts +104 -0
- package/src/hooks/presence/useReportPresence.test.tsx +202 -0
- package/src/hooks/presence/useReportPresence.ts +170 -0
- package/src/hooks/preview/useDocumentPreview.tsx +2 -6
- package/src/hooks/projects/useProjects.ts +0 -8
- package/src/hooks/query/useQuery.ts +3 -8
- package/src/hooks/releases/useActiveReleases.ts +2 -2
- package/src/hooks/releases/useAllReleases.ts +2 -2
- package/src/hooks/users/useUser.ts +2 -8
- package/src/hooks/users/useUsers.ts +1 -2
- package/src/hooks/applications/useCreateUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useCreateUserApplication.ts +0 -11
- package/src/hooks/applications/useDeleteUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useDeleteUserApplication.ts +0 -11
- package/src/hooks/applications/useUpdateUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useUpdateUserApplication.ts +0 -11
- package/src/hooks/applications/useUserApplication.test-d.ts +0 -11
- package/src/hooks/applications/useUserApplication.ts +0 -14
- package/src/hooks/applications/useUserApplications.test-d.ts +0 -11
- package/src/hooks/applications/useUserApplications.ts +0 -14
- package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
- package/src/hooks/dashboard/useManageFavorite.ts +0 -173
- /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
|
-
}
|
|
File without changes
|