From a935ed83c3642eb81db3c00b36d723ff26031747 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Fri, 14 Feb 2025 12:22:18 +0100 Subject: [PATCH 01/35] :construction: umami test script --- aksel.nav.no/website/pages/_document.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aksel.nav.no/website/pages/_document.tsx b/aksel.nav.no/website/pages/_document.tsx index b0363ae6e8..f70f8e28a8 100644 --- a/aksel.nav.no/website/pages/_document.tsx +++ b/aksel.nav.no/website/pages/_document.tsx @@ -14,6 +14,12 @@ export default function Document() { type="font/woff2" crossOrigin="anonymous" /> +
From f66a7e64007805572a5c4425de811acdf714bc9c Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Fri, 14 Feb 2025 12:48:18 +0100 Subject: [PATCH 02/35] :construction: CSP for umami --- aksel.nav.no/website/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aksel.nav.no/website/next.config.js b/aksel.nav.no/website/next.config.js index d106e364cb..9b196964cd 100644 --- a/aksel.nav.no/website/next.config.js +++ b/aksel.nav.no/website/next.config.js @@ -12,7 +12,7 @@ const ContentSecurityPolicy = ` img-src 'self' cdn.sanity.io ${dekoratorUrl} https://avatars.githubusercontent.com data: ${cdnUrl} ${hotjarUrl}; script-src 'self' ${dekoratorUrl} ${cdnUrl} ${hotjarUrl} https://in2.taskanalytics.com/tm.js 'nonce-4e1aa203a32e' 'unsafe-eval'; style-src 'self' ${dekoratorUrl} ${cdnUrl} ${hotjarUrl} 'unsafe-inline'; - connect-src 'self' ${dekoratorUrl} ${cdnUrl} ${hotjarUrl} https://*.hotjar.io wss://*.hotjar.com https://raw.githubusercontent.com/navikt/ https://hnbe3yhs.apicdn.sanity.io wss://hnbe3yhs.api.sanity.io cdn.sanity.io *.api.sanity.io https://amplitude.nav.no https://in2.taskanalytics.com/03346; + connect-src 'self' ${dekoratorUrl} ${cdnUrl} ${hotjarUrl} https://*.hotjar.io wss://*.hotjar.com https://raw.githubusercontent.com/navikt/ https://hnbe3yhs.apicdn.sanity.io wss://hnbe3yhs.api.sanity.io cdn.sanity.io *.api.sanity.io https://umami.nav.no https://in2.taskanalytics.com/03346; frame-ancestors 'self' localhost:3000; media-src 'self' ${cdnUrl} cdn.sanity.io; frame-src 'self' https://web.microsoftstream.com localhost:3000 https://aksel.ekstern.dev.nav.no; From 68ab8e12c844ecf4ba42be91d8069b1d933aeed5 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Fri, 14 Feb 2025 14:05:49 +0100 Subject: [PATCH 03/35] :construction: test data-tag, set no auto tracking --- aksel.nav.no/website/pages/_document.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/aksel.nav.no/website/pages/_document.tsx b/aksel.nav.no/website/pages/_document.tsx index f70f8e28a8..b7b94a6ec3 100644 --- a/aksel.nav.no/website/pages/_document.tsx +++ b/aksel.nav.no/website/pages/_document.tsx @@ -1,8 +1,19 @@ // This is imported to allow the Output File Tracing feature of Next.js to work correctly with the log patcher import "next-logger"; import { Head, Html, Main, NextScript } from "next/document"; +import { useEffect, useState } from "react"; export default function Document() { + const [umamiTag, setUmamiTag] = useState(); + + useEffect(() => { + const isProdUrl = () => window.location.host === "aksel.nav.no"; + const isPreview = () => !!document.getElementById("exit-preview-id"); + + setUmamiTag( + isPreview() ? "preview" : isProdUrl() ? "production" : "development", + ); + }, []); return ( @@ -19,6 +30,8 @@ export default function Document() { src="https://cdn.nav.no/team-researchops/sporing/sporing.js" data-host-url="https://umami.nav.no" data-website-id="7b9fb2cd-40f4-4a30-b208-5b4dba026b57" + data-auto-track="false" + data-tag={umamiTag} > From 21ec03b01777798557e4205019b0c510f6e45031 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Tue, 18 Feb 2025 09:30:15 +0100 Subject: [PATCH 04/35] :recycle: make consentBanner more self contained --- aksel.nav.no/website/pages/ConsentBanner.tsx | 63 ++++++++++++++++++++ aksel.nav.no/website/pages/_app.tsx | 34 ++++++++++- aksel.nav.no/website/pages/_document.tsx | 19 ------ 3 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 aksel.nav.no/website/pages/ConsentBanner.tsx diff --git a/aksel.nav.no/website/pages/ConsentBanner.tsx b/aksel.nav.no/website/pages/ConsentBanner.tsx new file mode 100644 index 0000000000..a003009b03 --- /dev/null +++ b/aksel.nav.no/website/pages/ConsentBanner.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useLayoutEffect, useRef } from "react"; +import { BodyLong, Button, Modal } from "@navikt/ds-react"; + +const CONSENT_TRACKER_ID = "acceptTracking"; + +type CONSENT_TRACKER_STATE = "undecided" | "accepted" | "rejected"; + +export const getStorageAcceptedTracking = () => { + const rawState = localStorage.getItem(CONSENT_TRACKER_ID); + if (rawState === null) { + return "undecided"; + } + + return rawState as CONSENT_TRACKER_STATE; +}; + +export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { + return localStorage.setItem(CONSENT_TRACKER_ID, state); +}; + +export const ConsentBanner = () => { + useLayoutEffect(() => { + const consentAnswer = getStorageAcceptedTracking(); + if (consentAnswer === "undecided") { + ref.current?.showModal(); + } + }, []); + + const ref = useRef(null); + + return ( +
+ + + Legg til cookie tekst her! + + + + + + +
+ ); +}; diff --git a/aksel.nav.no/website/pages/_app.tsx b/aksel.nav.no/website/pages/_app.tsx index a86c4589ca..b34cdd1518 100644 --- a/aksel.nav.no/website/pages/_app.tsx +++ b/aksel.nav.no/website/pages/_app.tsx @@ -1,19 +1,23 @@ import { AppProps } from "next/app"; -import { useEffect } from "react"; +import Head from "next/head"; +import { useLayoutEffect, useState } from "react"; import "@navikt/ds-tokens/darkside-css"; import { useCheckAuth } from "@/hooks/useCheckAuth"; import { useHashScroll } from "@/hooks/useHashScroll"; import { SanityDataContext } from "@/hooks/useSanityData"; import { BaseSEO } from "@/web/seo/BaseSEO"; import "../components/styles/index.css"; +import { ConsentBanner, getStorageAcceptedTracking } from "./ConsentBanner"; function App({ Component, pageProps, router }: AppProps) { useHashScroll(); + const [umamiTag, setUmamiTag] = useState(); + const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); /* As of 01.01.25, removed until cookie compliance is implemented */ /* useAmplitudeInit(); */ - useEffect(() => { + useLayoutEffect(() => { window.location.host === "design.nav.no" && window.location.replace(`http://aksel.nav.no`); @@ -23,6 +27,18 @@ function App({ Component, pageProps, router }: AppProps) { * Import: import { hotjar } from "react-hotjar"; * Script: hotjar.initialize(148751, 6); */ + + /** + ** TODO: put umami tracking in a custom hook, useUmami + */ + const isProdUrl = () => window.location.host === "aksel.nav.no"; + const isPreview = () => !!document.getElementById("exit-preview-id"); + + setUmamiTag( + isPreview() ? "preview" : isProdUrl() ? "production" : "development", + ); + + setClientAcceptsTracking(getStorageAcceptedTracking() === "accepted"); }, []); const useGlobalStyles = @@ -33,6 +49,20 @@ function App({ Component, pageProps, router }: AppProps) { return ( <> + + + +

+ clientAcceptsTracking: {clientAcceptsTracking ? "true" : "false"} +

+ (); - - useEffect(() => { - const isProdUrl = () => window.location.host === "aksel.nav.no"; - const isPreview = () => !!document.getElementById("exit-preview-id"); - - setUmamiTag( - isPreview() ? "preview" : isProdUrl() ? "production" : "development", - ); - }, []); return ( @@ -25,14 +14,6 @@ export default function Document() { type="font/woff2" crossOrigin="anonymous" /> -
From 6c47f3421b92f8ece39d80a0ef7e80da5caddb17 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Tue, 18 Feb 2025 10:58:35 +0100 Subject: [PATCH 05/35] :construction: working implementation for consent modal --- aksel.nav.no/website/package.json | 1 + aksel.nav.no/website/pages/ConsentBanner.tsx | 9 +++++++-- aksel.nav.no/website/pages/_app.tsx | 7 ++----- aksel.nav.no/website/tsconfig.json | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/aksel.nav.no/website/package.json b/aksel.nav.no/website/package.json index e3594d8685..8fe0fac6cc 100644 --- a/aksel.nav.no/website/package.json +++ b/aksel.nav.no/website/package.json @@ -89,6 +89,7 @@ "@types/jscodeshift": "^0.11.11", "@types/react": "^18.3.11", "@types/react-dom": "^18.0.0", + "@types/umami": "^2.10.0", "autoprefixer": "^10.4.20", "babel-loader": "^9.1.3", "copyfiles": "^2.4.1", diff --git a/aksel.nav.no/website/pages/ConsentBanner.tsx b/aksel.nav.no/website/pages/ConsentBanner.tsx index a003009b03..90935acf27 100644 --- a/aksel.nav.no/website/pages/ConsentBanner.tsx +++ b/aksel.nav.no/website/pages/ConsentBanner.tsx @@ -1,6 +1,6 @@ "use client"; -import { useLayoutEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import { BodyLong, Button, Modal } from "@navikt/ds-react"; const CONSENT_TRACKER_ID = "acceptTracking"; @@ -21,7 +21,7 @@ export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { }; export const ConsentBanner = () => { - useLayoutEffect(() => { + useEffect(() => { const consentAnswer = getStorageAcceptedTracking(); if (consentAnswer === "undecided") { ref.current?.showModal(); @@ -41,6 +41,11 @@ export const ConsentBanner = () => { type="button" onClick={() => { setStorageAcceptedTracking("accepted"); + // NOTE: umami _should_ exist on window object here (loaded via ) + // we call track manually this _one_ time to ensure the current page is + // accounted for, any new page loads will be captured by data-auto-track + // https://umami.is/docs/tracker-configuration + umami.track(); ref.current?.close(); }} > diff --git a/aksel.nav.no/website/pages/_app.tsx b/aksel.nav.no/website/pages/_app.tsx index b34cdd1518..a6e541affa 100644 --- a/aksel.nav.no/website/pages/_app.tsx +++ b/aksel.nav.no/website/pages/_app.tsx @@ -1,6 +1,6 @@ import { AppProps } from "next/app"; import Head from "next/head"; -import { useLayoutEffect, useState } from "react"; +import { useEffect, useState } from "react"; import "@navikt/ds-tokens/darkside-css"; import { useCheckAuth } from "@/hooks/useCheckAuth"; import { useHashScroll } from "@/hooks/useHashScroll"; @@ -17,7 +17,7 @@ function App({ Component, pageProps, router }: AppProps) { /* As of 01.01.25, removed until cookie compliance is implemented */ /* useAmplitudeInit(); */ - useLayoutEffect(() => { + useEffect(() => { window.location.host === "design.nav.no" && window.location.replace(`http://aksel.nav.no`); @@ -59,9 +59,6 @@ function App({ Component, pageProps, router }: AppProps) { data-tag={umamiTag} > -

- clientAcceptsTracking: {clientAcceptsTracking ? "true" : "false"} -

Date: Tue, 18 Feb 2025 13:27:32 +0100 Subject: [PATCH 06/35] :recycle: move ConsentBanner to website-modules + small changes --- .../website-modules}/ConsentBanner.tsx | 30 ++++++++++++++++++- aksel.nav.no/website/pages/_app.tsx | 29 ++---------------- 2 files changed, 31 insertions(+), 28 deletions(-) rename aksel.nav.no/website/{pages => components/website-modules}/ConsentBanner.tsx (65%) diff --git a/aksel.nav.no/website/pages/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx similarity index 65% rename from aksel.nav.no/website/pages/ConsentBanner.tsx rename to aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 90935acf27..817a1915c5 100644 --- a/aksel.nav.no/website/pages/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -1,6 +1,7 @@ "use client"; -import { useEffect, useRef } from "react"; +import Head from "next/head"; +import { useEffect, useRef, useState } from "react"; import { BodyLong, Button, Modal } from "@navikt/ds-react"; const CONSENT_TRACKER_ID = "acceptTracking"; @@ -21,17 +22,44 @@ export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { }; export const ConsentBanner = () => { + const refUmamiTag = useRef(""); + useEffect(() => { const consentAnswer = getStorageAcceptedTracking(); if (consentAnswer === "undecided") { ref.current?.showModal(); } + + const isProdUrl = () => window.location.host === "aksel.nav.no"; + const isPreview = () => !!document.getElementById("exit-preview-id"); + + refUmamiTag.current = isPreview() + ? "preview" + : isProdUrl() + ? "production" + : "development"; + + setClientAcceptsTracking(getStorageAcceptedTracking() === "accepted"); }, []); const ref = useRef(null); + const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); + return (
+ {refUmamiTag.current && ( + + + + )} Legg til cookie tekst her! diff --git a/aksel.nav.no/website/pages/_app.tsx b/aksel.nav.no/website/pages/_app.tsx index a6e541affa..4458090231 100644 --- a/aksel.nav.no/website/pages/_app.tsx +++ b/aksel.nav.no/website/pages/_app.tsx @@ -1,18 +1,15 @@ import { AppProps } from "next/app"; -import Head from "next/head"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import "@navikt/ds-tokens/darkside-css"; import { useCheckAuth } from "@/hooks/useCheckAuth"; import { useHashScroll } from "@/hooks/useHashScroll"; import { SanityDataContext } from "@/hooks/useSanityData"; +import { ConsentBanner } from "@/web/ConsentBanner"; import { BaseSEO } from "@/web/seo/BaseSEO"; import "../components/styles/index.css"; -import { ConsentBanner, getStorageAcceptedTracking } from "./ConsentBanner"; function App({ Component, pageProps, router }: AppProps) { useHashScroll(); - const [umamiTag, setUmamiTag] = useState(); - const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); /* As of 01.01.25, removed until cookie compliance is implemented */ /* useAmplitudeInit(); */ @@ -27,18 +24,6 @@ function App({ Component, pageProps, router }: AppProps) { * Import: import { hotjar } from "react-hotjar"; * Script: hotjar.initialize(148751, 6); */ - - /** - ** TODO: put umami tracking in a custom hook, useUmami - */ - const isProdUrl = () => window.location.host === "aksel.nav.no"; - const isPreview = () => !!document.getElementById("exit-preview-id"); - - setUmamiTag( - isPreview() ? "preview" : isProdUrl() ? "production" : "development", - ); - - setClientAcceptsTracking(getStorageAcceptedTracking() === "accepted"); }, []); const useGlobalStyles = @@ -49,16 +34,6 @@ function App({ Component, pageProps, router }: AppProps) { return ( <> - - - Date: Tue, 18 Feb 2025 14:33:13 +0100 Subject: [PATCH 07/35] use typescript-cookies over localStorage API --- .../website-modules/ConsentBanner.tsx | 79 ++++++++++++++----- aksel.nav.no/website/package.json | 1 + yarn.lock | 16 ++++ 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 817a1915c5..9065cba279 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -1,16 +1,25 @@ "use client"; -import Head from "next/head"; +import Script from "next/script"; import { useEffect, useRef, useState } from "react"; -import { BodyLong, Button, Modal } from "@navikt/ds-react"; +import { Cookies } from "typescript-cookie"; +import { FaceLaughIcon } from "@navikt/aksel-icons"; +import { + BodyLong, + Button, + Detail, + HStack, + Link, + Modal, +} from "@navikt/ds-react"; const CONSENT_TRACKER_ID = "acceptTracking"; type CONSENT_TRACKER_STATE = "undecided" | "accepted" | "rejected"; export const getStorageAcceptedTracking = () => { - const rawState = localStorage.getItem(CONSENT_TRACKER_ID); - if (rawState === null) { + const rawState = Cookies.get(CONSENT_TRACKER_ID); + if (!rawState) { return "undecided"; } @@ -18,7 +27,7 @@ export const getStorageAcceptedTracking = () => { }; export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { - return localStorage.setItem(CONSENT_TRACKER_ID, state); + Cookies.set(CONSENT_TRACKER_ID, state, { expires: 365 }); }; export const ConsentBanner = () => { @@ -49,21 +58,53 @@ export const ConsentBanner = () => { return (
{refUmamiTag.current && ( - - - + )} - + - Legg til cookie tekst her! + + + + + Er det greit at vi logger litt brukeradferd under domenet + aksel.nav.no? + + + Vi bruker{" "} + + Umami + + , og dataene lagres på NAV sin sky i Google Cloud Platform. + + + Et par cookies blir lagret på nettleseren din uansett for at Aksel + nettsiden skal virke, og slik at vi ikke glemmer det valget du tar + akkurat nå! + + + + Trykker du vekk denne modalen vil du få den på nytt ved neste + sidelasting, og vi logger da ikke brukeradferd. + + + Trykker du "Nei" vil vi ikke logge brukeradferd. + + + Trykker du "Ja" logger vi (helst ikke personlig + identifiserbar) brukeradferd under aksel.nav.no domenet. + + diff --git a/aksel.nav.no/website/package.json b/aksel.nav.no/website/package.json index 8fe0fac6cc..7892abdcba 100644 --- a/aksel.nav.no/website/package.json +++ b/aksel.nav.no/website/package.json @@ -75,6 +75,7 @@ "swr": "^1.1.2", "tailwindcss": "^3.3.3", "tinycolor2": "^1.6.0", + "typescript-cookie": "^1.0.6", "zod": "^3.22.4" }, "_note": "When upgrading @axe-core/playwright, try removing the resolution entry in the root package.json", diff --git a/yarn.lock b/yarn.lock index 51e0f7fad3..f8a1206e5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6643,6 +6643,13 @@ __metadata: languageName: node linkType: hard +"@types/umami@npm:^2.10.0": + version: 2.10.0 + resolution: "@types/umami@npm:2.10.0" + checksum: 10/8e489202966fb6429ac079f10dc0717fe44daeedbf37927e326017b9eed9c821dc6bf5d9aaad69fd4ce5eb92df0e53fafb57c9b47185f73a547969e4db365d66 + languageName: node + linkType: hard + "@types/unist@npm:*, @types/unist@npm:^3.0.0": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" @@ -23565,6 +23572,13 @@ __metadata: languageName: node linkType: hard +"typescript-cookie@npm:^1.0.6": + version: 1.0.6 + resolution: "typescript-cookie@npm:1.0.6" + checksum: 10/105d4774ee847daf3e5e9a4433746eb34cd40feb1703bc7c9bde0bfc77c94ee4318386b94af66f9380fd24fa36600cc8e730ead7dfa67097ebbeff158e8020c3 + languageName: node + linkType: hard + "typescript@npm:5.5.4": version: 5.5.4 resolution: "typescript@npm:5.5.4" @@ -24775,6 +24789,7 @@ __metadata: "@types/jscodeshift": "npm:^0.11.11" "@types/react": "npm:^18.3.11" "@types/react-dom": "npm:^18.0.0" + "@types/umami": "npm:^2.10.0" autoprefixer: "npm:^10.4.20" babel-loader: "npm:^9.1.3" boring-avatars: "npm:1.10.1" @@ -24819,6 +24834,7 @@ __metadata: tinycolor2: "npm:^1.6.0" tsx: "npm:^4.19.1" typescript: "npm:5.5.4" + typescript-cookie: "npm:^1.0.6" vitest: "npm:^2.1.8" zod: "npm:^3.22.4" languageName: unknown From f063f899ce68d86fadc1791494a1e91006be11ad Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Tue, 18 Feb 2025 15:04:11 +0100 Subject: [PATCH 08/35] Copy Nav.no consent text --- .../website-modules/ConsentBanner.tsx | 51 ++++--------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 9065cba279..60104f4b51 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -3,15 +3,7 @@ import Script from "next/script"; import { useEffect, useRef, useState } from "react"; import { Cookies } from "typescript-cookie"; -import { FaceLaughIcon } from "@navikt/aksel-icons"; -import { - BodyLong, - Button, - Detail, - HStack, - Link, - Modal, -} from "@navikt/ds-react"; +import { BodyLong, Button, Link, Modal } from "@navikt/ds-react"; const CONSENT_TRACKER_ID = "acceptTracking"; @@ -69,40 +61,18 @@ export const ConsentBanner = () => { )} - - - - Er det greit at vi logger litt brukeradferd under domenet - aksel.nav.no? - - - Vi bruker{" "} - - Umami + Nødvendige informasjonskapsler sørger for at nettstedet fungerer og + er sikkert, og kan ikke velges bort. Andre brukes til statistikk og + analyse. Godkjenner du alle, hjelper du oss å lage bedre nettsider + og tjenester.{" "} + + Mer om våre informasjonskapsler. - , og dataene lagres på NAV sin sky i Google Cloud Platform. - - Et par cookies blir lagret på nettleseren din uansett for at Aksel - nettsiden skal virke, og slik at vi ikke glemmer det valget du tar - akkurat nå! - - - - Trykker du vekk denne modalen vil du få den på nytt ved neste - sidelasting, og vi logger da ikke brukeradferd. - - - Trykker du "Nei" vil vi ikke logge brukeradferd. - - - Trykker du "Ja" logger vi (helst ikke personlig - identifiserbar) brukeradferd under aksel.nav.no domenet. - @@ -118,17 +88,16 @@ export const ConsentBanner = () => { ref.current?.close(); }} > - Ja + Godkjenn alle From 45b8104d0dce503a1a8ffab80dc819c962e2c9ee Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Tue, 18 Feb 2025 15:31:44 +0100 Subject: [PATCH 09/35] better name for cookie --- .../website/components/website-modules/ConsentBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 60104f4b51..361627348b 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react"; import { Cookies } from "typescript-cookie"; import { BodyLong, Button, Link, Modal } from "@navikt/ds-react"; -const CONSENT_TRACKER_ID = "acceptTracking"; +const CONSENT_TRACKER_ID = "aksel-consent"; type CONSENT_TRACKER_STATE = "undecided" | "accepted" | "rejected"; From ee999ab1e53fecb731a3348872045759973136a3 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Tue, 18 Feb 2025 15:57:50 +0100 Subject: [PATCH 10/35] :construction: crude complex consent object --- .../website-modules/ConsentBanner.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 361627348b..7cbff9df77 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -9,17 +9,37 @@ const CONSENT_TRACKER_ID = "aksel-consent"; type CONSENT_TRACKER_STATE = "undecided" | "accepted" | "rejected"; +type CookieData = { + createdAt: string; + updatedAt: string; + version: number; + consents: { + tracking?: string; + }; +}; + export const getStorageAcceptedTracking = () => { - const rawState = Cookies.get(CONSENT_TRACKER_ID); + const rawState = Cookies.get(CONSENT_TRACKER_ID) as string; if (!rawState) { return "undecided"; } - return rawState as CONSENT_TRACKER_STATE; + const cookieData = JSON.parse(rawState) as CookieData; + + return cookieData.consents.tracking as CONSENT_TRACKER_STATE; }; export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { - Cookies.set(CONSENT_TRACKER_ID, state, { expires: 365 }); + const cookieData: CookieData = { + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + version: 1, + consents: {}, + }; + + cookieData.consents.tracking = state; + + Cookies.set(CONSENT_TRACKER_ID, JSON.stringify(cookieData), { expires: 365 }); }; export const ConsentBanner = () => { @@ -61,7 +81,7 @@ export const ConsentBanner = () => { )} From a346db800d91194a738a2788c8450e49fbc3c446 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Wed, 19 Feb 2025 12:35:31 +0100 Subject: [PATCH 11/35] Update aksel.nav.no/website/components/website-modules/ConsentBanner.tsx Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com> --- .../website/components/website-modules/ConsentBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 7cbff9df77..7984f0bd22 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -14,7 +14,7 @@ type CookieData = { updatedAt: string; version: number; consents: { - tracking?: string; + tracking?: CONSENT_TRACKER_STATE; }; }; From 757d931c5732794d8299f0928d787b2a2fa20301 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Wed, 19 Feb 2025 12:49:28 +0100 Subject: [PATCH 12/35] classify traffic better (umami) --- .../utils/get-current-environment.ts | 20 +++++++++++++++++++ .../website-modules/ConsentBanner.tsx | 19 ++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 aksel.nav.no/website/components/utils/get-current-environment.ts diff --git a/aksel.nav.no/website/components/utils/get-current-environment.ts b/aksel.nav.no/website/components/utils/get-current-environment.ts new file mode 100644 index 0000000000..d128f7ff6b --- /dev/null +++ b/aksel.nav.no/website/components/utils/get-current-environment.ts @@ -0,0 +1,20 @@ +"use client"; + +export const classifyTraffic = () => { + const isProdUrl = () => window.location.host === "aksel.nav.no"; + const isPreview = () => !!document.getElementById("exit-preview-id"); + const isExample = () => window.location.pathname.startsWith("/eksempler/"); + const isTemplate = () => window.location.pathname.startsWith("/templates/"); + const isAdmin = () => window.location.pathname.startsWith("/admin/"); + + if ( + isProdUrl() && + !isPreview() && + !isExample() && + !isTemplate() && + !isAdmin() + ) { + return "organic"; + } + return "polluted"; +}; diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 7984f0bd22..6afa14cc52 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -4,6 +4,7 @@ import Script from "next/script"; import { useEffect, useRef, useState } from "react"; import { Cookies } from "typescript-cookie"; import { BodyLong, Button, Link, Modal } from "@navikt/ds-react"; +import { classifyTraffic } from "../utils/get-current-environment"; const CONSENT_TRACKER_ID = "aksel-consent"; @@ -43,7 +44,8 @@ export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { }; export const ConsentBanner = () => { - const refUmamiTag = useRef(""); + const [umamiTag, setUmamiTag] = useState(); + const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); useEffect(() => { const consentAnswer = getStorageAcceptedTracking(); @@ -51,32 +53,23 @@ export const ConsentBanner = () => { ref.current?.showModal(); } - const isProdUrl = () => window.location.host === "aksel.nav.no"; - const isPreview = () => !!document.getElementById("exit-preview-id"); - - refUmamiTag.current = isPreview() - ? "preview" - : isProdUrl() - ? "production" - : "development"; + setUmamiTag(classifyTraffic()); setClientAcceptsTracking(getStorageAcceptedTracking() === "accepted"); }, []); const ref = useRef(null); - const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); - return (
- {refUmamiTag.current && ( + {umamiTag && ( )} Date: Wed, 19 Feb 2025 12:50:02 +0100 Subject: [PATCH 13/35] Update aksel.nav.no/website/components/website-modules/ConsentBanner.tsx Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com> --- .../website/components/website-modules/ConsentBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 6afa14cc52..9d169144cb 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -68,7 +68,7 @@ export const ConsentBanner = () => { src="https://cdn.nav.no/team-researchops/sporing/sporing.js" data-host-url="https://umami.nav.no" data-website-id="7b9fb2cd-40f4-4a30-b208-5b4dba026b57" - data-auto-track={clientAcceptsTracking ? "true" : "false"} + data-auto-track={clientAcceptsTracking} data-tag={umamiTag} > )} From 681eb55ac6278bfbd61281b720d79205b1193589 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Wed, 19 Feb 2025 13:42:02 +0100 Subject: [PATCH 14/35] link to our own personvernerklering + disable modal on that specific page --- .../website-modules/ConsentBanner.tsx | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index 9d169144cb..d6361530f7 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -1,9 +1,10 @@ "use client"; +import Link from "next/link"; import Script from "next/script"; import { useEffect, useRef, useState } from "react"; import { Cookies } from "typescript-cookie"; -import { BodyLong, Button, Link, Modal } from "@navikt/ds-react"; +import { BodyLong, Button, Modal } from "@navikt/ds-react"; import { classifyTraffic } from "../utils/get-current-environment"; const CONSENT_TRACKER_ID = "aksel-consent"; @@ -44,21 +45,37 @@ export const setStorageAcceptedTracking = (state: CONSENT_TRACKER_STATE) => { }; export const ConsentBanner = () => { + const ref = useRef(null); const [umamiTag, setUmamiTag] = useState(); const [clientAcceptsTracking, setClientAcceptsTracking] = useState(false); useEffect(() => { const consentAnswer = getStorageAcceptedTracking(); - if (consentAnswer === "undecided") { + + const disabledModalParam = new URLSearchParams(window.location.search).get( + "no_consent_modal", + ); + if (consentAnswer === "undecided" && !disabledModalParam) { ref.current?.showModal(); } + let previousPath = ""; + const observer = new MutationObserver(function () { + if (location.pathname !== previousPath) { + if (previousPath == "/side/personvernerklaering") { + ref.current?.showModal(); + } + previousPath = location.pathname; + } + }); + observer.observe(document, { subtree: true, childList: true }); setUmamiTag(classifyTraffic()); - setClientAcceptsTracking(getStorageAcceptedTracking() === "accepted"); - }, []); - const ref = useRef(null); + return () => { + observer.disconnect(); + }; + }, []); return (
@@ -82,7 +99,12 @@ export const ConsentBanner = () => { er sikkert, og kan ikke velges bort. Andre brukes til statistikk og analyse. Godkjenner du alle, hjelper du oss å lage bedre nettsider og tjenester.{" "} - + { + ref.current?.close(); + }} + > Mer om våre informasjonskapsler. From 65484731c8447c70e3a0abee016804353839f258 Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Wed, 19 Feb 2025 13:44:43 +0100 Subject: [PATCH 15/35] use same conditions for all showModal() triggers --- .../website/components/website-modules/ConsentBanner.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index d6361530f7..a9ab4d2b32 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -62,7 +62,9 @@ export const ConsentBanner = () => { const observer = new MutationObserver(function () { if (location.pathname !== previousPath) { if (previousPath == "/side/personvernerklaering") { - ref.current?.showModal(); + if (consentAnswer === "undecided" && !disabledModalParam) { + ref.current?.showModal(); + } } previousPath = location.pathname; } From b7840228043504448e406a3bb5c06b41d514ab2a Mon Sep 17 00:00:00 2001 From: Julian Nymark Date: Wed, 19 Feb 2025 14:01:43 +0100 Subject: [PATCH 16/35] update comment --- .../website/components/website-modules/ConsentBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx index a9ab4d2b32..1c9b6b3116 100644 --- a/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx +++ b/aksel.nav.no/website/components/website-modules/ConsentBanner.tsx @@ -117,7 +117,7 @@ export const ConsentBanner = () => { type="button" onClick={() => { setStorageAcceptedTracking("accepted"); - // NOTE: umami _should_ exist on window object here (loaded via ) + // NOTE: umami _should_ exist on window object here (loaded via )} - - - - Nødvendige informasjonskapsler sørger for at nettstedet fungerer og - er sikkert, og kan ikke velges bort. Andre brukes til statistikk og - analyse. Godkjenner du alle, hjelper du oss å lage bedre nettsider - og tjenester.{" "} - + +
+ + Vi bruker cookies + + + Nødvendige informasjonskapsler sørger for at nettstedet fungerer + og er sikkert, og kan ikke velges bort. Andre brukes til + statistikk og analyse. Godkjenner du alle, hjelper du oss å lage + bedre nettsider og tjenester.{" "} + + Mer om våre informasjonskapsler. + + +
+ + +