@burdenoff/website-sdk 2026.1027.1 → 2026.1029.1
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 +186 -1
- package/dist/avatar-live/index.d.mts +11 -0
- package/dist/avatar-live/index.d.ts +11 -0
- package/dist/avatar-live/index.js +120 -0
- package/dist/avatar-live/index.js.map +1 -0
- package/dist/avatar-live/index.mjs +118 -0
- package/dist/avatar-live/index.mjs.map +1 -0
- package/dist/avatar-live-view/index.d.mts +42 -0
- package/dist/avatar-live-view/index.d.ts +42 -0
- package/dist/avatar-live-view/index.js +1422 -0
- package/dist/avatar-live-view/index.js.map +1 -0
- package/dist/avatar-live-view/index.mjs +1420 -0
- package/dist/avatar-live-view/index.mjs.map +1 -0
- package/dist/comparison/index.d.mts +83 -0
- package/dist/comparison/index.d.ts +83 -0
- package/dist/comparison/index.js +116 -0
- package/dist/comparison/index.js.map +1 -0
- package/dist/comparison/index.mjs +109 -0
- package/dist/comparison/index.mjs.map +1 -0
- package/dist/components/explore.d.mts +2 -2
- package/dist/components/explore.d.ts +2 -2
- package/dist/components/explore.js +11 -4
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +11 -4
- package/dist/components/explore.mjs.map +1 -1
- package/dist/components/partners.js.map +1 -1
- package/dist/components/partners.mjs.map +1 -1
- package/dist/content/index.d.mts +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +32 -4
- package/dist/content/index.js.map +1 -1
- package/dist/content/index.mjs +32 -5
- package/dist/content/index.mjs.map +1 -1
- package/dist/{explore-BEgq1_0O.d.mts → explore-DUTNYUOm.d.mts} +1 -1
- package/dist/{explore-CHiP9u-4.d.ts → explore-DjQE7Xia.d.ts} +1 -1
- package/dist/explore-avatar-live-config-B171o1Yo.d.ts +283 -0
- package/dist/explore-avatar-live-config-BGsGwbcq.d.mts +283 -0
- package/dist/{explore-types-_9UJu6EA.d.mts → explore-types-Bb2Ih3d8.d.mts} +1 -1
- package/dist/{explore-types-_9UJu6EA.d.ts → explore-types-Bb2Ih3d8.d.ts} +1 -1
- package/dist/hooks/index.d.mts +3 -2
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/index.js +6 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +6 -1
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/{index-CB0Uw1xD.d.ts → index-87BLB-Hj.d.ts} +26 -1
- package/dist/{index-Ctws7kGs.d.mts → index-Chw0pDXS.d.mts} +26 -1
- package/dist/index.d.mts +19 -255
- package/dist/index.d.ts +19 -255
- package/dist/index.js +358 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +352 -41
- package/dist/index.mjs.map +1 -1
- package/dist/types-CndbmBK-.d.mts +68 -0
- package/dist/types-CndbmBK-.d.ts +68 -0
- package/dist/{use-progressive-reveal-C0hkdu_i.d.ts → use-explore-chat-DunjG-3p.d.ts} +10 -36
- package/dist/{use-progressive-reveal-BFPWbEXy.d.mts → use-explore-chat-zRuoAin6.d.mts} +10 -36
- package/dist/use-progressive-reveal-ppicEgzZ.d.mts +35 -0
- package/dist/use-progressive-reveal-ppicEgzZ.d.ts +35 -0
- package/package.json +20 -3
package/dist/index.js
CHANGED
|
@@ -6468,6 +6468,11 @@ function ConceptDetail({
|
|
|
6468
6468
|
);
|
|
6469
6469
|
}
|
|
6470
6470
|
|
|
6471
|
+
// src/components/explore-fill.ts
|
|
6472
|
+
function fill(template, values) {
|
|
6473
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6471
6476
|
// src/components/explore-i18n.ts
|
|
6472
6477
|
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
6473
6478
|
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
|
|
@@ -7255,9 +7260,6 @@ function resolveExploreLocale(explicit) {
|
|
|
7255
7260
|
function exploreStrings(locale) {
|
|
7256
7261
|
return STRINGS[locale] ?? EN;
|
|
7257
7262
|
}
|
|
7258
|
-
function fill(template, values) {
|
|
7259
|
-
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
7260
|
-
}
|
|
7261
7263
|
|
|
7262
7264
|
// src/components/answers-i18n.ts
|
|
7263
7265
|
var EN2 = {
|
|
@@ -11031,6 +11033,10 @@ function useExploreChat(options = {}) {
|
|
|
11031
11033
|
[client, productId]
|
|
11032
11034
|
);
|
|
11033
11035
|
const canEscalate = hasConversation && !isSharedView;
|
|
11036
|
+
const getConversationToken = React.useCallback(
|
|
11037
|
+
() => conversationTokenRef.current,
|
|
11038
|
+
[]
|
|
11039
|
+
);
|
|
11034
11040
|
return {
|
|
11035
11041
|
phase,
|
|
11036
11042
|
catalog,
|
|
@@ -11055,7 +11061,8 @@ function useExploreChat(options = {}) {
|
|
|
11055
11061
|
escalate,
|
|
11056
11062
|
canEscalate,
|
|
11057
11063
|
createShareLink,
|
|
11058
|
-
isSharedView
|
|
11064
|
+
isSharedView,
|
|
11065
|
+
getConversationToken
|
|
11059
11066
|
};
|
|
11060
11067
|
}
|
|
11061
11068
|
var optimisticCounter = 0;
|
|
@@ -15162,6 +15169,164 @@ var ExploreCta = React.forwardRef(
|
|
|
15162
15169
|
);
|
|
15163
15170
|
}
|
|
15164
15171
|
);
|
|
15172
|
+
|
|
15173
|
+
// src/components/explore-avatar-live-config.ts
|
|
15174
|
+
var AVATAR_WEB_API_PATH = "/api/web-avatar/v1";
|
|
15175
|
+
var SITE = /^[a-z][a-z0-9-]{1,31}$/;
|
|
15176
|
+
var VERSION = /^[A-Za-z0-9._-]{1,32}$/;
|
|
15177
|
+
var SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;
|
|
15178
|
+
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
15179
|
+
function isAvatarSite(value) {
|
|
15180
|
+
return SITE.test(value);
|
|
15181
|
+
}
|
|
15182
|
+
function isAvatarVersion(value) {
|
|
15183
|
+
return VERSION.test(value);
|
|
15184
|
+
}
|
|
15185
|
+
function avatarApiBase(gatewayUrl) {
|
|
15186
|
+
if (!gatewayUrl) return null;
|
|
15187
|
+
let url;
|
|
15188
|
+
try {
|
|
15189
|
+
url = new URL(gatewayUrl);
|
|
15190
|
+
} catch {
|
|
15191
|
+
return null;
|
|
15192
|
+
}
|
|
15193
|
+
const local = url.protocol === "http:" && LOOPBACK_HOSTS2.has(url.hostname.toLowerCase());
|
|
15194
|
+
if (url.protocol !== "https:" && !local) return null;
|
|
15195
|
+
return `${url.origin}${AVATAR_WEB_API_PATH}`;
|
|
15196
|
+
}
|
|
15197
|
+
function liveConfigComplete(live) {
|
|
15198
|
+
return typeof live.load === "function" && typeof live.site === "string" && isAvatarSite(live.site) && typeof live.manifestVersion === "string" && isAvatarVersion(live.manifestVersion) && typeof live.turnstileSiteKey === "string" && SITE_KEY.test(live.turnstileSiteKey);
|
|
15199
|
+
}
|
|
15200
|
+
var REVIEWED = /* @__PURE__ */ new Set(["en"]);
|
|
15201
|
+
function isAvatarLiveLocaleReviewed(locale) {
|
|
15202
|
+
return REVIEWED.has(locale);
|
|
15203
|
+
}
|
|
15204
|
+
var VIEW_LOAD_TIMEOUT_MS = 8e3;
|
|
15205
|
+
function loadAvatarLiveView() {
|
|
15206
|
+
return import('./avatar-live-view/index.js').then(
|
|
15207
|
+
(loaded) => loaded.AvatarLiveView
|
|
15208
|
+
);
|
|
15209
|
+
}
|
|
15210
|
+
function ExploreAvatarLive({
|
|
15211
|
+
live,
|
|
15212
|
+
active,
|
|
15213
|
+
locale,
|
|
15214
|
+
productName,
|
|
15215
|
+
still,
|
|
15216
|
+
fallback,
|
|
15217
|
+
onContinueInChat,
|
|
15218
|
+
loadView = loadAvatarLiveView
|
|
15219
|
+
}) {
|
|
15220
|
+
const T = exploreStrings(locale);
|
|
15221
|
+
const config = useWebSDKConfig();
|
|
15222
|
+
const conversation = React.useContext(ExploreConversationContext);
|
|
15223
|
+
const base = avatarApiBase(config.gatewayUrl);
|
|
15224
|
+
const headers = React.useMemo(
|
|
15225
|
+
() => cfAccessHeaders(config.gatewayUrl, config.cfAccess),
|
|
15226
|
+
[config.gatewayUrl, config.cfAccess]
|
|
15227
|
+
);
|
|
15228
|
+
const complete = liveConfigComplete(live);
|
|
15229
|
+
const off = !complete || base === null || conversation === null || conversation.isSharedView || !isAvatarLiveLocaleReviewed(locale);
|
|
15230
|
+
React.useEffect(() => {
|
|
15231
|
+
if (!complete) {
|
|
15232
|
+
console.warn(
|
|
15233
|
+
"[WebSDK] ExplorePanel: avatar.live needs site, load, turnstileSiteKey and manifestVersion; the live avatar stays off."
|
|
15234
|
+
);
|
|
15235
|
+
}
|
|
15236
|
+
}, [complete]);
|
|
15237
|
+
const [View, setView] = React.useState(null);
|
|
15238
|
+
const [failed, setFailed] = React.useState(false);
|
|
15239
|
+
const wanted = active && !off;
|
|
15240
|
+
React.useEffect(() => {
|
|
15241
|
+
if (!wanted || View !== null) return;
|
|
15242
|
+
let cancelled = false;
|
|
15243
|
+
let loading;
|
|
15244
|
+
try {
|
|
15245
|
+
loading = loadView();
|
|
15246
|
+
} catch (error) {
|
|
15247
|
+
loading = Promise.reject(error);
|
|
15248
|
+
}
|
|
15249
|
+
const patience = setTimeout(() => {
|
|
15250
|
+
if (!cancelled) setFailed(true);
|
|
15251
|
+
}, VIEW_LOAD_TIMEOUT_MS);
|
|
15252
|
+
loading.then(
|
|
15253
|
+
(component) => {
|
|
15254
|
+
if (!cancelled) setView(() => component);
|
|
15255
|
+
},
|
|
15256
|
+
() => {
|
|
15257
|
+
clearTimeout(patience);
|
|
15258
|
+
if (!cancelled) setFailed(true);
|
|
15259
|
+
}
|
|
15260
|
+
);
|
|
15261
|
+
return () => {
|
|
15262
|
+
cancelled = true;
|
|
15263
|
+
clearTimeout(patience);
|
|
15264
|
+
};
|
|
15265
|
+
}, [wanted, View, loadView]);
|
|
15266
|
+
if (View !== null) {
|
|
15267
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15268
|
+
View,
|
|
15269
|
+
{
|
|
15270
|
+
live,
|
|
15271
|
+
active,
|
|
15272
|
+
locale,
|
|
15273
|
+
productName,
|
|
15274
|
+
still,
|
|
15275
|
+
fallback,
|
|
15276
|
+
onContinueInChat,
|
|
15277
|
+
continueInChat: T.continueInChat,
|
|
15278
|
+
off,
|
|
15279
|
+
base,
|
|
15280
|
+
headers,
|
|
15281
|
+
conversation
|
|
15282
|
+
}
|
|
15283
|
+
);
|
|
15284
|
+
}
|
|
15285
|
+
if (off || failed) {
|
|
15286
|
+
return (
|
|
15287
|
+
// `display: contents`: the earlier Avatar view's picture, line and button lay out in the pane exactly as before.
|
|
15288
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15289
|
+
"div",
|
|
15290
|
+
{
|
|
15291
|
+
style: { display: "contents" },
|
|
15292
|
+
"data-boff-explore": "avatar-live",
|
|
15293
|
+
"data-phase": off ? "off" : "inert",
|
|
15294
|
+
children: fallback
|
|
15295
|
+
}
|
|
15296
|
+
)
|
|
15297
|
+
);
|
|
15298
|
+
}
|
|
15299
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15300
|
+
"div",
|
|
15301
|
+
{
|
|
15302
|
+
"data-boff-explore": "avatar-live",
|
|
15303
|
+
"data-phase": "idle",
|
|
15304
|
+
className: "flex max-h-full w-full flex-col items-center gap-4 overflow-y-auto",
|
|
15305
|
+
children: [
|
|
15306
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15307
|
+
"img",
|
|
15308
|
+
{
|
|
15309
|
+
src: still.src,
|
|
15310
|
+
alt: still.alt,
|
|
15311
|
+
decoding: "async",
|
|
15312
|
+
width: 128,
|
|
15313
|
+
height: 128,
|
|
15314
|
+
className: "shrink-0 rounded-full border border-border object-cover shadow-sm h-32 w-32"
|
|
15315
|
+
}
|
|
15316
|
+
),
|
|
15317
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15318
|
+
"p",
|
|
15319
|
+
{
|
|
15320
|
+
role: "status",
|
|
15321
|
+
"data-boff-explore": "avatar-live-status",
|
|
15322
|
+
className: "text-sm text-muted-foreground"
|
|
15323
|
+
}
|
|
15324
|
+
),
|
|
15325
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-boff-explore": "avatar-live-bot-check" })
|
|
15326
|
+
]
|
|
15327
|
+
}
|
|
15328
|
+
);
|
|
15329
|
+
}
|
|
15165
15330
|
var MARK_KEY = "boffExplorePanel";
|
|
15166
15331
|
var POS_KEY = "boffExplorePanelPos";
|
|
15167
15332
|
var DOC_KEY = "boffExplorePanelDoc";
|
|
@@ -15353,15 +15518,15 @@ function ExplorePanel({
|
|
|
15353
15518
|
const rtl = isRtlLocale(resolvedLocale);
|
|
15354
15519
|
const catalogProducts = React.useContext(ExploreCatalogProductsContext);
|
|
15355
15520
|
const pinnedSlug = pinnedProductSlug(scope, productSlug);
|
|
15356
|
-
const
|
|
15357
|
-
|
|
15358
|
-
const name = productNameFor(
|
|
15521
|
+
const pinnedName = React.useMemo(
|
|
15522
|
+
() => pinnedSlug === null ? null : productNameFor(
|
|
15359
15523
|
catalogProducts ?? ECOSYSTEM_PRODUCTS,
|
|
15360
15524
|
pinnedSlug,
|
|
15361
15525
|
productName
|
|
15362
|
-
)
|
|
15363
|
-
|
|
15364
|
-
|
|
15526
|
+
),
|
|
15527
|
+
[catalogProducts, pinnedSlug, productName]
|
|
15528
|
+
);
|
|
15529
|
+
const title = pinnedName === null ? T.panelTitle : fill(T.panelTitleProduct, { product: pinnedName });
|
|
15365
15530
|
const uid = React.useId();
|
|
15366
15531
|
const titleId = `${uid}-title`;
|
|
15367
15532
|
const chatTabId = `${uid}-chat-tab`;
|
|
@@ -15682,6 +15847,32 @@ function ExplorePanel({
|
|
|
15682
15847
|
const tabButton = "rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
15683
15848
|
const expanded = size === "expanded";
|
|
15684
15849
|
const sizeLabel = expanded ? T.exitFullScreen : T.enterFullScreen;
|
|
15850
|
+
const avatarAlt = avatar?.name ? `${avatar.name}, ${T.liveAvatar}` : T.liveAvatar;
|
|
15851
|
+
const comingSoon = avatar ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
15852
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15853
|
+
"img",
|
|
15854
|
+
{
|
|
15855
|
+
src: avatar.src,
|
|
15856
|
+
alt: avatarAlt,
|
|
15857
|
+
loading: "lazy",
|
|
15858
|
+
decoding: "async",
|
|
15859
|
+
width: 128,
|
|
15860
|
+
height: 128,
|
|
15861
|
+
className: "h-32 w-32 rounded-full border border-border object-cover shadow-sm"
|
|
15862
|
+
}
|
|
15863
|
+
),
|
|
15864
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base font-semibold", children: T.avatarComingSoon }),
|
|
15865
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15866
|
+
"button",
|
|
15867
|
+
{
|
|
15868
|
+
type: "button",
|
|
15869
|
+
"data-boff-explore": "panel-continue-chat",
|
|
15870
|
+
className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
15871
|
+
onClick: continueInChat,
|
|
15872
|
+
children: T.continueInChat
|
|
15873
|
+
}
|
|
15874
|
+
)
|
|
15875
|
+
] }) : null;
|
|
15685
15876
|
const tabs = [
|
|
15686
15877
|
{ key: "chat", id: chatTabId, pane: chatPaneId, label: T.tabChat },
|
|
15687
15878
|
{ key: "avatar", id: avatarTabId, pane: avatarPaneId, label: T.tabAvatar }
|
|
@@ -15811,7 +16002,7 @@ function ExplorePanel({
|
|
|
15811
16002
|
)
|
|
15812
16003
|
}
|
|
15813
16004
|
),
|
|
15814
|
-
avatar ? /* @__PURE__ */ jsxRuntime.
|
|
16005
|
+
avatar ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
15815
16006
|
"div",
|
|
15816
16007
|
{
|
|
15817
16008
|
id: avatarPaneId,
|
|
@@ -15822,31 +16013,18 @@ function ExplorePanel({
|
|
|
15822
16013
|
"absolute inset-0 flex-col items-center justify-center gap-4 overflow-y-auto bg-background p-6 text-center",
|
|
15823
16014
|
view === "avatar" ? "flex" : "hidden"
|
|
15824
16015
|
),
|
|
15825
|
-
children:
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
),
|
|
15838
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base font-semibold", children: T.avatarComingSoon }),
|
|
15839
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15840
|
-
"button",
|
|
15841
|
-
{
|
|
15842
|
-
type: "button",
|
|
15843
|
-
"data-boff-explore": "panel-continue-chat",
|
|
15844
|
-
className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
15845
|
-
onClick: continueInChat,
|
|
15846
|
-
children: T.continueInChat
|
|
15847
|
-
}
|
|
15848
|
-
)
|
|
15849
|
-
]
|
|
16016
|
+
children: avatar.live ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16017
|
+
ExploreAvatarLive,
|
|
16018
|
+
{
|
|
16019
|
+
live: avatar.live,
|
|
16020
|
+
active: open && view === "avatar",
|
|
16021
|
+
locale: resolvedLocale,
|
|
16022
|
+
productName: pinnedName,
|
|
16023
|
+
still: { src: avatar.src, alt: avatarAlt },
|
|
16024
|
+
fallback: comingSoon,
|
|
16025
|
+
onContinueInChat: continueInChat
|
|
16026
|
+
}
|
|
16027
|
+
) : comingSoon
|
|
15850
16028
|
}
|
|
15851
16029
|
) : null
|
|
15852
16030
|
] })
|
|
@@ -16628,6 +16806,10 @@ function BlogPostPage(props) {
|
|
|
16628
16806
|
] });
|
|
16629
16807
|
}
|
|
16630
16808
|
var CASE_STUDY_TAG = "case-study";
|
|
16809
|
+
function hasCustomerSignOff(page) {
|
|
16810
|
+
const signedAt = page.customerSignOff?.signedAt;
|
|
16811
|
+
return typeof signedAt === "string" && !Number.isNaN(new Date(signedAt).getTime());
|
|
16812
|
+
}
|
|
16631
16813
|
var ANIMATION_STYLES = `
|
|
16632
16814
|
@keyframes csFadeUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
|
|
16633
16815
|
@keyframes csFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
|
|
@@ -16657,6 +16839,9 @@ var CASE_STUDIES_QUERY = `
|
|
|
16657
16839
|
readingTime
|
|
16658
16840
|
featured
|
|
16659
16841
|
category
|
|
16842
|
+
customerSignOff {
|
|
16843
|
+
signedAt
|
|
16844
|
+
}
|
|
16660
16845
|
}
|
|
16661
16846
|
total
|
|
16662
16847
|
hasMore
|
|
@@ -16683,6 +16868,9 @@ var CASE_STUDY_QUERY = `
|
|
|
16683
16868
|
readingTime
|
|
16684
16869
|
featured
|
|
16685
16870
|
category
|
|
16871
|
+
customerSignOff {
|
|
16872
|
+
signedAt
|
|
16873
|
+
}
|
|
16686
16874
|
}
|
|
16687
16875
|
}
|
|
16688
16876
|
`;
|
|
@@ -17053,11 +17241,15 @@ function CaseStudiesListPage(props) {
|
|
|
17053
17241
|
setPosts([]);
|
|
17054
17242
|
return;
|
|
17055
17243
|
}
|
|
17056
|
-
const
|
|
17057
|
-
|
|
17244
|
+
const items = result.data?.publicContentPages?.items || [];
|
|
17245
|
+
const caseStudies = items.filter(
|
|
17246
|
+
(p) => p.tags?.includes(CASE_STUDY_TAG) && hasCustomerSignOff(p)
|
|
17058
17247
|
);
|
|
17059
17248
|
setPosts(caseStudies);
|
|
17060
|
-
|
|
17249
|
+
const dropped = items.length - caseStudies.length;
|
|
17250
|
+
setTotal(
|
|
17251
|
+
Math.max(0, (result.data?.publicContentPages?.total || 0) - dropped)
|
|
17252
|
+
);
|
|
17061
17253
|
setError(null);
|
|
17062
17254
|
} catch {
|
|
17063
17255
|
setError("load-failed");
|
|
@@ -17239,7 +17431,8 @@ function CaseStudyPage(props) {
|
|
|
17239
17431
|
setError(result.errors[0]?.message ?? "Failed to load case study");
|
|
17240
17432
|
return;
|
|
17241
17433
|
}
|
|
17242
|
-
|
|
17434
|
+
const page = result.data?.publicContentPage ?? null;
|
|
17435
|
+
setPost(page && hasCustomerSignOff(page) ? page : null);
|
|
17243
17436
|
setError(null);
|
|
17244
17437
|
} catch {
|
|
17245
17438
|
setError("Failed to load case study");
|
|
@@ -17308,6 +17501,7 @@ function CaseStudyPage(props) {
|
|
|
17308
17501
|
] }) });
|
|
17309
17502
|
}
|
|
17310
17503
|
const formattedDate = formatDate(post.publishedAt || post.lastUpdated);
|
|
17504
|
+
const approvedDate = formatDate(post.customerSignOff?.signedAt);
|
|
17311
17505
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `w-full ${className}`, children: [
|
|
17312
17506
|
/* @__PURE__ */ jsxRuntime.jsx(ProseStyles, {}),
|
|
17313
17507
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17345,6 +17539,17 @@ function CaseStudyPage(props) {
|
|
|
17345
17539
|
] })
|
|
17346
17540
|
] }) : null
|
|
17347
17541
|
] }),
|
|
17542
|
+
approvedDate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17543
|
+
"p",
|
|
17544
|
+
{
|
|
17545
|
+
className: "-mt-4 mb-8 text-xs text-muted-foreground",
|
|
17546
|
+
"data-cs": "customer-signoff",
|
|
17547
|
+
children: [
|
|
17548
|
+
"Approved for publication by the customer on ",
|
|
17549
|
+
approvedDate
|
|
17550
|
+
]
|
|
17551
|
+
}
|
|
17552
|
+
),
|
|
17348
17553
|
post.thumbnail && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-10 aspect-[16/9] overflow-hidden rounded-xl", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17349
17554
|
"img",
|
|
17350
17555
|
{
|
|
@@ -20637,6 +20842,112 @@ function shouldRenderSiteFooter(pathname) {
|
|
|
20637
20842
|
return !isFooterlessPath(pathname);
|
|
20638
20843
|
}
|
|
20639
20844
|
|
|
20845
|
+
// src/comparison/index.ts
|
|
20846
|
+
var DEFAULT_COMPARISON_MAX_AGE_DAYS = 180;
|
|
20847
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
20848
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
20849
|
+
function parseVerifiedAt(value) {
|
|
20850
|
+
if (typeof value !== "string" || !ISO_DATE.test(value)) return null;
|
|
20851
|
+
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
20852
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
20853
|
+
return date.toISOString().slice(0, 10) === value ? date : null;
|
|
20854
|
+
}
|
|
20855
|
+
function isHttpUrl(value) {
|
|
20856
|
+
try {
|
|
20857
|
+
const url = new URL(value);
|
|
20858
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
20859
|
+
} catch {
|
|
20860
|
+
return false;
|
|
20861
|
+
}
|
|
20862
|
+
}
|
|
20863
|
+
function checkComparisonFreshness(claims, options = {}) {
|
|
20864
|
+
const maxAgeDays = options.maxAgeDays ?? DEFAULT_COMPARISON_MAX_AGE_DAYS;
|
|
20865
|
+
const now2 = options.now ?? /* @__PURE__ */ new Date();
|
|
20866
|
+
const issues = [];
|
|
20867
|
+
for (const claim of claims) {
|
|
20868
|
+
const sources = claim.sources ?? [];
|
|
20869
|
+
if (sources.length === 0) {
|
|
20870
|
+
issues.push({
|
|
20871
|
+
path: claim.path,
|
|
20872
|
+
problem: "missing-source",
|
|
20873
|
+
detail: "no source: add { url, verifiedAt } for the vendor page this claim was checked against"
|
|
20874
|
+
});
|
|
20875
|
+
continue;
|
|
20876
|
+
}
|
|
20877
|
+
let newest = null;
|
|
20878
|
+
for (const source of sources) {
|
|
20879
|
+
if (!isHttpUrl(source.url)) {
|
|
20880
|
+
issues.push({
|
|
20881
|
+
path: claim.path,
|
|
20882
|
+
problem: "invalid-url",
|
|
20883
|
+
detail: `source URL is not an http(s) URL: ${JSON.stringify(source.url)}`
|
|
20884
|
+
});
|
|
20885
|
+
}
|
|
20886
|
+
const verified = parseVerifiedAt(source.verifiedAt);
|
|
20887
|
+
if (!verified) {
|
|
20888
|
+
issues.push({
|
|
20889
|
+
path: claim.path,
|
|
20890
|
+
problem: "invalid-date",
|
|
20891
|
+
detail: `verifiedAt must be YYYY-MM-DD, got ${JSON.stringify(source.verifiedAt)}`
|
|
20892
|
+
});
|
|
20893
|
+
continue;
|
|
20894
|
+
}
|
|
20895
|
+
if (verified.getTime() > now2.getTime() + DAY_MS) {
|
|
20896
|
+
issues.push({
|
|
20897
|
+
path: claim.path,
|
|
20898
|
+
problem: "future-date",
|
|
20899
|
+
detail: `verifiedAt ${source.verifiedAt} is in the future`
|
|
20900
|
+
});
|
|
20901
|
+
continue;
|
|
20902
|
+
}
|
|
20903
|
+
if (!newest || verified.getTime() > newest.getTime()) newest = verified;
|
|
20904
|
+
}
|
|
20905
|
+
if (newest) {
|
|
20906
|
+
const ageDays = Math.floor((now2.getTime() - newest.getTime()) / DAY_MS);
|
|
20907
|
+
if (ageDays > maxAgeDays) {
|
|
20908
|
+
issues.push({
|
|
20909
|
+
path: claim.path,
|
|
20910
|
+
problem: "stale",
|
|
20911
|
+
detail: `last verified ${newest.toISOString().slice(0, 10)} (${ageDays} days ago, limit ${maxAgeDays}): re-read the vendor page and update verifiedAt or the claim`
|
|
20912
|
+
});
|
|
20913
|
+
}
|
|
20914
|
+
}
|
|
20915
|
+
}
|
|
20916
|
+
return issues;
|
|
20917
|
+
}
|
|
20918
|
+
function latestVerification(claims) {
|
|
20919
|
+
let latest = null;
|
|
20920
|
+
for (const claim of claims) {
|
|
20921
|
+
for (const source of claim.sources ?? []) {
|
|
20922
|
+
if (parseVerifiedAt(source.verifiedAt) && (!latest || source.verifiedAt > latest)) {
|
|
20923
|
+
latest = source.verifiedAt;
|
|
20924
|
+
}
|
|
20925
|
+
}
|
|
20926
|
+
}
|
|
20927
|
+
return latest;
|
|
20928
|
+
}
|
|
20929
|
+
function oldestVerification(claims) {
|
|
20930
|
+
let oldest = null;
|
|
20931
|
+
for (const claim of claims) {
|
|
20932
|
+
for (const source of claim.sources ?? []) {
|
|
20933
|
+
if (parseVerifiedAt(source.verifiedAt) && (!oldest || source.verifiedAt < oldest)) {
|
|
20934
|
+
oldest = source.verifiedAt;
|
|
20935
|
+
}
|
|
20936
|
+
}
|
|
20937
|
+
}
|
|
20938
|
+
return oldest;
|
|
20939
|
+
}
|
|
20940
|
+
function formatFreshnessReport(issues) {
|
|
20941
|
+
if (issues.length === 0)
|
|
20942
|
+
return "Comparison sources: all claims sourced and fresh.";
|
|
20943
|
+
const lines = issues.map((i) => ` - [${i.problem}] ${i.path}: ${i.detail}`);
|
|
20944
|
+
return [
|
|
20945
|
+
`Comparison sources: ${issues.length} problem(s) found.`,
|
|
20946
|
+
...lines,
|
|
20947
|
+
"Re-check each claim against the vendor's current page, then update the claim and its verifiedAt."
|
|
20948
|
+
].join("\n");
|
|
20949
|
+
}
|
|
20950
|
+
|
|
20640
20951
|
exports.ANSWERS_PATH = ANSWERS_PATH;
|
|
20641
20952
|
exports.ANSWER_HEADING_SHIFT = ANSWER_HEADING_SHIFT;
|
|
20642
20953
|
exports.ANSWER_PATH_PREFIX = ANSWER_PATH_PREFIX;
|
|
@@ -20659,6 +20970,7 @@ exports.ConceptsPage = ConceptsPage;
|
|
|
20659
20970
|
exports.ContactPage = ContactPage;
|
|
20660
20971
|
exports.ContentRenderer = ContentRenderer;
|
|
20661
20972
|
exports.ContractsPage = ContractsPage;
|
|
20973
|
+
exports.DEFAULT_COMPARISON_MAX_AGE_DAYS = DEFAULT_COMPARISON_MAX_AGE_DAYS;
|
|
20662
20974
|
exports.DEFAULT_CONTACT_CATEGORIES = DEFAULT_CONTACT_CATEGORIES;
|
|
20663
20975
|
exports.DEFAULT_EXPLORE_EXAMPLE_PROMPTS = DEFAULT_EXPLORE_EXAMPLE_PROMPTS;
|
|
20664
20976
|
exports.DOMAINS_URL = DOMAINS_URL;
|
|
@@ -20726,6 +21038,7 @@ exports.answerSpeechText = answerSpeechText;
|
|
|
20726
21038
|
exports.answerStrings = answerStrings;
|
|
20727
21039
|
exports.availablePlanCurrencies = availablePlanCurrencies;
|
|
20728
21040
|
exports.buttonVariants = buttonVariants;
|
|
21041
|
+
exports.checkComparisonFreshness = checkComparisonFreshness;
|
|
20729
21042
|
exports.classifyExploreError = classifyExploreError;
|
|
20730
21043
|
exports.cleanPlanDisplayName = cleanPlanDisplayName;
|
|
20731
21044
|
exports.cn = cn;
|
|
@@ -20742,11 +21055,13 @@ exports.fetchPublicProduct = fetchPublicProduct;
|
|
|
20742
21055
|
exports.findLegalDocument = findLegalDocument;
|
|
20743
21056
|
exports.formatAnswerDate = formatAnswerDate;
|
|
20744
21057
|
exports.formatDownloads = formatDownloads;
|
|
21058
|
+
exports.formatFreshnessReport = formatFreshnessReport;
|
|
20745
21059
|
exports.formatLegalDate = formatLegalDate;
|
|
20746
21060
|
exports.formatPrice = formatPrice2;
|
|
20747
21061
|
exports.getAnswerBySlug = getAnswerBySlug;
|
|
20748
21062
|
exports.getConceptBySlug = getConceptBySlug;
|
|
20749
21063
|
exports.getRecaptchaV3Token = getRecaptchaV3Token;
|
|
21064
|
+
exports.hasCustomerSignOff = hasCustomerSignOff;
|
|
20750
21065
|
exports.inlineImageOrigins = inlineImageOrigins;
|
|
20751
21066
|
exports.isFooterlessPath = isFooterlessPath;
|
|
20752
21067
|
exports.isInlineImageAllowed = isInlineImageAllowed;
|
|
@@ -20755,11 +21070,14 @@ exports.isPublishedSourceUrl = isPublishedSourceUrl;
|
|
|
20755
21070
|
exports.isSameProductSlug = isSameProductSlug;
|
|
20756
21071
|
exports.isUuidLike = isUuidLike;
|
|
20757
21072
|
exports.isValidUrl = isValidUrl;
|
|
21073
|
+
exports.latestVerification = latestVerification;
|
|
20758
21074
|
exports.legacyProductSlug = legacyProductSlug;
|
|
20759
21075
|
exports.loadRecaptchaV3 = loadRecaptchaV3;
|
|
20760
21076
|
exports.looksLikeDeliverableEmail = looksLikeDeliverableEmail;
|
|
20761
21077
|
exports.normalizeProductSlug = normalizeProductSlug;
|
|
20762
21078
|
exports.normalizeRoutePath = normalizeRoutePath;
|
|
21079
|
+
exports.oldestVerification = oldestVerification;
|
|
21080
|
+
exports.parseVerifiedAt = parseVerifiedAt;
|
|
20763
21081
|
exports.planPriceIn = planPriceIn;
|
|
20764
21082
|
exports.productSlugVariants = productSlugVariants;
|
|
20765
21083
|
exports.productSlugsByOrigin = productSlugsByOrigin;
|