@jmp-technologies/analytics 0.1.27 → 0.1.32

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.
Files changed (100) hide show
  1. package/dist/astro/client.cjs +256 -11
  2. package/dist/astro/client.cjs.map +1 -1
  3. package/dist/astro/client.d.cts +2 -2
  4. package/dist/astro/client.d.ts +2 -2
  5. package/dist/astro/client.js +2 -2
  6. package/dist/astro/content.cjs +308 -13
  7. package/dist/astro/content.cjs.map +1 -1
  8. package/dist/astro/content.d.cts +12 -3
  9. package/dist/astro/content.d.ts +12 -3
  10. package/dist/astro/content.js +9 -3
  11. package/dist/astro/integration.cjs +228 -10
  12. package/dist/astro/integration.cjs.map +1 -1
  13. package/dist/astro/integration.js +3 -3
  14. package/dist/astro/middleware.cjs +2 -1
  15. package/dist/astro/middleware.cjs.map +1 -1
  16. package/dist/astro/middleware.d.cts +2 -2
  17. package/dist/astro/middleware.d.ts +2 -2
  18. package/dist/astro/middleware.js +2 -2
  19. package/dist/astro/register.cjs +228 -10
  20. package/dist/astro/register.cjs.map +1 -1
  21. package/dist/astro/register.js +3 -3
  22. package/dist/{chunk-KZ4WVDGL.js → chunk-7623G3XL.js} +3 -3
  23. package/dist/{chunk-A4RRTY5D.js → chunk-AIFJ7DVO.js} +3 -2
  24. package/dist/{chunk-A4RRTY5D.js.map → chunk-AIFJ7DVO.js.map} +1 -1
  25. package/dist/{chunk-YCVHRMM6.js → chunk-G5BMWPQ6.js} +2 -3
  26. package/dist/{chunk-YCVHRMM6.js.map → chunk-G5BMWPQ6.js.map} +1 -1
  27. package/dist/{chunk-HODG4NJQ.js → chunk-HFLU7HQE.js} +2 -2
  28. package/dist/{chunk-PG3IOI5O.js → chunk-HKMDWTA2.js} +53 -6
  29. package/dist/chunk-HKMDWTA2.js.map +1 -0
  30. package/dist/{chunk-DNYJF7BG.js → chunk-KPTMZQCY.js} +3 -3
  31. package/dist/{chunk-FNWE2DT5.js → chunk-NDSPSAWQ.js} +18 -8
  32. package/dist/chunk-NDSPSAWQ.js.map +1 -0
  33. package/dist/{chunk-UVYF33XE.js → chunk-NI7R6QZQ.js} +293 -13
  34. package/dist/chunk-NI7R6QZQ.js.map +1 -0
  35. package/dist/chunk-RI3KKDPX.js +75 -0
  36. package/dist/chunk-RI3KKDPX.js.map +1 -0
  37. package/dist/{create-jmp-analytics-D4Qw_d6o.d.cts → create-jmp-analytics-D1dUsqCQ.d.cts} +1 -2
  38. package/dist/{create-jmp-analytics-DpG5Hdl5.d.ts → create-jmp-analytics-htx2oOEf.d.ts} +1 -2
  39. package/dist/index.cjs +295 -12
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +8 -6
  42. package/dist/index.d.ts +8 -6
  43. package/dist/index.js +10 -2
  44. package/dist/next/content.cjs +310 -13
  45. package/dist/next/content.cjs.map +1 -1
  46. package/dist/next/content.d.cts +7 -5
  47. package/dist/next/content.d.ts +7 -5
  48. package/dist/next/content.js +11 -3
  49. package/dist/next/content.js.map +1 -1
  50. package/dist/next/instrumentation.cjs +228 -10
  51. package/dist/next/instrumentation.cjs.map +1 -1
  52. package/dist/next/instrumentation.js +2 -2
  53. package/dist/next/landing-capture-script-server.cjs +1 -2
  54. package/dist/next/landing-capture-script-server.cjs.map +1 -1
  55. package/dist/next/landing-capture-script-server.js +1 -1
  56. package/dist/next/landing-pages.cjs +228 -12
  57. package/dist/next/landing-pages.cjs.map +1 -1
  58. package/dist/next/landing-pages.d.cts +4 -16
  59. package/dist/next/landing-pages.d.ts +4 -16
  60. package/dist/next/landing-pages.js +13 -67
  61. package/dist/next/landing-pages.js.map +1 -1
  62. package/dist/next/locations.cjs +737 -0
  63. package/dist/next/locations.cjs.map +1 -0
  64. package/dist/next/locations.d.cts +55 -0
  65. package/dist/next/locations.d.ts +55 -0
  66. package/dist/next/locations.js +158 -0
  67. package/dist/next/locations.js.map +1 -0
  68. package/dist/next/middleware.cjs +2 -1
  69. package/dist/next/middleware.cjs.map +1 -1
  70. package/dist/next/middleware.d.cts +2 -2
  71. package/dist/next/middleware.d.ts +2 -2
  72. package/dist/next/middleware.js +2 -2
  73. package/dist/next/site-root-server.cjs +259 -14
  74. package/dist/next/site-root-server.cjs.map +1 -1
  75. package/dist/next/site-root-server.js +5 -5
  76. package/dist/next.cjs +270 -17
  77. package/dist/next.cjs.map +1 -1
  78. package/dist/next.d.cts +3 -3
  79. package/dist/next.d.ts +3 -3
  80. package/dist/next.js +4 -4
  81. package/dist/react.cjs +389 -136
  82. package/dist/react.cjs.map +1 -1
  83. package/dist/react.d.cts +2 -2
  84. package/dist/react.d.ts +2 -2
  85. package/dist/react.js +3 -3
  86. package/dist/{site-config-UTzDG-UG.d.ts → site-config-B7USGGzu.d.cts} +22 -2
  87. package/dist/{site-config-CJqYnZm7.d.cts → site-config-C4bcUoLI.d.ts} +22 -2
  88. package/dist/{track-page-view-tX0Magkd.d.cts → track-page-view-C4TbbULE.d.cts} +1 -1
  89. package/dist/{track-page-view-CMAqyhD_.d.ts → track-page-view-RiB-xgIx.d.ts} +1 -1
  90. package/dist/{types-B-111Wht.d.ts → types-DRljY1Yy.d.cts} +92 -2
  91. package/dist/{types-B-111Wht.d.cts → types-DRljY1Yy.d.ts} +92 -2
  92. package/dist/utils-BajG58pj.d.cts +15 -0
  93. package/dist/utils-CHT3SQFv.d.ts +15 -0
  94. package/package.json +7 -2
  95. package/dist/chunk-FNWE2DT5.js.map +0 -1
  96. package/dist/chunk-PG3IOI5O.js.map +0 -1
  97. package/dist/chunk-UVYF33XE.js.map +0 -1
  98. /package/dist/{chunk-KZ4WVDGL.js.map → chunk-7623G3XL.js.map} +0 -0
  99. /package/dist/{chunk-HODG4NJQ.js.map → chunk-HFLU7HQE.js.map} +0 -0
  100. /package/dist/{chunk-DNYJF7BG.js.map → chunk-KPTMZQCY.js.map} +0 -0
