Skip to content

Commit fa485a4

Browse files
committed
Create a story for gdpr
1 parent 01294db commit fa485a4

File tree

3 files changed

+121
-111
lines changed

3 files changed

+121
-111
lines changed

src/gdpr/createGdprApi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { useRerenderOnChange } from "../tools/StatefulObservable/hooks";
99
import { createConsentBannerAndConsentManagement } from "./ConsentBannerAndConsentManagement";
1010
import { isBrowser } from "../tools/isBrowser";
1111

12+
export const localStorageKey = "@codegouvfr/react-dsfr gdpr finalityConsent";
13+
1214
export function createGdprApi<
1315
FinalityDescription extends Record<
1416
string,
@@ -26,8 +28,6 @@ export function createGdprApi<
2628

2729
const { finalityDescription, personalDataPolicyLinkProps, consentCallback } = params;
2830

29-
const localStorageKey = "@codegouvfr/react-dsfr gdpr finalityConsent";
30-
3131
const $finalityConsent = createStatefulObservable<FinalityConsent<Finality> | undefined>(() => {
3232
if (!isBrowser) {
3333
return undefined;

stories/ConsentBanner.stories.tsx

Lines changed: 107 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,131 @@
1-
import * as React from "react";
2-
import { ConsentBanner, ConsentBannerProps, consentModalButtonProps } from "../dist/ConsentBanner";
1+
import React from "react";
32
import { sectionName } from "./sectionName";
43
import { getStoryFactory } from "./getStory";
5-
import { symToStr } from "tsafe/symToStr";
6-
import { ConsentBannerContent } from "../dist/ConsentBanner/ConsentBannerContent";
4+
import { createGdprApi } from "../dist/gdpr";
5+
import { localStorageKey } from "../dist/gdpr/createGdprApi";
6+
import { Footer } from "../dist/Footer";
7+
import { Button } from "../dist/Button";
8+
import { fr } from "../dist/fr";
79

810
const { meta, getStory } = getStoryFactory({
911
sectionName,
1012
"wrappedComponent": {
11-
[symToStr({ ConsentBanner })]: Story
13+
"ConsentBanner": Story
1214
},
1315
"description": `
14-
15-
WARNING: This is [a temporary implementation](https://github.com/codegouvfr/react-dsfr/pull/107#issuecomment-1517538228).
16-
17-
Manage cookies and consent with a banner and a dedicated modal.
18-
1916
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/gestionnaire-de-consentement),
20-
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/ConsentBanner/index.tsx)
21-
22-
Optionally, you can also use \`import { useGdprStore } from "@codegouvfr/react-dsfr/gdpr"\` to manually monitor and controls
23-
the consent state.
24-
25-
## Usage example
26-
27-
\`\`\`tsx
28-
import { Footer } from "@codegouvfr/react-dsfr/Footer";
29-
import { ConsentBanner, consentModalButtonProps } from "@codegouvfr/react-dsfr/ConsentBanner";
30-
31-
// You can augment the service registry to have autocomplete when using useGdprStore
32-
declare module "@codegouvfr/react-dsfr/gdpr" {
33-
interface RegisterGdprServices {
34-
// the value can be anything (or never), but you can set true
35-
// as a reminder that this service is mandatory
36-
mandatory-cookie-consumer: true;
37-
cookie-consumer: never;
38-
}
39-
}
40-
41-
function App(){
17+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/gdpr)
4218
43-
return (
44-
<>
45-
{/* must be on root level */}
46-
<ConsentBanner
47-
{/* depending on your registered Link */}
48-
gdprLinkProps={{href: "#"}}
49-
services={[
50-
{
51-
name: "mandatory-cookie-consumer",
52-
title: "Any service consuming 🍪",
53-
description: "As a mandatory service, user cannot disable it.",
54-
mandatory: true
55-
},
56-
{
57-
name: "cookie-consumer",
58-
title: "Any service consuming 🍪",
59-
description: "Here you can describe why this service use cookies."
60-
}
61-
]}
62-
siteName={siteName}
63-
/>
64-
{/* ... Header ...*/}
65-
{/* ... your app ...*/}
66-
<Footer
67-
// other Footer props...
68-
cookiesManagementButtonProps={consentModalButtonProps}
69-
/>
70-
<>
71-
);
72-
73-
}
74-
\`\`\`
19+
Thorough documentation coming soon.
7520
`,
76-
argTypes: {
77-
gdprLinkProps: {
78-
description: "Usually the same as FooterProps.personalDataLinkProps"
21+
"disabledProps": ["containerWidth"],
22+
"doHideImportInstruction": true
23+
});
24+
25+
const {
26+
ConsentBannerAndConsentManagement,
27+
FooterConsentManagementItem,
28+
FooterPersonalDataPolicyItem,
29+
useGdpr
30+
} = createGdprApi({
31+
"finalityDescription": {
32+
"advertising": {
33+
"title": "Publicité",
34+
"description":
35+
"Nous utilisons des cookies pour vous proposer des publicités adaptées à vos centres d’intérêts et mesurer leur efficacité."
36+
},
37+
"analytics": {
38+
"title": "Analyse",
39+
"description":
40+
"Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu."
7941
},
80-
siteName: {
81-
description: "Current website name"
42+
"personalization": {
43+
"title": "Personnalisation",
44+
"description":
45+
"Nous utilisons des cookies pour vous proposer des contenus adaptés à vos centres d’intérêts."
46+
},
47+
"statistics": {
48+
"title": "Statistiques",
49+
"description":
50+
"Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu.",
51+
"subFinalities": {
52+
"deviceInfo": "Informations sur votre appareil",
53+
"traffic": "Informations sur votre navigation"
54+
}
8255
}
8356
},
84-
"disabledProps": ["containerWidth"]
57+
"personalDataPolicyLinkProps": {
58+
"href": "#",
59+
"onClick": () => alert("Navigate to the page where you explain your personal data policy")
60+
},
61+
"consentCallback": ({ finalityConsent, finalityConsent_prev }) =>
62+
alert(
63+
[
64+
"Opportunity to do an async operation here.",
65+
"",
66+
"Previously the finalityConsent object was:",
67+
"",
68+
finalityConsent_prev === undefined
69+
? "undefined (the user hadn't took stance yet)"
70+
: JSON.stringify(finalityConsent_prev, null, 2),
71+
"",
72+
"The new finalityConsentObject is:",
73+
"",
74+
JSON.stringify(finalityConsent, null, 2)
75+
].join("\n")
76+
)
8577
});
8678

87-
export default meta;
88-
89-
const siteName = "Nom de l’entité (ministère, secrétariat d‘état, gouvernement)";
79+
function Story() {
80+
const { finalityConsent } = useGdpr();
9081

91-
function Story(props: ConsentBannerProps) {
9282
return (
9383
<>
94-
<ConsentBanner {...props} />
95-
<style>{`
96-
.fr-consent-banner {
97-
position: static;
84+
{finalityConsent === undefined ? (
85+
<p>User hasn't given consent nor explicitly refused use of third party cookies.</p>
86+
) : (
87+
<pre>{JSON.stringify({ finalityConsent }, null, 2)}</pre>
88+
)}
89+
<Button
90+
onClick={() => {
91+
localStorage.removeItem(localStorageKey);
92+
93+
location.reload();
94+
}}
95+
className={fr.cx("fr-mb-10v", "fr-mt-10v")}
96+
>
97+
Clear localStorage and reload.
98+
</Button>
99+
100+
<ConsentBannerAndConsentManagement />
101+
<Footer
102+
accessibility="fully compliant"
103+
contentDescription={`
104+
Ce message est à remplacer par les informations de votre site.
105+
106+
Comme exemple de contenu, vous pouvez indiquer les informations
107+
suivantes : Le site officiel d’information administrative pour les entreprises.
108+
Retrouvez toutes les informations et démarches administratives nécessaires à la création,
109+
à la gestion et au développement de votre entreprise.
110+
`}
111+
brandTop={
112+
<>
113+
INTITULE
114+
<br />
115+
OFFICIEL
116+
</>
98117
}
99-
`}</style>
100-
<ConsentBannerContent {...props} consentModalButtonProps={consentModalButtonProps} />
118+
homeLinkProps={{
119+
"href": "/",
120+
"title":
121+
"Accueil - Nom de l’entité (ministère, secrétariat d‘état, gouvernement)"
122+
}}
123+
bottomItems={[<FooterPersonalDataPolicyItem />, <FooterConsentManagementItem />]}
124+
/>
101125
</>
102126
);
103127
}
104128

105-
export const Default = getStory({
106-
gdprLinkProps: { href: "#" },
107-
services: [
108-
{
109-
name: "mandatory-cookie-consumer",
110-
title: "Any service consuming 🍪",
111-
description: "As a mandatory service, user cannot disable it.",
112-
mandatory: true
113-
},
114-
{
115-
name: "cookie-consumer",
116-
title: "Any service consuming 🍪",
117-
description: "Here you can describe why this service use cookies."
118-
}
119-
],
120-
siteName
121-
});
129+
export default meta;
130+
131+
export const Default = getStory({});

stories/Footer.stories.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ const { meta, getStory } = getStoryFactory({
5252
"termsLinkProps": {
5353
"control": { "type": null }
5454
},
55-
"personalDataLinkProps": {
56-
"control": { "type": null }
57-
},
58-
"cookiesManagementLinkProps": {
59-
"control": { "type": null }
60-
},
6155
"bottomItems": {
6256
"description":
6357
"To integrate the Dark mode switch head over to the documentation of the [Display component](https://react-dsfr-components.etalab.studio/?path=/docs/components-display)"
@@ -68,6 +62,18 @@ const { meta, getStory } = getStoryFactory({
6862
},
6963
"linkList": {
7064
"controls": { "type": null }
65+
},
66+
"brandTop": {
67+
"control": { "type": null },
68+
"description": `In the example here it's \`<>INTITULE<br />OFFICIEL</>\`
69+
If you are using the DSFR Header (as you should) this prop is optional,
70+
the \`brandTop\` of the \`<Header />\` will be used.`
71+
},
72+
"homeLinkProps": {
73+
"control": { "type": null },
74+
"description": `A link to the home, when the user click on the logo he must navigate to the homepage of the website
75+
If you are using the DSFR Header (as you should) this prop is optional,
76+
the \`homeLinkProps\` of the \`<Header />\` will be used.`
7177
}
7278
}
7379
});
@@ -89,12 +95,6 @@ export const Default = getStory({
8995
},
9096
"termsLinkProps": {
9197
"href": "#"
92-
},
93-
"personalDataLinkProps": {
94-
"href": "#"
95-
},
96-
"cookiesManagementLinkProps": {
97-
"href": "#"
9898
}
9999
});
100100

0 commit comments

Comments
 (0)