@sanity/sdk-react 0.0.0-alpha.1
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/_chunks-es/useLogOut.js +36 -0
- package/dist/_chunks-es/useLogOut.js.map +1 -0
- package/dist/components.d.ts +235 -0
- package/dist/components.js +250 -0
- package/dist/components.js.map +1 -0
- package/dist/hooks.d.ts +145 -0
- package/dist/hooks.js +27 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/package.json +113 -0
- package/src/_exports/components.ts +12 -0
- package/src/_exports/hooks.ts +7 -0
- package/src/_exports/index.ts +10 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +95 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +42 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +23 -0
- package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +95 -0
- package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +42 -0
- package/src/components/DocumentListLayout/DocumentListLayout.tsx +15 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +49 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +34 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +30 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +115 -0
- package/src/components/Login/LoginLinks.test.tsx +100 -0
- package/src/components/Login/LoginLinks.tsx +73 -0
- package/src/components/auth/AuthBoundary.test.tsx +103 -0
- package/src/components/auth/AuthBoundary.tsx +101 -0
- package/src/components/auth/AuthError.test.ts +36 -0
- package/src/components/auth/AuthError.ts +27 -0
- package/src/components/auth/Login.test.tsx +41 -0
- package/src/components/auth/Login.tsx +58 -0
- package/src/components/auth/LoginCallback.test.tsx +86 -0
- package/src/components/auth/LoginCallback.tsx +41 -0
- package/src/components/auth/LoginError.test.tsx +56 -0
- package/src/components/auth/LoginError.tsx +54 -0
- package/src/components/auth/LoginFooter.test.tsx +29 -0
- package/src/components/auth/LoginFooter.tsx +67 -0
- package/src/components/auth/LoginLayout.test.tsx +33 -0
- package/src/components/auth/LoginLayout.tsx +99 -0
- package/src/components/context/SanityProvider.test.tsx +25 -0
- package/src/components/context/SanityProvider.tsx +42 -0
- package/src/hooks/Documents/.keep +0 -0
- package/src/hooks/auth/useAuthState.test.tsx +106 -0
- package/src/hooks/auth/useAuthState.tsx +33 -0
- package/src/hooks/auth/useAuthToken.test.tsx +94 -0
- package/src/hooks/auth/useAuthToken.tsx +16 -0
- package/src/hooks/auth/useCurrentUser.test.tsx +50 -0
- package/src/hooks/auth/useCurrentUser.tsx +27 -0
- package/src/hooks/auth/useHandleCallback.test.tsx +25 -0
- package/src/hooks/auth/useHandleCallback.tsx +50 -0
- package/src/hooks/auth/useLogOut.test.tsx +67 -0
- package/src/hooks/auth/useLogOut.tsx +15 -0
- package/src/hooks/auth/useLoginUrls.test.tsx +61 -0
- package/src/hooks/auth/useLoginUrls.tsx +51 -0
- package/src/hooks/client/useClient.test.tsx +130 -0
- package/src/hooks/client/useClient.ts +56 -0
- package/src/hooks/context/useSanityInstance.test.tsx +31 -0
- package/src/hooks/context/useSanityInstance.ts +23 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AuthState,
|
|
3
|
+
type AuthStore,
|
|
4
|
+
createSanityInstance,
|
|
5
|
+
getAuthStore,
|
|
6
|
+
type SanityInstance,
|
|
7
|
+
} from '@sanity/sdk'
|
|
8
|
+
import {renderHook} from '@testing-library/react'
|
|
9
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
10
|
+
|
|
11
|
+
import {SanityProvider} from '../../components/context/SanityProvider'
|
|
12
|
+
import * as context from '../context/useSanityInstance'
|
|
13
|
+
import {useAuthState} from './useAuthState'
|
|
14
|
+
|
|
15
|
+
// Mock dependencies
|
|
16
|
+
vi.mock('@sanity/sdk')
|
|
17
|
+
vi.mock('../context/useSanityInstance')
|
|
18
|
+
|
|
19
|
+
const createMockAuthStore = (authState: AuthState): AuthStore => ({
|
|
20
|
+
authState: {
|
|
21
|
+
getState: () => authState,
|
|
22
|
+
getInitialState: () => authState,
|
|
23
|
+
subscribe: vi.fn(),
|
|
24
|
+
},
|
|
25
|
+
tokenState: {
|
|
26
|
+
getState: vi.fn(),
|
|
27
|
+
getInitialState: vi.fn(),
|
|
28
|
+
subscribe: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
currentUserState: {
|
|
31
|
+
getState: vi.fn(),
|
|
32
|
+
getInitialState: vi.fn(),
|
|
33
|
+
subscribe: vi.fn(),
|
|
34
|
+
},
|
|
35
|
+
handleCallback: vi.fn(),
|
|
36
|
+
logout: vi.fn(),
|
|
37
|
+
dispose: vi.fn(),
|
|
38
|
+
getLoginUrls: vi.fn(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const mockUser = {
|
|
42
|
+
id: 'user-123',
|
|
43
|
+
name: 'Test User',
|
|
44
|
+
email: 'test@example.com',
|
|
45
|
+
role: 'developer',
|
|
46
|
+
roles: [{name: 'developer', title: 'Developer'}],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('useAuthState', () => {
|
|
50
|
+
const mockInstance: SanityInstance = {
|
|
51
|
+
identity: {
|
|
52
|
+
id: 'abc123store',
|
|
53
|
+
projectId: 'project-123',
|
|
54
|
+
dataset: 'dataset-123',
|
|
55
|
+
},
|
|
56
|
+
config: {},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Setup mock for useSanityInstance
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.spyOn(context, 'useSanityInstance').mockReturnValue(mockInstance)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should return the current auth state', () => {
|
|
65
|
+
const mockAuthStore = createMockAuthStore({
|
|
66
|
+
type: 'logged-in',
|
|
67
|
+
token: 'token-123',
|
|
68
|
+
currentUser: mockUser,
|
|
69
|
+
})
|
|
70
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
71
|
+
|
|
72
|
+
const sanityInstance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
73
|
+
const {result} = renderHook(() => useAuthState(), {
|
|
74
|
+
wrapper: ({children}) => (
|
|
75
|
+
<SanityProvider sanityInstance={sanityInstance}>{children}</SanityProvider>
|
|
76
|
+
),
|
|
77
|
+
})
|
|
78
|
+
const current = result.current as Extract<AuthState, {type: 'logged-in'}>
|
|
79
|
+
expect(current.type).toBe('logged-in')
|
|
80
|
+
expect(current.token).toBe('token-123')
|
|
81
|
+
expect(current.currentUser).toBe(mockUser)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should handle signed out state', () => {
|
|
85
|
+
const mockAuthStore = createMockAuthStore({type: 'logged-out', isDestroyingSession: false})
|
|
86
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
87
|
+
|
|
88
|
+
const {result} = renderHook(() => useAuthState())
|
|
89
|
+
expect(result.current.type).toBe('logged-out')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should subscribe to auth state changes', () => {
|
|
93
|
+
const subscribe = vi.fn()
|
|
94
|
+
const mockAuthStore = createMockAuthStore({
|
|
95
|
+
type: 'logged-in',
|
|
96
|
+
token: 'token-123',
|
|
97
|
+
currentUser: null,
|
|
98
|
+
})
|
|
99
|
+
mockAuthStore.authState.subscribe = subscribe
|
|
100
|
+
|
|
101
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
102
|
+
|
|
103
|
+
renderHook(() => useAuthState())
|
|
104
|
+
expect(subscribe).toHaveBeenCalled()
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {type AuthState, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {useStore} from 'zustand/react'
|
|
3
|
+
|
|
4
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React hook that subscribes to authentication state changes.
|
|
8
|
+
*
|
|
9
|
+
* This hook provides access to the current authentication state type from the Sanity auth store.
|
|
10
|
+
* It automatically re-renders the component when the authentication state changes.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* The hook uses `useSyncExternalStore` to safely subscribe to auth state changes
|
|
14
|
+
* and ensure consistency between server and client rendering.
|
|
15
|
+
*
|
|
16
|
+
* @returns The current authentication state type
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function AuthStatus() {
|
|
21
|
+
* const authState = useAuthState()
|
|
22
|
+
* return <div>Current auth state: {authState}</div>
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export function useAuthState(): AuthState {
|
|
29
|
+
const instance = useSanityInstance()
|
|
30
|
+
const {authState} = getAuthStore(instance)
|
|
31
|
+
|
|
32
|
+
return useStore(authState)
|
|
33
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {type AuthStore, getAuthStore, type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useAuthToken} from './useAuthToken'
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('@sanity/sdk', () => ({
|
|
10
|
+
getAuthStore: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../context/useSanityInstance', () => ({
|
|
14
|
+
useSanityInstance: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
describe('useAuthToken', () => {
|
|
18
|
+
// Helper function to create mock instance
|
|
19
|
+
const createMockInstance = (): SanityInstance => ({
|
|
20
|
+
identity: {
|
|
21
|
+
id: 'abc123',
|
|
22
|
+
projectId: 'test-project-id',
|
|
23
|
+
dataset: 'test-dataset',
|
|
24
|
+
},
|
|
25
|
+
config: {},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Helper function to create mock auth store
|
|
29
|
+
const createMockAuthStore = (token: string | null) => {
|
|
30
|
+
const authType = token ? 'logged-in' : 'logged-out'
|
|
31
|
+
return {
|
|
32
|
+
tokenState: {
|
|
33
|
+
getInitialState: () => token,
|
|
34
|
+
getState: () => token,
|
|
35
|
+
subscribe: vi.fn(),
|
|
36
|
+
},
|
|
37
|
+
authState: {
|
|
38
|
+
getInitialState: () => ({
|
|
39
|
+
type: authType,
|
|
40
|
+
isDestroyingSession: false,
|
|
41
|
+
...(token && {token, currentUser: null}),
|
|
42
|
+
}),
|
|
43
|
+
getState: () => ({
|
|
44
|
+
type: authType,
|
|
45
|
+
isDestroyingSession: false,
|
|
46
|
+
...(token && {token, currentUser: null}),
|
|
47
|
+
}),
|
|
48
|
+
subscribe: vi.fn(),
|
|
49
|
+
},
|
|
50
|
+
currentUserState: {
|
|
51
|
+
getInitialState: () => null,
|
|
52
|
+
getState: () => null,
|
|
53
|
+
subscribe: vi.fn(),
|
|
54
|
+
},
|
|
55
|
+
handleCallback: vi.fn(),
|
|
56
|
+
logout: vi.fn(),
|
|
57
|
+
dispose: vi.fn(),
|
|
58
|
+
getLoginUrls: vi.fn(),
|
|
59
|
+
} as unknown as AuthStore
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.clearAllMocks()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should return null when no token is present', () => {
|
|
67
|
+
const mockInstance = createMockInstance()
|
|
68
|
+
const mockAuthStore = createMockAuthStore(null)
|
|
69
|
+
|
|
70
|
+
vi.mocked(useSanityInstance).mockReturnValue(mockInstance)
|
|
71
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
72
|
+
|
|
73
|
+
const {result} = renderHook(() => useAuthToken())
|
|
74
|
+
|
|
75
|
+
expect(result.current).toBeNull()
|
|
76
|
+
expect(useSanityInstance).toHaveBeenCalled()
|
|
77
|
+
expect(getAuthStore).toHaveBeenCalledWith(mockInstance)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should return token when authenticated', () => {
|
|
81
|
+
const mockInstance = createMockInstance()
|
|
82
|
+
const mockToken = 'test-auth-token'
|
|
83
|
+
const mockAuthStore = createMockAuthStore(mockToken)
|
|
84
|
+
|
|
85
|
+
vi.mocked(useSanityInstance).mockReturnValue(mockInstance)
|
|
86
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
87
|
+
|
|
88
|
+
const {result} = renderHook(() => useAuthToken())
|
|
89
|
+
|
|
90
|
+
expect(result.current).toBe(mockToken)
|
|
91
|
+
expect(useSanityInstance).toHaveBeenCalled()
|
|
92
|
+
expect(getAuthStore).toHaveBeenCalledWith(mockInstance)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {useStore} from 'zustand'
|
|
3
|
+
|
|
4
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook to get the currently logged in user
|
|
8
|
+
* @public
|
|
9
|
+
* @returns The current user or null if not authenticated
|
|
10
|
+
*/
|
|
11
|
+
export const useAuthToken = (): string | null => {
|
|
12
|
+
const instance = useSanityInstance()
|
|
13
|
+
const {tokenState} = getAuthStore(instance)
|
|
14
|
+
|
|
15
|
+
return useStore(tokenState)
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {type AuthStore, type CurrentUser, getAuthStore, type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useCurrentUser} from './useCurrentUser'
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('../context/useSanityInstance')
|
|
10
|
+
vi.mock('@sanity/sdk')
|
|
11
|
+
|
|
12
|
+
const mockUser: CurrentUser = {
|
|
13
|
+
id: 'user-123',
|
|
14
|
+
name: 'Test User',
|
|
15
|
+
email: 'test@example.com',
|
|
16
|
+
role: 'admin',
|
|
17
|
+
roles: [],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('useCurrentUser', () => {
|
|
21
|
+
it('returns the current user when authenticated', () => {
|
|
22
|
+
vi.mocked(useSanityInstance).mockReturnValue({} as unknown as SanityInstance)
|
|
23
|
+
|
|
24
|
+
// Mock the auth store with an authenticated user
|
|
25
|
+
vi.mocked(getAuthStore).mockReturnValue({
|
|
26
|
+
currentUserState: {
|
|
27
|
+
getState: () => mockUser,
|
|
28
|
+
subscribe: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
} as unknown as AuthStore)
|
|
31
|
+
|
|
32
|
+
const {result} = renderHook(() => useCurrentUser())
|
|
33
|
+
expect(result.current).toEqual(mockUser)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('returns null when not authenticated', () => {
|
|
37
|
+
vi.mocked(useSanityInstance).mockReturnValue({} as unknown as SanityInstance)
|
|
38
|
+
|
|
39
|
+
// Mock the auth store with no user
|
|
40
|
+
vi.mocked(getAuthStore).mockReturnValue({
|
|
41
|
+
currentUserState: {
|
|
42
|
+
getState: () => null,
|
|
43
|
+
subscribe: vi.fn(),
|
|
44
|
+
},
|
|
45
|
+
} as unknown as AuthStore)
|
|
46
|
+
|
|
47
|
+
const {result} = renderHook(() => useCurrentUser())
|
|
48
|
+
expect(result.current).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {type CurrentUser, type CurrentUserSlice, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {useStore} from 'zustand'
|
|
3
|
+
|
|
4
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook to get the currently logged in user
|
|
8
|
+
* @public
|
|
9
|
+
* @returns The current user or null if not authenticated
|
|
10
|
+
*/
|
|
11
|
+
export const useCurrentUser = (): CurrentUser | null => {
|
|
12
|
+
const instance = useSanityInstance()
|
|
13
|
+
const {currentUserState} = getAuthStore(instance)
|
|
14
|
+
|
|
15
|
+
// TODO: update this hook so it can never return null
|
|
16
|
+
if (!currentUserState.getState())
|
|
17
|
+
throw new Promise<void>((resolve) => {
|
|
18
|
+
const unsubscribe = currentUserState.subscribe((currentUser) => {
|
|
19
|
+
if (currentUser) {
|
|
20
|
+
unsubscribe()
|
|
21
|
+
resolve()
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return useStore<CurrentUserSlice>(currentUserState)
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {type Mock, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useHandleCallback} from './useHandleCallback'
|
|
7
|
+
|
|
8
|
+
vi.mock('../context/useSanityInstance')
|
|
9
|
+
vi.mock('@sanity/sdk', () => ({getAuthStore: vi.fn()}))
|
|
10
|
+
|
|
11
|
+
describe('useHandleCallback', () => {
|
|
12
|
+
it('returns handleCallback from auth store', () => {
|
|
13
|
+
const mockInstance = {id: 'test'}
|
|
14
|
+
const mockHandleCallback = vi.fn()
|
|
15
|
+
const mockAuthStore = {handleCallback: mockHandleCallback}
|
|
16
|
+
|
|
17
|
+
;(useSanityInstance as Mock).mockReturnValue(mockInstance)
|
|
18
|
+
;(getAuthStore as Mock).mockReturnValue(mockAuthStore)
|
|
19
|
+
|
|
20
|
+
const {result} = renderHook(() => useHandleCallback())
|
|
21
|
+
|
|
22
|
+
expect(getAuthStore).toHaveBeenCalledWith(mockInstance)
|
|
23
|
+
expect(result.current).toBe(mockHandleCallback)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {type AuthStore, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {useMemo} from 'react'
|
|
3
|
+
|
|
4
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React hook that returns a function for handling authentication callbacks.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* This hook provides access to the authentication store's callback handler,
|
|
11
|
+
* which processes auth redirects by extracting the session ID and fetching the
|
|
12
|
+
* authentication token. If fetching the long-lived token is successful,
|
|
13
|
+
* `handleCallback` will return a Promise that resolves a new location that
|
|
14
|
+
* removes the short-lived token from the URL. Use this in combination with
|
|
15
|
+
* `history.replaceState` or your own router's `replace` function to update the
|
|
16
|
+
* current location without triggering a reload.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function AuthCallback() {
|
|
21
|
+
* const handleCallback = useHandleCallback()
|
|
22
|
+
* const router = useRouter() // Example router
|
|
23
|
+
*
|
|
24
|
+
* useEffect(() => {
|
|
25
|
+
* async function processCallback() {
|
|
26
|
+
* // Handle the callback and get the cleaned URL
|
|
27
|
+
* const newUrl = await handleCallback(window.location.href)
|
|
28
|
+
*
|
|
29
|
+
* if (newUrl) {
|
|
30
|
+
* // Replace URL without triggering navigation
|
|
31
|
+
* router.replace(newUrl, {shallow: true})
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* processCallback().catch(console.error)
|
|
36
|
+
* }, [handleCallback, router])
|
|
37
|
+
*
|
|
38
|
+
* return <div>Completing login...</div>
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @returns A callback handler function that processes OAuth redirects
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export function useHandleCallback(): AuthStore['handleCallback'] {
|
|
46
|
+
const instance = useSanityInstance()
|
|
47
|
+
const authStore = useMemo(() => getAuthStore(instance), [instance])
|
|
48
|
+
|
|
49
|
+
return authStore.handleCallback
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {type AuthStore, getAuthStore, type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useLogOut} from './useLogOut'
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('@sanity/sdk', () => ({
|
|
10
|
+
getAuthStore: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../context/useSanityInstance', () => ({
|
|
14
|
+
useSanityInstance: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
describe('useLogOut', () => {
|
|
18
|
+
it('should return logout function from auth store', () => {
|
|
19
|
+
// Setup mocks
|
|
20
|
+
const mockInstance: SanityInstance = {
|
|
21
|
+
identity: {
|
|
22
|
+
id: 'abc123',
|
|
23
|
+
projectId: 'test-project-id',
|
|
24
|
+
dataset: 'test-dataset',
|
|
25
|
+
},
|
|
26
|
+
config: {},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const mockLogout = vi.fn()
|
|
30
|
+
const mockAuthStore: AuthStore = {
|
|
31
|
+
authState: {
|
|
32
|
+
getInitialState: vi.fn(),
|
|
33
|
+
getState: vi.fn(),
|
|
34
|
+
subscribe: vi.fn(),
|
|
35
|
+
},
|
|
36
|
+
tokenState: {
|
|
37
|
+
getInitialState: vi.fn(),
|
|
38
|
+
getState: vi.fn(),
|
|
39
|
+
subscribe: vi.fn(),
|
|
40
|
+
},
|
|
41
|
+
currentUserState: {
|
|
42
|
+
getInitialState: vi.fn(),
|
|
43
|
+
getState: vi.fn(),
|
|
44
|
+
subscribe: vi.fn(),
|
|
45
|
+
},
|
|
46
|
+
handleCallback: vi.fn(),
|
|
47
|
+
logout: mockLogout,
|
|
48
|
+
dispose: vi.fn(),
|
|
49
|
+
getLoginUrls: vi.fn(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
vi.mocked(useSanityInstance).mockReturnValue(mockInstance)
|
|
53
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore)
|
|
54
|
+
|
|
55
|
+
// Test the hook
|
|
56
|
+
const {result} = renderHook(() => useLogOut())
|
|
57
|
+
|
|
58
|
+
// Verify the returned function is the logout function
|
|
59
|
+
expect(result.current).toBe(mockLogout)
|
|
60
|
+
expect(useSanityInstance).toHaveBeenCalled()
|
|
61
|
+
expect(getAuthStore).toHaveBeenCalledWith(mockInstance)
|
|
62
|
+
|
|
63
|
+
// Verify the logout function can be called
|
|
64
|
+
result.current()
|
|
65
|
+
expect(mockLogout).toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {type AuthStore, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to log out of the current session
|
|
7
|
+
* @public
|
|
8
|
+
* @returns A function to log out of the current session
|
|
9
|
+
*/
|
|
10
|
+
export const useLogOut = (): AuthStore['logout'] => {
|
|
11
|
+
const instance = useSanityInstance()
|
|
12
|
+
const {logout} = getAuthStore(instance)
|
|
13
|
+
|
|
14
|
+
return logout
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {type AuthStore, createSanityInstance, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {renderHook, waitFor} from '@testing-library/react'
|
|
3
|
+
import {Suspense} from 'react'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {SanityProvider} from '../../components/context/SanityProvider'
|
|
7
|
+
import {useLoginUrls} from './useLoginUrls'
|
|
8
|
+
|
|
9
|
+
vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
10
|
+
const actual = await importOriginal()
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
getAuthStore: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('useLoginUrls', () => {
|
|
18
|
+
it('should handle synchronous provider URLs', () => {
|
|
19
|
+
const mockProviders = [{name: 'google', title: 'Google', url: 'http://test.com/auth/google'}]
|
|
20
|
+
const mockAuthStore = {
|
|
21
|
+
getLoginUrls: () => mockProviders,
|
|
22
|
+
}
|
|
23
|
+
vi.mocked(getAuthStore).mockReturnValue(mockAuthStore as AuthStore)
|
|
24
|
+
|
|
25
|
+
const sanityInstance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
26
|
+
const wrapper = ({children}: {children: React.ReactNode}) => (
|
|
27
|
+
<SanityProvider sanityInstance={sanityInstance}>{children}</SanityProvider>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const {result} = renderHook(() => useLoginUrls(), {wrapper})
|
|
31
|
+
expect(result.current).toEqual(mockProviders)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should handle asynchronous provider URLs', async () => {
|
|
35
|
+
const mockProviders = [{name: 'google', title: 'Google', url: 'http://test.com/auth/google'}]
|
|
36
|
+
|
|
37
|
+
const getLoginUrls = vi
|
|
38
|
+
.fn<AuthStore['getLoginUrls']>()
|
|
39
|
+
.mockResolvedValueOnce(mockProviders)
|
|
40
|
+
.mockReturnValueOnce(mockProviders)
|
|
41
|
+
|
|
42
|
+
vi.mocked(getAuthStore).mockReturnValue({getLoginUrls} as unknown as AuthStore)
|
|
43
|
+
|
|
44
|
+
const sanityInstance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
45
|
+
const wrapper = ({children}: {children: React.ReactNode}) => (
|
|
46
|
+
<Suspense fallback={<>Loading…</>}>
|
|
47
|
+
<SanityProvider sanityInstance={sanityInstance}>{children}</SanityProvider>
|
|
48
|
+
</Suspense>
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const {result} = renderHook(() => useLoginUrls(), {wrapper})
|
|
52
|
+
|
|
53
|
+
// Initially empty
|
|
54
|
+
expect(result.current).toEqual(null)
|
|
55
|
+
|
|
56
|
+
// Wait for providers to load
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(result.current).toEqual(mockProviders)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {type AuthProvider, getAuthStore} from '@sanity/sdk'
|
|
2
|
+
import {useMemo} from 'react'
|
|
3
|
+
|
|
4
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React hook that retrieves the available authentication provider URLs for login.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* This hook fetches the login URLs from the Sanity auth store when the component mounts.
|
|
11
|
+
* Each provider object contains information about an authentication method, including its URL.
|
|
12
|
+
* The hook will suspend if the login URLs have not yet loaded.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // LoginProviders component that uses the hook
|
|
17
|
+
* function LoginProviders() {
|
|
18
|
+
* const providers = useLoginUrls()
|
|
19
|
+
*
|
|
20
|
+
* return (
|
|
21
|
+
* <div>
|
|
22
|
+
* {providers.map((provider) => (
|
|
23
|
+
* <a key={provider.name} href={provider.url}>
|
|
24
|
+
* Login with {provider.title}
|
|
25
|
+
* </a>
|
|
26
|
+
* ))}
|
|
27
|
+
* </div>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // Parent component with Suspense boundary
|
|
32
|
+
* function LoginPage() {
|
|
33
|
+
* return (
|
|
34
|
+
* <Suspense fallback={<div>Loading authentication providers...</div>}>
|
|
35
|
+
* <LoginProviders />
|
|
36
|
+
* </Suspense>
|
|
37
|
+
* )
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @returns An array of {@link AuthProvider} objects containing login URLs and provider information
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export function useLoginUrls(): AuthProvider[] {
|
|
45
|
+
const instance = useSanityInstance()
|
|
46
|
+
const authStore = useMemo(() => getAuthStore(instance), [instance])
|
|
47
|
+
const result = authStore.getLoginUrls()
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(result)) return result
|
|
50
|
+
throw result
|
|
51
|
+
}
|