Skip to content

Commit

Permalink
feat: allow custom colors for actu tags
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurlbrjc committed Dec 5, 2024
1 parent 1f948bf commit bffdef2
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 79 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ NEXT_PUBLIC_APM_URL=APM_URL
NEXT_PUBLIC_API_ENDPOINT=NEXT_PUBLIC_API_ENDPOINT
NEXT_PUBLIC_ENABLE_ACTUS=true
NEXT_PUBLIC_WORDPRESS_ACTUS_MILO_LINK=actualites-milo.fr
NEXT_PUBLIC_WORDPRESS_ACTUS_TAGS=actualites-tags.fr
NEXTAUTH_URL=NEXTAUTH_URL
KEYCLOAK_ISSUER=KEYCLOAK_ISSUER
VERSION_CGU_CEJ_COURANTE=2023-09-29
Expand Down
21 changes: 18 additions & 3 deletions components/ActualitesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,28 @@ export default function ActualitesModal({ onClose }: ActualitesModalProps) {
{aDesActualites && (
<div className='p-6'>
{actualites?.articles.map((article) => (
<div
<article
className='bg-white px-4 py-2 rounded-base mb-4 [&_a]:underline [&_a]:text-primary [&_a]:hover:text-primary_darken [&_img]:max-w-[200px] [&_img]:my-4 [&_p]:text-grey_800'
key={article.id}
>
<h3 className='font-bold text-primary my-2'>{article.titre}</h3>
<header>
<h3 className='font-bold text-primary my-2'>{article.titre}</h3>
{article.etiquettes.length > 0 && (
<div className='flex gap-2'>
<span className='sr-only'>Catégories : </span>
{article.etiquettes.map((etiquette) => (
<span
key={etiquette.id}
className={`flex items-center w-fit text-s-medium text-${etiquette.couleur} px-3 bg-${etiquette.couleur}_lighten whitespace-nowrap rounded-full`}
>
{etiquette.nom}
</span>
))}
</div>
)}
</header>
{article.contenu}
</div>
</article>
))}
</div>
)}
Expand Down
39 changes: 33 additions & 6 deletions fixtures/actualites.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
import { ActualitesParsees, ActualitesRaw } from 'interfaces/actualites'

export const uneActualite = (): ActualitesParsees => {
export function uneActualite(): ActualitesParsees {
return {
articles: [
{
id: 'id-article',
contenu: <p>Rdv demain aux nouveaux locaux de la Fabrique</p>,
id: 1,
titre: 'Invitation à la journée présentiel du 31 octobre 2024',
etiquettes: [{ couleur: 'primary', id: 7, nom: 'Primaire' }],
contenu: [
<p key={0}>Rdv demain aux nouveaux locaux de la Fabrique</p>,
<a
key={1}
href='perdu.com'
target='_blank'
rel='noreferrer noopener'
className='inline-flex items-center whitespace-nowrap underline'
aria-label='Vous êtes perdu ? (nouvelle fenêtre)'
>
Vous êtes perdu ?
<svg
aria-hidden={true}
focusable={false}
viewBox='0 0 12 12'
className='ml-1 w-3 h-3'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M10 10.6667H2C1.63333 10.6667 1.33333 10.3667 1.33333 10V2C1.33333 1.63333 1.63333 1.33333 2 1.33333H5.33333C5.7 1.33333 6 1.03333 6 0.666667C6 0.3 5.7 0 5.33333 0H1.33333C0.593333 0 0 0.6 0 1.33333V10.6667C0 11.4 0.6 12 1.33333 12H10.6667C11.4 12 12 11.4 12 10.6667V6.66667C12 6.3 11.7 6 11.3333 6C10.9667 6 10.6667 6.3 10.6667 6.66667V10C10.6667 10.3667 10.3667 10.6667 10 10.6667ZM7.33333 0.666667C7.33333 1.03333 7.63333 1.33333 8 1.33333H9.72667L3.64 7.42C3.38 7.68 3.38 8.1 3.64 8.36C3.9 8.62 4.32 8.62 4.58 8.36L10.6667 2.27333V4C10.6667 4.36667 10.9667 4.66667 11.3333 4.66667C11.7 4.66667 12 4.36667 12 4V0H8C7.63333 0 7.33333 0.3 7.33333 0.666667Z'
fill='currentColor'
/>
</svg>
</a>,
],
},
],
dateDerniereModification: '2024-10-30',
}
}

export const uneActualiteRaw = (): ActualitesRaw => {
export function uneActualiteRaw(): ActualitesRaw {
return {
articles: [
{
id: 'id-article',
contenu: '<p>Rdv demain aux nouveaux locaux de la Fabrique</p>',
id: 1,
titre: 'Invitation à la journée présentiel du 31 octobre 2024',
etiquettes: [],
contenu:
'<p>Rdv demain aux nouveaux locaux de la Fabrique</p><a href="perdu.com">Vous êtes perdu ?</a>',
},
],
dateDerniereModification: '2024-10-30',
Expand Down
29 changes: 22 additions & 7 deletions interfaces/actualites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,44 @@ import { domToReact } from 'html-react-parser'

export type ActualitesRaw = {
articles: Array<{
contenu: string
id: string
id: number
titre: string
etiquettes: EtiquetteArticle[]
contenu: string
}>
dateDerniereModification: string
}

export type ActualitesParsees = {
articles: Array<{
contenu: ReturnType<typeof domToReact>
id: string
id: number
titre: ReturnType<typeof domToReact>
etiquettes: EtiquetteArticle[]
contenu: ReturnType<typeof domToReact>
}>
dateDerniereModification: string
}

export type ArticleJson = {
id: number
modified: string
content: {
title: {
rendered: string
}
title: {
tags: number[]
content: {
rendered: string
}
id: string
}

export type TagJson = {
id: number
name: string
description: string
}

export type EtiquetteArticle = {
id: number
nom: string
couleur: string
}
95 changes: 49 additions & 46 deletions services/actualites.service.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,79 @@
import sanitizeHtml from 'sanitize-html'

import { ActualitesRaw, ArticleJson } from 'interfaces/actualites'
import {
ActualitesRaw,
ArticleJson,
EtiquetteArticle,
TagJson,
} from 'interfaces/actualites'
import { StructureConseiller } from 'interfaces/conseiller'
import { fetchJson } from 'utils/httpClient'

export async function getActualites(
structure: StructureConseiller
): Promise<ActualitesRaw | undefined> {
const url = ((): string => {
switch (structure) {
case StructureConseiller.MILO:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_MILO_LINK as string
case StructureConseiller.POLE_EMPLOI:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FT_CEJ_LINK as string
case StructureConseiller.POLE_EMPLOI_BRSA:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FR_BRSA_LINK as string
case StructureConseiller.POLE_EMPLOI_AIJ:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FR_AIJ_LINK as string
case StructureConseiller.CONSEIL_DEPT:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_CD_LINK as string
case StructureConseiller.AVENIR_PRO:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_AVENIR_PRO_LINK as string
}
})()
const urlTags = process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_TAGS
const urlActualites = getUrlActualites(structure)
if (!urlTags || !urlActualites) return

if (!url) return
const [{ content: tagsJson }, { content: articlesJson }]: [
{ content: TagJson[] },
{ content: ArticleJson[] },
] = await Promise.all([fetchJson(urlTags), fetchJson(urlActualites)])
if (!articlesJson.length) return

const articlesJson = await fetchJson(url)

const derniereDateModification = articlesJson.content.reduce(
const derniereDateModification = articlesJson.reduce(
(latest: string, item: ArticleJson) => {
return new Date(item.modified) > new Date(latest) ? item.modified : latest
},
articlesJson.content[0].modified
articlesJson[0].modified
)

return {
articles: articlesJson.content.map((article: ArticleJson) => ({
articles: articlesJson.map((article: ArticleJson) => ({
id: article.id,
contenu: formaterArticle(article),
titre: article.title.rendered,
etiquettes: extraireEtiquettes(article, tagsJson),
contenu: extraireContenuAssaini(article),
})),
dateDerniereModification: derniereDateModification,
}
}

function formaterArticle({ content }: ArticleJson): string {
const contentAssaini = sanitizeHtml(content.rendered, {
function extraireContenuAssaini({ content }: ArticleJson): string {
return sanitizeHtml(content.rendered, {
disallowedTagsMode: 'recursiveEscape',
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
})

return ajouterTagCategorie(contentAssaini)
}

function ajouterTagCategorie(str: string): string {
const codeRegex = /<code\b[^>]*>([\s\S]*?)<\/code>/

const baliseCode = str.match(codeRegex)
if (!baliseCode) return str
const baliseCodeContent = baliseCode[1]

const tags = baliseCodeContent.split(',').map((word) => word.trim())

const categories = tags
.map((tag) => {
return `<span className='flex items-center w-fit text-s-medium text-additional_3 px-3 bg-additional_3_lighten whitespace-nowrap rounded-full'>${tag}</span>`
})
.join('')
function extraireEtiquettes(
article: ArticleJson,
tagsJson: TagJson[]
): EtiquetteArticle[] {
return article.tags
.map((idTag) => tagsJson.find(({ id }) => id === idTag))
.filter((tag) => tag !== undefined)
.map((tag) => ({
id: tag!.id,
nom: tag!.name,
couleur: tag!.description,
}))
}

return str.replace(
/<pre\b[^>]*>[\s\S]*?<\/pre>/g,
`<div className='flex gap-2'>${categories}</div>`
)
function getUrlActualites(structure: StructureConseiller) {
switch (structure) {
case StructureConseiller.MILO:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_MILO_LINK as string
case StructureConseiller.POLE_EMPLOI:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FT_CEJ_LINK as string
case StructureConseiller.POLE_EMPLOI_BRSA:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FR_BRSA_LINK as string
case StructureConseiller.POLE_EMPLOI_AIJ:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_FR_AIJ_LINK as string
case StructureConseiller.CONSEIL_DEPT:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_CD_LINK as string
case StructureConseiller.AVENIR_PRO:
return process.env.NEXT_PUBLIC_WORDPRESS_ACTUS_AVENIR_PRO_LINK as string
}
}
15 changes: 10 additions & 5 deletions tests/components/ActualitesModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { axe } from 'jest-axe'

import ActualitesModal from 'components/ActualitesModal'
import { unConseiller } from 'fixtures/conseiller'
import { ActualitesParsees } from 'interfaces/actualites'
import { ActualitesProvider } from 'utils/actualitesContext'
import { ConseillerProvider } from 'utils/conseiller/conseillerContext'

Expand All @@ -13,20 +14,22 @@ describe('ActualitesModal', () => {

describe('quand le conseiller a des actualités', () => {
const article = {
id: 1,
titre: 'Invitation à la journée présentiel du 31 octobre 2024',
etiquettes: [
{ id: 35, nom: 'Recette', couleur: 'additional_3' },
{ id: 42, nom: 'Test', couleur: 'additional_2' },
],
contenu: (
<>
<span>Catégorie</span>
<p>Rdv demain aux nouveaux locaux de la Fabrique</p>
<a href='www.google.com'>Google</a>
<img src='pouetImg.jpg' alt='pouet' />
</>
),
dateDerniereModification: '2024-01-01',
id: 'id-article-1',
}

const actualites = {
const actualites: ActualitesParsees = {
articles: [article],
dateDerniereModification: '2024-01-01',
}
Expand Down Expand Up @@ -59,7 +62,9 @@ describe('ActualitesModal', () => {
name: 'Invitation à la journée présentiel du 31 octobre 2024',
})
).toBeInTheDocument()
expect(screen.getByText('Catégorie')).toBeInTheDocument()
expect(screen.getByRole('banner')).toHaveTextContent(
'Invitation à la journée présentiel du 31 octobre 2024Catégories : RecetteTest'
)
expect(
screen.getByText('Rdv demain aux nouveaux locaux de la Fabrique')
).toBeInTheDocument()
Expand Down
35 changes: 24 additions & 11 deletions tests/services/actualites.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ActualitesRaw, ArticleJson, TagJson } from 'interfaces/actualites'
import { StructureConseiller } from 'interfaces/conseiller'
import { getActualites } from 'services/actualites.service'
import { fetchJson } from 'utils/httpClient'
Expand All @@ -8,48 +9,60 @@ describe('ActualitesService', () => {
describe('.getActualites', () => {
it('récupère les actualites', async () => {
//Given
const html = [
const tagsJson: TagJson[] = [
{ id: 7, name: 'Primaire', description: 'primary' },
{ id: 12, name: 'Secondaire', description: 'secondary' },
]
;(fetchJson as jest.Mock).mockResolvedValueOnce({
content: tagsJson,
})

const articlesJson: ArticleJson[] = [
{
content: {
rendered: 'Cette journée aura lieu au 35 rue de la République.',
},
id: 'id-article-1',
id: 1,
title: {
rendered: 'Invitation à la journée présentiel du 31 octobre 2024',
},
modified: '2024-11-20T15:38:54',
tags: [7],
},
{
content: { rendered: 'Retrouvez notre dernière note sur le blog.' },
id: 'id-article-2',
id: 2,
title: { rendered: 'Infolettre de novembre 2024' },
modified: '2024-11-19T15:38:54',
tags: [],
},
]

;(fetchJson as jest.Mock).mockResolvedValueOnce({
content: html,
content: articlesJson,
})

//When
const output = await getActualites(StructureConseiller.MILO)

//Then
expect(output).toEqual({
const actualites: ActualitesRaw = {
articles: [
{
contenu: 'Cette journée aura lieu au 35 rue de la République.',
id: 'id-article-1',
id: 1,
titre: 'Invitation à la journée présentiel du 31 octobre 2024',
etiquettes: [{ couleur: 'primary', id: 7, nom: 'Primaire' }],
contenu: 'Cette journée aura lieu au 35 rue de la République.',
},
{
contenu: 'Retrouvez notre dernière note sur le blog.',
id: 'id-article-2',
id: 2,
titre: 'Infolettre de novembre 2024',
etiquettes: [],
contenu: 'Retrouvez notre dernière note sur le blog.',
},
],
dateDerniereModification: '2024-11-20T15:38:54',
})
}
expect(output).toEqual(actualites)
})
})
})
Loading

0 comments on commit bffdef2

Please sign in to comment.