@liiift-studio/sanity-visitor-insights 0.9.0 → 0.10.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/dist/index.js +346 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +346 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/studio/Figure.tsx +243 -21
- package/src/studio/VisitorInsightsTool.tsx +71 -17
- package/src/studio/panels.test.tsx +94 -4
- package/src/studio/panels.tsx +213 -93
package/dist/index.js
CHANGED
|
@@ -200,6 +200,21 @@ var legendRow = {
|
|
|
200
200
|
alignItems: "center",
|
|
201
201
|
flexWrap: "wrap"
|
|
202
202
|
};
|
|
203
|
+
var disclosure = {
|
|
204
|
+
appearance: "none",
|
|
205
|
+
background: "transparent",
|
|
206
|
+
border: "none",
|
|
207
|
+
color: "inherit",
|
|
208
|
+
opacity: 0.75,
|
|
209
|
+
font: "inherit",
|
|
210
|
+
fontSize: "0.85em",
|
|
211
|
+
padding: "2px 0",
|
|
212
|
+
textDecoration: "underline",
|
|
213
|
+
textUnderlineOffset: 3,
|
|
214
|
+
cursor: "pointer",
|
|
215
|
+
justifySelf: "start",
|
|
216
|
+
width: "fit-content"
|
|
217
|
+
};
|
|
203
218
|
var noticeRow = {
|
|
204
219
|
display: "flex",
|
|
205
220
|
gap: 12,
|
|
@@ -208,11 +223,30 @@ var noticeRow = {
|
|
|
208
223
|
};
|
|
209
224
|
var noticeBadge = { flex: "0 0 auto" };
|
|
210
225
|
function NoticeList({ notices }) {
|
|
226
|
+
const [expanded, setExpanded] = import_react2.default.useState(false);
|
|
211
227
|
if (notices.length === 0) return null;
|
|
212
|
-
|
|
228
|
+
const alwaysShown = notices.slice(0, 2);
|
|
229
|
+
const hidden = notices.slice(2);
|
|
230
|
+
const item = (notice) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: noticeRow, children: [
|
|
213
231
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: noticeBadge, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Caveat" }) }),
|
|
214
232
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, children: notice })
|
|
215
|
-
] }) }) }, notice)
|
|
233
|
+
] }) }) }, notice);
|
|
234
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
235
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 8 }, children: [
|
|
236
|
+
alwaysShown.map(item),
|
|
237
|
+
expanded && hidden.map(item)
|
|
238
|
+
] }),
|
|
239
|
+
hidden.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
240
|
+
"button",
|
|
241
|
+
{
|
|
242
|
+
type: "button",
|
|
243
|
+
style: disclosure,
|
|
244
|
+
onClick: () => setExpanded((open) => !open),
|
|
245
|
+
"aria-expanded": expanded,
|
|
246
|
+
children: expanded ? "Show fewer caveats" : `${hidden.length} more ${hidden.length === 1 ? "caveat" : "caveats"}`
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
] });
|
|
216
250
|
}
|
|
217
251
|
var CHART = { w: 760, h: 240, left: 52, right: 12, top: 16, bottom: 30 };
|
|
218
252
|
function niceCeiling(value) {
|
|
@@ -393,6 +427,104 @@ function TrendChart({ points, label = "Daily pageviews, GA4 against Vercel" }) {
|
|
|
393
427
|
] })
|
|
394
428
|
] });
|
|
395
429
|
}
|
|
430
|
+
function SortableTable({ caption, columns, rows, rowKey, initialSort }) {
|
|
431
|
+
const [sort, setSort] = import_react2.default.useState(
|
|
432
|
+
initialSort ? { key: initialSort, desc: true } : null
|
|
433
|
+
);
|
|
434
|
+
const active = sort ? columns.find((c) => c.key === sort.key) : void 0;
|
|
435
|
+
const ordered = import_react2.default.useMemo(() => {
|
|
436
|
+
if (!active || !sort) return rows;
|
|
437
|
+
const copy = [...rows];
|
|
438
|
+
copy.sort((a, b) => {
|
|
439
|
+
const av = active.sortValue(a);
|
|
440
|
+
const bv = active.sortValue(b);
|
|
441
|
+
if (av === null && bv === null) return 0;
|
|
442
|
+
if (av === null) return 1;
|
|
443
|
+
if (bv === null) return -1;
|
|
444
|
+
const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
|
|
445
|
+
return sort.desc ? -cmp : cmp;
|
|
446
|
+
});
|
|
447
|
+
return copy;
|
|
448
|
+
}, [rows, active, sort]);
|
|
449
|
+
const toggle = (column) => {
|
|
450
|
+
setSort((current) => {
|
|
451
|
+
if (current?.key !== column.key) return { key: column.key, desc: Boolean(column.numeric) };
|
|
452
|
+
return { key: column.key, desc: !current.desc };
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Card, { radius: 2, tone: "transparent", border: true, style: tableWrapper, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: tableBase, children: [
|
|
456
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("caption", { style: visuallyHidden, children: caption }),
|
|
457
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.map((column) => {
|
|
458
|
+
const isActive = sort?.key === column.key;
|
|
459
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
460
|
+
"th",
|
|
461
|
+
{
|
|
462
|
+
scope: "col",
|
|
463
|
+
style: column.numeric ? headCellNumeric : headCell,
|
|
464
|
+
"aria-sort": isActive ? sort?.desc ? "descending" : "ascending" : "none",
|
|
465
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", style: sortButton(Boolean(column.numeric)), onClick: () => toggle(column), children: [
|
|
466
|
+
column.label,
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: sortMark, children: isActive ? sort?.desc ? "\u25BC" : "\u25B2" : "\u2195" })
|
|
468
|
+
] })
|
|
469
|
+
},
|
|
470
|
+
column.key
|
|
471
|
+
);
|
|
472
|
+
}) }) }),
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: ordered.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.map((column, index) => {
|
|
474
|
+
const content = column.render(row);
|
|
475
|
+
return index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "row", style: bodyCell, children: content }, column.key) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: column.numeric ? bodyCellNumeric : bodyCell, children: content }, column.key);
|
|
476
|
+
}) }, rowKey(row))) })
|
|
477
|
+
] }) });
|
|
478
|
+
}
|
|
479
|
+
var tableWrapper = { overflowX: "auto", width: "100%" };
|
|
480
|
+
var tableBase = { width: "100%", borderCollapse: "collapse", minWidth: 420 };
|
|
481
|
+
var headCell = {
|
|
482
|
+
padding: 0,
|
|
483
|
+
textAlign: "left",
|
|
484
|
+
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.3))",
|
|
485
|
+
whiteSpace: "nowrap"
|
|
486
|
+
};
|
|
487
|
+
var headCellNumeric = { ...headCell, textAlign: "right" };
|
|
488
|
+
var bodyCell = {
|
|
489
|
+
padding: "8px 12px",
|
|
490
|
+
textAlign: "left",
|
|
491
|
+
fontWeight: 400,
|
|
492
|
+
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.18))"
|
|
493
|
+
};
|
|
494
|
+
var bodyCellNumeric = {
|
|
495
|
+
...bodyCell,
|
|
496
|
+
textAlign: "right",
|
|
497
|
+
fontVariantNumeric: "tabular-nums"
|
|
498
|
+
};
|
|
499
|
+
function sortButton(numeric) {
|
|
500
|
+
return {
|
|
501
|
+
appearance: "none",
|
|
502
|
+
background: "transparent",
|
|
503
|
+
border: "none",
|
|
504
|
+
color: "inherit",
|
|
505
|
+
font: "inherit",
|
|
506
|
+
fontSize: "0.78em",
|
|
507
|
+
letterSpacing: "0.06em",
|
|
508
|
+
textTransform: "uppercase",
|
|
509
|
+
opacity: 0.7,
|
|
510
|
+
padding: "8px 12px",
|
|
511
|
+
width: "100%",
|
|
512
|
+
display: "flex",
|
|
513
|
+
gap: 6,
|
|
514
|
+
alignItems: "center",
|
|
515
|
+
justifyContent: numeric ? "flex-end" : "flex-start",
|
|
516
|
+
cursor: "pointer"
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
var sortMark = { opacity: 0.7, fontSize: "0.9em" };
|
|
520
|
+
var visuallyHidden = {
|
|
521
|
+
position: "absolute",
|
|
522
|
+
width: 1,
|
|
523
|
+
height: 1,
|
|
524
|
+
overflow: "hidden",
|
|
525
|
+
clip: "rect(0 0 0 0)",
|
|
526
|
+
whiteSpace: "nowrap"
|
|
527
|
+
};
|
|
396
528
|
|
|
397
529
|
// src/studio/VisitorInsightsTool.tsx
|
|
398
530
|
var import_sanity_ui_compat4 = require("@liiift-studio/sanity-ui-compat");
|
|
@@ -400,23 +532,34 @@ var import_sanity_ui_compat4 = require("@liiift-studio/sanity-ui-compat");
|
|
|
400
532
|
// src/studio/panels.tsx
|
|
401
533
|
var import_sanity_ui_compat2 = require("@liiift-studio/sanity-ui-compat");
|
|
402
534
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
535
|
+
function metricSortValue(metric) {
|
|
536
|
+
return metric.status === "unavailable" ? null : metric.value;
|
|
537
|
+
}
|
|
403
538
|
function maxOf(metrics) {
|
|
404
539
|
return metrics.reduce((max, metric) => metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
|
|
405
540
|
}
|
|
406
|
-
var
|
|
541
|
+
var sectionHeading = { margin: "0 0 2px", lineHeight: 1.3 };
|
|
542
|
+
var sourceLink = {
|
|
543
|
+
color: "inherit",
|
|
544
|
+
textDecoration: "underline",
|
|
545
|
+
textUnderlineOffset: 3,
|
|
546
|
+
textDecorationThickness: 1
|
|
547
|
+
};
|
|
548
|
+
function isLinkableHost(source) {
|
|
549
|
+
return /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(source);
|
|
550
|
+
}
|
|
407
551
|
var cardGrid = {
|
|
408
552
|
display: "grid",
|
|
409
553
|
gridTemplateColumns: "repeat(auto-fit, minmax(min(14rem, 100%), 1fr))",
|
|
410
554
|
gap: 16
|
|
411
555
|
};
|
|
412
|
-
var table = { width: "100%", borderCollapse: "collapse", minWidth: 420 };
|
|
413
556
|
var cell = { padding: "8px 12px", textAlign: "left", borderBottom: "1px solid var(--card-border-color)" };
|
|
414
557
|
var numericCell = { ...cell, textAlign: "right" };
|
|
415
558
|
function MeasurementHealthPanel({ data }) {
|
|
416
559
|
const pageviewMax = maxOf([data.ga4Pageviews, data.vercelPageviews]);
|
|
417
560
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
418
561
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
419
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Pageviews, source against source" }),
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Pageviews, source against source" }),
|
|
420
563
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "The same unit on both sides. Vercel is cookieless and ungated; GA4 is consent-gated and blockable, so GA4 seeing fewer is expected." }),
|
|
421
564
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComparisonBar, { label: "Vercel pageviews", metric: data.vercelPageviews, max: pageviewMax, tone: "primary" }),
|
|
422
565
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComparisonBar, { label: "GA4 pageviews", metric: data.ga4Pageviews, max: pageviewMax, tone: "default" })
|
|
@@ -426,12 +569,12 @@ function MeasurementHealthPanel({ data }) {
|
|
|
426
569
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: data.interpretation })
|
|
427
570
|
] }) }),
|
|
428
571
|
(data.daily?.length ?? 0) >= 3 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
429
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Day by day" }),
|
|
572
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Day by day" }),
|
|
430
573
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "A gap that has always been there is consent and blocking. A gap that opens on one day is an incident." }),
|
|
431
574
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrendChart, { points: data.daily ?? [] })
|
|
432
575
|
] }),
|
|
433
576
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
434
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Context" }),
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Context" }),
|
|
435
578
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "Different units to the figures above, and to each other. Shown for scale, never differenced." }),
|
|
436
579
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardGrid, children: [
|
|
437
580
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
@@ -466,72 +609,157 @@ function AcquisitionPanel({ data }) {
|
|
|
466
609
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 4, children: formatPercent(data.unattributedShare, 1) })
|
|
467
610
|
] }) })
|
|
468
611
|
] }),
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
470
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
471
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
613
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Traffic sources" }),
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "Sort by any column. Sessions is the default." }),
|
|
615
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
616
|
+
SortableTable,
|
|
617
|
+
{
|
|
618
|
+
caption: "Traffic sources by sessions",
|
|
619
|
+
initialSort: "sessions",
|
|
620
|
+
rows: data.rows ?? [],
|
|
621
|
+
rowKey: (row) => `${row.source}-${row.channel}`,
|
|
622
|
+
columns: [
|
|
623
|
+
{
|
|
624
|
+
key: "source",
|
|
625
|
+
label: "Source",
|
|
626
|
+
sortValue: (row) => row.source,
|
|
627
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
628
|
+
isLinkableHost(row.source) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
629
|
+
"a",
|
|
630
|
+
{
|
|
631
|
+
href: `https://${row.source}`,
|
|
632
|
+
target: "_blank",
|
|
633
|
+
rel: "noopener noreferrer",
|
|
634
|
+
style: sourceLink,
|
|
635
|
+
children: row.source
|
|
636
|
+
}
|
|
637
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.source }),
|
|
638
|
+
row.designIndustry && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: "primary", fontSize: 0, children: "Design" }),
|
|
639
|
+
row.unattributed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: "caution", fontSize: 0, children: "Unattributed" })
|
|
640
|
+
] })
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
key: "channel",
|
|
644
|
+
label: "Channel",
|
|
645
|
+
sortValue: (row) => row.channel,
|
|
646
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.channel })
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
key: "sessions",
|
|
650
|
+
label: "Sessions",
|
|
651
|
+
numeric: true,
|
|
652
|
+
sortValue: (row) => row.sessions,
|
|
653
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: formatCount(row.sessions) })
|
|
654
|
+
}
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
)
|
|
658
|
+
] }),
|
|
486
659
|
data.rowsWithheld && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NoticeList, { notices: ["GA4 withheld some low-traffic rows for privacy, so this list is shorter than reality."] })
|
|
487
660
|
] });
|
|
488
661
|
}
|
|
489
662
|
function JourneyPanel({ data }) {
|
|
490
|
-
const
|
|
663
|
+
const allSteps = data.steps ?? [];
|
|
664
|
+
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
665
|
+
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
666
|
+
const max = maxOf(shown.map((step) => step.count));
|
|
491
667
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
492
668
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: data.approximationNote }) }),
|
|
493
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children:
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: shown.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 2, children: [
|
|
494
670
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComparisonBar, { label: step.label, metric: step.count, max, tone: "primary" }),
|
|
495
|
-
step.conversionFromPrevious !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Text, { size: 0, muted: true, children: [
|
|
671
|
+
step.conversionFromPrevious !== null && index > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Text, { size: 0, muted: true, children: [
|
|
496
672
|
formatPercent(step.conversionFromPrevious, 1),
|
|
497
|
-
" of
|
|
673
|
+
" of ",
|
|
674
|
+
shown[index - 1]?.label.toLowerCase()
|
|
498
675
|
] })
|
|
499
676
|
] }, step.key)) }),
|
|
677
|
+
hiddenSteps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: [
|
|
678
|
+
"Not shown: ",
|
|
679
|
+
hiddenSteps.map((step) => step.label.toLowerCase()).join(", "),
|
|
680
|
+
" \u2014 not instrumented on this site, so there is no figure to place in the funnel."
|
|
681
|
+
] }),
|
|
500
682
|
(data.topLandingPages?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
501
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Where sessions began" }),
|
|
502
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Where sessions began" }),
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
685
|
+
SortableTable,
|
|
686
|
+
{
|
|
687
|
+
caption: "Landing pages by sessions",
|
|
688
|
+
initialSort: "sessions",
|
|
689
|
+
rows: data.topLandingPages ?? [],
|
|
690
|
+
rowKey: (page) => page.path,
|
|
691
|
+
columns: [
|
|
692
|
+
{
|
|
693
|
+
key: "path",
|
|
694
|
+
label: "Page",
|
|
695
|
+
sortValue: (page) => page.path,
|
|
696
|
+
render: (page) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: page.path })
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
key: "sessions",
|
|
700
|
+
label: "Sessions",
|
|
701
|
+
numeric: true,
|
|
702
|
+
sortValue: (page) => page.sessions,
|
|
703
|
+
render: (page) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: formatCount(page.sessions) })
|
|
704
|
+
}
|
|
705
|
+
]
|
|
706
|
+
}
|
|
707
|
+
)
|
|
512
708
|
] })
|
|
513
709
|
] });
|
|
514
710
|
}
|
|
515
711
|
function TypefaceInterestPanel({ data }) {
|
|
516
712
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
517
713
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: data.interpretationNote }) }),
|
|
518
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
519
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
520
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
714
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
715
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, style: sectionHeading, children: "Engagement by typeface" }),
|
|
716
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "Sort by any column. Views is the default." }),
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
718
|
+
SortableTable,
|
|
719
|
+
{
|
|
720
|
+
caption: "Engagement by typeface",
|
|
721
|
+
initialSort: "viewed",
|
|
722
|
+
rows: data.rows ?? [],
|
|
723
|
+
rowKey: (row) => row.typeface,
|
|
724
|
+
columns: [
|
|
725
|
+
{
|
|
726
|
+
key: "typeface",
|
|
727
|
+
label: "Typeface",
|
|
728
|
+
sortValue: (row) => row.typeface,
|
|
729
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.typeface })
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
key: "viewed",
|
|
733
|
+
label: "Viewed",
|
|
734
|
+
numeric: true,
|
|
735
|
+
sortValue: (row) => metricSortValue(row.viewed),
|
|
736
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.viewed, label: `${row.typeface} viewed`, size: 1 })
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
key: "tested",
|
|
740
|
+
label: "Tested",
|
|
741
|
+
numeric: true,
|
|
742
|
+
sortValue: (row) => metricSortValue(row.tested),
|
|
743
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.tested, label: `${row.typeface} tested`, size: 1 })
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
key: "bought",
|
|
747
|
+
label: "Bought",
|
|
748
|
+
numeric: true,
|
|
749
|
+
sortValue: (row) => metricSortValue(row.bought),
|
|
750
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.bought, label: `${row.typeface} bought`, size: 1 })
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
key: "testRate",
|
|
754
|
+
label: "Test rate",
|
|
755
|
+
numeric: true,
|
|
756
|
+
sortValue: (row) => row.testRate,
|
|
757
|
+
render: (row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, "aria-label": row.testRate === null ? `${row.typeface} test rate unavailable` : void 0, children: row.testRate === null ? "\u2014" : formatPercent(row.testRate, 1) })
|
|
758
|
+
}
|
|
759
|
+
]
|
|
760
|
+
}
|
|
761
|
+
)
|
|
762
|
+
] })
|
|
535
763
|
] });
|
|
536
764
|
}
|
|
537
765
|
var CHECK_TONE = {
|
|
@@ -547,15 +775,16 @@ var CHECK_WORD = {
|
|
|
547
775
|
skipped: "Skipped"
|
|
548
776
|
};
|
|
549
777
|
function DiagnosticsPanel({ data }) {
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
778
|
+
const checks = data.checks ?? [];
|
|
779
|
+
const failing = checks.filter((c) => c.status === "fail").length;
|
|
780
|
+
const warning = checks.filter((c) => c.status === "warn").length;
|
|
781
|
+
const summary = checks.length === 0 ? "No checks ran. Nothing here has been verified either way." : data.verdict === "pass" ? "Configuration and credentials check out. That covers the wiring, not whether the figures are worth trusting \u2014 the caveats on each panel still apply." : `${failing} failing, ${warning} worth a look. Panels depending on these will be wrong or incomplete until they are resolved.`;
|
|
553
782
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
554
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: CHECK_TONE[data.verdict], border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: summary }) }),
|
|
555
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children:
|
|
783
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: CHECK_TONE[data.verdict] ?? "default", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: summary }) }),
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: checks.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
556
785
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
557
786
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, weight: "semibold", children: item.label }),
|
|
558
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: CHECK_TONE[item.status], fontSize: 0, children: CHECK_WORD[item.status] })
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: CHECK_TONE[item.status] ?? "default", fontSize: 0, children: CHECK_WORD[item.status] ?? item.status })
|
|
559
788
|
] }),
|
|
560
789
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: item.detail }),
|
|
561
790
|
item.remedy && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: item.remedy })
|
|
@@ -565,16 +794,39 @@ function DiagnosticsPanel({ data }) {
|
|
|
565
794
|
|
|
566
795
|
// src/studio/VisitorInsightsTool.tsx
|
|
567
796
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
797
|
+
var tabStrip = {
|
|
798
|
+
display: "flex",
|
|
799
|
+
gap: 2,
|
|
800
|
+
alignItems: "stretch",
|
|
801
|
+
flexWrap: "wrap",
|
|
802
|
+
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.25))"
|
|
803
|
+
};
|
|
804
|
+
function tabStyle(selected) {
|
|
805
|
+
return {
|
|
806
|
+
appearance: "none",
|
|
807
|
+
background: "transparent",
|
|
808
|
+
border: "none",
|
|
809
|
+
borderBottom: `2px solid ${selected ? "currentColor" : "transparent"}`,
|
|
810
|
+
color: "inherit",
|
|
811
|
+
opacity: selected ? 1 : 0.62,
|
|
812
|
+
font: "inherit",
|
|
813
|
+
fontWeight: selected ? 600 : 400,
|
|
814
|
+
padding: "8px 12px",
|
|
815
|
+
marginBottom: -1,
|
|
816
|
+
cursor: "pointer",
|
|
817
|
+
whiteSpace: "nowrap"
|
|
818
|
+
};
|
|
819
|
+
}
|
|
568
820
|
var RANGES = [
|
|
569
|
-
{ key: "week", label: "
|
|
570
|
-
{ key: "quarter", label: "
|
|
571
|
-
{ key: "year", label: "
|
|
821
|
+
{ key: "week", label: "7 days", span: "the last 7 days" },
|
|
822
|
+
{ key: "quarter", label: "91 days", span: "the last 91 days" },
|
|
823
|
+
{ key: "year", label: "365 days", span: "the last 365 days" }
|
|
572
824
|
];
|
|
573
825
|
var PANELS = [
|
|
574
|
-
{ report: "measurement-health", label: "Measurement health", blurb: "How much of reality each source actually sees" },
|
|
575
826
|
{ report: "acquisition", label: "Acquisition", blurb: "Where visitors come from" },
|
|
576
|
-
{ report: "journey", label: "Journey", blurb: "How far visitors get
|
|
827
|
+
{ report: "journey", label: "Journey", blurb: "How far visitors get" },
|
|
577
828
|
{ report: "typeface-interest", label: "Typeface interest", blurb: "Viewed, tested and bought, by family" },
|
|
829
|
+
{ report: "measurement-health", label: "Measurement health", blurb: "How much of reality each source actually sees" },
|
|
578
830
|
{ report: "diagnostics", label: "Diagnostics", blurb: "What to fix before trusting the numbers above" }
|
|
579
831
|
];
|
|
580
832
|
function RangeSelector({ value, onChange }) {
|
|
@@ -610,6 +862,7 @@ function RangeSelector({ value, onChange }) {
|
|
|
610
862
|
mode: selected ? "default" : "bleed",
|
|
611
863
|
tone: selected ? "primary" : "default",
|
|
612
864
|
text: range.label,
|
|
865
|
+
title: `Ending today, covering ${range.span}`,
|
|
613
866
|
onClick: () => onChange(range.key),
|
|
614
867
|
fontSize: 1,
|
|
615
868
|
padding: 3
|
|
@@ -653,27 +906,30 @@ function PanelTabs({ value, onChange }) {
|
|
|
653
906
|
},
|
|
654
907
|
[value, onChange]
|
|
655
908
|
);
|
|
656
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
909
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "tablist", "aria-label": "Report", style: tabStrip, onKeyDown, children: PANELS.map((panel, index) => {
|
|
657
910
|
const selected = panel.report === value;
|
|
658
|
-
return
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
911
|
+
return (
|
|
912
|
+
// A plain button rather than the UI kit's, so the label is real children and
|
|
913
|
+
// cannot vanish: the compat shim's DOM fallback does not forward `text`, which
|
|
914
|
+
// would leave five blank tabs on any Studio version where it falls back.
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
916
|
+
"button",
|
|
917
|
+
{
|
|
918
|
+
type: "button",
|
|
919
|
+
ref: (el) => {
|
|
920
|
+
refs.current[index] = el;
|
|
921
|
+
},
|
|
922
|
+
id: `tab-${panel.report}`,
|
|
923
|
+
role: "tab",
|
|
924
|
+
"aria-selected": selected,
|
|
925
|
+
"aria-controls": `panel-${panel.report}`,
|
|
926
|
+
tabIndex: selected ? 0 : -1,
|
|
927
|
+
style: tabStyle(selected),
|
|
928
|
+
onClick: () => onChange(panel.report),
|
|
929
|
+
children: panel.label
|
|
663
930
|
},
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
"aria-selected": selected,
|
|
667
|
-
"aria-controls": `panel-${panel.report}`,
|
|
668
|
-
tabIndex: selected ? 0 : -1,
|
|
669
|
-
mode: selected ? "default" : "bleed",
|
|
670
|
-
tone: selected ? "primary" : "default",
|
|
671
|
-
text: panel.label,
|
|
672
|
-
onClick: () => onChange(panel.report),
|
|
673
|
-
fontSize: 1,
|
|
674
|
-
padding: 3
|
|
675
|
-
},
|
|
676
|
-
panel.report
|
|
931
|
+
panel.report
|
|
932
|
+
)
|
|
677
933
|
);
|
|
678
934
|
}) });
|
|
679
935
|
}
|
|
@@ -699,12 +955,12 @@ function ReportPanel({ report, apiBaseUrl, range }) {
|
|
|
699
955
|
report === "typeface-interest" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TypefaceInterestPanel, { data: state.envelope.data }),
|
|
700
956
|
report === "diagnostics" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DiagnosticsPanel, { data: state.envelope.data }),
|
|
701
957
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
958
|
+
"Figures cover ",
|
|
702
959
|
state.envelope.range.start,
|
|
703
960
|
" to ",
|
|
704
961
|
state.envelope.range.end,
|
|
705
|
-
"
|
|
706
|
-
state.envelope.range.timezone
|
|
707
|
-
")"
|
|
962
|
+
", in ",
|
|
963
|
+
state.envelope.range.timezone
|
|
708
964
|
] })
|
|
709
965
|
] })
|
|
710
966
|
] });
|
|
@@ -713,7 +969,7 @@ function VisitorInsightsTool(props) {
|
|
|
713
969
|
const apiBaseUrl = props.tool?.options?.apiBaseUrl ?? props.apiBaseUrl ?? "";
|
|
714
970
|
const siteLabel = props.tool?.options?.siteLabel ?? props.siteLabel ?? "";
|
|
715
971
|
const [range, setRange] = (0, import_react3.useState)("week");
|
|
716
|
-
const [activePanel, setActivePanel] = (0, import_react3.useState)("
|
|
972
|
+
const [activePanel, setActivePanel] = (0, import_react3.useState)("acquisition");
|
|
717
973
|
const active = PANELS.find((p) => p.report === activePanel) ?? PANELS[0];
|
|
718
974
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Container, { width: 4, padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 5, children: [
|
|
719
975
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Flex, { align: "flex-start", justify: "space-between", gap: 4, wrap: "wrap", children: [
|