@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.
Files changed (116) hide show
  1. package/README.md +38 -67
  2. package/dist/index.d.ts +4811 -2
  3. package/dist/index.js +1069 -2
  4. package/dist/index.js.map +1 -1
  5. package/package.json +27 -58
  6. package/src/_exports/index.ts +66 -10
  7. package/src/components/Login/LoginLinks.test.tsx +4 -14
  8. package/src/components/Login/LoginLinks.tsx +16 -31
  9. package/src/components/SDKProvider.test.tsx +79 -0
  10. package/src/components/SDKProvider.tsx +42 -0
  11. package/src/components/SanityApp.test.tsx +156 -0
  12. package/src/components/SanityApp.tsx +90 -0
  13. package/src/components/auth/AuthBoundary.test.tsx +6 -19
  14. package/src/components/auth/AuthBoundary.tsx +20 -4
  15. package/src/components/auth/Login.test.tsx +2 -16
  16. package/src/components/auth/Login.tsx +11 -30
  17. package/src/components/auth/LoginCallback.test.tsx +5 -20
  18. package/src/components/auth/LoginCallback.tsx +9 -14
  19. package/src/components/auth/LoginError.test.tsx +2 -17
  20. package/src/components/auth/LoginError.tsx +11 -16
  21. package/src/components/auth/LoginFooter.test.tsx +2 -16
  22. package/src/components/auth/LoginFooter.tsx +8 -24
  23. package/src/components/auth/LoginLayout.test.tsx +2 -16
  24. package/src/components/auth/LoginLayout.tsx +8 -38
  25. package/src/components/auth/authTestHelpers.tsx +11 -0
  26. package/src/components/utils.ts +22 -0
  27. package/src/context/SanityInstanceContext.ts +4 -0
  28. package/src/{components/context → context}/SanityProvider.test.tsx +2 -2
  29. package/src/context/SanityProvider.tsx +50 -0
  30. package/src/hooks/_synchronous-groq-js.mjs +4 -0
  31. package/src/hooks/auth/useAuthState.tsx +4 -5
  32. package/src/hooks/auth/useAuthToken.tsx +1 -1
  33. package/src/hooks/auth/useCurrentUser.tsx +28 -4
  34. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +42 -0
  35. package/src/hooks/auth/useDashboardOrganizationId.tsx +29 -0
  36. package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
  37. package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +7 -6
  38. package/src/hooks/auth/useLogOut.test.tsx +2 -2
  39. package/src/hooks/auth/useLogOut.tsx +1 -1
  40. package/src/hooks/auth/useLoginUrls.tsx +1 -0
  41. package/src/hooks/client/useClient.ts +9 -30
  42. package/src/hooks/comlink/useFrameConnection.test.tsx +167 -0
  43. package/src/hooks/comlink/useFrameConnection.ts +107 -0
  44. package/src/hooks/comlink/useManageFavorite.test.ts +111 -0
  45. package/src/hooks/comlink/useManageFavorite.ts +130 -0
  46. package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +81 -0
  47. package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +106 -0
  48. package/src/hooks/comlink/useWindowConnection.test.ts +135 -0
  49. package/src/hooks/comlink/useWindowConnection.ts +122 -0
  50. package/src/hooks/context/useSanityInstance.test.tsx +2 -2
  51. package/src/hooks/context/useSanityInstance.ts +24 -8
  52. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +178 -0
  53. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +123 -0
  54. package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +278 -0
  55. package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +92 -0
  56. package/src/hooks/datasets/useDatasets.ts +40 -0
  57. package/src/hooks/document/useApplyDocumentActions.test.ts +25 -0
  58. package/src/hooks/document/useApplyDocumentActions.ts +75 -0
  59. package/src/hooks/document/useDocument.test.ts +81 -0
  60. package/src/hooks/document/useDocument.ts +107 -0
  61. package/src/hooks/document/useDocumentEvent.test.ts +63 -0
  62. package/src/hooks/document/useDocumentEvent.ts +54 -0
  63. package/src/hooks/document/useDocumentPermissions.ts +84 -0
  64. package/src/hooks/document/useDocumentSyncStatus.test.ts +16 -0
  65. package/src/hooks/document/useDocumentSyncStatus.ts +33 -0
  66. package/src/hooks/document/useEditDocument.test.ts +179 -0
  67. package/src/hooks/document/useEditDocument.ts +195 -0
  68. package/src/hooks/documents/useDocuments.test.tsx +152 -0
  69. package/src/hooks/documents/useDocuments.ts +174 -0
  70. package/src/hooks/helpers/createCallbackHook.tsx +3 -2
  71. package/src/hooks/helpers/createStateSourceHook.test.tsx +66 -0
  72. package/src/hooks/helpers/createStateSourceHook.tsx +29 -10
  73. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +259 -0
  74. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +290 -0
  75. package/src/hooks/preview/usePreview.test.tsx +19 -10
  76. package/src/hooks/preview/usePreview.tsx +67 -13
  77. package/src/hooks/projection/useProjection.test.tsx +218 -0
  78. package/src/hooks/projection/useProjection.ts +147 -0
  79. package/src/hooks/projects/useProject.ts +48 -0
  80. package/src/hooks/projects/useProjects.ts +45 -0
  81. package/src/hooks/query/useQuery.test.tsx +188 -0
  82. package/src/hooks/query/useQuery.ts +103 -0
  83. package/src/hooks/users/useUsers.test.ts +163 -0
  84. package/src/hooks/users/useUsers.ts +107 -0
  85. package/src/utils/getEnv.ts +21 -0
  86. package/src/version.ts +8 -0
  87. package/src/vite-env.d.ts +10 -0
  88. package/dist/_chunks-es/useLogOut.js +0 -44
  89. package/dist/_chunks-es/useLogOut.js.map +0 -1
  90. package/dist/assets/bundle-CcAyERuZ.css +0 -11
  91. package/dist/components.d.ts +0 -257
  92. package/dist/components.js +0 -316
  93. package/dist/components.js.map +0 -1
  94. package/dist/hooks.d.ts +0 -187
  95. package/dist/hooks.js +0 -81
  96. package/dist/hooks.js.map +0 -1
  97. package/src/_exports/components.ts +0 -13
  98. package/src/_exports/hooks.ts +0 -9
  99. package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +0 -113
  100. package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +0 -42
  101. package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +0 -21
  102. package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +0 -105
  103. package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +0 -42
  104. package/src/components/DocumentListLayout/DocumentListLayout.tsx +0 -12
  105. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +0 -49
  106. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +0 -39
  107. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +0 -30
  108. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +0 -171
  109. package/src/components/context/SanityProvider.tsx +0 -42
  110. package/src/css/css.config.js +0 -220
  111. package/src/css/paramour.css +0 -2347
  112. package/src/css/styles.css +0 -11
  113. package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
  114. package/src/hooks/client/useClient.test.tsx +0 -130
  115. package/src/hooks/documentCollection/useDocuments.test.ts +0 -130
  116. package/src/hooks/documentCollection/useDocuments.ts +0 -87
