Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/core/src/_exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ export {resolveProjection} from '../projection/resolveProjection'
export {type ProjectionValuePending, type ValidProjection} from '../projection/types'
export {getProjectsState, resolveProjects} from '../projects/projects'
export {
clearQueryError,
getQueryErrorState,
getQueryKey,
getQueryState,
parseQueryKey,
Expand Down
36 changes: 0 additions & 36 deletions packages/core/src/query/queryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,29 +317,6 @@ const _getQueryState = bindActionByDataset(
}),
)

/**
* Returns a state source for the top-level query store error (if any).
*
* Unlike {@link getQueryState}, this selector does not throw; it simply returns the error value.
* Subscribe to this to be notified when a global query error occurs (e.g., CORS failures).
*
* @beta
*/
export function getQueryErrorState(instance: SanityInstance): StateSource<unknown | undefined> {
return _getQueryErrorState(instance)
}

const _getQueryErrorState = bindActionByDataset(
queryStore,
createStateSourceAction({
selector: ({state}: SelectorContext<QueryStoreState>) => state.error,
onSubscribe: () => {
// No-op subscription as we don't track per-query subscribers here
return () => {}
},
}),
)

/**
* Resolves the result of a query without registering a lasting subscriber.
*
Expand Down Expand Up @@ -413,16 +390,3 @@ const _resolveQuery = bindActionByDataset(
return firstValueFrom(race([resolved$, aborted$]))
},
)

/**
* Clears the top-level query store error.
* @beta
*/
export function clearQueryError(instance: SanityInstance): void
export function clearQueryError(...args: Parameters<typeof _clearQueryError>): void {
return _clearQueryError(...args)
}

const _clearQueryError = bindActionByDataset(queryStore, ({state}) => {
state.set('setError', {error: undefined})
})
19 changes: 2 additions & 17 deletions packages/react/src/components/auth/AuthBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {ComlinkTokenRefreshProvider} from '../../context/ComlinkTokenRefresh'
import {useAuthState} from '../../hooks/auth/useAuthState'
import {useLoginUrl} from '../../hooks/auth/useLoginUrl'
import {useVerifyOrgProjects} from '../../hooks/auth/useVerifyOrgProjects'
import {useCorsOriginError} from '../../hooks/errors/useCorsOriginError'
import {CorsErrorComponent} from '../errors/CorsErrorComponent'
import {isInIframe} from '../utils'
import {AuthError} from './AuthError'
Expand Down Expand Up @@ -108,38 +107,24 @@ export function AuthBoundary({
LoginErrorComponent = LoginError,
...props
}: AuthBoundaryProps): React.ReactNode {
const {error: corsError, projectId, clear: clearCorsError} = useCorsOriginError()

const FallbackComponent = useMemo(() => {
return function LoginComponentWithLayoutProps(fallbackProps: FallbackProps) {
if (fallbackProps.error instanceof CorsOriginError) {
return (
<CorsErrorComponent
{...fallbackProps}
projectId={getCorsErrorProjectId(fallbackProps.error)}
resetErrorBoundary={() => {
clearCorsError()
fallbackProps.resetErrorBoundary()
}}
/>
)
}
return <LoginErrorComponent {...fallbackProps} />
}
}, [LoginErrorComponent, clearCorsError])
}, [LoginErrorComponent])

return (
<ComlinkTokenRefreshProvider>
<ErrorBoundary FallbackComponent={FallbackComponent}>
{corsError ? (
<CorsErrorComponent
error={corsError}
resetErrorBoundary={() => clearCorsError()}
projectId={projectId}
/>
) : (
<AuthSwitch {...props} />
)}
<AuthSwitch {...props} />
</ErrorBoundary>
</ComlinkTokenRefreshProvider>
)
Expand Down
21 changes: 9 additions & 12 deletions packages/react/src/components/auth/LoginError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {type FallbackProps} from 'react-error-boundary'

