Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ajoute la date de mise à jour aux carte d'actu #3509

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
}
.card .content {
@include utilities.text-medium;
// FIXME (GAFI 11-12-2024): Idéalement .content est sur <p> plutôt que parent du titre
& p {
@include utilities.line-clamp(3, 1.2);
}
Expand Down Expand Up @@ -33,16 +32,24 @@
object-fit: cover;
}

.content {
padding-block: 1.25rem;
& .content {
padding-bottom: 1.25rem;
padding-top: .5rem;
padding-inline: 1.5rem;
display: flex;
gap: .5rem;
flex: 1;
flex-direction: column;
}

.title {
margin-bottom: 0.5rem;
& time {
align-self: flex-end;
@include utilities.text-small;
}
& time:empty {
visibility: hidden;
// NOTE (GAFI 17-12-2024): equivalent à 1lh dans ce cas précis mais meilleur support
height: 1.2em;
}

& a {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import { anActualite } from '../../../../server/actualites/domain/actualite.fixture';
import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { JsDateService } from '~/client/services/date/js/js.date.service';
import { anActualite } from '~/server/actualites/domain/actualite.fixture';

import ActualiteCard from './ActualiteCard';

const meta: Meta<typeof ActualiteCard> = {
Expand All @@ -14,6 +17,11 @@ const meta: Meta<typeof ActualiteCard> = {
actualite: anActualite(),
},
component: ActualiteCard,
render: (args) => (
<DependenciesProvider dateService={new JsDateService()}>
<ActualiteCard {...args} />
</DependenciesProvider>
),
title: 'Components/Cards/ActualiteCard',
};

Expand All @@ -34,3 +42,8 @@ export const ContenuCourt: Story = {
}),
},
};
export const avecDateMiseAJour: Story = {
args: {
actualite: anActualite({ dateMiseAJour: new Date('2024-01-01') }),
},
};
44 changes: 23 additions & 21 deletions src/client/components/features/Actualites/ActualiteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import classNames from 'classnames';
import React from 'react';

import { Card } from '~/client/components/ui/Card/Card';
import Date from '~/client/components/ui/Date';
import { Link } from '~/client/components/ui/Link/Link';
import { useIsInternalLink } from '~/client/hooks/useIsInternalLink';
import { Actualite } from '~/server/actualites/domain/actualite';
import { getExtraitContenu } from '~/server/cms/infra/repositories/strapi.utils';

Expand All @@ -16,30 +18,30 @@ type ActualiteCardProps = Omit<React.ComponentPropsWithRef<typeof Card>, 'layout
};

export default function ActualiteCard({ actualite, headingLevel = 'h2', className, ...rest }: ActualiteCardProps) {
// FIXME (GAFI 14-11-2024): Passer plutôt par actualite.lien, actualite.article n'est pas utilisé dans le composant
// ou bien utiliser actualite.article.slug dans le composant
const isExternalLink = actualite.article == null;
const isInternalLink = useIsInternalLink(actualite.link);

const extrait = getExtraitContenu(actualite.contenu);

return (
<Card className={classNames(styles.card, className)} layout='vertical' {...rest}>
{actualite.bannière && (
<Card.Image
src={actualite.bannière.src}
alt={actualite.bannière.alt}
className={styles.imgWrapper}
width={320}
height={180} />
)}
<Card.Content className={styles.content}>
<Card.Title className={styles.title} titleAs={headingLevel}>{actualite.titre}</Card.Title>
<p>{extrait}</p>
<Link appearance={'asQuaternaryButton'} href={actualite.link}>
{isExternalLink ? 'En savoir plus' : "Lire l'article"}
<Link.Icon />
</Link>
</Card.Content>
</Card>
<article>
<Card className={classNames(styles.card, className)} layout="vertical" {...rest}>
{actualite.bannière && (
<Card.Image
src={actualite.bannière.src}
alt={actualite.bannière.alt}
width={320}
height={180} />
)}
<Card.Content className={styles.content}>
{actualite.dateMiseAJour ? <Date date={actualite.dateMiseAJour} /> : <time></time>}
<Card.Title className={styles.title} titleAs={headingLevel}>{actualite.titre}</Card.Title>
<p>{extrait}</p>
<Link appearance={'asQuaternaryButton'} href={actualite.link}>
{isInternalLink ? 'Lire l\'article' : 'En savoir plus'}
<Link.Icon />
</Link>
</Card.Content>
</Card>
</article>
);
}
30 changes: 30 additions & 0 deletions src/client/components/ui/Date/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { JsDateService } from '~/client/services/date/js/js.date.service';

import DateComponent from '.';

const meta: Meta<typeof DateComponent> = {
argTypes: {
date: {
control: 'date',
},
},
args: {
date: new Date(),
},
component: DateComponent,
render: ({ date, ...args }) => (
<DependenciesProvider dateService={new JsDateService()}>
<DateComponent date={new Date(date)} {...args} />
</DependenciesProvider>
),
title: 'Components/Date',
};

