diff --git a/.changeset/good-eels-drive.md b/.changeset/good-eels-drive.md new file mode 100644 index 0000000000..c90a1263ec --- /dev/null +++ b/.changeset/good-eels-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +It is possible to define a custom error element to be shown when sign in fails diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index 24c835f638..7ffb430c7f 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -15,6 +15,7 @@ import { CardHeaderProps } from '@material-ui/core/CardHeader'; import { Column } from '@material-table/core'; import { ComponentClass } from 'react'; import { ComponentProps } from 'react'; +import { ComponentType } from 'react'; import { default as CSS_2 } from 'csstype'; import { CSSProperties } from 'react'; import { ElementType } from 'react'; @@ -876,6 +877,9 @@ export const ProxiedSignInPage: ( export type ProxiedSignInPageProps = SignInPageProps & { provider: string; headers?: HeadersInit | (() => HeadersInit) | (() => Promise); + ErrorComponent?: ComponentType<{ + error?: Error; + }>; }; // Warning: (ae-missing-release-tag) "ResponseErrorPanel" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1748,7 +1752,7 @@ export type WarningPanelClassKey = // src/layout/Sidebar/config.d.ts:8:1 - (ae-undocumented) Missing documentation for "SubmenuOptions". // src/layout/Sidebar/config.d.ts:12:22 - (ae-undocumented) Missing documentation for "sidebarConfig". // src/layout/Sidebar/config.d.ts:52:22 - (ae-undocumented) Missing documentation for "SIDEBAR_INTRO_LOCAL_STORAGE". -// src/layout/SignInPage/SignInPage.d.ts:17:1 - (ae-undocumented) Missing documentation for "SignInPage". +// src/layout/SignInPage/SignInPage.d.ts:26:1 - (ae-undocumented) Missing documentation for "SignInPage". // src/layout/SignInPage/customProvider.d.ts:3:1 - (ae-undocumented) Missing documentation for "CustomProviderClassKey". // src/layout/SignInPage/styles.d.ts:2:1 - (ae-undocumented) Missing documentation for "SignInPageClassKey". // src/layout/SignInPage/types.d.ts:3:1 - (ae-undocumented) Missing documentation for "SignInProviderConfig". diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.test.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.test.tsx index ad7e650d24..d0cfbbbe31 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.test.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.test.tsx @@ -19,9 +19,9 @@ import { render, screen } from '@testing-library/react'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { - TestApiProvider, mockApis, registerMswTestHooks, + TestApiProvider, wrapInTestApp, } from '@backstage/test-utils'; import { ProxiedSignInPage } from './ProxiedSignInPage'; @@ -99,4 +99,58 @@ describe('ProxiedSignInPage', () => { screen.findByText('Request failed with 401 Unauthorized'), ).resolves.toBeInTheDocument(); }); + + it('should allow custom error component', async () => { + const ErrorComponent = ({ error }: { error?: Error }) => ( + <> +

Failed to authenticate

+
{error?.message}
+ + ); + + const CustomSubject = wrapInTestApp(
authenticated
, { + components: { + SignInPage: props => ( + 'http://example.com/api/auth', + }, + ], + ]} + > + + + ), + }, + }); + + worker.use( + rest.get('http://example.com/api/auth/test/refresh', (_, res, ctx) => + res( + ctx.status(401), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + error: { name: 'Error', message: 'not-displayed' }, + }), + ), + ), + ); + + render(CustomSubject); + + await expect( + screen.findByText('Failed to authenticate'), + ).resolves.toBeInTheDocument(); + + await expect( + screen.findByText('Request failed with 401 Unauthorized'), + ).resolves.toBeInTheDocument(); + }); }); diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index df0d256312..01519ce0b3 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -19,7 +19,7 @@ import { SignInPageProps, useApi, } from '@backstage/core-plugin-api'; -import React from 'react'; +import React, { ComponentType } from 'react'; import { useAsync, useMountEffect } from '@react-hookz/web'; import { ErrorPanel } from '../../components/ErrorPanel'; import { Progress } from '../../components/Progress'; @@ -44,6 +44,12 @@ export type ProxiedSignInPageProps = SignInPageProps & { * underlying provider */ headers?: HeadersInit | (() => HeadersInit) | (() => Promise); + + /** + * Error component to be rendered instead of the default error panel in case + * sign in fails. + */ + ErrorComponent?: ComponentType<{ error?: Error }>; }; /** @@ -82,7 +88,11 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { if (status === 'loading') { return ; } else if (error) { - return ; + return props.ErrorComponent ? ( + + ) : ( + + ); } return null; diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index fb8f804d5e..3dcccb50df 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -24,7 +24,7 @@ import { UserIdentity } from './UserIdentity'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import React, { ReactNode, useState } from 'react'; +import React, { ComponentType, ReactNode, useState } from 'react'; import { useMountEffect } from '@react-hookz/web'; import { Progress } from '../../components/Progress'; import { Content } from '../Content/Content'; @@ -38,14 +38,22 @@ import { IdentityProviders, SignInProviderConfig } from './types'; import { coreComponentsTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -type MultiSignInPageProps = SignInPageProps & { +type CommonSignInPageProps = SignInPageProps & { + /** + * Error component to be rendered instead of the default error panel in case + * sign in fails. + */ + ErrorComponent?: ComponentType<{ error?: Error }>; +}; + +type MultiSignInPageProps = CommonSignInPageProps & { providers: IdentityProviders; title?: string; titleComponent?: ReactNode; align?: 'center' | 'left'; }; -type SingleSignInPageProps = SignInPageProps & { +type SingleSignInPageProps = CommonSignInPageProps & { provider: SignInProviderConfig; auto?: boolean; }; @@ -101,6 +109,7 @@ export const SingleSignInPage = ({ provider, auto, onSignInSuccess, + ErrorComponent, }: SingleSignInPageProps) => { const classes = useStyles(); const authApi = useApi(provider.apiRef); @@ -190,11 +199,15 @@ export const SingleSignInPage = ({ } > {provider.message} - {error && error.name !== 'PopupRejectedError' && ( - - {error.message} - - )} + {error && + error.name !== 'PopupRejectedError' && + (ErrorComponent ? ( + + ) : ( + + {error.message} + + ))}