(frontend) add back the footer and cgu pages

We need to add back the footer and cgu pages,
but we will not display the footer on the doc
editor pages.
This commit is contained in:
Anthony LC
2024-10-09 13:07:07 +02:00
committed by Anthony LC
parent 3a0dff5b0e
commit ebdcb4b2f0
13 changed files with 555 additions and 2 deletions

View File

@@ -24,6 +24,7 @@ and this project adheres to
- 💄(frontend) error alert closeable on editor #284
- ♻️(backend) Change email content #283
- 🛂(frontend) viewers and editors can access share modal #302
- ♻️(frontend) remove footer on doc editor #313
## Fixed

View File

@@ -0,0 +1,77 @@
import { expect, test } from '@playwright/test';
import { goToGridDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test.describe('Footer', () => {
test('checks all the elements are visible', async ({ page }) => {
const footer = page.locator('footer').first();
await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
await expect(
footer.getByRole('link', { name: 'legifrance.gouv.fr' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'info.gouv.fr' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'service-public.fr' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'data.gouv.fr' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Legal Notice' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Personal data and cookies' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Accessibility' }),
).toBeVisible();
await expect(
footer.getByText(
'Unless otherwise stated, all content on this site is under licence',
),
).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/' },
{ name: 'Accessibility', url: '/accessibility/' },
];
for (const { name, url } of legalPages) {
test(`checks ${name} page`, async ({ page }) => {
const footer = page.locator('footer').first();
await footer.getByRole('link', { name }).click();
await expect(
page
.getByRole('heading', {
name,
})
.first(),
).toBeVisible();
await expect(page).toHaveURL(url);
});
}
});

View File

@@ -0,0 +1,166 @@
import Image from 'next/image';
import React from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { Box, StyledLink, Text } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
import IconLink from './assets/external-link.svg';
const BlueStripe = styled.div`
position: absolute;
height: 2px;
width: 100%;
background: var(--c--theme--colors--primary-600);
top: 0;
`;
export const Footer = () => {
const { t } = useTranslation();
const { themeTokens } = useCunninghamTheme();
const logo = themeTokens().logo;
return (
<Box $position="relative" as="footer">
<BlueStripe />
<Box $padding={{ top: 'large', horizontal: 'big', bottom: 'small' }}>
<Box
$direction="row"
$gap="1.5rem"
$align="center"
$justify="space-between"
$css="flex-wrap: wrap;"
>
<Box>
<Box $align="center" $gap="6rem" $direction="row">
{logo && (
<Image
priority
src={logo.src}
alt={logo.alt}
width={0}
height={0}
style={{ width: logo.widthFooter, height: 'auto' }}
/>
)}
</Box>
</Box>
<Box
$direction="row"
$css={`
column-gap: 1.5rem;
row-gap: .5rem;
flex-wrap: wrap;
`}
>
{[
{
label: 'legifrance.gouv.fr',
href: 'https://legifrance.gouv.fr/',
},
{
label: 'info.gouv.fr',
href: 'https://info.gouv.fr/',
},
{
label: 'service-public.fr',
href: 'https://service-public.fr/',
},
{
label: 'data.gouv.fr',
href: 'https://data.gouv.fr/',
},
].map(({ label, href }) => (
<StyledLink
key={label}
href={href}
target="__blank"
$css={`
gap:0.2rem;
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
}
`}
>
<Text $weight="bold">{label}</Text>
<IconLink width={18} />
</StyledLink>
))}
</Box>
</Box>
<Box
$direction="row"
$margin={{ top: 'big' }}
$padding={{ top: 'tiny' }}
$css={`
flex-wrap: wrap;
border-top: 1px solid var(--c--theme--colors--greyscale-200);
column-gap: 1rem;
row-gap: .5rem;
`}
>
{[
{
label: t('Legal Notice'),
href: '/legal-notice',
},
{
label: t('Personal data and cookies'),
href: '/personal-data-cookies',
},
{
label: t('Accessibility'),
href: '/accessibility',
},
].map(({ label, href }) => (
<StyledLink
key={label}
href={href}
$css={`
padding-right: 1rem;
&:not(:last-child) {
box-shadow: inset -1px 0px 0px 0px var(--c--theme--colors--greyscale-200);
}
`}
>
<Text
$variation="600"
$size="m"
$transition="box-shadow 0.3s"
$css={`
&:hover {
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
}
`}
>
{label}
</Text>
</StyledLink>
))}
</Box>
<Text
as="p"
$size="m"
$margin={{ top: 'big' }}
$variation="600"
$display="inline"
>
{t('Unless otherwise stated, all content on this site is under')}{' '}
<StyledLink
href="https://github.com/etalab/licence-ouverte/blob/master/LO.md"
target="__blank"
$css={`
display:inline-flex;
box-shadow: 0px 1px 0 0 var(--c--theme--colors--greyscale-text);
`}
>
<Text $variation="600">licence etalab-2.0</Text>
<IconLink width={18} />
</StyledLink>
</Text>
</Box>
</Box>
);
};

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6Zm11-3v8h-2V6.413l-7.793 7.794-1.414-1.414L17.585 5H13V3h8Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1 @@
export * from './Footer';

View File

@@ -94,6 +94,9 @@ const precacheResources = [
'/',
'/index.html',
'/404/',
'/accessibility/',
'/legal-notice/',
'/personal-data-cookies/',
FALLBACK.offline,
FALLBACK.images,
FALLBACK.docs,

View File

@@ -1,8 +1,18 @@
import { PropsWithChildren } from 'react';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Footer } from '@/features/footer';
import { Header } from '@/features/header';
export function MainLayout({ children }: { children: React.ReactNode }) {
interface MainLayoutProps {
withoutFooter?: boolean;
}
export function MainLayout({
children,
withoutFooter,
}: PropsWithChildren<MainLayoutProps>) {
const { colorsTokens } = useCunninghamTheme();
return (
@@ -20,6 +30,7 @@ export function MainLayout({ children }: { children: React.ReactNode }) {
</Box>
</Box>
</Box>
{!withoutFooter && <Footer />}
</Box>
);
}

View File

@@ -0,0 +1,17 @@
import { PropsWithChildren } from 'react';
import { Box } from '@/components';
import { Footer } from '@/features/footer';
import { Header } from '@/features/header';
export function PageLayout({ children }: PropsWithChildren) {
return (
<Box $minHeight="100vh">
<Header />
<Box as="main" $width="100%" $css="flex-grow:1;">
{children}
</Box>
<Footer />
</Box>
);
}

View File

@@ -1 +1,2 @@
export * from './MainLayout';
export * from './PageLayout';

View File

@@ -0,0 +1,133 @@
import { ReactElement } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Box, Text, TextStyled } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens()['primary-100']}
$margin="none"
$padding="large"
>
{t('Accessibility')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="p" $display="inline">
<Trans t={t} i18nKey="accessibility-dinum-services">
<strong>DINUM</strong> is committed to making its digital services
accessible, in accordance with article 47 of French law n° 2005-102
dated February 11, 2005.
</Trans>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Accessibility statement')}
</Text>
<Text as="p">{t('Established on December 20, 2023.')}</Text>
<Text as="p" $display="inline">
{t('This accessibility statement applies to the site hosted on')}{' '}
<strong>https://docs.numerique.gouv.fr</strong>.
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Compliance status')}
</Text>
<Text as="p" $display="inline">
<Trans t={t} i18nKey="accessibility-not-audit">
<strong>https://docs.numerique.gouv.fr</strong> is not compliant
with RGAA 4.1. The site has <strong>not yet been audited.</strong>
</Trans>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Improvement and contact')}
</Text>
<Text as="p" $display="inline">
{t(
'If you are unable to access a content or a service, you can contact the person responsible for https://lasuite.numerique.gouv.fr to be directed to an accessible alternative or to obtain the content in another form.',
)}
</Text>
<Text as="p" $display="inline">
<li>
{t('E-mail:')}{' '}
<TextStyled
as="a"
href="mailto:lasuite@modernisation.gouv.fr"
$display="inline"
>
lasuite@modernisation.gouv.fr
</TextStyled>
</li>
<li>
{t('Address:')} <strong>DINUM</strong>, 20 avenue de Ségur 75007
Paris
</li>
</Text>
<Text as="p" $display="inline">
{t('We try to respond within 2 working days.')}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Remedies')}
</Text>
<Text as="p" $display="inline">
{t('This procedure should be used in the following case:')}{' '}
{t(
'you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.',
)}
</Text>
<Text as="p" $display="inline" $margin={{ bottom: 'tiny' }}>
{t('You can:')}
</Text>
<Text as="p" $display="inline" $margin={{ top: 'tiny' }}>
<li>
<Trans t={t} i18nKey="accessibility-form-defenseurdesdroits">
Write a message to the
<TextStyled
as="a"
href="https://formulaire.defenseurdesdroits.fr/formulaire_saisine/"
$display="inline"
$margin={{ left: '4px' }}
>
Defender of Rights
</TextStyled>
</Trans>
</li>
<li>
<Trans t={t} i18nKey="accessibility-contact-defenseurdesdroits">
Contact the delegate of the
<TextStyled
as="a"
href="https://www.defenseurdesdroits.fr/carte-des-delegues"
$display="inline"
$margin={{ left: '4px' }}
>
Defender of Rights in your region
</TextStyled>
</Trans>
</li>
<li>
{t('Send a letter by post (free of charge, no stamp needed):')}{' '}
<strong>
{t(
'Defender of Rights - Free response - 71120 75342 Paris CEDEX 07',
)}
</strong>
</li>
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;

View File

@@ -21,7 +21,7 @@ export function DocLayout() {
}
return (
<MainLayout>
<MainLayout withoutFooter>
<DocPage id={id} />
</MainLayout>
);

View File

@@ -0,0 +1,72 @@
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text, TextStyled } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens()['primary-100']}
$margin="none"
$padding="large"
>
{t('Legal notice')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Publisher')}
</Text>
<Text as="p">
{t(
'French Interministerial Directorate for Digital Affairs (DINUM), 20 avenue de Ségur 75007 Paris.',
)}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Publication Director')}
</Text>
<Text as="p">
{t('Stéphanie Schaer: Interministerial Digital Director (DINUM).')}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Copyright')}
</Text>
<Text as="p" $display="inline">
{t('Illustration:')}{' '}
<Text $weight="bold" $display="inline">
DINUM
</Text>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('More info?')}
</Text>
<Text as="p" $display="inline">
{t(
'The team in charge of the digital workspace "La Suite numérique" can be contacted directly at',
)}{' '}
<TextStyled
as="a"
href="mailto:lasuite@modernisation.gouv.fr"
$display="inline"
>
lasuite@modernisation.gouv.fr
</TextStyled>
.
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;

View File

@@ -0,0 +1,66 @@
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens()['primary-100']}
$margin="none"
$padding="large"
>
{t('Personal data and cookies')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Cookies placed')}
</Text>
<Text as="p">
{t(
'This site places a small text file (a "cookie") on your computer when you visit it.',
)}
{t(
'This allows us to measure the number of visits and understand which pages are the most viewed.',
)}
</Text>
<Text as="p">
{t('You can oppose the tracking of your browsing on this website.')}
{t(
'This will protect your privacy, but will also prevent the owner from learning from your actions and creating a better experience for you and other users.',
)}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('This site does not display a cookie consent banner, why?')}
</Text>
<Text as="p">
{t(
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!",
)}
</Text>
<Text as="p">
{t(
'Nothing exceptional, no special privileges related to a .gouv.fr.',
)}
{t(
'We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.',
)}
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;