Skip to content

Commit 70c90f6

Browse files
bjlaaClemogdependabot[bot]paulsoucheincubateur-ademe-admin
authored
🔖 Release 2.42.0 (#1423)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Clément <55186402+Clemog@users.noreply.github.com> Co-authored-by: Benjamin Arias <12382534+bjlaa@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Clément <clem.auger@hotmail.fr> Co-authored-by: Paul Souche <paul@bettercallpaul.fr> Co-authored-by: Bot Incubateur Ademe <153178900+incubateur-ademe-admin@users.noreply.github.com>
1 parent cbb64e7 commit 70c90f6

File tree

17 files changed

+209
-94
lines changed

17 files changed

+209
-94
lines changed

‎.husky/pre-push-default‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@ yarn lint || exit 1
44

55
# Run TypeScript type checks and exit if there are errors
66
yarn lint:types || exit 1
7-
8-
yarn test

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "nosgestesclimat-site-nextjs",
33
"license": "MIT",
4-
"version": "2.41.0",
4+
"version": "2.42.0",
55
"description": "The leading open source climate footprint calculator",
66
"type": "module",
77
"repository": {

‎src/app/[locale]/(narrow-layout)/(pages-statiques)/politique-de-confidentialite/policy_en.mdx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,6 @@ Data Subjects may communicate their Personal Data to ADEME through various means
107107

108108
## 5. Processing purposes and legal bases
109109

110-
For temporary and specific partnerships (contests, prizes to be won), ADEME's partners receive only email and identification data to contact winners and provide them with their prizes
111-
112-
113-
## 6\. How long is personal data retained?
114110
The purposes of Personal Data Processing carried out by ADEME are based on the following legal bases: contractual execution, consent of Data Subjects, ADEME's legal and regulatory obligations, its legitimate interest or public interest mission.
115111

116112
The purposes associated with each legal basis are listed below:

‎src/app/[locale]/(simulation)/(large-layout)/profil/_components/simulationBanner/_components/TutorialLink.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { profilClickTutoriel } from '@/constants/tracking/pages/profil'
66
import ButtonLink from '@/design-system/buttons/ButtonLink'
77
import { getLinkToTutoriel } from '@/helpers/navigation/simulateurPages'
88
import { useLocale } from '@/hooks/useLocale'
9+
import { useSearchParams } from 'next/navigation'
910
import { twMerge } from 'tailwind-merge'
1011

1112
type Props = {
@@ -14,11 +15,12 @@ type Props = {
1415

1516
export default function TutorialLink({ className }: Props) {
1617
const locale = useLocale()
18+
const searchParams = useSearchParams()
1719

1820
return (
1921
<ButtonLink
2022
color="text"
21-
href={getLinkToTutoriel({ locale })}
23+
href={getLinkToTutoriel({ locale, currentSearchParams: searchParams })}
2224
className={twMerge('flex w-full justify-center', className)}
2325
trackingEvent={profilClickTutoriel}>
2426
<GlassesIcon className="fill-primary-700 mr-2" />

‎src/app/[locale]/(simulation)/(large-layout-nosticky)/fin/page.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export default function FinPage() {
3838
const { currentMetric } = useCurrentMetric()
3939

4040
const isIframe = getIsIframe()
41-
const { isIframeShareData, isFrenchRegion } = useIframe()
41+
42+
const { isFrenchRegion, isIframeShareData } = useIframe()
4243

4344
useEffect(() => {
4445
const titleTags = document.querySelectorAll('head > title')

‎src/app/[locale]/_components/mainLayoutProviders/IframeOptionsContext.tsx‎

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import { getIsFrenchRegion } from '@/helpers/regions/getIsFrenchRegion'
44
import { useTrackIframe } from '@/hooks/tracking/useTrackIframe'
5-
import { useIsClient } from '@/hooks/useIsClient'
65
import { useUser } from '@/publicodes-state'
76
import { getIsIframe } from '@/utils/getIsIframe'
7+
import { useSearchParams } from 'next/navigation'
88
import { createContext, useEffect, useState } from 'react'
99

1010
export const IframeOptionsContext = createContext<{
@@ -14,110 +14,83 @@ export const IframeOptionsContext = createContext<{
1414
isIframeOnlySimulation?: boolean
1515
iframeLang?: string | null
1616
isFrenchRegion?: boolean
17+
containerRef?: React.RefObject<HTMLDivElement | null>
1718
}>({})
1819

19-
const nullDecode = (string: string) =>
20-
string == null ? string : decodeURIComponent(string)
21-
2220
export const IframeOptionsProvider = ({
2321
children,
2422
}: {
2523
children: (
2624
containerRef: React.RefObject<HTMLDivElement | null>
2725
) => React.ReactNode
2826
}) => {
29-
const isClient = useIsClient()
30-
const isIframe = isClient && getIsIframe()
31-
32-
const [iframeIntegratorOptions, setIframeIntegratorOptions] = useState({
33-
integratorLogo: null,
34-
integratorName: null,
35-
integratorActionUrl: null,
36-
integratorYoutubeVideo: null,
37-
integratorActionText: null,
38-
})
27+
const searchParams = useSearchParams()
28+
const { user } = useUser()
29+
30+
// Detect iframe mode using window check
31+
const isIframe = typeof window !== 'undefined' && getIsIframe()
32+
3933
const [isIframeShareData, setIsIframeShareData] = useState(false)
4034
const [isIframeOnlySimulation, setIsIframeOnlySimulation] = useState(false)
4135
const [iframeLang, setIframeLang] = useState<string | null>(null)
4236
const [iframeRegion, setIframeRegion] = useState<string | null>(null)
4337

4438
const containerRef = useTrackIframe(isIframe)
4539

40+
// Read iframe parameters from URL
4641
useEffect(() => {
47-
if (isIframe) return
48-
49-
const urlParams = new URLSearchParams(window.location.search)
50-
51-
setIframeIntegratorOptions(
52-
Object.fromEntries(
53-
[
54-
'integratorLogo',
55-
'integratorName',
56-
'integratorActionUrl',
57-
'integratorYoutubeVideo',
58-
'integratorActionText',
59-
].map((key) => [
60-
key,
61-
nullDecode(
62-
new URLSearchParams(document.location.search).get(key) ?? ''
63-
),
64-
])
65-
) as any
66-
)
42+
if (!isIframe) return
6743

6844
if (!isIframeShareData) {
69-
setIsIframeShareData(Boolean(urlParams.get('shareData')))
45+
setIsIframeShareData(Boolean(searchParams.get('shareData')))
7046
}
7147

7248
if (!isIframeOnlySimulation) {
73-
setIsIframeOnlySimulation(Boolean(urlParams.get('onlySimulation')))
49+
setIsIframeOnlySimulation(Boolean(searchParams.get('onlySimulation')))
7450
}
7551

7652
if (!iframeRegion) {
77-
setIframeRegion(urlParams.get('region'))
53+
setIframeRegion(searchParams.get('region'))
7854
}
7955

8056
if (!iframeLang) {
81-
setIframeLang(urlParams.get('lang'))
57+
setIframeLang(searchParams.get('lang'))
8258
}
8359
// eslint-disable-next-line react-hooks/exhaustive-deps
84-
}, [isIframe])
60+
}, [isIframe, searchParams])
8561

62+
// Add body classes for iframe styling
8663
useEffect(() => {
8764
if (isIframe) {
88-
// Add class to body to modify the style of the page on iframe mode
8965
document.body.classList.add('iframe-mode')
9066
}
9167
}, [isIframe])
9268

9369
useEffect(() => {
9470
if (isIframeOnlySimulation) {
95-
// Add class to body that hides the header and the footer
9671
document.body.classList.add('iframeOnlySimulation')
9772
}
9873
}, [isIframeOnlySimulation])
9974

100-
const { user } = useUser()
101-
10275
const regionCode = user?.region?.code
10376

10477
const isFrenchRegion = getIsFrenchRegion({
105-
isIframe,
78+
isIframe: isIframe ?? false,
10679
iframeRegion: regionCode,
10780
})
10881

10982
return (
11083
<IframeOptionsContext.Provider
11184
value={{
112-
...iframeIntegratorOptions,
11385
isIframeShareData,
11486
iframeRegion: regionCode,
11587
isIframe,
11688
isIframeOnlySimulation,
11789
iframeLang,
11890
isFrenchRegion,
91+
containerRef,
11992
}}>
120-
{children(containerRef)}
93+
{children(containerRef ?? { current: null })}
12194
</IframeOptionsContext.Provider>
12295
)
12396
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { ArticleType } from '@/adapters/cmsClient'
2+
import Trans from '@/components/translation/trans/TransServer'
3+
import type { Locale } from '@/i18nConfig'
4+
5+
type Props = {
6+
article: ArticleType
7+
locale: Locale
8+
}
9+
10+
export default function ArticleDate({ article, locale }: Props) {
11+
const createdAt = article.createdAt
12+
? new Date(article.createdAt).toLocaleDateString('fr')
13+
: null
14+
const updatedAt = article.updatedAt
15+
? new Date(article.updatedAt).toLocaleDateString('fr')
16+
: null
17+
18+
return (
19+
<>
20+
<p className="mb-0">
21+
<span className="text-primary-600">
22+
<Trans locale={locale}>Publié le :</Trans>
23+
</span>{' '}
24+
{createdAt}
25+
</p>
26+
{updatedAt && updatedAt !== createdAt && (
27+
<p className="mb-0">
28+
<span className="text-primary-600">
29+
<Trans locale={locale}>Dernière mise à jour :</Trans>
30+
</span>{' '}
31+
{updatedAt}
32+
</p>
33+
)}
34+
</>
35+
)
36+
}

‎src/app/[locale]/blog/[category]/[article]/page.tsx‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { DefaultPageProps } from '@/types'
1313
import Image from 'next/image'
1414
import { redirect } from 'next/navigation'
1515
import ArticleBreadcrumbs from './_components/ArticleBreadcrumbs'
16+
import ArticleDate from './_components/ArticleDate'
1617
import ArticleJSONLD from './_components/ArticleJSONLD'
1718
import AuthorBlock from './_components/AuthorBlock'
1819
import OtherArticles from './_components/OtherArticles'
@@ -106,25 +107,16 @@ export default async function ArticlePage({
106107
</Badge>
107108
</div>
108109

109-
<div className="flex flex-row gap-3">
110-
<p className="mb-0 text-lg">
110+
<div className="flex flex-col flex-wrap gap-1">
111+
<p className="mb-0">
111112
<span className="text-primary-600">
112113
<Trans locale={locale}>Temps de lecture :</Trans>
113114
</span>{' '}
114115
{Math.round(article.duration / 60)}{' '}
115116
<Trans locale={locale}>minutes</Trans>
116117
</p>
117118

118-
<span className="text-lg text-gray-500">|</span>
119-
120-
<p className="mb-0 text-lg">
121-
<span className="text-primary-600">
122-
<Trans locale={locale}>Publié le :</Trans>
123-
</span>{' '}
124-
{article.createdAt
125-
? new Date(article.createdAt).toLocaleDateString('fr')
126-
: ''}
127-
</p>
119+
<ArticleDate article={article} locale={locale} />
128120
</div>
129121
</div>
130122

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { ReadonlyURLSearchParams } from 'next/navigation'
2+
3+
/**
4+
* List of iframe-related parameters that should be preserved across navigation
5+
*/
6+
export const IFRAME_PARAMS = [
7+
'iframe',
8+
'shareData',
9+
'onlySimulation',
10+
'region',
11+
'lang',
12+
'integratorUrl',
13+
'PR',
14+
] as const
15+
16+
export function preserveIframeParams(
17+
currentSearchParams: ReadonlyURLSearchParams,
18+
newParams: Record<string, string | null | undefined> = {}
19+
): URLSearchParams {
20+
const preservedParams = new URLSearchParams()
21+
22+
// Preserve existing iframe params
23+
IFRAME_PARAMS.forEach((param) => {
24+
const value = currentSearchParams.get(param)
25+
if (value) {
26+
preservedParams.set(param, value)
27+
}
28+
})
29+
30+
// Add/override with new params
31+
Object.entries(newParams).forEach(([key, value]) => {
32+
if (value !== null && value !== undefined) {
33+
preservedParams.set(key, value)
34+
}
35+
})
36+
37+
return preservedParams
38+
}
39+
40+
export function buildUrlWithPreservedParams(
41+
pathname: string,
42+
currentSearchParams: ReadonlyURLSearchParams,
43+
newParams: Record<string, string | null | undefined> = {}
44+
): string {
45+
const searchParams = preserveIframeParams(currentSearchParams, newParams)
46+
const searchString = searchParams.toString()
47+
48+
return searchString ? `${pathname}?${searchString}` : pathname
49+
}
Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,57 @@
11
import { SIMULATOR_PATH } from '@/constants/urls/paths'
2+
import { buildUrlWithPreservedParams } from '@/helpers/iframe/preserveIframeParams'
23
import type { DottedName } from '@incubateur-ademe/nosgestesclimat'
4+
import type { ReadonlyURLSearchParams } from 'next/navigation'
35

46
type Props = {
57
question?: DottedName
68
locale?: string
9+
currentSearchParams?: ReadonlyURLSearchParams
710
}
811

912
type TutorielProps = {
1013
locale?: string
14+
currentSearchParams?: ReadonlyURLSearchParams
1115
}
1216

13-
export const getLinkToSimulateur = ({ question, locale }: Props = {}) => {
17+
export const getLinkToSimulateur = ({
18+
question,
19+
locale,
20+
currentSearchParams,
21+
}: Props = {}) => {
1422
const basePath = locale ? `/${locale}` : ''
15-
// If no question is provided, we return
23+
const pathname = `${basePath}${SIMULATOR_PATH}`
24+
25+
// If no question is provided, we return the base path with preserved params
1626
if (!question) {
17-
return `${basePath}${SIMULATOR_PATH}`
27+
if (currentSearchParams) {
28+
return buildUrlWithPreservedParams(pathname, currentSearchParams)
29+
}
30+
return pathname
31+
}
32+
33+
// Build URL with question param and preserved iframe params
34+
const questionParam = question.replaceAll(' . ', '.').replaceAll(' ', '_')
35+
36+
if (currentSearchParams) {
37+
return buildUrlWithPreservedParams(pathname, currentSearchParams, {
38+
question: questionParam,
39+
})
1840
}
19-
//
20-
return `${basePath}${SIMULATOR_PATH}?question=${question
21-
.replaceAll(' . ', '.')
22-
.replaceAll(' ', '_')}`
41+
42+
return `${pathname}?question=${questionParam}`
2343
}
2444

25-
export const getLinkToTutoriel = ({ locale }: TutorielProps = {}) => {
45+
export const getLinkToTutoriel = ({
46+
locale,
47+
currentSearchParams,
48+
}: TutorielProps = {}) => {
2649
const basePath = locale ? `/${locale}` : ''
27-
return `${basePath}/tutoriel`
50+
const pathname = `${basePath}/tutoriel`
51+
52+
if (currentSearchParams) {
53+
return buildUrlWithPreservedParams(pathname, currentSearchParams)
54+
}
55+
56+
return pathname
2857
}

0 commit comments

Comments
 (0)