@sanity/sdk-react 0.0.0-alpha.2 → 0.0.0-alpha.21
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 +38 -67
- package/dist/index.d.ts +4811 -2
- package/dist/index.js +1069 -2
- package/dist/index.js.map +1 -1
- package/package.json +27 -58
- package/src/_exports/index.ts +66 -10
- package/src/components/Login/LoginLinks.test.tsx +4 -14
- package/src/components/Login/LoginLinks.tsx +16 -31
- package/src/components/SDKProvider.test.tsx +79 -0
- package/src/components/SDKProvider.tsx +42 -0
- package/src/components/SanityApp.test.tsx +156 -0
- package/src/components/SanityApp.tsx +90 -0
- package/src/components/auth/AuthBoundary.test.tsx +6 -19
- package/src/components/auth/AuthBoundary.tsx +20 -4
- package/src/components/auth/Login.test.tsx +2 -16
- package/src/components/auth/Login.tsx +11 -30
- package/src/components/auth/LoginCallback.test.tsx +5 -20
- package/src/components/auth/LoginCallback.tsx +9 -14
- package/src/components/auth/LoginError.test.tsx +2 -17
- package/src/components/auth/LoginError.tsx +11 -16
- package/src/components/auth/LoginFooter.test.tsx +2 -16
- package/src/components/auth/LoginFooter.tsx +8 -24
- package/src/components/auth/LoginLayout.test.tsx +2 -16
- package/src/components/auth/LoginLayout.tsx +8 -38
- package/src/components/auth/authTestHelpers.tsx +11 -0
- package/src/components/utils.ts +22 -0
- package/src/context/SanityInstanceContext.ts +4 -0
- package/src/{components/context → context}/SanityProvider.test.tsx +2 -2
- package/src/context/SanityProvider.tsx +50 -0
- package/src/hooks/_synchronous-groq-js.mjs +4 -0
- package/src/hooks/auth/useAuthState.tsx +4 -5
- package/src/hooks/auth/useAuthToken.tsx +1 -1
- package/src/hooks/auth/useCurrentUser.tsx +28 -4
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +42 -0
- package/src/hooks/auth/useDashboardOrganizationId.tsx +29 -0
- package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
- package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +7 -6
- package/src/hooks/auth/useLogOut.test.tsx +2 -2
- package/src/hooks/auth/useLogOut.tsx +1 -1
- package/src/hooks/auth/useLoginUrls.tsx +1 -0
- package/src/hooks/client/useClient.ts +9 -30
- package/src/hooks/comlink/useFrameConnection.test.tsx +167 -0
- package/src/hooks/comlink/useFrameConnection.ts +107 -0
- package/src/hooks/comlink/useManageFavorite.test.ts +111 -0
- package/src/hooks/comlink/useManageFavorite.ts +130 -0
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +81 -0
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +106 -0
- package/src/hooks/comlink/useWindowConnection.test.ts +135 -0
- package/src/hooks/comlink/useWindowConnection.ts +122 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -2
- package/src/hooks/context/useSanityInstance.ts +24 -8
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +178 -0
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +123 -0
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +278 -0
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +92 -0
- package/src/hooks/datasets/useDatasets.ts +40 -0
- package/src/hooks/document/useApplyDocumentActions.test.ts +25 -0
- package/src/hooks/document/useApplyDocumentActions.ts +75 -0
- package/src/hooks/document/useDocument.test.ts +81 -0
- package/src/hooks/document/useDocument.ts +107 -0
- package/src/hooks/document/useDocumentEvent.test.ts +63 -0
- package/src/hooks/document/useDocumentEvent.ts +54 -0
- package/src/hooks/document/useDocumentPermissions.ts +84 -0
- package/src/hooks/document/useDocumentSyncStatus.test.ts +16 -0
- package/src/hooks/document/useDocumentSyncStatus.ts +33 -0
- package/src/hooks/document/useEditDocument.test.ts +179 -0
- package/src/hooks/document/useEditDocument.ts +195 -0
- package/src/hooks/documents/useDocuments.test.tsx +152 -0
- package/src/hooks/documents/useDocuments.ts +174 -0
- package/src/hooks/helpers/createCallbackHook.tsx +3 -2
- package/src/hooks/helpers/createStateSourceHook.test.tsx +66 -0
- package/src/hooks/helpers/createStateSourceHook.tsx +29 -10
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +259 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +290 -0
- package/src/hooks/preview/usePreview.test.tsx +19 -10
- package/src/hooks/preview/usePreview.tsx +67 -13
- package/src/hooks/projection/useProjection.test.tsx +218 -0
- package/src/hooks/projection/useProjection.ts +147 -0
- package/src/hooks/projects/useProject.ts +48 -0
- package/src/hooks/projects/useProjects.ts +45 -0
- package/src/hooks/query/useQuery.test.tsx +188 -0
- package/src/hooks/query/useQuery.ts +103 -0
- package/src/hooks/users/useUsers.test.ts +163 -0
- package/src/hooks/users/useUsers.ts +107 -0
- package/src/utils/getEnv.ts +21 -0
- package/src/version.ts +8 -0
- package/src/vite-env.d.ts +10 -0
- package/dist/_chunks-es/useLogOut.js +0 -44
- package/dist/_chunks-es/useLogOut.js.map +0 -1
- package/dist/assets/bundle-CcAyERuZ.css +0 -11
- package/dist/components.d.ts +0 -257
- package/dist/components.js +0 -316
- package/dist/components.js.map +0 -1
- package/dist/hooks.d.ts +0 -187
- package/dist/hooks.js +0 -81
- package/dist/hooks.js.map +0 -1
- package/src/_exports/components.ts +0 -13
- package/src/_exports/hooks.ts +0 -9
- package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +0 -113
- package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +0 -42
- package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +0 -21
- package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +0 -105
- package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +0 -42
- package/src/components/DocumentListLayout/DocumentListLayout.tsx +0 -12
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +0 -49
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +0 -39
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +0 -30
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +0 -171
- package/src/components/context/SanityProvider.tsx +0 -42
- package/src/css/css.config.js +0 -220
- package/src/css/paramour.css +0 -2347
- package/src/css/styles.css +0 -11
- package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
- package/src/hooks/client/useClient.test.tsx +0 -130
- package/src/hooks/documentCollection/useDocuments.test.ts +0 -130
- package/src/hooks/documentCollection/useDocuments.ts +0 -87
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import {AuthStateType, createSanityInstance} from '@sanity/sdk'
|
|
2
|
-
import {
|
|
3
|
-
import {buildTheme} from '@sanity/ui/theme'
|
|
2
|
+
import {SanityProvider, useAuthState, useLoginUrls} from '@sanity/sdk-react'
|
|
4
3
|
import {render, screen} from '@testing-library/react'
|
|
5
4
|
import React from 'react'
|
|
6
5
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
7
6
|
|
|
8
|
-
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
9
|
-
import {useLoginUrls} from '../../hooks/auth/useLoginUrls'
|
|
10
|
-
import {SanityProvider} from '../context/SanityProvider'
|
|
11
7
|
import {LoginLinks} from './LoginLinks'
|
|
12
8
|
|
|
13
9
|
// Mock the hooks and SDK functions
|
|
@@ -40,12 +36,10 @@ vi.mock('../../hooks/auth/useAuthState', () => ({
|
|
|
40
36
|
useAuthState: vi.fn(() => 'logged-out'),
|
|
41
37
|
}))
|
|
42
38
|
|
|
43
|
-
vi.mock('../../hooks/auth/
|
|
44
|
-
|
|
39
|
+
vi.mock('../../hooks/auth/useHandleAuthCallback', () => ({
|
|
40
|
+
useHandleAuthCallback: vi.fn(),
|
|
45
41
|
}))
|
|
46
42
|
|
|
47
|
-
const theme = buildTheme({})
|
|
48
|
-
|
|
49
43
|
describe('LoginLinks', () => {
|
|
50
44
|
beforeEach(() => {
|
|
51
45
|
vi.clearAllMocks()
|
|
@@ -53,11 +47,7 @@ describe('LoginLinks', () => {
|
|
|
53
47
|
|
|
54
48
|
const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
|
|
55
49
|
const renderWithWrappers = (ui: React.ReactElement) => {
|
|
56
|
-
return render(
|
|
57
|
-
<ThemeProvider theme={theme}>
|
|
58
|
-
<SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
|
|
59
|
-
</ThemeProvider>,
|
|
60
|
-
)
|
|
50
|
+
return render(<SanityProvider sanityInstances={[sanityInstance]}>{ui}</SanityProvider>)
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
it('renders auth provider links correctly when not authenticated', () => {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {Button, Card, Container, Flex, Heading, Stack} from '@sanity/ui'
|
|
2
1
|
import {type ReactElement} from 'react'
|
|
3
2
|
|
|
4
3
|
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
5
|
-
import {
|
|
4
|
+
import {useHandleAuthCallback} from '../../hooks/auth/useHandleAuthCallback'
|
|
6
5
|
import {useLoginUrls} from '../../hooks/auth/useLoginUrls'
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -27,47 +26,33 @@ import {useLoginUrls} from '../../hooks/auth/useLoginUrls'
|
|
|
27
26
|
export const LoginLinks = (): ReactElement => {
|
|
28
27
|
const loginUrls = useLoginUrls()
|
|
29
28
|
const authState = useAuthState()
|
|
30
|
-
|
|
29
|
+
useHandleAuthCallback()
|
|
31
30
|
|
|
32
31
|
if (authState.type === 'logging-in') {
|
|
33
|
-
return <div>Logging in...</div>
|
|
32
|
+
return <div className="sc-login-links__logging-in">Logging in...</div>
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// Show success state after authentication
|
|
37
36
|
if (authState.type === 'logged-in') {
|
|
38
|
-
return <div>You are logged in</div>
|
|
37
|
+
return <div className="sc-login-links__logged-in">You are logged in</div>
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* Render provider selection UI
|
|
43
|
-
* Uses Sanity UI components for consistent styling
|
|
44
42
|
*/
|
|
45
43
|
return (
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
<Container width={0}>
|
|
49
|
-
<Stack space={4}>
|
|
50
|
-
<Heading align="center" size={1}>
|
|
51
|
-
Choose login provider
|
|
52
|
-
</Heading>
|
|
44
|
+
<div className="sc-login-links">
|
|
45
|
+
<h2 className="sc-login-links__title">Choose login provider</h2>
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
text={provider.title}
|
|
65
|
-
/>
|
|
66
|
-
))}
|
|
67
|
-
</Stack>
|
|
68
|
-
</Stack>
|
|
69
|
-
</Container>
|
|
70
|
-
</Flex>
|
|
71
|
-
</Card>
|
|
47
|
+
<ul className="sc-login-links__list">
|
|
48
|
+
{loginUrls.map((provider, index) => (
|
|
49
|
+
<li key={`${provider.url}_${index}`} className="sc-login-links__item">
|
|
50
|
+
<a href={provider.url} className="sc-login-links__link">
|
|
51
|
+
{provider.title}
|
|
52
|
+
</a>
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ul>
|
|
56
|
+
</div>
|
|
72
57
|
)
|
|
73
58
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as SanitySDK from '@sanity/sdk'
|
|
2
|
+
import {render} from '@testing-library/react'
|
|
3
|
+
import {type ReactNode} from 'react'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {type SanityProviderProps} from '../context/SanityProvider'
|
|
7
|
+
import {SDKProvider} from './SDKProvider'
|
|
8
|
+
|
|
9
|
+
// Mock the SDK module
|
|
10
|
+
vi.mock('@sanity/sdk', () => ({
|
|
11
|
+
createSanityInstance: vi.fn(() => ({
|
|
12
|
+
// Mock return value of createSanityInstance
|
|
13
|
+
id: 'mock-instance',
|
|
14
|
+
})),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
// Mock the SanityProvider context to verify the instance is passed correctly
|
|
18
|
+
vi.mock('../context/SanityProvider', () => ({
|
|
19
|
+
SanityProvider: ({children, sanityInstances}: SanityProviderProps) => (
|
|
20
|
+
<div data-testid="sanity-provider" data-instances={JSON.stringify(sanityInstances)}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
),
|
|
24
|
+
useSanity: vi.fn(),
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
// Mock the AuthBoundary component
|
|
28
|
+
vi.mock('./auth/AuthBoundary', () => ({
|
|
29
|
+
AuthBoundary: ({children}: {children: ReactNode}) => (
|
|
30
|
+
<div data-testid="auth-boundary">{children}</div>
|
|
31
|
+
),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
describe('SDKProvider', () => {
|
|
35
|
+
const mockConfig = {
|
|
36
|
+
projectId: 'test-project',
|
|
37
|
+
dataset: 'test-dataset',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it('creates a Sanity instance with the provided config', () => {
|
|
41
|
+
render(
|
|
42
|
+
<SDKProvider sanityConfigs={[mockConfig]} fallback={<div>Fallback</div>}>
|
|
43
|
+
<div>Test Child</div>
|
|
44
|
+
</SDKProvider>,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(SanitySDK.createSanityInstance).toHaveBeenCalledWith(mockConfig)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('renders children within SanityProvider and AuthBoundary', () => {
|
|
51
|
+
const {getByText, getByTestId} = render(
|
|
52
|
+
<SDKProvider sanityConfigs={[mockConfig]} fallback={<div>Fallback</div>}>
|
|
53
|
+
<div>Test Child</div>
|
|
54
|
+
</SDKProvider>,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// Verify the component hierarchy
|
|
58
|
+
const sanityProvider = getByTestId('sanity-provider')
|
|
59
|
+
const authBoundary = getByTestId('auth-boundary')
|
|
60
|
+
const childElement = getByText('Test Child')
|
|
61
|
+
|
|
62
|
+
expect(sanityProvider).toBeInTheDocument()
|
|
63
|
+
expect(authBoundary).toBeInTheDocument()
|
|
64
|
+
expect(childElement).toBeInTheDocument()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('passes the created Sanity instance to SanityProvider', () => {
|
|
68
|
+
const {getByTestId} = render(
|
|
69
|
+
<SDKProvider sanityConfigs={[mockConfig]} fallback={<div>Fallback</div>}>
|
|
70
|
+
<div>Test Child</div>
|
|
71
|
+
</SDKProvider>,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const sanityProvider = getByTestId('sanity-provider')
|
|
75
|
+
const passedInstances = JSON.parse(sanityProvider.dataset['instances'] || '[]')
|
|
76
|
+
|
|
77
|
+
expect(passedInstances).toEqual([{id: 'mock-instance'}])
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {createSanityInstance, type SanityConfig} from '@sanity/sdk'
|
|
2
|
+
import {type ReactElement, type ReactNode, Suspense, useMemo} from 'react'
|
|
3
|
+
|
|
4
|
+
import {SanityProvider} from '../context/SanityProvider'
|
|
5
|
+
import {AuthBoundary} from './auth/AuthBoundary'
|
|
6
|
+
|
|
7
|
+
const DEFAULT_FALLBACK = (
|
|
8
|
+
<>
|
|
9
|
+
Warning: No fallback provided. Please supply a fallback prop to ensure proper Suspense handling.
|
|
10
|
+
</>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export interface SDKProviderProps {
|
|
17
|
+
children: ReactNode
|
|
18
|
+
sanityConfigs: SanityConfig[]
|
|
19
|
+
fallback: ReactNode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @internal
|
|
24
|
+
*
|
|
25
|
+
* Top-level context provider that provides access to the Sanity SDK.
|
|
26
|
+
*/
|
|
27
|
+
export function SDKProvider({children, sanityConfigs, fallback}: SDKProviderProps): ReactElement {
|
|
28
|
+
const sanityInstances = useMemo(() => {
|
|
29
|
+
return sanityConfigs.map((sanityConfig: SanityConfig) => createSanityInstance(sanityConfig))
|
|
30
|
+
}, [sanityConfigs])
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<SanityProvider sanityInstances={sanityInstances}>
|
|
34
|
+
{/* This Suspense boundary is necessary because some hooks may suspend.
|
|
35
|
+
It ensures that the Sanity instance state created above remains stable
|
|
36
|
+
before rendering the AuthBoundary and its children. */}
|
|
37
|
+
<Suspense fallback={fallback ?? DEFAULT_FALLBACK}>
|
|
38
|
+
<AuthBoundary>{children}</AuthBoundary>
|
|
39
|
+
</Suspense>
|
|
40
|
+
</SanityProvider>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {AuthStateType, type SanityConfig} from '@sanity/sdk'
|
|
2
|
+
import {render, screen} from '@testing-library/react'
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {SanityApp} from './SanityApp'
|
|
6
|
+
|
|
7
|
+
vi.mock('@sanity/sdk', async () => {
|
|
8
|
+
const actual = await vi.importActual('@sanity/sdk')
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
createSanityInstance: vi.fn(() => ({
|
|
12
|
+
config: {},
|
|
13
|
+
auth: {
|
|
14
|
+
getSession: vi.fn(),
|
|
15
|
+
signIn: vi.fn(),
|
|
16
|
+
signOut: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
identity: {
|
|
19
|
+
projectId: 'test-project',
|
|
20
|
+
dataset: 'test-dataset',
|
|
21
|
+
},
|
|
22
|
+
dispose: vi.fn(),
|
|
23
|
+
})),
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
vi.mock('../hooks/auth/useAuthState', () => ({
|
|
28
|
+
useAuthState: () => ({
|
|
29
|
+
type: AuthStateType.LOGGED_IN,
|
|
30
|
+
session: {
|
|
31
|
+
user: {
|
|
32
|
+
id: 'test-user',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
}))
|
|
37
|
+
|
|
38
|
+
describe('SanityApp', () => {
|
|
39
|
+
const mockSanityConfig: SanityConfig = {
|
|
40
|
+
projectId: 'test-project',
|
|
41
|
+
dataset: 'test-dataset',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
it('renders children correctly', async () => {
|
|
45
|
+
const testMessage = 'Test Child Component'
|
|
46
|
+
render(
|
|
47
|
+
<SanityApp sanityConfigs={[mockSanityConfig]} fallback={<div>Fallback</div>}>
|
|
48
|
+
<div>{testMessage}</div>
|
|
49
|
+
</SanityApp>,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
expect(await screen.findByText(testMessage)).toBeInTheDocument()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('handles iframe environment correctly', async () => {
|
|
56
|
+
// Mock window.self and window.top to simulate iframe environment
|
|
57
|
+
const originalTop = window.top
|
|
58
|
+
const originalSelf = window.self
|
|
59
|
+
|
|
60
|
+
const mockTop = {}
|
|
61
|
+
Object.defineProperty(window, 'top', {
|
|
62
|
+
value: mockTop,
|
|
63
|
+
writable: true,
|
|
64
|
+
})
|
|
65
|
+
Object.defineProperty(window, 'self', {
|
|
66
|
+
value: window,
|
|
67
|
+
writable: true,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
render(
|
|
71
|
+
<SanityApp sanityConfigs={[mockSanityConfig]} fallback={<div>Fallback</div>}>
|
|
72
|
+
<div>Test Child</div>
|
|
73
|
+
</SanityApp>,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// Wait for 1 second
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 1010))
|
|
78
|
+
|
|
79
|
+
// Add assertions based on your iframe-specific behavior
|
|
80
|
+
expect(window.location.href).toBe('http://localhost:3000/')
|
|
81
|
+
|
|
82
|
+
// Clean up the mock
|
|
83
|
+
Object.defineProperty(window, 'top', {
|
|
84
|
+
value: originalTop,
|
|
85
|
+
writable: true,
|
|
86
|
+
})
|
|
87
|
+
Object.defineProperty(window, 'self', {
|
|
88
|
+
value: originalSelf,
|
|
89
|
+
writable: true,
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('redirects to core if not inside iframe and not local url', async () => {
|
|
94
|
+
const originalLocation = window.location
|
|
95
|
+
|
|
96
|
+
const mockLocation = {
|
|
97
|
+
replace: vi.fn(),
|
|
98
|
+
href: 'http://sanity-test.app',
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Object.defineProperty(window, 'location', {
|
|
102
|
+
value: mockLocation,
|
|
103
|
+
writable: true,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
render(
|
|
107
|
+
<SanityApp sanityConfigs={[mockSanityConfig]} fallback={<div>Fallback</div>}>
|
|
108
|
+
<div>Test Child</div>
|
|
109
|
+
</SanityApp>,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Wait for 1 second
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, 1010))
|
|
114
|
+
|
|
115
|
+
// Add assertions based on your iframe-specific behavior
|
|
116
|
+
expect(mockLocation.replace).toHaveBeenCalledWith('https://core.sanity.io')
|
|
117
|
+
|
|
118
|
+
// Clean up the mock
|
|
119
|
+
Object.defineProperty(window, 'location', {
|
|
120
|
+
value: originalLocation,
|
|
121
|
+
writable: true,
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('does not redirect to core if not inside iframe and local url', async () => {
|
|
126
|
+
const originalLocation = window.location
|
|
127
|
+
|
|
128
|
+
const mockLocation = {
|
|
129
|
+
replace: vi.fn(),
|
|
130
|
+
href: 'http://localhost:3000',
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Object.defineProperty(window, 'location', {
|
|
134
|
+
value: mockLocation,
|
|
135
|
+
writable: true,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
render(
|
|
139
|
+
<SanityApp sanityConfigs={[mockSanityConfig]} fallback={<div>Fallback</div>}>
|
|
140
|
+
<div>Test Child</div>
|
|
141
|
+
</SanityApp>,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// Wait for 1 second
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, 1010))
|
|
146
|
+
|
|
147
|
+
// Add assertions based on your iframe-specific behavior
|
|
148
|
+
expect(mockLocation.replace).not.toHaveBeenCalled()
|
|
149
|
+
|
|
150
|
+
// Clean up the mock
|
|
151
|
+
Object.defineProperty(window, 'location', {
|
|
152
|
+
value: originalLocation,
|
|
153
|
+
writable: true,
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {type SanityConfig} from '@sanity/sdk'
|
|
2
|
+
import {type ReactElement, useEffect} from 'react'
|
|
3
|
+
|
|
4
|
+
import {SDKProvider} from './SDKProvider'
|
|
5
|
+
import {isInIframe, isLocalUrl} from './utils'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface SanityAppProps {
|
|
11
|
+
sanityConfigs: SanityConfig[]
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
fallback: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const CORE_URL = 'https://core.sanity.io'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @public
|
|
20
|
+
*
|
|
21
|
+
* The SanityApp component provides your Sanity application with access to your Sanity configuration,
|
|
22
|
+
* as well as application context and state which is used by the Sanity React hooks. Your application
|
|
23
|
+
* must be wrapped with the SanityApp component to function properly.
|
|
24
|
+
*
|
|
25
|
+
* @param props - Your Sanity configuration and the React children to render
|
|
26
|
+
* @returns Your Sanity application, integrated with your Sanity configuration and application context
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* import { SanityApp } from '@sanity/sdk-react'
|
|
31
|
+
*
|
|
32
|
+
* import MyAppRoot from './Root'
|
|
33
|
+
*
|
|
34
|
+
* // Single project configuration
|
|
35
|
+
* const mySanityConfigs = [
|
|
36
|
+
* {
|
|
37
|
+
* projectId: 'my-project-id',
|
|
38
|
+
* dataset: 'production',
|
|
39
|
+
* },
|
|
40
|
+
* ]
|
|
41
|
+
*
|
|
42
|
+
* // Or multiple project configurations
|
|
43
|
+
* const multipleConfigs = [
|
|
44
|
+
* // Configuration for your main project. This will be used as the default project for all hooks if no resource ID override is provided.
|
|
45
|
+
* {
|
|
46
|
+
* projectId: 'marketing-website-project',
|
|
47
|
+
* dataset: 'production',
|
|
48
|
+
* },
|
|
49
|
+
* // Configuration for a separate blog project
|
|
50
|
+
* {
|
|
51
|
+
* projectId: 'blog-project',
|
|
52
|
+
* dataset: 'production',
|
|
53
|
+
* },
|
|
54
|
+
* // Configuration for a separate ecommerce project
|
|
55
|
+
* {
|
|
56
|
+
* projectId: 'ecommerce-project',
|
|
57
|
+
* dataset: 'production',
|
|
58
|
+
* }
|
|
59
|
+
* ]
|
|
60
|
+
*
|
|
61
|
+
* export default function MyApp() {
|
|
62
|
+
* return (
|
|
63
|
+
* <SanityApp sanityConfigs={mySanityConfigs}>
|
|
64
|
+
* <MyAppRoot />
|
|
65
|
+
* </SanityApp>
|
|
66
|
+
* )
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function SanityApp({sanityConfigs, children, fallback}: SanityAppProps): ReactElement {
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
let timeout: NodeJS.Timeout | undefined
|
|
73
|
+
|
|
74
|
+
if (!isInIframe() && !isLocalUrl(window)) {
|
|
75
|
+
// If the app is not running in an iframe and is not a local url, redirect to core.
|
|
76
|
+
timeout = setTimeout(() => {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.warn('Redirecting to core', CORE_URL)
|
|
79
|
+
window.location.replace(CORE_URL)
|
|
80
|
+
}, 1000)
|
|
81
|
+
}
|
|
82
|
+
return () => clearTimeout(timeout)
|
|
83
|
+
}, [sanityConfigs])
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<SDKProvider sanityConfigs={sanityConfigs} fallback={fallback}>
|
|
87
|
+
{children}
|
|
88
|
+
</SDKProvider>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {AuthStateType
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {render, screen, waitFor} from '@testing-library/react'
|
|
5
|
-
import React from 'react'
|
|
1
|
+
import {AuthStateType} from '@sanity/sdk'
|
|
2
|
+
import {useAuthState} from '@sanity/sdk-react'
|
|
3
|
+
import {screen, waitFor} from '@testing-library/react'
|
|
6
4
|
import {beforeEach, describe, expect, it, type MockInstance, vi} from 'vitest'
|
|
7
5
|
|
|
8
|
-
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
9
|
-
import {SanityProvider} from '../context/SanityProvider'
|
|
10
6
|
import {AuthBoundary} from './AuthBoundary'
|
|
7
|
+
import {renderWithWrappers} from './authTestHelpers'
|
|
11
8
|
|
|
12
9
|
// Mock hooks
|
|
13
10
|
vi.mock('../../hooks/auth/useAuthState', () => ({
|
|
@@ -16,8 +13,8 @@ vi.mock('../../hooks/auth/useAuthState', () => ({
|
|
|
16
13
|
vi.mock('../../hooks/auth/useLoginUrls', () => ({
|
|
17
14
|
useLoginUrls: vi.fn(() => [{title: 'Provider A', url: 'https://provider-a.com/auth'}]),
|
|
18
15
|
}))
|
|
19
|
-
vi.mock('../../hooks/auth/
|
|
20
|
-
|
|
16
|
+
vi.mock('../../hooks/auth/useHandleAuthCallback', () => ({
|
|
17
|
+
useHandleAuthCallback: vi.fn(() => async () => {}),
|
|
21
18
|
}))
|
|
22
19
|
vi.mock('../../hooks/auth/useLogOut', () => ({
|
|
23
20
|
useLogOut: vi.fn(() => async () => {}),
|
|
@@ -38,16 +35,6 @@ vi.mock('./AuthError', async (importOriginal) => {
|
|
|
38
35
|
}
|
|
39
36
|
})
|
|
40
37
|
|
|
41
|
-
const theme = buildTheme({})
|
|
42
|
-
const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
|
|
43
|
-
const renderWithWrappers = (ui: React.ReactElement) => {
|
|
44
|
-
return render(
|
|
45
|
-
<ThemeProvider theme={theme}>
|
|
46
|
-
<SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
|
|
47
|
-
</ThemeProvider>,
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
38
|
describe('AuthBoundary', () => {
|
|
52
39
|
let consoleErrorSpy: MockInstance
|
|
53
40
|
beforeEach(() => {
|
|
@@ -3,16 +3,32 @@ import {useMemo} from 'react'
|
|
|
3
3
|
import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
|
|
4
4
|
|
|
5
5
|
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
6
|
+
import {isInIframe} from '../utils'
|
|
6
7
|
import {AuthError} from './AuthError'
|
|
7
8
|
import {Login} from './Login'
|
|
8
9
|
import {LoginCallback} from './LoginCallback'
|
|
9
10
|
import {LoginError, type LoginErrorProps} from './LoginError'
|
|
10
|
-
import type
|
|
11
|
+
import {type LoginLayoutProps} from './LoginLayout'
|
|
12
|
+
|
|
13
|
+
// Only import bridge if we're in an iframe. This assumes that the app is
|
|
14
|
+
// running within SanityOS if it is in an iframe.
|
|
15
|
+
if (isInIframe()) {
|
|
16
|
+
const parsedUrl = new URL(window.location.href)
|
|
17
|
+
const mode = new URLSearchParams(parsedUrl.hash.slice(1)).get('mode')
|
|
18
|
+
const script = document.createElement('script')
|
|
19
|
+
script.src =
|
|
20
|
+
mode === 'core-ui--staging'
|
|
21
|
+
? 'https://core.sanity-cdn.work/bridge.js'
|
|
22
|
+
: 'https://core.sanity-cdn.com/bridge.js'
|
|
23
|
+
script.type = 'module'
|
|
24
|
+
script.async = true
|
|
25
|
+
document.head.appendChild(script)
|
|
26
|
+
}
|
|
11
27
|
|
|
12
28
|
/**
|
|
13
|
-
* @
|
|
29
|
+
* @internal
|
|
14
30
|
*/
|
|
15
|
-
|
|
31
|
+
interface AuthBoundaryProps extends LoginLayoutProps {
|
|
16
32
|
/**
|
|
17
33
|
* Custom component to render the login screen.
|
|
18
34
|
* Receives all login layout props. Defaults to {@link Login}.
|
|
@@ -52,7 +68,7 @@ export interface AuthBoundaryProps extends LoginLayoutProps {
|
|
|
52
68
|
* }
|
|
53
69
|
* ```
|
|
54
70
|
*
|
|
55
|
-
* @
|
|
71
|
+
* @internal
|
|
56
72
|
*/
|
|
57
73
|
export function AuthBoundary({
|
|
58
74
|
LoginErrorComponent = LoginError,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {ThemeProvider} from '@sanity/ui'
|
|
3
|
-
import {buildTheme} from '@sanity/ui/theme'
|
|
4
|
-
import {render, screen} from '@testing-library/react'
|
|
5
|
-
import React from 'react'
|
|
1
|
+
import {screen} from '@testing-library/react'
|
|
6
2
|
import {describe, expect, it, vi} from 'vitest'
|
|
7
3
|
|
|
8
|
-
import {
|
|
4
|
+
import {renderWithWrappers} from './authTestHelpers'
|
|
9
5
|
import {Login} from './Login'
|
|
10
6
|
|
|
11
7
|
vi.mock('../../hooks/auth/useLoginUrls', () => ({
|
|
@@ -15,16 +11,6 @@ vi.mock('../../hooks/auth/useLoginUrls', () => ({
|
|
|
15
11
|
]),
|
|
16
12
|
}))
|
|
17
13
|
|
|
18
|
-
const theme = buildTheme({})
|
|
19
|
-
const sanityInstance = createSanityInstance({projectId: 'test-project-id', dataset: 'production'})
|
|
20
|
-
const renderWithWrappers = (ui: React.ReactElement) => {
|
|
21
|
-
return render(
|
|
22
|
-
<ThemeProvider theme={theme}>
|
|
23
|
-
<SanityProvider sanityInstance={sanityInstance}>{ui}</SanityProvider>
|
|
24
|
-
</ThemeProvider>,
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
14
|
describe('Login', () => {
|
|
29
15
|
it('renders login providers', () => {
|
|
30
16
|
renderWithWrappers(<Login />)
|
|
@@ -1,46 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {Suspense} from 'react'
|
|
3
|
-
import styled from 'styled-components'
|
|
1
|
+
import {type JSX, Suspense} from 'react'
|
|
4
2
|
|
|
5
3
|
import {useLoginUrls} from '../../hooks/auth/useLoginUrls'
|
|
6
4
|
import {LoginLayout, type LoginLayoutProps} from './LoginLayout'
|
|
7
5
|
|
|
8
|
-
/**
|
|
9
|
-
* @alpha
|
|
10
|
-
*/
|
|
11
|
-
export interface LoginProps {
|
|
12
|
-
header?: React.ReactNode
|
|
13
|
-
footer?: React.ReactNode
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const FallbackRoot = styled(Flex)`
|
|
17
|
-
height: 123px;
|
|
18
|
-
`
|
|
19
|
-
|
|
20
6
|
/**
|
|
21
7
|
* Login component that displays available authentication providers.
|
|
22
8
|
* Renders a list of login options with a loading fallback while providers load.
|
|
23
9
|
*
|
|
24
10
|
* @alpha
|
|
11
|
+
* @internal
|
|
25
12
|
*/
|
|
26
13
|
export function Login({header, footer}: LoginLayoutProps): JSX.Element {
|
|
27
14
|
return (
|
|
28
15
|
<LoginLayout header={header} footer={footer}>
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
Choose login provider
|
|
32
|
-
</Heading>
|
|
16
|
+
<div className="sc-login">
|
|
17
|
+
<h1 className="sc-login__title">Choose login provider</h1>
|
|
33
18
|
|
|
34
|
-
<Suspense
|
|
35
|
-
fallback={
|
|
36
|
-
<FallbackRoot align="center" justify="center">
|
|
37
|
-
<Spinner />
|
|
38
|
-
</FallbackRoot>
|
|
39
|
-
}
|
|
40
|
-
>
|
|
19
|
+
<Suspense fallback={<div className="sc-login__loading">Loading…</div>}>
|
|
41
20
|
<Providers />
|
|
42
21
|
</Suspense>
|
|
43
|
-
</
|
|
22
|
+
</div>
|
|
44
23
|
</LoginLayout>
|
|
45
24
|
)
|
|
46
25
|
}
|
|
@@ -49,10 +28,12 @@ function Providers() {
|
|
|
49
28
|
const loginUrls = useLoginUrls()
|
|
50
29
|
|
|
51
30
|
return (
|
|
52
|
-
<
|
|
31
|
+
<div className="sc-login-providers">
|
|
53
32
|
{loginUrls.map(({title, url}) => (
|
|
54
|
-
<
|
|
33
|
+
<a key={url} href={url}>
|
|
34
|
+
{title}
|
|
35
|
+
</a>
|
|
55
36
|
))}
|
|
56
|
-
</
|
|
37
|
+
</div>
|
|
57
38
|
)
|
|
58
39
|
}
|