@convex-internal/web-analytics 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,13 +31,8 @@ capture("www-login", undefined, {
31
31
 
32
32
  ### Consent banner
33
33
 
34
- The banner shows when `consent` is `"undecided"`. It records
35
- `cookie_consent_shown` when it appears and `cookie_consent_decided` with
36
- `{ decision: "accepted" | "declined" }` just before the choice is saved.
37
-
38
- The banner stays hidden while Global Privacy Control is on, because that
39
- resolves `consent` to `"declined"` rather than `"undecided"`. GPC does not
40
- write the cookie; see [Cookie behavior](#cookie-behavior).
34
+ The banner is visible when `consent` is `"undecided"`. It is hidden while
35
+ `consent` is `"unknown"`, `"consented"`, or `"declined"`.
41
36
 
42
37
  Theme with `--wa-banner-*` variables on `.wa-banner`:
43
38
 
@@ -59,13 +54,22 @@ Theme with `--wa-banner-*` variables on `.wa-banner`:
59
54
  - Link and focus: `--wa-banner-link-color`, `--wa-banner-focus-color`
60
55
  - Buttons: `--wa-banner-button-border-radius`, `--wa-banner-button-padding`,
61
56
  `--wa-banner-button-min-height`, `--wa-banner-button-font-size`,
62
- `--wa-banner-button-font-weight`, `--wa-banner-button-line-height`
57
+ `--wa-banner-button-font-weight`
63
58
  - Accept button: `--wa-banner-accept-background-color`, `--wa-banner-accept-text-color`
64
59
  - Decline button: `--wa-banner-decline-background-color`,
65
60
  `--wa-banner-decline-text-color`
66
61
 
67
62
  Buttons default to 80% opacity and go to 100% on hover or keyboard focus.
68
63
 
64
+ ### Consent events
65
+
66
+ `cookie_consent_shown` is recorded when the banner appears.
67
+ `cookie_consent_decided` is recorded with
68
+ `{ decision: "accepted" | "declined", source: "banner" }` just before the
69
+ choice is saved, and with `{ decision: "declined", source: "gpc" }` when
70
+ the GPC preference is applied. Historic events without `source` are banner
71
+ decisions.
72
+
69
73
  ### Cookie behavior
70
74
 
71
75
  Once the cookie has been read, `consent` is resolved in this order:
package/dist/banner.css CHANGED
@@ -14,6 +14,7 @@
14
14
  color: var(--wa-banner-text-color, black);
15
15
  font-family: var(--wa-banner-font-family, inherit);
16
16
  font-size: var(--wa-banner-font-size, 1rem);
17
+ line-height: 1.25;
17
18
 
18
19
  .wa-banner-text {
19
20
  margin: 0 0 0.5rem;
@@ -44,7 +45,7 @@
44
45
  font-family: inherit;
45
46
  font-size: var(--wa-banner-button-font-size, inherit);
46
47
  font-weight: var(--wa-banner-button-font-weight, inherit);
47
- line-height: var(--wa-banner-button-line-height, inherit);
48
+ line-height: 1;
48
49
  cursor: pointer;
49
50
  opacity: 0.8;
50
51
  transition: opacity 0.15s;
@@ -1,14 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef } from "react";
4
- import { bannerCopy } from "../strings.js";
5
4
  import { capture } from "../capture.js";
5
+ import { hasGPC } from "../consent.js";
6
+ import { bannerCopy } from "../strings.js";
6
7
  import { useConsent } from "./useConsent.js";
7
8
  import "../banner.css";
8
9
  export function ConsentBanner() {
9
10
  const { consent, setConsent } = useConsent();
10
11
  const shouldShow = consent === "undecided";
11
12
  const wasShowing = useRef(false);
13
+ const recordedGPCDecision = useRef(false);
12
14
  useEffect(() => {
13
15
  if (shouldShow && !wasShowing.current) {
14
16
  // Captures once when the banner becomes visible, not on later renders.
@@ -16,13 +18,22 @@ export function ConsentBanner() {
16
18
  }
17
19
  wasShowing.current = shouldShow;
18
20
  }, [shouldShow]);
21
+ useEffect(() => {
22
+ if (consent === "declined" && hasGPC() && !recordedGPCDecision.current) {
23
+ recordedGPCDecision.current = true;
24
+ capture("cookie_consent_decided", {
25
+ decision: "declined",
26
+ source: "gpc",
27
+ });
28
+ }
29
+ }, [consent]);
19
30
  if (!shouldShow) {
20
31
  return null;
21
32
  }
22
33
  const decide = (decision) => {
23
34
  try {
24
35
  // Captures before the cookie write so decline is captured cookieless.
25
- capture("cookie_consent_decided", { decision });
36
+ capture("cookie_consent_decided", { decision, source: "banner" });
26
37
  }
27
38
  finally {
28
39
  setConsent(decision === "accepted" ? "consented" : "declined");
@@ -9,6 +9,7 @@
9
9
 
10
10
  let isReady = $state(false);
11
11
  let cookieValue = $state<boolean | undefined>(undefined);
12
+ let recordedGPCDecision = false;
12
13
 
13
14
  const consent = $derived(resolveConsent(isReady, cookieValue, hasGPC()));
14
15
  const shouldShowBanner = $derived(consent === "undecided");
@@ -60,6 +61,17 @@
60
61
  wasShowingBanner = shouldShowBanner;
61
62
  });
62
63
 
64
+ $effect(() => {
65
+ if (consent === "declined" && hasGPC() && !recordedGPCDecision) {
66
+ // GPC hides the banner, so record the decline here instead of a click.
67
+ recordedGPCDecision = true;
68
+ capture("cookie_consent_decided", {
69
+ decision: "declined",
70
+ source: "gpc",
71
+ });
72
+ }
73
+ });
74
+
63
75
  function setConsent(status: "consented" | "declined") {
64
76
  setCookie(COOKIE_NAME, status === "consented" ? "true" : "false");
65
77
  cookieValue = status === "consented";
@@ -68,7 +80,7 @@
68
80
  function decide(decision: "accepted" | "declined") {
69
81
  try {
70
82
  // Captures before the cookie write so decline is captured cookieless.
71
- capture("cookie_consent_decided", { decision });
83
+ capture("cookie_consent_decided", { decision, source: "banner" });
72
84
  } finally {
73
85
  setConsent(decision === "accepted" ? "consented" : "declined");
74
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@convex-internal/web-analytics",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {