@burdenoff/website-sdk 2026.1101.1 → 2026.1101.2

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 (276) hide show
  1. package/README.md +42 -8
  2. package/dist/analytics/index.js +19 -69
  3. package/dist/analytics/index.js.map +1 -1
  4. package/dist/analytics/index.mjs +3 -69
  5. package/dist/analytics/index.mjs.map +1 -1
  6. package/dist/avatar-live/index.js +1 -0
  7. package/dist/avatar-live/index.js.map +1 -1
  8. package/dist/avatar-live/index.mjs +1 -0
  9. package/dist/avatar-live/index.mjs.map +1 -1
  10. package/dist/avatar-live-view/index.js +10 -30
  11. package/dist/avatar-live-view/index.js.map +1 -1
  12. package/dist/avatar-live-view/index.mjs +4 -24
  13. package/dist/avatar-live-view/index.mjs.map +1 -1
  14. package/dist/chunk-23H4JUL4.js +41 -0
  15. package/dist/chunk-23H4JUL4.js.map +1 -0
  16. package/dist/chunk-34RX5P75.mjs +25 -0
  17. package/dist/chunk-34RX5P75.mjs.map +1 -0
  18. package/dist/chunk-3LFFY7AZ.js +184 -0
  19. package/dist/chunk-3LFFY7AZ.js.map +1 -0
  20. package/dist/chunk-3X26GE4S.js +2794 -0
  21. package/dist/chunk-3X26GE4S.js.map +1 -0
  22. package/dist/chunk-45GCA4GG.js +85 -0
  23. package/dist/chunk-45GCA4GG.js.map +1 -0
  24. package/dist/chunk-4SAGVLHN.js +876 -0
  25. package/dist/chunk-4SAGVLHN.js.map +1 -0
  26. package/dist/chunk-4U6YAII5.js +218 -0
  27. package/dist/chunk-4U6YAII5.js.map +1 -0
  28. package/dist/chunk-5GMTBOO6.js +21 -0
  29. package/dist/chunk-5GMTBOO6.js.map +1 -0
  30. package/dist/chunk-5JRJ57CL.mjs +329 -0
  31. package/dist/chunk-5JRJ57CL.mjs.map +1 -0
  32. package/dist/chunk-6C2DKIC7.js +4 -0
  33. package/dist/chunk-6C2DKIC7.js.map +1 -0
  34. package/dist/chunk-6IBFBWC5.mjs +17 -0
  35. package/dist/chunk-6IBFBWC5.mjs.map +1 -0
  36. package/dist/chunk-6KY2ZRVI.js +116 -0
  37. package/dist/chunk-6KY2ZRVI.js.map +1 -0
  38. package/dist/chunk-6VJT3SM4.mjs +182 -0
  39. package/dist/chunk-6VJT3SM4.mjs.map +1 -0
  40. package/dist/chunk-6YZUFMK2.mjs +40 -0
  41. package/dist/chunk-6YZUFMK2.mjs.map +1 -0
  42. package/dist/chunk-7A6R7CGG.js +47 -0
  43. package/dist/chunk-7A6R7CGG.js.map +1 -0
  44. package/dist/chunk-7PWL4V2R.js +4 -0
  45. package/dist/chunk-7PWL4V2R.js.map +1 -0
  46. package/dist/chunk-AA33WDYS.js +161 -0
  47. package/dist/chunk-AA33WDYS.js.map +1 -0
  48. package/dist/chunk-AHRGFXV2.js +525 -0
  49. package/dist/chunk-AHRGFXV2.js.map +1 -0
  50. package/dist/chunk-B63XLDZH.mjs +109 -0
  51. package/dist/chunk-B63XLDZH.mjs.map +1 -0
  52. package/dist/chunk-BX2USEWF.mjs +35 -0
  53. package/dist/chunk-BX2USEWF.mjs.map +1 -0
  54. package/dist/chunk-BYSZT35R.js +150 -0
  55. package/dist/chunk-BYSZT35R.js.map +1 -0
  56. package/dist/chunk-C6OJIHNO.js +12 -0
  57. package/dist/chunk-C6OJIHNO.js.map +1 -0
  58. package/dist/chunk-COOELBX7.mjs +63 -0
  59. package/dist/chunk-COOELBX7.mjs.map +1 -0
  60. package/dist/chunk-D2TW7MNC.mjs +2771 -0
  61. package/dist/chunk-D2TW7MNC.mjs.map +1 -0
  62. package/dist/chunk-D5KEZ6V6.mjs +3 -0
  63. package/dist/chunk-D5KEZ6V6.mjs.map +1 -0
  64. package/dist/chunk-E3GUGJMY.js +46 -0
  65. package/dist/chunk-E3GUGJMY.js.map +1 -0
  66. package/dist/chunk-EBZ2HGJ2.mjs +52 -0
  67. package/dist/chunk-EBZ2HGJ2.mjs.map +1 -0
  68. package/dist/chunk-EDLTUOIA.mjs +210 -0
  69. package/dist/chunk-EDLTUOIA.mjs.map +1 -0
  70. package/dist/chunk-EJQSOT4X.js +4 -0
  71. package/dist/chunk-EJQSOT4X.js.map +1 -0
  72. package/dist/chunk-EUPFS6MZ.mjs +2019 -0
  73. package/dist/chunk-EUPFS6MZ.mjs.map +1 -0
  74. package/dist/chunk-F3XWDC3B.js +77 -0
  75. package/dist/chunk-F3XWDC3B.js.map +1 -0
  76. package/dist/chunk-FFADMVIK.mjs +5127 -0
  77. package/dist/chunk-FFADMVIK.mjs.map +1 -0
  78. package/dist/chunk-FJWS7BKT.mjs +3 -0
  79. package/dist/chunk-FJWS7BKT.mjs.map +1 -0
  80. package/dist/chunk-FJZJDUOR.mjs +3 -0
  81. package/dist/chunk-FJZJDUOR.mjs.map +1 -0
  82. package/dist/chunk-GCFLCO2Z.mjs +145 -0
  83. package/dist/chunk-GCFLCO2Z.mjs.map +1 -0
  84. package/dist/chunk-GEXC4LRP.js +61 -0
  85. package/dist/chunk-GEXC4LRP.js.map +1 -0
  86. package/dist/chunk-GTRAXQGC.mjs +3 -0
  87. package/dist/chunk-GTRAXQGC.mjs.map +1 -0
  88. package/dist/chunk-IKOIRUKW.js +270 -0
  89. package/dist/chunk-IKOIRUKW.js.map +1 -0
  90. package/dist/chunk-IXTWOPRN.js +45 -0
  91. package/dist/chunk-IXTWOPRN.js.map +1 -0
  92. package/dist/chunk-JDXOXGOK.js +547 -0
  93. package/dist/chunk-JDXOXGOK.js.map +1 -0
  94. package/dist/chunk-JSLDWYV7.mjs +558 -0
  95. package/dist/chunk-JSLDWYV7.mjs.map +1 -0
  96. package/dist/chunk-JXLSTVBK.js +336 -0
  97. package/dist/chunk-JXLSTVBK.js.map +1 -0
  98. package/dist/chunk-KHYXK7FG.mjs +49 -0
  99. package/dist/chunk-KHYXK7FG.mjs.map +1 -0
  100. package/dist/chunk-KSMBOWQQ.js +2034 -0
  101. package/dist/chunk-KSMBOWQQ.js.map +1 -0
  102. package/dist/chunk-KU2PXHGB.mjs +83 -0
  103. package/dist/chunk-KU2PXHGB.mjs.map +1 -0
  104. package/dist/chunk-KZANIV3S.mjs +37 -0
  105. package/dist/chunk-KZANIV3S.mjs.map +1 -0
  106. package/dist/chunk-L4BP6YOB.js +20 -0
  107. package/dist/chunk-L4BP6YOB.js.map +1 -0
  108. package/dist/chunk-MRKA2OAF.mjs +75 -0
  109. package/dist/chunk-MRKA2OAF.mjs.map +1 -0
  110. package/dist/chunk-N5EC53MH.mjs +80 -0
  111. package/dist/chunk-N5EC53MH.mjs.map +1 -0
  112. package/dist/chunk-NZANQGDM.mjs +34 -0
  113. package/dist/chunk-NZANQGDM.mjs.map +1 -0
  114. package/dist/chunk-O4GS3XPZ.js +10 -0
  115. package/dist/chunk-O4GS3XPZ.js.map +1 -0
  116. package/dist/chunk-O76QDGRM.js +5154 -0
  117. package/dist/chunk-O76QDGRM.js.map +1 -0
  118. package/dist/chunk-OYEUNZJY.js +114 -0
  119. package/dist/chunk-OYEUNZJY.js.map +1 -0
  120. package/dist/chunk-PHXAGQNB.js +74 -0
  121. package/dist/chunk-PHXAGQNB.js.map +1 -0
  122. package/dist/chunk-PIHHPP7J.mjs +16 -0
  123. package/dist/chunk-PIHHPP7J.mjs.map +1 -0
  124. package/dist/chunk-PXRHVVOU.mjs +499 -0
  125. package/dist/chunk-PXRHVVOU.mjs.map +1 -0
  126. package/dist/chunk-QMA6U6AU.js +36 -0
  127. package/dist/chunk-QMA6U6AU.js.map +1 -0
  128. package/dist/chunk-R6J7IULF.js +44 -0
  129. package/dist/chunk-R6J7IULF.js.map +1 -0
  130. package/dist/chunk-R7G6TWIS.mjs +10 -0
  131. package/dist/chunk-R7G6TWIS.mjs.map +1 -0
  132. package/dist/chunk-RUMGFI6L.mjs +544 -0
  133. package/dist/chunk-RUMGFI6L.mjs.map +1 -0
  134. package/dist/chunk-S54JWSJF.js +565 -0
  135. package/dist/chunk-S54JWSJF.js.map +1 -0
  136. package/dist/chunk-SK5UGG6A.js +2041 -0
  137. package/dist/chunk-SK5UGG6A.js.map +1 -0
  138. package/dist/chunk-SKIYDKIS.mjs +70 -0
  139. package/dist/chunk-SKIYDKIS.mjs.map +1 -0
  140. package/dist/chunk-SZNS5IK6.mjs +322 -0
  141. package/dist/chunk-SZNS5IK6.mjs.map +1 -0
  142. package/dist/chunk-T7JIPZAC.js +66 -0
  143. package/dist/chunk-T7JIPZAC.js.map +1 -0
  144. package/dist/chunk-TGYQHO2E.mjs +864 -0
  145. package/dist/chunk-TGYQHO2E.mjs.map +1 -0
  146. package/dist/chunk-TT3MHN42.mjs +8 -0
  147. package/dist/chunk-TT3MHN42.mjs.map +1 -0
  148. package/dist/chunk-UM55U65W.mjs +268 -0
  149. package/dist/chunk-UM55U65W.mjs.map +1 -0
  150. package/dist/chunk-UQ2HALUD.js +72 -0
  151. package/dist/chunk-UQ2HALUD.js.map +1 -0
  152. package/dist/chunk-UXWT7G77.js +82 -0
  153. package/dist/chunk-UXWT7G77.js.map +1 -0
  154. package/dist/chunk-VNS5WDWO.mjs +7 -0
  155. package/dist/chunk-VNS5WDWO.mjs.map +1 -0
  156. package/dist/chunk-VRWZLGR7.js +4 -0
  157. package/dist/chunk-VRWZLGR7.js.map +1 -0
  158. package/dist/chunk-VZLDSNH4.mjs +22 -0
  159. package/dist/chunk-VZLDSNH4.mjs.map +1 -0
  160. package/dist/chunk-W6P3YETJ.js +92 -0
  161. package/dist/chunk-W6P3YETJ.js.map +1 -0
  162. package/dist/chunk-WPKFFQCR.mjs +90 -0
  163. package/dist/chunk-WPKFFQCR.mjs.map +1 -0
  164. package/dist/chunk-WTVTI5TO.mjs +22 -0
  165. package/dist/chunk-WTVTI5TO.mjs.map +1 -0
  166. package/dist/chunk-XJOBZ4RJ.js +44 -0
  167. package/dist/chunk-XJOBZ4RJ.js.map +1 -0
  168. package/dist/chunk-XP2JDNDU.js +325 -0
  169. package/dist/chunk-XP2JDNDU.js.map +1 -0
  170. package/dist/chunk-Y4V5CEWD.mjs +112 -0
  171. package/dist/chunk-Y4V5CEWD.mjs.map +1 -0
  172. package/dist/chunk-YERN2UWM.js +116 -0
  173. package/dist/chunk-YERN2UWM.js.map +1 -0
  174. package/dist/chunk-ZKJQWFAA.mjs +114 -0
  175. package/dist/chunk-ZKJQWFAA.mjs.map +1 -0
  176. package/dist/chunk-ZOON3S3Y.mjs +138 -0
  177. package/dist/chunk-ZOON3S3Y.mjs.map +1 -0
  178. package/dist/chunk-ZP6QRAYC.mjs +2024 -0
  179. package/dist/chunk-ZP6QRAYC.mjs.map +1 -0
  180. package/dist/chunk-ZSTFX7FV.js +9 -0
  181. package/dist/chunk-ZSTFX7FV.js.map +1 -0
  182. package/dist/client/index.js +23 -331
  183. package/dist/client/index.js.map +1 -1
  184. package/dist/client/index.mjs +3 -330
  185. package/dist/client/index.mjs.map +1 -1
  186. package/dist/comparison/index.js +28 -111
  187. package/dist/comparison/index.js.map +1 -1
  188. package/dist/comparison/index.mjs +2 -107
  189. package/dist/comparison/index.mjs.map +1 -1
  190. package/dist/components/contact.js +21 -931
  191. package/dist/components/contact.js.map +1 -1
  192. package/dist/components/contact.mjs +12 -909
  193. package/dist/components/contact.mjs.map +1 -1
  194. package/dist/components/electron-download.js +12 -124
  195. package/dist/components/electron-download.js.map +1 -1
  196. package/dist/components/electron-download.mjs +4 -106
  197. package/dist/components/electron-download.mjs.map +1 -1
  198. package/dist/components/explore-cta.js +8 -112
  199. package/dist/components/explore-cta.js.map +1 -1
  200. package/dist/components/explore-cta.mjs +3 -114
  201. package/dist/components/explore-cta.mjs.map +1 -1
  202. package/dist/components/explore.js +62 -7833
  203. package/dist/components/explore.js.map +1 -1
  204. package/dist/components/explore.mjs +13 -7800
  205. package/dist/components/explore.mjs.map +1 -1
  206. package/dist/components/launch-countdown.js +8 -83
  207. package/dist/components/launch-countdown.js.map +1 -1
  208. package/dist/components/launch-countdown.mjs +3 -85
  209. package/dist/components/launch-countdown.mjs.map +1 -1
  210. package/dist/components/newsletter.js +20 -993
  211. package/dist/components/newsletter.js.map +1 -1
  212. package/dist/components/newsletter.mjs +11 -971
  213. package/dist/components/newsletter.mjs.map +1 -1
  214. package/dist/components/partners.js +24 -1302
  215. package/dist/components/partners.js.map +1 -1
  216. package/dist/components/partners.mjs +15 -1276
  217. package/dist/components/partners.mjs.map +1 -1
  218. package/dist/components/pricing.js +49 -1249
  219. package/dist/components/pricing.js.map +1 -1
  220. package/dist/components/pricing.mjs +8 -1226
  221. package/dist/components/pricing.mjs.map +1 -1
  222. package/dist/components/problems-solved.js +8 -150
  223. package/dist/components/problems-solved.js.map +1 -1
  224. package/dist/components/problems-solved.mjs +4 -133
  225. package/dist/components/problems-solved.mjs.map +1 -1
  226. package/dist/components/resource-links.js +8 -304
  227. package/dist/components/resource-links.js.map +1 -1
  228. package/dist/components/resource-links.mjs +5 -308
  229. package/dist/components/resource-links.mjs.map +1 -1
  230. package/dist/components/social-links.js +8 -343
  231. package/dist/components/social-links.js.map +1 -1
  232. package/dist/components/social-links.mjs +5 -347
  233. package/dist/components/social-links.mjs.map +1 -1
  234. package/dist/components/unsubscribe.js +10 -627
  235. package/dist/components/unsubscribe.js.map +1 -1
  236. package/dist/components/unsubscribe.mjs +6 -611
  237. package/dist/components/unsubscribe.mjs.map +1 -1
  238. package/dist/components/waitlist.js +21 -1001
  239. package/dist/components/waitlist.js.map +1 -1
  240. package/dist/components/waitlist.mjs +12 -982
  241. package/dist/components/waitlist.mjs.map +1 -1
  242. package/dist/components/website-switcher.js +12 -175
  243. package/dist/components/website-switcher.js.map +1 -1
  244. package/dist/components/website-switcher.mjs +4 -157
  245. package/dist/components/website-switcher.mjs.map +1 -1
  246. package/dist/content/index.js +118 -3616
  247. package/dist/content/index.js.map +1 -1
  248. package/dist/content/index.mjs +12 -3567
  249. package/dist/content/index.mjs.map +1 -1
  250. package/dist/hooks/index.js +37 -2321
  251. package/dist/hooks/index.js.map +1 -1
  252. package/dist/hooks/index.mjs +6 -2318
  253. package/dist/hooks/index.mjs.map +1 -1
  254. package/dist/index.d.mts +1 -1
  255. package/dist/index.d.ts +1 -1
  256. package/dist/index.js +4145 -20599
  257. package/dist/index.js.map +1 -1
  258. package/dist/index.mjs +3766 -20567
  259. package/dist/index.mjs.map +1 -1
  260. package/dist/product/index.js +20 -393
  261. package/dist/product/index.js.map +1 -1
  262. package/dist/product/index.mjs +5 -394
  263. package/dist/product/index.mjs.map +1 -1
  264. package/dist/seo/index.js +8 -76
  265. package/dist/seo/index.js.map +1 -1
  266. package/dist/seo/index.mjs +3 -78
  267. package/dist/seo/index.mjs.map +1 -1
  268. package/dist/store/index.js +51 -2650
  269. package/dist/store/index.js.map +1 -1
  270. package/dist/store/index.mjs +13 -2619
  271. package/dist/store/index.mjs.map +1 -1
  272. package/dist/utils/index.js +69 -134
  273. package/dist/utils/index.js.map +1 -1
  274. package/dist/utils/index.mjs +5 -122
  275. package/dist/utils/index.mjs.map +1 -1
  276. package/package.json +2 -1