export default meta;

type Story = StoryObj<React.ComponentPropsWithRef<typeof DateComponent>>;
export const Default: Story = {};
35 changes: 35 additions & 0 deletions src/client/components/ui/Date/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @jest-environment jsdom
*/

import { render, screen } from '@testing-library/react';

import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { JsDateService } from '~/client/services/date/js/js.date.service';

import DateComponent from '.';

describe('<Date />', () => {
it('affiche la date dans un format compréhensible', () => {
const date = new Date('2024-01-01');

render(
<DependenciesProvider dateService={new JsDateService()}>
<DateComponent date={date} />
</DependenciesProvider>,
);

expect(screen.getByRole('time')).toHaveTextContent('1 janvier 2024');
});
it('indique la date programmatiquement au format ISO', () => {
const date = new Date('2024-01-01');

render(
<DependenciesProvider dateService={new JsDateService()}>
<DateComponent date={date} />
</DependenciesProvider>,
);

expect(screen.getByRole('time')).toHaveAttribute('datetime', '2024-01-01T00:00:00.000Z');
});
});
16 changes: 16 additions & 0 deletions src/client/components/ui/Date/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

import { useDependency } from '~/client/context/dependenciesContainer.context';
import { DateService } from '~/client/services/date/date.service';

type DateProps = React.ComponentPropsWithoutRef<'time'> & {
date: Date,
}

export default function Date({ date, ...props }: DateProps) {
const dateService = useDependency<DateService>('dateService');

return (
<time dateTime={date.toISOString()} {...props}>{dateService.formatToHumanReadableDate(date)}</time>
);
};
2 changes: 1 addition & 1 deletion src/pages/actualites/index.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { mockUseRouter } from '~/client/components/useRouter.mock';
import { mockScrollIntoView, mockSmallScreen } from '~/client/components/window.mock';
import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { aManualAnalyticsService } from '~/client/services/analytics/analytics.service.fixture';
import ActualitesPage, { getStaticProps } from '~/pages/actualites/index.page';
import { ActualitesPage, getStaticProps } from '~/pages/actualites/index.page';
import { anActualite, anActualiteList } from '~/server/actualites/domain/actualite.fixture';
import { createFailure, createSuccess } from '~/server/errors/either';
import { ErreurMetier } from '~/server/errors/erreurMetier.types';
Expand Down
28 changes: 24 additions & 4 deletions src/pages/actualites/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ import useAnalytics from '~/client/hooks/useAnalytics';
import { Actualite } from '~/server/actualites/domain/actualite';
import { isFailure } from '~/server/errors/either';
import { dependencies } from '~/server/start';
import { ISODateTime } from '~/shared/ISODateTime';

import analytics from './index.analytics';
import styles from './index.module.scss';

interface ActualitesPageProps {
cartesActualites: Array<Actualite>
}

