diff --git a/.env.example b/.env.example index ebe8079..bb02f2b 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,4 @@ NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api + +NEXT_PUBLIC_MATOMO_URL=https://piwik.technologiestiftung-berlin.de +NEXT_PUBLIC_MATOMO_SITE_ID=1234 diff --git a/package-lock.json b/package-lock.json index c75c6c1..33eb20f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "dependencies": { "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", + "@socialgouv/matomo-next": "1.6.1", "lucide-react": "^0.263.1", "next": "13.4.7", "next-intl": "^2.17.5", @@ -4725,6 +4726,14 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socialgouv/matomo-next": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@socialgouv/matomo-next/-/matomo-next-1.6.1.tgz", + "integrity": "sha512-O2lxMKZHVzvXGZNsKOnsSS+hzqxIX35CPRuVkuU8Z+EFORuO2R/iiBBfyY1d+XvexIsJOA2uHnxhJxsdeFIrRw==", + "peerDependencies": { + "next": ">= 9.5.5" + } + }, "node_modules/@storybook/addon-actions": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.2.1.tgz", @@ -27461,6 +27470,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "@socialgouv/matomo-next": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@socialgouv/matomo-next/-/matomo-next-1.6.1.tgz", + "integrity": "sha512-O2lxMKZHVzvXGZNsKOnsSS+hzqxIX35CPRuVkuU8Z+EFORuO2R/iiBBfyY1d+XvexIsJOA2uHnxhJxsdeFIrRw==", + "requires": {} + }, "@storybook/addon-actions": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.2.1.tgz", diff --git a/package.json b/package.json index 92ee6a4..d86071c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", + "@socialgouv/matomo-next": "1.6.1", "lucide-react": "^0.263.1", "next": "13.4.7", "next-intl": "^2.17.5", diff --git a/src/components/HomePage/NewsletterSection/index.tsx b/src/components/HomePage/NewsletterSection/index.tsx index 7516d8d..71147cb 100644 --- a/src/components/HomePage/NewsletterSection/index.tsx +++ b/src/components/HomePage/NewsletterSection/index.tsx @@ -1,6 +1,7 @@ import styled from "@emotion/styled"; import { useTranslations } from "next-intl"; import { borderWidths, colors, spacings } from "../../../common/styleVariables"; +import { trackEvent } from "../../../services/analytics"; import ButtonWithIcon from "../../ButtonWithIcon"; import Spacer from "../../Spacer"; import Text from "../../Text"; @@ -21,8 +22,15 @@ const TextContainer = styled.div({ flex: "1 1 auto", }); -export default function NewsletterSection() { +type Props = { + trackingPosition: string; +}; + +export default function NewsletterSection({ trackingPosition }: Props) { const t = useTranslations("Home.newsletter-section"); + const trackButtonClick = () => { + trackEvent("Homepage", "Click 'Contact us' button", trackingPosition); + }; return ( @@ -34,7 +42,7 @@ export default function NewsletterSection() { {t("title")} - + {t("button")} diff --git a/src/components/HomePage/RequestCreatorAndList/RequestCreator/index.tsx b/src/components/HomePage/RequestCreatorAndList/RequestCreator/index.tsx index 4f4319b..3b5ca35 100644 --- a/src/components/HomePage/RequestCreatorAndList/RequestCreator/index.tsx +++ b/src/components/HomePage/RequestCreatorAndList/RequestCreator/index.tsx @@ -1,5 +1,6 @@ import { useTranslations } from "next-intl"; import { useCallback, useState } from "react"; +import { trackEvent } from "../../../../services/analytics"; import { getRandomIndexWithout } from "../../../../services/arrays"; import ButtonWithIcon from "../../../ButtonWithIcon"; import Spacer from "../../../Spacer"; @@ -30,6 +31,7 @@ export default function RequestCreator({ onStartRequestCreation, onRequestCreate const handleCreateRequest = useCallback(() => { onStartRequestCreation(); randomizeRequest(); + trackEvent("Homepage", "Create random request"); }, [onStartRequestCreation, randomizeRequest]); const handleAnimationFinished = useCallback(() => { if (request) { diff --git a/src/components/HomePage/index.tsx b/src/components/HomePage/index.tsx index cd8446e..c0a2836 100644 --- a/src/components/HomePage/index.tsx +++ b/src/components/HomePage/index.tsx @@ -67,19 +67,19 @@ export default function HomePage() { - +
- +
- +
- + ); } diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts new file mode 100644 index 0000000..6c85a2e --- /dev/null +++ b/src/hooks/useAnalytics.ts @@ -0,0 +1,17 @@ +import { init } from "@socialgouv/matomo-next"; +import { useEffect, useRef } from "react"; + +const MATOMO_URL = process.env.NEXT_PUBLIC_MATOMO_URL as string; +const MATOMO_SITE_ID = process.env.NEXT_PUBLIC_MATOMO_SITE_ID as string; + +export function useAnalytics() { + const matomoInitialized = useRef(false); + useEffect(() => { + if (MATOMO_URL && MATOMO_SITE_ID && matomoInitialized.current === false) { + init({ url: MATOMO_URL, siteId: MATOMO_SITE_ID }); + } + return () => { + matomoInitialized.current = true; + }; + }, []); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 46695f6..1586e38 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,12 +2,14 @@ import { NextIntlClientProvider } from "next-intl"; import type { AppProps } from "next/app"; import "react-tooltip/dist/react-tooltip.css"; import GlobalStyles from "../components/GlobalStyles"; +import { useAnalytics } from "../hooks/useAnalytics"; type CustomPageProps = { messages: IntlMessages; }; export default function App({ Component, pageProps }: AppProps) { + useAnalytics(); return ( diff --git a/src/services/analytics.ts b/src/services/analytics.ts new file mode 100644 index 0000000..44201c6 --- /dev/null +++ b/src/services/analytics.ts @@ -0,0 +1,14 @@ +import { push } from "@socialgouv/matomo-next"; + +type EventCategory = "Homepage"; + +/** + * Track a custom analytics event via Matomo. + * Docs: https://matomo.org/faq/reports/implement-event-tracking-with-matomo/ + */ +export function trackEvent(eventCategory: EventCategory, eventAction: string, eventName?: string, eventValue?: number) { + const filledParameters = ["trackEvent", eventCategory, eventAction, eventName, eventValue].filter( + (parameter) => parameter !== undefined + ); + push(filledParameters); +}