From 004a4edfe753d1921570e6e3e1baaa85d953596e Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 14 May 2024 13:08:21 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20logout=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the header based on latest Johann's design, which introduced a dropdown menu to manage user account. In this menu, you can find a logout button, which ends up the backend session by calling the logout endpoint. Please that automatic redirection when receiving the backend response were disabled. We handle it in our custom hook, which reload the page. Has the session cookie have been cleared, on reloading the page, a new loggin flow is initiated, and the user is redirected to the OIDC provider. --- .../e2e/__tests__/app-impress/header.spec.ts | 16 +++++++++ .../src/api/__tests__/fetchApi.test.tsx | 20 +++-------- src/frontend/apps/impress/src/api/fetchApi.ts | 6 +--- .../impress/src/components/DropButton.tsx | 4 +++ .../impress/src/core/auth/useAuthStore.tsx | 9 ++--- .../src/features/header/AccountDropdown.tsx | 34 ++++++++++++++++++ .../impress/src/features/header/Header.tsx | 4 ++- .../header/assets/icon-my-account.png | Bin 7957 -> 0 bytes 8 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/header/AccountDropdown.tsx delete mode 100644 src/frontend/apps/impress/src/features/header/assets/icon-my-account.png 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 0dfb32ac..7e7326ff 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts @@ -64,4 +64,20 @@ test.describe('Header', () => { await expect(page.getByRole('link', { name: 'Grist' })).toBeVisible(); }); + + test('checks logout button', async ({ page }) => { + await page + .getByRole('button', { + name: 'My account', + }) + .click(); + + await page + .getByRole('button', { + name: 'Logout', + }) + .click(); + + await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + }); }); diff --git a/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx b/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx index 1a1fe346..da7cef7b 100644 --- a/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx +++ b/src/frontend/apps/impress/src/api/__tests__/fetchApi.test.tsx @@ -31,25 +31,15 @@ describe('fetchAPI', () => { }); it('logout if 401 response', async () => { - const mockReplace = jest.fn(); - Object.defineProperty(window, 'location', { - configurable: true, - enumerable: true, - value: { - replace: mockReplace, - }, - }); - - useAuthStore.setState({ userData: { email: 'test@test.com', id: '1234' } }); + const logoutMock = jest.fn(); + jest + .spyOn(useAuthStore.getState(), 'logout') + .mockImplementation(logoutMock); fetchMock.mock('http://test.jest/api/some/url', 401); await fetchAPI('some/url'); - expect(useAuthStore.getState().userData).toBeUndefined(); - - expect(mockReplace).toHaveBeenCalledWith( - 'http://test.jest/api/authenticate/', - ); + expect(logoutMock).toHaveBeenCalled(); }); }); diff --git a/src/frontend/apps/impress/src/api/fetchApi.ts b/src/frontend/apps/impress/src/api/fetchApi.ts index 4513e9bc..fc224367 100644 --- a/src/frontend/apps/impress/src/api/fetchApi.ts +++ b/src/frontend/apps/impress/src/api/fetchApi.ts @@ -1,4 +1,4 @@ -import { baseApiUrl, login, useAuthStore } from '@/core'; +import { baseApiUrl, useAuthStore } from '@/core'; /** * Retrieves the CSRF token from the document's cookies. @@ -29,12 +29,8 @@ export const fetchAPI = async (input: string, init?: RequestInit) => { }, }); - // todo - handle 401, redirect to login screen - // todo - please have a look to this documentation page https://mozilla-django-oidc.readthedocs.io/en/stable/xhr.html if (response.status === 401) { logout(); - // Fix - force re-logging the user, will be refactored - login(); } return response; diff --git a/src/frontend/apps/impress/src/components/DropButton.tsx b/src/frontend/apps/impress/src/components/DropButton.tsx index 2493c9c9..f79b78e6 100644 --- a/src/frontend/apps/impress/src/components/DropButton.tsx +++ b/src/frontend/apps/impress/src/components/DropButton.tsx @@ -23,6 +23,10 @@ const StyledButton = styled(Button)` background: none; outline: none; transition: all 0.2s ease-in-out; + font-family: Marianne, Arial, serif; + font-weight: 500; + font-size: 0.938rem; + text-wrap: nowrap; `; interface DropButtonProps { diff --git a/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx b/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx index b1f3c56d..90dde94f 100644 --- a/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx +++ b/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx @@ -4,10 +4,6 @@ import { baseApiUrl } from '@/core/conf'; import { User, getMe } from './api'; -export const login = () => { - window.location.replace(new URL('authenticate/', baseApiUrl()).href); -}; - interface AuthStore { authenticated: boolean; initAuth: () => void; @@ -30,11 +26,10 @@ export const useAuthStore = create((set) => ({ set({ authenticated: true, userData: data }); }) .catch(() => { - // todo - implement a proper login screen to prevent automatic navigation. - login(); + window.location.replace(new URL('authenticate/', baseApiUrl()).href); }); }, logout: () => { - set(initialState); + window.location.replace(new URL('logout/', baseApiUrl()).href); }, })); diff --git a/src/frontend/apps/impress/src/features/header/AccountDropdown.tsx b/src/frontend/apps/impress/src/features/header/AccountDropdown.tsx new file mode 100644 index 00000000..13c77b5c --- /dev/null +++ b/src/frontend/apps/impress/src/features/header/AccountDropdown.tsx @@ -0,0 +1,34 @@ +import { Button } from '@openfun/cunningham-react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Box, DropButton, Text } from '@/components'; +import { useAuthStore } from '@/core/auth'; + +export const AccountDropdown = () => { + const { t } = useTranslation(); + const { logout } = useAuthStore(); + + return ( + + {t('My account')} + + arrow_drop_down + + + } + > + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/header/Header.tsx b/src/frontend/apps/impress/src/features/header/Header.tsx index 8b79e634..66f496d8 100644 --- a/src/frontend/apps/impress/src/features/header/Header.tsx +++ b/src/frontend/apps/impress/src/features/header/Header.tsx @@ -9,6 +9,7 @@ import { Box, StyledLink, Text } from '@/components/'; import { LanguagePicker } from '../language/'; +import { AccountDropdown } from './AccountDropdown'; import { LaGaufre } from './LaGaufre'; import { default as IconImpress } from './assets/icon-impress.svg?url'; @@ -59,7 +60,8 @@ export const Header = () => { - + + diff --git a/src/frontend/apps/impress/src/features/header/assets/icon-my-account.png b/src/frontend/apps/impress/src/features/header/assets/icon-my-account.png deleted file mode 100644 index 54bd64cc7e3f92769d86ec0fe7a06f0ac8980b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7957 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