✨(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:
@@ -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
|
||||
|
||||
|
||||
77
src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts
Normal file
77
src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
166
src/frontend/apps/impress/src/features/footer/Footer.tsx
Normal file
166
src/frontend/apps/impress/src/features/footer/Footer.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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 |
1
src/frontend/apps/impress/src/features/footer/index.tsx
Normal file
1
src/frontend/apps/impress/src/features/footer/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Footer';
|
||||
@@ -94,6 +94,9 @@ const precacheResources = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/404/',
|
||||
'/accessibility/',
|
||||
'/legal-notice/',
|
||||
'/personal-data-cookies/',
|
||||
FALLBACK.offline,
|
||||
FALLBACK.images,
|
||||
FALLBACK.docs,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
17
src/frontend/apps/impress/src/layouts/PageLayout.tsx
Normal file
17
src/frontend/apps/impress/src/layouts/PageLayout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './MainLayout';
|
||||
export * from './PageLayout';
|
||||
|
||||
133
src/frontend/apps/impress/src/pages/accessibility/index.tsx
Normal file
133
src/frontend/apps/impress/src/pages/accessibility/index.tsx
Normal 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;
|
||||
@@ -21,7 +21,7 @@ export function DocLayout() {
|
||||
}
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<MainLayout withoutFooter>
|
||||
<DocPage id={id} />
|
||||
</MainLayout>
|
||||
);
|
||||
|
||||
72
src/frontend/apps/impress/src/pages/legal-notice/index.tsx
Normal file
72
src/frontend/apps/impress/src/pages/legal-notice/index.tsx
Normal 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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user