From 1aa4844eebbf7eeb63aaa24d1c5332e0e4a4054b Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Mon, 3 Feb 2025 11:02:29 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8(frontend)=20add=20dsfr=20proconnec?= =?UTF-8?q?t=20homepage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we are with the DSFR theme, we need to add the proconnect button to the homepage. We add an option in the cunningham theme to display the proconnect section instead of the opensource section. --- .../apps/e2e/__tests__/app-impress/common.ts | 18 +- .../e2e/__tests__/app-impress/config.spec.ts | 46 +- .../__tests__/app-impress/doc-create.spec.ts | 2 - .../__tests__/app-impress/doc-grid.spec.ts | 5 - .../__tests__/app-impress/doc-routing.spec.ts | 11 +- .../app-impress/doc-visibility.spec.ts | 34 +- .../e2e/__tests__/app-impress/footer.spec.ts | 17 +- .../e2e/__tests__/app-impress/header.spec.ts | 4 +- .../e2e/__tests__/app-impress/home.spec.ts | 43 +- src/frontend/apps/impress/cunningham.ts | 6 + .../apps/impress/public/assets/logo-gouv.svg | 675 ++++++++++++------ .../src/cunningham/cunningham-tokens.css | 2 + .../src/cunningham/cunningham-tokens.ts | 2 + .../auth/assets/button-proconnect.svg | 40 ++ .../features/auth/components/ButtonLogin.tsx | 24 + .../features/home/components/HomeBanner.tsx | 29 +- .../{HomeOpenSource.tsx => HomeBottom.tsx} | 58 +- .../features/home/components/HomeContent.tsx | 4 +- 18 files changed, 696 insertions(+), 324 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg rename src/frontend/apps/impress/src/features/home/components/{HomeOpenSource.tsx => HomeBottom.tsx} (68%) diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts index 6e65dc65..31de3f4a 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts @@ -1,6 +1,17 @@ import { Page, expect } from '@playwright/test'; -export const keyCloakSignIn = async (page: Page, browserName: string) => { +export const keyCloakSignIn = async ( + page: Page, + browserName: string, + fromHome: boolean = true, +) => { + if (fromHome) { + await page + .getByRole('button', { name: 'Proconnect Login' }) + .first() + .click(); + } + const login = `user-e2e-${browserName}`; const password = `password-e2e-${browserName}`; @@ -258,3 +269,8 @@ export const mockedAccesses = async (page: Page, json?: object) => { } }); }; + +export const expectLoginPage = async (page: Page) => + await expect( + page.getByRole('heading', { name: 'Collaborative writing' }), + ).toBeVisible(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index 042c6287..7c9639ba 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -63,27 +63,6 @@ test.describe('Config', () => { expect((await consoleMessage).text()).toContain(invalidMsg); }); - test('it checks that theme is configured from config endpoint', async ({ - page, - }) => { - const responsePromise = page.waitForResponse( - (response) => - response.url().includes('/config/') && response.status() === 200, - ); - - await page.goto('/'); - - const response = await responsePromise; - expect(response.ok()).toBeTruthy(); - - const jsonResponse = await response.json(); - expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr'); - - const footer = page.locator('footer').first(); - // alt 'Gouvernement Logo' comes from the theme - await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); - }); - test('it checks that media server is configured from config endpoint', async ({ page, browserName, @@ -161,3 +140,28 @@ test.describe('Config', () => { ).toBeVisible(); }); }); + +test.describe('Config: Not loggued', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('it checks that theme is configured from config endpoint', async ({ + page, + }) => { + const responsePromise = page.waitForResponse( + (response) => + response.url().includes('/config/') && response.status() === 200, + ); + + await page.goto('/'); + + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + + const jsonResponse = await response.json(); + expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr'); + + const footer = page.locator('footer').first(); + // alt 'Gouvernement Logo' comes from the theme + await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts index 3fbd1d08..52aedce0 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts @@ -24,8 +24,6 @@ test.describe('Doc Create', () => { const header = page.locator('header').first(); await header.locator('h2').getByText('Docs').click(); - await expect(page.getByTestId('grid-loader')).toBeVisible(); - const docsGrid = page.getByTestId('docs-grid'); await expect(docsGrid).toBeVisible(); await expect(page.getByTestId('grid-loader')).toBeHidden(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 772ec104..31cee30d 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -213,7 +213,6 @@ test.describe('Document grid item options', () => { test.describe('Documents filters', () => { test('it checks the prebuild left panel filters', async ({ page }) => { // All Docs - await expect(page.getByTestId('grid-loader')).toBeVisible(); const response = await page.waitForResponse( (response) => response.url().endsWith('documents/?page=1') && @@ -254,7 +253,6 @@ test.describe('Documents filters', () => { url = new URL(page.url()); target = url.searchParams.get('target'); expect(target).toBe('my_docs'); - await expect(page.getByTestId('grid-loader')).toBeVisible(); const responseMyDocs = await page.waitForResponse( (response) => response.url().endsWith('documents/?page=1&is_creator_me=true') && @@ -270,7 +268,6 @@ test.describe('Documents filters', () => { url = new URL(page.url()); target = url.searchParams.get('target'); expect(target).toBe('shared_with_me'); - await expect(page.getByTestId('grid-loader')).toBeVisible(); const responseSharedWithMe = await page.waitForResponse( (response) => response.url().includes('documents/?page=1&is_creator_me=false') && @@ -291,8 +288,6 @@ test.describe('Documents Grid', () => { test('checks all the elements are visible', async ({ page }) => { let docs: SmallDoc[] = []; - await expect(page.getByTestId('grid-loader')).toBeVisible(); - const response = await page.waitForResponse( (response) => response.url().endsWith('documents/?page=1') && diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts index 4a080dee..5280a892 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { keyCloakSignIn, mockedDocument } from './common'; +import { expectLoginPage, keyCloakSignIn, mockedDocument } from './common'; test.describe('Doc Routing', () => { test.beforeEach(async ({ page }) => { @@ -63,16 +63,13 @@ test.describe('Doc Routing: Not loggued', () => { await page.goto('/docs/mocked-document-id/'); await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); await page.getByRole('button', { name: 'Login' }).click(); - await keyCloakSignIn(page, browserName); + await keyCloakSignIn(page, browserName, false); await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); }); + // eslint-disable-next-line playwright/expect-expect test('The homepage redirects to login.', async ({ page }) => { await page.goto('/'); - await expect( - page.getByRole('button', { - name: 'Sign In', - }), - ).toBeVisible(); + await expectLoginPage(page); }); }); 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 dc7fb7eb..e4ee37f4 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 @@ -1,6 +1,11 @@ import { expect, test } from '@playwright/test'; -import { createDoc, keyCloakSignIn, verifyDocName } from './common'; +import { + createDoc, + expectLoginPage, + keyCloakSignIn, + verifyDocName, +} from './common'; const browsersName = ['chromium', 'webkit', 'firefox']; @@ -91,7 +96,7 @@ test.describe('Doc Visibility: Restricted', () => { }) .click(); - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + await expectLoginPage(page); await page.goto(urlDoc); @@ -121,6 +126,10 @@ test.describe('Doc Visibility: Restricted', () => { await keyCloakSignIn(page, otherBrowser!); + await expect( + page.getByRole('link', { name: 'Docs Logo Docs BETA' }), + ).toBeVisible(); + await page.goto(urlDoc); await expect( @@ -169,10 +178,11 @@ test.describe('Doc Visibility: Restricted', () => { await keyCloakSignIn(page, otherBrowser!); - await page.goto(urlDoc); + await expect( + page.getByRole('link', { name: 'Docs Logo Docs BETA' }), + ).toBeVisible(); - // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(1000); + await page.goto(urlDoc); await verifyDocName(page, docTitle); await expect(page.getByLabel('Share button')).toBeVisible(); @@ -247,7 +257,7 @@ test.describe('Doc Visibility: Public', () => { }) .click(); - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + await expectLoginPage(page); await page.goto(urlDoc); @@ -313,7 +323,7 @@ test.describe('Doc Visibility: Public', () => { }) .click(); - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + await expectLoginPage(page); await page.goto(urlDoc); @@ -364,7 +374,7 @@ test.describe('Doc Visibility: Authenticated', () => { }) .click(); - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + await expectLoginPage(page); await page.goto(urlDoc); @@ -414,6 +424,10 @@ test.describe('Doc Visibility: Authenticated', () => { const otherBrowser = browsersName.find((b) => b !== browserName); await keyCloakSignIn(page, otherBrowser!); + await expect( + page.getByRole('link', { name: 'Docs Logo Docs BETA' }), + ).toBeVisible(); + await page.goto(urlDoc); await expect(page.locator('h2').getByText(docTitle)).toBeVisible(); @@ -470,6 +484,10 @@ test.describe('Doc Visibility: Authenticated', () => { const otherBrowser = browsersName.find((b) => b !== browserName); await keyCloakSignIn(page, otherBrowser!); + await expect( + page.getByRole('link', { name: 'Docs Logo Docs BETA' }), + ).toBeVisible(); + await page.goto(urlDoc); await verifyDocName(page, docTitle); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts index 0862051f..a1878ccb 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts @@ -1,13 +1,10 @@ import { expect, test } from '@playwright/test'; -import { goToGridDoc } from './common'; - -test.beforeEach(async ({ page }) => { - await page.goto('/'); -}); - test.describe('Footer', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/'); const footer = page.locator('footer').first(); await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); @@ -47,12 +44,6 @@ test.describe('Footer', () => { ).toBeVisible(); }); - test('checks footer is not visible on doc editor', async ({ page }) => { - await expect(page.locator('footer')).toBeVisible(); - await goToGridDoc(page); - await expect(page.locator('footer')).toBeHidden(); - }); - const legalPages = [ { name: 'Legal Notice', url: '/legal-notice/' }, { name: 'Personal data and cookies', url: '/personal-data-cookies/' }, @@ -60,6 +51,8 @@ test.describe('Footer', () => { ]; for (const { name, url } of legalPages) { test(`checks ${name} page`, async ({ page }) => { + await page.goto('/'); + const footer = page.locator('footer').first(); await footer.getByRole('link', { name }).click(); 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 5ef24c46..795d04cf 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { keyCloakSignIn } from './common'; +import { expectLoginPage, keyCloakSignIn } from './common'; test.describe('Header', () => { test.beforeEach(async ({ page }) => { @@ -98,6 +98,6 @@ test.describe('Header: Log out', () => { }) .click(); - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); + await expectLoginPage(page); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts index 5299d017..8f09c346 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts @@ -20,30 +20,33 @@ test.describe('Home page', () => { await expect( header.getByRole('img', { name: 'Gouvernement Logo' }), ).toBeVisible(); - await expect( - header.getByRole('img', { name: 'Docs app logo' }), - ).toBeVisible(); + await expect(header.getByRole('img', { name: 'Docs 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(); + // Check the titles + const h2 = page.locator('h2'); await expect( - page.getByRole('link', { name: 'expand_more See more' }), + h2.getByText('Collaborative writing, Simplified.'), ).toBeVisible(); + await expect( + h2.getByText('An uncompromising writing experience.'), + ).toBeVisible(); + await expect( + h2.getByText('Simple and secure collaboration.'), + ).toBeVisible(); + await expect(h2.getByText('Flexible export.')).toBeVisible(); + await expect( + h2.getByText('A new way to organize knowledge.'), + ).toBeVisible(); + + await expect( + page.getByText('Docs is already available, log in to use it now.'), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Proconnect Login' }), + ).toHaveCount(2); + + await expect(footer).toBeVisible(); }); }); diff --git a/src/frontend/apps/impress/cunningham.ts b/src/frontend/apps/impress/cunningham.ts index 633e2db9..3a96c054 100644 --- a/src/frontend/apps/impress/cunningham.ts +++ b/src/frontend/apps/impress/cunningham.ts @@ -253,6 +253,9 @@ const config = { 'la-gauffre': { activated: false, }, + 'home-proconnect': { + activated: false, + }, }, }, dsfr: { @@ -468,6 +471,9 @@ const config = { 'la-gauffre': { activated: true, }, + 'home-proconnect': { + activated: true, + }, }, }, }, diff --git a/src/frontend/apps/impress/public/assets/logo-gouv.svg b/src/frontend/apps/impress/public/assets/logo-gouv.svg index 583adb17..824e1b94 100644 --- a/src/frontend/apps/impress/public/assets/logo-gouv.svg +++ b/src/frontend/apps/impress/public/assets/logo-gouv.svg @@ -1,241 +1,454 @@ - - - - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css index c0572eb0..d3af0dec 100644 --- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css +++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css @@ -345,6 +345,7 @@ --c--components--button--disabled--color: white; --c--components--button--disabled--background--color: #b3cef0; --c--components--la-gauffre--activated: false; + --c--components--home-proconnect--activated: false; } .cunningham-theme--dark { @@ -590,6 +591,7 @@ ); --c--components--forms-textarea--border-radius: 0; --c--components--la-gauffre--activated: true; + --c--components--home-proconnect--activated: true; } .clr-secondary-text { diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts index b63ef0bf..9c38dcd8 100644 --- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts +++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts @@ -336,6 +336,7 @@ export const tokens = { disabled: { color: 'white', background: { color: '#b3cef0' } }, }, 'la-gauffre': { activated: false }, + 'home-proconnect': { activated: false }, }, }, dark: { @@ -581,6 +582,7 @@ export const tokens = { }, 'forms-textarea': { 'border-radius': '0' }, 'la-gauffre': { activated: true }, + 'home-proconnect': { activated: true }, }, }, }, diff --git a/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg b/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg new file mode 100644 index 00000000..10d1f499 --- /dev/null +++ b/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg @@ -0,0 +1,40 @@ + + + + + + + + + + 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 decfb5d7..a96f3555 100644 --- a/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx +++ b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx @@ -1,6 +1,11 @@ import { Button } from '@openfun/cunningham-react'; +import Image from 'next/image'; import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; +import { BoxButton } from '@/components'; + +import ProConnectImg from '../assets/button-proconnect.svg?url'; import { useAuth } from '../hooks'; import { gotoLogin, gotoLogout } from '../utils'; @@ -22,3 +27,22 @@ export const ButtonLogin = () => { ); }; + +export const ProConnectButton = () => { + const { t } = useTranslation(); + + return ( + + {t('ProConnect + + ); +}; diff --git a/src/frontend/apps/impress/src/features/home/components/HomeBanner.tsx b/src/frontend/apps/impress/src/features/home/components/HomeBanner.tsx index 10fd0eac..39da6ec4 100644 --- a/src/frontend/apps/impress/src/features/home/components/HomeBanner.tsx +++ b/src/frontend/apps/impress/src/features/home/components/HomeBanner.tsx @@ -6,7 +6,7 @@ import { css } from 'styled-components'; import DocLogo from '@/assets/icons/icon-docs.svg?url'; import { Box, Icon, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { gotoLogin } from '@/features/auth'; +import { ProConnectButton, gotoLogin } from '@/features/auth'; import { useResponsiveStore } from '@/stores'; import banner from '../assets/banner.jpg'; @@ -15,9 +15,10 @@ import { getHeaderHeight } from './HomeHeader'; export default function HomeBanner() { const { t } = useTranslation(); - const { spacingsTokens } = useCunninghamTheme(); + const { componentTokens, spacingsTokens } = useCunninghamTheme(); const spacings = spacingsTokens(); const { isMobile, isSmallMobile } = useResponsiveStore(); + const withProConnect = componentTokens()['home-proconnect'].activated; return ( - + {withProConnect ? ( + + ) : ( + + )} {!isMobile && ( ; + } else { + return ; + } +} + +function HomeOpenSource() { const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); + const { isTablet } = useResponsiveStore(); return ( ); } + +function HomeProConnect() { + const { t } = useTranslation(); + const { spacingsTokens } = useCunninghamTheme(); + const spacings = spacingsTokens(); + const { isMobile } = useResponsiveStore(); + const parentGap = '230px'; + + return ( + + + + DocLogo + + </Box> + <Text $size="md" $variation="1000" $textAlign="center"> + {t('Docs is already available, log in to use it now.')} + </Text> + <ProConnectButton /> + </Box> + </Box> + ); +} diff --git a/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx index 903bae0b..6e9dc7d9 100644 --- a/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx +++ b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx @@ -19,7 +19,7 @@ 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 { HomeBottom } from './HomeBottom'; import { HomeHeader, getHeaderHeight } from './HomeHeader'; import { HomeSection } from './HomeSection'; @@ -110,7 +110,7 @@ export function HomeContent() { 'Docs transforms your documents into knowledge bases thanks to subpages, powerful search and the ability to pin your important documents.', )} /> - <HomeOpenSource /> + <HomeBottom /> </Box> </Box> <Footer />