mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
✨(frontend) add a user menu
We are now using the UserMenu component from the ui-kit. It is a dropdown displaying the user's name and email, along with a logout button and a language picker. To fit the design of the user menu, we adapted the LanguagePicker adn are now using the ui-kit language picker component.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 (
|
||||
<Button
|
||||
onClick={() => gotoLogin()}
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
aria-label={t('Login')}
|
||||
className="--docs--button-login"
|
||||
>
|
||||
{t('Login')}
|
||||
</Button>
|
||||
);
|
||||
if (authenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
.--docs--button-logout:focus-visible {
|
||||
box-shadow: 0 0 0 2px ${colorsTokens['brand-400']} !important;
|
||||
border-radius: var(--c--globals--spacings--st);
|
||||
}
|
||||
`}
|
||||
<Button
|
||||
onClick={() => gotoLogin()}
|
||||
color="brand"
|
||||
size="small"
|
||||
aria-label={t('Sign in')}
|
||||
className="--docs--button-login"
|
||||
>
|
||||
<Button
|
||||
onClick={gotoLogout}
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
aria-label={t('Logout')}
|
||||
className="--docs--button-logout"
|
||||
>
|
||||
{t('Logout')}
|
||||
</Button>
|
||||
</Box>
|
||||
{t('Sign in')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<boolean>(
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -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 = () => {
|
||||
</Box>
|
||||
{!isSmallMobile && (
|
||||
<Box $direction="row" $gap="1rem" $align="center">
|
||||
<LanguagePicker />
|
||||
<LanguagePickerLegacy />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -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 = () => {
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<LanguagePickerUIKit
|
||||
languages={languages}
|
||||
size="small"
|
||||
onChange={onChange}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<SeparatedSection showSeparator="top">
|
||||
@@ -15,14 +27,15 @@ export const LeftPanelFooter = () => {
|
||||
$justify="space-between"
|
||||
$direction="row"
|
||||
>
|
||||
<Box $direction="row">
|
||||
<Box $direction="row" $align="center" $gap="0.2rem">
|
||||
<UserMenu
|
||||
user={userMenu}
|
||||
logout={user ? gotoLogout : undefined}
|
||||
actions={<LanguagePicker />}
|
||||
withMobileView={false}
|
||||
/>
|
||||
<Waffle />
|
||||
{!isLargeScreen && (
|
||||
<>
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
</>
|
||||
)}
|
||||
{!isLargeScreen && <ButtonLogin />}
|
||||
</Box>
|
||||
<HelpMenu />
|
||||
</Box>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user