Skip to content

Commit

Permalink
Create a story for gdpr
Browse files Browse the repository at this point in the history
  • Loading branch information
garronej committed Jun 25, 2023
1 parent 01294db commit fa485a4
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 111 deletions.
4 changes: 2 additions & 2 deletions src/gdpr/createGdprApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useRerenderOnChange } from "../tools/StatefulObservable/hooks";
import { createConsentBannerAndConsentManagement } from "./ConsentBannerAndConsentManagement";
import { isBrowser } from "../tools/isBrowser";

export const localStorageKey = "@codegouvfr/react-dsfr gdpr finalityConsent";

export function createGdprApi<
FinalityDescription extends Record<
string,
Expand All @@ -26,8 +28,6 @@ export function createGdprApi<

const { finalityDescription, personalDataPolicyLinkProps, consentCallback } = params;

const localStorageKey = "@codegouvfr/react-dsfr gdpr finalityConsent";

const $finalityConsent = createStatefulObservable<FinalityConsent<Finality> | undefined>(() => {
if (!isBrowser) {
return undefined;
Expand Down
204 changes: 107 additions & 97 deletions stories/ConsentBanner.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,131 @@
import * as React from "react";
import { ConsentBanner, ConsentBannerProps, consentModalButtonProps } from "../dist/ConsentBanner";
import React from "react";
import { sectionName } from "./sectionName";
import { getStoryFactory } from "./getStory";
import { symToStr } from "tsafe/symToStr";
import { ConsentBannerContent } from "../dist/ConsentBanner/ConsentBannerContent";
import { createGdprApi } from "../dist/gdpr";
import { localStorageKey } from "../dist/gdpr/createGdprApi";
import { Footer } from "../dist/Footer";
import { Button } from "../dist/Button";
import { fr } from "../dist/fr";

const { meta, getStory } = getStoryFactory({
sectionName,
"wrappedComponent": {
[symToStr({ ConsentBanner })]: Story
"ConsentBanner": Story
},
"description": `
WARNING: This is [a temporary implementation](https://github.com/codegouvfr/react-dsfr/pull/107#issuecomment-1517538228).
Manage cookies and consent with a banner and a dedicated modal.
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/gestionnaire-de-consentement),
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/ConsentBanner/index.tsx)
Optionally, you can also use \`import { useGdprStore } from "@codegouvfr/react-dsfr/gdpr"\` to manually monitor and controls
the consent state.
## Usage example
\`\`\`tsx
import { Footer } from "@codegouvfr/react-dsfr/Footer";
import { ConsentBanner, consentModalButtonProps } from "@codegouvfr/react-dsfr/ConsentBanner";
// You can augment the service registry to have autocomplete when using useGdprStore
declare module "@codegouvfr/react-dsfr/gdpr" {
interface RegisterGdprServices {
// the value can be anything (or never), but you can set true
// as a reminder that this service is mandatory
mandatory-cookie-consumer: true;
cookie-consumer: never;
}
}
function App(){
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/gdpr)
return (
<>
{/* must be on root level */}
<ConsentBanner
{/* depending on your registered Link */}
gdprLinkProps={{href: "#"}}
services={[
{
name: "mandatory-cookie-consumer",
title: "Any service consuming 🍪",
description: "As a mandatory service, user cannot disable it.",
mandatory: true
},
{
name: "cookie-consumer",
title: "Any service consuming 🍪",
description: "Here you can describe why this service use cookies."
}
]}
siteName={siteName}
/>
{/* ... Header ...*/}
{/* ... your app ...*/}
<Footer
// other Footer props...
cookiesManagementButtonProps={consentModalButtonProps}
/>
<>
);
}
\`\`\`
Thorough documentation coming soon.
`,
argTypes: {
gdprLinkProps: {
description: "Usually the same as FooterProps.personalDataLinkProps"
"disabledProps": ["containerWidth"],
"doHideImportInstruction": true
});

const {
ConsentBannerAndConsentManagement,
FooterConsentManagementItem,
FooterPersonalDataPolicyItem,
useGdpr
} = createGdprApi({
"finalityDescription": {
"advertising": {
"title": "Publicité",
"description":
"Nous utilisons des cookies pour vous proposer des publicités adaptées à vos centres d’intérêts et mesurer leur efficacité."
},
"analytics": {
"title": "Analyse",
"description":
"Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu."
},
siteName: {
description: "Current website name"
"personalization": {
"title": "Personnalisation",
"description":
"Nous utilisons des cookies pour vous proposer des contenus adaptés à vos centres d’intérêts."
},
"statistics": {
"title": "Statistiques",
"description":
"Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu.",
"subFinalities": {
"deviceInfo": "Informations sur votre appareil",
"traffic": "Informations sur votre navigation"
}
}
},
"disabledProps": ["containerWidth"]
"personalDataPolicyLinkProps": {
"href": "#",
"onClick": () => alert("Navigate to the page where you explain your personal data policy")
},
"consentCallback": ({ finalityConsent, finalityConsent_prev }) =>
alert(
[
"Opportunity to do an async operation here.",
"",
"Previously the finalityConsent object was:",
"",
finalityConsent_prev === undefined
? "undefined (the user hadn't took stance yet)"
: JSON.stringify(finalityConsent_prev, null, 2),
"",
"The new finalityConsentObject is:",
"",
JSON.stringify(finalityConsent, null, 2)
].join("\n")
)
});

export default meta;

const siteName = "Nom de l’entité (ministère, secrétariat d‘état, gouvernement)";
function Story() {
const { finalityConsent } = useGdpr();

function Story(props: ConsentBannerProps) {
return (
<>
<ConsentBanner {...props} />
<style>{`
.fr-consent-banner {
position: static;
{finalityConsent === undefined ? (
<p>User hasn't given consent nor explicitly refused use of third party cookies.</p>
) : (
<pre>{JSON.stringify({ finalityConsent }, null, 2)}</pre>
)}
<Button
onClick={() => {
localStorage.removeItem(localStorageKey);

location.reload();
}}
className={fr.cx("fr-mb-10v", "fr-mt-10v")}
>
Clear localStorage and reload.
</Button>

<ConsentBannerAndConsentManagement />
<Footer
accessibility="fully compliant"
contentDescription={`
Ce message est à remplacer par les informations de votre site.
Comme exemple de contenu, vous pouvez indiquer les informations
suivantes : Le site officiel d’information administrative pour les entreprises.
Retrouvez toutes les informations et démarches administratives nécessaires à la création,
à la gestion et au développement de votre entreprise.
`}
brandTop={
<>
INTITULE
<br />
OFFICIEL
</>
}
`}</style>
<ConsentBannerContent {...props} consentModalButtonProps={consentModalButtonProps} />
homeLinkProps={{
"href": "/",
"title":
"Accueil - Nom de l’entité (ministère, secrétariat d‘état, gouvernement)"
}}
bottomItems={[<FooterPersonalDataPolicyItem />, <FooterConsentManagementItem />]}
/>
</>
);
}

export const Default = getStory({
gdprLinkProps: { href: "#" },
services: [
{
name: "mandatory-cookie-consumer",
title: "Any service consuming 🍪",
description: "As a mandatory service, user cannot disable it.",
mandatory: true
},
{
name: "cookie-consumer",
title: "Any service consuming 🍪",
description: "Here you can describe why this service use cookies."
}
],
siteName
});
export default meta;

export const Default = getStory({});
24 changes: 12 additions & 12 deletions stories/Footer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ const { meta, getStory } = getStoryFactory({
"termsLinkProps": {
"control": { "type": null }
},
"personalDataLinkProps": {
"control": { "type": null }
},
"cookiesManagementLinkProps": {
"control": { "type": null }
},
"bottomItems": {
"description":
"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)"
Expand All @@ -68,6 +62,18 @@ const { meta, getStory } = getStoryFactory({
},
"linkList": {
"controls": { "type": null }
},
"brandTop": {
"control": { "type": null },
"description": `In the example here it's \`<>INTITULE<br />OFFICIEL</>\`
If you are using the DSFR Header (as you should) this prop is optional,
the \`brandTop\` of the \`<Header />\` will be used.`
},
"homeLinkProps": {
"control": { "type": null },
"description": `A link to the home, when the user click on the logo he must navigate to the homepage of the website
If you are using the DSFR Header (as you should) this prop is optional,
the \`homeLinkProps\` of the \`<Header />\` will be used.`
}
}
});
Expand All @@ -89,12 +95,6 @@ export const Default = getStory({
},
"termsLinkProps": {
"href": "#"
},
"personalDataLinkProps": {
"href": "#"
},
"cookiesManagementLinkProps": {
"href": "#"
}
});

Expand Down

0 comments on commit fa485a4

Please sign in to comment.