@liiift-studio/sanity-visitor-insights 0.11.0 → 0.12.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
@@ -184,6 +184,7 @@ empty charts — pasting a measurement id here is caught by name.
184
184
 
185
185
  | Variable | Where | What |
186
186
  |---|---|---|
187
+ | `VISITOR_INSIGHTS_ENABLED` | Site (server) | **Required.** Any truthy value switches the route on |
187
188
  | `VISITOR_INSIGHTS_GA4_SERVICE_ACCOUNT` | Site (server) | Service-account JSON, raw or base64 |
188
189
  | `VISITOR_INSIGHTS_VERCEL_TOKEN` | Site (server) | Vercel API token with project read access |
189
190
  | `SANITY_STUDIO_PROJECT_ID` | Site (server) | Already set; used to verify Studio tokens |
@@ -191,6 +192,17 @@ empty charts — pasting a measurement id here is caught by name.
191
192
  The service account needs **Viewer** on each GA4 property. Nothing here is `NEXT_PUBLIC_`; none of
192
193
  it reaches the browser.
193
194
 
195
+ #### The master switch
196
+
197
+ `VISITOR_INSIGHTS_ENABLED` is an explicit opt-in. Unset, the route answers `503` with
198
+ `disabled: true` and does no Sanity or GA4 work, and the Studio tool shows a neutral "Switched off"
199
+ card rather than an error. Set it to anything that is not `false`, `0`, `off`, `no`, `disabled` or
200
+ empty — a word, a code, `true`, whatever the site's operator prefers.
201
+
202
+ This is deliberately the opposite default to `SALES_PORTAL_ENABLED`, which is on unless the value
203
+ is literally `"false"`. Mounting this route wires up analytics credentials, so it should not begin
204
+ answering on a site where nobody chose to switch it on.
205
+
194
206
  ---
195
207
 
196
208
  ## Before it will show anything useful
package/dist/index.d.mts CHANGED
@@ -54,6 +54,7 @@ type ReportState<T> = {
54
54
  } | {
55
55
  status: 'error';
56
56
  message: string;
57
+ disabled?: boolean;
57
58
  };
58
59
  /** Options for useReport. */
59
60
  interface UseReportOptions {
package/dist/index.d.ts CHANGED
@@ -54,6 +54,7 @@ type ReportState<T> = {
54
54
  } | {
55
55
  status: 'error';
56
56
  message: string;
57
+ disabled?: boolean;
57
58
  };
58
59
  /** Options for useReport. */
59
60
  interface UseReportOptions {
package/dist/index.js CHANGED
@@ -103,12 +103,14 @@ function useReport({ apiBaseUrl, report, range, custom }) {
103
103
  if (requestIdRef.current !== requestId) return;
104
104
  if (!response.ok) {
105
105
  let detail = response.status === 401 ? "Not authorised \u2014 sign in to the Studio again." : `Request failed (${response.status})`;
106
- if (response.status === 400) {
106
+ let disabled = false;
107
+ if (response.status === 400 || response.status === 503) {
107
108
  const body = await response.json().catch(() => null);
108
109
  if (body?.error) detail = body.error;
110
+ disabled = body?.disabled === true;
109
111
  }
110
112
  if (requestIdRef.current !== requestId) return;
111
- setState({ status: "error", message: detail });
113
+ setState({ status: "error", message: detail, disabled });
112
114
  return;
113
115
  }
114
116
  const envelope = await response.json();
@@ -1126,16 +1128,20 @@ function ReportPanel({
1126
1128
  custom
1127
1129
  }) {
1128
1130
  const { state, reload } = useReport({ apiBaseUrl, report, range, custom });
1129
- const liveMessage = state.status === "loading" ? "Loading report" : state.status === "ready" ? `${report} updated for ${range === "custom" ? `${custom.start} to ${custom.end}` : `the selected ${range}`}` : state.status === "error" ? `Report failed: ${state.message}` : "";
1131
+ const liveMessage = state.status === "loading" ? "Loading report" : state.status === "ready" ? `${report} updated for ${range === "custom" ? `${custom.start} to ${custom.end}` : `the selected ${range}`}` : state.status === "error" ? state.disabled ? "Visitor insights is switched off for this site" : `Report failed: ${state.message}` : "";
1130
1132
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
1131
1133
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Box, { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: liveMessage }),
1132
1134
  state.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Flex, { align: "center", gap: 3, padding: 4, children: [
1133
1135
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Spinner, { muted: true }),
1134
1136
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Loading\u2026" })
1135
1137
  ] }),
1136
- state.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 4, radius: 2, tone: "critical", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
1137
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: state.message }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Button, { text: "Try again", mode: "ghost", fontSize: 1, onClick: reload }) })
1138
+ state.status === "error" && // A switched-off site is a configuration state, not a fault: it gets a neutral card
1139
+ // and no retry, because retrying cannot change the answer. Everything else keeps
1140
+ // the critical tone and the retry.
1141
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 4, radius: 2, tone: state.disabled ? "transparent" : "critical", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
1142
+ state.disabled && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: "Switched off" }),
1143
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: state.disabled, children: state.message }),
1144
+ !state.disabled && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Button, { text: "Try again", mode: "ghost", fontSize: 1, onClick: reload }) })
1139
1145
  ] }) }),
1140
1146
  state.status === "ready" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
1141
1147
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SourceStatusRow, { sources: state.envelope.sources }),