const MAX_VISIBLE_ACTUALITES = 3;
export default function ActualitesPage({ cartesActualites }: ActualitesPageProps) {

export function ActualitesPage({ cartesActualites }: ActualitesPageProps) {
useAnalytics(analytics);

const articleCardList = useMemo(() => {
Expand Down Expand Up @@ -54,7 +55,26 @@ export default function ActualitesPage({ cartesActualites }: ActualitesPageProps
);
}

export async function getStaticProps(): Promise<GetStaticPropsResult<ActualitesPageProps>> {
type SerializedActualite = Omit<Actualite, 'dateMiseAJour'> & {
dateMiseAJour?: ISODateTime
}
interface SerializedActualitesPageProps {
cartesActualites: Array<SerializedActualite>
}
function deserialize(actualites: Array<SerializedActualite>): Array<Actualite> {
return actualites.map((actualite) => ({
...actualite,
dateMiseAJour: actualite.dateMiseAJour ? new Date(actualite.dateMiseAJour) : undefined,
}));
}
function serialize(cartesActualitesResponse: Array<Actualite>): Array<SerializedActualite> {
return JSON.parse(JSON.stringify(cartesActualitesResponse));
}
export default function Deserialize(props: SerializedActualitesPageProps) {
const deserializedActus = deserialize(props.cartesActualites);
return <ActualitesPage cartesActualites={deserializedActus} />;
}
export async function getStaticProps(): Promise<GetStaticPropsResult<SerializedActualitesPageProps>> {
const isEspaceJeuneVisible = process.env.NEXT_PUBLIC_OLD_ESPACE_JEUNE_FEATURE === '0';
if (!isEspaceJeuneVisible) {
return { notFound: true };
Expand All @@ -68,7 +88,7 @@ export async function getStaticProps(): Promise<GetStaticPropsResult<ActualitesP

return {
props: {
cartesActualites: JSON.parse(JSON.stringify(cartesActualitesResponse.result)),
cartesActualites: serialize(cartesActualitesResponse.result),
},
revalidate: dependencies.cmsDependencies.duréeDeValiditéEnSecondes(),
};
Expand Down
2 changes: 1 addition & 1 deletion src/pages/espace-jeune/index.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { mockUseRouter } from '~/client/components/useRouter.mock';
import { mockScrollIntoView, mockSmallScreen } from '~/client/components/window.mock';
import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { aManualAnalyticsService } from '~/client/services/analytics/analytics.service.fixture';
import EspaceJeunePage, { getStaticProps } from '~/pages/espace-jeune/index.page';
import { EspaceJeunePage, getStaticProps } from '~/pages/espace-jeune/index.page';
import { anActualite, anActualiteList } from '~/server/actualites/domain/actualite.fixture';
import { aServiceJeune, aServiceJeuneList } from '~/server/services-jeunes/domain/servicesJeunes.fixture';

Expand Down
35 changes: 28 additions & 7 deletions src/pages/espace-jeune/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@ import { LightHero, LightHeroPrimaryText, LightHeroSecondaryText } from '~/clien
import SeeMoreItemList from '~/client/components/ui/SeeMore/SeeMoreItemList';
import useAnalytics from '~/client/hooks/useAnalytics';
import analytics from '~/pages/espace-jeune/index.analytics';
import { Accueil } from '~/pages/index.page';
import { Actualite } from '~/server/actualites/domain/actualite';
import { isFailure } from '~/server/errors/either';
import { ServiceJeune } from '~/server/services-jeunes/domain/servicesJeunes';
import { dependencies } from '~/server/start';
import { ISODateTime } from '~/shared/ISODateTime';

import styles from './index.module.scss';

interface EspaceJeunePageProps {
cartesActualites: Actualite[]
type EspaceJeunePageProps = {
cartesActualites: Array<Actualite>
serviceJeuneList: Array<ServiceJeune>
}

const MAX_VISIBLE_ACTUALITES_LENGTH = 3;

export default function EspaceJeunePage({ cartesActualites, serviceJeuneList }: EspaceJeunePageProps) {
export function EspaceJeunePage({ cartesActualites, serviceJeuneList }: EspaceJeunePageProps) {
useAnalytics(analytics);

const articleCardList: React.ReactNode[] = useMemo(() => {
Expand Down Expand Up @@ -72,7 +73,27 @@ export default function EspaceJeunePage({ cartesActualites, serviceJeuneList }:
);
}

export async function getStaticProps(): Promise<GetStaticPropsResult<EspaceJeunePageProps>> {
type SerializedActualite = Omit<Actualite, 'dateMiseAJour'> & {
dateMiseAJour?: ISODateTime
}
type SerializedEspaceJeunePageProps = {
cartesActualites: Array<SerializedActualite>
serviceJeuneList: Array<ServiceJeune>
}
function deserialize(actualites: Array<SerializedActualite>): Array<Actualite> {
return actualites.map((actualite) => ({
...actualite,
dateMiseAJour: actualite.dateMiseAJour ? new Date(actualite.dateMiseAJour) : undefined,
}));
}
function serialize<SerializedType, InitialType>(cartesActualitesResponse: InitialType): SerializedType {
return JSON.parse(JSON.stringify(cartesActualitesResponse));
}
export default function Deserialize(props: SerializedEspaceJeunePageProps) {
const deserializedActus = deserialize(props.cartesActualites);
return <Accueil actualites={deserializedActus} />;
}
export async function getStaticProps(): Promise<GetStaticPropsResult<SerializedEspaceJeunePageProps>> {
const isEspaceJeuneVisible = process.env.NEXT_PUBLIC_OLD_ESPACE_JEUNE_FEATURE === '1';
if (!isEspaceJeuneVisible) {
return { notFound: true };
Expand All @@ -87,8 +108,8 @@ export async function getStaticProps(): Promise<GetStaticPropsResult<EspaceJeune

return {
props: {
cartesActualites: JSON.parse(JSON.stringify(cartesActualitesResponse.result)),
serviceJeuneList: JSON.parse(JSON.stringify(serviceJeuneList.result)),
cartesActualites: serialize(cartesActualitesResponse.result),
serviceJeuneList: serialize(serviceJeuneList.result),
},
revalidate: dependencies.cmsDependencies.duréeDeValiditéEnSecondes(),
};
Expand Down
2 changes: 1 addition & 1 deletion src/pages/index.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { mockScrollIntoView, mockSmallScreen } from '~/client/components/window.
import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { ManualAnalyticsService } from '~/client/services/analytics/analytics.service';
import { aManualAnalyticsService } from '~/client/services/analytics/analytics.service.fixture';
import Accueil, { getStaticProps } from '~/pages/index.page';
import { Accueil, getStaticProps } from '~/pages/index.page';
import { Actualite } from '~/server/actualites/domain/actualite';
import { anActualiteList, anActualiteLongList } from '~/server/actualites/domain/actualite.fixture';
import { createFailure, createSuccess } from '~/server/errors/either';
Expand Down
Loading
Loading