@@ -0,0 +1,737 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next/locations.tsx
31
+ var locations_exports = {};
32
+ __export(locations_exports, {
33
+ JmpLocationDetailPage: () => JmpLocationDetailPage,
34
+ JmpLocationHubPage: () => JmpLocationHubPage,
35
+ buildJmpLocationHubMetadata: () => buildJmpLocationHubMetadata,
36
+ buildJmpLocationMetadata: () => buildJmpLocationMetadata,
37
+ buildJmpLocationSitemapRoutes: () => buildJmpLocationSitemapRoutes,
38
+ generateJmpLocationStaticParams: () => generateJmpLocationStaticParams,
39
+ plainTextFromMarkdown: () => plainTextFromMarkdown,
40
+ resolveJmpSiteUrl: () => resolveJmpSiteUrl
41
+ });
42
+ module.exports = __toCommonJS(locations_exports);
43
+ var import_link = __toESM(require("next/link"), 1);
44
+ var import_navigation = require("next/navigation");
45
+
46
+ // src/landing-capture.ts
47
+ var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
48
+
49
+ // src/constants.ts
50
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.26";
51
+
52
+ // src/http.ts
53
+ function responseLooksLikeHtml(body) {
54
+ const s = body.trimStart();
55
+ return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
56
+ }
57
+ function normalizeBaseUrl(baseUrl) {
58
+ return baseUrl.replace(/\/$/, "");
59
+ }
60
+
61
+ // src/content/manifest.ts
62
+ var JMP_CONTENT_MANIFEST_VERSION = 1;
63
+ var TRUST_SECTIONS = /* @__PURE__ */ new Set([
64
+ "summary",
65
+ "testimonials",
66
+ "reviews",
67
+ "stats",
68
+ "logos",
69
+ "credentials"
70
+ ]);
71
+ function normalizeKey(value) {
72
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
73
+ }
74
+ function isValidExtraKey(key) {
75
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key);
76
+ }
77
+ var MAX_EXTERNAL_ADMIN_LINKS = 10;
78
+ var MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80;
79
+ var EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
80
+ function tryNormalizeHttpUrl(input) {
81
+ if (typeof input !== "string" || !input.trim()) {
82
+ return null;
83
+ }
84
+ try {
85
+ const url = new URL(input.trim());
86
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
87
+ return null;
88
+ }
89
+ return url.toString().replace(/\/$/, "");
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+ function normalizeShopifyExternalSource(shopify) {
95
+ if (!shopify || typeof shopify !== "object") {
96
+ return null;
97
+ }
98
+ const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl);
99
+ const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl);
100
+ if (!adminUrl && !storeUrl) {
101
+ return null;
102
+ }
103
+ return {
104
+ ...adminUrl ? { adminUrl } : {},
105
+ ...storeUrl ? { storeUrl } : {}
106
+ };
107
+ }
108
+ function normalizeExternalAdminLinkEntries(links) {
109
+ if (!Array.isArray(links)) {
110
+ return [];
111
+ }
112
+ const seenIds = /* @__PURE__ */ new Set();
113
+ const out = [];
114
+ for (const raw of links) {
115
+ if (!raw || typeof raw !== "object" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {
116
+ continue;
117
+ }
118
+ const id = typeof raw.id === "string" ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") : "";
119
+ const label = typeof raw.label === "string" ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH) : "";
120
+ const url = tryNormalizeHttpUrl(raw.url);
121
+ if (!id || !label || !url || !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) || seenIds.has(id)) {
122
+ continue;
123
+ }
124
+ seenIds.add(id);
125
+ out.push({ id, label, url });
126
+ }
127
+ return out;
128
+ }
129
+ function normalizeExternalSources(externalSources) {
130
+ if (!externalSources || typeof externalSources !== "object") {
131
+ return null;
132
+ }
133
+ const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl);
134
+ const shopify = normalizeShopifyExternalSource(externalSources.shopify);
135
+ const links = normalizeExternalAdminLinkEntries(externalSources.links);
136
+ if (!studioUrl && !shopify && links.length === 0) {
137
+ return null;
138
+ }
139
+ return {
140
+ ...studioUrl ? { sanity: { studioUrl } } : {},
141
+ ...shopify ? { shopify } : {},
142
+ ...links.length > 0 ? { links } : {}
143
+ };
144
+ }
145
+ function normalizeJmpContentManifest(input) {
146
+ if (!input || typeof input !== "object") {
147
+ return null;
148
+ }
149
+ const routes = normalizeRoutes(input.routes);
150
+ const externalSources = normalizeExternalSources(input.externalSources);
151
+ const trust = normalizeTrust(input.trust);
152
+ const contact = normalizeContact(input.contact);
153
+ if (routes.length === 0 && !externalSources && !trust && !contact) {
154
+ return null;
155
+ }
156
+ return {
157
+ version: JMP_CONTENT_MANIFEST_VERSION,
158
+ ...routes.length > 0 ? { routes } : {},
159
+ ...externalSources ? { externalSources } : {},
160
+ ...trust ? { trust } : {},
161
+ ...contact ? { contact } : {}
162
+ };
163
+ }
164
+ function normalizeRoutes(routes) {
165
+ if (!Array.isArray(routes)) {
166
+ return [];
167
+ }
168
+ const seen = /* @__PURE__ */ new Set();
169
+ const out = [];
170
+ for (const raw of routes) {
171
+ if (!raw || typeof raw !== "object") continue;
172
+ const wrapper = typeof raw.wrapper === "string" ? raw.wrapper.trim().toLowerCase() : "";
173
+ const pattern = typeof raw.pattern === "string" ? raw.pattern.trim() : "";
174
+ if (!wrapper || !pattern) continue;
175
+ const dedupe = `${wrapper}:${pattern}`;
176
+ if (seen.has(dedupe)) continue;
177
+ seen.add(dedupe);
178
+ out.push({
179
+ wrapper,
180
+ pattern,
181
+ ...typeof raw.index === "string" && raw.index.trim() ? { index: raw.index.trim() } : {},
182
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
183
+ });
184
+ }
185
+ return out;
186
+ }
187
+ function normalizeTrust(trust) {
188
+ if (!trust || typeof trust !== "object") return null;
189
+ const blocks = normalizeTrustBlocks(trust.blocks);
190
+ const labels = normalizeTrustLabels(trust.labels);
191
+ const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true;
192
+ if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null;
193
+ return {
194
+ ...blocks.length > 0 ? { blocks } : {},
195
+ ...labels ? { labels } : {},
196
+ ...mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}
197
+ };
198
+ }
199
+ function normalizeTrustBlocks(blocks) {
200
+ if (!Array.isArray(blocks)) return [];
201
+ const seen = /* @__PURE__ */ new Set();
202
+ const out = [];
203
+ for (const raw of blocks) {
204
+ if (!raw || typeof raw !== "object") continue;
205
+ const section = typeof raw.section === "string" ? raw.section.trim().toLowerCase() : "";
206
+ if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue;
207
+ seen.add(section);
208
+ out.push({
209
+ section,
210
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
211
+ });
212
+ }
213
+ return out;
214
+ }
215
+ function normalizeTrustLabels(labels) {
216
+ if (!labels || typeof labels !== "object") return null;
217
+ const out = {};
218
+ for (const [key, value] of Object.entries(labels)) {
219
+ if (TRUST_SECTIONS.has(key) && typeof value === "string" && value.trim()) {
220
+ out[key] = value.trim();
221
+ }
222
+ }
223
+ return Object.keys(out).length > 0 ? out : null;
224
+ }
225
+ function normalizeContact(contact) {
226
+ if (!contact || typeof contact !== "object") return null;
227
+ const extras = normalizeContactExtras(contact.extras);
228
+ return extras.length > 0 ? { extras } : null;
229
+ }
230
+ function normalizeContactExtras(extras) {
231
+ if (!Array.isArray(extras)) return [];
232
+ const seen = /* @__PURE__ */ new Set();
233
+ const out = [];
234
+ for (const raw of extras) {
235
+ if (!raw || typeof raw !== "object") continue;
236
+ const label = typeof raw.label === "string" ? raw.label.trim() : "";
237
+ const key = normalizeKey(
238
+ typeof raw.key === "string" && raw.key.trim() ? raw.key : label
239
+ );
240
+ if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue;
241
+ seen.add(key);
242
+ out.push({ key, label });
243
+ }
244
+ return out;
245
+ }
246
+ function stableJmpManifestFingerprint(manifest) {
247
+ return manifest ? JSON.stringify(manifest) : "";
248
+ }
249
+ function inferWrapperSlotsFromManifest(manifest) {
250
+ const slots = /* @__PURE__ */ new Set();
251
+ for (const route of manifest?.routes ?? []) {
252
+ slots.add(route.wrapper);
253
+ }
254
+ for (const block of manifest?.trust?.blocks ?? []) {
255
+ slots.add(`trust.${block.section}`);
256
+ slots.add("trust");
257
+ }
258
+ if (manifest?.contact?.extras?.length) {
259
+ slots.add("contact");
260
+ }
261
+ return [...slots];
262
+ }
263
+
264
+ // src/content/fetch.ts
265
+ async function getContent(config, path) {
266
+ const text = await getContentText(config, path);
267
+ try {
268
+ return JSON.parse(text);
269
+ } catch {
270
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
271
+ const url = `${baseUrl}${path}`;
272
+ throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
273
+ }
274
+ }
275
+ async function getContentText(config, path) {
276
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
277
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
278
+ if (!resolvedFetch) {
279
+ throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
280
+ }
281
+ const url = `${baseUrl}${path}`;
282
+ if (config.debug) {
283
+ console.debug("[@jmp-technologies/analytics]", "GET", url);
284
+ }
285
+ const res = await resolvedFetch(url, {
286
+ method: "GET",
287
+ headers: {
288
+ "X-JMP-Tracking-Key": config.trackingKey
289
+ }
290
+ });
291
+ const text = await res.text();
292
+ if (!res.ok) {
293
+ if (responseLooksLikeHtml(text)) {
294
+ throw new Error(
295
+ `JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
296
+ );
297
+ }
298
+ let parsed = {};
299
+ try {
300
+ parsed = JSON.parse(text);
301
+ } catch {
302
+ parsed = { error: text };
303
+ }
304
+ throw new Error(
305
+ [`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
306
+ );
307
+ }
308
+ return text;
309
+ }
310
+
311
+ // src/content/wrapper-discovery.ts
312
+ var WRAPPER_SLOT_ORDER = [
313
+ "blog",
314
+ "faq",
315
+ "contact",
316
+ "trust",
317
+ "trust.summary",
318
+ "trust.testimonials",
319
+ "trust.reviews",
320
+ "trust.stats",
321
+ "trust.logos",
322
+ "trust.credentials"
323
+ ];
324
+ var discovered = /* @__PURE__ */ new Set();
325
+ var syncTimer = null;
326
+ var pendingSyncConfig = null;
327
+ var reportedDedupeKeys = /* @__PURE__ */ new Set();
328
+ var syncHandler = null;
329
+ function markContentWrapper(slot) {
330
+ const normalized = slot.trim().toLowerCase();
331
+ if (!normalized) {
332
+ return;
333
+ }
334
+ discovered.add(normalized);
335
+ }
336
+ function getDiscoveredContentWrappers() {
337
+ return sortWrapperSlots([...discovered]);
338
+ }
339
+ function resolveContentManifestForReport(config) {
340
+ return normalizeJmpContentManifest(config.contentManifest);
341
+ }
342
+ function resolveContentWrappersForReport(config) {
343
+ const explicit = config.contentWrappers ?? [];
344
+ const auto = getDiscoveredContentWrappers();
345
+ const fromManifest = inferWrapperSlotsFromManifest(
346
+ resolveContentManifestForReport(config)
347
+ );
348
+ if (config.contentWrapperMode === "manual") {
349
+ return sortWrapperSlots([...explicit, ...fromManifest]);
350
+ }
351
+ if (config.contentWrapperMode === "explicit-only") {
352
+ return sortWrapperSlots([...explicit, ...fromManifest]);
353
+ }
354
+ const merged = /* @__PURE__ */ new Set([...explicit, ...auto, ...fromManifest]);
355
+ return sortWrapperSlots([...merged]);
356
+ }
357
+ function hasReportedContentWrappers(dedupeKey) {
358
+ return reportedDedupeKeys.has(dedupeKey);
359
+ }
360
+ function rememberReportedContentWrappers(dedupeKey) {
361
+ reportedDedupeKeys.add(dedupeKey);
362
+ }
363
+ function setContentWrapperSyncHandler(handler) {
364
+ syncHandler = handler;
365
+ }
366
+ function scheduleContentWrapperSync(config) {
367
+ if (!syncHandler) {
368
+ return;
369
+ }
370
+ pendingSyncConfig = config;
371
+ if (syncTimer) {
372
+ clearTimeout(syncTimer);
373
+ }
374
+ syncTimer = setTimeout(() => {
375
+ syncTimer = null;
376
+ const cfg = pendingSyncConfig;
377
+ pendingSyncConfig = null;
378
+ if (!cfg || !syncHandler) {
379
+ return;
380
+ }
381
+ void syncHandler(cfg).catch(() => {
382
+ });
383
+ }, 750);
384
+ }
385
+ function sortWrapperSlots(slots) {
386
+ const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]));
387
+ return [...new Set(slots)].sort((a, b) => {
388
+ const ai = order.get(a) ?? 999;
389
+ const bi = order.get(b) ?? 999;
390
+ if (ai !== bi) {
391
+ return ai - bi;
392
+ }
393
+ return a.localeCompare(b);
394
+ });
395
+ }
396
+
397
+ // src/content/api.ts
398
+ function afterContentWrapperUse(config) {
399
+ scheduleContentWrapperSync(config);
400
+ }
401
+ async function getJmpLocations(config) {
402
+ const data = await getContent(
403
+ config,
404
+ "/api/content/locations"
405
+ );
406
+ markContentWrapper("locations");
407
+ afterContentWrapperUse(config);
408
+ return data.locations;
409
+ }
410
+ async function getJmpLocationDetail(config, slug) {
411
+ const normalized = slug.trim();
412
+ if (!normalized) {
413
+ return null;
414
+ }
415
+ try {
416
+ const detail = await getContent(
417
+ config,
418
+ `/api/content/locations/${encodeURIComponent(normalized)}`
419
+ );
420
+ markContentWrapper("locations");
421
+ afterContentWrapperUse(config);
422
+ return detail;
423
+ } catch (error) {
424
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
425
+ return null;
426
+ }
427
+ throw error;
428
+ }
429
+ }
430
+ async function registerJmpContentWrappers(config) {
431
+ const wrappers = resolveContentWrappersForReport(config);
432
+ if (!wrappers.length) {
433
+ return { ok: false, skipped: true };
434
+ }
435
+ const manifest = resolveContentManifestForReport(config);
436
+ const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}:${stableJmpManifestFingerprint(manifest)}`;
437
+ if (hasReportedContentWrappers(dedupeKey)) {
438
+ return { ok: true, wrappers };
439
+ }
440
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
441
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
442
+ if (!resolvedFetch) {
443
+ throw new Error(
444
+ "JMP content wrappers: fetch is not available; pass fetchImpl in config"
445
+ );
446
+ }
447
+ const url = `${baseUrl}/api/content/capabilities`;
448
+ if (config.debug) {
449
+ console.debug("[@jmp-technologies/analytics]", "POST", url, {
450
+ wrappers,
451
+ manifest
452
+ });
453
+ }
454
+ const body = {
455
+ wrappers,
456
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
457
+ };
458
+ if (manifest) {
459
+ body.manifest = manifest;
460
+ }
461
+ const res = await resolvedFetch(url, {
462
+ method: "POST",
463
+ headers: {
464
+ "Content-Type": "application/json",
465
+ "X-JMP-Tracking-Key": config.trackingKey
466
+ },
467
+ body: JSON.stringify(body)
468
+ });
469
+ const text = await res.text();
470
+ if (!res.ok) {
471
+ let parsed = {};
472
+ try {
473
+ parsed = JSON.parse(text);
474
+ } catch {
475
+ parsed = { error: text };
476
+ }
477
+ throw new Error(
478
+ [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
479
+ );
480
+ }
481
+ rememberReportedContentWrappers(dedupeKey);
482
+ const data = JSON.parse(text);
483
+ return { ok: true, wrappers: data.wrappers ?? wrappers };
484
+ }
485
+ setContentWrapperSyncHandler(registerJmpContentWrappers);
486
+
487
+ // src/env-public.ts
488
+ function firstDefined(...values) {
489
+ for (const v of values) {
490
+ if (v) return v;
491
+ }
492
+ return void 0;
493
+ }
494
+ function readJmpPublicEnvFromProcessEnv() {
495
+ if (typeof process === "undefined" || !process.env) {
496
+ return {};
497
+ }
498
+ const { env } = process;
499
+ const analyticsUrl = firstDefined(
500
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
501
+ env.PUBLIC_JMP_ANALYTICS_URL
502
+ );
503
+ const trackingKey = firstDefined(
504
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
505
+ env.PUBLIC_JMP_TRACKING_KEY
506
+ );
507
+ const requireConsentRaw = firstDefined(
508
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
509
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
510
+ );
511
+ const autoContactRaw = firstDefined(
512
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
513
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
514
+ );
515
+ return {
516
+ analyticsUrl,
517
+ trackingKey,
518
+ requireConsent: requireConsentRaw === "true",
519
+ autoContactLinks: autoContactRaw === "true"
520
+ };
521
+ }
522
+ function readJmpPublicEnvFromProcess(overrides) {
523
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
524
+ return {
525
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
526
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
527
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
528
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
529
+ };
530
+ }
531
+
532
+ // src/site-config.ts
533
+ var registeredSite = null;
534
+ function getJmpSiteDefinition() {
535
+ return registeredSite;
536
+ }
537
+ function getJmpContentConfig() {
538
+ const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
539
+ if (!analyticsUrl || !trackingKey) {
540
+ return null;
541
+ }
542
+ const site = getJmpSiteDefinition();
543
+ return {
544
+ baseUrl: analyticsUrl,
545
+ trackingKey,
546
+ contentWrappers: site?.contentWrappers ?? void 0,
547
+ contentManifest: site?.contentManifest ?? void 0
548
+ };
549
+ }
550
+
551
+ // src/content/site-content.ts
552
+ async function loadLocations() {
553
+ const c = getJmpContentConfig();
554
+ if (!c) {
555
+ return [];
556
+ }
557
+ try {
558
+ return await getJmpLocations(c);
559
+ } catch {
560
+ return [];
561
+ }
562
+ }
563
+ async function loadLocationDetail(slug) {
564
+ const c = getJmpContentConfig();
565
+ if (!c) {
566
+ return null;
567
+ }
568
+ try {
569
+ return await getJmpLocationDetail(c, slug);
570
+ } catch {
571
+ return null;
572
+ }
573
+ }
574
+
575
+ // src/landing-pages/utils.ts
576
+ function plainTextFromMarkdown(input, maxLength = 5e3) {
577
+ return input.replace(/[#*_`[\]]/g, "").trim().slice(0, maxLength);
578
+ }
579
+ function resolveJmpSiteUrl(override) {
580
+ const fromArg = override?.trim().replace(/\/$/, "");
581
+ if (fromArg) return fromArg;
582
+ const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\/$/, "");
583
+ return fromConfig || null;
584
+ }
585
+ function absoluteJmpSiteUrl(path, siteUrl) {
586
+ const base = resolveJmpSiteUrl(siteUrl);
587
+ if (!base) {
588
+ return path.startsWith("/") ? path : `/${path}`;
589
+ }
590
+ const normalized = path.startsWith("/") ? path : `/${path}`;
591
+ return `${base}${normalized}`;
592
+ }
593
+
594
+ // src/next/locations.tsx
595
+ var import_jsx_runtime = require("react/jsx-runtime");
596
+ function DefaultJsonLd({
597
+ data,
598
+ id
599
+ }) {
600
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
601
+ "script",
602
+ {
603
+ type: "application/ld+json",
604
+ dangerouslySetInnerHTML: { __html: JSON.stringify(item) }
605
+ },
606
+ `${id}-${index}`
607
+ )) });
608
+ }
609
+ function resolveComponents(components) {
610
+ const LinkComponent = components?.Link ?? import_link.default;
611
+ return {
612
+ Link: LinkComponent,
613
+ renderBody: components?.renderBody ?? ((body) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { style: { whiteSpace: "pre-wrap", fontFamily: "inherit" }, children: body }) })),
614
+ renderJsonLd: components?.renderJsonLd ?? ((data, id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultJsonLd, { data, id }))
615
+ };
616
+ }
617
+ function locationCanonicalUrl(location, siteUrl) {
618
+ return absoluteJmpSiteUrl(location.path, siteUrl);
619
+ }
620
+ async function generateJmpLocationStaticParams() {
621
+ const locations = await loadLocations();
622
+ return locations.map((location) => ({ slug: location.slug }));
623
+ }
624
+ async function buildJmpLocationMetadata({
625
+ slug,
626
+ siteUrl
627
+ }) {
628
+ const detail = await loadLocationDetail(slug);
629
+ if (!detail) {
630
+ return { title: "Not found" };
631
+ }
632
+ const { location } = detail;
633
+ const title = location.seo_title?.trim() || location.name;
634
+ const description = location.meta_description?.trim() || location.excerpt?.trim() || plainTextFromMarkdown(location.body, 160) || location.name;
635
+ const canonical = locationCanonicalUrl(location, siteUrl);
636
+ return {
637
+ title,
638
+ description,
639
+ alternates: { canonical },
640
+ openGraph: {
641
+ title,
642
+ description,
643
+ type: "website",
644
+ url: canonical
645
+ },
646
+ twitter: {
647
+ card: "summary",
648
+ title,
649
+ description
650
+ }
651
+ };
652
+ }
653
+ function buildJmpLocationHubMetadata(siteUrl) {
654
+ const canonical = absoluteJmpSiteUrl("/contact", siteUrl);
655
+ return {
656
+ title: "Contact",
657
+ description: "Find our office locations, phone numbers, and hours.",
658
+ alternates: { canonical }
659
+ };
660
+ }
661
+ function buildJmpLocationSitemapRoutes(locations, options = {}) {
662
+ const changeFrequency = options.changeFrequency ?? "monthly";
663
+ const hub = {
664
+ url: absoluteJmpSiteUrl("/contact", options.siteUrl),
665
+ changeFrequency,
666
+ priority: 0.8
667
+ };
668
+ const detailRoutes = locations.map((location) => ({
669
+ url: absoluteJmpSiteUrl(location.path, options.siteUrl),
670
+ lastModified: location.updated_at ? new Date(location.updated_at) : void 0,
671
+ changeFrequency,
672
+ priority: location.is_primary ? 0.75 : 0.65
673
+ }));
674
+ return [hub, ...detailRoutes];
675
+ }
676
+ async function JmpLocationHubPage({
677
+ components,
678
+ className,
679
+ classNames
680
+ }) {
681
+ const locations = await loadLocations();
682
+ const { Link: LinkComponent } = resolveComponents(components);
683
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
684
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: "Contact" }),
685
+ locations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.emptyState, children: "Add office locations in the JMP dashboard (Content \u2192 Locations)." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: classNames?.list, children: locations.map((location) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: classNames?.listItem, children: [
686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkComponent, { href: location.path, children: location.name }),
687
+ location.phone ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: location.phone }) : null,
688
+ location.address ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: location.address }) : null
689
+ ] }, location.id)) })
690
+ ] });
691
+ }
692
+ async function JmpLocationDetailPage({
693
+ slug,
694
+ siteUrl,
695
+ components,
696
+ className,
697
+ classNames
698
+ }) {
699
+ const detail = await loadLocationDetail(slug);
700
+ if (!detail) {
701
+ (0, import_navigation.notFound)();
702
+ }
703
+ const { location, schema_json_ld } = detail;
704
+ const { Link: LinkComponent, renderBody, renderJsonLd } = resolveComponents(components);
705
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
706
+ schema_json_ld && schema_json_ld.length > 0 ? renderJsonLd(schema_json_ld, "jmp-location-jsonld") : null,
707
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkComponent, { href: "/contact", children: "All locations" }) }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: location.name }),
709
+ location.excerpt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.excerpt, children: location.excerpt }) : null,
710
+ location.phone ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
711
+ "Phone:",
712
+ " ",
713
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: `tel:${location.phone.replace(/[^\d+]/g, "")}`, children: location.phone })
714
+ ] }) : null,
715
+ location.email ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
716
+ "Email: ",
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: `mailto:${location.email}`, children: location.email })
718
+ ] }) : null,
719
+ location.address ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: location.address }) : null,
720
+ location.hours ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: location.hours }) : null,
721
+ location.google_maps_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: location.google_maps_url, children: "Directions" }) }) : null,
722
+ location.body.trim() ? renderBody(location.body) : null,
723
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkComponent, { href: "/contact", children: "All locations" }) })
724
+ ] });
725
+ }
726
+ // Annotate the CommonJS export names for ESM import in node:
727
+ 0 && (module.exports = {
728
+ JmpLocationDetailPage,
729
+ JmpLocationHubPage,
730
+ buildJmpLocationHubMetadata,
731
+ buildJmpLocationMetadata,
732
+ buildJmpLocationSitemapRoutes,
733
+ generateJmpLocationStaticParams,
734
+ plainTextFromMarkdown,
735
+ resolveJmpSiteUrl
736
+ });
737
+ //# sourceMappingURL=locations.cjs.map