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.')}
+
+
+
+ {t('Please sign in')}
+
+
+ {t('You need to sign in before accessing the document')}
-
-
+
>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
- return {page};
+ return (
+ }>
+ {page}
+
+ );
};
export default Page;