@jmp-technologies/analytics 0.1.25 → 0.1.27

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/README.md +43 -0
  2. package/bin/jmp-init.mjs +34 -0
  3. package/dist/astro/client.cjs +103 -0
  4. package/dist/astro/client.cjs.map +1 -1
  5. package/dist/astro/client.d.cts +2 -2
  6. package/dist/astro/client.d.ts +2 -2
  7. package/dist/astro/client.js +5 -3
  8. package/dist/astro/client.js.map +1 -1
  9. package/dist/astro/content.cjs +271 -1
  10. package/dist/astro/content.cjs.map +1 -1
  11. package/dist/astro/content.d.cts +9 -3
  12. package/dist/astro/content.d.ts +9 -3
  13. package/dist/astro/content.js +10 -3
  14. package/dist/astro/integration.cjs +56 -10
  15. package/dist/astro/integration.cjs.map +1 -1
  16. package/dist/astro/integration.js +4 -3
  17. package/dist/astro/integration.js.map +1 -1
  18. package/dist/astro/middleware.cjs.map +1 -1
  19. package/dist/astro/middleware.d.cts +2 -2
  20. package/dist/astro/middleware.d.ts +2 -2
  21. package/dist/astro/middleware.js +2 -2
  22. package/dist/astro/register.cjs +56 -10
  23. package/dist/astro/register.cjs.map +1 -1
  24. package/dist/astro/register.js +4 -3
  25. package/dist/{chunk-MOX7RJUU.js → chunk-A4RRTY5D.js} +1 -1
  26. package/dist/{chunk-MOX7RJUU.js.map → chunk-A4RRTY5D.js.map} +1 -1
  27. package/dist/{chunk-L67PVZYL.js → chunk-DNYJF7BG.js} +3 -24
  28. package/dist/chunk-DNYJF7BG.js.map +1 -0
  29. package/dist/{chunk-5GORAM7J.js → chunk-FNWE2DT5.js} +2 -2
  30. package/dist/{chunk-7MLPSL6T.js → chunk-HODG4NJQ.js} +2 -2
  31. package/dist/{chunk-OLWCYWVB.js → chunk-KZ4WVDGL.js} +3 -3
  32. package/dist/{chunk-AJ33NQVZ.js → chunk-PG3IOI5O.js} +42 -3
  33. package/dist/chunk-PG3IOI5O.js.map +1 -0
  34. package/dist/chunk-TCZ6TC2L.js +149 -0
  35. package/dist/chunk-TCZ6TC2L.js.map +1 -0
  36. package/dist/{chunk-VRDPVKAN.js → chunk-UVYF33XE.js} +213 -157
  37. package/dist/chunk-UVYF33XE.js.map +1 -0
  38. package/dist/chunk-YCVHRMM6.js +28 -0
  39. package/dist/chunk-YCVHRMM6.js.map +1 -0
  40. package/dist/{create-jmp-analytics-ClLOMb9p.d.cts → create-jmp-analytics-D4Qw_d6o.d.cts} +1 -1
  41. package/dist/{create-jmp-analytics-COxo3-yg.d.ts → create-jmp-analytics-DpG5Hdl5.d.ts} +1 -1
  42. package/dist/index.cjs +213 -12
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +22 -7
  45. package/dist/index.d.ts +22 -7
  46. package/dist/index.js +19 -3
  47. package/dist/next/content.cjs +274 -1
  48. package/dist/next/content.cjs.map +1 -1
  49. package/dist/next/content.d.cts +7 -4
  50. package/dist/next/content.d.ts +7 -4
  51. package/dist/next/content.js +13 -3
  52. package/dist/next/content.js.map +1 -1
  53. package/dist/next/instrumentation.cjs +56 -10
  54. package/dist/next/instrumentation.cjs.map +1 -1
  55. package/dist/next/instrumentation.js +3 -2
  56. package/dist/next/instrumentation.js.map +1 -1
  57. package/dist/next/landing-capture-script-server.cjs +61 -0
  58. package/dist/next/landing-capture-script-server.cjs.map +1 -0
  59. package/dist/next/landing-capture-script-server.d.cts +14 -0
  60. package/dist/next/landing-capture-script-server.d.ts +14 -0
  61. package/dist/next/landing-capture-script-server.js +8 -0
  62. package/dist/next/landing-capture-script-server.js.map +1 -0
  63. package/dist/next/landing-pages.cjs +659 -0
  64. package/dist/next/landing-pages.cjs.map +1 -0
  65. package/dist/next/landing-pages.d.cts +104 -0
  66. package/dist/next/landing-pages.d.ts +104 -0
  67. package/dist/next/landing-pages.js +299 -0
  68. package/dist/next/landing-pages.js.map +1 -0
  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 +159 -23
  74. package/dist/next/site-root-server.cjs.map +1 -1
  75. package/dist/next/site-root-server.js +9 -4
  76. package/dist/next/site-root-server.js.map +1 -1
  77. package/dist/next.cjs +152 -70
  78. package/dist/next.cjs.map +1 -1
  79. package/dist/next.d.cts +7 -15
  80. package/dist/next.d.ts +7 -15
  81. package/dist/next.js +7 -7
  82. package/dist/react.cjs +103 -0
  83. package/dist/react.cjs.map +1 -1
  84. package/dist/react.d.cts +2 -2
  85. package/dist/react.d.ts +2 -2
  86. package/dist/react.js +4 -3
  87. package/dist/{site-config-BSMVyUjk.d.ts → site-config-CJqYnZm7.d.cts} +6 -3
  88. package/dist/{site-config-8TTHIW6w.d.cts → site-config-UTzDG-UG.d.ts} +6 -3
  89. package/dist/{track-page-view-DtxbdM5q.d.ts → track-page-view-CMAqyhD_.d.ts} +1 -1
  90. package/dist/{track-page-view-BweavmXz.d.cts → track-page-view-tX0Magkd.d.cts} +1 -1
  91. package/dist/{types-Bk0ST1aH.d.ts → types-B-111Wht.d.cts} +52 -5
  92. package/dist/{types-Bk0ST1aH.d.cts → types-B-111Wht.d.ts} +52 -5
  93. package/docs/astro-integration.md +1 -1
  94. package/package.json +16 -2
  95. package/dist/chunk-AJ33NQVZ.js.map +0 -1
  96. package/dist/chunk-L67PVZYL.js.map +0 -1
  97. package/dist/chunk-VRDPVKAN.js.map +0 -1
  98. /package/dist/{chunk-5GORAM7J.js.map → chunk-FNWE2DT5.js.map} +0 -0
  99. /package/dist/{chunk-7MLPSL6T.js.map → chunk-HODG4NJQ.js.map} +0 -0
  100. /package/dist/{chunk-OLWCYWVB.js.map → chunk-KZ4WVDGL.js.map} +0 -0