@@ -1,3569 +1,14 @@
1
- import * as React from 'react';
2
- import { createContext, useState, useCallback, useEffect, useMemo, useContext } from 'react';
3
- import DOMPurify from 'isomorphic-dompurify';
4
- import ReactMarkdown from 'react-markdown';
5
- import rehypeSanitize from 'rehype-sanitize';
6
- import remarkGfm from 'remark-gfm';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { Helmet } from 'react-helmet-async';
9
- import { History, Search, ChevronLeft, ChevronRight } from 'lucide-react';
10
- import { Slot } from '@radix-ui/react-slot';
11
- import { cva } from 'class-variance-authority';
12
- import { clsx } from 'clsx';
13
- import { twMerge } from 'tailwind-merge';
14
-
15
- var __defProp = Object.defineProperty;
16
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
17
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
18
- function MarkdownLink({
19
- href,
20
- children,
21
- // react-markdown passes the hast node to every override; it is not a DOM attribute.
22
- node: _node,
23
- ...props
24
- }) {
25
- const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
26
- return /* @__PURE__ */ jsx(
27
- "a",
28
- {
29
- href,
30
- ...props,
31
- ...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
32
- children
33
- }
34
- );
35
- }
36
- var defaultComponents = {
37
- a: MarkdownLink
38
- };
39
- function Markdown({
40
- children,
41
- className,
42
- inline,
43
- components,
44
- rehypePlugins
45
- }) {
46
- const content = /* @__PURE__ */ jsx(
47
- ReactMarkdown,
48
- {
49
- remarkPlugins: [remarkGfm],
50
- rehypePlugins: [rehypeSanitize, ...rehypePlugins ?? []],
51
- components: { ...defaultComponents, ...components },
52
- ...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
53
- children
54
- }
55
- );
56
- if (inline) {
57
- return /* @__PURE__ */ jsx("span", { className, children: content });
58
- }
59
- return /* @__PURE__ */ jsx("div", { className, children: content });
60
- }
61
-
62
- // src/client/security.ts
63
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
64
- var RESERVED_HEADERS = /* @__PURE__ */ new Set([
65
- "authorization",
66
- "cookie",
67
- "x-actor-id",
68
- "x-actor-type",
69
- "x-consent-granted-by",
70
- "x-consent-scope",
71
- "x-consent-granted-at",
72
- "x-consent-expires-at"
73
- ]);
74
- function validateGatewayUrl(value) {
75
- let url;
76
- try {
77
- url = new URL(value);
78
- } catch {
79
- throw new Error("Gateway URL must be a valid absolute URL");
80
- }
81
- if (url.protocol === "https:") {
82
- return url.toString();
83
- }
84
- if (url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase())) {
85
- return url.toString();
86
- }
87
- throw new Error("Gateway URL must use HTTPS unless it targets localhost");
88
- }
89
- function sanitizeHeaders(headers) {
90
- if (!headers) return void 0;
91
- return Object.fromEntries(
92
- Object.entries(headers).filter(
93
- ([key]) => !RESERVED_HEADERS.has(key.toLowerCase())
94
- )
95
- );
96
- }
97
- var CF_ACCESS_GATED_HOSTS = /* @__PURE__ */ new Set([
98
- "alphagraphql.burdenoff.com",
99
- "alphagraphqlworkspaces.burdenoff.com"
100
- ]);
101
- function cfAccessHeaders(gatewayUrl, cfAccess) {
102
- const clientId = cfAccess?.clientId?.trim();
103
- const clientSecret = cfAccess?.clientSecret?.trim();
104
- if (!clientId || !clientSecret) return {};
105
- let host;
106
- try {
107
- host = new URL(gatewayUrl).hostname.toLowerCase();
108
- } catch {
109
- return {};
110
- }
111
- if (!CF_ACCESS_GATED_HOSTS.has(host)) return {};
112
- return {
113
- "CF-Access-Client-Id": clientId,
114
- "CF-Access-Client-Secret": clientSecret
115
- };
116
- }
117
-
118
- // src/client/graphql-client.ts
119
- var _WebSDKClient = class _WebSDKClient {
120
- constructor(config) {
121
- __publicField(this, "config");
122
- __publicField(this, "queryCache", /* @__PURE__ */ new Map());
123
- __publicField(this, "inflight", /* @__PURE__ */ new Map());
124
- this.config = {
125
- ...config,
126
- gatewayUrl: validateGatewayUrl(config.gatewayUrl),
127
- headers: sanitizeHeaders(config.headers)
128
- };
129
- }
130
- getConfig() {
131
- return this.config;
132
- }
133
- /**
134
- * Execute a GraphQL query (with deduplication and caching).
135
- *
136
- * Pass `{ cache: false }` to bypass the cache entirely — required for poll
137
- * loops, where every tick must reach the network.
138
- */
139
- async query(query, variables, options) {
140
- if (options?.cache === false) {
141
- return this.request(query, variables, true, options.timeoutMs);
142
- }
143
- const cacheKey = JSON.stringify({ query, variables });
144
- const cached = this.queryCache.get(cacheKey);
145
- if (cached) {
146
- if (Date.now() - cached.ts < _WebSDKClient.CACHE_TTL) {
147
- return cached.data;
148
- }
149
- this.queryCache.delete(cacheKey);
150
- }
151
- const existing = this.inflight.get(cacheKey);
152
- if (existing) {
153
- return existing;
154
- }
155
- const promise = this.request(query, variables, true, options?.timeoutMs).then((result) => {
156
- if (result.data && !result.errors?.length) {
157
- if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
158
- const oldestEntry = this.queryCache.keys().next().value;
159
- if (oldestEntry) {
160
- this.queryCache.delete(oldestEntry);
161
- }
162
- }
163
- this.queryCache.set(cacheKey, { data: result, ts: Date.now() });
164
- }
165
- return result;
166
- }).finally(() => {
167
- this.inflight.delete(cacheKey);
168
- });
169
- this.inflight.set(cacheKey, promise);
170
- return promise;
171
- }
172
- /**
173
- * Execute a GraphQL mutation
174
- */
175
- async mutate(mutation, variables, options) {
176
- return this.request(mutation, variables, false, options?.timeoutMs);
177
- }
178
- /** HTTP statuses worth another attempt — transient server/edge conditions. */
179
- static isRetriableStatus(status) {
180
- return status === 429 || status === 408 || status >= 500 && status < 600;
181
- }
182
- static sleep(ms) {
183
- return new Promise((resolve) => setTimeout(resolve, ms));
184
- }
185
- /**
186
- * Perform the HTTP call with a hard timeout. Every outcome resolves to a
187
- * GraphQLResponse — callers never hang, so a UI's `loading` state always
188
- * settles into data or a renderable error.
189
- */
190
- async attempt(query, variables, timeoutMs) {
191
- const controller = new AbortController();
192
- const timer = setTimeout(() => controller.abort(), timeoutMs);
193
- try {
194
- const response = await fetch(this.config.gatewayUrl, {
195
- method: "POST",
196
- headers: {
197
- "Content-Type": "application/json",
198
- "x-product-id": this.config.productId,
199
- ...this.config.headers,
200
- ...cfAccessHeaders(this.config.gatewayUrl, this.config.cfAccess)
201
- },
202
- body: JSON.stringify({ query, variables }),
203
- signal: controller.signal
204
- });
205
- if (!response.ok) {
206
- console.error(
207
- "[WebSDK] GraphQL request failed",
208
- response.status,
209
- response.statusText
210
- );
211
- return {
212
- res: {
213
- errors: [
214
- {
215
- message: `HTTP ${response.status}: ${response.statusText}`,
216
- extensions: { code: `HTTP_${response.status}` }
217
- }
218
- ]
219
- },
220
- retriable: _WebSDKClient.isRetriableStatus(response.status)
221
- };
222
- }
223
- return {
224
- res: await response.json(),
225
- retriable: false
226
- };
227
- } catch (error) {
228
- const aborted = error instanceof Error && (error.name === "AbortError" || controller.signal.aborted);
229
- const message = aborted ? `Request timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : "Network error";
230
- console.error("[WebSDK] Network error", message);
231
- return {
232
- res: {
233
- errors: [
234
- {
235
- message,
236
- extensions: { code: aborted ? "TIMEOUT" : "NETWORK_ERROR" }
237
- }
238
- ]
239
- },
240
- // Transport-level failures are worth one more try; a genuine outage
241
- // just fails twice quickly and still renders an error state.
242
- retriable: true
243
- };
244
- } finally {
245
- clearTimeout(timer);
246
- }
247
- }
248
- async request(query, variables, retry = true, timeoutOverrideMs) {
249
- const timeoutMs = timeoutOverrideMs ?? this.config.timeoutMs ?? _WebSDKClient.DEFAULT_TIMEOUT_MS;
250
- const maxRetries = retry ? this.config.maxRetries ?? _WebSDKClient.DEFAULT_MAX_RETRIES : 0;
251
- let last;
252
- for (let i = 0; i <= maxRetries; i++) {
253
- const { res, retriable } = await this.attempt(
254
- query,
255
- variables,
256
- timeoutMs
257
- );
258
- if (!retriable || i === maxRetries) return res;
259
- last = res;
260
- await _WebSDKClient.sleep(
261
- _WebSDKClient.RETRY_BASE_DELAY_MS * (i + 1) + Math.random() * 200
262
- );
263
- }
264
- return last;
265
- }
266
- };
267
- __publicField(_WebSDKClient, "CACHE_TTL", 5 * 60 * 1e3);
268
- // 5 minutes
269
- __publicField(_WebSDKClient, "MAX_CACHE_ENTRIES", 100);
270
- __publicField(_WebSDKClient, "DEFAULT_TIMEOUT_MS", 12e3);
271
- __publicField(_WebSDKClient, "DEFAULT_MAX_RETRIES", 1);
272
- __publicField(_WebSDKClient, "RETRY_BASE_DELAY_MS", 400);
273
- var WebSDKContext = createContext(null);
274
- function useWebSDK() {
275
- const client = useContext(WebSDKContext);
276
- if (!client) {
277
- throw new Error("useWebSDK must be used within a WebSDKProvider");
278
- }
279
- return client;
280
- }
281
- function useWebSDKConfig() {
282
- const client = useWebSDK();
283
- return client.getConfig();
284
- }
285
-
286
- // src/utils/product-slug.ts
287
- var PRODUCT_SLUG_RENAMES = Object.freeze(
288
- [Object.freeze({ legacy: "botlit", current: "botmanor" })]
289
- );
290
- Object.freeze(
291
- Object.fromEntries(PRODUCT_SLUG_RENAMES.map((r) => [r.legacy, r.current]))
292
- );
293
- var ProductContext = createContext({
294
- product: null,
295
- loading: true,
296
- error: null,
297
- hasProvider: false,
298
- refetch: () => {
299
- }
300
- });
301
- function useProduct() {
302
- return useContext(ProductContext);
303
- }
304
- var PUBLIC_CONTENT_PAGE_QUERY = `
305
- query PublicContentPage($slug: String!, $productId: String) {
306
- publicContentPage(slug: $slug, productId: $productId) {
307
- id
308
- title
309
- slug
310
- content
311
- contentFormat
312
- seoTitle
313
- seoDescription
314
- seoKeywords
315
- lastUpdated
316
- version
317
- excerpt
318
- thumbnail
319
- author
320
- publishedAt
321
- readingTime
322
- featured
323
- category
324
- }
325
- }
326
- `;
327
- var PUBLIC_RESOLVED_CONTENT_PAGE_QUERY = `
328
- query PublicResolvedContentPage($slug: String!, $productId: String!) {
329
- publicResolvedContentPage(slug: $slug, productId: $productId) {
330
- id
331
- title
332
- slug
333
- content
334
- contentFormat
335
- seoTitle
336
- seoDescription
337
- seoKeywords
338
- lastUpdated
339
- version
340
- excerpt
341
- thumbnail
342
- author
343
- publishedAt
344
- readingTime
345
- featured
346
- category
347
- }
348
- }
349
- `;
350
- var PUBLIC_CONTENT_PAGES_QUERY = `
351
- query PublicContentPages($productId: String!, $limit: Int, $offset: Int) {
352
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
353
- items {
354
- id
355
- title
356
- slug
357
- lastUpdated
358
- tags
359
- seoDescription
360
- excerpt
361
- thumbnail
362
- author
363
- publishedAt
364
- readingTime
365
- featured
366
- category
367
- }
368
- total
369
- hasMore
370
- }
371
- }
372
- `;
373
- function resolveContentPageGate(resolvedProductId, hasProvider, productLoading) {
374
- if (resolvedProductId) return "fetch";
375
- return hasProvider && productLoading ? "wait" : "unavailable";
376
- }
377
- function useContentPage(slug, options) {
378
- const client = useWebSDK();
379
- const config = useWebSDKConfig();
380
- const {
381
- product,
382
- loading: productLoading,
383
- error: productError,
384
- hasProvider
385
- } = useProduct();
386
- const resolvedProductId = product?.id || config.productId;
387
- const gate = resolveContentPageGate(
388
- resolvedProductId,
389
- hasProvider,
390
- productLoading
391
- );
392
- const enabled = options?.enabled !== false;
393
- const [data, setData] = useState(null);
394
- const [loading, setLoading] = useState(enabled);
395
- const [error, setError] = useState(null);
396
- const fetchPage = useCallback(async () => {
397
- if (!enabled) {
398
- setLoading(false);
399
- return;
400
- }
401
- if (gate === "wait") return;
402
- if (gate === "unavailable") {
403
- setData(null);
404
- setError(
405
- productError ?? "Product unavailable \u2014 cannot load content page"
406
- );
407
- setLoading(false);
408
- return;
409
- }
410
- setLoading(true);
411
- setError(null);
412
- try {
413
- const query = options?.resolve ? PUBLIC_RESOLVED_CONTENT_PAGE_QUERY : PUBLIC_CONTENT_PAGE_QUERY;
414
- const queryName = options?.resolve ? "publicResolvedContentPage" : "publicContentPage";
415
- const result = await client.query(
416
- query,
417
- {
418
- slug,
419
- productId: resolvedProductId
420
- }
421
- );
422
- if (result.errors?.length) {
423
- setError(result.errors[0]?.message ?? "Unknown error");
424
- setData(null);
425
- } else {
426
- setData(result.data?.[queryName] ?? null);
427
- }
428
- } catch (err) {
429
- setError(err instanceof Error ? err.message : "Failed to fetch content");
430
- setData(null);
431
- } finally {
432
- setLoading(false);
433
- }
434
- }, [
435
- client,
436
- enabled,
437
- gate,
438
- productError,
439
- resolvedProductId,
440
- slug,
441
- options?.resolve
442
- ]);
443
- useEffect(() => {
444
- fetchPage();
445
- }, [fetchPage]);
446
- return { data, loading, error, refetch: fetchPage };
447
- }
448
- function useContentPages(options) {
449
- const client = useWebSDK();
450
- const config = useWebSDKConfig();
451
- const [data, setData] = useState(null);
452
- const [loading, setLoading] = useState(true);
453
- const [error, setError] = useState(null);
454
- const fetchPages = useCallback(async () => {
455
- setLoading(true);
456
- setError(null);
457
- try {
458
- const result = await client.query(PUBLIC_CONTENT_PAGES_QUERY, {
459
- productId: config.productId,
460
- limit: options?.limit ?? 50,
461
- offset: options?.offset ?? 0
462
- });
463
- if (result.errors?.length) {
464
- setError(result.errors[0]?.message ?? "Unknown error");
465
- setData(null);
466
- } else {
467
- setData(result.data?.publicContentPages ?? null);
468
- }
469
- } catch (err) {
470
- setError(
471
- err instanceof Error ? err.message : "Failed to fetch content pages"
472
- );
473
- setData(null);
474
- } finally {
475
- setLoading(false);
476
- }
477
- }, [client, config.productId, options?.limit, options?.offset]);
478
- useEffect(() => {
479
- fetchPages();
480
- }, [fetchPages]);
481
- return { data, loading, error, refetch: fetchPages };
482
- }
483
- function ContentRenderer({
484
- content,
485
- format = "markdown",
486
- className
487
- }) {
488
- if (format === "markdown") {
489
- return /* @__PURE__ */ jsx(Markdown, { className, children: content });
490
- }
491
- if (format === "html") {
492
- const sanitized = DOMPurify.sanitize(content);
493
- return /* @__PURE__ */ jsx(
494
- "div",
495
- {
496
- className,
497
- dangerouslySetInnerHTML: { __html: sanitized }
498
- }
499
- );
500
- }
501
- return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("pre", { style: { whiteSpace: "pre-wrap", fontFamily: "inherit" }, children: content }) });
502
- }
503
- var PROSE_CSS = `
504
- .boff-prose {
505
- max-width: none;
506
- }
507
-
508
- .boff-prose h1 {
509
- font-size: 2.25rem;
510
- font-weight: 800;
511
- line-height: 1.2;
512
- margin-top: 0;
513
- margin-bottom: 1rem;
514
- color: hsl(var(--foreground));
515
- }
516
-
517
- .boff-prose h2 {
518
- font-size: 1.75rem;
519
- font-weight: 700;
520
- line-height: 1.3;
521
- margin-top: 2rem;
522
- margin-bottom: 1rem;
523
- color: hsl(var(--foreground));
524
- }
525
-
526
- .boff-prose h3 {
527
- font-size: 1.5rem;
528
- font-weight: 600;
529
- line-height: 1.4;
530
- margin-top: 1.5rem;
531
- margin-bottom: 0.75rem;
532
- color: hsl(var(--foreground));
533
- }
534
-
535
- .boff-prose h4 {
536
- font-size: 1.25rem;
537
- font-weight: 600;
538
- line-height: 1.5;
539
- margin-top: 1.25rem;
540
- margin-bottom: 0.5rem;
541
- color: hsl(var(--foreground));
542
- }
543
-
544
- .boff-prose p {
545
- margin-bottom: 1rem;
546
- line-height: 1.75;
547
- color: hsl(var(--foreground));
548
- }
549
-
550
- .boff-prose ul,
551
- .boff-prose ol {
552
- margin-bottom: 1rem;
553
- padding-left: 1.5rem;
554
- }
555
-
556
- .boff-prose ul {
557
- list-style-type: disc;
558
- }
559
-
560
- .boff-prose ol {
561
- list-style-type: decimal;
562
- }
563
-
564
- .boff-prose li {
565
- margin-bottom: 0.25rem;
566
- line-height: 1.75;
567
- color: hsl(var(--foreground));
568
- }
569
-
570
- .boff-prose li > ul,
571
- .boff-prose li > ol {
572
- margin-top: 0.25rem;
573
- margin-bottom: 0.25rem;
574
- }
575
-
576
- .boff-prose a {
577
- color: hsl(var(--primary));
578
- text-decoration: underline;
579
- text-underline-offset: 2px;
580
- }
581
-
582
- .boff-prose a:hover {
583
- opacity: 0.8;
584
- }
585
-
586
- .boff-prose hr {
587
- border: none;
588
- height: 1px;
589
- background-color: hsl(var(--border));
590
- margin: 2rem 0;
591
- }
592
-
593
- .boff-prose blockquote {
594
- border-left: 4px solid hsl(var(--border));
595
- padding-left: 1rem;
596
- margin: 1.5rem 0;
597
- font-style: italic;
598
- color: hsl(var(--muted-foreground));
599
- }
600
-
601
- .boff-prose strong {
602
- font-weight: 700;
603
- color: hsl(var(--foreground));
604
- }
605
-
606
- .boff-prose code {
607
- background-color: hsl(var(--muted));
608
- padding: 0.125rem 0.25rem;
609
- border-radius: 0.25rem;
610
- font-size: 0.875em;
611
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
612
- }
613
-
614
- .boff-prose pre {
615
- background-color: hsl(var(--muted));
616
- padding: 1rem;
617
- border-radius: 0.5rem;
618
- overflow-x: auto;
619
- margin-bottom: 1rem;
620
- }
621
-
622
- .boff-prose pre code {
623
- background-color: transparent;
624
- padding: 0;
625
- }
626
-
627
- .boff-prose img {
628
- max-width: 100%;
629
- height: auto;
630
- }
631
-
632
- .boff-prose table {
633
- width: 100%;
634
- border-collapse: collapse;
635
- margin-bottom: 1rem;
636
- }
637
-
638
- .boff-prose th,
639
- .boff-prose td {
640
- border: 1px solid hsl(var(--border));
641
- padding: 0.5rem;
642
- text-align: left;
643
- }
644
-
645
- .boff-prose th {
646
- font-weight: 600;
647
- background-color: hsl(var(--muted));
648
- }
649
-
650
- /* Smaller variant used inside cards and compact panels */
651
- .boff-prose-sm {
652
- font-size: 0.875rem;
653
- }
654
-
655
- .boff-prose-sm h1 {
656
- font-size: 1.75rem;
657
- }
658
-
659
- .boff-prose-sm h2 {
660
- font-size: 1.5rem;
661
- }
662
-
663
- .boff-prose-sm h3 {
664
- font-size: 1.25rem;
665
- }
666
-
667
- .boff-prose-sm h4 {
668
- font-size: 1.125rem;
669
- }
670
-
671
- .boff-prose-sm p,
672
- .boff-prose-sm li {
673
- line-height: 1.625;
674
- }
675
- `;
676
- function ProseStyles() {
677
- return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: PROSE_CSS } });
678
- }
679
- function canonicalFromLocation() {
680
- if (typeof window === "undefined" || !window.location) return void 0;
681
- const { origin, pathname } = window.location;
682
- if (!origin || origin === "null") return void 0;
683
- const path = pathname === "/" ? "/" : pathname.replace(/\/+$/, "");
684
- return `${origin}${path}`;
685
- }
686
- function resolveImageUrl(image, base) {
687
- if (!image || /^https?:\/\//i.test(image)) return image;
688
- if (!base) return image;
689
- try {
690
- return new URL(image, base).toString();
691
- } catch {
692
- return image;
693
- }
694
- }
695
- function serializeJsonLd(data) {
696
- return JSON.stringify(data).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
697
- }
698
- function PageHead({
699
- title,
700
- description,
701
- productName,
702
- keywords,
703
- image,
704
- url,
705
- favicon,
706
- robots = "index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1",
707
- locale = "en_US",
708
- twitterHandle,
709
- themeColor = "#000000",
710
- author,
711
- structuredData,
712
- additionalMeta
713
- }) {
714
- const fullTitle = productName && !title.includes(productName) ? `${title} | ${productName}` : title;
715
- const resolvedUrl = url ?? canonicalFromLocation();
716
- const fullImageUrl = resolveImageUrl(image, resolvedUrl);
717
- const structuredDataItems = structuredData ? Array.isArray(structuredData) ? structuredData : [structuredData] : [];
718
- return /* @__PURE__ */ jsxs(Helmet, { children: [
719
- /* @__PURE__ */ jsx("title", { children: fullTitle }),
720
- /* @__PURE__ */ jsx("meta", { name: "title", content: fullTitle }),
721
- description ? /* @__PURE__ */ jsx("meta", { name: "description", content: description }) : null,
722
- keywords ? /* @__PURE__ */ jsx("meta", { name: "keywords", content: keywords }) : null,
723
- author ? /* @__PURE__ */ jsx("meta", { name: "author", content: author }) : null,
724
- /* @__PURE__ */ jsx("meta", { name: "robots", content: robots }),
725
- resolvedUrl ? /* @__PURE__ */ jsx("link", { rel: "canonical", href: resolvedUrl }) : null,
726
- favicon ? /* @__PURE__ */ jsx("link", { rel: "icon", href: favicon }) : null,
727
- /* @__PURE__ */ jsx("meta", { property: "og:type", content: "website" }),
728
- /* @__PURE__ */ jsx("meta", { property: "og:title", content: fullTitle }),
729
- description ? /* @__PURE__ */ jsx("meta", { property: "og:description", content: description }) : null,
730
- fullImageUrl ? /* @__PURE__ */ jsx("meta", { property: "og:image", content: fullImageUrl }) : null,
731
- resolvedUrl ? /* @__PURE__ */ jsx("meta", { property: "og:url", content: resolvedUrl }) : null,
732
- /* @__PURE__ */ jsx("meta", { property: "og:locale", content: locale }),
733
- productName ? /* @__PURE__ */ jsx("meta", { property: "og:site_name", content: productName }) : null,
734
- /* @__PURE__ */ jsx("meta", { name: "twitter:card", content: "summary_large_image" }),
735
- /* @__PURE__ */ jsx("meta", { name: "twitter:title", content: fullTitle }),
736
- description ? /* @__PURE__ */ jsx("meta", { name: "twitter:description", content: description }) : null,
737
- fullImageUrl ? /* @__PURE__ */ jsx("meta", { name: "twitter:image", content: fullImageUrl }) : null,
738
- twitterHandle ? /* @__PURE__ */ jsx("meta", { name: "twitter:creator", content: twitterHandle }) : null,
739
- /* @__PURE__ */ jsx("meta", { name: "theme-color", content: themeColor }),
740
- structuredDataItems.map((data, index) => /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: serializeJsonLd(data) }, `ld-${index}`)),
741
- additionalMeta?.map((meta, index) => /* @__PURE__ */ jsx(
742
- "meta",
743
- {
744
- ...meta.name ? { name: meta.name } : {},
745
- ...meta.property ? { property: meta.property } : {},
746
- content: meta.content
747
- },
748
- `meta-${index}`
749
- ))
750
- ] });
751
- }
752
- var LEGAL_DOCUMENTS = {
753
- privacy: {
754
- key: "privacy",
755
- slug: "privacy-policy",
756
- title: "Privacy Policy",
757
- description: "How we collect, use, share and protect your personal information."
758
- },
759
- terms: {
760
- key: "terms",
761
- slug: "terms-of-service",
762
- title: "Terms of Service",
763
- description: "The terms that govern your use of the service."
764
- },
765
- cookies: {
766
- key: "cookies",
767
- slug: "cookie-policy",
768
- title: "Cookie Policy",
769
- description: "The cookies and similar technologies we use, and why."
770
- },
771
- refunds: {
772
- key: "refunds",
773
- slug: "refund-policy",
774
- title: "Refund Policy",
775
- description: "How cancellations and refunds work for paid plans."
776
- },
777
- aup: {
778
- key: "aup",
779
- slug: "acceptable-use-policy",
780
- title: "Acceptable Use Policy",
781
- description: "What you may and may not do with the service."
782
- },
783
- dpa: {
784
- key: "dpa",
785
- slug: "data-processing-addendum",
786
- title: "Data Processing Addendum",
787
- description: "Our obligations as a processor of personal data on your behalf."
788
- },
789
- "ai-terms": {
790
- key: "ai-terms",
791
- slug: "ai-product-terms",
792
- title: "AI Product Terms",
793
- description: "The terms that apply to AI features, including how your data is and is not used."
794
- },
795
- subprocessors: {
796
- key: "subprocessors",
797
- slug: "subprocessors",
798
- title: "Subprocessors",
799
- description: "The third parties that help us host, secure and support the service."
800
- }
801
- };
802
- var LEGAL_DOCUMENT_KEYS = Object.keys(
803
- LEGAL_DOCUMENTS
804
- );
805
- function findLegalDocument(keyOrSlug) {
806
- if (Object.prototype.hasOwnProperty.call(LEGAL_DOCUMENTS, keyOrSlug)) {
807
- return LEGAL_DOCUMENTS[keyOrSlug];
808
- }
809
- return Object.values(LEGAL_DOCUMENTS).find((doc) => doc.slug === keyOrSlug) ?? null;
810
- }
811
- var LEGAL_CONTENT_QUERY = `
812
- query PublicLegalContentPage($slug: String!, $productId: String!) {
813
- publicResolvedContentPage(slug: $slug, productId: $productId) {
814
- title
815
- slug
816
- content
817
- contentFormat
818
- seoTitle
819
- seoDescription
820
- seoKeywords
821
- lastUpdated
822
- }
823
- }
824
- `;
825
- async function fetchLegalContentPage(client, slug, productId) {
826
- const result = await client.query(LEGAL_CONTENT_QUERY, { slug, productId }, { cache: false });
827
- if (result.errors?.length) {
828
- throw new Error(result.errors[0]?.message ?? "Unknown error");
829
- }
830
- return result.data?.publicResolvedContentPage ?? null;
831
- }
832
- async function fetchLegalContent(client, options) {
833
- const docs = (options.documents ?? LEGAL_DOCUMENT_KEYS).map(
834
- (key) => LEGAL_DOCUMENTS[key]
835
- );
836
- const entries = await Promise.all(
837
- docs.map(
838
- async (doc) => [
839
- doc.slug,
840
- await fetchLegalContentPage(client, doc.slug, options.productId)
841
- ]
842
- )
843
- );
844
- return Object.fromEntries(entries);
845
- }
846
- function splitLeadingHeading(content, format) {
847
- if (format.toLowerCase() !== "markdown")
848
- return { heading: null, body: content };
849
- const match = /^\s*#[ \t]+([^\n]+?)[ \t#]*(?:\n|$)/.exec(content);
850
- if (!match?.[1]) return { heading: null, body: content };
851
- return {
852
- heading: match[1].trim(),
853
- body: content.slice(match[0].length).replace(/^\s+/, "")
854
- };
855
- }
856
- function formatLegalDate(iso) {
857
- if (!iso) return null;
858
- const date = new Date(iso);
859
- if (Number.isNaN(date.getTime())) return null;
860
- return date.toLocaleDateString("en-US", {
861
- year: "numeric",
862
- month: "long",
863
- day: "numeric",
864
- timeZone: "UTC"
865
- });
866
- }
867
- function LegalPage(props) {
868
- const {
869
- document: document2,
870
- slug: slugProp,
871
- initialContent,
872
- fallbackTitle,
873
- fallbackDescription,
874
- productName,
875
- canonicalUrl,
876
- contactEmail,
877
- className = ""
878
- } = props;
879
- const registered = document2 ? LEGAL_DOCUMENTS[document2] : slugProp ? findLegalDocument(slugProp) : null;
880
- const slug = registered?.slug ?? slugProp ?? "";
881
- const prefetched = initialContent !== void 0;
882
- const { data: fetched, loading } = useContentPage(slug, {
883
- resolve: true,
884
- enabled: !prefetched && slug !== ""
885
- });
886
- const page = prefetched ? initialContent ?? null : fetched;
887
- const isLoading = !prefetched && slug !== "" && loading;
888
- const defaultTitle = fallbackTitle ?? registered?.title ?? "Legal";
889
- const defaultDescription = fallbackDescription ?? registered?.description ?? "";
890
- const { heading, body } = page ? splitLeadingHeading(page.content, page.contentFormat) : { heading: null, body: "" };
891
- const title = page?.title || heading || defaultTitle;
892
- const updated = formatLegalDate(page?.lastUpdated);
893
- const unavailable = !isLoading && !page;
894
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
895
- /* @__PURE__ */ jsx(ProseStyles, {}),
896
- /* @__PURE__ */ jsx(
897
- PageHead,
898
- {
899
- title: page?.seoTitle || title,
900
- description: page?.seoDescription?.trim() || defaultDescription,
901
- keywords: page?.seoKeywords ?? void 0,
902
- productName,
903
- url: canonicalUrl,
904
- robots: unavailable ? "noindex, follow" : void 0
905
- }
906
- ),
907
- /* @__PURE__ */ jsxs(
908
- "article",
909
- {
910
- className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-24",
911
- "aria-busy": isLoading,
912
- children: [
913
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl font-bold tracking-tight text-foreground mb-3", children: title }),
914
- updated && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-10", children: [
915
- "Last updated:",
916
- " ",
917
- /* @__PURE__ */ jsx("time", { dateTime: page?.lastUpdated ?? void 0, children: updated })
918
- ] }),
919
- isLoading ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "legal-page-loading", children: [
920
- /* @__PURE__ */ jsx("div", { className: "h-4 w-3/4 rounded bg-muted animate-pulse" }),
921
- /* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded bg-muted animate-pulse" }),
922
- /* @__PURE__ */ jsx("div", { className: "h-4 w-5/6 rounded bg-muted animate-pulse" })
923
- ] }) : page ? /* @__PURE__ */ jsx("div", { className: "boff-prose max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content: body, format: page.contentFormat }) }) : /* @__PURE__ */ jsxs(
924
- "div",
925
- {
926
- role: "status",
927
- className: "rounded-xl border border-border bg-muted/30 p-6",
928
- children: [
929
- /* @__PURE__ */ jsx("p", { className: "text-base text-foreground", children: "This document is not available right now." }),
930
- contactEmail && /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm text-muted-foreground", children: [
931
- "For a copy, contact",
932
- " ",
933
- /* @__PURE__ */ jsx(
934
- "a",
935
- {
936
- className: "text-primary underline",
937
- href: `mailto:${contactEmail}`,
938
- children: contactEmail
939
- }
940
- ),
941
- "."
942
- ] })
943
- ]
944
- }
945
- )
946
- ]
947
- }
948
- )
949
- ] });
950
- }
951
- var PUBLIC_JOB_POSTING_FIELDS = (
952
- /* GraphQL */
953
- `
954
- id
955
- slug
956
- title
957
- department
958
- location
959
- employmentType
960
- openings
961
- description
962
- overview
963
- responsibilities
964
- learnings
965
- qualifications
966
- rounds {
967
- roundNumber
968
- name
969
- roundType
970
- isRequired
971
- description
972
- }
973
- `
974
- );
975
- var PUBLIC_JOB_POSTINGS_QUERY = (
976
- /* GraphQL */
977
- `
978
- query PublicJobPostings($productId: ID, $limit: Int, $offset: Int) {
979
- publicJobPostings(productId: $productId, limit: $limit, offset: $offset) {
980
- ${PUBLIC_JOB_POSTING_FIELDS}
981
- }
982
- }
983
- `
984
- );
985
- var PUBLIC_JOB_POSTING_QUERY = (
986
- /* GraphQL */
987
- `
988
- query PublicJobPosting($slug: String!, $productId: ID) {
989
- publicJobPosting(slug: $slug, productId: $productId) {
990
- ${PUBLIC_JOB_POSTING_FIELDS}
991
- }
992
- }
993
- `
994
- );
995
- var PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY = (
996
- /* GraphQL */
997
- `
998
- query PublicJobPostingsForSubmission(
999
- $productId: ID
1000
- $limit: Int
1001
- $offset: Int
1002
- ) {
1003
- publicJobPostingsForSubmission(
1004
- productId: $productId
1005
- limit: $limit
1006
- offset: $offset
1007
- ) {
1008
- id
1009
- title
1010
- }
1011
- }
1012
- `
1013
- );
1014
- async function fetchPublicJobPostings(client, options) {
1015
- const result = await client.query(PUBLIC_JOB_POSTINGS_QUERY, {
1016
- productId: options?.productId,
1017
- limit: options?.limit ?? 50,
1018
- offset: options?.offset ?? 0
1019
- });
1020
- if (result.errors?.length) {
1021
- throw new Error(result.errors[0]?.message ?? "Unknown error");
1022
- }
1023
- return result.data?.publicJobPostings ?? [];
1024
- }
1025
- async function fetchPublicJobPosting(client, slug, options) {
1026
- const result = await client.query(PUBLIC_JOB_POSTING_QUERY, {
1027
- slug,
1028
- productId: options?.productId
1029
- });
1030
- if (result.errors?.length) {
1031
- throw new Error(result.errors[0]?.message ?? "Unknown error");
1032
- }
1033
- return result.data?.publicJobPosting ?? null;
1034
- }
1035
- async function fetchPublicJobPostingsForSubmission(client, options) {
1036
- const result = await client.query(PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY, {
1037
- productId: options?.productId,
1038
- limit: options?.limit ?? 50,
1039
- offset: options?.offset ?? 0
1040
- });
1041
- if (result.errors?.length) {
1042
- throw new Error(result.errors[0]?.message ?? "Unknown error");
1043
- }
1044
- return result.data?.publicJobPostingsForSubmission ?? [];
1045
- }
1046
- function usePublicJobPostings(options) {
1047
- const client = useWebSDK();
1048
- const config = useWebSDKConfig();
1049
- const { product } = useProduct();
1050
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
1051
- const [postings, setPostings] = useState([]);
1052
- const [loading, setLoading] = useState(true);
1053
- const [error, setError] = useState(null);
1054
- const fetchPostings = useCallback(async () => {
1055
- setLoading(true);
1056
- setError(null);
1057
- try {
1058
- const items = await fetchPublicJobPostings(client, {
1059
- productId: resolvedProductId,
1060
- limit: options?.limit,
1061
- offset: options?.offset
1062
- });
1063
- setPostings(items);
1064
- } catch (err) {
1065
- setError(
1066
- err instanceof Error ? err.message : "Failed to fetch job postings"
1067
- );
1068
- setPostings([]);
1069
- } finally {
1070
- setLoading(false);
1071
- }
1072
- }, [client, resolvedProductId, options?.limit, options?.offset]);
1073
- useEffect(() => {
1074
- fetchPostings();
1075
- }, [fetchPostings]);
1076
- return { postings, loading, error, refetch: fetchPostings };
1077
- }
1078
- function usePublicJobPostingsForSubmission(options) {
1079
- const client = useWebSDK();
1080
- const config = useWebSDKConfig();
1081
- const { product } = useProduct();
1082
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
1083
- const [postings, setPostings] = useState([]);
1084
- const [loading, setLoading] = useState(true);
1085
- const [error, setError] = useState(null);
1086
- const fetchPostings = useCallback(async () => {
1087
- setLoading(true);
1088
- setError(null);
1089
- try {
1090
- const items = await fetchPublicJobPostingsForSubmission(client, {
1091
- productId: resolvedProductId,
1092
- limit: options?.limit,
1093
- offset: options?.offset
1094
- });
1095
- setPostings(items);
1096
- } catch (err) {
1097
- setError(
1098
- err instanceof Error ? err.message : "Failed to fetch job postings"
1099
- );
1100
- setPostings([]);
1101
- } finally {
1102
- setLoading(false);
1103
- }
1104
- }, [client, resolvedProductId, options?.limit, options?.offset]);
1105
- useEffect(() => {
1106
- fetchPostings();
1107
- }, [fetchPostings]);
1108
- return { postings, loading, error, refetch: fetchPostings };
1109
- }
1110
- function usePublicJobPosting(slug, options) {
1111
- const client = useWebSDK();
1112
- const config = useWebSDKConfig();
1113
- const { product } = useProduct();
1114
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
1115
- const [posting, setPosting] = useState(null);
1116
- const [loading, setLoading] = useState(true);
1117
- const [error, setError] = useState(null);
1118
- const fetchPosting = useCallback(async () => {
1119
- if (!slug) {
1120
- setPosting(null);
1121
- setLoading(false);
1122
- return;
1123
- }
1124
- setLoading(true);
1125
- setError(null);
1126
- try {
1127
- const item = await fetchPublicJobPosting(client, slug, {
1128
- productId: resolvedProductId
1129
- });
1130
- setPosting(item);
1131
- } catch (err) {
1132
- setError(
1133
- err instanceof Error ? err.message : "Failed to fetch job posting"
1134
- );
1135
- setPosting(null);
1136
- } finally {
1137
- setLoading(false);
1138
- }
1139
- }, [client, resolvedProductId, slug]);
1140
- useEffect(() => {
1141
- fetchPosting();
1142
- }, [fetchPosting]);
1143
- return { posting, loading, error, refetch: fetchPosting };
1144
- }
1145
- var BLOG_POSTS_QUERY = `
1146
- query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
1147
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
1148
- items {
1149
- id
1150
- title
1151
- slug
1152
- lastUpdated
1153
- tags
1154
- seoDescription
1155
- excerpt
1156
- thumbnail
1157
- author
1158
- publishedAt
1159
- readingTime
1160
- featured
1161
- category
1162
- }
1163
- total
1164
- hasMore
1165
- }
1166
- }
1167
- `;
1168
- var BLOG_POST_QUERY = `
1169
- query BlogPost($slug: String!, $productId: String) {
1170
- publicContentPage(slug: $slug, productId: $productId) {
1171
- id
1172
- title
1173
- slug
1174
- content
1175
- contentFormat
1176
- seoTitle
1177
- seoDescription
1178
- seoKeywords
1179
- lastUpdated
1180
- tags
1181
- excerpt
1182
- thumbnail
1183
- author
1184
- publishedAt
1185
- readingTime
1186
- featured
1187
- category
1188
- }
1189
- }
1190
- `;
1191
- function BlogCard({
1192
- post,
1193
- basePath = "/blog"
1194
- }) {
1195
- const date = post.publishedAt || post.lastUpdated;
1196
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
1197
- year: "numeric",
1198
- month: "long",
1199
- day: "numeric"
1200
- }) : "";
1201
- const [imgError, setImgError] = useState(false);
1202
- return /* @__PURE__ */ jsxs(
1203
- "a",
1204
- {
1205
- href: `${basePath}/${post.slug}`,
1206
- className: "group block bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300",
1207
- children: [
1208
- post.thumbnail && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden", children: /* @__PURE__ */ jsx(
1209
- "img",
1210
- {
1211
- src: post.thumbnail,
1212
- alt: post.title,
1213
- className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
1214
- loading: "lazy",
1215
- onError: () => setImgError(true)
1216
- }
1217
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
1218
- "svg",
1219
- {
1220
- className: "w-12 h-12 text-muted-foreground/20",
1221
- fill: "none",
1222
- viewBox: "0 0 24 24",
1223
- stroke: "currentColor",
1224
- children: /* @__PURE__ */ jsx(
1225
- "path",
1226
- {
1227
- strokeLinecap: "round",
1228
- strokeLinejoin: "round",
1229
- strokeWidth: 1.5,
1230
- d: "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
1231
- }
1232
- )
1233
- }
1234
- ) }),
1235
- /* @__PURE__ */ jsxs("div", { className: "p-5 sm:p-6", children: [
1236
- post.category && /* @__PURE__ */ jsx("span", { className: "inline-block text-xs font-medium text-primary bg-primary/10 px-2.5 py-1 rounded-full mb-3", children: post.category }),
1237
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-2", children: post.title }),
1238
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4 line-clamp-3", children: post.excerpt || post.seoDescription || "" }),
1239
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1240
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: post.author }),
1241
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1242
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
1243
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
1244
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1245
- /* @__PURE__ */ jsxs("span", { children: [
1246
- post.readingTime,
1247
- " min read"
1248
- ] })
1249
- ] })
1250
- ] })
1251
- ] })
1252
- ]
1253
- }
1254
- );
1255
- }
1256
- function BlogListPage(props) {
1257
- const {
1258
- productName,
1259
- heroTitle = "Blog",
1260
- heroSubtitle = "Latest updates, engineering insights, and product news",
1261
- blogPostBasePath = "/blog",
1262
- className = "",
1263
- seoTitle,
1264
- seoDescription,
1265
- seoKeywords
1266
- } = props;
1267
- const client = useWebSDK();
1268
- const config = useWebSDKConfig();
1269
- const { product } = useProduct();
1270
- const resolvedProductId = product?.id || config.productId;
1271
- const [posts, setPosts] = useState([]);
1272
- const [total, setTotal] = useState(0);
1273
- const [loading, setLoading] = useState(true);
1274
- const [error, setError] = useState(null);
1275
- const [offset, setOffset] = useState(0);
1276
- const limit = 12;
1277
- const fetchPosts = useCallback(async () => {
1278
- if (!resolvedProductId) return;
1279
- setLoading(true);
1280
- try {
1281
- const result = await client.query(BLOG_POSTS_QUERY, {
1282
- productId: resolvedProductId,
1283
- limit,
1284
- offset
1285
- });
1286
- if (result.errors?.length) {
1287
- setError(result.errors[0]?.message ?? "Failed to load posts");
1288
- return;
1289
- }
1290
- const blogPosts = (result.data?.publicContentPages?.items || []).filter(
1291
- (p) => p.tags?.includes("blog")
1292
- );
1293
- setPosts(blogPosts);
1294
- setTotal(result.data?.publicContentPages?.total || 0);
1295
- setError(null);
1296
- } catch {
1297
- setError("Failed to load blog posts");
1298
- } finally {
1299
- setLoading(false);
1300
- }
1301
- }, [client, resolvedProductId, offset]);
1302
- useEffect(() => {
1303
- fetchPosts();
1304
- }, [fetchPosts]);
1305
- const featuredPosts = posts.filter((p) => p.featured);
1306
- const regularPosts = posts.filter((p) => !p.featured);
1307
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
1308
- seoTitle && /* @__PURE__ */ jsx(
1309
- PageHead,
1310
- {
1311
- title: seoTitle,
1312
- description: seoDescription || heroSubtitle,
1313
- productName,
1314
- keywords: seoKeywords,
1315
- url: typeof window !== "undefined" ? window.location.origin + window.location.pathname : void 0
1316
- }
1317
- ),
1318
- /* @__PURE__ */ jsx("section", { className: "py-16 sm:py-20 md:py-24", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-3xl mx-auto", children: [
1319
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground", children: heroTitle }),
1320
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle })
1321
- ] }) }) }),
1322
- /* @__PURE__ */ jsx("section", { className: "pb-16 sm:pb-20", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: loading ? /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs(
1323
- "div",
1324
- {
1325
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
1326
- children: [
1327
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
1328
- /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-3", children: [
1329
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
1330
- /* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded w-3/4" }),
1331
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }),
1332
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
1333
- ] })
1334
- ]
1335
- },
1336
- i
1337
- )) }) : error ? /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: error }) }) : posts.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No blog posts yet. Check back soon!" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1338
- featuredPosts.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 gap-6 sm:gap-8 mb-8", children: featuredPosts.map((post) => /* @__PURE__ */ jsx(
1339
- BlogCard,
1340
- {
1341
- post,
1342
- basePath: blogPostBasePath
1343
- },
1344
- post.id
1345
- )) }),
1346
- /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: regularPosts.map((post) => /* @__PURE__ */ jsx(
1347
- BlogCard,
1348
- {
1349
- post,
1350
- basePath: blogPostBasePath
1351
- },
1352
- post.id
1353
- )) }),
1354
- total > limit && /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-4 mt-12", children: [
1355
- /* @__PURE__ */ jsx(
1356
- "button",
1357
- {
1358
- onClick: () => setOffset(Math.max(0, offset - limit)),
1359
- disabled: offset === 0,
1360
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
1361
- children: "Previous"
1362
- }
1363
- ),
1364
- /* @__PURE__ */ jsx(
1365
- "button",
1366
- {
1367
- onClick: () => setOffset(offset + limit),
1368
- disabled: offset + limit >= total,
1369
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
1370
- children: "Next"
1371
- }
1372
- )
1373
- ] })
1374
- ] }) }) })
1375
- ] });
1376
- }
1377
- function BlogPostPage(props) {
1378
- const {
1379
- productName,
1380
- blogListUrl = "/blog",
1381
- backLabel = "Back to Blog",
1382
- className = ""
1383
- } = props;
1384
- const client = useWebSDK();
1385
- const config = useWebSDKConfig();
1386
- const { product } = useProduct();
1387
- const resolvedProductId = product?.id || config.productId;
1388
- const [post, setPost] = useState(null);
1389
- const [loading, setLoading] = useState(true);
1390
- const [error, setError] = useState(null);
1391
- const slug = typeof window !== "undefined" ? window.location.pathname.split("/").pop() || "" : "";
1392
- useEffect(() => {
1393
- if (!slug || !resolvedProductId) return;
1394
- async function fetchPost() {
1395
- setLoading(true);
1396
- try {
1397
- const result = await client.query(BLOG_POST_QUERY, {
1398
- slug,
1399
- productId: resolvedProductId
1400
- });
1401
- if (result.errors?.length) {
1402
- setError(result.errors[0]?.message ?? "Failed to load post");
1403
- return;
1404
- }
1405
- setPost(result.data?.publicContentPage ?? null);
1406
- setError(null);
1407
- } catch {
1408
- setError("Failed to load blog post");
1409
- } finally {
1410
- setLoading(false);
1411
- }
1412
- }
1413
- fetchPost();
1414
- }, [client, slug, resolvedProductId]);
1415
- if (loading) {
1416
- return /* @__PURE__ */ jsx("div", { className: `w-full ${className}`, children: /* @__PURE__ */ jsx("div", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-24", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse space-y-6", children: [
1417
- /* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded w-2/3" }),
1418
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/3" }),
1419
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted rounded-xl" }),
1420
- /* @__PURE__ */ jsx("div", { className: "space-y-3", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }, i)) })
1421
- ] }) }) });
1422
- }
1423
- if (error || !post) {
1424
- return /* @__PURE__ */ jsx("div", { className: `w-full ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-24 text-center", children: [
1425
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mb-4", children: "Post not found" }),
1426
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-6", children: error || "This blog post doesn't exist or has been removed." }),
1427
- /* @__PURE__ */ jsxs(
1428
- "a",
1429
- {
1430
- href: blogListUrl,
1431
- className: "text-primary hover:underline font-medium",
1432
- children: [
1433
- "\u2190 ",
1434
- backLabel
1435
- ]
1436
- }
1437
- )
1438
- ] }) });
1439
- }
1440
- const date = post.publishedAt || post.lastUpdated;
1441
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
1442
- year: "numeric",
1443
- month: "long",
1444
- day: "numeric"
1445
- }) : "";
1446
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
1447
- /* @__PURE__ */ jsx(ProseStyles, {}),
1448
- /* @__PURE__ */ jsx(
1449
- PageHead,
1450
- {
1451
- title: post.seoTitle || post.title,
1452
- description: post.seoDescription || post.excerpt || "",
1453
- productName,
1454
- keywords: post.seoKeywords || "",
1455
- image: post.thumbnail || void 0,
1456
- url: typeof window !== "undefined" ? window.location.origin + window.location.pathname : void 0
1457
- }
1458
- ),
1459
- /* @__PURE__ */ jsxs("article", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-20", children: [
1460
- /* @__PURE__ */ jsxs(
1461
- "a",
1462
- {
1463
- href: blogListUrl,
1464
- className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors",
1465
- children: [
1466
- "\u2190 ",
1467
- backLabel
1468
- ]
1469
- }
1470
- ),
1471
- post.category && /* @__PURE__ */ jsx("span", { className: "inline-block text-xs font-medium text-primary bg-primary/10 px-2.5 py-1 rounded-full mb-4", children: post.category }),
1472
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4", children: post.title }),
1473
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm text-muted-foreground mb-8", children: [
1474
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: post.author }),
1475
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1476
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
1477
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
1478
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1479
- /* @__PURE__ */ jsxs("span", { children: [
1480
- post.readingTime,
1481
- " min read"
1482
- ] })
1483
- ] })
1484
- ] }),
1485
- post.thumbnail && /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] rounded-xl overflow-hidden mb-10", children: /* @__PURE__ */ jsx(
1486
- "img",
1487
- {
1488
- src: post.thumbnail,
1489
- alt: post.title,
1490
- className: "w-full h-full object-cover"
1491
- }
1492
- ) }),
1493
- /* @__PURE__ */ jsx("div", { className: "boff-prose max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content: post.content, format: post.contentFormat }) })
1494
- ] })
1495
- ] });
1496
- }
1497
- var CASE_STUDY_TAG = "case-study";
1498
- function hasCustomerSignOff(page) {
1499
- const signedAt = page.customerSignOff?.signedAt;
1500
- return typeof signedAt === "string" && !Number.isNaN(new Date(signedAt).getTime());
1501
- }
1502
- var ANIMATION_STYLES = `
1503
- @keyframes csFadeUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
1504
- @keyframes csFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
1505
- .cs-fade-up { animation: csFadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
1506
- .cs-float { animation: csFloat 7s ease-in-out infinite; }
1507
- @media (prefers-reduced-motion: reduce) {
1508
- .cs-fade-up, .cs-float { animation: none !important; }
1509
- }
1510
- `;
1511
- function AnimationStyles() {
1512
- return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: ANIMATION_STYLES } });
1513
- }
1514
- var CASE_STUDIES_QUERY = `
1515
- query CaseStudies($productId: String!, $tag: String, $limit: Int, $offset: Int) {
1516
- publicContentPages(productId: $productId, tag: $tag, limit: $limit, offset: $offset) {
1517
- items {
1518
- id
1519
- title
1520
- slug
1521
- lastUpdated
1522
- tags
1523
- seoDescription
1524
- excerpt
1525
- thumbnail
1526
- author
1527
- publishedAt
1528
- readingTime
1529
- featured
1530
- category
1531
- customerSignOff {
1532
- signedAt
1533
- }
1534
- }
1535
- total
1536
- hasMore
1537
- }
1538
- }
1539
- `;
1540
- var CASE_STUDY_QUERY = `
1541
- query CaseStudy($slug: String!, $productId: String) {
1542
- publicContentPage(slug: $slug, productId: $productId) {
1543
- id
1544
- title
1545
- slug
1546
- content
1547
- contentFormat
1548
- seoTitle
1549
- seoDescription
1550
- seoKeywords
1551
- lastUpdated
1552
- tags
1553
- excerpt
1554
- thumbnail
1555
- author
1556
- publishedAt
1557
- readingTime
1558
- featured
1559
- category
1560
- customerSignOff {
1561
- signedAt
1562
- }
1563
- }
1564
- }
1565
- `;
1566
- function formatDate(value) {
1567
- if (!value) return "";
1568
- const d = new Date(value);
1569
- if (Number.isNaN(d.getTime())) return "";
1570
- return d.toLocaleDateString("en-US", {
1571
- year: "numeric",
1572
- month: "long",
1573
- day: "numeric"
1574
- });
1575
- }
1576
- function Thumbnail({
1577
- src,
1578
- alt,
1579
- className = ""
1580
- }) {
1581
- const [imgError, setImgError] = useState(false);
1582
- if (src && !imgError) {
1583
- return /* @__PURE__ */ jsx(
1584
- "img",
1585
- {
1586
- src,
1587
- alt,
1588
- className,
1589
- loading: "lazy",
1590
- onError: () => setImgError(true)
1591
- }
1592
- );
1593
- }
1594
- return /* @__PURE__ */ jsx(
1595
- "div",
1596
- {
1597
- className: `flex items-center justify-center bg-gradient-to-br from-muted/60 to-muted ${className}`,
1598
- "aria-hidden": "true",
1599
- children: /* @__PURE__ */ jsx(
1600
- "svg",
1601
- {
1602
- className: "w-12 h-12 text-muted-foreground/25",
1603
- fill: "none",
1604
- viewBox: "0 0 24 24",
1605
- stroke: "currentColor",
1606
- children: /* @__PURE__ */ jsx(
1607
- "path",
1608
- {
1609
- strokeLinecap: "round",
1610
- strokeLinejoin: "round",
1611
- strokeWidth: 1.5,
1612
- d: "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
1613
- }
1614
- )
1615
- }
1616
- )
1617
- }
1618
- );
1619
- }
1620
- function MetaRow({
1621
- post,
1622
- className = ""
1623
- }) {
1624
- const formattedDate = formatDate(post.publishedAt || post.lastUpdated);
1625
- return /* @__PURE__ */ jsxs(
1626
- "div",
1627
- {
1628
- className: `flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground ${className}`,
1629
- children: [
1630
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: post.author }),
1631
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1632
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
1633
- post.readingTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
1634
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
1635
- /* @__PURE__ */ jsxs("span", { children: [
1636
- post.readingTime,
1637
- " min read"
1638
- ] })
1639
- ] }) : null
1640
- ]
1641
- }
1642
- );
1643
- }
1644
- function FeaturedCard({
1645
- post,
1646
- basePath
1647
- }) {
1648
- return /* @__PURE__ */ jsxs(
1649
- "a",
1650
- {
1651
- href: `${basePath}/${post.slug}`,
1652
- className: "group relative grid md:grid-cols-2 gap-0 overflow-hidden rounded-2xl border bg-card shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-xl hover:border-primary/40",
1653
- children: [
1654
- /* @__PURE__ */ jsxs("div", { className: "relative aspect-[16/10] md:aspect-auto md:h-full overflow-hidden", children: [
1655
- /* @__PURE__ */ jsx(
1656
- Thumbnail,
1657
- {
1658
- src: post.thumbnail,
1659
- alt: post.title,
1660
- className: "w-full h-full min-h-[220px] object-cover transition-transform duration-500 group-hover:scale-[1.04]"
1661
- }
1662
- ),
1663
- /* @__PURE__ */ jsxs("span", { className: "absolute top-4 left-4 inline-flex items-center gap-1 rounded-full bg-primary px-2.5 py-1 text-xs font-semibold text-primary-foreground shadow-sm", children: [
1664
- /* @__PURE__ */ jsx(
1665
- "svg",
1666
- {
1667
- className: "h-3 w-3",
1668
- viewBox: "0 0 20 20",
1669
- fill: "currentColor",
1670
- "aria-hidden": "true",
1671
- children: /* @__PURE__ */ jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.286 3.957a1 1 0 00.95.69h4.162c.969 0 1.371 1.24.588 1.81l-3.368 2.447a1 1 0 00-.364 1.118l1.287 3.957c.3.921-.755 1.688-1.54 1.118l-3.367-2.447a1 1 0 00-1.176 0l-3.367 2.447c-.784.57-1.838-.197-1.539-1.118l1.286-3.957a1 1 0 00-.363-1.118L2.98 9.384c-.783-.57-.38-1.81.588-1.81h4.162a1 1 0 00.95-.69l1.286-3.957z" })
1672
- }
1673
- ),
1674
- "Featured"
1675
- ] })
1676
- ] }),
1677
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center p-6 sm:p-8", children: [
1678
- post.category && /* @__PURE__ */ jsx("span", { className: "mb-3 inline-block w-fit rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary", children: post.category }),
1679
- /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold text-foreground transition-colors group-hover:text-primary line-clamp-3", children: post.title }),
1680
- /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm sm:text-base text-muted-foreground line-clamp-3", children: post.excerpt || post.seoDescription || "" }),
1681
- /* @__PURE__ */ jsx(MetaRow, { post, className: "mt-5" }),
1682
- /* @__PURE__ */ jsxs("span", { className: "mt-5 inline-flex items-center gap-1 text-sm font-semibold text-primary", children: [
1683
- "Read the story",
1684
- /* @__PURE__ */ jsx(
1685
- "svg",
1686
- {
1687
- className: "h-4 w-4 transition-transform duration-300 group-hover:translate-x-1",
1688
- viewBox: "0 0 20 20",
1689
- fill: "currentColor",
1690
- "aria-hidden": "true",
1691
- children: /* @__PURE__ */ jsx(
1692
- "path",
1693
- {
1694
- fillRule: "evenodd",
1695
- d: "M7.293 4.293a1 1 0 011.414 0l5 5a1 1 0 010 1.414l-5 5a1 1 0 01-1.414-1.414L11.586 10 7.293 5.707a1 1 0 010-1.414z",
1696
- clipRule: "evenodd"
1697
- }
1698
- )
1699
- }
1700
- )
1701
- ] })
1702
- ] })
1703
- ]
1704
- }
1705
- );
1706
- }
1707
- function CaseStudyCard({
1708
- post,
1709
- basePath = "/case-studies"
1710
- }) {
1711
- return /* @__PURE__ */ jsxs(
1712
- "a",
1713
- {
1714
- href: `${basePath}/${post.slug}`,
1715
- className: "group flex flex-col overflow-hidden rounded-xl border bg-card transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:border-primary/40",
1716
- children: [
1717
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden", children: /* @__PURE__ */ jsx(
1718
- Thumbnail,
1719
- {
1720
- src: post.thumbnail,
1721
- alt: post.title,
1722
- className: "w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
1723
- }
1724
- ) }),
1725
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col p-5 sm:p-6", children: [
1726
- post.category && /* @__PURE__ */ jsx("span", { className: "mb-3 inline-block w-fit rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary", children: post.category }),
1727
- /* @__PURE__ */ jsx("h2", { className: "mb-2 text-lg font-semibold text-foreground transition-colors group-hover:text-primary line-clamp-2", children: post.title }),
1728
- /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground line-clamp-3", children: post.excerpt || post.seoDescription || "" }),
1729
- /* @__PURE__ */ jsx(MetaRow, { post, className: "mt-auto" })
1730
- ] })
1731
- ]
1732
- }
1733
- );
1734
- }
1735
- function EmptyState({
1736
- productLabel,
1737
- title,
1738
- subtitle,
1739
- contactHref,
1740
- contactLabel,
1741
- exploreHref,
1742
- exploreLabel
1743
- }) {
1744
- const highlights = [
1745
- {
1746
- title: "Real outcomes, real teams",
1747
- body: `We're partnering with our earliest adopters right now. As they ship results with ${productLabel}, their stories will land here \u2014 measured and unembellished.`,
1748
- icon: /* @__PURE__ */ jsx(
1749
- "path",
1750
- {
1751
- strokeLinecap: "round",
1752
- strokeLinejoin: "round",
1753
- strokeWidth: 1.5,
1754
- d: "M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
1755
- }
1756
- )
1757
- },
1758
- {
1759
- title: "See it on your use case",
1760
- body: `Want to know how ${productLabel} would work for your team? We'll walk you through exactly what to expect \u2014 no pitch, just specifics.`,
1761
- icon: /* @__PURE__ */ jsx(
1762
- "path",
1763
- {
1764
- strokeLinecap: "round",
1765
- strokeLinejoin: "round",
1766
- strokeWidth: 1.5,
1767
- d: "M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z M15 12a3 3 0 11-6 0 3 3 0 016 0z"
1768
- }
1769
- )
1770
- },
1771
- {
1772
- title: "Be the first story",
1773
- body: `Building something with ${productLabel} worth talking about? Tell us \u2014 the best early stories become the case studies featured on this page.`,
1774
- icon: /* @__PURE__ */ jsx(
1775
- "path",
1776
- {
1777
- strokeLinecap: "round",
1778
- strokeLinejoin: "round",
1779
- strokeWidth: 1.5,
1780
- d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.562.562 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.562.562 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"
1781
- }
1782
- )
1783
- }
1784
- ];
1785
- return /* @__PURE__ */ jsxs("div", { className: "cs-fade-up mx-auto max-w-4xl", children: [
1786
- /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-3xl border bg-card px-6 py-14 text-center sm:px-12 sm:py-16", children: [
1787
- /* @__PURE__ */ jsx(
1788
- "div",
1789
- {
1790
- className: "pointer-events-none absolute -top-24 -right-16 h-64 w-64 rounded-full bg-primary/10 blur-3xl",
1791
- "aria-hidden": "true"
1792
- }
1793
- ),
1794
- /* @__PURE__ */ jsx(
1795
- "div",
1796
- {
1797
- className: "pointer-events-none absolute -bottom-24 -left-16 h-64 w-64 rounded-full bg-primary/5 blur-3xl",
1798
- "aria-hidden": "true"
1799
- }
1800
- ),
1801
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1802
- /* @__PURE__ */ jsx("div", { className: "cs-float mx-auto flex h-20 w-20 items-center justify-center rounded-2xl bg-primary/10 text-primary ring-1 ring-primary/20", children: /* @__PURE__ */ jsx(
1803
- "svg",
1804
- {
1805
- className: "h-10 w-10",
1806
- fill: "none",
1807
- viewBox: "0 0 24 24",
1808
- stroke: "currentColor",
1809
- "aria-hidden": "true",
1810
- children: /* @__PURE__ */ jsx(
1811
- "path",
1812
- {
1813
- strokeLinecap: "round",
1814
- strokeLinejoin: "round",
1815
- strokeWidth: 1.5,
1816
- d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
1817
- }
1818
- )
1819
- }
1820
- ) }),
1821
- /* @__PURE__ */ jsx("h2", { className: "mt-8 text-2xl font-bold tracking-tight text-foreground sm:text-3xl", children: title }),
1822
- /* @__PURE__ */ jsx("p", { className: "mx-auto mt-4 max-w-2xl text-base leading-relaxed text-muted-foreground sm:text-lg", children: subtitle }),
1823
- /* @__PURE__ */ jsxs("div", { className: "mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row", children: [
1824
- /* @__PURE__ */ jsxs(
1825
- "a",
1826
- {
1827
- href: contactHref,
1828
- className: "inline-flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground shadow-sm transition-all hover:opacity-90 hover:shadow-md sm:w-auto",
1829
- children: [
1830
- contactLabel,
1831
- /* @__PURE__ */ jsx(
1832
- "svg",
1833
- {
1834
- className: "h-4 w-4",
1835
- viewBox: "0 0 20 20",
1836
- fill: "currentColor",
1837
- "aria-hidden": "true",
1838
- children: /* @__PURE__ */ jsx(
1839
- "path",
1840
- {
1841
- fillRule: "evenodd",
1842
- d: "M7.293 4.293a1 1 0 011.414 0l5 5a1 1 0 010 1.414l-5 5a1 1 0 01-1.414-1.414L11.586 10 7.293 5.707a1 1 0 010-1.414z",
1843
- clipRule: "evenodd"
1844
- }
1845
- )
1846
- }
1847
- )
1848
- ]
1849
- }
1850
- ),
1851
- /* @__PURE__ */ jsx(
1852
- "a",
1853
- {
1854
- href: exploreHref,
1855
- className: "inline-flex w-full items-center justify-center gap-2 rounded-lg border border-border bg-background px-6 py-3 text-sm font-semibold text-foreground transition-colors hover:bg-muted sm:w-auto",
1856
- children: exploreLabel
1857
- }
1858
- )
1859
- ] })
1860
- ] })
1861
- ] }),
1862
- /* @__PURE__ */ jsx("div", { className: "mt-8 grid gap-4 sm:grid-cols-3", children: highlights.map((h) => /* @__PURE__ */ jsxs(
1863
- "div",
1864
- {
1865
- className: "rounded-2xl border bg-card/60 p-5 text-left transition-colors hover:border-primary/30",
1866
- children: [
1867
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(
1868
- "svg",
1869
- {
1870
- className: "h-5 w-5",
1871
- fill: "none",
1872
- viewBox: "0 0 24 24",
1873
- stroke: "currentColor",
1874
- "aria-hidden": "true",
1875
- children: h.icon
1876
- }
1877
- ) }),
1878
- /* @__PURE__ */ jsx("h3", { className: "mt-4 text-sm font-semibold text-foreground", children: h.title }),
1879
- /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm leading-relaxed text-muted-foreground", children: h.body })
1880
- ]
1881
- },
1882
- h.title
1883
- )) })
1884
- ] });
1885
- }
1886
- function CaseStudiesListPage(props) {
1887
- const {
1888
- productName,
1889
- eyebrow = "Customer Stories",
1890
- heroTitle = "Case Studies",
1891
- heroSubtitle = "Real-world stories of teams building with our platform.",
1892
- caseStudyBasePath = "/case-studies",
1893
- className = "",
1894
- seoTitle,
1895
- seoDescription,
1896
- seoKeywords,
1897
- contactHref = "/contact",
1898
- contactLabel = "Get in touch",
1899
- exploreHref = "/features",
1900
- exploreLabel = "Explore the product",
1901
- emptyTitle,
1902
- emptySubtitle
1903
- } = props;
1904
- const client = useWebSDK();
1905
- const config = useWebSDKConfig();
1906
- const { product } = useProduct();
1907
- const resolvedProductId = product?.id || config.productId;
1908
- const productLabel = product?.name || productName || "our platform";
1909
- const [posts, setPosts] = useState([]);
1910
- const [total, setTotal] = useState(0);
1911
- const [loading, setLoading] = useState(true);
1912
- const [error, setError] = useState(null);
1913
- const [offset, setOffset] = useState(0);
1914
- const [activeCategory, setActiveCategory] = useState("All");
1915
- const limit = 12;
1916
- const fetchPosts = useCallback(async () => {
1917
- if (!resolvedProductId) return;
1918
- setLoading(true);
1919
- try {
1920
- const result = await client.query(CASE_STUDIES_QUERY, {
1921
- productId: resolvedProductId,
1922
- // Filter server-side: an unfiltered page of `limit` rows is mostly blog/legal pages, so
1923
- // real case studies past it were never listed and the page fell back to the empty state.
1924
- tag: CASE_STUDY_TAG,
1925
- limit,
1926
- offset
1927
- });
1928
- if (result.errors?.length) {
1929
- setError(result.errors[0]?.message ?? "Failed to load case studies");
1930
- setPosts([]);
1931
- return;
1932
- }
1933
- const items = result.data?.publicContentPages?.items || [];
1934
- const caseStudies = items.filter(
1935
- (p) => p.tags?.includes(CASE_STUDY_TAG) && hasCustomerSignOff(p)
1936
- );
1937
- setPosts(caseStudies);
1938
- const dropped = items.length - caseStudies.length;
1939
- setTotal(
1940
- Math.max(0, (result.data?.publicContentPages?.total || 0) - dropped)
1941
- );
1942
- setError(null);
1943
- } catch {
1944
- setError("load-failed");
1945
- setPosts([]);
1946
- } finally {
1947
- setLoading(false);
1948
- }
1949
- }, [client, resolvedProductId, offset]);
1950
- useEffect(() => {
1951
- fetchPosts();
1952
- }, [fetchPosts]);
1953
- const categories = useMemo(() => {
1954
- const set = /* @__PURE__ */ new Set();
1955
- posts.forEach((p) => {
1956
- if (p.category) set.add(p.category);
1957
- });
1958
- return ["All", ...Array.from(set)];
1959
- }, [posts]);
1960
- const visiblePosts = useMemo(() => {
1961
- if (activeCategory === "All") return posts;
1962
- return posts.filter((p) => p.category === activeCategory);
1963
- }, [posts, activeCategory]);
1964
- const featuredPosts = visiblePosts.filter((p) => p.featured);
1965
- const regularPosts = visiblePosts.filter((p) => !p.featured);
1966
- const hasContent = !loading && !error && posts.length > 0;
1967
- const showEmpty = !loading && (error !== null || posts.length === 0);
1968
- const resolvedEmptyTitle = emptyTitle ?? "Case studies are on the way";
1969
- const resolvedEmptySubtitle = emptySubtitle ?? `${productLabel} is early in its journey, and we'd rather show you real, measurable results than filler. We're working closely with our first teams now \u2014 their stories will be published here soon. In the meantime, we'd love to show you what's possible.`;
1970
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, "data-cs": "case-studies-v2", children: [
1971
- /* @__PURE__ */ jsx(ProseStyles, {}),
1972
- /* @__PURE__ */ jsx(AnimationStyles, {}),
1973
- seoTitle && /* @__PURE__ */ jsx(
1974
- PageHead,
1975
- {
1976
- title: seoTitle,
1977
- description: seoDescription || heroSubtitle,
1978
- productName,
1979
- keywords: seoKeywords
1980
- }
1981
- ),
1982
- /* @__PURE__ */ jsxs("section", { className: "relative overflow-hidden", children: [
1983
- /* @__PURE__ */ jsx(
1984
- "div",
1985
- {
1986
- className: "pointer-events-none absolute inset-x-0 -top-32 mx-auto h-72 max-w-3xl rounded-full bg-primary/10 blur-3xl",
1987
- "aria-hidden": "true"
1988
- }
1989
- ),
1990
- /* @__PURE__ */ jsx("div", { className: "relative mx-auto max-w-7xl px-4 py-16 sm:px-6 sm:py-20 md:py-24 lg:px-8", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl text-center", children: [
1991
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-primary", children: [
1992
- /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-primary" }),
1993
- eyebrow
1994
- ] }),
1995
- /* @__PURE__ */ jsx("h1", { className: "mt-5 text-3xl font-bold tracking-tight text-foreground sm:text-4xl md:text-5xl", children: heroTitle }),
1996
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle })
1997
- ] }) })
1998
- ] }),
1999
- /* @__PURE__ */ jsx("section", { className: "pb-20 sm:pb-24", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: loading ? /* @__PURE__ */ jsx("div", { className: "grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3", children: [1, 2, 3, 4, 5, 6].map((i) => /* @__PURE__ */ jsxs(
2000
- "div",
2001
- {
2002
- className: "overflow-hidden rounded-xl border bg-card animate-pulse",
2003
- children: [
2004
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
2005
- /* @__PURE__ */ jsxs("div", { className: "space-y-3 p-6", children: [
2006
- /* @__PURE__ */ jsx("div", { className: "h-4 w-1/4 rounded bg-muted" }),
2007
- /* @__PURE__ */ jsx("div", { className: "h-6 w-3/4 rounded bg-muted" }),
2008
- /* @__PURE__ */ jsx("div", { className: "h-4 rounded bg-muted" }),
2009
- /* @__PURE__ */ jsx("div", { className: "h-4 w-1/2 rounded bg-muted" })
2010
- ] })
2011
- ]
2012
- },
2013
- i
2014
- )) }) : showEmpty ? /* @__PURE__ */ jsx(
2015
- EmptyState,
2016
- {
2017
- productLabel,
2018
- title: resolvedEmptyTitle,
2019
- subtitle: resolvedEmptySubtitle,
2020
- contactHref,
2021
- contactLabel,
2022
- exploreHref,
2023
- exploreLabel
2024
- }
2025
- ) : hasContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
2026
- categories.length > 2 && /* @__PURE__ */ jsx("div", { className: "mb-10 flex flex-wrap justify-center gap-2", children: categories.map((cat) => /* @__PURE__ */ jsx(
2027
- "button",
2028
- {
2029
- type: "button",
2030
- onClick: () => setActiveCategory(cat),
2031
- className: `rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${activeCategory === cat ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:border-primary/40 hover:text-foreground"}`,
2032
- children: cat
2033
- },
2034
- cat
2035
- )) }),
2036
- featuredPosts.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-10 grid gap-6 sm:gap-8 lg:grid-cols-2", children: featuredPosts.map((post) => /* @__PURE__ */ jsx(
2037
- FeaturedCard,
2038
- {
2039
- post,
2040
- basePath: caseStudyBasePath
2041
- },
2042
- post.id
2043
- )) }),
2044
- regularPosts.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3", children: regularPosts.map((post) => /* @__PURE__ */ jsx(
2045
- CaseStudyCard,
2046
- {
2047
- post,
2048
- basePath: caseStudyBasePath
2049
- },
2050
- post.id
2051
- )) }),
2052
- activeCategory === "All" && total > limit && /* @__PURE__ */ jsxs("div", { className: "mt-14 flex justify-center gap-4", children: [
2053
- /* @__PURE__ */ jsx(
2054
- "button",
2055
- {
2056
- onClick: () => setOffset(Math.max(0, offset - limit)),
2057
- disabled: offset === 0,
2058
- className: "rounded-lg border px-5 py-2.5 text-sm font-medium transition-colors hover:bg-muted disabled:opacity-40",
2059
- children: "Previous"
2060
- }
2061
- ),
2062
- /* @__PURE__ */ jsx(
2063
- "button",
2064
- {
2065
- onClick: () => setOffset(offset + limit),
2066
- disabled: offset + limit >= total,
2067
- className: "rounded-lg border px-5 py-2.5 text-sm font-medium transition-colors hover:bg-muted disabled:opacity-40",
2068
- children: "Next"
2069
- }
2070
- )
2071
- ] }),
2072
- /* @__PURE__ */ jsxs("div", { className: "mt-16 overflow-hidden rounded-2xl border bg-gradient-to-br from-primary/10 via-card to-card p-8 text-center sm:p-10", children: [
2073
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold text-foreground sm:text-2xl", children: "Want results like these?" }),
2074
- /* @__PURE__ */ jsxs("p", { className: "mx-auto mt-2 max-w-xl text-sm text-muted-foreground sm:text-base", children: [
2075
- "Tell us about your goals and we\u2019ll show you exactly how",
2076
- " ",
2077
- productLabel,
2078
- " can help."
2079
- ] }),
2080
- /* @__PURE__ */ jsx(
2081
- "a",
2082
- {
2083
- href: contactHref,
2084
- className: "mt-6 inline-flex items-center justify-center gap-2 rounded-lg bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground shadow-sm transition-all hover:opacity-90 hover:shadow-md",
2085
- children: contactLabel
2086
- }
2087
- )
2088
- ] })
2089
- ] }) : null }) })
2090
- ] });
2091
- }
2092
- function CaseStudyPage(props) {
2093
- const {
2094
- productName,
2095
- caseStudiesListUrl = "/case-studies",
2096
- backLabel = "Back to Case Studies",
2097
- contactHref = "/contact",
2098
- contactLabel = "Get in touch",
2099
- className = ""
2100
- } = props;
2101
- const client = useWebSDK();
2102
- const config = useWebSDKConfig();
2103
- const { product } = useProduct();
2104
- const resolvedProductId = product?.id || config.productId;
2105
- const productLabel = product?.name || productName || "our team";
2106
- const [post, setPost] = useState(null);
2107
- const [loading, setLoading] = useState(true);
2108
- const [error, setError] = useState(null);
2109
- const slug = typeof window !== "undefined" ? window.location.pathname.split("/").pop() || "" : "";
2110
- useEffect(() => {
2111
- if (!slug || !resolvedProductId) return;
2112
- async function fetchPost() {
2113
- setLoading(true);
2114
- try {
2115
- const result = await client.query(CASE_STUDY_QUERY, {
2116
- slug,
2117
- productId: resolvedProductId
2118
- });
2119
- if (result.errors?.length) {
2120
- setError(result.errors[0]?.message ?? "Failed to load case study");
2121
- return;
2122
- }
2123
- const page = result.data?.publicContentPage ?? null;
2124
- setPost(page && hasCustomerSignOff(page) ? page : null);
2125
- setError(null);
2126
- } catch {
2127
- setError("Failed to load case study");
2128
- } finally {
2129
- setLoading(false);
2130
- }
2131
- }
2132
- fetchPost();
2133
- }, [client, slug, resolvedProductId]);
2134
- if (loading) {
2135
- return /* @__PURE__ */ jsx("div", { className: `w-full ${className}`, children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-4xl px-4 py-24 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse space-y-6", children: [
2136
- /* @__PURE__ */ jsx("div", { className: "h-8 w-2/3 rounded bg-muted" }),
2137
- /* @__PURE__ */ jsx("div", { className: "h-4 w-1/3 rounded bg-muted" }),
2138
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] rounded-xl bg-muted" }),
2139
- /* @__PURE__ */ jsx("div", { className: "space-y-3", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx("div", { className: "h-4 rounded bg-muted" }, i)) })
2140
- ] }) }) });
2141
- }
2142
- if (error || !post) {
2143
- return /* @__PURE__ */ jsx("div", { className: `w-full ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-2xl px-4 py-24 text-center sm:px-6 lg:px-8", children: [
2144
- /* @__PURE__ */ jsx("div", { className: "mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(
2145
- "svg",
2146
- {
2147
- className: "h-8 w-8",
2148
- fill: "none",
2149
- viewBox: "0 0 24 24",
2150
- stroke: "currentColor",
2151
- "aria-hidden": "true",
2152
- children: /* @__PURE__ */ jsx(
2153
- "path",
2154
- {
2155
- strokeLinecap: "round",
2156
- strokeLinejoin: "round",
2157
- strokeWidth: 1.5,
2158
- d: "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
2159
- }
2160
- )
2161
- }
2162
- ) }),
2163
- /* @__PURE__ */ jsx("h1", { className: "mt-6 text-2xl font-bold text-foreground", children: "Case study not found" }),
2164
- /* @__PURE__ */ jsxs("p", { className: "mx-auto mt-3 max-w-md text-muted-foreground", children: [
2165
- "This story isn\u2019t available yet \u2014 but there\u2019s plenty more to explore, and we\u2019re always happy to talk through what ",
2166
- productLabel,
2167
- " can do."
2168
- ] }),
2169
- /* @__PURE__ */ jsxs("div", { className: "mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row", children: [
2170
- /* @__PURE__ */ jsxs(
2171
- "a",
2172
- {
2173
- href: caseStudiesListUrl,
2174
- className: "inline-flex items-center justify-center rounded-lg border px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted",
2175
- children: [
2176
- "\u2190 ",
2177
- backLabel
2178
- ]
2179
- }
2180
- ),
2181
- /* @__PURE__ */ jsx(
2182
- "a",
2183
- {
2184
- href: contactHref,
2185
- className: "inline-flex items-center justify-center rounded-lg bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-all hover:opacity-90",
2186
- children: contactLabel
2187
- }
2188
- )
2189
- ] })
2190
- ] }) });
2191
- }
2192
- const formattedDate = formatDate(post.publishedAt || post.lastUpdated);
2193
- const approvedDate = formatDate(post.customerSignOff?.signedAt);
2194
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
2195
- /* @__PURE__ */ jsx(ProseStyles, {}),
2196
- /* @__PURE__ */ jsx(
2197
- PageHead,
2198
- {
2199
- title: post.seoTitle || post.title,
2200
- description: post.seoDescription || post.excerpt || "",
2201
- productName,
2202
- keywords: post.seoKeywords || ""
2203
- }
2204
- ),
2205
- /* @__PURE__ */ jsxs("article", { className: "mx-auto max-w-4xl px-4 py-16 sm:px-6 sm:py-20 lg:px-8", children: [
2206
- /* @__PURE__ */ jsxs(
2207
- "a",
2208
- {
2209
- href: caseStudiesListUrl,
2210
- className: "mb-8 inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground",
2211
- children: [
2212
- "\u2190 ",
2213
- backLabel
2214
- ]
2215
- }
2216
- ),
2217
- post.category && /* @__PURE__ */ jsx("span", { className: "mb-4 inline-block rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary", children: post.category }),
2218
- /* @__PURE__ */ jsx("h1", { className: "mb-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl md:text-5xl", children: post.title }),
2219
- /* @__PURE__ */ jsxs("div", { className: "mb-8 flex flex-wrap items-center gap-3 text-sm text-muted-foreground", children: [
2220
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: post.author }),
2221
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
2222
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
2223
- post.readingTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
2224
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
2225
- /* @__PURE__ */ jsxs("span", { children: [
2226
- post.readingTime,
2227
- " min read"
2228
- ] })
2229
- ] }) : null
2230
- ] }),
2231
- approvedDate && /* @__PURE__ */ jsxs(
2232
- "p",
2233
- {
2234
- className: "-mt-4 mb-8 text-xs text-muted-foreground",
2235
- "data-cs": "customer-signoff",
2236
- children: [
2237
- "Approved for publication by the customer on ",
2238
- approvedDate
2239
- ]
2240
- }
2241
- ),
2242
- post.thumbnail && /* @__PURE__ */ jsx("div", { className: "mb-10 aspect-[16/9] overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
2243
- "img",
2244
- {
2245
- src: post.thumbnail,
2246
- alt: post.title,
2247
- className: "h-full w-full object-cover"
2248
- }
2249
- ) }),
2250
- /* @__PURE__ */ jsx("div", { className: "boff-prose max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content: post.content, format: post.contentFormat }) }),
2251
- /* @__PURE__ */ jsxs("div", { className: "mt-14 rounded-2xl border bg-gradient-to-br from-primary/10 via-card to-card p-8 text-center", children: [
2252
- /* @__PURE__ */ jsxs("h2", { className: "text-xl font-bold text-foreground", children: [
2253
- "Curious what ",
2254
- productLabel,
2255
- " could do for you?"
2256
- ] }),
2257
- /* @__PURE__ */ jsx("p", { className: "mx-auto mt-2 max-w-lg text-sm text-muted-foreground", children: "We\u2019re happy to walk you through it \u2014 no pressure, just a conversation about your goals." }),
2258
- /* @__PURE__ */ jsx(
2259
- "a",
2260
- {
2261
- href: contactHref,
2262
- className: "mt-6 inline-flex items-center justify-center rounded-lg bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground shadow-sm transition-all hover:opacity-90 hover:shadow-md",
2263
- children: contactLabel
2264
- }
2265
- )
2266
- ] })
2267
- ] })
2268
- ] });
2269
- }
2270
- function cn(...inputs) {
2271
- return twMerge(clsx(inputs));
2272
- }
2273
- var buttonVariants = cva(
2274
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2275
- {
2276
- variants: {
2277
- variant: {
2278
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2279
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
2280
- outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2281
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
2282
- ghost: "hover:bg-accent hover:text-accent-foreground",
2283
- link: "text-primary underline-offset-4 hover:underline"
2284
- },
2285
- size: {
2286
- default: "h-9 px-4 py-2",
2287
- sm: "h-8 rounded-md px-3 text-xs",
2288
- lg: "h-10 rounded-md px-8",
2289
- icon: "h-9 w-9"
2290
- }
2291
- },
2292
- defaultVariants: {
2293
- variant: "default",
2294
- size: "default"
2295
- }
2296
- }
2297
- );
2298
- var Button = React.forwardRef(
2299
- ({ className, variant, size, asChild = false, ...props }, ref) => {
2300
- const Comp = asChild ? Slot : "button";
2301
- return /* @__PURE__ */ jsx(
2302
- Comp,
2303
- {
2304
- className: cn(buttonVariants({ variant, size, className })),
2305
- ref,
2306
- ...props
2307
- }
2308
- );
2309
- }
2310
- );
2311
- Button.displayName = "Button";
2312
- var Input = React.forwardRef(
2313
- ({ className, type, ...props }, ref) => {
2314
- return /* @__PURE__ */ jsx(
2315
- "input",
2316
- {
2317
- type,
2318
- className: cn(
2319
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
2320
- className
2321
- ),
2322
- ref,
2323
- ...props
2324
- }
2325
- );
2326
- }
2327
- );
2328
- Input.displayName = "Input";
2329
- var CHANGELOG_TAG = "changelog";
2330
- var CHANGELOG_LIST_QUERY = `
2331
- query ChangelogList($productId: String!, $limit: Int, $offset: Int) {
2332
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
2333
- items {
2334
- id
2335
- title
2336
- slug
2337
- lastUpdated
2338
- tags
2339
- excerpt
2340
- seoDescription
2341
- thumbnail
2342
- author
2343
- publishedAt
2344
- readingTime
2345
- featured
2346
- category
2347
- }
2348
- total
2349
- hasMore
2350
- }
2351
- }
2352
- `;
2353
- var CHANGELOG_ENTRY_QUERY = `
2354
- query ChangelogEntry($slug: String!, $productId: String) {
2355
- publicContentPage(slug: $slug, productId: $productId) {
2356
- content
2357
- contentFormat
2358
- }
2359
- }
2360
- `;
2361
- function formatDate2(value) {
2362
- if (!value) return "";
2363
- return new Date(value).toLocaleDateString("en-US", {
2364
- year: "numeric",
2365
- month: "long",
2366
- day: "numeric"
2367
- });
2368
- }
2369
- function sortByDateDesc(a, b) {
2370
- const da = new Date(a.publishedAt || a.lastUpdated || 0).getTime();
2371
- const db = new Date(b.publishedAt || b.lastUpdated || 0).getTime();
2372
- return db - da;
2373
- }
2374
- function normalize(value) {
2375
- return value.toLowerCase().trim();
2376
- }
2377
- function paginate(items, page, pageSize) {
2378
- const start = (page - 1) * pageSize;
2379
- return items.slice(start, start + pageSize);
2380
- }
2381
- function parseEntryDate(date) {
2382
- return new Date(date).getTime();
2383
- }
2384
- function useCmsChangelog() {
2385
- const client = useWebSDK();
2386
- const config = useWebSDKConfig();
2387
- const { product } = useProduct();
2388
- const resolvedProductId = product?.id || config.productId;
2389
- const [entries, setEntries] = useState([]);
2390
- const [loading, setLoading] = useState(true);
2391
- const [error, setError] = useState(null);
2392
- const fetchEntries = useCallback(async () => {
2393
- if (!resolvedProductId) {
2394
- setLoading(false);
2395
- return;
2396
- }
2397
- setLoading(true);
2398
- try {
2399
- const listResult = await client.query(CHANGELOG_LIST_QUERY, {
2400
- productId: resolvedProductId,
2401
- limit: 50,
2402
- offset: 0
2403
- });
2404
- if (listResult.errors?.length) {
2405
- setError(listResult.errors[0]?.message ?? "Failed to load changelog");
2406
- return;
2407
- }
2408
- const changelogItems = (listResult.data?.publicContentPages?.items || []).filter((p) => p.tags?.includes(CHANGELOG_TAG)).sort(sortByDateDesc);
2409
- const resolved = await Promise.all(
2410
- changelogItems.map(async (item) => {
2411
- try {
2412
- const detail = await client.query(CHANGELOG_ENTRY_QUERY, {
2413
- slug: item.slug,
2414
- productId: resolvedProductId
2415
- });
2416
- const page = detail.data?.publicContentPage;
2417
- return {
2418
- ...item,
2419
- content: page?.content,
2420
- contentFormat: page?.contentFormat
2421
- };
2422
- } catch {
2423
- return { ...item };
2424
- }
2425
- })
2426
- );
2427
- setEntries(resolved);
2428
- setError(null);
2429
- } catch {
2430
- setError("Failed to load changelog");
2431
- } finally {
2432
- setLoading(false);
2433
- }
2434
- }, [client, resolvedProductId]);
2435
- useEffect(() => {
2436
- fetchEntries();
2437
- }, [fetchEntries]);
2438
- return { entries, loading, error };
2439
- }
2440
- function CmsTimeline(props) {
2441
- const { entries, loading, error } = props;
2442
- if (loading) {
2443
- return /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto space-y-12", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx(
2444
- "div",
2445
- {
2446
- className: "rounded-2xl border border-border/40 bg-card overflow-hidden animate-pulse",
2447
- children: /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8 space-y-4", children: [
2448
- /* @__PURE__ */ jsx("div", { className: "h-3 bg-muted rounded w-1/3" }),
2449
- /* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded w-2/3" }),
2450
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }),
2451
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-5/6" })
2452
- ] })
2453
- },
2454
- i
2455
- )) });
2456
- }
2457
- if (error) {
2458
- return /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: error }) });
2459
- }
2460
- if (entries.length === 0) {
2461
- return /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No changelog entries yet. Check back soon!" }) });
2462
- }
2463
- return /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto space-y-12", children: entries.map((entry) => /* @__PURE__ */ jsxs(
2464
- "article",
2465
- {
2466
- id: entry.slug,
2467
- className: "rounded-2xl border border-border/40 bg-card overflow-hidden scroll-mt-24",
2468
- children: [
2469
- entry.thumbnail && /* @__PURE__ */ jsx(
2470
- "img",
2471
- {
2472
- src: entry.thumbnail,
2473
- alt: "",
2474
- width: 1024,
2475
- height: 576,
2476
- loading: "lazy",
2477
- decoding: "async",
2478
- className: "w-full aspect-video object-cover border-b border-border/40"
2479
- }
2480
- ),
2481
- /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8", children: [
2482
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mb-2", children: [
2483
- entry.publishedAt && /* @__PURE__ */ jsx("time", { dateTime: entry.publishedAt, children: formatDate2(entry.publishedAt || entry.lastUpdated) }),
2484
- entry.author && /* @__PURE__ */ jsxs(Fragment, { children: [
2485
- " \xB7 ",
2486
- entry.author
2487
- ] })
2488
- ] }),
2489
- /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold text-foreground mb-4", children: entry.title }),
2490
- entry.content ? /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm max-w-none", children: /* @__PURE__ */ jsx(
2491
- ContentRenderer,
2492
- {
2493
- content: entry.content,
2494
- format: entry.contentFormat
2495
- }
2496
- ) }) : entry.excerpt && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed", children: entry.excerpt })
2497
- ] })
2498
- ]
2499
- },
2500
- entry.id
2501
- )) });
2502
- }
2503
- function EntryContent({ content }) {
2504
- if (typeof content === "string") {
2505
- return /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content, format: "markdown" }) });
2506
- }
2507
- return /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm max-w-none", children: content });
2508
- }
2509
- function EntriesTimeline(props) {
2510
- const { entries, pageSize, productName } = props;
2511
- const [query, setQuery] = useState("");
2512
- const [selectedCategory, setSelectedCategory] = useState(null);
2513
- const [page, setPage] = useState(1);
2514
- const categories = useMemo(
2515
- () => Array.from(new Set(entries.map((e) => e.category))).sort(),
2516
- [entries]
2517
- );
2518
- const filtered = useMemo(() => {
2519
- const normalized = normalize(query);
2520
- return entries.filter((entry) => {
2521
- if (selectedCategory && entry.category !== selectedCategory) {
2522
- return false;
2523
- }
2524
- if (!normalized) return true;
2525
- const haystack = [
2526
- entry.title,
2527
- entry.version,
2528
- entry.category,
2529
- ...entry.tags ?? [],
2530
- typeof entry.content === "string" ? entry.content : ""
2531
- ].join(" ").toLowerCase();
2532
- return haystack.includes(normalized);
2533
- }).sort((a, b) => parseEntryDate(b.date) - parseEntryDate(a.date));
2534
- }, [entries, query, selectedCategory]);
2535
- const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
2536
- const safePage = Math.min(page, totalPages);
2537
- const pageItems = paginate(filtered, safePage, pageSize);
2538
- useEffect(() => {
2539
- setPage(1);
2540
- }, [query, selectedCategory]);
2541
- if (entries.length === 0) {
2542
- return /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No changelog entries yet. Check back soon!" }) });
2543
- }
2544
- return /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto", children: [
2545
- /* @__PURE__ */ jsxs("div", { className: "mb-8 space-y-4", children: [
2546
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2547
- /* @__PURE__ */ jsx(
2548
- Search,
2549
- {
2550
- className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground",
2551
- "aria-hidden": "true"
2552
- }
2553
- ),
2554
- /* @__PURE__ */ jsx(
2555
- Input,
2556
- {
2557
- type: "search",
2558
- placeholder: "Search releases, features, fixes\u2026",
2559
- value: query,
2560
- onChange: (e) => setQuery(e.target.value),
2561
- className: "pl-9",
2562
- "aria-label": "Search changelog"
2563
- }
2564
- )
2565
- ] }),
2566
- categories.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2567
- /* @__PURE__ */ jsx(
2568
- "button",
2569
- {
2570
- type: "button",
2571
- onClick: () => setSelectedCategory(null),
2572
- className: cn(
2573
- "px-3 py-1 rounded-full text-xs font-medium border transition-colors",
2574
- selectedCategory === null ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border/60 hover:border-primary/40"
2575
- ),
2576
- children: "All"
2577
- }
2578
- ),
2579
- categories.map((category) => /* @__PURE__ */ jsx(
2580
- "button",
2581
- {
2582
- type: "button",
2583
- onClick: () => setSelectedCategory(category),
2584
- className: cn(
2585
- "px-3 py-1 rounded-full text-xs font-medium border transition-colors",
2586
- selectedCategory === category ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border/60 hover:border-primary/40"
2587
- ),
2588
- children: category
2589
- },
2590
- category
2591
- ))
2592
- ] })
2593
- ] }),
2594
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2595
- /* @__PURE__ */ jsx(
2596
- "div",
2597
- {
2598
- className: "absolute left-[1.125rem] top-3 bottom-3 w-px bg-border/60 hidden sm:block",
2599
- "aria-hidden": "true"
2600
- }
2601
- ),
2602
- /* @__PURE__ */ jsx("div", { className: "space-y-8", children: pageItems.map((entry) => /* @__PURE__ */ jsxs(
2603
- "article",
2604
- {
2605
- className: "relative sm:pl-12",
2606
- children: [
2607
- /* @__PURE__ */ jsx(
2608
- "div",
2609
- {
2610
- className: "hidden sm:flex absolute left-2 top-2 h-5 w-5 items-center justify-center rounded-full border-2 border-background bg-primary ring-1 ring-border/40",
2611
- "aria-hidden": "true",
2612
- children: /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-primary-foreground" })
2613
- }
2614
- ),
2615
- /* @__PURE__ */ jsx("div", { className: "rounded-2xl border border-border/40 bg-card overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8", children: [
2616
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-3", children: [
2617
- /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-md bg-primary/10 px-2 py-1 text-xs font-medium text-primary", children: entry.category }),
2618
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-mono font-medium text-muted-foreground", children: [
2619
- "v",
2620
- entry.version
2621
- ] }),
2622
- /* @__PURE__ */ jsx(
2623
- "time",
2624
- {
2625
- className: "text-xs text-muted-foreground",
2626
- dateTime: entry.date,
2627
- children: formatDate2(entry.date)
2628
- }
2629
- )
2630
- ] }),
2631
- entry.tags && entry.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 mb-3", children: entry.tags.map((tag) => /* @__PURE__ */ jsx(
2632
- "span",
2633
- {
2634
- className: "text-[10px] uppercase tracking-wide text-muted-foreground bg-background border border-border/40 px-1.5 py-0.5 rounded",
2635
- children: tag
2636
- },
2637
- tag
2638
- )) }),
2639
- /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold text-foreground mb-4", children: entry.title }),
2640
- /* @__PURE__ */ jsx(EntryContent, { content: entry.content })
2641
- ] }) })
2642
- ]
2643
- },
2644
- `${entry.version}-${entry.date}`
2645
- )) })
2646
- ] }),
2647
- pageItems.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No entries match your filters." }) }),
2648
- totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "mt-10 flex items-center justify-between", children: [
2649
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
2650
- "Page ",
2651
- safePage,
2652
- " of ",
2653
- totalPages,
2654
- " \xB7 ",
2655
- filtered.length,
2656
- " entries"
2657
- ] }),
2658
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2659
- /* @__PURE__ */ jsx(
2660
- Button,
2661
- {
2662
- variant: "outline",
2663
- size: "sm",
2664
- onClick: () => setPage((p) => Math.max(1, p - 1)),
2665
- disabled: safePage <= 1,
2666
- "aria-label": "Previous page",
2667
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
2668
- }
2669
- ),
2670
- /* @__PURE__ */ jsx(
2671
- Button,
2672
- {
2673
- variant: "outline",
2674
- size: "sm",
2675
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
2676
- disabled: safePage >= totalPages,
2677
- "aria-label": "Next page",
2678
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
2679
- }
2680
- )
2681
- ] })
2682
- ] }),
2683
- productName && pageItems.length > 0 && /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: JSON.stringify({
2684
- "@context": "https://schema.org",
2685
- "@type": "ItemList",
2686
- name: `${productName} Changelog`,
2687
- itemListElement: pageItems.map((entry, index) => ({
2688
- "@type": "ListItem",
2689
- position: (safePage - 1) * pageSize + index + 1,
2690
- name: entry.title,
2691
- description: typeof entry.content === "string" ? entry.content.slice(0, 200) : void 0,
2692
- datePublished: entry.date
2693
- }))
2694
- }) })
2695
- ] });
2696
- }
2697
- function ChangelogPage(props) {
2698
- const {
2699
- productName,
2700
- heroEyebrow = "What's new",
2701
- heroTitle = "Changelog",
2702
- heroSubtitle = "Platform updates, improvements, and announcements \u2014 newest first.",
2703
- ctaTitle,
2704
- ctaSubtitle,
2705
- ctaHref,
2706
- ctaLabel,
2707
- className = "",
2708
- seoTitle,
2709
- seoDescription,
2710
- seoKeywords,
2711
- entries,
2712
- pageSize = 10
2713
- } = props;
2714
- const { product } = useProduct();
2715
- const resolvedProductName = productName || product?.name;
2716
- const cms = useCmsChangelog();
2717
- const showCta = Boolean(ctaHref && ctaLabel);
2718
- const isPropDriven = entries != null;
2719
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
2720
- /* @__PURE__ */ jsx(ProseStyles, {}),
2721
- /* @__PURE__ */ jsx(
2722
- PageHead,
2723
- {
2724
- title: seoTitle || heroTitle,
2725
- description: seoDescription || heroSubtitle,
2726
- productName: resolvedProductName,
2727
- keywords: seoKeywords
2728
- }
2729
- ),
2730
- /* @__PURE__ */ jsx("section", { className: "py-16 md:py-20 px-4 sm:px-6 lg:px-8 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto", children: [
2731
- /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-xs font-medium text-primary mb-6", children: [
2732
- /* @__PURE__ */ jsx(History, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2733
- heroEyebrow
2734
- ] }),
2735
- /* @__PURE__ */ jsx("h1", { className: "text-4xl sm:text-5xl font-bold text-foreground mb-4", children: heroTitle }),
2736
- /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: heroSubtitle })
2737
- ] }) }),
2738
- /* @__PURE__ */ jsx("section", { className: "px-4 sm:px-6 lg:px-8 pb-20", children: isPropDriven ? /* @__PURE__ */ jsx(
2739
- EntriesTimeline,
2740
- {
2741
- entries,
2742
- pageSize,
2743
- productName: resolvedProductName
2744
- }
2745
- ) : /* @__PURE__ */ jsx(
2746
- CmsTimeline,
2747
- {
2748
- entries: cms.entries,
2749
- loading: cms.loading,
2750
- error: cms.error
2751
- }
2752
- ) }),
2753
- showCta && /* @__PURE__ */ jsx("section", { className: "py-14 px-4 sm:px-6 lg:px-8 bg-primary/5 border-t border-border/40", children: /* @__PURE__ */ jsxs("div", { className: "max-w-2xl mx-auto text-center", children: [
2754
- ctaTitle && /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-foreground mb-3", children: ctaTitle }),
2755
- ctaSubtitle && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-7", children: ctaSubtitle }),
2756
- /* @__PURE__ */ jsx(
2757
- "a",
2758
- {
2759
- href: ctaHref,
2760
- className: "inline-flex items-center justify-center rounded-lg bg-primary px-6 py-3 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity",
2761
- children: ctaLabel
2762
- }
2763
- )
2764
- ] }) })
2765
- ] });
2766
- }
2767
-
2768
- // src/content/utils.ts
2769
- function formatBytes(bytes) {
2770
- if (bytes == null) return "";
2771
- if (bytes < 1024) return `${bytes} B`;
2772
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
2773
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
2774
- }
2775
- function fileExt(file) {
2776
- const name = file.fileName || file.fileUrl.split("/").pop()?.split("?")[0] || "";
2777
- const ext = name.split(".").pop();
2778
- return ext && ext !== name ? ext.toUpperCase() : "FILE";
2779
- }
2780
- async function downloadFile(e, file) {
2781
- e.preventDefault();
2782
- const openInTab = () => window.open(file.fileUrl, "_blank", "noopener,noreferrer");
2783
- const name = file.fileName || file.fileUrl.split("/").pop()?.split("?")[0] || file.title || "download";
2784
- try {
2785
- const res = await fetch(file.fileUrl, { mode: "cors" });
2786
- if (!res.ok) {
2787
- openInTab();
2788
- return;
2789
- }
2790
- const blob = await res.blob();
2791
- const objUrl = URL.createObjectURL(blob);
2792
- const a = document.createElement("a");
2793
- a.href = objUrl;
2794
- a.download = name;
2795
- document.body.appendChild(a);
2796
- a.click();
2797
- a.remove();
2798
- URL.revokeObjectURL(objUrl);
2799
- } catch {
2800
- openInTab();
2801
- }
2802
- }
2803
- var PRESS_KIT_QUERY = `
2804
- query PublicPressKit($productId: String!, $limit: Int, $offset: Int) {
2805
- publicPressKitAssets(productId: $productId, limit: $limit, offset: $offset) {
2806
- items {
2807
- id
2808
- category
2809
- title
2810
- slug
2811
- description
2812
- fileUrl
2813
- fileName
2814
- fileSize
2815
- mimeType
2816
- thumbnailUrl
2817
- version
2818
- tags
2819
- sortOrder
2820
- updatedAt
2821
- }
2822
- total
2823
- hasMore
2824
- }
2825
- }
2826
- `;
2827
- var PRESS_RELEASES_QUERY = `
2828
- query PublicPressReleases($productId: String!, $limit: Int, $offset: Int) {
2829
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
2830
- items {
2831
- id
2832
- title
2833
- slug
2834
- lastUpdated
2835
- tags
2836
- seoDescription
2837
- excerpt
2838
- thumbnail
2839
- author
2840
- publishedAt
2841
- readingTime
2842
- featured
2843
- category
2844
- }
2845
- total
2846
- hasMore
2847
- }
2848
- }
2849
- `;
2850
- var CATEGORY_ORDER = [
2851
- "LOGO",
2852
- "BANNER",
2853
- "SCREENSHOT",
2854
- "BRAND_GUIDE",
2855
- "BROCHURE",
2856
- "DOCUMENT",
2857
- "PRESS_RELEASE"
2858
- ];
2859
- var CATEGORY_LABELS = {
2860
- LOGO: "Logos",
2861
- BANNER: "Banners",
2862
- SCREENSHOT: "Screenshots",
2863
- BRAND_GUIDE: "Brand Guides",
2864
- BROCHURE: "Brochures",
2865
- DOCUMENT: "Documents",
2866
- PRESS_RELEASE: "Press Releases"
2867
- };
2868
- function isImageAsset(a) {
2869
- return (a.mimeType || "").startsWith("image/");
2870
- }
2871
- function PressKitLightbox({
2872
- asset,
2873
- onClose
2874
- }) {
2875
- useEffect(() => {
2876
- if (!asset) return;
2877
- const onKey = (e) => {
2878
- if (e.key === "Escape") onClose();
2879
- };
2880
- document.addEventListener("keydown", onKey);
2881
- const prevOverflow = document.body.style.overflow;
2882
- document.body.style.overflow = "hidden";
2883
- return () => {
2884
- document.removeEventListener("keydown", onKey);
2885
- document.body.style.overflow = prevOverflow;
2886
- };
2887
- }, [asset, onClose]);
2888
- if (!asset) return null;
2889
- const ext = (asset.mimeType || "file").split("/").pop()?.toUpperCase() || "FILE";
2890
- const size = formatBytes(asset.fileSize);
2891
- return /* @__PURE__ */ jsx(
2892
- "div",
2893
- {
2894
- role: "dialog",
2895
- "aria-modal": "true",
2896
- "aria-label": `${asset.title} preview`,
2897
- onClick: onClose,
2898
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4 sm:p-8",
2899
- children: /* @__PURE__ */ jsxs(
2900
- "div",
2901
- {
2902
- onClick: (e) => e.stopPropagation(),
2903
- className: "relative flex max-h-full w-full max-w-4xl flex-col overflow-hidden rounded-xl border bg-card shadow-2xl",
2904
- children: [
2905
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 border-b p-4", children: [
2906
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2907
- /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-foreground", children: asset.title }),
2908
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
2909
- ext,
2910
- size ? ` \xB7 ${size}` : ""
2911
- ] })
2912
- ] }),
2913
- /* @__PURE__ */ jsx(
2914
- "button",
2915
- {
2916
- type: "button",
2917
- onClick: onClose,
2918
- "aria-label": "Close preview",
2919
- className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2920
- children: /* @__PURE__ */ jsx(
2921
- "svg",
2922
- {
2923
- className: "h-5 w-5",
2924
- fill: "none",
2925
- viewBox: "0 0 24 24",
2926
- stroke: "currentColor",
2927
- strokeWidth: 2,
2928
- children: /* @__PURE__ */ jsx(
2929
- "path",
2930
- {
2931
- strokeLinecap: "round",
2932
- strokeLinejoin: "round",
2933
- d: "M6 18L18 6M6 6l12 12"
2934
- }
2935
- )
2936
- }
2937
- )
2938
- }
2939
- )
2940
- ] }),
2941
- /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center overflow-auto bg-muted/20 p-4", children: /* @__PURE__ */ jsx(
2942
- "img",
2943
- {
2944
- src: asset.fileUrl,
2945
- alt: asset.title,
2946
- className: "max-h-[70vh] w-auto max-w-full object-contain"
2947
- }
2948
- ) }),
2949
- /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t p-4", children: /* @__PURE__ */ jsxs(
2950
- "a",
2951
- {
2952
- href: asset.fileUrl,
2953
- target: "_blank",
2954
- rel: "noopener noreferrer",
2955
- download: true,
2956
- onClick: (e) => {
2957
- void downloadFile(e, asset);
2958
- },
2959
- className: "inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90",
2960
- children: [
2961
- /* @__PURE__ */ jsx(
2962
- "svg",
2963
- {
2964
- className: "h-4 w-4",
2965
- fill: "none",
2966
- viewBox: "0 0 24 24",
2967
- stroke: "currentColor",
2968
- strokeWidth: 2,
2969
- children: /* @__PURE__ */ jsx(
2970
- "path",
2971
- {
2972
- strokeLinecap: "round",
2973
- strokeLinejoin: "round",
2974
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
2975
- }
2976
- )
2977
- }
2978
- ),
2979
- "Download"
2980
- ]
2981
- }
2982
- ) })
2983
- ]
2984
- }
2985
- )
2986
- }
2987
- );
2988
- }
2989
- function AssetCard({
2990
- asset,
2991
- onPreview
2992
- }) {
2993
- const date = asset.updatedAt ? new Date(asset.updatedAt).toLocaleDateString("en-US", {
2994
- year: "numeric",
2995
- month: "short",
2996
- day: "numeric"
2997
- }) : "";
2998
- const ext = (asset.mimeType || "file").split("/").pop()?.toUpperCase() || "FILE";
2999
- const size = formatBytes(asset.fileSize);
3000
- const [imgError, setImgError] = useState(false);
3001
- const thumb = asset.thumbnailUrl && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden bg-muted/30", children: /* @__PURE__ */ jsx(
3002
- "img",
3003
- {
3004
- src: asset.thumbnailUrl,
3005
- alt: asset.title,
3006
- className: "w-full h-full object-contain group-hover:scale-105 transition-transform duration-300",
3007
- loading: "lazy",
3008
- onError: () => setImgError(true)
3009
- }
3010
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
3011
- "svg",
3012
- {
3013
- className: "w-12 h-12 text-muted-foreground/20",
3014
- fill: "none",
3015
- viewBox: "0 0 24 24",
3016
- stroke: "currentColor",
3017
- children: /* @__PURE__ */ jsx(
3018
- "path",
3019
- {
3020
- strokeLinecap: "round",
3021
- strokeLinejoin: "round",
3022
- strokeWidth: 1.5,
3023
- d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
3024
- }
3025
- )
3026
- }
3027
- ) });
3028
- return /* @__PURE__ */ jsxs("div", { className: "group flex flex-col bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300", children: [
3029
- onPreview && isImageAsset(asset) ? /* @__PURE__ */ jsx(
3030
- "button",
3031
- {
3032
- type: "button",
3033
- onClick: () => onPreview(asset),
3034
- "aria-label": `Preview ${asset.title}`,
3035
- className: "block w-full text-left cursor-zoom-in",
3036
- children: thumb
3037
- }
3038
- ) : /* @__PURE__ */ jsx(
3039
- "a",
3040
- {
3041
- href: asset.fileUrl,
3042
- target: "_blank",
3043
- rel: "noopener noreferrer",
3044
- "aria-label": `Open ${asset.title}`,
3045
- className: "block",
3046
- children: thumb
3047
- }
3048
- ),
3049
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-5", children: [
3050
- /* @__PURE__ */ jsx("span", { className: "inline-block self-start text-xs font-medium text-primary bg-primary/10 px-2.5 py-1 rounded-full mb-3", children: CATEGORY_LABELS[asset.category] ?? asset.category }),
3051
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground mb-1 line-clamp-2", children: asset.title }),
3052
- asset.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3 line-clamp-2", children: asset.description }),
3053
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-3", children: [
3054
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
3055
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: ext }),
3056
- size && /* @__PURE__ */ jsxs(Fragment, { children: [
3057
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
3058
- /* @__PURE__ */ jsx("span", { children: size })
3059
- ] }),
3060
- date && /* @__PURE__ */ jsxs(Fragment, { children: [
3061
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
3062
- /* @__PURE__ */ jsx("span", { children: date })
3063
- ] })
3064
- ] }),
3065
- /* @__PURE__ */ jsxs(
3066
- "a",
3067
- {
3068
- href: asset.fileUrl,
3069
- target: "_blank",
3070
- rel: "noopener noreferrer",
3071
- download: true,
3072
- onClick: (e) => {
3073
- void downloadFile(e, asset);
3074
- },
3075
- className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline",
3076
- children: [
3077
- /* @__PURE__ */ jsx(
3078
- "svg",
3079
- {
3080
- className: "w-4 h-4",
3081
- fill: "none",
3082
- viewBox: "0 0 24 24",
3083
- stroke: "currentColor",
3084
- strokeWidth: 2,
3085
- children: /* @__PURE__ */ jsx(
3086
- "path",
3087
- {
3088
- strokeLinecap: "round",
3089
- strokeLinejoin: "round",
3090
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
3091
- }
3092
- )
3093
- }
3094
- ),
3095
- "Download"
3096
- ]
3097
- }
3098
- )
3099
- ] })
3100
- ] })
3101
- ] });
3102
- }
3103
- function PressReleaseCard({
3104
- release,
3105
- basePath
3106
- }) {
3107
- const date = release.publishedAt || release.lastUpdated;
3108
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
3109
- year: "numeric",
3110
- month: "long",
3111
- day: "numeric"
3112
- }) : "";
3113
- const [imgError, setImgError] = useState(false);
3114
- return /* @__PURE__ */ jsxs(
3115
- "a",
3116
- {
3117
- href: `${basePath}/${release.slug}`,
3118
- className: "group flex flex-col bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300",
3119
- children: [
3120
- release.thumbnail && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden bg-muted/30", children: /* @__PURE__ */ jsx(
3121
- "img",
3122
- {
3123
- src: release.thumbnail,
3124
- alt: release.title,
3125
- className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
3126
- loading: "lazy",
3127
- onError: () => setImgError(true)
3128
- }
3129
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
3130
- "svg",
3131
- {
3132
- className: "w-12 h-12 text-muted-foreground/20",
3133
- fill: "none",
3134
- viewBox: "0 0 24 24",
3135
- stroke: "currentColor",
3136
- children: /* @__PURE__ */ jsx(
3137
- "path",
3138
- {
3139
- strokeLinecap: "round",
3140
- strokeLinejoin: "round",
3141
- strokeWidth: 1.5,
3142
- d: "M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m-1 13a2 2 0 01-2-2V7m3 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
3143
- }
3144
- )
3145
- }
3146
- ) }),
3147
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-5 sm:p-6", children: [
3148
- release.category && /* @__PURE__ */ jsx("span", { className: "inline-block self-start text-xs font-medium text-primary bg-primary/10 px-2.5 py-1 rounded-full mb-3", children: release.category }),
3149
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-2", children: release.title }),
3150
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4 line-clamp-3", children: release.excerpt || release.seoDescription || "" }),
3151
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center gap-3 text-xs text-muted-foreground", children: [
3152
- release.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: release.author }),
3153
- release.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
3154
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
3155
- release.readingTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
3156
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
3157
- /* @__PURE__ */ jsxs("span", { children: [
3158
- release.readingTime,
3159
- " min read"
3160
- ] })
3161
- ] }) : null
3162
- ] })
3163
- ] })
3164
- ]
3165
- }
3166
- );
3167
- }
3168
- function PressKitPage(props) {
3169
- const {
3170
- productName,
3171
- heroTitle = "Press & Media Kit",
3172
- heroSubtitle = "Official logos, brand assets, and media resources. Everything you need to write about us.",
3173
- mediaContactEmail,
3174
- brandKitUrl,
3175
- className = "",
3176
- seoTitle,
3177
- seoDescription,
3178
- seoKeywords,
3179
- showPressReleases = true,
3180
- pressReleaseBasePath = "/press",
3181
- pressReleasesTitle = "Press Releases"
3182
- } = props;
3183
- const client = useWebSDK();
3184
- const config = useWebSDKConfig();
3185
- const { product } = useProduct();
3186
- const resolvedProductId = product?.id || config.productId;
3187
- const contactEmail = mediaContactEmail || product?.contactEmail || void 0;
3188
- const [assets, setAssets] = useState([]);
3189
- const [loading, setLoading] = useState(true);
3190
- const [error, setError] = useState(null);
3191
- const [activeCategory, setActiveCategory] = useState("ALL");
3192
- const [releases, setReleases] = useState([]);
3193
- const [releasesLoading, setReleasesLoading] = useState(true);
3194
- const [preview, setPreview] = useState(null);
3195
- const fetchAssets = useCallback(async () => {
3196
- if (!resolvedProductId) {
3197
- setLoading(false);
3198
- return;
3199
- }
3200
- setLoading(true);
3201
- try {
3202
- const PAGE_SIZE = 100;
3203
- const MAX_PAGES = 10;
3204
- const all = [];
3205
- let offset = 0;
3206
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
3207
- const result = await client.query(PRESS_KIT_QUERY, {
3208
- productId: resolvedProductId,
3209
- limit: PAGE_SIZE,
3210
- offset
3211
- });
3212
- if (result.errors?.length) {
3213
- setError(result.errors[0]?.message ?? "Failed to load press kit");
3214
- return;
3215
- }
3216
- const page = result.data?.publicPressKitAssets;
3217
- const items = page?.items || [];
3218
- all.push(...items);
3219
- if (!page?.hasMore || items.length === 0) break;
3220
- offset += PAGE_SIZE;
3221
- }
3222
- setAssets(all);
3223
- setError(null);
3224
- } catch {
3225
- setError("Failed to load press kit");
3226
- } finally {
3227
- setLoading(false);
3228
- }
3229
- }, [client, resolvedProductId]);
3230
- useEffect(() => {
3231
- fetchAssets();
3232
- }, [fetchAssets]);
3233
- const fetchReleases = useCallback(async () => {
3234
- if (!resolvedProductId || !showPressReleases) {
3235
- setReleasesLoading(false);
3236
- return;
3237
- }
3238
- setReleasesLoading(true);
3239
- try {
3240
- const PAGE_SIZE = 100;
3241
- const MAX_PAGES = 10;
3242
- const all = [];
3243
- let offset = 0;
3244
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
3245
- const result = await client.query(PRESS_RELEASES_QUERY, {
3246
- productId: resolvedProductId,
3247
- limit: PAGE_SIZE,
3248
- offset
3249
- });
3250
- if (result.errors?.length) {
3251
- setReleases([]);
3252
- return;
3253
- }
3254
- const page = result.data?.publicContentPages;
3255
- const items = page?.items || [];
3256
- all.push(...items);
3257
- if (!page?.hasMore || items.length === 0) break;
3258
- offset += PAGE_SIZE;
3259
- }
3260
- const pressOnly = all.filter((p) => p.tags?.includes("press")).sort((a, b) => {
3261
- const da = new Date(a.publishedAt || a.lastUpdated || 0).getTime();
3262
- const db = new Date(b.publishedAt || b.lastUpdated || 0).getTime();
3263
- return db - da;
3264
- });
3265
- setReleases(pressOnly);
3266
- } catch {
3267
- setReleases([]);
3268
- } finally {
3269
- setReleasesLoading(false);
3270
- }
3271
- }, [client, resolvedProductId, showPressReleases]);
3272
- useEffect(() => {
3273
- fetchReleases();
3274
- }, [fetchReleases]);
3275
- const presentCategories = CATEGORY_ORDER.filter(
3276
- (c) => assets.some((a) => a.category === c)
3277
- );
3278
- const visibleAssets = activeCategory === "ALL" ? assets : assets.filter((a) => a.category === activeCategory);
3279
- const grouped = (activeCategory === "ALL" ? presentCategories : [activeCategory]).map((cat) => ({
3280
- cat,
3281
- items: visibleAssets.filter((a) => a.category === cat).sort((a, b) => a.sortOrder - b.sortOrder)
3282
- })).filter((g) => g.items.length > 0);
3283
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
3284
- seoTitle && /* @__PURE__ */ jsx(
3285
- PageHead,
3286
- {
3287
- title: seoTitle,
3288
- description: seoDescription || heroSubtitle,
3289
- productName,
3290
- keywords: seoKeywords
3291
- }
3292
- ),
3293
- /* @__PURE__ */ jsx("section", { className: "py-16 sm:py-20 md:py-24", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-3xl mx-auto", children: [
3294
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground", children: heroTitle }),
3295
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle }),
3296
- (brandKitUrl || contactEmail) && /* @__PURE__ */ jsxs("div", { className: "mt-8 flex flex-wrap items-center justify-center gap-4", children: [
3297
- brandKitUrl && /* @__PURE__ */ jsx(
3298
- "a",
3299
- {
3300
- href: brandKitUrl,
3301
- target: "_blank",
3302
- rel: "noopener noreferrer",
3303
- className: "inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-primary text-primary-foreground font-medium hover:opacity-90 transition-opacity",
3304
- children: "Download brand kit"
3305
- }
3306
- ),
3307
- contactEmail && /* @__PURE__ */ jsx(
3308
- "a",
3309
- {
3310
- href: `mailto:${contactEmail}`,
3311
- className: "inline-flex items-center gap-2 px-6 py-3 rounded-lg border font-medium text-foreground hover:bg-muted transition-colors",
3312
- children: "Media inquiries"
3313
- }
3314
- )
3315
- ] })
3316
- ] }) }) }),
3317
- showPressReleases && (releasesLoading || releases.length > 0) && /* @__PURE__ */ jsx("section", { className: "pb-16 sm:pb-20", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
3318
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground mb-6", children: pressReleasesTitle }),
3319
- /* @__PURE__ */ jsx("div", { className: "grid sm:grid-cols-2 lg:grid-cols-3 gap-6", children: releasesLoading ? [1, 2, 3].map((i) => /* @__PURE__ */ jsxs(
3320
- "div",
3321
- {
3322
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
3323
- children: [
3324
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
3325
- /* @__PURE__ */ jsxs("div", { className: "p-5 space-y-3", children: [
3326
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
3327
- /* @__PURE__ */ jsx("div", { className: "h-5 bg-muted rounded w-3/4" }),
3328
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
3329
- ] })
3330
- ]
3331
- },
3332
- i
3333
- )) : releases.map((r) => /* @__PURE__ */ jsx(
3334
- PressReleaseCard,
3335
- {
3336
- release: r,
3337
- basePath: pressReleaseBasePath
3338
- },
3339
- r.id
3340
- )) })
3341
- ] }) }),
3342
- /* @__PURE__ */ jsx("section", { className: "pb-16 sm:pb-20", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: loading ? /* @__PURE__ */ jsx("div", { className: "grid sm:grid-cols-2 lg:grid-cols-3 gap-6", children: [1, 2, 3, 4, 5, 6].map((i) => /* @__PURE__ */ jsxs(
3343
- "div",
3344
- {
3345
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
3346
- children: [
3347
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
3348
- /* @__PURE__ */ jsxs("div", { className: "p-5 space-y-3", children: [
3349
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
3350
- /* @__PURE__ */ jsx("div", { className: "h-5 bg-muted rounded w-3/4" }),
3351
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
3352
- ] })
3353
- ]
3354
- },
3355
- i
3356
- )) }) : error ? /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: error }) }) : assets.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
3357
- "Press materials are coming soon. For media inquiries",
3358
- contactEmail ? /* @__PURE__ */ jsxs(Fragment, { children: [
3359
- ", contact ",
3360
- /* @__PURE__ */ jsx(
3361
- "a",
3362
- {
3363
- href: `mailto:${contactEmail}`,
3364
- className: "text-primary underline underline-offset-4 hover:no-underline",
3365
- children: contactEmail
3366
- }
3367
- )
3368
- ] }) : " please reach out to our team",
3369
- "."
3370
- ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3371
- presentCategories.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mb-10 justify-center", children: ["ALL", ...presentCategories].map((c) => {
3372
- const active = activeCategory === c;
3373
- return /* @__PURE__ */ jsx(
3374
- "button",
3375
- {
3376
- type: "button",
3377
- onClick: () => setActiveCategory(c),
3378
- className: `px-4 py-2 rounded-full text-sm font-medium transition-colors ${active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`,
3379
- children: c === "ALL" ? "All" : CATEGORY_LABELS[c] ?? c
3380
- },
3381
- c
3382
- );
3383
- }) }),
3384
- grouped.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-12", children: [
3385
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground mb-5", children: CATEGORY_LABELS[group.cat] ?? group.cat }),
3386
- /* @__PURE__ */ jsx("div", { className: "grid sm:grid-cols-2 lg:grid-cols-3 gap-6", children: group.items.map((a) => /* @__PURE__ */ jsx(AssetCard, { asset: a, onPreview: setPreview }, a.id)) })
3387
- ] }, group.cat)),
3388
- contactEmail && /* @__PURE__ */ jsxs("div", { className: "mt-8 border-t pt-10 text-center", children: [
3389
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground mb-2", children: "Media Contact" }),
3390
- /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
3391
- "For interviews, partnerships, and press inquiries:",
3392
- " ",
3393
- /* @__PURE__ */ jsx(
3394
- "a",
3395
- {
3396
- href: `mailto:${contactEmail}`,
3397
- className: "text-primary underline underline-offset-4 hover:no-underline",
3398
- children: contactEmail
3399
- }
3400
- )
3401
- ] })
3402
- ] })
3403
- ] }) }) }),
3404
- /* @__PURE__ */ jsx(PressKitLightbox, { asset: preview, onClose: () => setPreview(null) })
3405
- ] });
3406
- }
3407
- var CONTRACTS_QUERY = `
3408
- query PublicContracts($productId: String!, $limit: Int, $offset: Int) {
3409
- publicPressKitAssets(productId: $productId, category: CONTRACT, limit: $limit, offset: $offset) {
3410
- items {
3411
- id
3412
- title
3413
- slug
3414
- description
3415
- fileUrl
3416
- fileName
3417
- fileSize
3418
- mimeType
3419
- version
3420
- tags
3421
- sortOrder
3422
- updatedAt
3423
- }
3424
- hasMore
3425
- }
3426
- }
3427
- `;
3428
- function ContractsPage(props) {
3429
- const {
3430
- productName,
3431
- heroTitle = "Contracts & Legal Agreements",
3432
- heroSubtitle = "Download our standard legal agreements and policies.",
3433
- className = "",
3434
- seoTitle,
3435
- seoDescription,
3436
- seoKeywords
3437
- } = props;
3438
- const client = useWebSDK();
3439
- const config = useWebSDKConfig();
3440
- const { product } = useProduct();
3441
- const resolvedProductId = product?.id || config.productId;
3442
- const displayName = productName ?? product?.name ?? config.productId;
3443
- const [contracts, setContracts] = useState([]);
3444
- const [loading, setLoading] = useState(true);
3445
- const [error, setError] = useState(null);
3446
- const fetchContracts = useCallback(async () => {
3447
- if (!resolvedProductId) {
3448
- setLoading(false);
3449
- return;
3450
- }
3451
- setLoading(true);
3452
- try {
3453
- const PAGE_SIZE = 100;
3454
- const MAX_PAGES = 10;
3455
- const all = [];
3456
- let offset = 0;
3457
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
3458
- const result = await client.query(CONTRACTS_QUERY, {
3459
- productId: resolvedProductId,
3460
- limit: PAGE_SIZE,
3461
- offset
3462
- });
3463
- if (result.errors?.length) {
3464
- setError(result.errors[0]?.message ?? "Failed to load contracts");
3465
- return;
3466
- }
3467
- const page = result.data?.publicPressKitAssets;
3468
- const items = page?.items || [];
3469
- all.push(...items);
3470
- if (!page?.hasMore || items.length === 0) break;
3471
- offset += PAGE_SIZE;
3472
- }
3473
- all.sort(
3474
- (a, b) => a.sortOrder - b.sortOrder || a.title.localeCompare(b.title)
3475
- );
3476
- setContracts(all);
3477
- setError(null);
3478
- } catch {
3479
- setError("Failed to load contracts");
3480
- } finally {
3481
- setLoading(false);
3482
- }
3483
- }, [client, resolvedProductId]);
3484
- useEffect(() => {
3485
- fetchContracts();
3486
- }, [fetchContracts]);
3487
- return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-background ${className}`, children: [
3488
- /* @__PURE__ */ jsx(
3489
- PageHead,
3490
- {
3491
- title: seoTitle || `Contracts \u2014 ${displayName}`,
3492
- description: seoDescription || heroSubtitle,
3493
- productName,
3494
- keywords: seoKeywords
3495
- }
3496
- ),
3497
- /* @__PURE__ */ jsx("section", { className: "border-b border-border bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center", children: [
3498
- /* @__PURE__ */ jsx("h1", { className: "text-4xl md:text-5xl font-bold text-foreground mb-4", children: heroTitle }),
3499
- /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground max-w-2xl mx-auto", children: heroSubtitle })
3500
- ] }) }),
3501
- /* @__PURE__ */ jsx("section", { className: "max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-20", children: /* @__PURE__ */ jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : error ? /* @__PURE__ */ jsx("div", { className: "text-center py-16", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: error }) }) : contracts.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-16", children: /* @__PURE__ */ jsx("p", { className: "text-base text-muted-foreground", children: "No contracts are available yet." }) }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border rounded-xl border border-border overflow-hidden bg-card", children: contracts.map((c) => {
3502
- const ext = fileExt(c);
3503
- const size = formatBytes(c.fileSize);
3504
- return /* @__PURE__ */ jsxs(
3505
- "li",
3506
- {
3507
- className: "flex items-center justify-between gap-4 p-5 hover:bg-muted/40 transition-colors",
3508
- children: [
3509
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3510
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
3511
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground truncate", children: c.title }),
3512
- c.version && /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-primary bg-primary/10 px-2 py-0.5 rounded-full", children: c.version })
3513
- ] }),
3514
- c.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 line-clamp-2", children: c.description }),
3515
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: [
3516
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: ext }),
3517
- size && /* @__PURE__ */ jsxs(Fragment, { children: [
3518
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
3519
- /* @__PURE__ */ jsx("span", { children: size })
3520
- ] })
3521
- ] })
3522
- ] }),
3523
- /* @__PURE__ */ jsxs(
3524
- "a",
3525
- {
3526
- href: c.fileUrl,
3527
- target: "_blank",
3528
- rel: "noopener noreferrer",
3529
- download: true,
3530
- "aria-label": `Download ${c.title}`,
3531
- onClick: (e) => {
3532
- void downloadFile(e, c);
3533
- },
3534
- className: "flex-shrink-0 inline-flex items-center gap-1.5 text-sm font-medium text-primary-foreground bg-primary hover:opacity-90 px-4 py-2 rounded-lg transition-opacity",
3535
- children: [
3536
- /* @__PURE__ */ jsx(
3537
- "svg",
3538
- {
3539
- className: "w-4 h-4",
3540
- fill: "none",
3541
- viewBox: "0 0 24 24",
3542
- stroke: "currentColor",
3543
- "aria-hidden": "true",
3544
- children: /* @__PURE__ */ jsx(
3545
- "path",
3546
- {
3547
- strokeLinecap: "round",
3548
- strokeLinejoin: "round",
3549
- strokeWidth: 2,
3550
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
3551
- }
3552
- )
3553
- }
3554
- ),
3555
- "Download"
3556
- ]
3557
- }
3558
- )
3559
- ]
3560
- },
3561
- c.id
3562
- );
3563
- }) }) })
3564
- ] });
3565
- }
3566
-
3567
- export { BlogListPage, BlogPostPage, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ContentRenderer, ContractsPage, LEGAL_DOCUMENTS, LEGAL_DOCUMENT_KEYS, LegalPage, Markdown, PressKitPage, fetchLegalContent, fetchLegalContentPage, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, findLegalDocument, formatLegalDate, hasCustomerSignOff, splitLeadingHeading, useContentPage, useContentPages, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission };
1
+ export { BlogListPage, BlogPostPage, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ContractsPage, LEGAL_DOCUMENTS, LEGAL_DOCUMENT_KEYS, LegalPage, PressKitPage, fetchLegalContent, fetchLegalContentPage, fetchPublicJobPosting, fetchPublicJobPostings, fetchPublicJobPostingsForSubmission, findLegalDocument, formatLegalDate, hasCustomerSignOff, splitLeadingHeading, usePublicJobPosting, usePublicJobPostings, usePublicJobPostingsForSubmission } from '../chunk-D2TW7MNC.mjs';
2
+ export { ContentRenderer, useContentPage, useContentPages } from '../chunk-EDLTUOIA.mjs';
3
+ import '../chunk-6VJT3SM4.mjs';
4
+ export { Markdown } from '../chunk-EBZ2HGJ2.mjs';
5
+ import '../chunk-34RX5P75.mjs';
6
+ import '../chunk-N5EC53MH.mjs';
7
+ import '../chunk-KHYXK7FG.mjs';
8
+ import '../chunk-R7G6TWIS.mjs';
9
+ import '../chunk-GCFLCO2Z.mjs';
10
+ import '../chunk-KZANIV3S.mjs';
11
+ import '../chunk-5JRJ57CL.mjs';
12
+ import '../chunk-VNS5WDWO.mjs';
3568
13
  //# sourceMappingURL=index.mjs.map
3569
14
  //# sourceMappingURL=index.mjs.map