diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e8bdb282..5c7bcd77b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to
- ✨(y-provider) preserve callouts, PDFs, page breaks, interlinking
links and commented text on HTML/markdown export #2296
+- ✨(frontend) add a user menu #2463
### Changed
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts
index 5ccee2192..a28440f44 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts
@@ -24,10 +24,11 @@ const saveStorageState = async (
await SignIn(page, browserName);
+ /**
+ * If the grid is displayed, it means the user is logged in and the storage state can be saved.
+ */
await expect(
- page.locator('header').first().getByRole('button', {
- name: 'Logout',
- }),
+ page.getByRole('heading', { name: 'All docs', level: 2 }),
).toBeVisible({ timeout: 10000 });
await page.context().storageState({
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
index 2b4f746a2..0b7ad3a9d 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
@@ -17,6 +17,7 @@ import {
updateRoleUser,
updateShareLink,
} from './utils-share';
+import { logOut } from './utils-signin';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -312,7 +313,7 @@ test.describe('Doc Comments', () => {
await updateShareLink(page, 'Public', 'Editing');
// Anonymous user can see and add comments
- await otherPage.getByRole('button', { name: 'Logout' }).click();
+ await logOut(otherPage);
await expect(
otherPage
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
index c6c804e8c..37817bc12 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
@@ -9,7 +9,7 @@ import {
} from './utils-common';
import { writeInEditor } from './utils-editor';
import { connectOtherUserToDoc, updateRoleUser } from './utils-share';
-import { SignIn } from './utils-signin';
+import { SignIn, logOut } from './utils-signin';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Document create member', () => {
@@ -371,11 +371,7 @@ test.describe('Document create member: Multiple login', () => {
const urlDoc = page.url();
- await page
- .getByRole('button', {
- name: 'Logout',
- })
- .click();
+ await logOut(page);
const otherBrowser = BROWSERS.find((b) => b !== browserName);
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts
index 060eb4a33..d6d4cfaaf 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts
@@ -407,21 +407,23 @@ test.describe('Doc Tree', () => {
await expect(selectedSubDoc).toBeFocused();
await page.keyboard.press('Tab');
+ await expect(page.getByLabel('User menu')).toBeFocused();
+ await page.keyboard.press('Tab');
await expect(page.getByLabel('Open help menu')).toBeFocused();
await page.keyboard.press('Tab');
-
await expect(
page.locator('[data-panel-resize-handle-id]').first(),
).toBeFocused();
await page.keyboard.press('Shift+Tab');
-
await expect(page.getByLabel('Open help menu')).toBeFocused();
await page.keyboard.press('Shift+Tab');
+ await expect(page.getByLabel('User menu')).toBeFocused();
+ await page.keyboard.press('Shift+Tab');
await expect(selectedSubDoc).toBeFocused();
});
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 6dca8b40b..548880600 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
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import { BROWSERS, createDoc, verifyDocName } from './utils-common';
import { getEditor, writeInEditor } from './utils-editor';
import { addNewMember, connectOtherUserToDoc } from './utils-share';
-import { SignIn, expectLoginPage } from './utils-signin';
+import { SignIn, expectLoginPage, logOut } from './utils-signin';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Doc Visibility', () => {
@@ -81,13 +81,7 @@ test.describe('Doc Visibility: Restricted', () => {
await verifyDocName(page, docTitle);
const urlDoc = page.url();
-
- await page
- .getByRole('button', {
- name: 'Logout',
- })
- .click();
-
+ await logOut(page);
await expectLoginPage(page);
await page.goto(urlDoc);
@@ -112,11 +106,7 @@ test.describe('Doc Visibility: Restricted', () => {
const urlDoc = page.url();
- await page
- .getByRole('button', {
- name: 'Logout',
- })
- .click();
+ await logOut(page);
const otherBrowser = BROWSERS.find((b) => b !== browserName);
if (!otherBrowser) {
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
index 2036733d8..cdeacf0df 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
@@ -1,7 +1,6 @@
import { expect, test } from '@playwright/test';
import { overrideConfig } from './utils-common';
-import { SignIn, expectLoginPage } from './utils-signin';
test.describe('Header', () => {
test('checks all the elements are visible', async ({ page }) => {
@@ -14,14 +13,6 @@ test.describe('Header', () => {
'font-family',
/Roboto/i,
);
-
- await expect(
- header.getByRole('button', {
- name: 'Logout',
- }),
- ).toBeVisible();
-
- await expect(header.getByText('English')).toBeVisible();
});
test('checks all the elements are visible with DSFR theme', async ({
@@ -106,21 +97,3 @@ test.describe('Header', () => {
await expect(logoImage).toHaveAttribute('alt', '');
});
});
-
-test.describe('Header: Log out', () => {
- test.use({ storageState: { cookies: [], origins: [] } });
-
- // eslint-disable-next-line playwright/expect-expect
- test('checks logout button', async ({ page, browserName }) => {
- await page.goto('/');
- await SignIn(page, browserName);
-
- await page
- .getByRole('button', {
- name: 'Logout',
- })
- .click();
-
- await expectLoginPage(page);
- });
-});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/language.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/language.spec.ts
index 56e20515e..51082d147 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/language.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/language.spec.ts
@@ -40,75 +40,71 @@ test.describe('Language', () => {
});
test('checks language switching', async ({ page }) => {
- const header = page.locator('header').first();
- const languagePicker = header.locator('.--docs--language-picker-text');
-
- await expect(page.locator('html')).toHaveAttribute('lang', 'en-us');
-
- // initial language should be english
- await expect(
- page.getByRole('link', {
- name: 'New',
- exact: true,
- }),
- ).toBeVisible();
-
// switch to french
await waitForLanguageSwitch(page, TestLanguage.French);
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
+ await page.getByLabel('Menu utilisateur').click();
await expect(
- header.getByRole('button').getByText('Français'),
+ page.getByRole('button', { name: 'Language: Français' }),
).toBeVisible();
- await expect(page.getByLabel('Se déconnecter')).toBeVisible();
+ await expect(page.getByText('Déconnexion')).toBeVisible();
+
+ await page.keyboard.press('Escape');
// Switch to German using the utility function for consistency
- await waitForLanguageSwitch(page, TestLanguage.German);
- await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible();
+ await waitForLanguageSwitch(page, TestLanguage.German, 'Menu utilisateur');
- await expect(page.getByLabel('Abmelden')).toBeVisible();
+ await page.getByLabel('User menu').click();
+ await expect(
+ page.getByRole('button', { name: 'Language: Deutsch' }),
+ ).toBeVisible();
+
+ await expect(page.getByText('Logout', { exact: true })).toBeVisible();
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
-
- await languagePicker.click();
-
- await expect(page.locator('[role="menu"]')).toBeVisible();
-
- const menuItems = page.locator('[role="menuitemradio"]');
- await expect(menuItems.first()).toBeVisible();
-
- await menuItems.first().click();
-
- await expect(page.locator('html')).toHaveAttribute('lang', 'en');
- await expect(languagePicker).toContainText('English');
});
test('can switch language using only keyboard', async ({ page }) => {
- await page.goto('/');
- await waitForLanguageSwitch(page, TestLanguage.English);
+ await waitForLanguageSwitch(page, TestLanguage.English, 'User menu', false);
- const languagePicker = page.getByRole('button', {
- name: /select language/i,
- });
-
- await expect(languagePicker).toBeVisible();
+ await page.getByLabel('User menu').click();
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
- await page.keyboard.press('Tab');
- await page.keyboard.press('Tab');
-
await page.keyboard.press('Enter');
- const menu = page.getByRole('menu');
- await expect(menu).toBeVisible();
+ await expect(page.getByRole('menuitem', { name: 'English' })).toBeVisible();
+ await expect(
+ page.getByRole('menuitem', { name: 'Français' }),
+ ).toBeVisible();
+
+ await page.waitForTimeout(300);
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Enter');
- await expect(page.locator('html')).not.toHaveAttribute('lang', 'en-us');
+ await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
+ await expect(
+ page.getByRole('button', { name: 'Déconnexion' }),
+ ).toBeVisible();
+
+ await page.keyboard.press('Escape');
+
+ await page.getByLabel('Menu utilisateur').click();
+
+ await page.keyboard.press('Tab');
+ await page.keyboard.press('Tab');
+ await page.keyboard.press('Enter');
+ await page.waitForTimeout(300);
+ await page.keyboard.press('ArrowDown');
+ await page.keyboard.press('ArrowUp');
+ await page.keyboard.press('Enter');
+
+ await expect(page.locator('html')).toHaveAttribute('lang', 'en');
+ await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible();
});
test('checks that backend uses the same language as the frontend', async ({
@@ -171,7 +167,7 @@ test.describe('Language', () => {
/**
* Swedish is not yet supported in the BlockNote locales, so it should fallback to English
*/
- await waitForLanguageSwitch(page, TestLanguage.Swedish);
+ await waitForLanguageSwitch(page, TestLanguage.Swedish, 'Menu utilisateur');
await openSuggestionMenu({ page });
await expect(
suggestionMenu.getByText('Headings', { exact: true }),
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts
index e3104b4bd..5a3eeddd2 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts
@@ -7,6 +7,7 @@ import {
verifyDocName,
} from './utils-common';
import { tryFocusEditorContent } from './utils-editor';
+import { SignIn, expectLoginPage, logOut } from './utils-signin';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Left panel desktop', () => {
@@ -19,6 +20,8 @@ test.describe('Left panel desktop', () => {
await expect(page.getByTestId('left-panel-mobile')).toBeHidden();
await expect(page.getByTestId('home-button')).toBeHidden();
await expect(page.getByTestId('new-doc-button')).toBeVisible();
+ await expect(page.getByLabel('User menu')).toBeVisible();
+ await expect(page.getByLabel('Open help menu')).toBeVisible();
await goToGridDoc(page);
@@ -178,6 +181,7 @@ test.describe('Left panel desktop', () => {
test.describe('Left panel responsive', () => {
test('checks elements visibility on different screen sizes', async ({
page,
+ browserName,
}) => {
await page.setViewportSize({ width: 500, height: 1200 });
await page.goto('/');
@@ -188,26 +192,22 @@ test.describe('Left panel responsive', () => {
const header = page.locator('header').first();
const homeButton = page.getByTestId('home-button');
const newDocButton = page.getByTestId('new-doc-button');
- const languageButton = page.getByRole('button', {
- name: 'Select language',
+ const userMenu = page.getByRole('button', {
+ name: 'User menu',
});
- const logoutButton = page.getByRole('button', { name: 'Logout' });
await expect(homeButton).not.toBeInViewport();
await expect(newDocButton).not.toBeInViewport();
- await expect(languageButton).not.toBeInViewport();
- await expect(logoutButton).not.toBeInViewport();
+ await expect(userMenu).not.toBeInViewport();
- const title = await goToGridDoc(page);
- await verifyDocName(page, title);
+ await createDoc(page, 'mobile-doc-test', browserName, 1, true);
await header.getByLabel('Open the header menu').click();
await expect(page.getByTestId('left-panel-mobile')).toBeInViewport();
await expect(homeButton).toBeInViewport();
await expect(newDocButton).toBeInViewport();
- await expect(languageButton).toBeInViewport();
- await expect(logoutButton).toBeInViewport();
+ await expect(userMenu).toBeInViewport();
await header.getByLabel('Close the header menu').click();
@@ -217,8 +217,7 @@ test.describe('Left panel responsive', () => {
await expect(page.getByRole('link', { name: 'All docs' })).toBeInViewport();
await expect(newDocButton).toBeInViewport();
- await expect(languageButton).toBeInViewport();
- await expect(logoutButton).toBeInViewport();
+ await expect(userMenu).toBeInViewport();
await expect(header.getByLabel('Open the header menu')).toBeHidden();
});
@@ -309,3 +308,16 @@ test.describe('Left panel responsive', () => {
await expect(leftPanel).toBeHidden();
});
});
+
+test.describe('Left Panel: Log out', () => {
+ test.use({ storageState: { cookies: [], origins: [] } });
+
+ // eslint-disable-next-line playwright/expect-expect
+ test('checks logout button', async ({ page, browserName }) => {
+ await page.goto('/');
+ await SignIn(page, browserName);
+ await logOut(page);
+
+ await expectLoginPage(page);
+ });
+});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
index 24b2c0b88..c880c3aa9 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
@@ -399,9 +399,11 @@ type TestLanguageValue = (typeof TestLanguage)[TestLanguageKey];
export async function waitForLanguageSwitch(
page: Page,
lang: TestLanguageValue,
+ labelUserMenu = 'User menu',
+ withMocking = true,
) {
await page.route(/\**\/api\/v1.0\/users\/\**/, async (route, request) => {
- if (request.method().includes('PATCH')) {
+ if (request.method().includes('PATCH') && withMocking) {
await route.fulfill({
json: {
language: lang.expectedLocale[0],
@@ -412,19 +414,21 @@ export async function waitForLanguageSwitch(
}
});
- const header = page.locator('header').first();
- const languagePicker = header.locator('.--docs--language-picker-text');
+ await page.getByLabel(labelUserMenu).click();
+ const languagePicker = page.getByRole('button', { name: /Language/ });
const isAlreadyTargetLanguage = await languagePicker
.innerText()
.then((text) => text.toLowerCase().includes(lang.label.toLowerCase()));
if (isAlreadyTargetLanguage) {
+ await page.keyboard.press('Escape');
return;
}
await languagePicker.click();
- await page.getByRole('menuitemradio', { name: lang.label }).click();
+ await page.getByRole('menuitem', { name: lang.label }).click();
+ await page.keyboard.press('Escape');
}
export const clickInEditorShareButton = async (page: Page) => {
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
index a3331011f..1c7efeadf 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
@@ -13,6 +13,11 @@ export const SignIn = async (
await keycloakSignIn(page, browserName, fromHome);
};
+export const logOut = async (page: Page) => {
+ await page.getByLabel('User menu').click();
+ await page.getByText('Logout', { exact: true }).click();
+};
+
export const customSignIn = async (
page: Page,
browserName: string,
diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index fd0ee4682..8572c1412 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -42,7 +42,7 @@
"@fontsource/material-icons": "5.2.7",
"@gouvfr-lasuite/cunningham-react": "4.3.0",
"@gouvfr-lasuite/integration": "1.0.3",
- "@gouvfr-lasuite/ui-kit": "0.23.2",
+ "@gouvfr-lasuite/ui-kit": "0.25.0",
"@hocuspocus/provider": "3.4.4",
"@mantine/core": "9.3.1",
"@mantine/hooks": "9.3.1",
diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
index 7a9b67987..f995bee96 100644
--- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
+++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
@@ -838,6 +838,9 @@
--c--components--button--border-radius: 4px;
--c--components--button--border-radius--active: 4px;
--c--components--button--border-radius--focus: 4px;
+ --c--components--resize-handle--hover--color: var(
+ --c--contextuals--border--surface--primary
+ );
--c--components--datagrid--header--color: var(
--c--contextuals--content--semantic--neutral--primary
);
@@ -2539,6 +2542,9 @@
--c--components--button--border-radius: 4px;
--c--components--button--border-radius--active: 4px;
--c--components--button--border-radius--focus: 4px;
+ --c--components--resize-handle--hover--color: var(
+ --c--contextuals--border--surface--primary
+ );
--c--components--datagrid--header--color: var(
--c--contextuals--content--semantic--neutral--primary
);
diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
index b6c84c50b..1e5fb8bdf 100644
--- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
+++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
@@ -657,6 +657,7 @@ export const tokens = {
'border-radius--active': '4px',
'border-radius--focus': '4px',
},
+ 'resize-handle': { 'hover--color': '#E2E2EA' },
datagrid: {
'header--color': '#25252F',
'header--size': '12px',
@@ -1961,6 +1962,7 @@ export const tokens = {
'border-radius--active': '4px',
'border-radius--focus': '4px',
},
+ 'resize-handle': { 'hover--color': '#E2E2EA' },
datagrid: {
'header--color': '#25252F',
'header--size': '12px',
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 b4666f47a..2d5e5da18 100644
--- a/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx
+++ b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx
@@ -2,51 +2,30 @@ import { Button } from '@gouvfr-lasuite/cunningham-react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
-import { Box, BoxButton } from '@/components';
-import { useCunninghamTheme } from '@/cunningham';
+import { BoxButton } from '@/components';
import ProConnectImg from '../assets/button-proconnect.svg';
import { useAuth } from '../hooks';
-import { gotoLogin, gotoLogout } from '../utils';
+import { gotoLogin } from '../utils';
export const ButtonLogin = () => {
const { t } = useTranslation();
const { authenticated } = useAuth();
- const { colorsTokens } = useCunninghamTheme();
- if (!authenticated) {
- return (
-
- );
+ if (authenticated) {
+ return null;
}
return (
- gotoLogin()}
+ color="brand"
+ size="small"
+ aria-label={t('Sign in')}
+ className="--docs--button-login"
>
-
-
+ {t('Sign in')}
+
);
};
diff --git a/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx b/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx
index 0663a99d9..a7a6e11d5 100644
--- a/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx
+++ b/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx
@@ -8,14 +8,14 @@ import { useAuthQuery } from '../api';
const regexpUrlsAuth = [/\/docs\/$/g, /\/docs$/g, /^\/$/g];
export const useAuth = () => {
- const { data: user, ...authStates } = useAuthQuery();
+ const { data: user, isFetched, isLoading, isSuccess } = useAuthQuery();
const { pathname } = useRouter();
const { trackEvent } = useAnalytics();
- const [hasTracked, setHasTracked] = useState(authStates.isFetched);
- const isAuthLoading =
- authStates.fetchStatus !== 'idle' || authStates.isLoading;
+ const [hasTracked, setHasTracked] = useState(isFetched);
+ const isAuthLoading = isLoading;
+
const hasInitiallyLoaded = useRef(false);
- if (authStates.isFetched) {
+ if (isFetched) {
hasInitiallyLoaded.current = true;
}
const [pathAllowed, setPathAllowed] = useState(
@@ -27,7 +27,7 @@ export const useAuth = () => {
}, [pathname]);
useEffect(() => {
- if (!hasTracked && user && authStates.isSuccess) {
+ if (!hasTracked && user && isSuccess) {
trackEvent({
eventName: 'user',
id: user?.id || '',
@@ -35,14 +35,13 @@ export const useAuth = () => {
});
setHasTracked(true);
}
- }, [hasTracked, authStates.isSuccess, user, trackEvent]);
+ }, [hasTracked, isSuccess, user, trackEvent]);
return {
user,
- authenticated: !!user && authStates.isSuccess,
+ authenticated: !!user && isSuccess,
pathAllowed,
hasInitiallyLoaded: hasInitiallyLoaded.current,
isAuthLoading,
- ...authStates,
};
};
diff --git a/src/frontend/apps/impress/src/features/header/components/Header.tsx b/src/frontend/apps/impress/src/features/header/components/Header.tsx
index 4ce20a093..ae97f09cc 100644
--- a/src/frontend/apps/impress/src/features/header/components/Header.tsx
+++ b/src/frontend/apps/impress/src/features/header/components/Header.tsx
@@ -6,7 +6,6 @@ import { Box, SkipToContent, StyledLink } from '@/components/';
import { useConfig } from '@/core/config';
import { useCunninghamTheme } from '@/cunningham';
import { ButtonLogin } from '@/features/auth';
-import { LanguagePicker } from '@/features/language';
import { LeftPanelToggleMobile } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
@@ -89,7 +88,6 @@ export const Header = () => {
$direction="row"
>
-
)}
diff --git a/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx b/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx
index 0c854c1bf..e33746354 100644
--- a/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx
+++ b/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx
@@ -5,7 +5,7 @@ import { Waffle } from '@/components/Waffle';
import { useConfig } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { Title } from '@/features/header';
-import { LanguagePicker } from '@/features/language';
+import { LanguagePickerLegacy } from '@/features/language';
import { LeftPanelToggleMobile } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
@@ -80,7 +80,7 @@ export const HomeHeader = () => {
{!isSmallMobile && (
-
+
)}
diff --git a/src/frontend/apps/impress/src/features/language/components/LanguagePicker.tsx b/src/frontend/apps/impress/src/features/language/components/LanguagePicker.tsx
index 59655ca59..75b219fff 100644
--- a/src/frontend/apps/impress/src/features/language/components/LanguagePicker.tsx
+++ b/src/frontend/apps/impress/src/features/language/components/LanguagePicker.tsx
@@ -1,3 +1,7 @@
+import {
+ LanguagePicker as LanguagePickerUIKit,
+ LanguagesOption,
+} from '@gouvfr-lasuite/ui-kit';
import { announce } from '@react-aria/live-announcer';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -11,7 +15,13 @@ import {
useSynchronizedLanguage,
} from '@/features/language';
-export const LanguagePicker = () => {
+/**
+ * LanguagePickerLegacy component for selecting language.
+ * We still have some legacy code that uses this component, so we keep it for now.
+ * @deprecated Use LanguagePicker instead.
+ * @returns JSX.Element
+ */
+export const LanguagePickerLegacy = () => {
const { t, i18n } = useTranslation();
const { data: conf } = useConfig();
const { data: user } = useAuthQuery();
@@ -85,3 +95,43 @@ export const LanguagePicker = () => {
);
};
+
+export const LanguagePicker = () => {
+ const { t, i18n } = useTranslation();
+ const { data: conf } = useConfig();
+ const { data: user } = useAuthQuery();
+ const { changeLanguageSynchronized } = useSynchronizedLanguage();
+ const language = i18n.language;
+
+ const languages: LanguagesOption[] = useMemo(() => {
+ const backendOptions = conf?.LANGUAGES ?? [[language, language]];
+ return backendOptions.map(([backendLocale, backendLabel]) => ({
+ label: backendLabel,
+ value: backendLocale,
+ isChecked: getMatchingLocales([backendLocale], [language]).length > 0,
+ }));
+ }, [conf?.LANGUAGES, language]);
+
+ const onChange = (value: string) => {
+ const lang = conf?.LANGUAGES?.find(([code]) => code === value);
+ const backendLabel = lang?.[1] ?? value;
+ void changeLanguageSynchronized(value, user).then(() => {
+ announce(
+ t('Language changed to {{language}}', {
+ language: backendLabel,
+ defaultValue: `Language changed to ${backendLabel}`,
+ }),
+ 'polite',
+ );
+ });
+ };
+
+ return (
+
+ );
+};
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 5c6e762db..54082afac 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,12 +1,24 @@
+import { UserMenu } from '@gouvfr-lasuite/ui-kit';
+import { useTranslation } from 'react-i18next';
+
import { Box, SeparatedSection } from '@/components';
import { Waffle } from '@/components/Waffle';
import { ButtonLogin } from '@/features/auth';
+import { useAuth } from '@/features/auth/hooks/useAuth';
+import { gotoLogout } from '@/features/auth/utils';
import { HelpMenu } from '@/features/help';
-import { LanguagePicker } from '@/features/language';
+import { LanguagePicker } from '@/features/language/components/LanguagePicker';
import { useResponsiveStore } from '@/stores';
export const LeftPanelFooter = () => {
const { isLargeScreen } = useResponsiveStore();
+ const { t } = useTranslation();
+ const { user } = useAuth();
+
+ const userMenu = user || {
+ full_name: t('Guest'),
+ email: '',
+ };
return (
@@ -15,14 +27,15 @@ export const LeftPanelFooter = () => {
$justify="space-between"
$direction="row"
>
-
+
+ }
+ withMobileView={false}
+ />
- {!isLargeScreen && (
- <>
-
-
- >
- )}
+ {!isLargeScreen && }
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index fb673957b..49e02c248 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -2128,10 +2128,10 @@
resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/integration/-/integration-1.0.3.tgz#7aca824ba61d343a7905dc90c8a8bbdbce8f9a09"
integrity sha512-OgP28CqlPi35wQPul1Dr52SngACXAk8buLGqHYXDp23fbTOJThqarrZE/pgJHoc9Ndwiu7ngwBSO4rZ7OPyMpA==
-"@gouvfr-lasuite/ui-kit@0.23.2":
- version "0.23.2"
- resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/ui-kit/-/ui-kit-0.23.2.tgz#9a7531fb35821b75b786bfc3167ad53aab116c4c"
- integrity sha512-o6bQjqJadBbWMbFiwDYwVeNKYAD/EGeU2W0/omqNgTEYWjKQCGYwgcxgeYR+SZN9GnWKCXEARs18aYgBl+oRbQ==
+"@gouvfr-lasuite/ui-kit@0.25.0":
+ version "0.25.0"
+ resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/ui-kit/-/ui-kit-0.25.0.tgz#9feb7da860ea610cb5f171b90ee074fe39271940"
+ integrity sha512-yvP1/rpckaDPFGcuNiZqwUn43asnSq8ffS660Oikxrq/m+/5oMtRsApHjkt2Wtn1HfUWGvqazN8wSrr1yEwG0Q==
dependencies:
"@dnd-kit/core" "6.3.1"
"@dnd-kit/modifiers" "9.0.0"
@@ -8323,11 +8323,6 @@ crelt@^1.0.0:
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
-crisp-sdk-web@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/crisp-sdk-web/-/crisp-sdk-web-1.1.2.tgz#e5b31ee784cef678a711170f54eca68e28968a22"
- integrity sha512-qxSpiT4EGHnVEO6J/t1UURY6fg1ojgquLU0IIr7RNjZxXZs1R2e6AhAiFqxiGZ13Vb4o5VoUpATFU2TzX/ppOQ==
-
cross-env@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783"