Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,21 @@ pnpm run build-storybook
# Lancer les tests de composants (Vitest + navigateur, voir plus bas)
pnpm run test
pnpm run test:watch

# Lancer les tests unitaires (Node, sans navigateur)
pnpm run test:unit

# Lancer les deux
pnpm run test:all
```

#### Tests unitaires

`pnpm run test:unit` exécute les fichiers `src/**/*.test.ts` en environnement Node, sans navigateur :
quelques centaines de millisecondes, là où les tests de composants demandent une bonne minute. C'est
le bon endroit pour vérifier de la logique pure, par exemple la décision de rendu prise face à une
réponse Strapi (`src/lib/cms-outcome.ts`).

#### Tests de composants

Les stories servent de tests : chaque story est rendue dans un vrai navigateur (Vitest +
Expand Down
8 changes: 8 additions & 0 deletions frontend/messages/fr/error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"error": {
"title": "Contenu momentanément indisponible",
"description": "Cette page n'a pas pu charger son contenu. La panne vient de notre côté, pas de votre navigateur.",
"retry": "Réessayer",
"backToHome": "Retour à l'accueil"
}
}
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "vitest run --project storybook",
"test:unit": "vitest run --project unit",
"test:all": "vitest run",
"test:watch": "vitest --project storybook",
"typecheck": "tsc --noEmit"
},
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/app/[locale]/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';
import AboutPage from './about';

export async function generateMetadata(
props: { params: Promise<{ locale: string }> },
) {
const { locale } = await props.params;

Check warning on line 9 in frontend/src/app/[locale]/about/page.tsx

View workflow job for this annotation

GitHub Actions / frontend-quality

'locale' is assigned a value but never used. Allowed unused vars must match /^_/u

const { data } = await fetchAboutPageData();

Expand Down Expand Up @@ -74,12 +75,8 @@


export default async function Page() {
const { data, error } = await fetchAboutPageData();
const result = await fetchAboutPageData();
const data = requireCmsData<AboutPageData>(result, 'singleton', '/about');

if (!data?.data) {
console.error(error);
return null;
}

return <AboutPage data={data.data} />;
return <AboutPage data={data} />;
};
14 changes: 5 additions & 9 deletions frontend/src/app/[locale]/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import ArticlePage from './article';
import { getMarkdownContent } from '@/lib/markdown';

Expand Down Expand Up @@ -48,14 +49,9 @@ export async function generateMetadata(

export default async function Page(props: { params: Promise<{ slug: string }> }) {
const { slug } = await props.params;
const { data } = await fetchBlogPageData(slug);

if (!data?.data || !data.data.length) {
return null;
}

const blogPageData = data.data[0] as BlogPageData;
const contentHtml = await getMarkdownContent(blogPageData.content)
const result = await fetchBlogPageData(slug);
const [blogPageData] = requireCmsData<BlogPageData[]>(result, 'detail', `/blogs?slug=${slug}`);
const contentHtml = await getMarkdownContent(blogPageData.content);

return <ArticlePage blog={{...blogPageData, contentHtml}} />;
return <ArticlePage blog={{ ...blogPageData, contentHtml }} />;
};
9 changes: 6 additions & 3 deletions frontend/src/app/[locale]/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import BlogPage from './blog';
import client from '@/lib/strapi-client';
import { cmsUnavailable, requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -52,10 +53,12 @@ export type BlogsPageMeta = NonNullable<NonNullable<BlogsPageResponse["meta"]>["

export default async function Page() {
const response = await fetchBlogsPageData();
const data = requireCmsData<BlogsPageData>(response, 'singleton', '/resources');
const pagination = response.data?.meta?.pagination;

if (!response?.data || !response?.data?.meta?.pagination) {
return null;
if (!pagination) {
throw cmsUnavailable('/resources', 'pagination absente de la reponse');
}

return <BlogPage data={response.data?.data} pagination={response.data.meta.pagination} />;
return <BlogPage data={data} pagination={pagination} />;
};
12 changes: 6 additions & 6 deletions frontend/src/app/[locale]/climate-and-biodiversity/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import ClimatePage from './climate';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -92,12 +93,11 @@ export type ThematicPageData = NonNullable<NonNullable<Awaited<ReturnType<typeof
export type ThematicsData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchThematics>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchThematicPageData();
const { data: thematicsData } = await fetchThematics();
const themeResult = await fetchThematicPageData();
const thematicsResult = await fetchThematics();

if (!data?.data || !thematicsData?.data) {
return null;
}
const data = requireCmsData<ThematicPageData>(themeResult, 'singleton', '/climate-and-biodiversity');
const thematicsData = requireCmsData<ThematicsData>(thematicsResult, 'singleton', '/thematics');

return <ClimatePage data={data.data} thematicsData={thematicsData.data} />;
return <ClimatePage data={data} thematicsData={thematicsData} />;
};
12 changes: 6 additions & 6 deletions frontend/src/app/[locale]/democracy/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import DemocracyPage from './democracy';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -70,12 +71,11 @@ export type ThematicPageData = NonNullable<NonNullable<Awaited<ReturnType<typeof
export type ThematicsData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchThematics>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchThematicPageData();
const { data: thematicsData } = await fetchThematics();
const themeResult = await fetchThematicPageData();
const thematicsResult = await fetchThematics();

if (!data?.data || !thematicsData?.data) {
return null;
}
const data = requireCmsData<ThematicPageData>(themeResult, 'singleton', '/democracy');
const thematicsData = requireCmsData<ThematicsData>(thematicsResult, 'singleton', '/thematics');

return <DemocracyPage data={data.data} thematicsData={thematicsData.data} />;
return <DemocracyPage data={data} thematicsData={thematicsData} />;
};
12 changes: 5 additions & 7 deletions frontend/src/app/[locale]/donations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import DonationsPage from './donation';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';
import { getMarkdownContent } from '@/lib/markdown';

Expand Down Expand Up @@ -48,12 +49,9 @@ async function fetchDonationData() {
export type DonationsData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchDonationData>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchDonationData();

if (!data?.data) {
return null;
}
const result = await fetchDonationData();
const data = requireCmsData<DonationsData>(result, 'singleton', '/donation');

const introduction_text = await getMarkdownContent(data.data.introduction_text);
return <DonationsPage data={{ ...data.data, introduction_text }} />;
const introduction_text = await getMarkdownContent(data.introduction_text);
return <DonationsPage data={{ ...data, introduction_text }} />;
};
42 changes: 42 additions & 0 deletions frontend/src/app/[locale]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { useEffect } from 'react';
import { useTranslations } from 'next-intl';
import { Button, Title } from '@/components';

export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
const t = useTranslations('error');

useEffect(() => {
// Le serveur a deja journalise la cause avec l'adresse appelee. Ici, c'est
// la console du navigateur qui parle, utile pendant le developpement.
console.error(error);
}, [error]);

return (
<div className="container my-lg pt-lg">
<div className="max-w-2xl mx-auto text-center">
<Title variant="medium" className="mb-xs">
{t('title')}
</Title>
<p className="lead mb-md">
{t('description')}
</p>
<div className="flex flex-wrap items-center justify-center gap-sm">
<Button variant="primary" color="black" onClick={() => reset()}>
{t('retry')}
</Button>
<Button href="/" variant="tertiary" color="black" hasArrow={false}>
{t('backToHome')}
</Button>
</div>
</div>
</div>
);
}
9 changes: 6 additions & 3 deletions frontend/src/app/[locale]/events/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import EventsPage from './events';
import client from '@/lib/strapi-client';
import { cmsUnavailable, requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -52,10 +53,12 @@ export default async function Page(
const pageSize = 6;

const response = await fetchEventPageData(page, pageSize);
const data = requireCmsData<EventsPageData>(response, 'singleton', '/events');
const pagination = response.data?.meta?.pagination;

if (!response?.data || !response?.data.meta?.pagination) {
return null;
if (!pagination) {
throw cmsUnavailable('/events', 'pagination absente de la reponse');
}

return <EventsPage data={response.data?.data} pagination={response.data.meta.pagination} currentPage={page} />;
return <EventsPage data={data} pagination={pagination} currentPage={page} />;
};
10 changes: 4 additions & 6 deletions frontend/src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Homepage from './home';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -73,11 +74,8 @@ async function fetchHomepageData() {
export type HomepageData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchHomepageData>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchHomepageData();
const result = await fetchHomepageData();
const data = requireCmsData<HomepageData>(result, 'singleton', '/home-page');

if (!data?.data) {
return null;
}

return <Homepage data={data.data} />;
return <Homepage data={data} />;
}
10 changes: 4 additions & 6 deletions frontend/src/app/[locale]/positions/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PositionsPage from './positions';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -80,11 +81,8 @@ async function fetchPositionPageData() {
export type PositionsPageData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchPositionPageData>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchPositionPageData();

if (!data?.data) {
return null;
}
const result = await fetchPositionPageData();
const data = requireCmsData<PositionsPageData>(result, 'singleton', '/position');

return <PositionsPage data={data.data} />;
return <PositionsPage data={data} />;
};
11 changes: 4 additions & 7 deletions frontend/src/app/[locale]/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import ProjectDetailPage from './projectDetail';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { getMarkdownContent } from '@/lib/markdown';

async function fetchProjectPageData(slug: string) {
Expand Down Expand Up @@ -82,16 +83,12 @@ export async function generateMetadata(

export default async function Page(props: { params: Promise<{ slug: string }> }) {
const { slug } = await props.params;
const { data } = await fetchProjectPageData(slug);

if (!data?.data || !data.data.length) {
return null;
}
const result = await fetchProjectPageData(slug);
const [projectData] = requireCmsData<ProjectPageData[]>(result, 'detail', `/projects?slug=${slug}`);

const projectData = data.data[0] as ProjectPageData;
const context = await getMarkdownContent(projectData.context);
const long_description = await getMarkdownContent(projectData.long_description);
const delivrable = await getMarkdownContent(projectData.delivrable);

return <ProjectDetailPage project={{...projectData, context, long_description, delivrable}} />;
return <ProjectDetailPage project={{ ...projectData, context, long_description, delivrable }} />;
};
10 changes: 4 additions & 6 deletions frontend/src/app/[locale]/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import ProjectsPage from './projects';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -68,11 +69,8 @@ export type ProjectListPageData = NonNullable<NonNullable<Awaited<ReturnType<typ


export default async function Page() {
const { data } = await fetchProjectListPageData();

if (!data?.data) {
return null;
}
const result = await fetchProjectListPageData();
const data = requireCmsData<ProjectListPageData>(result, 'singleton', '/projects-list');

return <ProjectsPage data={data.data} />;
return <ProjectsPage data={data} />;
};
12 changes: 6 additions & 6 deletions frontend/src/app/[locale]/social-justice/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import SocialPage from './social';
import client from '@/lib/strapi-client';
import { requireCmsData } from '@/lib/cms-guard';
import { generateMetadataFromSeo } from '@/lib/utils';

export async function generateMetadata(
Expand Down Expand Up @@ -70,12 +71,11 @@ export type ThematicPageData = NonNullable<NonNullable<Awaited<ReturnType<typeof
export type ThematicsData = NonNullable<NonNullable<Awaited<ReturnType<typeof fetchThematics>>["data"]>["data"]>;

export default async function Page() {
const { data } = await fetchThematicPageData();
const { data: thematicsData } = await fetchThematics();
const themeResult = await fetchThematicPageData();
const thematicsResult = await fetchThematics();

if (!data?.data || !thematicsData?.data) {
return null;
}
const data = requireCmsData<ThematicPageData>(themeResult, 'singleton', '/social-justice');
const thematicsData = requireCmsData<ThematicsData>(thematicsResult, 'singleton', '/thematics');

return <SocialPage data={data.data} thematicsData={thematicsData.data} />;
return <SocialPage data={data} thematicsData={thematicsData} />;
};
1 change: 1 addition & 0 deletions frontend/src/i18n/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
...(await import(`../../messages/${locale}/climate.json`)).default,
...(await import(`../../messages/${locale}/democracy.json`)).default,
...(await import(`../../messages/${locale}/donations.json`)).default,
...(await import(`../../messages/${locale}/error.json`)).default,
...(await import(`../../messages/${locale}/events.json`)).default,
...(await import(`../../messages/${locale}/home.json`)).default,
...(await import(`../../messages/${locale}/layout.json`)).default,
Expand Down
Loading
Loading