Skip to content

Commit 1668657

Browse files
authored
fix: handle projectUserNotFoundErrors and display the error message (#651)
1 parent c4b6885 commit 1668657

File tree

3 files changed

+70
-7
lines changed

3 files changed

+70
-7
lines changed

packages/core/src/_exports/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export {
2424
export {observeOrganizationVerificationState} from '../auth/getOrganizationVerificationState'
2525
export {handleAuthCallback} from '../auth/handleAuthCallback'
2626
export {logout} from '../auth/logout'
27+
export {
28+
type ApiErrorBody,
29+
getClientErrorApiBody,
30+
getClientErrorApiDescription,
31+
getClientErrorApiType,
32+
isProjectUserNotFoundClientError,
33+
} from '../auth/utils'
2734
export type {ClientStoreState as ClientState} from '../client/clientStore'
2835
export {type ClientOptions, getClient, getClientState} from '../client/clientStore'
2936
export {

packages/core/src/auth/utils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {type ClientError} from '@sanity/client'
12
import {EMPTY, fromEvent, Observable} from 'rxjs'
23

34
import {AUTH_CODE_PARAM, DEFAULT_BASE} from './authConstants'
@@ -134,3 +135,38 @@ export function getCleanedUrl(locationUrl: string): string {
134135
loc.searchParams.delete('url')
135136
return loc.toString()
136137
}
138+
139+
// -----------------------------------------------------------------------------
140+
// ClientError helpers (shared)
141+
// -----------------------------------------------------------------------------
142+
143+
/** @internal */
144+
export type ApiErrorBody = {
145+
error?: {type?: string; description?: string}
146+
type?: string
147+
description?: string
148+
message?: string
149+
}
150+
151+
/** @internal Extracts the structured API error body from a ClientError, if present. */
152+
export function getClientErrorApiBody(error: ClientError): ApiErrorBody | undefined {
153+
const body: unknown = (error as ClientError).response?.body
154+
return body && typeof body === 'object' ? (body as ApiErrorBody) : undefined
155+
}
156+
157+
/** @internal Returns the error type string from an API error body, if available. */
158+
export function getClientErrorApiType(error: ClientError): string | undefined {
159+
const body = getClientErrorApiBody(error)
160+
return body?.error?.type ?? body?.type
161+
}
162+
163+
/** @internal Returns the error description string from an API error body, if available. */
164+
export function getClientErrorApiDescription(error: ClientError): string | undefined {
165+
const body = getClientErrorApiBody(error)
166+
return body?.error?.description ?? body?.description
167+
}
168+
169+
/** @internal True if the error represents a projectUserNotFoundError. */
170+
export function isProjectUserNotFoundClientError(error: ClientError): boolean {
171+
return getClientErrorApiType(error) === 'projectUserNotFoundError'
172+
}

packages/react/src/components/auth/LoginError.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import {ClientError} from '@sanity/client'
2-
import {AuthStateType} from '@sanity/sdk'
2+
import {
3+
AuthStateType,
4+
getClientErrorApiBody,
5+
getClientErrorApiDescription,
6+
isProjectUserNotFoundClientError,
7+
} from '@sanity/sdk'
38
import {useCallback, useEffect, useState} from 'react'
49
import {type FallbackProps} from 'react-error-boundary'
510

@@ -35,6 +40,7 @@ export function LoginError({error, resetErrorBoundary}: LoginErrorProps): React.
3540
const [authErrorMessage, setAuthErrorMessage] = useState(
3641
'Please try again or contact support if the problem persists.',
3742
)
43+
const [showRetryCta, setShowRetryCta] = useState(true)
3844

3945
const handleRetry = useCallback(async () => {
4046
await logout()
@@ -44,29 +50,43 @@ export function LoginError({error, resetErrorBoundary}: LoginErrorProps): React.
4450
useEffect(() => {
4551
if (error instanceof ClientError) {
4652
if (error.statusCode === 401) {
47-
handleRetry()
53+
// Surface a friendly message for projectUserNotFoundError (do not logout/refresh)
54+
if (isProjectUserNotFoundClientError(error)) {
55+
const description = getClientErrorApiDescription(error)
56+
if (description) setAuthErrorMessage(description)
57+
setShowRetryCta(false)
58+
} else {
59+
setShowRetryCta(true)
60+
handleRetry()
61+
}
4862
} else if (error.statusCode === 404) {
49-
const errorMessage = error.response.body.message || ''
63+
const errorMessage = getClientErrorApiBody(error)?.message || ''
5064
if (errorMessage.startsWith('Session with sid') && errorMessage.endsWith('not found')) {
5165
setAuthErrorMessage('The session ID is invalid or expired.')
5266
} else {
5367
setAuthErrorMessage('The login link is invalid or expired. Please try again.')
5468
}
69+
setShowRetryCta(true)
5570
}
5671
}
5772
if (authState.type !== AuthStateType.ERROR && error instanceof ConfigurationError) {
5873
setAuthErrorMessage(error.message)
74+
setShowRetryCta(true)
5975
}
6076
}, [authState, handleRetry, error])
6177

6278
return (
6379
<Error
6480
heading={error instanceof AuthError ? 'Authentication Error' : 'Configuration Error'}
6581
description={authErrorMessage}
66-
cta={{
67-
text: 'Retry',
68-
onClick: handleRetry,
69-
}}
82+
cta={
83+
showRetryCta
84+
? {
85+
text: 'Retry',
86+
onClick: handleRetry,
87+
}
88+
: undefined
89+
}
7090
/>
7191
)
7292
}

0 commit comments

Comments
 (0)