import {useAuthState} from '../../hooks/auth/useAuthState'
import {useLogOut} from '../../hooks/auth/useLogOut'
import {Error} from '../errors/Error'
import {AuthError} from './AuthError'
import {ConfigurationError} from './ConfigurationError'
/**
Expand Down Expand Up @@ -59,17 +60,13 @@ export function LoginError({error, resetErrorBoundary}: LoginErrorProps): React.
}, [authState, handleRetry, error])

return (
<div className="sc-login-error">
<div className="sc-login-error__content">
<h2 className="sc-login-error__title">
{error instanceof AuthError ? 'Authentication Error' : 'Configuration Error'}
</h2>
<p className="sc-login-error__description">{authErrorMessage}</p>
</div>

<button className="sc-login-error__button" onClick={handleRetry}>
Retry
</button>
</div>
<Error
heading={error instanceof AuthError ? 'Authentication Error' : 'Configuration Error'}
description={authErrorMessage}
cta={{
text: 'Retry',
onClick: handleRetry,
}}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('CorsErrorComponent', () => {
/>,
)

expect(screen.getByText('Before you continue...')).toBeInTheDocument()
expect(screen.getByText('Before you continue')).toBeInTheDocument()
expect(screen.getByText(origin)).toBeInTheDocument()

const link = screen.getByRole('link', {name: 'Manage CORS configuration'}) as HTMLAnchorElement
Expand All @@ -41,7 +41,7 @@ describe('CorsErrorComponent', () => {
const error = new Error('some error message')
render(<CorsErrorComponent projectId={null} error={error} resetErrorBoundary={() => {}} />)

expect(screen.getByText('Before you continue...')).toBeInTheDocument()
expect(screen.getByText('Before you continue')).toBeInTheDocument()
expect(screen.getByText('some error message')).toBeInTheDocument()
expect(screen.queryByRole('link', {name: 'Manage CORS configuration'})).toBeNull()
})
Expand Down
39 changes: 18 additions & 21 deletions packages/react/src/components/errors/CorsErrorComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {useMemo} from 'react'
import {type FallbackProps} from 'react-error-boundary'

import {Error} from './Error'

type CorsErrorComponentProps = FallbackProps & {
projectId: string | null
}
Expand All @@ -15,26 +17,21 @@ export function CorsErrorComponent({projectId, error}: CorsErrorComponentProps):
return url.toString()
}, [origin, projectId])
return (
<div className="sc-login-error">
<div className="sc-login-error__content">
<h1>Before you continue...</h1>
<p>
To access your content, you need to <b>add the following URL as a CORS origin</b> to your
Sanity project.
</p>
<p>
<code>{origin}</code>
</p>
{projectId ? (
<p>
<a href={corsUrl ?? ''} target="_blank" rel="noopener noreferrer">
Manage CORS configuration
</a>
</p>
) : (
<p>{error?.message}</p>
)}
</div>
</div>
<Error
heading="Before you continue…"
{...(projectId
? {
description:
'To access your content, you need to <strong>add the following URL as a CORS origin</strong> to your Sanity project.',
code: origin,
cta: {
text: 'Manage CORS configuration',
href: corsUrl,
},
}
: {
description: error?.message,
})}
/>
)
}
35 changes: 35 additions & 0 deletions packages/react/src/components/errors/Error.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const FONT_SANS_SERIF = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, system-ui, sans-serif`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stole these font choices from the uncaught error overlay in the sanity repo. I'm not sure if this is the best place for them but I put them here for now since it's the only styles file.

const FONT_MONOSPACE = `-apple-system-ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace`

const styles: Record<string, React.CSSProperties> = {
container: {
padding: '28px',
fontFamily: FONT_SANS_SERIF,
display: 'flex',
flexDirection: 'column',
gap: '21px',
fontSize: '14px',
},
heading: {
margin: 0,
fontSize: '28px',
fontWeight: 700,
},
paragraph: {
margin: 0,
},
link: {
appearance: 'none',
background: 'transparent',
border: 0,
padding: 0,
font: 'inherit',
textDecoration: 'underline',
cursor: 'pointer',
},
code: {
fontFamily: FONT_MONOSPACE,
},
}

export default styles
40 changes: 40 additions & 0 deletions packages/react/src/components/errors/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styles from './Error.styles'

type ErrorProps = {
heading: string
description?: string
code?: string
cta?: {
text: string
href?: string
onClick?: () => void
}
}

export function Error({heading, description, code, cta}: ErrorProps): React.ReactNode {
return (
<div style={styles['container']}>
<h1 style={styles['heading']}>{heading}</h1>

{description && (
<p style={styles['paragraph']} dangerouslySetInnerHTML={{__html: description}} />
)}

{code && <code style={styles['code']}>{code}</code>}

{cta && (cta.href || cta.onClick) && (
<p style={styles['paragraph']}>
{cta.href ? (
<a style={styles['link']} href={cta.href} target="_blank" rel="noopener noreferrer">
{cta.text}
</a>
) : (
<button style={styles['link']} onClick={cta.onClick}>
{cta.text}
</button>
)}
</p>
)}
</div>
)
}
22 changes: 0 additions & 22 deletions packages/react/src/hooks/errors/useCorsOriginError.ts

This file was deleted.

Loading