@@ -1,13 +1,9 @@
1
1
  import {AuthStateType, createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
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/useHandleCallback', () => ({
44
- useHandleCallback: vi.fn(),
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 {useHandleCallback} from '../../hooks/auth/useHandleCallback'
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
- useHandleCallback()
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
- <Card height="fill" overflow="auto" paddingX={4}>
47
- <Flex height="fill" direction="column" align="center" justify="center" paddingTop={4}>
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
- <Stack space={2}>
55
- {loginUrls.map((provider, index) => (
56
- <Button
57
- key={`${provider.url}_${index}`}
58
- as="a"
59
- href={provider.url}
60
- mode="ghost"
61
- tone="default"
62
- space={3}
63
- padding={3}
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, createSanityInstance} from '@sanity/sdk'
2
- import {ThemeProvider} from '@sanity/ui'
3
- import {buildTheme} from '@sanity/ui/theme'
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/useHandleCallback', () => ({
20
- useHandleCallback: vi.fn(() => async () => {}),
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 {LoginLayoutProps} from './LoginLayout'
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
- * @alpha
29
+ * @internal
14
30
  */
15
- export interface AuthBoundaryProps extends LoginLayoutProps {
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
- * @alpha
71
+ * @internal
56
72
  */
57
73
  export function AuthBoundary({
58
74
  LoginErrorComponent = LoginError,
@@ -1,11 +1,7 @@
1
- import {createSanityInstance} from '@sanity/sdk'
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 {SanityProvider} from '../context/SanityProvider'
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 {Button, Flex, Heading, Spinner} from '@sanity/ui'
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
- <Flex direction="column" gap={4}>
30
- <Heading as="h1" size={1} align="center">
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
- </Flex>
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
- <Flex direction="column" gap={3}>
31
+ <div className="sc-login-providers">
53
32
  {loginUrls.map(({title, url}) => (
54
- <Button key={url} text={title} as="a" href={url} mode="ghost" />
33
+ <a key={url} href={url}>
34
+ {title}
35
+ </a>
55
36
  ))}
56
- </Flex>
37
+ </div>
57
38
  )
58
39
  }