diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d62d79b3..6748cc4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ and this project adheres to `/external_api/{version}/jwks` - 🔧(collaboration) adapt docker stack for development purpose - 🔧(helm) run a valkey for the backend and one for yhub in dev and feature +- 💄(frontend) redesign 401 error standalone page #2716 ### Fixed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/401.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/401.spec.ts new file mode 100644 index 000000000..0252cf0fc --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/401.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; + +test.describe('401', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/401'); + + await expect( + page.getByRole('heading', { + level: 1, + name: 'Please sign in', + }), + ).toBeVisible(); + await expect( + page.getByText('You need to sign in before accessing the document'), + ).toBeVisible(); + await expect(page.getByTestId('header-logo-link')).toBeVisible(); + await expect(page.getByRole('link', { name: 'Try it now' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Sign in' })).toHaveCount(2); + await expect(page).toHaveTitle(/401 Unauthorized - Docs/); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts index 7d79f05f3..9ec83ffc8 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts @@ -132,7 +132,9 @@ test.describe('Doc Routing', () => { await page.reload(); - await expect(page.getByText('Log in to access the document.')).toBeVisible({ + await expect( + page.getByText('You need to sign in before accessing the document'), + ).toBeVisible({ timeout: 10000, }); @@ -196,7 +198,7 @@ test.describe('Doc Routing: Not logged', () => { await expect( page2 .getByRole('main', { name: 'Main content' }) - .getByRole('button', { name: 'Login' }), + .getByRole('link', { name: 'Sign in' }), ).toBeVisible({ timeout: 10000, }); @@ -206,7 +208,7 @@ test.describe('Doc Routing: Not logged', () => { await expect( page .getByRole('main', { name: 'Main content' }) - .getByRole('button', { name: 'Login' }), + .getByRole('link', { name: 'Sign in' }), ).toBeVisible({ timeout: 10000, }); @@ -214,7 +216,7 @@ test.describe('Doc Routing: Not logged', () => { // Reconnected await page .getByRole('main', { name: 'Main content' }) - .getByRole('button', { name: 'Login' }) + .getByRole('link', { name: 'Sign in' }) .click(); await SignIn(page, browserName, false); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts index f9a0fab20..e7e7e915b 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts @@ -87,7 +87,7 @@ test.describe('Doc Visibility: Restricted', () => { await page.goto(urlDoc); await expect( - page.getByText('Log in to access the document.'), + page.getByText('You need to sign in before accessing the document'), ).toBeVisible(); }); @@ -361,7 +361,7 @@ test.describe('Doc Visibility: Authenticated', () => { await expect(otherPage.locator('h2').getByText(docTitle)).toBeHidden(); await expect( - otherPage.getByText('Log in to access the document.'), + otherPage.getByText('You need to sign in before accessing the document'), ).toBeVisible(); await cleanup(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts index 69dc4c515..757f6995d 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts @@ -129,7 +129,7 @@ export const connectOtherUserToDoc = async ({ if (!withoutSignIn) { const loginFromApp = otherPage .getByRole('main', { name: 'Main content' }) - .getByLabel('Login'); + .getByRole('link', { name: 'Sign in' }); const loginFromHome = otherPage.getByRole('button', { name: process.env.SIGN_IN_EL_TRIGGER, }); diff --git a/src/frontend/apps/impress/src/assets/icons/error-401.svg b/src/frontend/apps/impress/src/assets/icons/error-401.svg new file mode 100644 index 000000000..e910ada1d --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/error-401.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx index f7103be54..3268e83be 100644 --- a/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx +++ b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx @@ -1,16 +1,29 @@ import { Button } from '@gouvfr-lasuite/ui-components'; +import { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { BoxButton } from '@/components'; import ProConnectImg from '../assets/button-proconnect.svg'; +import { LOGIN_URL } from '../conf'; import { useAuth } from '../hooks'; import { gotoLogin } from '../utils'; -export const ButtonLogin = () => { +type ButtonLoginProps = { + href?: string; + variant?: 'primary' | 'tertiary'; + children?: ReactNode; +}; + +export const ButtonLogin = ({ + href = LOGIN_URL, + variant = 'primary', + children, +}: ButtonLoginProps) => { const { t } = useTranslation(); const { authenticated } = useAuth(); + const label = children ?? t('Sign in'); if (authenticated) { return null; @@ -18,13 +31,14 @@ export const ButtonLogin = () => { return ( ); }; diff --git a/src/frontend/apps/impress/src/features/footer/components/FooterActions.tsx b/src/frontend/apps/impress/src/features/footer/components/FooterActions.tsx index 1640929b9..ec2aff180 100644 --- a/src/frontend/apps/impress/src/features/footer/components/FooterActions.tsx +++ b/src/frontend/apps/impress/src/features/footer/components/FooterActions.tsx @@ -1,10 +1,11 @@ import { UserMenu } from '@gouvfr-lasuite/ui-components'; +import { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { createGlobalStyle } from 'styled-components'; import { Box } from '@/components'; import { Waffle } from '@/components/Waffle'; -import { ButtonLogin, gotoLogout, useAuth } from '@/features/auth'; +import { gotoLogout, useAuth } from '@/features/auth'; import { HelpMenu } from '@/features/help'; import { LanguagePicker } from '@/features/language/components/LanguagePicker'; @@ -14,7 +15,11 @@ const FooterActionsGlobalStyle = createGlobalStyle` } `; -export const FooterActions = () => { +type FooterActionsProps = { + loginAction?: ReactNode; +}; + +export const FooterActions = ({ loginAction }: FooterActionsProps) => { const { t } = useTranslation(); const { user } = useAuth(); @@ -42,7 +47,7 @@ export const FooterActions = () => { withMobileView={false} /> - + {loginAction} diff --git a/src/frontend/apps/impress/src/features/header/components/HeaderBar.tsx b/src/frontend/apps/impress/src/features/header/components/HeaderBar.tsx index 1978439ab..208de1af1 100644 --- a/src/frontend/apps/impress/src/features/header/components/HeaderBar.tsx +++ b/src/frontend/apps/impress/src/features/header/components/HeaderBar.tsx @@ -1,22 +1,30 @@ +import { ReactNode } from 'react'; + import { Box } from '@/components'; import { HEADER_ROW_MIN_HEIGHT } from '../conf'; import { HeaderLogoLink } from './HeaderLogoLink'; -export const HeaderBar = () => { +type HeaderBarProps = { + actions?: ReactNode; +}; + +export const HeaderBar = ({ actions }: HeaderBarProps) => { return ( + {actions} ); }; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFooter.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFooter.tsx index 56733d35c..5bac7288c 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFooter.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFooter.tsx @@ -1,10 +1,11 @@ import { SeparatedSection } from '@/components'; +import { ButtonLogin } from '@/features/auth'; import { FooterActions } from '@/features/footer'; export const LeftPanelFooter = () => { return ( - + } /> ); }; diff --git a/src/frontend/apps/impress/src/layouts/StandalonePageLayout.tsx b/src/frontend/apps/impress/src/layouts/StandalonePageLayout.tsx index fb6bb2097..e097b64af 100644 --- a/src/frontend/apps/impress/src/layouts/StandalonePageLayout.tsx +++ b/src/frontend/apps/impress/src/layouts/StandalonePageLayout.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, ReactNode } from 'react'; import { Box } from '@/components'; import { FooterBar } from '@/features/footer'; @@ -6,14 +6,21 @@ import { HeaderBar } from '@/features/header'; import { MainContent } from './MainLayout'; +type StandalonePageLayoutProps = PropsWithChildren<{ + headerActions?: ReactNode; +}>; + /** * Layout for pages reached outside of the application shell (email links, * error pages): no left panel, a slim header and footer around the content. */ -export function StandalonePageLayout({ children }: PropsWithChildren) { +export function StandalonePageLayout({ + children, + headerActions, +}: StandalonePageLayoutProps) { return ( - + {children} diff --git a/src/frontend/apps/impress/src/pages/401.tsx b/src/frontend/apps/impress/src/pages/401.tsx index 184092bf5..87b8fac12 100644 --- a/src/frontend/apps/impress/src/pages/401.tsx +++ b/src/frontend/apps/impress/src/pages/401.tsx @@ -1,20 +1,30 @@ -import { Button } from '@gouvfr-lasuite/ui-components'; import Head from 'next/head'; -import Image from 'next/image'; import { useRouter } from 'next/router'; import { ReactElement, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import img401 from '@/assets/icons/icon-401.png'; +import Error401Svg from '@/assets/icons/error-401.svg'; import { Box, Text } from '@/components'; -import { gotoLogin, useAuth } from '@/features/auth'; -import { PageLayout } from '@/layouts'; +import { ButtonLogin, useAuth } from '@/features/auth'; +import { StandalonePageLayout } from '@/layouts'; import { NextPageWithLayout } from '@/types/next'; +const HeaderAuthActions = () => { + const { t } = useTranslation(); + + return ( + + {t('Try it now')} + + + ); +}; + const Page: NextPageWithLayout = () => { const { t } = useTranslation(); const { authenticated } = useAuth(); const { replace } = useRouter(); + const pageTitle = `${t('401 Unauthorized')} - ${t('Docs')}`; useEffect(() => { if (authenticated) { @@ -26,49 +36,52 @@ const Page: NextPageWithLayout = () => { <> - {`${t('401 Unauthorized')} - ${t('Docs')}`} - + {pageTitle} + - - {t('401 Unauthorized')} - {t('Docs')} - - - - - - {t('Log in to access the document.')} + + + ); }; Page.getLayout = function getLayout(page: ReactElement) { - return {page}; + return ( + }> + {page} + + ); }; export default Page;