@sanity/sdk-react 2.8.0 → 3.0.0-rc.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/README.md +125 -63
- package/dist/index.d.ts +381 -571
- package/dist/index.js +435 -366
- package/dist/index.js.map +1 -1
- package/package.json +7 -9
- package/src/_exports/index.ts +4 -0
- package/src/_exports/sdk-react.ts +16 -0
- package/src/components/SDKProvider.test.tsx +23 -58
- package/src/components/SDKProvider.tsx +38 -30
- package/src/components/SanityApp.test.tsx +12 -68
- package/src/components/SanityApp.tsx +88 -65
- package/src/components/auth/AuthBoundary.test.tsx +8 -26
- package/src/components/auth/LoginError.tsx +5 -5
- package/src/config/handles.ts +53 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +99 -19
- package/src/context/ResourceProvider.tsx +103 -37
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SDKStudioContext.test.tsx +33 -28
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/context/renderSanityApp.test.tsx +49 -151
- package/src/context/renderSanityApp.tsx +8 -12
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +56 -19
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
- package/src/hooks/client/useClient.test.tsx +4 -1
- package/src/hooks/client/useClient.ts +0 -1
- package/src/hooks/context/useDefaultResource.test.tsx +25 -0
- package/src/hooks/context/useDefaultResource.ts +30 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -140
- package/src/hooks/context/useSanityInstance.ts +9 -53
- package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
- package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
- package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
- package/src/hooks/dashboard/useManageFavorite.ts +16 -10
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
- package/src/hooks/datasets/useDatasets.test.ts +8 -22
- package/src/hooks/datasets/useDatasets.ts +8 -16
- package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
- package/src/hooks/document/useApplyDocumentActions.ts +35 -37
- package/src/hooks/document/useDocument.test.tsx +8 -37
- package/src/hooks/document/useDocument.ts +78 -129
- package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
- package/src/hooks/document/useDocumentEvent.ts +21 -19
- package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
- package/src/hooks/document/useDocumentPermissions.ts +41 -28
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
- package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
- package/src/hooks/document/useEditDocument.test.tsx +28 -70
- package/src/hooks/document/useEditDocument.ts +29 -149
- package/src/hooks/documents/useDocuments.test.tsx +44 -64
- package/src/hooks/documents/useDocuments.ts +19 -25
- package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
- package/src/hooks/presence/usePresence.test.tsx +71 -9
- package/src/hooks/presence/usePresence.ts +28 -3
- package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
- package/src/hooks/preview/useDocumentPreview.tsx +42 -62
- package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
- package/src/hooks/projection/useDocumentProjection.ts +9 -82
- package/src/hooks/projects/useProject.test.ts +1 -2
- package/src/hooks/projects/useProject.ts +7 -8
- package/src/hooks/query/useQuery.test.tsx +5 -6
- package/src/hooks/query/useQuery.ts +12 -91
- package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
- package/src/hooks/releases/useActiveReleases.ts +25 -13
- package/src/hooks/releases/usePerspective.test.tsx +9 -17
- package/src/hooks/releases/usePerspective.ts +29 -18
- package/src/hooks/users/useUser.test.tsx +9 -3
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.test.tsx +5 -2
- package/src/hooks/users/useUsers.ts +1 -1
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_RESOURCE_NAME,
|
|
3
|
+
type DocumentResource,
|
|
4
|
+
isStudioConfig,
|
|
5
|
+
type SanityConfig,
|
|
6
|
+
} from '@sanity/sdk'
|
|
2
7
|
import {type ReactElement, useContext, useEffect, useMemo} from 'react'
|
|
3
8
|
|
|
4
9
|
import {SDKStudioContext, type StudioWorkspaceHandle} from '../context/SDKStudioContext'
|
|
@@ -11,15 +16,17 @@ import {isInIframe, isLocalUrl} from './utils'
|
|
|
11
16
|
*/
|
|
12
17
|
export interface SanityAppProps {
|
|
13
18
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Optional when `SanityApp` is rendered inside an `SDKStudioContext`
|
|
16
|
-
* (e.g. inside Sanity Studio) — the config is derived from
|
|
17
|
-
* automatically.
|
|
19
|
+
* Core configuration for the SDK instance (auth, studio, perspective).
|
|
20
|
+
* Optional when `SanityApp` is rendered inside an `SDKStudioContext`
|
|
21
|
+
* provider (e.g. inside Sanity Studio) — the config is derived from
|
|
22
|
+
* the workspace automatically.
|
|
18
23
|
*/
|
|
19
|
-
config?: SanityConfig
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
config?: SanityConfig
|
|
25
|
+
/**
|
|
26
|
+
* Named document resources for the application. The resource keyed `"default"`
|
|
27
|
+
* is used automatically when no explicit resource is specified in hooks.
|
|
28
|
+
*/
|
|
29
|
+
resources?: Record<string, DocumentResource>
|
|
23
30
|
children: React.ReactNode
|
|
24
31
|
/* Fallback content to show when child components are suspending. Same as the `fallback` prop for React Suspense. */
|
|
25
32
|
fallback: React.ReactNode
|
|
@@ -28,16 +35,22 @@ export interface SanityAppProps {
|
|
|
28
35
|
const REDIRECT_URL = 'https://sanity.io/welcome'
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
|
-
* Derive a SanityConfig from a Studio workspace handle.
|
|
32
|
-
* Maps the workspace's projectId, dataset, and reactive auth token into
|
|
33
|
-
* the SDK's config shape.
|
|
38
|
+
* Derive a SanityConfig and resources map from a Studio workspace handle.
|
|
34
39
|
*/
|
|
35
|
-
function
|
|
40
|
+
function deriveFromWorkspace(workspace: StudioWorkspaceHandle): {
|
|
41
|
+
config: SanityConfig
|
|
42
|
+
resources: Record<string, DocumentResource>
|
|
43
|
+
} {
|
|
36
44
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
config: {
|
|
46
|
+
studio: {
|
|
47
|
+
authenticated: workspace.authenticated,
|
|
48
|
+
auth: workspace.auth.token ? {token: workspace.auth.token} : undefined,
|
|
49
|
+
projectId: workspace.projectId,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
resources: {
|
|
53
|
+
[DEFAULT_RESOURCE_NAME]: {projectId: workspace.projectId, dataset: workspace.dataset},
|
|
41
54
|
},
|
|
42
55
|
}
|
|
43
56
|
}
|
|
@@ -49,57 +62,49 @@ function deriveConfigFromWorkspace(workspace: StudioWorkspaceHandle): SanityConf
|
|
|
49
62
|
* as well as application context and state which is used by the Sanity React hooks. Your application
|
|
50
63
|
* must be wrapped with the SanityApp component to function properly.
|
|
51
64
|
*
|
|
52
|
-
* The `config` prop
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* When rendered inside a Sanity Studio that provides `SDKStudioContext`, the `config` prop is
|
|
56
|
-
* optional — `SanityApp` will automatically derive `projectId`, `dataset`, and auth from the
|
|
57
|
-
* Studio workspace.
|
|
65
|
+
* The `config` prop accepts a {@link SanityConfig} object. Use the `resources` prop to declare
|
|
66
|
+
* one or more named data resources for your app.
|
|
58
67
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* configuration used by the App SDK Hooks.
|
|
68
|
+
* When rendered inside a Sanity Studio that provides `SDKStudioContext`, the `config` and `resources`
|
|
69
|
+
* props are optional — `SanityApp` will automatically derive them from the Studio workspace.
|
|
62
70
|
*
|
|
63
|
-
* When both `config` and `SDKStudioContext` are available, the explicit
|
|
71
|
+
* When both `config` and `SDKStudioContext` are available, the explicit props take precedence.
|
|
64
72
|
*
|
|
65
73
|
* @category Components
|
|
66
74
|
* @param props - Your Sanity configuration and the React children to render
|
|
67
75
|
* @returns Your Sanity application, integrated with your Sanity configuration and application context
|
|
68
76
|
*
|
|
69
|
-
* @example
|
|
77
|
+
* @example Single project
|
|
70
78
|
* ```tsx
|
|
71
|
-
* import { SanityApp
|
|
79
|
+
* import { SanityApp } from '@sanity/sdk-react'
|
|
72
80
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
81
|
+
* export default function MyApp() {
|
|
82
|
+
* return (
|
|
83
|
+
* <SanityApp
|
|
84
|
+
* resources={{
|
|
85
|
+
* default: { projectId: 'my-project-id', dataset: 'production' },
|
|
86
|
+
* }}
|
|
87
|
+
* fallback={<div>Loading…</div>}
|
|
88
|
+
* >
|
|
89
|
+
* <MyAppRoot />
|
|
90
|
+
* </SanityApp>
|
|
91
|
+
* )
|
|
79
92
|
* }
|
|
93
|
+
* ```
|
|
80
94
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* {
|
|
85
|
-
* projectId: 'marketing-website-project',
|
|
86
|
-
* dataset: 'production',
|
|
87
|
-
* },
|
|
88
|
-
* // Configuration for a separate blog project
|
|
89
|
-
* {
|
|
90
|
-
* projectId: 'blog-project',
|
|
91
|
-
* dataset: 'production',
|
|
92
|
-
* },
|
|
93
|
-
* // Configuration for a separate ecommerce project
|
|
94
|
-
* {
|
|
95
|
-
* projectId: 'ecommerce-project',
|
|
96
|
-
* dataset: 'production',
|
|
97
|
-
* }
|
|
98
|
-
* ]
|
|
95
|
+
* @example Multiple resources
|
|
96
|
+
* ```tsx
|
|
97
|
+
* import { SanityApp } from '@sanity/sdk-react'
|
|
99
98
|
*
|
|
100
99
|
* export default function MyApp() {
|
|
101
100
|
* return (
|
|
102
|
-
* <SanityApp
|
|
101
|
+
* <SanityApp
|
|
102
|
+
* resources={{
|
|
103
|
+
* default: { projectId: 'abc123', dataset: 'production' },
|
|
104
|
+
* 'blog-project': { projectId: 'def456', dataset: 'production' },
|
|
105
|
+
* }}
|
|
106
|
+
* fallback={<div>Loading…</div>}
|
|
107
|
+
* >
|
|
103
108
|
* <MyAppRoot />
|
|
104
109
|
* </SanityApp>
|
|
105
110
|
* )
|
|
@@ -110,29 +115,42 @@ export function SanityApp({
|
|
|
110
115
|
children,
|
|
111
116
|
fallback,
|
|
112
117
|
config: configProp,
|
|
118
|
+
resources: resourcesProp,
|
|
113
119
|
...props
|
|
114
120
|
}: SanityAppProps): ReactElement {
|
|
115
121
|
const studioWorkspace = useContext(SDKStudioContext)
|
|
116
122
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
122
|
-
}, [configProp, studioWorkspace])
|
|
123
|
+
const derived = useMemo(() => {
|
|
124
|
+
if (studioWorkspace && !configProp && !resourcesProp) {
|
|
125
|
+
return deriveFromWorkspace(studioWorkspace)
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}, [configProp, resourcesProp, studioWorkspace])
|
|
129
|
+
|
|
130
|
+
const resolvedConfig = useMemo<SanityConfig>(() => {
|
|
131
|
+
if (configProp) {
|
|
132
|
+
return configProp
|
|
133
|
+
}
|
|
134
|
+
if (derived) return derived.config
|
|
135
|
+
return {}
|
|
136
|
+
}, [configProp, derived])
|
|
137
|
+
|
|
138
|
+
const resolvedResources = useMemo<Record<string, DocumentResource>>(() => {
|
|
139
|
+
if (resourcesProp) return resourcesProp
|
|
140
|
+
if (derived) return derived.resources
|
|
141
|
+
return {}
|
|
142
|
+
}, [resourcesProp, derived])
|
|
123
143
|
|
|
124
144
|
useEffect(() => {
|
|
125
145
|
let timeout: NodeJS.Timeout | undefined
|
|
126
|
-
const primaryConfig = Array.isArray(resolvedConfig) ? resolvedConfig[0] : resolvedConfig
|
|
127
146
|
const shouldRedirectWithoutConfig =
|
|
128
|
-
configProp === undefined && !studioWorkspace && !
|
|
147
|
+
configProp === undefined && !studioWorkspace && !resolvedConfig
|
|
129
148
|
|
|
130
149
|
if (
|
|
131
150
|
!isInIframe() &&
|
|
132
151
|
!isLocalUrl(window) &&
|
|
133
|
-
(shouldRedirectWithoutConfig || (!!
|
|
152
|
+
(shouldRedirectWithoutConfig || (!!resolvedConfig && !isStudioConfig(resolvedConfig)))
|
|
134
153
|
) {
|
|
135
|
-
// If the app is not running in an iframe and is not a local url, redirect to core.
|
|
136
154
|
timeout = setTimeout(() => {
|
|
137
155
|
// eslint-disable-next-line no-console
|
|
138
156
|
console.warn('Redirecting to core', REDIRECT_URL)
|
|
@@ -143,7 +161,12 @@ export function SanityApp({
|
|
|
143
161
|
}, [configProp, resolvedConfig, studioWorkspace])
|
|
144
162
|
|
|
145
163
|
return (
|
|
146
|
-
<SDKProvider
|
|
164
|
+
<SDKProvider
|
|
165
|
+
{...props}
|
|
166
|
+
fallback={fallback}
|
|
167
|
+
config={resolvedConfig}
|
|
168
|
+
resources={resolvedResources}
|
|
169
|
+
>
|
|
147
170
|
{children}
|
|
148
171
|
</SDKProvider>
|
|
149
172
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {AuthStateType
|
|
1
|
+
import {AuthStateType} from '@sanity/sdk'
|
|
2
2
|
import {render, screen, waitFor} from '@testing-library/react'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import {type FallbackProps} from 'react-error-boundary'
|
|
@@ -110,24 +110,6 @@ describe('AuthBoundary', () => {
|
|
|
110
110
|
const mockUseVerifyOrgProjects = vi.mocked(useVerifyOrgProjects)
|
|
111
111
|
const testProjectIds = ['proj-test'] // Example project ID for tests
|
|
112
112
|
|
|
113
|
-
// Mock Sanity instance
|
|
114
|
-
const mockSanityInstance = {
|
|
115
|
-
instanceId: 'test-instance-id',
|
|
116
|
-
config: {
|
|
117
|
-
projectId: 'test-project',
|
|
118
|
-
dataset: 'test-dataset',
|
|
119
|
-
},
|
|
120
|
-
isDisposed: () => false,
|
|
121
|
-
dispose: () => {},
|
|
122
|
-
onDispose: () => () => {},
|
|
123
|
-
getParent: () => undefined,
|
|
124
|
-
createChild: (config: SanityConfig) => ({
|
|
125
|
-
...mockSanityInstance,
|
|
126
|
-
config: {...mockSanityInstance.config, ...config},
|
|
127
|
-
}),
|
|
128
|
-
match: () => undefined,
|
|
129
|
-
}
|
|
130
|
-
|
|
131
113
|
beforeEach(() => {
|
|
132
114
|
vi.clearAllMocks()
|
|
133
115
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
@@ -166,7 +148,7 @@ describe('AuthBoundary', () => {
|
|
|
166
148
|
isExchangingToken: false,
|
|
167
149
|
})
|
|
168
150
|
const {container} = render(
|
|
169
|
-
<ResourceProvider projectId
|
|
151
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
170
152
|
<AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
|
|
171
153
|
</ResourceProvider>,
|
|
172
154
|
)
|
|
@@ -183,7 +165,7 @@ describe('AuthBoundary', () => {
|
|
|
183
165
|
token: 'exampleToken',
|
|
184
166
|
})
|
|
185
167
|
render(
|
|
186
|
-
<ResourceProvider projectId
|
|
168
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
187
169
|
<AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
|
|
188
170
|
</ResourceProvider>,
|
|
189
171
|
)
|
|
@@ -197,7 +179,7 @@ describe('AuthBoundary', () => {
|
|
|
197
179
|
error: new Error('test error'),
|
|
198
180
|
})
|
|
199
181
|
render(
|
|
200
|
-
<ResourceProvider projectId
|
|
182
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
201
183
|
<AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
|
|
202
184
|
</ResourceProvider>,
|
|
203
185
|
)
|
|
@@ -214,7 +196,7 @@ describe('AuthBoundary', () => {
|
|
|
214
196
|
|
|
215
197
|
it('renders children when logged in and org verification passes', () => {
|
|
216
198
|
render(
|
|
217
|
-
<ResourceProvider projectId
|
|
199
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
218
200
|
<AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
|
|
219
201
|
</ResourceProvider>,
|
|
220
202
|
)
|
|
@@ -236,7 +218,7 @@ describe('AuthBoundary', () => {
|
|
|
236
218
|
|
|
237
219
|
// Need to catch the error thrown during render. ErrorBoundary mock handles this.
|
|
238
220
|
render(
|
|
239
|
-
<ResourceProvider projectId
|
|
221
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
240
222
|
<AuthBoundary verifyOrganization={true} projectIds={testProjectIds}>
|
|
241
223
|
<div>Protected Content</div>
|
|
242
224
|
</AuthBoundary>
|
|
@@ -268,7 +250,7 @@ describe('AuthBoundary', () => {
|
|
|
268
250
|
})
|
|
269
251
|
|
|
270
252
|
render(
|
|
271
|
-
<ResourceProvider projectId
|
|
253
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
272
254
|
<AuthBoundary verifyOrganization={false} projectIds={testProjectIds}>
|
|
273
255
|
<div>Protected Content</div>
|
|
274
256
|
</AuthBoundary>
|
|
@@ -293,7 +275,7 @@ describe('AuthBoundary', () => {
|
|
|
293
275
|
mockUseVerifyOrgProjects.mockImplementation(() => null)
|
|
294
276
|
|
|
295
277
|
render(
|
|
296
|
-
<ResourceProvider projectId
|
|
278
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
|
|
297
279
|
<AuthBoundary projectIds={testProjectIds}>
|
|
298
280
|
<div>Protected Content</div>
|
|
299
281
|
</AuthBoundary>
|
|
@@ -4,15 +4,16 @@ import {
|
|
|
4
4
|
AuthStateType,
|
|
5
5
|
getClientErrorApiBody,
|
|
6
6
|
getClientErrorApiDescription,
|
|
7
|
+
isDatasetResource,
|
|
7
8
|
isProjectUserNotFoundClientError,
|
|
8
9
|
} from '@sanity/sdk'
|
|
9
|
-
import {useCallback, useEffect, useState} from 'react'
|
|
10
|
+
import {useCallback, useContext, useEffect, useState} from 'react'
|
|
10
11
|
import {type FallbackProps} from 'react-error-boundary'
|
|
11
12
|
|
|
13
|
+
import {ResourceContext} from '../../context/DefaultResourceContext'
|
|
12
14
|
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
13
15
|
import {useLogOut} from '../../hooks/auth/useLogOut'
|
|
14
16
|
import {useWindowConnection} from '../../hooks/comlink/useWindowConnection'
|
|
15
|
-
import {useSanityInstance} from '../../hooks/context/useSanityInstance'
|
|
16
17
|
import {Error} from '../errors/Error'
|
|
17
18
|
import {AuthError} from './AuthError'
|
|
18
19
|
import {ConfigurationError} from './ConfigurationError'
|
|
@@ -39,9 +40,8 @@ export function LoginError({error, resetErrorBoundary}: LoginErrorProps): React.
|
|
|
39
40
|
|
|
40
41
|
const logout = useLogOut()
|
|
41
42
|
const authState = useAuthState()
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
} = useSanityInstance()
|
|
43
|
+
const resource = useContext(ResourceContext)
|
|
44
|
+
const projectId = resource && isDatasetResource(resource) ? resource.projectId : undefined
|
|
45
45
|
|
|
46
46
|
const [authErrorMessage, setAuthErrorMessage] = useState(
|
|
47
47
|
'Please try again or contact support if the problem persists.',
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {type DocumentResource, type PerspectiveHandle} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
// React-layer types — shadow core equivalents when imported from @sanity/sdk-react.
|
|
4
|
+
// resource is optional here and resolved from context by normalization,
|
|
5
|
+
// whereas core's DocumentHandle/ResourceHandle require resource explicitly.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SDK React ResourceHandle with optional explicit resource field.
|
|
9
|
+
* Resource is resolved from context when not provided.
|
|
10
|
+
* When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
|
|
11
|
+
* if there is a matching resource by that name.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface ResourceHandle<
|
|
15
|
+
TProjectId extends string = string,
|
|
16
|
+
TDataset extends string = string,
|
|
17
|
+
> {
|
|
18
|
+
resource?: DocumentResource<TProjectId, TDataset>
|
|
19
|
+
resourceName?: string
|
|
20
|
+
perspective?: PerspectiveHandle['perspective']
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SDK React DocumentTypeHandle with optional explicit resource field.
|
|
25
|
+
* Resource is resolved from context when not provided.
|
|
26
|
+
* When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
|
|
27
|
+
* if there is a matching resource by that name.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export interface DocumentTypeHandle<
|
|
31
|
+
TDocumentType extends string = string,
|
|
32
|
+
TDataset extends string = string,
|
|
33
|
+
TProjectId extends string = string,
|
|
34
|
+
> extends ResourceHandle<TProjectId, TDataset> {
|
|
35
|
+
documentType: TDocumentType
|
|
36
|
+
documentId?: string
|
|
37
|
+
liveEdit?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* SDK React DocumentHandle with optional explicit resource field.
|
|
42
|
+
* Resource is resolved from context when not provided.
|
|
43
|
+
* When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
|
|
44
|
+
* if there is a matching resource by that name.
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export interface DocumentHandle<
|
|
48
|
+
TDocumentType extends string = string,
|
|
49
|
+
TDataset extends string = string,
|
|
50
|
+
TProjectId extends string = string,
|
|
51
|
+
> extends DocumentTypeHandle<TDocumentType, TDataset, TProjectId> {
|
|
52
|
+
documentId: string
|
|
53
|
+
}
|
|
@@ -56,7 +56,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
56
56
|
it('should not request new token on 401 if not in dashboard', async () => {
|
|
57
57
|
mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN})
|
|
58
58
|
const {rerender} = render(
|
|
59
|
-
<ResourceProvider
|
|
59
|
+
<ResourceProvider
|
|
60
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
61
|
+
fallback={null}
|
|
62
|
+
>
|
|
60
63
|
<ComlinkTokenRefreshProvider>
|
|
61
64
|
<div>Test</div>
|
|
62
65
|
</ComlinkTokenRefreshProvider>
|
|
@@ -69,7 +72,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
69
72
|
})
|
|
70
73
|
act(() => {
|
|
71
74
|
rerender(
|
|
72
|
-
<ResourceProvider
|
|
75
|
+
<ResourceProvider
|
|
76
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
77
|
+
fallback={null}
|
|
78
|
+
>
|
|
73
79
|
<ComlinkTokenRefreshProvider>
|
|
74
80
|
<div>Test</div>
|
|
75
81
|
</ComlinkTokenRefreshProvider>
|
|
@@ -92,7 +98,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
92
98
|
it('should initialize useWindowConnection with correct parameters when not in studio mode', () => {
|
|
93
99
|
// Simulate studio mode disabled by default
|
|
94
100
|
render(
|
|
95
|
-
<ResourceProvider
|
|
101
|
+
<ResourceProvider
|
|
102
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
103
|
+
fallback={null}
|
|
104
|
+
>
|
|
96
105
|
<ComlinkTokenRefreshProvider>
|
|
97
106
|
<div>Test</div>
|
|
98
107
|
</ComlinkTokenRefreshProvider>
|
|
@@ -124,7 +133,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
124
133
|
document.body.appendChild(errorContainer)
|
|
125
134
|
|
|
126
135
|
render(
|
|
127
|
-
<ResourceProvider
|
|
136
|
+
<ResourceProvider
|
|
137
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
138
|
+
fallback={null}
|
|
139
|
+
>
|
|
128
140
|
<ComlinkTokenRefreshProvider>
|
|
129
141
|
<div>Test</div>
|
|
130
142
|
</ComlinkTokenRefreshProvider>
|
|
@@ -138,11 +150,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
138
150
|
expect(mockSetAuthToken).toHaveBeenCalledWith(expect.any(Object), 'new-token')
|
|
139
151
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
140
152
|
expect(mockFetch).toHaveBeenCalledWith('dashboard/v1/auth/tokens/create')
|
|
141
|
-
// Assert setAuthToken was called with
|
|
153
|
+
// Assert setAuthToken was called with a SanityInstance
|
|
142
154
|
const instanceArg = mockSetAuthToken.mock.calls[0][0]
|
|
143
|
-
expect(instanceArg
|
|
144
|
-
|
|
145
|
-
)
|
|
155
|
+
expect(instanceArg).toHaveProperty('instanceId')
|
|
156
|
+
expect(instanceArg).toHaveProperty('config')
|
|
146
157
|
// Unauthorized error container should be removed
|
|
147
158
|
expect(document.getElementById('__sanityError')).toBeNull()
|
|
148
159
|
})
|
|
@@ -155,7 +166,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
155
166
|
mockFetch.mockResolvedValueOnce({token: null})
|
|
156
167
|
|
|
157
168
|
render(
|
|
158
|
-
<ResourceProvider
|
|
169
|
+
<ResourceProvider
|
|
170
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
171
|
+
fallback={null}
|
|
172
|
+
>
|
|
159
173
|
<ComlinkTokenRefreshProvider>
|
|
160
174
|
<div>Test</div>
|
|
161
175
|
</ComlinkTokenRefreshProvider>
|
|
@@ -177,7 +191,10 @@ describe('ComlinkTokenRefresh', () => {
|
|
|
177
191
|
mockFetch.mockRejectedValueOnce(new Error('Fetch failed'))
|
|
178
192
|
|
|
179
193
|
render(
|
|
180
|
-
<ResourceProvider
|
|
194
|
+
<ResourceProvider
|
|
195
|
+
resource={{projectId: 'test-project', dataset: 'test-dataset'}}
|
|
196
|
+
fallback={null}
|
|
197
|
+
>
|
|
181
198
|
<ComlinkTokenRefreshProvider>
|
|
182
199
|
<div>Test</div>
|
|
183
200
|
</ComlinkTokenRefreshProvider>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {type DocumentResource} from '@sanity/sdk'
|
|
2
|
+
import {createContext} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides the active resource for a subtree.
|
|
6
|
+
* Set by `ResourceProvider` so hooks resolve the correct project/dataset
|
|
7
|
+
* without requiring an explicit `resource` option.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export const ResourceContext = createContext<DocumentResource | undefined>(undefined)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {type PerspectiveHandle} from '@sanity/sdk'
|
|
2
|
+
import {createContext} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides a perspective override for nested subtrees.
|
|
6
|
+
* Set by `ResourceProvider` so hooks resolve the correct perspective
|
|
7
|
+
* without requiring an explicit `perspective` option.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export const PerspectiveContext = createContext<PerspectiveHandle['perspective'] | undefined>(
|
|
11
|
+
undefined,
|
|
12
|
+
)
|