diff --git a/CHANGELOG.md b/CHANGELOG.md index e7b8e01a..0e07df1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ and this project adheres to ## [Unreleased] ## Added + - 📝(doc) Add security.md and codeofconduct.md #604 +- ✨(frontend) add home page #553 + ## Fixed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts new file mode 100644 index 00000000..5299d017 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts @@ -0,0 +1,49 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('/docs/'); +}); + +test.describe('Home page', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + test('checks all the elements are visible', async ({ page }) => { + // Check header content + const header = page.locator('header').first(); + const footer = page.locator('footer').first(); + await expect(header).toBeVisible(); + await expect( + header.getByRole('combobox', { name: 'Language' }), + ).toBeVisible(); + await expect( + header.getByRole('button', { name: 'Les services de La Suite numé' }), + ).toBeVisible(); + await expect( + header.getByRole('img', { name: 'Gouvernement Logo' }), + ).toBeVisible(); + await expect( + header.getByRole('img', { name: 'Docs app logo' }), + ).toBeVisible(); + await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible(); + await expect(header.getByText('BETA')).toBeVisible(); + + // Check the ttile and subtitle are visible + await expect(page.getByText('Collaborative writing made')).toBeVisible(); + await expect(page.getByText('Collaborate and write in real')).toBeVisible(); + await expect(page.getByText('An uncompromising writing')).toBeVisible(); + await expect(page.getByText('Docs offers an intuitive')).toBeVisible(); + await expect(page.getByText('Simple and secure')).toBeVisible(); + await expect(page.getByText('Docs makes real-time')).toBeVisible(); + await expect(page.getByText('Flexible export.')).toBeVisible(); + await expect(page.getByText('To facilitate the circulation')).toBeVisible(); + await expect(page.getByText('A new way to organize')).toBeVisible(); + await expect(page.getByText('Docs transforms your')).toBeVisible(); + + await expect(page.getByTestId('proconnect-button')).toHaveCount(2); + + // Footer - The footer is already tested in its entirety in the footer.spec.ts file + await expect(footer).toBeVisible(); + await expect( + page.getByRole('link', { name: 'expand_more See more' }), + ).toBeVisible(); + }); +}); diff --git a/src/frontend/apps/impress/cunningham.ts b/src/frontend/apps/impress/cunningham.ts index a324b170..633e2db9 100644 --- a/src/frontend/apps/impress/cunningham.ts +++ b/src/frontend/apps/impress/cunningham.ts @@ -5,6 +5,7 @@ const config = { colors: { 'card-border': '#ededed', 'primary-bg': '#FAFAFA', + 'primary-action': '#1212FF', 'primary-050': '#F5F5FE', 'primary-100': '#EDF5FA', 'primary-150': '#E5EEFA', @@ -59,6 +60,11 @@ const config = { h4: '1.375rem', h5: '1.25rem', h6: '1.125rem', + 'xl-alt': '5rem', + 'lg-alt': '4.5rem', + 'md-alt': '4rem', + 'sm-alt': '3.5rem', + 'xs-alt': '3rem', }, weights: { thin: 100, @@ -224,7 +230,7 @@ const config = { 'color-hover': 'var(--c--theme--colors--primary-700)', }, border: { - color: 'var(--c--theme--colors--primary-200)', + color: 'var(--c--theme--colors--greyscale-300)', }, }, tertiary: { @@ -379,8 +385,8 @@ const config = { 'color-active': '#EDEDED', }, border: { - color: 'var(--c--theme--colors--primary-600)', - 'color-hover': 'var(--c--theme--colors--primary-600)', + color: 'var(--c--theme--colors--greyscale-300)', + 'color-hover': 'var(--c--theme--colors--greyscale-300)', }, color: 'var(--c--theme--colors--primary-text)', }, diff --git a/src/frontend/apps/impress/public/assets/SC1-en.webm b/src/frontend/apps/impress/public/assets/SC1-en.webm new file mode 100644 index 00000000..ff1dc284 Binary files /dev/null and b/src/frontend/apps/impress/public/assets/SC1-en.webm differ diff --git a/src/frontend/apps/impress/public/assets/SC1-fr.webm b/src/frontend/apps/impress/public/assets/SC1-fr.webm new file mode 100644 index 00000000..1ebc7bef Binary files /dev/null and b/src/frontend/apps/impress/public/assets/SC1-fr.webm differ diff --git a/src/frontend/apps/impress/src/assets/icons/icon-docs.svg b/src/frontend/apps/impress/src/assets/icons/icon-docs.svg new file mode 100644 index 00000000..430eb0e0 --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/icon-docs.svg @@ -0,0 +1,18 @@ + + + + diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css index 3357c29c..c0572eb0 100644 --- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css +++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css @@ -71,6 +71,7 @@ --c--theme--colors--danger-text: var(--c--theme--colors--greyscale-000); --c--theme--colors--card-border: #ededed; --c--theme--colors--primary-bg: #fafafa; + --c--theme--colors--primary-action: #1212ff; --c--theme--colors--primary-050: #f5f5fe; --c--theme--colors--primary-150: #e5eefa; --c--theme--colors--primary-950: #1b1b35; @@ -122,6 +123,11 @@ --c--theme--font--sizes--ml: 0.938rem; --c--theme--font--sizes--xl: 1.25rem; --c--theme--font--sizes--t: 0.6875rem; + --c--theme--font--sizes--xl-alt: 5rem; + --c--theme--font--sizes--lg-alt: 4.5rem; + --c--theme--font--sizes--md-alt: 4rem; + --c--theme--font--sizes--sm-alt: 3.5rem; + --c--theme--font--sizes--xs-alt: 3rem; --c--theme--font--weights--thin: 100; --c--theme--font--weights--light: 300; --c--theme--font--weights--regular: 400; @@ -316,7 +322,7 @@ --c--theme--colors--primary-700 ); --c--components--button--secondary--border--color: var( - --c--theme--colors--primary-200 + --c--theme--colors--greyscale-300 ); --c--components--button--tertiary--color: var( --c--theme--colors--primary-text @@ -501,10 +507,10 @@ --c--components--button--secondary--background--color-hover: #f6f6f6; --c--components--button--secondary--background--color-active: #ededed; --c--components--button--secondary--border--color: var( - --c--theme--colors--primary-600 + --c--theme--colors--greyscale-300 ); --c--components--button--secondary--border--color-hover: var( - --c--theme--colors--primary-600 + --c--theme--colors--greyscale-300 ); --c--components--button--secondary--color: var( --c--theme--colors--primary-text @@ -874,6 +880,10 @@ color: var(--c--theme--colors--primary-bg); } +.clr-primary-action { + color: var(--c--theme--colors--primary-action); +} + .clr-primary-050 { color: var(--c--theme--colors--primary-050); } @@ -1302,6 +1312,10 @@ background-color: var(--c--theme--colors--primary-bg); } +.bg-primary-action { + background-color: var(--c--theme--colors--primary-action); +} + .bg-primary-050 { background-color: var(--c--theme--colors--primary-050); } @@ -1550,6 +1564,31 @@ letter-spacing: var(--c--theme--font--letterspacings--t); } +.fs-xl-alt { + font-size: var(--c--theme--font--sizes--xl-alt); + letter-spacing: var(--c--theme--font--letterspacings--xl-alt); +} + +.fs-lg-alt { + font-size: var(--c--theme--font--sizes--lg-alt); + letter-spacing: var(--c--theme--font--letterspacings--lg-alt); +} + +.fs-md-alt { + font-size: var(--c--theme--font--sizes--md-alt); + letter-spacing: var(--c--theme--font--letterspacings--md-alt); +} + +.fs-sm-alt { + font-size: var(--c--theme--font--sizes--sm-alt); + letter-spacing: var(--c--theme--font--letterspacings--sm-alt); +} + +.fs-xs-alt { + font-size: var(--c--theme--font--sizes--xs-alt); + letter-spacing: var(--c--theme--font--letterspacings--xs-alt); +} + .f-base { font-family: var(--c--theme--font--families--base); } diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts index 521094f4..b63ef0bf 100644 --- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts +++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts @@ -75,6 +75,7 @@ export const tokens = { 'danger-text': '#fff', 'card-border': '#ededed', 'primary-bg': '#FAFAFA', + 'primary-action': '#1212FF', 'primary-050': '#F5F5FE', 'primary-150': '#E5EEFA', 'primary-950': '#1B1B35', @@ -129,6 +130,11 @@ export const tokens = { ml: '0.938rem', xl: '1.25rem', t: '0.6875rem', + 'xl-alt': '5rem', + 'lg-alt': '4.5rem', + 'md-alt': '4rem', + 'sm-alt': '3.5rem', + 'xs-alt': '3rem', }, weights: { thin: 100, @@ -315,7 +321,7 @@ export const tokens = { color: 'white', 'color-hover': 'var(--c--theme--colors--primary-700)', }, - border: { color: 'var(--c--theme--colors--primary-200)' }, + border: { color: 'var(--c--theme--colors--greyscale-300)' }, }, tertiary: { color: 'var(--c--theme--colors--primary-text)', @@ -502,8 +508,8 @@ export const tokens = { secondary: { background: { 'color-hover': '#F6F6F6', 'color-active': '#EDEDED' }, border: { - color: 'var(--c--theme--colors--primary-600)', - 'color-hover': 'var(--c--theme--colors--primary-600)', + color: 'var(--c--theme--colors--greyscale-300)', + 'color-hover': 'var(--c--theme--colors--greyscale-300)', }, color: 'var(--c--theme--colors--primary-text)', }, diff --git a/src/frontend/apps/impress/src/features/auth/components/Auth.tsx b/src/frontend/apps/impress/src/features/auth/components/Auth.tsx index 714ef86f..73728786 100644 --- a/src/frontend/apps/impress/src/features/auth/components/Auth.tsx +++ b/src/frontend/apps/impress/src/features/auth/components/Auth.tsx @@ -1,23 +1,41 @@ import { Loader } from '@openfun/cunningham-react'; +import { useRouter } from 'next/router'; import { PropsWithChildren } from 'react'; import { Box } from '@/components'; -import HomeContent from '@/features/home/components/HomeContent'; import { useAuth } from '../hooks'; -/** - * TODO: Remove this restriction when we will have a homepage design for non-authenticated users. - * - * We define the paths that are not allowed without authentication. - * Actually, only the home page and the docs page are not allowed without authentication. - * When we will have a homepage design for non-authenticated users, we will remove this restriction to have - * the full website accessible without authentication. - */ export const Auth = ({ children }: PropsWithChildren) => { - const { user, isLoading, pathAllowed } = useAuth(); + const { isLoading, pathAllowed, isFetchedAfterMount, authenticated } = + useAuth(); + const { replace, pathname } = useRouter(); - if (isLoading) { + if (isLoading && !isFetchedAfterMount) { + return ( + + + + ); + } + + /** + * If the user is not authenticated and the path is not allowed, we redirect to the login page. + */ + if (!authenticated && !pathAllowed) { + void replace('/login'); + return ( + + + + ); + } + + /** + * If the user is authenticated and the path is the login page, we redirect to the home page. + */ + if (pathname === '/login' && authenticated) { + void replace('/'); return ( diff --git a/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg b/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg deleted file mode 100644 index 4f26ae3b..00000000 --- a/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx new file mode 100644 index 00000000..8504674e --- /dev/null +++ b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx @@ -0,0 +1,26 @@ +import { Button } from '@openfun/cunningham-react'; +import { useTranslation } from 'react-i18next'; + +import { Icon } from '@/components/'; +import { useLeftPanelStore } from '@/features/left-panel'; + +export const ButtonTogglePanel = () => { + const { t } = useTranslation(); + const { isPanelOpen, togglePanel } = useLeftPanelStore(); + + return ( + + + {!isMobile && ( + {t('Banner + )} + + + + + + ); +} diff --git a/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx new file mode 100644 index 00000000..903bae0b --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx @@ -0,0 +1,120 @@ +import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import { Box } from '@/components'; +import { Footer } from '@/features/footer'; +import { LeftPanel } from '@/features/left-panel'; +import { useLanguage } from '@/i18n/hooks/useLanguage'; +import { useResponsiveStore } from '@/stores'; + +import SC1ResponsiveEn from '../assets/SC1-responsive-en.png'; +import SC1ResponsiveFr from '../assets/SC1-responsive-fr.png'; +import SC2En from '../assets/SC2-en.png'; +import SC2Fr from '../assets/SC2-fr.png'; +import SC3En from '../assets/SC3-en.png'; +import SC3Fr from '../assets/SC3-fr.png'; +import SC4En from '../assets/SC4-en.png'; +import SC4Fr from '../assets/SC4-fr.png'; +import SC4ResponsiveEn from '../assets/SC4-responsive-en.png'; +import SC4ResponsiveFr from '../assets/SC4-responsive-fr.png'; + +import HomeBanner from './HomeBanner'; +import { HomeOpenSource } from './HomeOpenSource'; +import { HomeHeader, getHeaderHeight } from './HomeHeader'; +import { HomeSection } from './HomeSection'; + +export function HomeContent() { + const { t } = useTranslation(); + const { isMobile, isSmallMobile } = useResponsiveStore(); + const lang = useLanguage(); + const isFrLanguage = lang.language === 'fr'; + + return ( + + + {isSmallMobile && ( + + + + )} + + + + + + + + + + + +