diff --git a/src/next-appdir/index.ts b/src/next-appdir/index.ts index e0b44f2b..97685805 100644 --- a/src/next-appdir/index.ts +++ b/src/next-appdir/index.ts @@ -1,3 +1,4 @@ export type { RegisterLink } from "../link"; export type { DefaultColorScheme } from "./zz_internal/defaultColorScheme"; export { startReactDsfr } from "./zz_internal/start"; +export type { EulerianAnalytics } from "../start"; diff --git a/src/next-appdir/zz_internal/start.ts b/src/next-appdir/zz_internal/start.ts index aa2f19c1..761ba668 100644 --- a/src/next-appdir/zz_internal/start.ts +++ b/src/next-appdir/zz_internal/start.ts @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { start } from "../../start"; +import { start, type EulerianAnalytics } from "../../start"; import type { RegisteredLinkProps } from "../../link"; import { setLink } from "../../link"; import { type DefaultColorScheme, setDefaultColorSchemeClientSide } from "./defaultColorScheme"; @@ -14,8 +14,9 @@ export function startReactDsfr(params: { verbose?: boolean; /** Default: */ Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType; + eulerianAnalytics?: EulerianAnalytics; }) { - const { defaultColorScheme, verbose = false, Link } = params; + const { defaultColorScheme, verbose = false, Link, eulerianAnalytics } = params; setDefaultColorSchemeClientSide({ defaultColorScheme }); @@ -27,6 +28,7 @@ export function startReactDsfr(params: { start({ defaultColorScheme, verbose, + eulerianAnalytics, "nextParams": { "doPersistDarkModePreferenceWithCookie": false, "registerEffectAction": action => { diff --git a/src/next-pagesdir.tsx b/src/next-pagesdir.tsx index 3f58c480..8fe86965 100644 --- a/src/next-pagesdir.tsx +++ b/src/next-pagesdir.tsx @@ -16,13 +16,15 @@ import FaviconSvg from "./dsfr/favicon/favicon.svg"; import FaviconIco from "./dsfr/favicon/favicon.ico"; import { getAssetUrl } from "./tools/getAssetUrl"; import { getColors } from "./fr/colors"; -import { start } from "./start"; +import { start, type EulerianAnalytics } from "./start"; import type { RegisterLink, RegisteredLinkProps } from "./link"; import { setLink } from "./link"; import { setUseLang } from "./i18n"; import { assert } from "tsafe/assert"; import "./assets/dsfr_plus_icons.css"; +export type { EulerianAnalytics }; + const isProduction = process.env.NODE_ENV !== "development"; export type { RegisterLink, RegisteredLinkProps }; @@ -41,6 +43,7 @@ export type CreateNextDsfrIntegrationApiParams = { doPersistDarkModePreferenceWithCookie?: boolean; /** Default: ()=> "fr" */ useLang?: () => string; + eulerianAnalytics?: EulerianAnalytics; }; function readIsDarkInCookie(cookie: string) { @@ -88,7 +91,8 @@ export function createNextDsfrIntegrationApi( Link, preloadFonts = [], doPersistDarkModePreferenceWithCookie = false, - useLang + useLang, + eulerianAnalytics } = params; let isAfterFirstEffect = false; @@ -106,6 +110,7 @@ export function createNextDsfrIntegrationApi( start({ defaultColorScheme, verbose, + eulerianAnalytics, "nextParams": { doPersistDarkModePreferenceWithCookie, "registerEffectAction": action => { diff --git a/src/spa.ts b/src/spa.ts index 2b25d6f3..18598738 100644 --- a/src/spa.ts +++ b/src/spa.ts @@ -1,10 +1,12 @@ import type { ReactNode } from "react"; -import { start } from "./start"; +import { start, type EulerianAnalytics } from "./start"; import type { RegisterLink, RegisteredLinkProps } from "./link"; import { setLink } from "./link"; import { setUseLang } from "./i18n"; import type { ColorScheme } from "./useIsDark"; +export type { EulerianAnalytics }; + export type { RegisterLink, RegisteredLinkProps }; export function startReactDsfr(params: { @@ -15,8 +17,9 @@ export function startReactDsfr(params: { Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType; /** Default: ()=> "fr" */ useLang?: () => string; + eulerianAnalytics?: EulerianAnalytics; }) { - const { defaultColorScheme, verbose = false, Link, useLang } = params; + const { defaultColorScheme, verbose = false, Link, useLang, eulerianAnalytics } = params; if (Link !== undefined) { setLink({ Link }); @@ -29,6 +32,7 @@ export function startReactDsfr(params: { start({ defaultColorScheme, verbose, + eulerianAnalytics, "nextParams": undefined }); } diff --git a/src/start.ts b/src/start.ts index 19b6fd90..eae2b2aa 100644 --- a/src/start.ts +++ b/src/start.ts @@ -6,6 +6,7 @@ import { startClientSideIsDarkLogic } from "./useIsDark/client"; type Params = { defaultColorScheme: ColorScheme | "system"; verbose: boolean; + eulerianAnalytics: EulerianAnalytics | undefined; nextParams: | { doPersistDarkModePreferenceWithCookie: boolean; @@ -17,7 +18,7 @@ type Params = { let isStarted = false; export async function start(params: Params) { - const { defaultColorScheme, verbose, nextParams } = params; + const { defaultColorScheme, verbose, nextParams, eulerianAnalytics } = params; assert(isBrowser); @@ -37,11 +38,89 @@ export async function start(params: Params) { registerEffectAction }); - (window as any).dsfr = { verbose, "mode": "react" }; + (window as any).dsfr = { + verbose, + "mode": "react", + "analytics": eulerianAnalytics + }; await import("./dsfr/dsfr.module" as any); + if (eulerianAnalytics !== undefined) { + await import("./dsfr/analytics/analytics.module.js" as any); + } + const { dsfr } = window as unknown as { dsfr: { start: () => void } }; registerEffectAction(() => dsfr.start()); } + +export type EulerianAnalytics = { + domain: string; + /** default: false */ + enableRating?: boolean; + page?: Partial<{ + path: string; // path for page tracking + referrer: string; // referrer for virtual pages (not for real page, eulerian automatically collects document.referrer) + id: string; // unique page id (string) + title: string; // page title for virtual pages + name: string; // equivalent to title if not defined + author: string; // page author name + date: string; // page creation date + labels: string[]; + tags: string[]; // no tags limit + template: string; // page template + group: string; // page group. if not defined, fallback to template value + segment: string; // site segment. if not defined, fallback to template value + subtemplate: string; // page subtemplate + theme: string; // page theme + subtheme: string; // page subtheme + related: string; // related page id + depth: number; // page depth + isError: boolean; // is this an error page (404, 500, 503...) + current: number; // In case of pagination, current page number + total: number; // In case of pagination, total pages number + filters: string; // array of filters that were applied on the page (strings) + }>; + site?: Partial<{ + environment: "production" | "stage" | "production"; // by default development ['development', 'stage', 'production'] + entity: string; // Entity responsible for website + language: string; // language of the website (ISO 639-1). default to html lang + target: string; // site target + type: string; // site type + region: string; // region of the website (ISO 3166-2:FR) + department: string; // department of the website (ISO 3166-2:FR) + }>; + user?: Partial<{ + connect: { + uid: string; // user id - required when connected + email: string; // encoded user email - required when connected + isNew: boolean; // user just registered + }; + profile: string; // user profile + language: string; + type: string; + }>; + search?: Partial<{ + engine: string; + results: number; + terms: string; + category: string; + theme: string; + type: string; + method: string; + }>; + funnel?: Partial<{ + id: string; + type: string; + name: string; + step: string; // step name + current: number; // step number + total: number; // total number of steps + objective: string; // form objective + error: string; // form's error type + }>; + cmp?: Partial<{ + id: string; + }>; +};