@@ -0,0 +1,659 @@
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/landing-pages.tsx
31
+ var landing_pages_exports = {};
32
+ __export(landing_pages_exports, {
33
+ JMP_LANDING_HUB: () => JMP_LANDING_HUB,
34
+ JmpLandingDetailPage: () => JmpLandingDetailPage,
35
+ JmpLandingIndexPage: () => JmpLandingIndexPage,
36
+ absoluteJmpSiteUrl: () => absoluteJmpSiteUrl,
37
+ buildJmpLandingIndexMetadata: () => buildJmpLandingIndexMetadata,
38
+ buildJmpLandingMetadata: () => buildJmpLandingMetadata,
39
+ buildJmpLandingSitemapRoutes: () => buildJmpLandingSitemapRoutes,
40
+ filterJmpLandingPagesForDefaultLocale: () => filterJmpLandingPagesForDefaultLocale,
41
+ filterJmpLandingPagesForLocalizedRoutes: () => filterJmpLandingPagesForLocalizedRoutes,
42
+ generateJmpLandingStaticParams: () => generateJmpLandingStaticParams,
43
+ generateJmpLocalizedLandingStaticParams: () => generateJmpLocalizedLandingStaticParams,
44
+ jmpBreadcrumbListJsonLd: () => jmpBreadcrumbListJsonLd,
45
+ jmpLandingKindSegment: () => jmpLandingKindSegment,
46
+ jmpLocalizedLandingStaticParams: () => jmpLocalizedLandingStaticParams,
47
+ plainTextFromMarkdown: () => plainTextFromMarkdown,
48
+ resolveJmpSiteUrl: () => resolveJmpSiteUrl
49
+ });
50
+ module.exports = __toCommonJS(landing_pages_exports);
51
+ var import_link = __toESM(require("next/link"), 1);
52
+ var import_navigation = require("next/navigation");
53
+
54
+ // src/landing-capture.ts
55
+ var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
56
+
57
+ // src/constants.ts
58
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.26";
59
+
60
+ // src/http.ts
61
+ function responseLooksLikeHtml(body) {
62
+ const s = body.trimStart();
63
+ return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
64
+ }
65
+ function normalizeBaseUrl(baseUrl) {
66
+ return baseUrl.replace(/\/$/, "");
67
+ }
68
+
69
+ // src/content/fetch.ts
70
+ async function getContent(config, path) {
71
+ const text = await getContentText(config, path);
72
+ try {
73
+ return JSON.parse(text);
74
+ } catch {
75
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
76
+ const url = `${baseUrl}${path}`;
77
+ throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
78
+ }
79
+ }
80
+ async function getContentText(config, path) {
81
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
82
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
83
+ if (!resolvedFetch) {
84
+ throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
85
+ }
86
+ const url = `${baseUrl}${path}`;
87
+ if (config.debug) {
88
+ console.debug("[@jmp-technologies/analytics]", "GET", url);
89
+ }
90
+ const res = await resolvedFetch(url, {
91
+ method: "GET",
92
+ headers: {
93
+ "X-JMP-Tracking-Key": config.trackingKey
94
+ }
95
+ });
96
+ const text = await res.text();
97
+ if (!res.ok) {
98
+ if (responseLooksLikeHtml(text)) {
99
+ throw new Error(
100
+ `JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
101
+ );
102
+ }
103
+ let parsed = {};
104
+ try {
105
+ parsed = JSON.parse(text);
106
+ } catch {
107
+ parsed = { error: text };
108
+ }
109
+ throw new Error(
110
+ [`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
111
+ );
112
+ }
113
+ return text;
114
+ }
115
+
116
+ // src/content/wrapper-discovery.ts
117
+ var WRAPPER_SLOT_ORDER = [
118
+ "blog",
119
+ "faq",
120
+ "contact",
121
+ "trust",
122
+ "trust.summary",
123
+ "trust.testimonials",
124
+ "trust.reviews",
125
+ "trust.stats",
126
+ "trust.logos",
127
+ "trust.credentials"
128
+ ];
129
+ var discovered = /* @__PURE__ */ new Set();
130
+ var syncTimer = null;
131
+ var pendingSyncConfig = null;
132
+ var reportedDedupeKeys = /* @__PURE__ */ new Set();
133
+ var syncHandler = null;
134
+ function markContentWrapper(slot) {
135
+ const normalized = slot.trim().toLowerCase();
136
+ if (!normalized) {
137
+ return;
138
+ }
139
+ discovered.add(normalized);
140
+ }
141
+ function getDiscoveredContentWrappers() {
142
+ return sortWrapperSlots([...discovered]);
143
+ }
144
+ function resolveContentWrappersForReport(config) {
145
+ const explicit = config.contentWrappers ?? [];
146
+ const auto = getDiscoveredContentWrappers();
147
+ if (config.contentWrapperMode === "manual") {
148
+ return sortWrapperSlots([...explicit]);
149
+ }
150
+ if (config.contentWrapperMode === "explicit-only") {
151
+ return sortWrapperSlots([...explicit]);
152
+ }
153
+ const merged = /* @__PURE__ */ new Set([...explicit, ...auto]);
154
+ return sortWrapperSlots([...merged]);
155
+ }
156
+ function hasReportedContentWrappers(dedupeKey) {
157
+ return reportedDedupeKeys.has(dedupeKey);
158
+ }
159
+ function rememberReportedContentWrappers(dedupeKey) {
160
+ reportedDedupeKeys.add(dedupeKey);
161
+ }
162
+ function setContentWrapperSyncHandler(handler) {
163
+ syncHandler = handler;
164
+ }
165
+ function scheduleContentWrapperSync(config) {
166
+ if (!syncHandler) {
167
+ return;
168
+ }
169
+ pendingSyncConfig = config;
170
+ if (syncTimer) {
171
+ clearTimeout(syncTimer);
172
+ }
173
+ syncTimer = setTimeout(() => {
174
+ syncTimer = null;
175
+ const cfg = pendingSyncConfig;
176
+ pendingSyncConfig = null;
177
+ if (!cfg || !syncHandler) {
178
+ return;
179
+ }
180
+ void syncHandler(cfg).catch(() => {
181
+ });
182
+ }, 750);
183
+ }
184
+ function sortWrapperSlots(slots) {
185
+ const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]));
186
+ return [...new Set(slots)].sort((a, b) => {
187
+ const ai = order.get(a) ?? 999;
188
+ const bi = order.get(b) ?? 999;
189
+ if (ai !== bi) {
190
+ return ai - bi;
191
+ }
192
+ return a.localeCompare(b);
193
+ });
194
+ }
195
+
196
+ // src/content/api.ts
197
+ function afterContentWrapperUse(config) {
198
+ scheduleContentWrapperSync(config);
199
+ }
200
+ async function getJmpLandingPages(config, filters) {
201
+ const params = new URLSearchParams();
202
+ if (filters?.kind) {
203
+ params.set("kind", filters.kind);
204
+ }
205
+ if (filters?.locale) {
206
+ params.set("locale", filters.locale);
207
+ }
208
+ const query = params.toString();
209
+ const data = await getContent(
210
+ config,
211
+ `/api/content/landing-pages${query ? `?${query}` : ""}`
212
+ );
213
+ markContentWrapper("landing");
214
+ afterContentWrapperUse(config);
215
+ return data.pages;
216
+ }
217
+ async function getJmpLandingPageDetail(config, kind, slug, locale) {
218
+ const params = new URLSearchParams();
219
+ if (locale) {
220
+ params.set("locale", locale);
221
+ }
222
+ const query = params.toString();
223
+ try {
224
+ const detail = await getContent(
225
+ config,
226
+ `/api/content/landing-pages/${encodeURIComponent(kind)}/${encodeURIComponent(slug)}${query ? `?${query}` : ""}`
227
+ );
228
+ markContentWrapper("landing");
229
+ afterContentWrapperUse(config);
230
+ return detail;
231
+ } catch (error) {
232
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
233
+ return null;
234
+ }
235
+ throw error;
236
+ }
237
+ }
238
+ async function registerJmpContentWrappers(config) {
239
+ const wrappers = resolveContentWrappersForReport(config);
240
+ if (!wrappers.length) {
241
+ return { ok: false, skipped: true };
242
+ }
243
+ const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}`;
244
+ if (hasReportedContentWrappers(dedupeKey)) {
245
+ return { ok: true, wrappers };
246
+ }
247
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
248
+ const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
249
+ if (!resolvedFetch) {
250
+ throw new Error(
251
+ "JMP content wrappers: fetch is not available; pass fetchImpl in config"
252
+ );
253
+ }
254
+ const url = `${baseUrl}/api/content/capabilities`;
255
+ if (config.debug) {
256
+ console.debug("[@jmp-technologies/analytics]", "POST", url, { wrappers });
257
+ }
258
+ const res = await resolvedFetch(url, {
259
+ method: "POST",
260
+ headers: {
261
+ "Content-Type": "application/json",
262
+ "X-JMP-Tracking-Key": config.trackingKey
263
+ },
264
+ body: JSON.stringify({
265
+ wrappers,
266
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
267
+ })
268
+ });
269
+ const text = await res.text();
270
+ if (!res.ok) {
271
+ let parsed = {};
272
+ try {
273
+ parsed = JSON.parse(text);
274
+ } catch {
275
+ parsed = { error: text };
276
+ }
277
+ throw new Error(
278
+ [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
279
+ );
280
+ }
281
+ rememberReportedContentWrappers(dedupeKey);
282
+ const data = JSON.parse(text);
283
+ return { ok: true, wrappers: data.wrappers ?? wrappers };
284
+ }
285
+ setContentWrapperSyncHandler(registerJmpContentWrappers);
286
+
287
+ // src/env-public.ts
288
+ function firstDefined(...values) {
289
+ for (const v of values) {
290
+ if (v) return v;
291
+ }
292
+ return void 0;
293
+ }
294
+ function readJmpPublicEnvFromProcessEnv() {
295
+ if (typeof process === "undefined" || !process.env) {
296
+ return {};
297
+ }
298
+ const { env } = process;
299
+ const analyticsUrl = firstDefined(
300
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
301
+ env.PUBLIC_JMP_ANALYTICS_URL
302
+ );
303
+ const trackingKey = firstDefined(
304
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
305
+ env.PUBLIC_JMP_TRACKING_KEY
306
+ );
307
+ const requireConsentRaw = firstDefined(
308
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
309
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
310
+ );
311
+ const autoContactRaw = firstDefined(
312
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
313
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
314
+ );
315
+ return {
316
+ analyticsUrl,
317
+ trackingKey,
318
+ requireConsent: requireConsentRaw === "true",
319
+ autoContactLinks: autoContactRaw === "true"
320
+ };
321
+ }
322
+ function readJmpPublicEnvFromProcess(overrides) {
323
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
324
+ return {
325
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
326
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
327
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
328
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
329
+ };
330
+ }
331
+
332
+ // src/site-config.ts
333
+ var registeredSite = null;
334
+ function getJmpSiteDefinition() {
335
+ return registeredSite;
336
+ }
337
+ function getJmpContentConfig() {
338
+ const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
339
+ if (!analyticsUrl || !trackingKey) {
340
+ return null;
341
+ }
342
+ const site = getJmpSiteDefinition();
343
+ return {
344
+ baseUrl: analyticsUrl,
345
+ trackingKey,
346
+ contentWrappers: site?.contentWrappers ?? void 0
347
+ };
348
+ }
349
+
350
+ // src/content/site-content.ts
351
+ async function loadLandingPages(filters) {
352
+ const c = getJmpContentConfig();
353
+ if (!c) {
354
+ return [];
355
+ }
356
+ try {
357
+ return await getJmpLandingPages(c, filters);
358
+ } catch {
359
+ return [];
360
+ }
361
+ }
362
+ async function loadLandingPageDetail(kind, slug, locale) {
363
+ const c = getJmpContentConfig();
364
+ if (!c) {
365
+ return null;
366
+ }
367
+ try {
368
+ return await getJmpLandingPageDetail(c, kind, slug, locale);
369
+ } catch {
370
+ return null;
371
+ }
372
+ }
373
+
374
+ // src/landing-pages/hub.ts
375
+ var JMP_LANDING_HUB = {
376
+ location: {
377
+ title: "Areas we serve",
378
+ path: "/areas",
379
+ description: "Local pages for cities and regions we work in.",
380
+ breadcrumb: "Areas",
381
+ segment: "areas"
382
+ },
383
+ service: {
384
+ title: "Services",
385
+ path: "/services",
386
+ description: "What we offer and how we help.",
387
+ breadcrumb: "Services",
388
+ segment: "services"
389
+ }
390
+ };
391
+ function jmpLandingKindSegment(kind) {
392
+ return JMP_LANDING_HUB[kind].segment;
393
+ }
394
+
395
+ // src/landing-pages/utils.ts
396
+ function plainTextFromMarkdown(input, maxLength = 5e3) {
397
+ return input.replace(/[#*_`[\]]/g, "").trim().slice(0, maxLength);
398
+ }
399
+ function resolveJmpSiteUrl(override) {
400
+ const fromArg = override?.trim().replace(/\/$/, "");
401
+ if (fromArg) return fromArg;
402
+ const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\/$/, "");
403
+ return fromConfig || null;
404
+ }
405
+ function absoluteJmpSiteUrl(path, siteUrl) {
406
+ const base = resolveJmpSiteUrl(siteUrl);
407
+ if (!base) {
408
+ return path.startsWith("/") ? path : `/${path}`;
409
+ }
410
+ const normalized = path.startsWith("/") ? path : `/${path}`;
411
+ return `${base}${normalized}`;
412
+ }
413
+ function filterJmpLandingPagesForDefaultLocale(pages, kind) {
414
+ const segment = jmpLandingKindSegment(kind);
415
+ return pages.filter((page) => {
416
+ const parts = page.path.split("/").filter(Boolean);
417
+ return parts[0] === segment;
418
+ });
419
+ }
420
+ function filterJmpLandingPagesForLocalizedRoutes(pages, kind) {
421
+ const segment = jmpLandingKindSegment(kind);
422
+ return pages.filter((page) => {
423
+ const parts = page.path.split("/").filter(Boolean);
424
+ return parts.length >= 3 && parts[1] === segment;
425
+ });
426
+ }
427
+ function jmpLocalizedLandingStaticParams(pages, kind) {
428
+ const segment = jmpLandingKindSegment(kind);
429
+ return filterJmpLandingPagesForLocalizedRoutes(pages, kind).map((page) => {
430
+ const [locale, , slug] = page.path.split("/").filter(Boolean);
431
+ return { locale, slug };
432
+ });
433
+ }
434
+
435
+ // src/landing-pages/json-ld.ts
436
+ function jmpBreadcrumbListJsonLd(items, siteUrl) {
437
+ return {
438
+ "@context": "https://schema.org",
439
+ "@type": "BreadcrumbList",
440
+ itemListElement: items.map((item, index) => ({
441
+ "@type": "ListItem",
442
+ position: index + 1,
443
+ name: item.name,
444
+ item: absoluteJmpSiteUrl(item.path, siteUrl)
445
+ }))
446
+ };
447
+ }
448
+
449
+ // src/landing-pages/static-params.ts
450
+ async function generateJmpLandingStaticParams(kind) {
451
+ const pages = await loadLandingPages({ kind });
452
+ return filterJmpLandingPagesForDefaultLocale(pages, kind).map((page) => ({
453
+ slug: page.slug
454
+ }));
455
+ }
456
+ async function generateJmpLocalizedLandingStaticParams(kind) {
457
+ const pages = await loadLandingPages({ kind });
458
+ return jmpLocalizedLandingStaticParams(pages, kind);
459
+ }
460
+
461
+ // src/landing-pages/sitemap.ts
462
+ function buildJmpLandingSitemapRoutes(entries, options) {
463
+ const { siteUrl, changeFrequency = "monthly" } = options;
464
+ const defaultPriority = {
465
+ location: 0.7,
466
+ service: 0.68
467
+ };
468
+ return entries.map((entry) => {
469
+ const priority = options.priorityByKind?.[entry.kind] ?? defaultPriority[entry.kind];
470
+ const languages = entry.alternates.length > 0 ? Object.fromEntries(
471
+ entry.alternates.map((alt) => [
472
+ alt.locale,
473
+ absoluteJmpSiteUrl(alt.path, siteUrl)
474
+ ])
475
+ ) : void 0;
476
+ return {
477
+ url: absoluteJmpSiteUrl(entry.path, siteUrl),
478
+ lastModified: entry.updated_at ? new Date(entry.updated_at) : void 0,
479
+ changeFrequency,
480
+ priority,
481
+ alternates: languages ? { languages } : void 0
482
+ };
483
+ });
484
+ }
485
+
486
+ // src/next/landing-pages.tsx
487
+ var import_jsx_runtime = require("react/jsx-runtime");
488
+ async function buildJmpLandingMetadata({
489
+ kind,
490
+ slug,
491
+ locale,
492
+ siteUrl
493
+ }) {
494
+ const detail = await loadLandingPageDetail(kind, slug, locale);
495
+ if (!detail) {
496
+ return { title: "Not found" };
497
+ }
498
+ const { page, alternates } = detail;
499
+ const title = page.seo_title?.trim() || page.title;
500
+ const description = page.meta_description?.trim() || page.excerpt?.trim() || plainTextFromMarkdown(page.body, 160) || page.title;
501
+ const languages = {};
502
+ for (const alt of alternates) {
503
+ languages[alt.locale] = absoluteJmpSiteUrl(alt.path, siteUrl);
504
+ }
505
+ const canonical = absoluteJmpSiteUrl(page.path, siteUrl);
506
+ return {
507
+ title,
508
+ description,
509
+ alternates: {
510
+ canonical,
511
+ languages: Object.keys(languages).length > 0 ? languages : void 0
512
+ },
513
+ openGraph: {
514
+ title,
515
+ description,
516
+ type: "website",
517
+ url: canonical
518
+ },
519
+ twitter: {
520
+ card: "summary",
521
+ title,
522
+ description
523
+ }
524
+ };
525
+ }
526
+ function buildJmpLandingIndexMetadata(kind, siteUrl) {
527
+ const hub = JMP_LANDING_HUB[kind];
528
+ return {
529
+ title: hub.title,
530
+ description: hub.description,
531
+ alternates: {
532
+ canonical: absoluteJmpSiteUrl(hub.path, siteUrl)
533
+ }
534
+ };
535
+ }
536
+ function DefaultJsonLd({
537
+ data,
538
+ id
539
+ }) {
540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: data.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
541
+ "script",
542
+ {
543
+ type: "application/ld+json",
544
+ dangerouslySetInnerHTML: { __html: JSON.stringify(item) }
545
+ },
546
+ `${id}-${index}`
547
+ )) });
548
+ }
549
+ function DefaultBreadcrumbs({
550
+ items,
551
+ LinkComponent
552
+ }) {
553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { style: { display: "flex", flexWrap: "wrap", gap: "0.5rem", listStyle: "none", padding: 0 }, children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
554
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: " / " }) : null,
555
+ index < items.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkComponent, { href: item.path, children: item.name }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.name })
556
+ ] }, item.path)) }) });
557
+ }
558
+ function resolveComponents(components) {
559
+ const LinkComponent = components?.Link ?? import_link.default;
560
+ return {
561
+ Link: LinkComponent,
562
+ 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 }) })),
563
+ renderBreadcrumbs: components?.renderBreadcrumbs ?? ((items) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultBreadcrumbs, { items, LinkComponent })),
564
+ renderJsonLd: components?.renderJsonLd ?? ((data, id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultJsonLd, { data, id }))
565
+ };
566
+ }
567
+ async function JmpLandingDetailPage({
568
+ kind,
569
+ slug,
570
+ locale,
571
+ siteUrl,
572
+ components,
573
+ className,
574
+ classNames
575
+ }) {
576
+ const detail = await loadLandingPageDetail(kind, slug, locale);
577
+ if (!detail) {
578
+ (0, import_navigation.notFound)();
579
+ }
580
+ const ui = resolveComponents(components);
581
+ const resolvedSiteUrl = resolveJmpSiteUrl(siteUrl) ?? void 0;
582
+ const { page, schema_json_ld } = detail;
583
+ const hub = JMP_LANDING_HUB[kind];
584
+ const displayTitle = page.headline?.trim() || page.title;
585
+ const breadcrumbItems = [
586
+ { name: "Home", path: "/" },
587
+ { name: hub.breadcrumb, path: hub.path },
588
+ { name: page.title, path: page.path }
589
+ ];
590
+ const related = filterJmpLandingPagesForDefaultLocale(
591
+ await loadLandingPages({ kind }),
592
+ kind
593
+ ).filter((item) => item.id !== page.id && item.locale === page.locale);
594
+ const jsonLdBlocks = [
595
+ ...schema_json_ld ?? [],
596
+ jmpBreadcrumbListJsonLd(breadcrumbItems, resolvedSiteUrl)
597
+ ];
598
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
599
+ ui.renderJsonLd(jsonLdBlocks, `jmp-landing-${page.id}-ld-json`),
600
+ ui.renderBreadcrumbs(breadcrumbItems),
601
+ page.excerpt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.excerpt, children: page.excerpt }) : null,
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: displayTitle }),
603
+ page.body ? ui.renderBody(page.body) : null,
604
+ related.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
605
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { className: classNames?.relatedTitle, children: [
606
+ "More ",
607
+ hub.breadcrumb.toLowerCase()
608
+ ] }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: classNames?.list, children: related.slice(0, 8).map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: classNames?.listItem, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.Link, { href: item.path, children: item.title }) }, item.id)) })
610
+ ] }) : null
611
+ ] });
612
+ }
613
+ async function JmpLandingIndexPage({
614
+ kind,
615
+ siteUrl,
616
+ components,
617
+ className,
618
+ classNames
619
+ }) {
620
+ const ui = resolveComponents(components);
621
+ const hub = JMP_LANDING_HUB[kind];
622
+ const pages = filterJmpLandingPagesForDefaultLocale(
623
+ await loadLandingPages({ kind }),
624
+ kind
625
+ );
626
+ const breadcrumbItems = [
627
+ { name: "Home", path: "/" },
628
+ { name: hub.breadcrumb, path: hub.path }
629
+ ];
630
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className, children: [
631
+ ui.renderBreadcrumbs(breadcrumbItems),
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: classNames?.title, children: hub.title }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.hubDescription, children: hub.description }),
634
+ pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: classNames?.list, children: pages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: classNames?.listItem, children: [
635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.Link, { href: page.path, children: page.title }),
636
+ page.excerpt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.excerpt, children: page.excerpt }) : null
637
+ ] }, page.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.emptyState, children: "Pages will appear here once published in the JMP dashboard." })
638
+ ] });
639
+ }
640
+ // Annotate the CommonJS export names for ESM import in node:
641
+ 0 && (module.exports = {
642
+ JMP_LANDING_HUB,
643
+ JmpLandingDetailPage,
644
+ JmpLandingIndexPage,
645
+ absoluteJmpSiteUrl,
646
+ buildJmpLandingIndexMetadata,
647
+ buildJmpLandingMetadata,
648
+ buildJmpLandingSitemapRoutes,
649
+ filterJmpLandingPagesForDefaultLocale,
650
+ filterJmpLandingPagesForLocalizedRoutes,
651
+ generateJmpLandingStaticParams,
652
+ generateJmpLocalizedLandingStaticParams,
653
+ jmpBreadcrumbListJsonLd,
654
+ jmpLandingKindSegment,
655
+ jmpLocalizedLandingStaticParams,
656
+ plainTextFromMarkdown,
657
+ resolveJmpSiteUrl
658
+ });
659
+ //# sourceMappingURL=landing-pages.cjs.map