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 54bd64cc..00000000 Binary files a/src/frontend/apps/impress/src/features/header/assets/icon-my-account.png and /dev/null differ