@burdenoff/website-sdk 0.0.0-dev → 2026.511.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/analytics/index.js +0 -1
  2. package/dist/analytics/index.js.map +1 -1
  3. package/dist/analytics/index.mjs +0 -1
  4. package/dist/analytics/index.mjs.map +1 -1
  5. package/dist/client/index.d.mts +2 -5
  6. package/dist/client/index.d.ts +2 -5
  7. package/dist/client/index.js +2 -11
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/index.mjs +2 -11
  10. package/dist/client/index.mjs.map +1 -1
  11. package/dist/components/contact.d.mts +2 -77
  12. package/dist/components/contact.d.ts +2 -77
  13. package/dist/components/contact.js +9 -112
  14. package/dist/components/contact.js.map +1 -1
  15. package/dist/components/contact.mjs +10 -112
  16. package/dist/components/contact.mjs.map +1 -1
  17. package/dist/components/newsletter.js +6 -17
  18. package/dist/components/newsletter.js.map +1 -1
  19. package/dist/components/newsletter.mjs +6 -17
  20. package/dist/components/newsletter.mjs.map +1 -1
  21. package/dist/components/pricing.d.mts +0 -38
  22. package/dist/components/pricing.d.ts +0 -38
  23. package/dist/components/pricing.js +84 -267
  24. package/dist/components/pricing.js.map +1 -1
  25. package/dist/components/pricing.mjs +84 -267
  26. package/dist/components/pricing.mjs.map +1 -1
  27. package/dist/components/waitlist.js +2 -6
  28. package/dist/components/waitlist.js.map +1 -1
  29. package/dist/components/waitlist.mjs +2 -6
  30. package/dist/components/waitlist.mjs.map +1 -1
  31. package/dist/content/index.d.mts +9 -181
  32. package/dist/content/index.d.ts +9 -181
  33. package/dist/content/index.js +16 -2177
  34. package/dist/content/index.js.map +1 -1
  35. package/dist/content/index.mjs +6 -2141
  36. package/dist/content/index.mjs.map +1 -1
  37. package/dist/index.d.mts +5 -43
  38. package/dist/index.d.ts +5 -43
  39. package/dist/index.js +373 -3919
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +378 -3905
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/product/index.d.mts +0 -8
  44. package/dist/product/index.d.ts +0 -8
  45. package/dist/product/index.js +4 -24
  46. package/dist/product/index.js.map +1 -1
  47. package/dist/product/index.mjs +4 -24
  48. package/dist/product/index.mjs.map +1 -1
  49. package/package.json +3 -5
  50. package/dist/components/electron-download.d.mts +0 -32
  51. package/dist/components/electron-download.d.ts +0 -32
  52. package/dist/components/electron-download.js +0 -131
  53. package/dist/components/electron-download.js.map +0 -1
  54. package/dist/components/electron-download.mjs +0 -108
  55. package/dist/components/electron-download.mjs.map +0 -1
  56. package/dist/components/launch-countdown.d.mts +0 -15
  57. package/dist/components/launch-countdown.d.ts +0 -15
  58. package/dist/components/launch-countdown.js +0 -89
  59. package/dist/components/launch-countdown.js.map +0 -1
  60. package/dist/components/launch-countdown.mjs +0 -87
  61. package/dist/components/launch-countdown.mjs.map +0 -1
  62. package/dist/components/partners.d.mts +0 -88
  63. package/dist/components/partners.d.ts +0 -88
  64. package/dist/components/partners.js +0 -1169
  65. package/dist/components/partners.js.map +0 -1
  66. package/dist/components/partners.mjs +0 -1143
  67. package/dist/components/partners.mjs.map +0 -1
  68. package/dist/components/problems-solved.d.mts +0 -37
  69. package/dist/components/problems-solved.d.ts +0 -37
  70. package/dist/components/problems-solved.js +0 -157
  71. package/dist/components/problems-solved.js.map +0 -1
  72. package/dist/components/problems-solved.mjs +0 -135
  73. package/dist/components/problems-solved.mjs.map +0 -1
  74. package/dist/components/resource-links.d.mts +0 -28
  75. package/dist/components/resource-links.d.ts +0 -28
  76. package/dist/components/resource-links.js +0 -229
  77. package/dist/components/resource-links.js.map +0 -1
  78. package/dist/components/resource-links.mjs +0 -227
  79. package/dist/components/resource-links.mjs.map +0 -1
  80. package/dist/components/social-links.d.mts +0 -37
  81. package/dist/components/social-links.d.ts +0 -37
  82. package/dist/components/social-links.js +0 -268
  83. package/dist/components/social-links.js.map +0 -1
  84. package/dist/components/social-links.mjs +0 -266
  85. package/dist/components/social-links.mjs.map +0 -1
  86. package/dist/components/website-switcher.d.mts +0 -27
  87. package/dist/components/website-switcher.d.ts +0 -27
  88. package/dist/components/website-switcher.js +0 -168
  89. package/dist/components/website-switcher.js.map +0 -1
  90. package/dist/components/website-switcher.mjs +0 -146
  91. package/dist/components/website-switcher.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
- import { createContext, useMemo, useContext, useState, useCallback, useLayoutEffect, useRef, useEffect } from 'react';
2
+ import { createContext, useMemo, useContext, useRef, useState, useCallback, useLayoutEffect, useEffect } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { Instagram, Youtube, Facebook, Github, Linkedin, Twitter, Mail, Phone, MapPin, Clock, Send, CheckCircle, Upload, Globe, ChevronDown, Search, Rocket, ArrowRight, Download, History, ChevronLeft, ChevronRight, FileText } from 'lucide-react';
5
- import ReCAPTCHA3 from 'react-google-recaptcha';
4
+ import { Mail, Phone, MapPin, Clock, Send } from 'lucide-react';
5
+ import ReCAPTCHA2 from 'react-google-recaptcha';
6
6
  import { toast } from 'sonner';
7
7
  import { Helmet } from 'react-helmet-async';
8
8
  import { Slot } from '@radix-ui/react-slot';
@@ -13,7 +13,6 @@ import * as LabelPrimitive from '@radix-ui/react-label';
13
13
  import { zodResolver } from '@hookform/resolvers/zod';
14
14
  import { useForm } from 'react-hook-form';
15
15
  import { z } from 'zod';
16
- import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
17
16
 
18
17
  var __defProp = Object.defineProperty;
19
18
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -168,7 +167,6 @@ function getLocalFallback() {
168
167
  }
169
168
  var PRODUCT_DOMAINS = [
170
169
  "burdenoff.com",
171
- "algoshred.com",
172
170
  "vibecontrols.com",
173
171
  "fluidgrids.com",
174
172
  "botlit.ai",
@@ -213,25 +211,17 @@ function isAlphaHost(hostname) {
213
211
  );
214
212
  }
215
213
  function resolveGatewayUrl(explicitUrl) {
216
- if (explicitUrl && isAbsoluteUrl(explicitUrl) && !isLocalhostUrl(explicitUrl)) {
214
+ if (explicitUrl && !isLocalhostUrl(explicitUrl)) {
217
215
  return explicitUrl;
218
216
  }
219
217
  if (typeof window === "undefined") {
220
- return explicitUrl && isAbsoluteUrl(explicitUrl) ? explicitUrl : getLocalFallback();
218
+ return explicitUrl || getLocalFallback();
221
219
  }
222
220
  const hostname = window.location.hostname.toLowerCase();
223
221
  if (isProductionHost(hostname)) return PROD_GATEWAY;
224
222
  if (isAlphaHost(hostname)) return ALPHA_GATEWAY;
225
223
  return getLocalFallback();
226
224
  }
227
- function isAbsoluteUrl(url) {
228
- try {
229
- new URL(url);
230
- return true;
231
- } catch {
232
- return false;
233
- }
234
- }
235
225
  function isLocalhostUrl(url) {
236
226
  try {
237
227
  const parsed = new URL(url);
@@ -263,125 +253,6 @@ function useWebSDKConfig() {
263
253
  const client = useWebSDK();
264
254
  return client.getConfig();
265
255
  }
266
- var PRODUCT_FIELDS_BASE = `
267
- id
268
- name
269
- slug
270
- description
271
- status
272
- logoUrl
273
- website
274
- appUrl
275
- docsUrl
276
- contactEmail
277
- supportEmail
278
- contactPhone
279
- contactAddress
280
- seoTitle
281
- seoDescription
282
- seoKeywords
283
- ogImage
284
- socialLinks
285
- recaptchaSiteKey
286
- rybbitSiteId
287
- notificationEmail
288
- metadata
289
- `;
290
- var PUBLIC_PRODUCT_QUERY = `
291
- query PublicProduct($slug: String!) {
292
- publicProduct(slug: $slug) {
293
- ${PRODUCT_FIELDS_BASE}
294
- resourceLinks
295
- }
296
- }
297
- `;
298
- var PUBLIC_PRODUCT_QUERY_FALLBACK = `
299
- query PublicProductFallback($slug: String!) {
300
- publicProduct(slug: $slug) {
301
- ${PRODUCT_FIELDS_BASE}
302
- }
303
- }
304
- `;
305
- var ProductContext = createContext({
306
- product: null,
307
- loading: true,
308
- error: null,
309
- hasProvider: false,
310
- refetch: () => {
311
- }
312
- });
313
- function ProductProvider({ children, fallback }) {
314
- const client = useWebSDK();
315
- const config = useWebSDKConfig();
316
- const [product, setProduct] = useState(
317
- fallback ? fallback : null
318
- );
319
- const [loading, setLoading] = useState(true);
320
- const [error, setError] = useState(null);
321
- const fetchProduct = useCallback(async () => {
322
- if (!config.productSlug) {
323
- setLoading(false);
324
- return;
325
- }
326
- setLoading(true);
327
- setError(null);
328
- try {
329
- let result = await client.query(
330
- PUBLIC_PRODUCT_QUERY,
331
- { slug: config.productSlug }
332
- );
333
- const schemaMissingResourceLinks = result.errors?.some(
334
- (e) => /Cannot query field ["']?resourceLinks["']?/i.test(e?.message ?? "")
335
- );
336
- if (schemaMissingResourceLinks) {
337
- result = await client.query(
338
- PUBLIC_PRODUCT_QUERY_FALLBACK,
339
- { slug: config.productSlug }
340
- );
341
- }
342
- if (result.errors?.length) {
343
- setError(result.errors[0]?.message ?? "Unknown error");
344
- } else if (result.data?.publicProduct) {
345
- setProduct(result.data.publicProduct);
346
- }
347
- } catch (err) {
348
- setError(err instanceof Error ? err.message : "Failed to fetch product");
349
- } finally {
350
- setLoading(false);
351
- }
352
- }, [client, config.productSlug]);
353
- useLayoutEffect(() => {
354
- fetchProduct();
355
- }, [fetchProduct]);
356
- const value = useMemo(
357
- () => ({
358
- product,
359
- loading,
360
- error,
361
- hasProvider: true,
362
- refetch: fetchProduct
363
- }),
364
- [product, loading, error, fetchProduct]
365
- );
366
- return /* @__PURE__ */ jsx(ProductContext.Provider, { value, children });
367
- }
368
- function useProduct() {
369
- return useContext(ProductContext);
370
- }
371
- function useProductConfig() {
372
- const { product } = useProduct();
373
- const config = useWebSDKConfig();
374
- return {
375
- // Prefer product-level config, fall back to SDK config
376
- recaptchaSiteKey: product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? null,
377
- rybbitSiteId: product?.rybbitSiteId ?? config.rybbitSiteId ?? null,
378
- contactEmail: product?.contactEmail ?? null,
379
- supportEmail: product?.supportEmail ?? null,
380
- notificationEmail: product?.notificationEmail ?? product?.contactEmail ?? null,
381
- productName: product?.name ?? null,
382
- logoUrl: product?.logoUrl ?? null
383
- };
384
- }
385
256
  function PageHead({
386
257
  title,
387
258
  description,
@@ -505,28 +376,6 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
505
376
  }
506
377
  ));
507
378
  Label.displayName = LabelPrimitive.Root.displayName;
508
- var Select = React.forwardRef(({ className, children, ...props }, ref) => {
509
- return /* @__PURE__ */ jsx(
510
- "select",
511
- {
512
- className: cn(
513
- "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent bg-[length:1rem_1rem] bg-[right_0.75rem_center] bg-no-repeat px-3 py-1 pr-8 text-base shadow-sm transition-colors 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",
514
- // Inline SVG chevron: SVG inside a `background-image: url(data:…)`
515
- // does NOT inherit `currentColor` from the host element (the SVG is
516
- // its own document with no parent CSS context), so the stroke is
517
- // hardcoded to a muted slate-400 (#94a3b8). Matches the muted
518
- // foreground token visually in both light and dark themes well
519
- // enough for a passive chevron — addresses #9 review feedback.
520
- `bg-[url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>")]`,
521
- className
522
- ),
523
- ref,
524
- ...props,
525
- children
526
- }
527
- );
528
- });
529
- Select.displayName = "Select";
530
379
  var Textarea = React.forwardRef(({ className, ...props }, ref) => {
531
380
  return /* @__PURE__ */ jsx(
532
381
  "textarea",
@@ -541,15 +390,6 @@ var Textarea = React.forwardRef(({ className, ...props }, ref) => {
541
390
  );
542
391
  });
543
392
  Textarea.displayName = "Textarea";
544
- var DEFAULT_CONTACT_CATEGORIES = [
545
- { value: "general", label: "General enquiry" },
546
- { value: "sales", label: "Sales" },
547
- { value: "support", label: "Support" },
548
- { value: "partnership", label: "Partnership" },
549
- { value: "press", label: "Press / media" },
550
- { value: "careers", label: "Careers" },
551
- { value: "security", label: "Security disclosure" }
552
- ];
553
393
  var SUBMIT_CONTACT_INQUIRY_MUTATION = (
554
394
  /* GraphQL */
555
395
  `
@@ -571,12 +411,6 @@ async function submitContactForm(client, data, productId) {
571
411
  subject: data.subject,
572
412
  message: data.message,
573
413
  productId,
574
- // Send `category` only when the user actually picked one. Older
575
- // backend deployments without the `category` field in the
576
- // `SubmitContactInquiryInput` schema would reject any unknown
577
- // field; spreading conditionally keeps the SDK forward-
578
- // compatible.
579
- ...data.category ? { category: data.category } : {},
580
414
  ...data.recaptchaToken ? { recaptchaToken: data.recaptchaToken } : {}
581
415
  }
582
416
  });
@@ -615,17 +449,6 @@ function ContactPage({
615
449
  responseTime = "We typically respond within 24 hours",
616
450
  heroTitle = "Get in Touch",
617
451
  heroDescription = "Have questions about {productName}? We're here to help. Reach out to our team and we'll get back to you as soon as possible.",
618
- headingLevel = 1,
619
- // Backward-compatibility: default to `[]` so existing consumers
620
- // (fluidgrids/burdenoff/algoshred websites) that don't pass
621
- // `categories` continue to render with NO dropdown — same behavior
622
- // they had before this SDK version. To opt in, pass an explicit
623
- // `categories={…}` array (or the exported `DEFAULT_CONTACT_CATEGORIES`
624
- // constant for the sane Burdenoff-generic vocabulary).
625
- categories = [],
626
- categoryLabel = "Category",
627
- categoryPlaceholder = "Select a category",
628
- categoryRequired = true,
629
452
  additionalOptions = [
630
453
  {
631
454
  title: "Sales Inquiries",
@@ -650,10 +473,8 @@ function ContactPage({
650
473
  }) {
651
474
  const client = useWebSDK();
652
475
  const config = useWebSDKConfig();
653
- const { product } = useProduct();
654
- const resolvedProductId = product?.id || config.productId;
655
- const displayName = productName ?? product?.name ?? config.productId;
656
- const effectiveRecaptchaSiteKey = recaptchaSiteKey ?? product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? "";
476
+ const displayName = productName ?? config.productId;
477
+ const effectiveRecaptchaSiteKey = recaptchaSiteKey ?? config.recaptchaSiteKey ?? "";
657
478
  const recaptchaRef = useRef(null);
658
479
  const [isSubmitting, setIsSubmitting] = useState(false);
659
480
  const [submitStatus, setSubmitStatus] = useState("idle");
@@ -669,19 +490,11 @@ function ContactPage({
669
490
  const form = e.currentTarget;
670
491
  try {
671
492
  const formData = new FormData(form);
672
- const showCategory = categories.length > 0;
673
- const rawCategory = showCategory ? (formData.get("category") ?? "").trim() : "";
674
- if (showCategory && categoryRequired && !rawCategory) {
675
- toast.error("Please select a category");
676
- setIsSubmitting(false);
677
- return;
678
- }
679
493
  const contactData = {
680
494
  firstName: formData.get("firstName"),
681
495
  lastName: formData.get("lastName"),
682
496
  email: formData.get("email"),
683
497
  company: formData.get("company"),
684
- category: rawCategory || void 0,
685
498
  subject: formData.get("subject"),
686
499
  message: formData.get("message"),
687
500
  recaptchaToken
@@ -689,7 +502,7 @@ function ContactPage({
689
502
  const result = await submitContactForm(
690
503
  client,
691
504
  contactData,
692
- resolvedProductId
505
+ config.productId
693
506
  );
694
507
  if (result.success) {
695
508
  setSubmitStatus("success");
@@ -728,16 +541,10 @@ function ContactPage({
728
541
  url: seo.url
729
542
  }
730
543
  ),
731
- /* @__PURE__ */ jsx(
732
- "section",
733
- {
734
- className: headingLevel === 2 ? "py-12 md:py-16" : "bg-gradient-to-b from-background to-muted/20 py-12 sm:py-16 md:py-20 lg:py-24",
735
- 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: [
736
- headingLevel === 2 ? /* @__PURE__ */ jsx("h2", { className: "text-2xl md:text-3xl font-bold mb-3", children: heroTitle }) : /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6", children: heroTitle }),
737
- /* @__PURE__ */ jsx("p", { className: "text-lg sm:text-xl text-muted-foreground leading-relaxed", children: replacePlaceholders(heroDescription, displayName, contactEmail) })
738
- ] }) })
739
- }
740
- ),
544
+ /* @__PURE__ */ jsx("section", { className: "bg-gradient-to-b from-background to-muted/20 py-12 sm:py-16 md:py-20 lg: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: [
545
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6", children: heroTitle }),
546
+ /* @__PURE__ */ jsx("p", { className: "text-lg sm:text-xl text-muted-foreground leading-relaxed", children: replacePlaceholders(heroDescription, displayName, contactEmail) })
547
+ ] }) }) }),
741
548
  /* @__PURE__ */ jsx("section", { className: "py-12 sm:py-16 md:py-20 lg: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: "grid sm:grid-cols-1 lg:grid-cols-3 gap-8 sm:gap-10 lg:gap-12", children: [
742
549
  /* @__PURE__ */ jsxs("div", { className: "lg:col-span-1 order-2 lg:order-1", children: [
743
550
  /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold mb-4 sm:mb-6", children: "Contact Information" }),
@@ -749,7 +556,7 @@ function ContactPage({
749
556
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm sm:text-base break-all", children: contactEmail })
750
557
  ] })
751
558
  ] }),
752
- contactPhone && /* @__PURE__ */ jsxs("div", { className: "flex items-start space-x-3 sm:space-x-4", children: [
559
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start space-x-3 sm:space-x-4", children: [
753
560
  /* @__PURE__ */ jsx("div", { className: "w-8 h-8 sm:w-10 sm:h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx(Phone, { className: "h-4 w-4 sm:h-5 sm:w-5 text-primary" }) }),
754
561
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
755
562
  /* @__PURE__ */ jsx("h3", { className: "font-semibold mb-1 text-sm sm:text-base", children: "Phone" }),
@@ -856,33 +663,6 @@ function ContactPage({
856
663
  }
857
664
  )
858
665
  ] }),
859
- categories.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
860
- /* @__PURE__ */ jsxs(
861
- Label,
862
- {
863
- htmlFor: "category",
864
- className: "text-sm sm:text-base",
865
- children: [
866
- categoryLabel,
867
- categoryRequired ? " *" : ""
868
- ]
869
- }
870
- ),
871
- /* @__PURE__ */ jsxs(
872
- Select,
873
- {
874
- id: "category",
875
- name: "category",
876
- required: categoryRequired,
877
- defaultValue: "",
878
- className: "text-sm sm:text-base",
879
- children: [
880
- /* @__PURE__ */ jsx("option", { value: "", disabled: categoryRequired, children: categoryPlaceholder }),
881
- categories.map((c) => /* @__PURE__ */ jsx("option", { value: c.value, children: c.label }, c.value))
882
- ]
883
- }
884
- )
885
- ] }),
886
666
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
887
667
  /* @__PURE__ */ jsx(Label, { htmlFor: "subject", className: "text-sm sm:text-base", children: "Subject *" }),
888
668
  /* @__PURE__ */ jsx(
@@ -912,7 +692,7 @@ function ContactPage({
912
692
  ] }),
913
693
  /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
914
694
  effectiveRecaptchaSiteKey && /* @__PURE__ */ jsx("div", { className: "flex justify-center sm:justify-start", children: /* @__PURE__ */ jsx(
915
- ReCAPTCHA3,
695
+ ReCAPTCHA2,
916
696
  {
917
697
  ref: recaptchaRef,
918
698
  sitekey: effectiveRecaptchaSiteKey
@@ -953,7 +733,7 @@ function ContactPage({
953
733
  )
954
734
  ] }) })
955
735
  ] }) }) }),
956
- additionalOptions.length > 0 && /* @__PURE__ */ jsx("section", { className: "py-12 sm:py-16 md:py-20 lg:py-24 bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
736
+ /* @__PURE__ */ jsx("section", { className: "py-12 sm:py-16 md:py-20 lg:py-24 bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
957
737
  /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl font-bold text-center mb-8 sm:mb-12", children: "More Ways to Connect" }),
958
738
  /* @__PURE__ */ jsx("div", { className: "grid sm:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8 max-w-5xl mx-auto", children: additionalOptions.map((option, index) => /* @__PURE__ */ jsxs(
959
739
  "div",
@@ -982,999 +762,187 @@ function ContactPage({
982
762
  ] }) })
983
763
  ] });
984
764
  }
985
- var PUBLIC_CONTENT_PAGE_QUERY = `
986
- query PublicContentPage($slug: String!, $productId: String) {
987
- publicContentPage(slug: $slug, productId: $productId) {
988
- id
989
- title
990
- slug
991
- content
992
- contentFormat
993
- seoTitle
994
- seoDescription
995
- seoKeywords
996
- lastUpdated
997
- version
998
- excerpt
999
- thumbnail
1000
- author
1001
- publishedAt
1002
- readingTime
1003
- featured
1004
- category
1005
- }
1006
- }
1007
- `;
1008
- var PUBLIC_RESOLVED_CONTENT_PAGE_QUERY = `
1009
- query PublicResolvedContentPage($slug: String!, $productId: String!) {
1010
- publicResolvedContentPage(slug: $slug, productId: $productId) {
765
+ var PUBLIC_PRODUCT_QUERY = `
766
+ query PublicProduct($slug: String!) {
767
+ publicProduct(slug: $slug) {
1011
768
  id
1012
- title
769
+ name
1013
770
  slug
1014
- content
1015
- contentFormat
771
+ description
772
+ status
773
+ logoUrl
774
+ website
775
+ appUrl
776
+ docsUrl
777
+ contactEmail
778
+ supportEmail
779
+ contactPhone
780
+ contactAddress
1016
781
  seoTitle
1017
782
  seoDescription
1018
783
  seoKeywords
1019
- lastUpdated
1020
- version
1021
- excerpt
1022
- thumbnail
1023
- author
1024
- publishedAt
1025
- readingTime
1026
- featured
1027
- category
784
+ ogImage
785
+ socialLinks
786
+ recaptchaSiteKey
787
+ rybbitSiteId
788
+ notificationEmail
789
+ metadata
1028
790
  }
1029
791
  }
1030
792
  `;
1031
- var PUBLIC_CONTENT_PAGES_QUERY = `
1032
- query PublicContentPages($productId: String!, $limit: Int, $offset: Int) {
1033
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
1034
- items {
1035
- id
1036
- title
1037
- slug
1038
- lastUpdated
1039
- tags
1040
- seoDescription
1041
- excerpt
1042
- thumbnail
1043
- author
1044
- publishedAt
1045
- readingTime
1046
- featured
1047
- category
1048
- }
1049
- total
1050
- hasMore
1051
- }
793
+ var ProductContext = createContext({
794
+ product: null,
795
+ loading: true,
796
+ error: null,
797
+ hasProvider: false,
798
+ refetch: () => {
1052
799
  }
1053
- `;
1054
- function useContentPage(slug, options) {
800
+ });
801
+ function ProductProvider({ children, fallback }) {
1055
802
  const client = useWebSDK();
1056
803
  const config = useWebSDKConfig();
1057
- const { product } = useProduct();
1058
- const resolvedProductId = product?.id || config.productId;
1059
- const [data, setData] = useState(null);
804
+ const [product, setProduct] = useState(
805
+ fallback ? fallback : null
806
+ );
1060
807
  const [loading, setLoading] = useState(true);
1061
808
  const [error, setError] = useState(null);
1062
- const fetchPage = useCallback(async () => {
1063
- if (!resolvedProductId) return;
809
+ const fetchProduct = useCallback(async () => {
810
+ if (!config.productSlug) {
811
+ setLoading(false);
812
+ return;
813
+ }
1064
814
  setLoading(true);
1065
815
  setError(null);
1066
816
  try {
1067
- const query = options?.resolve ? PUBLIC_RESOLVED_CONTENT_PAGE_QUERY : PUBLIC_CONTENT_PAGE_QUERY;
1068
- const queryName = options?.resolve ? "publicResolvedContentPage" : "publicContentPage";
1069
817
  const result = await client.query(
1070
- query,
1071
- {
1072
- slug,
1073
- productId: resolvedProductId
1074
- }
818
+ PUBLIC_PRODUCT_QUERY,
819
+ { slug: config.productSlug }
1075
820
  );
1076
821
  if (result.errors?.length) {
1077
822
  setError(result.errors[0]?.message ?? "Unknown error");
1078
- setData(null);
1079
- } else {
1080
- setData(result.data?.[queryName] ?? null);
823
+ } else if (result.data?.publicProduct) {
824
+ setProduct(result.data.publicProduct);
1081
825
  }
1082
826
  } catch (err) {
1083
- setError(err instanceof Error ? err.message : "Failed to fetch content");
1084
- setData(null);
827
+ setError(err instanceof Error ? err.message : "Failed to fetch product");
1085
828
  } finally {
1086
829
  setLoading(false);
1087
830
  }
1088
- }, [client, resolvedProductId, slug, options?.resolve]);
1089
- useEffect(() => {
1090
- fetchPage();
1091
- }, [fetchPage]);
1092
- return { data, loading, error, refetch: fetchPage };
1093
- }
1094
- function useContentPages(options) {
1095
- const client = useWebSDK();
1096
- const config = useWebSDKConfig();
1097
- const [data, setData] = useState(null);
1098
- const [loading, setLoading] = useState(true);
1099
- const [error, setError] = useState(null);
1100
- const fetchPages = useCallback(async () => {
1101
- setLoading(true);
1102
- setError(null);
1103
- try {
1104
- const result = await client.query(PUBLIC_CONTENT_PAGES_QUERY, {
1105
- productId: config.productId,
1106
- limit: options?.limit ?? 50,
1107
- offset: options?.offset ?? 0
1108
- });
1109
- if (result.errors?.length) {
1110
- setError(result.errors[0]?.message ?? "Unknown error");
1111
- setData(null);
1112
- } else {
1113
- setData(result.data?.publicContentPages ?? null);
1114
- }
1115
- } catch (err) {
1116
- setError(
1117
- err instanceof Error ? err.message : "Failed to fetch content pages"
1118
- );
1119
- setData(null);
1120
- } finally {
1121
- setLoading(false);
1122
- }
1123
- }, [client, config.productId, options?.limit, options?.offset]);
1124
- useEffect(() => {
1125
- fetchPages();
1126
- }, [fetchPages]);
1127
- return { data, loading, error, refetch: fetchPages };
1128
- }
1129
- function renderMarkdown(md) {
1130
- let html = md;
1131
- html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1132
- html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
1133
- return `<pre><code>${code.trim()}</code></pre>`;
1134
- });
1135
- html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
1136
- html = html.replace(/^###### (.+)$/gm, "<h6>$1</h6>");
1137
- html = html.replace(/^##### (.+)$/gm, "<h5>$1</h5>");
1138
- html = html.replace(/^#### (.+)$/gm, "<h4>$1</h4>");
1139
- html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>");
1140
- html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>");
1141
- html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>");
1142
- html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
1143
- html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
1144
- html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
1145
- html = html.replace(
1146
- /\[([^\]]+)\]\(([^)]+)\)/g,
1147
- '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
831
+ }, [client, config.productSlug]);
832
+ useLayoutEffect(() => {
833
+ fetchProduct();
834
+ }, [fetchProduct]);
835
+ const value = useMemo(
836
+ () => ({
837
+ product,
838
+ loading,
839
+ error,
840
+ hasProvider: true,
841
+ refetch: fetchProduct
842
+ }),
843
+ [product, loading, error, fetchProduct]
1148
844
  );
1149
- html = html.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
1150
- html = html.replace(/(^- .+$(\n^- .+$)*)/gm, (block) => {
1151
- const items = block.split("\n").map((line) => `<li>${line.replace(/^- /, "")}</li>`).join("");
1152
- return `<ul>${items}</ul>`;
1153
- });
1154
- html = html.replace(/(^\d+\. .+$(\n^\d+\. .+$)*)/gm, (block) => {
1155
- const items = block.split("\n").map((line) => `<li>${line.replace(/^\d+\. /, "")}</li>`).join("");
1156
- return `<ol>${items}</ol>`;
1157
- });
1158
- html = html.replace(/^---$/gm, "<hr />");
1159
- html = html.split(/\n\n+/).map((block) => {
1160
- const trimmed = block.trim();
1161
- if (!trimmed) return "";
1162
- if (/^<(h[1-6]|ul|ol|pre|blockquote|hr)/.test(trimmed)) return trimmed;
1163
- return `<p>${trimmed.replace(/\n/g, "<br />")}</p>`;
1164
- }).join("\n");
1165
- return html;
1166
- }
1167
- function ContentRenderer({
1168
- content,
1169
- format = "markdown",
1170
- className
1171
- }) {
1172
- if (format === "markdown") {
1173
- return /* @__PURE__ */ jsx(
1174
- "div",
1175
- {
1176
- className,
1177
- dangerouslySetInnerHTML: { __html: renderMarkdown(content) }
1178
- }
1179
- );
1180
- }
1181
- if (format === "html") {
1182
- return /* @__PURE__ */ jsx(
1183
- "div",
1184
- {
1185
- className,
1186
- dangerouslySetInnerHTML: { __html: content }
1187
- }
1188
- );
1189
- }
1190
- return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("pre", { style: { whiteSpace: "pre-wrap", fontFamily: "inherit" }, children: content }) });
1191
- }
1192
- var PARTNER_CONTENT_QUERY = `
1193
- query PartnerContent($productId: String!, $limit: Int) {
1194
- publicContentPages(productId: $productId, limit: $limit, offset: 0) {
1195
- items {
1196
- id
1197
- title
1198
- slug
1199
- excerpt
1200
- content
1201
- contentFormat
1202
- thumbnail
1203
- tags
1204
- publishedAt
1205
- lastUpdated
1206
- }
1207
- }
1208
- }
1209
- `;
1210
- var PARTNER_DOCUMENTS_QUERY = `
1211
- query PartnerDocuments($productId: String!, $limit: Int) {
1212
- publicPressKitAssets(productId: $productId, limit: $limit, offset: 0) {
1213
- items {
1214
- id
1215
- category
1216
- title
1217
- description
1218
- fileUrl
1219
- fileName
1220
- fileSize
1221
- mimeType
1222
- version
1223
- tags
1224
- sortOrder
1225
- updatedAt
1226
- }
1227
- }
1228
- }
1229
- `;
1230
- function formatFileSize(bytes) {
1231
- if (!bytes || bytes <= 0) return "";
1232
- const units = ["B", "KB", "MB", "GB"];
1233
- let i = 0;
1234
- let n = bytes;
1235
- while (n >= 1024 && i < units.length - 1) {
1236
- n /= 1024;
1237
- i++;
1238
- }
1239
- return `${n.toFixed(n >= 10 || i === 0 ? 0 : 1)} ${units[i]}`;
1240
- }
1241
- async function downloadDocument(e, doc) {
1242
- e.preventDefault();
1243
- const name = doc.fileName || doc.fileUrl.split("/").pop()?.split("?")[0] || doc.title || "partner-document";
1244
- const openInTab = () => window.open(doc.fileUrl, "_blank", "noopener,noreferrer");
1245
- try {
1246
- const res = await fetch(doc.fileUrl, { mode: "cors" });
1247
- if (!res.ok) {
1248
- openInTab();
1249
- return;
1250
- }
1251
- const blob = await res.blob();
1252
- const objUrl = URL.createObjectURL(blob);
1253
- const a = document.createElement("a");
1254
- a.href = objUrl;
1255
- a.download = name;
1256
- document.body.appendChild(a);
1257
- a.click();
1258
- a.remove();
1259
- URL.revokeObjectURL(objUrl);
1260
- } catch {
1261
- openInTab();
1262
- }
1263
- }
1264
- function PartnerHero({
1265
- title,
1266
- subtitle
1267
- }) {
1268
- return /* @__PURE__ */ jsx("section", { className: "bg-gradient-to-b from-background to-muted/20 py-16 md:py-24", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 max-w-4xl text-center", children: [
1269
- /* @__PURE__ */ jsx("h1", { className: "text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4", children: title }),
1270
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto", children: subtitle })
1271
- ] }) });
1272
- }
1273
- function stripLeadingHeading(content, format) {
1274
- if (format === "html") {
1275
- return content.replace(/^\s*<h1\b[^>]*>[\s\S]*?<\/h1>\s*/i, "");
1276
- }
1277
- return content.replace(/^\s*#\s+.*(?:\r?\n)*/, "");
1278
- }
1279
- function PartnerContentSection({
1280
- page
1281
- }) {
1282
- return /* @__PURE__ */ jsx("section", { className: "py-12 md:py-16", children: /* @__PURE__ */ jsx(
1283
- "div",
1284
- {
1285
- className: [
1286
- "mx-auto max-w-3xl px-4",
1287
- // Headings
1288
- "[&_h1]:text-3xl [&_h1]:font-bold [&_h1]:tracking-tight [&_h1]:mt-0 [&_h1]:mb-6",
1289
- "[&_h2]:text-2xl [&_h2]:font-bold [&_h2]:tracking-tight [&_h2]:mt-10 [&_h2]:mb-4",
1290
- "[&_h3]:text-xl [&_h3]:font-semibold [&_h3]:mt-7 [&_h3]:mb-3",
1291
- "[&_h4]:text-lg [&_h4]:font-semibold [&_h4]:mt-6 [&_h4]:mb-2",
1292
- // Block elements
1293
- "[&_p]:my-4 [&_p]:leading-relaxed [&_p]:text-base",
1294
- "[&_ul]:my-4 [&_ul]:pl-6 [&_ul]:list-disc [&_ul]:space-y-1",
1295
- "[&_ol]:my-4 [&_ol]:pl-6 [&_ol]:list-decimal [&_ol]:space-y-1",
1296
- "[&_li]:leading-relaxed",
1297
- "[&_li>p]:my-1",
1298
- "[&_blockquote]:border-l-4 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:my-4 [&_blockquote]:text-muted-foreground",
1299
- "[&_hr]:my-8 [&_hr]:border-border",
1300
- // Inline elements
1301
- "[&_strong]:font-semibold [&_strong]:text-foreground",
1302
- "[&_em]:italic",
1303
- "[&_a]:underline [&_a]:underline-offset-2 [&_a]:text-accent hover:[&_a]:text-accent/80 [&_a]:transition-colors",
1304
- "[&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-sm [&_code]:font-mono",
1305
- "[&_pre]:my-4 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-muted [&_pre]:p-4 [&_pre]:text-sm",
1306
- "[&_pre>code]:bg-transparent [&_pre>code]:p-0"
1307
- ].join(" "),
1308
- children: page.content && /* @__PURE__ */ jsx(
1309
- ContentRenderer,
1310
- {
1311
- content: stripLeadingHeading(page.content, page.contentFormat),
1312
- format: (
1313
- // Validate before passing; unknown formats fall through to
1314
- // ContentRenderer's default behavior (markdown rendering)
1315
- // rather than getting silently force-cast to an
1316
- // unsupported variant.
1317
- page.contentFormat === "markdown" || page.contentFormat === "html" ? page.contentFormat : void 0
1318
- )
1319
- }
1320
- )
1321
- }
1322
- ) });
1323
- }
1324
- function PartnerDocumentCard({ doc }) {
1325
- const sizeLabel = formatFileSize(doc.fileSize);
1326
- const ext = doc.fileName?.split(".").pop()?.toUpperCase();
1327
- return /* @__PURE__ */ jsxs("article", { className: "flex flex-col gap-3 rounded-xl border border-border bg-card p-5 shadow-sm hover:shadow-md transition-shadow", children: [
1328
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1329
- /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted p-2.5 shrink-0", children: /* @__PURE__ */ jsx(FileText, { size: 20, className: "text-muted-foreground" }) }),
1330
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1331
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground truncate", children: doc.title }),
1332
- doc.version && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
1333
- "Version ",
1334
- doc.version
1335
- ] })
1336
- ] })
1337
- ] }),
1338
- doc.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground line-clamp-3", children: doc.description }),
1339
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 mt-auto pt-2 border-t border-border/60", children: [
1340
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [
1341
- ext && /* @__PURE__ */ jsx("span", { children: ext }),
1342
- sizeLabel && /* @__PURE__ */ jsxs(Fragment, { children: [
1343
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
1344
- /* @__PURE__ */ jsx("span", { children: sizeLabel })
1345
- ] })
1346
- ] }),
1347
- /* @__PURE__ */ jsxs(
1348
- "a",
1349
- {
1350
- href: doc.fileUrl,
1351
- download: doc.fileName ?? void 0,
1352
- onClick: (e) => void downloadDocument(e, doc),
1353
- className: "inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted transition-colors",
1354
- children: [
1355
- /* @__PURE__ */ jsx(Download, { size: 14 }),
1356
- "Download"
1357
- ]
1358
- }
1359
- )
1360
- ] })
1361
- ] });
845
+ return /* @__PURE__ */ jsx(ProductContext.Provider, { value, children });
1362
846
  }
1363
- function PartnerDocumentsSection({
1364
- title,
1365
- subtitle,
1366
- docs
1367
- }) {
1368
- return /* @__PURE__ */ jsx("section", { className: "py-12 md:py-16 bg-muted/20", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 max-w-5xl", children: [
1369
- /* @__PURE__ */ jsxs("header", { className: "mb-8 md:mb-10 text-center", children: [
1370
- /* @__PURE__ */ jsx("h2", { className: "text-2xl md:text-3xl font-bold text-foreground mb-2", children: title }),
1371
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground max-w-2xl mx-auto", children: subtitle })
1372
- ] }),
1373
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: docs.map((doc) => /* @__PURE__ */ jsx(PartnerDocumentCard, { doc }, doc.id)) })
1374
- ] }) });
847
+ function useProduct() {
848
+ return useContext(ProductContext);
1375
849
  }
1376
- var DEFAULT_PARTNER_CATEGORY = {
1377
- value: "partnership",
1378
- label: "Partner program"
1379
- };
1380
- function PartnersPage(props) {
1381
- const {
1382
- productName: productNameProp,
1383
- heroTitle = "Partner with us",
1384
- heroSubtitle,
1385
- contentTag = "partner",
1386
- contentSlug,
1387
- documentsTag = "partner",
1388
- documentsTitle = "Partner Documents",
1389
- documentsSubtitle,
1390
- showContactForm = true,
1391
- contactCategory,
1392
- formTitle = "Become a partner",
1393
- formSubtitle,
1394
- contactEmail,
1395
- contactPhone,
1396
- officeAddress,
1397
- responseTime,
1398
- recaptchaSiteKey,
1399
- additionalOptions,
1400
- seoTitle,
1401
- seoDescription,
1402
- seoKeywords,
1403
- className
1404
- } = props;
1405
- const client = useWebSDK();
1406
- const config = useWebSDKConfig();
850
+ function useProductConfig() {
1407
851
  const { product } = useProduct();
1408
- const resolvedProductId = product?.id ?? config?.productId ?? productNameProp ?? "";
1409
- const resolvedProductName = productNameProp ?? product?.name ?? config?.productId ?? "our team";
1410
- const [contentPage, setContentPage] = useState(null);
1411
- const [docs, setDocs] = useState([]);
1412
- const [loading, setLoading] = useState(true);
1413
- useEffect(() => {
1414
- if (!client || !resolvedProductId) {
1415
- setContentPage(null);
1416
- setDocs([]);
1417
- setLoading(false);
1418
- return;
1419
- }
1420
- let cancelled = false;
1421
- setLoading(true);
1422
- void Promise.allSettled([
1423
- client.query(PARTNER_CONTENT_QUERY, { productId: resolvedProductId, limit: 50 }),
1424
- client.query(
1425
- PARTNER_DOCUMENTS_QUERY,
1426
- { productId: resolvedProductId, limit: 50 }
1427
- )
1428
- ]).then(([contentResult, docsResult]) => {
1429
- if (cancelled) return;
1430
- if (contentResult.status === "rejected") {
1431
- setContentPage(null);
1432
- } else if (contentResult.status === "fulfilled") {
1433
- const items = contentResult.value.data?.publicContentPages?.items ?? [];
1434
- const match = contentSlug ? items.find((p) => p.slug === contentSlug) : items.find((p) => p.tags?.includes(contentTag));
1435
- setContentPage(match ?? null);
1436
- }
1437
- if (docsResult.status === "rejected") {
1438
- setDocs([]);
1439
- } else if (docsResult.status === "fulfilled") {
1440
- const items = docsResult.value.data?.publicPressKitAssets?.items ?? [];
1441
- const filtered = items.filter(
1442
- (d) => d.category === "DOCUMENT" && d.tags?.includes(documentsTag) && !!d.fileUrl
1443
- ).sort(
1444
- (a, b) => a.sortOrder - b.sortOrder
1445
- );
1446
- setDocs(filtered);
1447
- }
1448
- setLoading(false);
1449
- });
1450
- return () => {
1451
- cancelled = true;
1452
- };
1453
- }, [client, resolvedProductId, contentSlug, contentTag, documentsTag]);
1454
- let partnerCategories;
1455
- if (contactCategory === false) {
1456
- partnerCategories = [];
1457
- } else if (Array.isArray(contactCategory)) {
1458
- partnerCategories = contactCategory.length > 0 ? contactCategory : [DEFAULT_PARTNER_CATEGORY];
1459
- } else {
1460
- partnerCategories = [contactCategory ?? DEFAULT_PARTNER_CATEGORY];
1461
- }
1462
- return /* @__PURE__ */ jsxs("div", { className, children: [
1463
- /* @__PURE__ */ jsx(
1464
- PageHead,
1465
- {
1466
- title: seoTitle ?? `${heroTitle} \u2014 ${resolvedProductName}`,
1467
- description: seoDescription ?? heroSubtitle ?? `Join the ${resolvedProductName} partner programme.`,
1468
- keywords: seoKeywords
1469
- }
1470
- ),
1471
- /* @__PURE__ */ jsx(PartnerHero, { title: heroTitle, subtitle: heroSubtitle }),
1472
- !loading && contentPage && /* @__PURE__ */ jsx(PartnerContentSection, { page: contentPage }),
1473
- !loading && docs.length > 0 && /* @__PURE__ */ jsx(
1474
- PartnerDocumentsSection,
1475
- {
1476
- title: documentsTitle,
1477
- subtitle: documentsSubtitle,
1478
- docs
1479
- }
1480
- ),
1481
- showContactForm && /* @__PURE__ */ jsx(
1482
- ContactPage,
1483
- {
1484
- productName: resolvedProductName,
1485
- heroTitle: formTitle,
1486
- headingLevel: 2,
1487
- heroDescription: formSubtitle ?? `Tell us about your organisation and a partner manager from ${resolvedProductName} will be in touch.`,
1488
- contactEmail,
1489
- contactPhone,
1490
- officeAddress,
1491
- responseTime,
1492
- recaptchaSiteKey,
1493
- additionalOptions,
1494
- categories: partnerCategories,
1495
- categoryLabel: "Partnership type",
1496
- categoryRequired: partnerCategories.length > 0
1497
- }
1498
- )
1499
- ] });
1500
- }
1501
- var ALLOWED_RESUME_TYPES = {
1502
- "application/pdf": "PDF",
1503
- "application/msword": "DOC",
1504
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX"
1505
- };
1506
- var EXT_TO_MIME = {
1507
- pdf: "application/pdf",
1508
- doc: "application/msword",
1509
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1510
- };
1511
- var DEFAULT_MAX_RESUME_BYTES = 10 * 1024 * 1024;
1512
- function resolveResumeType(file) {
1513
- if (ALLOWED_RESUME_TYPES[file.type]) return file.type;
1514
- const ext = file.name.split(".").pop()?.toLowerCase();
1515
- return ext && EXT_TO_MIME[ext] ? EXT_TO_MIME[ext] : null;
852
+ const config = useWebSDKConfig();
853
+ return {
854
+ // Prefer product-level config, fall back to SDK config
855
+ recaptchaSiteKey: product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? null,
856
+ rybbitSiteId: product?.rybbitSiteId ?? config.rybbitSiteId ?? null,
857
+ contactEmail: product?.contactEmail ?? null,
858
+ supportEmail: product?.supportEmail ?? null,
859
+ notificationEmail: product?.notificationEmail ?? product?.contactEmail ?? null,
860
+ productName: product?.name ?? null,
861
+ logoUrl: product?.logoUrl ?? null
862
+ };
1516
863
  }
1517
- var SUBMIT_JOB_APPLICATION_MUTATION = (
864
+ var newsletterSchema = z.object({
865
+ email: z.string().email("Please enter a valid email address")
866
+ });
867
+ var SUBSCRIBE_MUTATION = (
1518
868
  /* GraphQL */
1519
869
  `
1520
- mutation SubmitJobApplication($input: SubmitJobApplicationInput!) {
1521
- submitJobApplication(input: $input) {
1522
- success
1523
- message
870
+ mutation Subscribe($input: SubscribeInput!) {
871
+ subscribe(input: $input) {
872
+ id
873
+ email
874
+ alreadySubscribed
875
+ status
876
+ subscribedAt
1524
877
  }
1525
878
  }
1526
879
  `
1527
880
  );
1528
- function fileToBase64(file) {
1529
- return new Promise((resolve, reject) => {
1530
- const reader = new FileReader();
1531
- reader.onload = () => {
1532
- const result = typeof reader.result === "string" ? reader.result : "";
1533
- const comma = result.indexOf(",");
1534
- resolve(comma >= 0 ? result.slice(comma + 1) : result);
1535
- };
1536
- reader.onerror = () => reject(new Error("Failed to read the resume file"));
1537
- reader.readAsDataURL(file);
1538
- });
1539
- }
1540
- async function submitApplication(client, data, productId) {
881
+ async function subscribeNewsletter(client, email, productId, recaptchaToken) {
1541
882
  try {
1542
- const result = await client.mutate(SUBMIT_JOB_APPLICATION_MUTATION, {
883
+ const result = await client.mutate(SUBSCRIBE_MUTATION, {
1543
884
  input: {
1544
- firstName: data.firstName,
1545
- lastName: data.lastName,
1546
- email: data.email.trim().toLowerCase(),
1547
- position: data.position,
1548
- resumeBase64: data.resumeBase64,
1549
- resumeFileName: data.resumeFileName,
1550
- resumeContentType: data.resumeContentType,
1551
- productId,
1552
- ...data.phone ? { phone: data.phone } : {},
1553
- ...data.linkedinUrl ? { linkedinUrl: data.linkedinUrl } : {},
1554
- ...data.portfolioUrl ? { portfolioUrl: data.portfolioUrl } : {},
1555
- ...data.coverLetter ? { coverLetter: data.coverLetter } : {},
1556
- ...data.recaptchaToken ? { recaptchaToken: data.recaptchaToken } : {}
885
+ email: email.trim().toLowerCase(),
886
+ platformProductId: productId,
887
+ metadata: {
888
+ source: "website",
889
+ referrer: typeof window !== "undefined" ? window.location.href : "",
890
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
891
+ ...recaptchaToken ? { recaptchaToken } : {}
892
+ }
1557
893
  }
1558
894
  });
1559
895
  if (result.errors?.length) {
896
+ const errorMsg = result.errors[0]?.message ?? "Subscription failed";
897
+ const code = result.errors[0]?.extensions?.code;
898
+ if (code === "DUPLICATE_SUBSCRIBER" || errorMsg.toLowerCase().includes("already subscribed")) {
899
+ return {
900
+ success: true,
901
+ message: "You are already part of this newsletter."
902
+ };
903
+ }
904
+ return { success: false, message: errorMsg };
905
+ }
906
+ if (result.data?.subscribe?.alreadySubscribed) {
1560
907
  return {
1561
- success: false,
1562
- message: result.errors[0]?.message ?? "Failed to submit application"
908
+ success: true,
909
+ message: "You are already part of this newsletter."
1563
910
  };
1564
911
  }
1565
- const response = result.data?.submitJobApplication;
1566
- return response?.success ? { success: true, message: response.message || "Application submitted!" } : {
1567
- success: false,
1568
- message: response?.message || "Failed to submit application"
1569
- };
912
+ return { success: true };
1570
913
  } catch (error) {
1571
- console.error("Job application submission error:", error);
914
+ console.error("Newsletter subscription error:", error);
1572
915
  return { success: false, message: "Network error. Please try again." };
1573
916
  }
1574
917
  }
1575
- function CareersApplyForm({
1576
- productName,
1577
- recaptchaSiteKey,
1578
- positions = [],
1579
- heroTitle = "Become a part of the team",
1580
- heroDescription = "Tell us about yourself and upload your resume \u2014 we'll be in touch.",
1581
- headingLevel = 1,
1582
- maxResumeBytes = DEFAULT_MAX_RESUME_BYTES,
1583
- className,
1584
- seo
1585
- }) {
1586
- const client = useWebSDK();
1587
- const config = useWebSDKConfig();
1588
- const { product } = useProduct();
1589
- const resolvedProductId = product?.id || config.productId;
1590
- const displayName = productName ?? product?.name ?? config.productId;
1591
- const effectiveRecaptchaSiteKey = recaptchaSiteKey ?? product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? "";
1592
- const recaptchaRef = useRef(null);
1593
- const [isSubmitting, setIsSubmitting] = useState(false);
1594
- const [submitted, setSubmitted] = useState(false);
1595
- const [resumeFile, setResumeFile] = useState(null);
1596
- const [resumeType, setResumeType] = useState(null);
1597
- const [resumeError, setResumeError] = useState(null);
1598
- const maxMb = Math.floor(maxResumeBytes / (1024 * 1024));
1599
- const clearResume = () => {
1600
- setResumeFile(null);
1601
- setResumeType(null);
1602
- };
1603
- const handleFileChange = (e) => {
1604
- setResumeError(null);
1605
- const file = e.target.files?.[0] ?? null;
1606
- if (!file) {
1607
- clearResume();
1608
- return;
918
+ function useRateLimit(maxAttempts, windowMinutes) {
919
+ const rateLimitRef = useRef(null);
920
+ const [isLimited, setIsLimited] = useState(false);
921
+ const [resetTime, setResetTime] = useState(null);
922
+ useEffect(() => {
923
+ const state = rateLimitRef.current;
924
+ if (state && Date.now() > state.resetTime) {
925
+ rateLimitRef.current = null;
926
+ setIsLimited(false);
927
+ setResetTime(null);
928
+ } else if (state && state.attempts >= maxAttempts) {
929
+ setIsLimited(true);
930
+ setResetTime(new Date(state.resetTime));
1609
931
  }
1610
- const type = resolveResumeType(file);
1611
- if (!type) {
1612
- clearResume();
1613
- setResumeError("Resume must be a PDF, DOC, or DOCX file.");
1614
- return;
932
+ }, [maxAttempts]);
933
+ function recordAttempt() {
934
+ const now = Date.now();
935
+ const state = rateLimitRef.current;
936
+ if (state && now > state.resetTime) {
937
+ rateLimitRef.current = null;
1615
938
  }
1616
- if (file.size > maxResumeBytes) {
1617
- clearResume();
1618
- setResumeError(`Resume must be ${maxMb}MB or smaller.`);
1619
- return;
1620
- }
1621
- setResumeFile(file);
1622
- setResumeType(type);
1623
- };
1624
- const handleSubmit = async (e) => {
1625
- e.preventDefault();
1626
- const recaptchaToken = recaptchaRef.current?.getValue() ?? "";
1627
- if (effectiveRecaptchaSiteKey && !recaptchaToken) {
1628
- toast.error("Please complete the reCAPTCHA verification");
1629
- return;
1630
- }
1631
- if (!resumeFile) {
1632
- setResumeError("Please attach your resume (PDF, DOC, or DOCX).");
1633
- return;
1634
- }
1635
- if (!resolvedProductId) {
1636
- console.warn(
1637
- "[CareersApplyForm] No productId resolved \u2014 submission will not be attributed to a product."
1638
- );
1639
- }
1640
- const form = e.currentTarget;
1641
- setIsSubmitting(true);
1642
- try {
1643
- const formData = new FormData(form);
1644
- const resumeBase64 = await fileToBase64(resumeFile);
1645
- const result = await submitApplication(
1646
- client,
1647
- {
1648
- firstName: formData.get("firstName") ?? "",
1649
- lastName: formData.get("lastName") ?? "",
1650
- email: formData.get("email") ?? "",
1651
- phone: formData.get("phone") || void 0,
1652
- position: formData.get("position") ?? "",
1653
- linkedinUrl: formData.get("linkedinUrl") || void 0,
1654
- portfolioUrl: formData.get("portfolioUrl") || void 0,
1655
- coverLetter: formData.get("coverLetter") || void 0,
1656
- resumeBase64,
1657
- resumeFileName: resumeFile.name,
1658
- // Use the resolved type (handles browsers that report an empty
1659
- // file.type for .doc/.docx).
1660
- resumeContentType: resumeType ?? resumeFile.type,
1661
- recaptchaToken
1662
- },
1663
- resolvedProductId
1664
- );
1665
- if (result.success) {
1666
- setSubmitted(true);
1667
- toast.success(result.message ?? "Application submitted!");
1668
- } else {
1669
- toast.error(result.message ?? "Failed to submit application");
1670
- recaptchaRef.current?.reset();
1671
- }
1672
- } catch (err) {
1673
- console.error("Careers form error:", err);
1674
- toast.error("Something went wrong. Please try again.");
1675
- recaptchaRef.current?.reset();
1676
- } finally {
1677
- setIsSubmitting(false);
1678
- }
1679
- };
1680
- const Heading = headingLevel === 2 ? "h2" : "h1";
1681
- const headingClass = headingLevel === 2 ? "text-2xl md:text-3xl font-bold mb-3" : "text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6";
1682
- return /* @__PURE__ */ jsxs("div", { className, children: [
1683
- seo && /* @__PURE__ */ jsx(
1684
- PageHead,
1685
- {
1686
- title: seo.title ?? `Careers \u2014 ${displayName}`,
1687
- description: seo.description ?? heroDescription,
1688
- keywords: seo.keywords
1689
- }
1690
- ),
1691
- /* @__PURE__ */ jsx(
1692
- "section",
1693
- {
1694
- className: headingLevel === 2 ? "py-12 md:py-16" : "bg-gradient-to-b from-background to-muted/20 py-12 sm:py-16 md:py-20 lg:py-24",
1695
- children: /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1696
- /* @__PURE__ */ jsxs("div", { className: "text-center mb-8 sm:mb-10", children: [
1697
- /* @__PURE__ */ jsx(Heading, { className: headingClass, children: heroTitle }),
1698
- /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground leading-relaxed", children: heroDescription })
1699
- ] }),
1700
- submitted ? /* @__PURE__ */ jsxs("div", { className: "text-center rounded-xl border border-border bg-card p-10", children: [
1701
- /* @__PURE__ */ jsx(CheckCircle, { className: "mx-auto mb-4 h-12 w-12 text-accent" }),
1702
- /* @__PURE__ */ jsx("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Application received" }),
1703
- /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
1704
- "Thanks for applying to ",
1705
- displayName,
1706
- ". We've emailed you a confirmation and our team will be in touch."
1707
- ] })
1708
- ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-5", children: [
1709
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-5", children: [
1710
- /* @__PURE__ */ jsxs("div", { children: [
1711
- /* @__PURE__ */ jsx(Label, { htmlFor: "firstName", children: "First name *" }),
1712
- /* @__PURE__ */ jsx(
1713
- Input,
1714
- {
1715
- id: "firstName",
1716
- name: "firstName",
1717
- required: true,
1718
- placeholder: "Ada"
1719
- }
1720
- )
1721
- ] }),
1722
- /* @__PURE__ */ jsxs("div", { children: [
1723
- /* @__PURE__ */ jsx(Label, { htmlFor: "lastName", children: "Last name *" }),
1724
- /* @__PURE__ */ jsx(
1725
- Input,
1726
- {
1727
- id: "lastName",
1728
- name: "lastName",
1729
- required: true,
1730
- placeholder: "Lovelace"
1731
- }
1732
- )
1733
- ] })
1734
- ] }),
1735
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-5", children: [
1736
- /* @__PURE__ */ jsxs("div", { children: [
1737
- /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: "Email *" }),
1738
- /* @__PURE__ */ jsx(
1739
- Input,
1740
- {
1741
- id: "email",
1742
- name: "email",
1743
- type: "email",
1744
- required: true,
1745
- placeholder: "ada@example.com"
1746
- }
1747
- )
1748
- ] }),
1749
- /* @__PURE__ */ jsxs("div", { children: [
1750
- /* @__PURE__ */ jsx(Label, { htmlFor: "phone", children: "Phone" }),
1751
- /* @__PURE__ */ jsx(
1752
- Input,
1753
- {
1754
- id: "phone",
1755
- name: "phone",
1756
- placeholder: "+1 555 010 0100"
1757
- }
1758
- )
1759
- ] })
1760
- ] }),
1761
- /* @__PURE__ */ jsxs("div", { children: [
1762
- /* @__PURE__ */ jsx(Label, { htmlFor: "position", children: "Position *" }),
1763
- positions.length > 0 ? /* @__PURE__ */ jsxs(
1764
- "select",
1765
- {
1766
- id: "position",
1767
- name: "position",
1768
- required: true,
1769
- defaultValue: "",
1770
- className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
1771
- children: [
1772
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a role" }),
1773
- positions.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: p }, p))
1774
- ]
1775
- }
1776
- ) : /* @__PURE__ */ jsx(
1777
- Input,
1778
- {
1779
- id: "position",
1780
- name: "position",
1781
- required: true,
1782
- placeholder: "Role you're applying for"
1783
- }
1784
- )
1785
- ] }),
1786
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-5", children: [
1787
- /* @__PURE__ */ jsxs("div", { children: [
1788
- /* @__PURE__ */ jsx(Label, { htmlFor: "linkedinUrl", children: "LinkedIn" }),
1789
- /* @__PURE__ */ jsx(
1790
- Input,
1791
- {
1792
- id: "linkedinUrl",
1793
- name: "linkedinUrl",
1794
- type: "url",
1795
- placeholder: "https://linkedin.com/in/\u2026"
1796
- }
1797
- )
1798
- ] }),
1799
- /* @__PURE__ */ jsxs("div", { children: [
1800
- /* @__PURE__ */ jsx(Label, { htmlFor: "portfolioUrl", children: "Portfolio / GitHub" }),
1801
- /* @__PURE__ */ jsx(
1802
- Input,
1803
- {
1804
- id: "portfolioUrl",
1805
- name: "portfolioUrl",
1806
- type: "url",
1807
- placeholder: "https://\u2026"
1808
- }
1809
- )
1810
- ] })
1811
- ] }),
1812
- /* @__PURE__ */ jsxs("div", { children: [
1813
- /* @__PURE__ */ jsx(Label, { htmlFor: "coverLetter", children: "Cover letter" }),
1814
- /* @__PURE__ */ jsx(
1815
- Textarea,
1816
- {
1817
- id: "coverLetter",
1818
- name: "coverLetter",
1819
- rows: 5,
1820
- placeholder: "Tell us why you'd be a great fit\u2026"
1821
- }
1822
- )
1823
- ] }),
1824
- /* @__PURE__ */ jsxs("div", { children: [
1825
- /* @__PURE__ */ jsxs(Label, { htmlFor: "resume", children: [
1826
- "Resume * (PDF, DOC, or DOCX \u2014 max ",
1827
- maxMb,
1828
- "MB)"
1829
- ] }),
1830
- /* @__PURE__ */ jsxs(
1831
- "label",
1832
- {
1833
- htmlFor: "resume",
1834
- className: "mt-1 flex cursor-pointer items-center gap-3 rounded-md border border-dashed border-input bg-background px-4 py-3 text-sm hover:bg-muted/40 transition-colors",
1835
- children: [
1836
- /* @__PURE__ */ jsx(
1837
- Upload,
1838
- {
1839
- className: "h-5 w-5 text-muted-foreground",
1840
- "aria-hidden": "true"
1841
- }
1842
- ),
1843
- /* @__PURE__ */ jsx("span", { className: "text-foreground", children: resumeFile ? resumeFile.name : "Choose a file\u2026" }),
1844
- /* @__PURE__ */ jsx(
1845
- "input",
1846
- {
1847
- id: "resume",
1848
- name: "resume",
1849
- type: "file",
1850
- accept: ".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1851
- className: "sr-only",
1852
- "aria-describedby": resumeError ? "resume-error" : void 0,
1853
- "aria-invalid": resumeError ? true : void 0,
1854
- onChange: handleFileChange
1855
- }
1856
- )
1857
- ]
1858
- }
1859
- ),
1860
- resumeError && /* @__PURE__ */ jsx(
1861
- "p",
1862
- {
1863
- id: "resume-error",
1864
- role: "alert",
1865
- className: "mt-1 text-sm text-destructive",
1866
- children: resumeError
1867
- }
1868
- )
1869
- ] }),
1870
- effectiveRecaptchaSiteKey && /* @__PURE__ */ jsx(
1871
- ReCAPTCHA3,
1872
- {
1873
- ref: recaptchaRef,
1874
- sitekey: effectiveRecaptchaSiteKey
1875
- }
1876
- ),
1877
- /* @__PURE__ */ jsx(
1878
- Button,
1879
- {
1880
- type: "submit",
1881
- size: "lg",
1882
- disabled: isSubmitting,
1883
- className: "w-full sm:w-auto",
1884
- children: isSubmitting ? "Submitting\u2026" : /* @__PURE__ */ jsxs(Fragment, { children: [
1885
- /* @__PURE__ */ jsx(Send, { className: "mr-2 h-4 w-4" }),
1886
- " Submit application"
1887
- ] })
1888
- }
1889
- )
1890
- ] })
1891
- ] })
1892
- }
1893
- )
1894
- ] });
1895
- }
1896
- var newsletterSchema = z.object({
1897
- email: z.string().email("Please enter a valid email address")
1898
- });
1899
- var SUBSCRIBE_MUTATION = (
1900
- /* GraphQL */
1901
- `
1902
- mutation Subscribe($input: SubscribeInput!) {
1903
- subscribe(input: $input) {
1904
- id
1905
- email
1906
- alreadySubscribed
1907
- status
1908
- subscribedAt
1909
- }
1910
- }
1911
- `
1912
- );
1913
- async function subscribeNewsletter(client, email, productId, recaptchaToken) {
1914
- try {
1915
- const result = await client.mutate(SUBSCRIBE_MUTATION, {
1916
- input: {
1917
- email: email.trim().toLowerCase(),
1918
- platformProductId: productId,
1919
- metadata: {
1920
- source: "website",
1921
- referrer: typeof window !== "undefined" ? window.location.href : "",
1922
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1923
- ...recaptchaToken ? { recaptchaToken } : {}
1924
- }
1925
- }
1926
- });
1927
- if (result.errors?.length) {
1928
- const errorMsg = result.errors[0]?.message ?? "Subscription failed";
1929
- const code = result.errors[0]?.extensions?.code;
1930
- if (code === "DUPLICATE_SUBSCRIBER" || errorMsg.toLowerCase().includes("already subscribed")) {
1931
- return {
1932
- success: true,
1933
- message: "You are already part of this newsletter."
1934
- };
1935
- }
1936
- return { success: false, message: errorMsg };
1937
- }
1938
- if (result.data?.subscribe?.alreadySubscribed) {
1939
- return {
1940
- success: true,
1941
- message: "You are already part of this newsletter."
1942
- };
1943
- }
1944
- return { success: true };
1945
- } catch (error) {
1946
- console.error("Newsletter subscription error:", error);
1947
- return { success: false, message: "Network error. Please try again." };
1948
- }
1949
- }
1950
- function useRateLimit(maxAttempts, windowMinutes) {
1951
- const rateLimitRef = useRef(null);
1952
- const [isLimited, setIsLimited] = useState(false);
1953
- const [resetTime, setResetTime] = useState(null);
1954
- useEffect(() => {
1955
- const state = rateLimitRef.current;
1956
- if (state && Date.now() > state.resetTime) {
1957
- rateLimitRef.current = null;
1958
- setIsLimited(false);
1959
- setResetTime(null);
1960
- } else if (state && state.attempts >= maxAttempts) {
1961
- setIsLimited(true);
1962
- setResetTime(new Date(state.resetTime));
1963
- }
1964
- }, [maxAttempts]);
1965
- function recordAttempt() {
1966
- const now = Date.now();
1967
- const state = rateLimitRef.current;
1968
- if (state && now > state.resetTime) {
1969
- rateLimitRef.current = null;
1970
- }
1971
- const current = rateLimitRef.current;
1972
- if (!current) {
1973
- rateLimitRef.current = {
1974
- attempts: 1,
1975
- resetTime: now + windowMinutes * 60 * 1e3
1976
- };
1977
- return false;
939
+ const current = rateLimitRef.current;
940
+ if (!current) {
941
+ rateLimitRef.current = {
942
+ attempts: 1,
943
+ resetTime: now + windowMinutes * 60 * 1e3
944
+ };
945
+ return false;
1978
946
  }
1979
947
  current.attempts += 1;
1980
948
  if (current.attempts >= maxAttempts) {
@@ -2182,7 +1150,7 @@ function NewsletterPage({
2182
1150
  errors.email && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errors.email.message })
2183
1151
  ] }),
2184
1152
  effectiveRecaptchaSiteKey && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
2185
- ReCAPTCHA3,
1153
+ ReCAPTCHA2,
2186
1154
  {
2187
1155
  ref: recaptchaRef,
2188
1156
  sitekey: effectiveRecaptchaSiteKey
@@ -2234,7 +1202,7 @@ function NewsletterPage({
2234
1202
  errors.email && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errors.email.message })
2235
1203
  ] }),
2236
1204
  effectiveRecaptchaSiteKey && /* @__PURE__ */ jsx("div", { className: "flex justify-center sm:justify-start", children: /* @__PURE__ */ jsx(
2237
- ReCAPTCHA3,
1205
+ ReCAPTCHA2,
2238
1206
  {
2239
1207
  ref: recaptchaRef,
2240
1208
  sitekey: effectiveRecaptchaSiteKey
@@ -2402,25 +1370,14 @@ function NewsletterForm({
2402
1370
  background: rgba(0,0,0,0.3) !important;
2403
1371
  }
2404
1372
  ` }),
2405
- /* @__PURE__ */ jsx(
2406
- "div",
1373
+ /* @__PURE__ */ jsx("div", { style: { position: "relative", zIndex: 50 }, children: /* @__PURE__ */ jsx(
1374
+ ReCAPTCHA2,
2407
1375
  {
2408
- style: {
2409
- position: "relative",
2410
- zIndex: 50,
2411
- display: "flex",
2412
- justifyContent: "flex-start"
2413
- },
2414
- children: /* @__PURE__ */ jsx(
2415
- ReCAPTCHA3,
2416
- {
2417
- ref: recaptchaRef,
2418
- sitekey: effectiveRecaptchaSiteKey,
2419
- size: "normal"
2420
- }
2421
- )
1376
+ ref: recaptchaRef,
1377
+ sitekey: effectiveRecaptchaSiteKey,
1378
+ size: "compact"
2422
1379
  }
2423
- )
1380
+ ) })
2424
1381
  ] }),
2425
1382
  effectiveRecaptchaSiteKey && /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isLoading, className: "w-full", children: isLoading ? "Subscribing..." : buttonText })
2426
1383
  ]
@@ -2428,9 +1385,9 @@ function NewsletterForm({
2428
1385
  )
2429
1386
  ] });
2430
1387
  }
2431
- var GET_PLANS_QUERY = `
2432
- query GetPublicPlans($productId: String!) {
2433
- publicPlans(productId: $productId) {
1388
+ var GET_ALL_PLANS_QUERY = `
1389
+ query GetAllPlans {
1390
+ getAllPlans {
2434
1391
  id
2435
1392
  name
2436
1393
  price
@@ -2475,15 +1432,11 @@ function formatInterval(duration, billingIntervals) {
2475
1432
  const intervals = billingIntervals || defaultIntervals;
2476
1433
  return intervals[duration] || `/${duration}`;
2477
1434
  }
2478
- function isEnterprisePlan(planName) {
2479
- const name = planName.toLowerCase();
2480
- return name.includes("team") || name.includes("business") || name.includes("enterprise");
2481
- }
2482
1435
  function detectPlanTier(planName) {
2483
1436
  const name = planName.toLowerCase();
2484
- if (name.includes("free") || name.includes("basic") || name.includes("starter") || name.includes("hobby")) {
1437
+ if (name.includes("basic") || name.includes("starter")) {
2485
1438
  return "basic";
2486
- } else if (name.includes("pro") || name.includes("professional") || name.includes("premium") || name.includes("team")) {
1439
+ } else if (name.includes("pro") || name.includes("professional") || name.includes("premium")) {
2487
1440
  return "professional";
2488
1441
  } else if (name.includes("enterprise") || name.includes("business")) {
2489
1442
  return "enterprise";
@@ -2503,8 +1456,7 @@ function getMetadataForPlan(planName, planMetadata) {
2503
1456
  defaultDescription: meta.defaultDescription || "Choose this plan",
2504
1457
  popular: meta.popular ?? false,
2505
1458
  ctaText: meta.ctaText || "Get Started",
2506
- ctaType: meta.ctaType || "signup",
2507
- features: meta.features
1459
+ ctaType: meta.ctaType || "signup"
2508
1460
  };
2509
1461
  }
2510
1462
  const tier = detectPlanTier(planName);
@@ -2527,54 +1479,37 @@ function getMetadataForPlan(planName, planMetadata) {
2527
1479
  ctaType: "signup"
2528
1480
  };
2529
1481
  }
2530
- function humanizeQuotaName(name) {
2531
- const parts = name.split(".");
2532
- const resource = parts.length >= 3 ? parts[parts.length - 2] : parts[parts.length - 1];
2533
- return resource.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2534
- }
2535
1482
  function transformQuotaToFeature(quota) {
2536
1483
  const { name, limits } = quota;
2537
1484
  if (!limits || typeof limits !== "object") {
2538
- return humanizeQuotaName(name);
1485
+ return name;
2539
1486
  }
2540
1487
  const limitsObj = limits;
2541
1488
  const value = limitsObj.value;
2542
1489
  const unit = limitsObj.unit;
2543
1490
  const type = limitsObj.type;
2544
1491
  const description = limitsObj.description;
2545
- if (typeof description === "string" && description) {
2546
- if (type === "number" && typeof value === "number") {
2547
- return `${value.toLocaleString()} ${description}`;
2548
- }
2549
- return description;
2550
- }
2551
- if (typeof unit === "string" && unit) {
2552
- if (typeof value === "number") {
2553
- return `${value.toLocaleString()} ${unit}`;
2554
- }
2555
- return `${unit}`;
2556
- }
2557
- const label = humanizeQuotaName(name);
2558
- if (type === "boolean") {
2559
- return label;
1492
+ if (type === "number" && typeof value === "number") {
1493
+ const formattedValue = value.toLocaleString();
1494
+ return `${formattedValue} ${unit || ""}${description ? " - " + description : ""}`.trim();
1495
+ } else if (type === "boolean") {
1496
+ return typeof description === "string" && description || name;
2560
1497
  }
2561
1498
  if (typeof value === "number") {
2562
- return `${value.toLocaleString()} ${label}`;
1499
+ const formattedValue = value.toLocaleString();
1500
+ return `${name}: ${formattedValue} ${unit || ""}`.trim();
2563
1501
  }
2564
- return label;
1502
+ return name;
2565
1503
  }
2566
1504
  function extractDisplayFeatures(features, planName, fallbackFeatures) {
2567
- const tier = detectPlanTier(planName);
2568
- if (tier) {
2569
- const tierFeatures = getFallbackFeaturesForTier(tier, fallbackFeatures);
2570
- if (tierFeatures.length > 0) {
2571
- return tierFeatures;
2572
- }
2573
- }
2574
1505
  const quotaFeatures = features.filter((f) => f.quota).map((f) => transformQuotaToFeature(f.quota));
2575
1506
  if (quotaFeatures.length > 0) {
2576
1507
  return quotaFeatures;
2577
1508
  }
1509
+ const tier = detectPlanTier(planName);
1510
+ if (tier) {
1511
+ return getFallbackFeaturesForTier(tier, fallbackFeatures);
1512
+ }
2578
1513
  return [];
2579
1514
  }
2580
1515
  function transformToDisplayCard(plan, props) {
@@ -2585,7 +1520,7 @@ function transformToDisplayCard(plan, props) {
2585
1520
  props.currencies
2586
1521
  );
2587
1522
  const interval = formatInterval(plan.duration, props.billingIntervals);
2588
- const features = metadata.features && metadata.features.length > 0 ? metadata.features : extractDisplayFeatures(
1523
+ const features = extractDisplayFeatures(
2589
1524
  plan.features,
2590
1525
  plan.name,
2591
1526
  props.fallbackFeatures
@@ -2623,25 +1558,22 @@ function PricingSection(props) {
2623
1558
  const [billingCycle, setBillingCycle] = useState(
2624
1559
  "monthly"
2625
1560
  );
2626
- const [audience, setAudience] = useState(
2627
- "individual"
2628
- );
2629
1561
  useEffect(() => {
2630
1562
  let cancelled = false;
2631
1563
  async function loadPlans() {
2632
1564
  try {
2633
1565
  setLoading(true);
2634
- const response = await client.query(GET_PLANS_QUERY, {
2635
- productId: resolvedProductId
2636
- });
1566
+ const response = await client.query(GET_ALL_PLANS_QUERY);
2637
1567
  if (cancelled) return;
2638
1568
  if (response.errors && response.errors.length > 0) {
2639
1569
  throw new Error(response.errors[0].message);
2640
1570
  }
2641
- if (!response.data?.publicPlans) {
1571
+ if (!response.data?.getAllPlans) {
2642
1572
  throw new Error("No plans data received");
2643
1573
  }
2644
- const cards = response.data.publicPlans.filter((plan) => plan.isActive).map((plan) => transformToDisplayCard(plan, props)).sort((a, b) => a.priceRaw - b.priceRaw);
1574
+ const cards = response.data.getAllPlans.filter(
1575
+ (plan) => plan.isActive && (!resolvedProductId || plan.productID === resolvedProductId)
1576
+ ).map((plan) => transformToDisplayCard(plan, props)).sort((a, b) => a.priceRaw - b.priceRaw);
2645
1577
  setAllPlans(cards);
2646
1578
  setError(null);
2647
1579
  } catch (err) {
@@ -2659,56 +1591,13 @@ function PricingSection(props) {
2659
1591
  cancelled = true;
2660
1592
  };
2661
1593
  }, [client, resolvedProductId]);
2662
- const individualAllPlans = allPlans.filter((p) => !isEnterprisePlan(p.name));
2663
- const enterpriseApiPlans = allPlans.filter((p) => isEnterprisePlan(p.name));
2664
- const individualPlans = individualAllPlans.filter(
1594
+ const plans = allPlans.filter(
2665
1595
  (plan) => billingCycle === "monthly" ? plan.duration === "monthly" : plan.duration === "yearly"
2666
1596
  );
2667
- const enterpriseCards = enterpriseApiPlans.map((p) => {
2668
- if (p.priceRaw === 0 && p.name.toLowerCase().includes("enterprise")) {
2669
- return {
2670
- ...p,
2671
- price: "Custom",
2672
- interval: "",
2673
- ctaText: "Contact Sales",
2674
- ctaLink: props.contactUrl || "/contact"
2675
- };
2676
- }
2677
- return p;
2678
- });
2679
- const hasApiEnterprisePlans = enterpriseCards.length > 0;
2680
- const defaultEnterpriseCard = {
2681
- name: "Enterprise",
2682
- subtitle: "For organizations that need more",
2683
- description: "For organizations that need more",
2684
- price: "Custom",
2685
- period: "",
2686
- interval: "",
2687
- button: "Contact Sales",
2688
- buttonLink: props.contactUrl || "/contact",
2689
- ctaText: "Contact Sales",
2690
- ctaLink: props.contactUrl || "/contact",
2691
- popular: false,
2692
- features: props.fallbackFeatures?.enterprise || [
2693
- "Unlimited everything",
2694
- "SSO & SAML",
2695
- "Audit logs",
2696
- "Dedicated support",
2697
- "Self-hosted option"
2698
- ]
2699
- };
2700
- const resolvedEnterprisePlans = hasApiEnterprisePlans ? enterpriseCards : props.enterprisePlans ? props.enterprisePlans : [defaultEnterpriseCard];
2701
- const plans = props.showAudienceTabs && audience === "enterprise" ? [] : individualPlans;
2702
1597
  if (loading) {
2703
1598
  return /* @__PURE__ */ jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 sm:px-6", children: [
2704
1599
  /* @__PURE__ */ jsxs("div", { className: "text-center mb-8 sm:mb-12", children: [
2705
- sectionHeading ? /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }) : (
2706
- // Visually hidden but DOM-present heading so axe's heading-order
2707
- // rule still sees an h2 between the consumer's hero h1 and the
2708
- // h3 plan-card titles. See `getMetadataForPlan` for why a falsy
2709
- // sectionHeading is the canonical "no visible heading" signal.
2710
- /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Pricing" })
2711
- ),
1600
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }),
2712
1601
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm sm:text-base md:text-lg", children: "Loading pricing information..." })
2713
1602
  ] }),
2714
1603
  /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-3 gap-6 sm:gap-8", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs(
@@ -2729,205 +1618,90 @@ function PricingSection(props) {
2729
1618
  }
2730
1619
  if (error && plans.length === 0) {
2731
1620
  return /* @__PURE__ */ jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 sm:px-6", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2732
- sectionHeading ? /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-4", children: sectionHeading }) : /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Pricing" }),
1621
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-4", children: sectionHeading }),
2733
1622
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-8", children: error }),
2734
1623
  /* @__PURE__ */ jsx(Button, { onClick: () => window.location.reload(), children: "Retry" })
2735
1624
  ] }) }) });
2736
1625
  }
2737
1626
  return /* @__PURE__ */ jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 sm:px-6", children: [
2738
1627
  /* @__PURE__ */ jsxs("div", { className: "text-center mb-8 sm:mb-12", children: [
2739
- sectionHeading ? /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }) : /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Pricing" }),
1628
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }),
2740
1629
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm sm:text-base md:text-lg mb-6", children: sectionSubtitle }),
2741
- props.showAudienceTabs && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center bg-muted p-1.5 rounded-xl", children: [
2742
- /* @__PURE__ */ jsx(
2743
- "button",
2744
- {
2745
- type: "button",
2746
- onClick: () => setAudience("individual"),
2747
- className: `px-6 sm:px-8 py-2.5 rounded-lg text-sm font-semibold transition-all ${audience === "individual" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
2748
- children: "Individual"
2749
- }
2750
- ),
2751
- /* @__PURE__ */ jsx(
2752
- "button",
2753
- {
2754
- type: "button",
2755
- onClick: () => setAudience("enterprise"),
2756
- className: `px-6 sm:px-8 py-2.5 rounded-lg text-sm font-semibold transition-all ${audience === "enterprise" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
2757
- children: "Enterprise"
2758
- }
2759
- )
2760
- ] }) }),
2761
- (audience === "individual" || !props.showAudienceTabs) && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center bg-muted/60 p-1.5 rounded-xl", children: [
1630
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-3 bg-muted p-1.5 rounded-lg", children: [
2762
1631
  /* @__PURE__ */ jsx(
2763
1632
  "button",
2764
1633
  {
2765
- type: "button",
2766
1634
  onClick: () => setBillingCycle("monthly"),
2767
- className: `px-5 sm:px-6 py-2 rounded-lg text-sm font-medium transition-all ${billingCycle === "monthly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1635
+ className: `px-6 py-2 rounded-md text-sm font-medium transition-all ${billingCycle === "monthly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
2768
1636
  children: "Monthly"
2769
1637
  }
2770
1638
  ),
2771
1639
  /* @__PURE__ */ jsx(
2772
1640
  "button",
2773
1641
  {
2774
- type: "button",
2775
1642
  onClick: () => setBillingCycle("yearly"),
2776
- className: `px-5 sm:px-6 py-2 rounded-lg text-sm font-medium transition-all ${billingCycle === "yearly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1643
+ className: `px-6 py-2 rounded-md text-sm font-medium transition-all ${billingCycle === "yearly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
2777
1644
  children: "Yearly"
2778
1645
  }
2779
1646
  )
2780
- ] }) })
2781
- ] }),
2782
- props.showAudienceTabs && audience === "enterprise" && /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl text-center mb-8", children: [
2783
- /* @__PURE__ */ jsx("h3", { className: "text-xl sm:text-2xl font-bold text-foreground", children: props.enterpriseHeading || "Enterprise Solutions" }),
2784
- /* @__PURE__ */ jsx("p", { className: "mt-3 text-muted-foreground", children: props.enterpriseSubtitle || "Built for teams, agencies, and organizations that need advanced collaboration, security, support, and scale." })
1647
+ ] })
2785
1648
  ] }),
2786
- /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-3 gap-6 sm:gap-8 max-w-6xl mx-auto items-stretch", children: props.showAudienceTabs && audience === "enterprise" ? (
2787
- /* Enterprise cards — from API, static prop, or built-in default */
2788
- resolvedEnterprisePlans.map((ep) => {
2789
- const subtitle = ep.subtitle || ep.description || "";
2790
- const btnText = ep.button || ep.ctaText || "Get Started";
2791
- const btnLink = ep.buttonLink || ep.ctaLink || props.appLoginUrl;
2792
- const period = ep.period || ep.interval || "";
2793
- const note = ep.note || "";
2794
- const badge = ep.badge || "";
2795
- const isPopular = ep.popular;
2796
- const features = ep.features || [];
2797
- const price = String(ep.price);
2798
- return /* @__PURE__ */ jsxs(
2799
- "div",
2800
- {
2801
- className: `bg-card rounded-xl p-6 sm:p-8 transition-all duration-300 flex flex-col ${isPopular ? "border-2 border-primary shadow-xl ring-1 ring-primary/20" : "border hover:shadow-lg hover:border-primary/50"}`,
2802
- children: [
2803
- /* @__PURE__ */ jsxs("div", { className: "text-center mb-6", children: [
2804
- isPopular && badge && /* @__PURE__ */ jsx("span", { className: "inline-block bg-primary text-primary-foreground px-4 py-1 rounded-full text-xs font-semibold mb-3", children: badge }),
2805
- /* @__PURE__ */ jsx("h3", { className: "text-xl sm:text-2xl font-bold mb-2", children: ep.name }),
2806
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: subtitle })
2807
- ] }),
2808
- /* @__PURE__ */ jsxs("div", { className: "text-center mb-2", children: [
2809
- price === "Custom" ? /* @__PURE__ */ jsx("span", { className: "text-3xl sm:text-4xl font-bold", children: "Custom" }) : /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-center gap-1", children: [
2810
- /* @__PURE__ */ jsx("span", { className: "text-4xl sm:text-5xl font-extrabold", children: price }),
2811
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-lg", children: period })
2812
- ] }),
2813
- note && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium text-primary", children: note })
2814
- ] }),
2815
- /* @__PURE__ */ jsx("ul", { className: "space-y-3 mb-8 mt-6 flex-1", children: features.map((feature) => /* @__PURE__ */ jsxs(
2816
- "li",
2817
- {
2818
- className: "text-sm flex items-start gap-2",
2819
- children: [
2820
- /* @__PURE__ */ jsx("span", { className: "text-primary mt-0.5 flex-shrink-0", children: "\u2713" }),
2821
- /* @__PURE__ */ jsx("span", { children: feature })
2822
- ]
2823
- },
2824
- feature
2825
- )) }),
2826
- btnLink.startsWith("/") ? /* @__PURE__ */ jsx(
2827
- Button,
1649
+ /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-3 gap-6 sm:gap-8 max-w-6xl mx-auto items-stretch", children: plans.map((plan) => /* @__PURE__ */ jsxs(
1650
+ "div",
1651
+ {
1652
+ className: `bg-card rounded-xl p-6 sm:p-8 transition-all duration-300 flex flex-col ${plan.popular ? "border-2 border-primary shadow-xl ring-1 ring-primary/20" : "border hover:shadow-lg hover:border-primary/50"}`,
1653
+ children: [
1654
+ /* @__PURE__ */ jsxs("div", { className: "text-center mb-6", children: [
1655
+ plan.popular && /* @__PURE__ */ jsx("span", { className: "inline-block bg-primary text-primary-foreground px-4 py-1 rounded-full text-xs font-semibold mb-3", children: "Most Popular" }),
1656
+ /* @__PURE__ */ jsx("h3", { className: "text-xl sm:text-2xl font-bold mb-2", children: plan.name }),
1657
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: plan.description })
1658
+ ] }),
1659
+ /* @__PURE__ */ jsx("div", { className: "text-center mb-8", children: /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-center gap-1", children: [
1660
+ /* @__PURE__ */ jsx("span", { className: "text-4xl sm:text-5xl font-bold", children: plan.price }),
1661
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-lg", children: plan.interval })
1662
+ ] }) }),
1663
+ /* @__PURE__ */ jsx("ul", { className: "space-y-3 mb-8 flex-1", children: plan.features.map((feature, index) => /* @__PURE__ */ jsxs("li", { className: "text-sm flex items-start gap-2", children: [
1664
+ /* @__PURE__ */ jsx("span", { className: "text-primary mt-0.5 flex-shrink-0", children: "\u2713" }),
1665
+ /* @__PURE__ */ jsx("span", { children: feature })
1666
+ ] }, index)) }),
1667
+ plan.ctaLink.startsWith("/") ? /* @__PURE__ */ jsx(
1668
+ Button,
1669
+ {
1670
+ className: "w-full h-11",
1671
+ variant: plan.popular ? "default" : "outline",
1672
+ size: "lg",
1673
+ asChild: true,
1674
+ children: /* @__PURE__ */ jsx(
1675
+ "a",
2828
1676
  {
2829
- className: "w-full h-11",
2830
- variant: isPopular ? "default" : "outline",
2831
- size: "lg",
2832
- asChild: true,
2833
- children: /* @__PURE__ */ jsx(
2834
- "a",
2835
- {
2836
- href: btnLink,
2837
- onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }),
2838
- children: btnText
2839
- }
2840
- )
1677
+ href: plan.ctaLink,
1678
+ onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }),
1679
+ children: plan.ctaText
2841
1680
  }
2842
- ) : /* @__PURE__ */ jsx(
2843
- Button,
1681
+ )
1682
+ }
1683
+ ) : /* @__PURE__ */ jsx(
1684
+ Button,
1685
+ {
1686
+ className: "w-full h-11",
1687
+ variant: plan.popular ? "default" : "outline",
1688
+ size: "lg",
1689
+ asChild: true,
1690
+ children: /* @__PURE__ */ jsx(
1691
+ "a",
2844
1692
  {
2845
- className: "w-full h-11",
2846
- variant: isPopular ? "default" : "outline",
2847
- size: "lg",
2848
- asChild: true,
2849
- children: /* @__PURE__ */ jsx(
2850
- "a",
2851
- {
2852
- href: btnLink,
2853
- target: "_blank",
2854
- rel: "noopener noreferrer",
2855
- children: btnText
2856
- }
2857
- )
1693
+ href: plan.ctaLink,
1694
+ target: "_blank",
1695
+ rel: "noopener noreferrer",
1696
+ children: plan.ctaText
2858
1697
  }
2859
1698
  )
2860
- ]
2861
- },
2862
- ep.name
2863
- );
2864
- })
2865
- ) : (
2866
- /* Individual API-fetched cards */
2867
- plans.map((plan) => /* @__PURE__ */ jsxs(
2868
- "div",
2869
- {
2870
- className: `bg-card rounded-xl p-6 sm:p-8 transition-all duration-300 flex flex-col ${plan.popular ? "border-2 border-primary shadow-xl ring-1 ring-primary/20" : "border hover:shadow-lg hover:border-primary/50"}`,
2871
- children: [
2872
- /* @__PURE__ */ jsxs("div", { className: "text-center mb-6", children: [
2873
- plan.popular && /* @__PURE__ */ jsx("span", { className: "inline-block bg-primary text-primary-foreground px-4 py-1 rounded-full text-xs font-semibold mb-3", children: "Most Popular" }),
2874
- /* @__PURE__ */ jsx("h3", { className: "text-xl sm:text-2xl font-bold mb-2", children: plan.name }),
2875
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: plan.description })
2876
- ] }),
2877
- /* @__PURE__ */ jsx("div", { className: "text-center mb-8", children: plan.price === "Custom" ? /* @__PURE__ */ jsx("span", { className: "text-3xl sm:text-4xl font-bold", children: "Custom" }) : /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-center gap-1", children: [
2878
- /* @__PURE__ */ jsx("span", { className: "text-4xl sm:text-5xl font-bold", children: plan.price }),
2879
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-lg", children: plan.interval })
2880
- ] }) }),
2881
- /* @__PURE__ */ jsx("ul", { className: "space-y-3 mb-8 flex-1", children: plan.features.map((feature, index) => /* @__PURE__ */ jsxs(
2882
- "li",
2883
- {
2884
- className: "text-sm flex items-start gap-2",
2885
- children: [
2886
- /* @__PURE__ */ jsx("span", { className: "text-primary mt-0.5 flex-shrink-0", children: "\u2713" }),
2887
- /* @__PURE__ */ jsx("span", { children: feature })
2888
- ]
2889
- },
2890
- index
2891
- )) }),
2892
- plan.ctaLink.startsWith("/") ? /* @__PURE__ */ jsx(
2893
- Button,
2894
- {
2895
- className: "w-full h-11",
2896
- variant: plan.popular ? "default" : "outline",
2897
- size: "lg",
2898
- asChild: true,
2899
- children: /* @__PURE__ */ jsx(
2900
- "a",
2901
- {
2902
- href: plan.ctaLink,
2903
- onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }),
2904
- children: plan.ctaText
2905
- }
2906
- )
2907
- }
2908
- ) : /* @__PURE__ */ jsx(
2909
- Button,
2910
- {
2911
- className: "w-full h-11",
2912
- variant: plan.popular ? "default" : "outline",
2913
- size: "lg",
2914
- asChild: true,
2915
- children: /* @__PURE__ */ jsx(
2916
- "a",
2917
- {
2918
- href: plan.ctaLink,
2919
- target: "_blank",
2920
- rel: "noopener noreferrer",
2921
- children: plan.ctaText
2922
- }
2923
- )
2924
- }
2925
- )
2926
- ]
2927
- },
2928
- plan.id
2929
- ))
2930
- ) })
1699
+ }
1700
+ )
1701
+ ]
1702
+ },
1703
+ plan.id
1704
+ )) })
2931
1705
  ] }) });
2932
1706
  }
2933
1707
  function PricingPage(props) {
@@ -3225,11 +1999,7 @@ function WaitlistPage({
3225
1999
  }
3226
2000
  setIsSubmitting(true);
3227
2001
  try {
3228
- const result = await joinWaitlist(
3229
- client,
3230
- data,
3231
- product?.id || config.productId
3232
- );
2002
+ const result = await joinWaitlist(client, data, config.productId);
3233
2003
  if (result.success) {
3234
2004
  toast.success(result.message ?? successMessage);
3235
2005
  reset();
@@ -3378,7 +2148,7 @@ function WaitlistForm({
3378
2148
  const result = await joinWaitlist(
3379
2149
  client,
3380
2150
  { email, name: name || void 0 },
3381
- product?.id || config.productId
2151
+ config.productId
3382
2152
  );
3383
2153
  if (result.success) {
3384
2154
  toast.success(result.message ?? "You have been added to the waitlist!");
@@ -3729,2476 +2499,179 @@ function UnsubscribePage({
3729
2499
  strokeLinejoin: "round",
3730
2500
  d: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
3731
2501
  }
3732
- )
3733
- }
3734
- ) }),
3735
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground mb-3", children: "Invalid Unsubscribe Link" }),
3736
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "This unsubscribe link is invalid or missing. If you want to unsubscribe, please use the link from your newsletter email." })
3737
- ] })
3738
- ] })
3739
- ]
3740
- }
3741
- );
3742
- }
3743
- function DiscordIcon({ className }) {
3744
- return /* @__PURE__ */ jsx(
3745
- "svg",
3746
- {
3747
- role: "img",
3748
- viewBox: "0 0 24 24",
3749
- fill: "currentColor",
3750
- xmlns: "http://www.w3.org/2000/svg",
3751
- className,
3752
- "aria-hidden": "true",
3753
- children: /* @__PURE__ */ jsx("path", { d: "M20.317 4.369A19.79 19.79 0 0 0 16.558 3a.075.075 0 0 0-.079.037c-.34.6-.717 1.382-.98 1.99a18.27 18.27 0 0 0-5.998 0 12.51 12.51 0 0 0-.99-1.99.077.077 0 0 0-.079-.037 19.74 19.74 0 0 0-3.76 1.369.07.07 0 0 0-.032.028C.533 8.045-.32 11.6.099 15.114a.082.082 0 0 0 .031.056 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.027 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.105 13.1 13.1 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .078-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .079.009c.12.099.246.198.372.292a.077.077 0 0 1-.006.127 12.3 12.3 0 0 1-1.873.892.077.077 0 0 0-.041.106c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.055c.5-4.063-.838-7.587-3.549-10.717a.061.061 0 0 0-.031-.028zM8.02 13.001c-1.182 0-2.157-1.085-2.157-2.419 0-1.333.956-2.418 2.157-2.418 1.21 0 2.176 1.094 2.157 2.418 0 1.334-.956 2.419-2.157 2.419zm7.974 0c-1.182 0-2.157-1.085-2.157-2.419 0-1.333.956-2.418 2.157-2.418 1.21 0 2.176 1.094 2.157 2.418 0 1.334-.946 2.419-2.157 2.419z" })
3754
- }
3755
- );
3756
- }
3757
- function PinterestIcon({ className }) {
3758
- return /* @__PURE__ */ jsx(
3759
- "svg",
3760
- {
3761
- role: "img",
3762
- viewBox: "0 0 24 24",
3763
- fill: "currentColor",
3764
- xmlns: "http://www.w3.org/2000/svg",
3765
- className,
3766
- "aria-hidden": "true",
3767
- children: /* @__PURE__ */ jsx("path", { d: "M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.357-.629-2.75-1.378l-.748 2.853c-.271 1.043-1.002 2.35-1.492 3.146C9.57 23.812 10.763 24 12.017 24c6.624 0 11.99-5.367 11.99-11.987C24.007 5.367 18.641 0 12.017 0z" })
3768
- }
3769
- );
3770
- }
3771
- var PLATFORMS = {
3772
- twitter: { Icon: Twitter, label: "Twitter / X", aliases: ["x"] },
3773
- linkedin: { Icon: Linkedin, label: "LinkedIn" },
3774
- github: { Icon: Github, label: "GitHub" },
3775
- facebook: { Icon: Facebook, label: "Facebook" },
3776
- youtube: { Icon: Youtube, label: "YouTube" },
3777
- instagram: { Icon: Instagram, label: "Instagram" },
3778
- discord: { Icon: DiscordIcon, label: "Discord" },
3779
- pinterest: { Icon: PinterestIcon, label: "Pinterest" }
3780
- };
3781
- var KEY_TO_PLATFORM = (() => {
3782
- const map = {};
3783
- for (const [key, def] of Object.entries(PLATFORMS)) {
3784
- map[key] = key;
3785
- for (const alias of def.aliases ?? []) {
3786
- map[alias] = key;
3787
- }
3788
- }
3789
- return map;
3790
- })();
3791
- var DEFAULT_ITEM_CLASS = "text-muted-foreground hover:text-foreground transition-colors";
3792
- function isSafeUrl(value) {
3793
- const trimmed = value.trim();
3794
- if (/^[/?#]/.test(trimmed)) return true;
3795
- return /^(https?:|mailto:|tel:)/i.test(trimmed);
3796
- }
3797
- function SocialLinks({
3798
- links: linksProp,
3799
- email: emailProp,
3800
- platforms: platformAllow,
3801
- showEmail,
3802
- iconClassName = "h-5 w-5",
3803
- itemClassName = DEFAULT_ITEM_CLASS,
3804
- className = "flex space-x-4"
3805
- }) {
3806
- const { product } = useProduct();
3807
- const links = linksProp ?? product?.socialLinks ?? {};
3808
- const email = emailProp !== void 0 ? emailProp : product?.contactEmail ?? null;
3809
- const resolved = /* @__PURE__ */ new Map();
3810
- for (const [rawKey, url] of Object.entries(links)) {
3811
- if (!url || typeof url !== "string") continue;
3812
- if (!isSafeUrl(url)) continue;
3813
- const platform = KEY_TO_PLATFORM[rawKey.toLowerCase()];
3814
- if (!platform) continue;
3815
- resolved.set(platform, url);
3816
- }
3817
- const orderedPlatforms = platformAllow ? platformAllow.filter((p) => resolved.has(p)) : Array.from(resolved.keys());
3818
- const wantEmail = (showEmail ?? !!email) && !!email;
3819
- if (orderedPlatforms.length === 0 && !wantEmail) return null;
3820
- return /* @__PURE__ */ jsxs("div", { className, children: [
3821
- orderedPlatforms.map((platform) => {
3822
- const { Icon, label } = PLATFORMS[platform];
3823
- const url = resolved.get(platform);
3824
- return /* @__PURE__ */ jsx(
3825
- "a",
3826
- {
3827
- href: url,
3828
- target: "_blank",
3829
- rel: "noopener noreferrer",
3830
- "aria-label": label,
3831
- className: itemClassName,
3832
- children: /* @__PURE__ */ jsx(Icon, { className: iconClassName })
3833
- },
3834
- platform
3835
- );
3836
- }),
3837
- wantEmail && /* @__PURE__ */ jsx(
3838
- "a",
3839
- {
3840
- href: `mailto:${email}`,
3841
- "aria-label": "Email",
3842
- className: itemClassName,
3843
- children: /* @__PURE__ */ jsx(Mail, { className: iconClassName })
3844
- },
3845
- "email"
3846
- )
3847
- ] });
3848
- }
3849
- var DEFAULT_LABELS = {
3850
- docs: "Docs",
3851
- community: "Community",
3852
- careers: "Careers",
3853
- team: "Team",
3854
- companies: "Sister Companies"
3855
- };
3856
- var BURDENOFF_GROUP_DEFAULTS = {
3857
- docs: null,
3858
- // per-product → read from `Product.docsUrl`
3859
- community: null,
3860
- // per-product → caller supplies via prop until CMS field exists
3861
- careers: "https://burdenoff.com/careers",
3862
- team: "https://burdenoff.com/team",
3863
- companies: "https://burdenoff.com/companies"
3864
- };
3865
- var DEFAULT_ORDER = [
3866
- "docs",
3867
- "community",
3868
- "careers",
3869
- "team",
3870
- "companies"
3871
- ];
3872
- var DEFAULT_ITEM_CLASS2 = "text-sm text-muted-foreground hover:text-foreground transition-colors";
3873
- function isSafeUrl2(value) {
3874
- const trimmed = value.trim();
3875
- if (/^[/?#]/.test(trimmed)) return true;
3876
- return /^(https?:|mailto:|tel:)/i.test(trimmed);
3877
- }
3878
- function ResourceLinks({
3879
- links: linksProp,
3880
- resources = DEFAULT_ORDER,
3881
- labels = {},
3882
- className = "space-y-2",
3883
- itemClassName = DEFAULT_ITEM_CLASS2
3884
- }) {
3885
- const { product } = useProduct();
3886
- function resolve(resource) {
3887
- if (linksProp && resource in linksProp) {
3888
- const override = linksProp[resource];
3889
- if (override === void 0) ; else {
3890
- return override;
3891
- }
3892
- }
3893
- const fromCms = product?.resourceLinks?.[resource];
3894
- if (typeof fromCms === "string" && fromCms.length > 0) {
3895
- return fromCms;
3896
- }
3897
- if (resource === "docs" && product?.docsUrl) {
3898
- return product.docsUrl;
3899
- }
3900
- return BURDENOFF_GROUP_DEFAULTS[resource];
3901
- }
3902
- const items = resources.map((r) => ({ resource: r, url: resolve(r) })).filter(
3903
- (x) => !!x.url && isSafeUrl2(x.url)
3904
- );
3905
- if (items.length === 0) return null;
3906
- return /* @__PURE__ */ jsx("ul", { className, children: items.map(({ resource, url }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
3907
- "a",
3908
- {
3909
- href: url,
3910
- target: "_blank",
3911
- rel: "noopener noreferrer",
3912
- className: itemClassName,
3913
- children: labels[resource] ?? DEFAULT_LABELS[resource]
3914
- }
3915
- ) }, resource)) });
3916
- }
3917
- function normalizeSearch(value) {
3918
- return value.toLowerCase().trim();
3919
- }
3920
- function filterProducts(products, query) {
3921
- const normalized = normalizeSearch(query);
3922
- if (!normalized) return products;
3923
- return products.filter((p) => normalizeSearch(p.name).includes(normalized));
3924
- }
3925
- function WebsiteSwitcher({
3926
- products,
3927
- triggerLabel = "Explore products",
3928
- searchPlaceholder = "Find a product\u2026",
3929
- emptyText = "No products match your search.",
3930
- className
3931
- }) {
3932
- const [open, setOpen] = useState(false);
3933
- const [query, setQuery] = useState("");
3934
- const filtered = useMemo(
3935
- () => filterProducts(products, query),
3936
- [products, query]
3937
- );
3938
- return /* @__PURE__ */ jsxs(DropdownMenu.Root, { open, onOpenChange: setOpen, children: [
3939
- /* @__PURE__ */ jsxs(
3940
- DropdownMenu.Trigger,
3941
- {
3942
- className: cn(
3943
- "inline-flex items-center gap-1.5 text-sm font-medium transition-colors",
3944
- "text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-md",
3945
- className
3946
- ),
3947
- "aria-label": triggerLabel,
3948
- children: [
3949
- /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4", "aria-hidden": "true" }),
3950
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: triggerLabel }),
3951
- /* @__PURE__ */ jsx(
3952
- ChevronDown,
3953
- {
3954
- className: cn(
3955
- "h-3.5 w-3.5 transition-transform duration-200",
3956
- open && "rotate-180"
3957
- ),
3958
- "aria-hidden": "true"
3959
- }
3960
- )
3961
- ]
3962
- }
3963
- ),
3964
- /* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs(
3965
- DropdownMenu.Content,
3966
- {
3967
- className: "z-50 min-w-[16rem] max-w-[20rem] rounded-lg border border-border/60 bg-popover p-2 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
3968
- sideOffset: 8,
3969
- align: "end",
3970
- avoidCollisions: true,
3971
- children: [
3972
- /* @__PURE__ */ jsxs("div", { className: "px-2 pb-2 pt-1", children: [
3973
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Burdenoff ecosystem" }),
3974
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3975
- /* @__PURE__ */ jsx(
3976
- Search,
3977
- {
3978
- className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground",
3979
- "aria-hidden": "true"
3980
- }
3981
- ),
3982
- /* @__PURE__ */ jsx(
3983
- Input,
3984
- {
3985
- type: "search",
3986
- value: query,
3987
- onChange: (e) => setQuery(e.target.value),
3988
- placeholder: searchPlaceholder,
3989
- className: "h-8 pl-8 text-sm",
3990
- "aria-label": searchPlaceholder,
3991
- onKeyDown: (e) => {
3992
- if (e.key === "Escape") {
3993
- setQuery("");
3994
- }
3995
- }
3996
- }
3997
- )
3998
- ] })
3999
- ] }),
4000
- /* @__PURE__ */ jsx(DropdownMenu.Separator, { className: "my-1 h-px bg-border/60" }),
4001
- /* @__PURE__ */ jsx("div", { className: "max-h-[min(60vh,24rem)] overflow-y-auto py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filtered.map((product) => /* @__PURE__ */ jsx(DropdownMenu.Item, { asChild: true, children: /* @__PURE__ */ jsxs(
4002
- "a",
4003
- {
4004
- href: product.websiteUrl,
4005
- target: "_blank",
4006
- rel: "noopener noreferrer",
4007
- className: cn(
4008
- "flex items-center gap-3 rounded-md px-2 py-2 text-sm outline-none",
4009
- "text-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
4010
- ),
4011
- children: [
4012
- product.logoUrl ? /* @__PURE__ */ jsx(
4013
- "img",
4014
- {
4015
- src: product.logoUrl,
4016
- alt: "",
4017
- "aria-hidden": "true",
4018
- className: "h-5 w-5 shrink-0 rounded object-contain"
4019
- }
4020
- ) : /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary/10 text-[10px] font-bold text-primary", children: product.name.charAt(0) }),
4021
- /* @__PURE__ */ jsx("span", { className: "truncate", children: product.name })
4022
- ]
4023
- }
4024
- ) }, product.slug)) }),
4025
- /* @__PURE__ */ jsx(DropdownMenu.Arrow, { className: "fill-popover" })
4026
- ]
4027
- }
4028
- ) })
4029
- ] });
4030
- }
4031
- function splitRemaining(ms) {
4032
- const clamped = Math.max(0, ms);
4033
- const totalSeconds = Math.floor(clamped / 1e3);
4034
- return {
4035
- days: Math.floor(totalSeconds / 86400),
4036
- hours: Math.floor(totalSeconds % 86400 / 3600),
4037
- minutes: Math.floor(totalSeconds % 3600 / 60),
4038
- seconds: totalSeconds % 60
4039
- };
4040
- }
4041
- function padTwo(n) {
4042
- return String(n).padStart(2, "0");
4043
- }
4044
- function LaunchCountdown({
4045
- launchDate,
4046
- launchLabel,
4047
- launchedMessage = "We have launched!",
4048
- className
4049
- }) {
4050
- const [now, setNow] = useState(() => Date.now());
4051
- const remainingMs = launchDate - now;
4052
- const hasLaunched = remainingMs <= 0;
4053
- useEffect(() => {
4054
- if (hasLaunched) return;
4055
- const interval = setInterval(() => {
4056
- setNow(Date.now());
4057
- }, 1e3);
4058
- return () => clearInterval(interval);
4059
- }, [hasLaunched]);
4060
- const { days, hours, minutes, seconds } = splitRemaining(remainingMs);
4061
- const cells = [
4062
- { label: "Days", value: padTwo(Math.min(days, 99)) },
4063
- { label: "Hours", value: padTwo(hours) },
4064
- { label: "Minutes", value: padTwo(minutes) },
4065
- { label: "Seconds", value: padTwo(seconds) }
4066
- ];
4067
- return /* @__PURE__ */ jsxs(
4068
- "div",
4069
- {
4070
- className: cn("inline-flex flex-col gap-3", className),
4071
- role: "timer",
4072
- "aria-label": launchLabel ?? (hasLaunched ? launchedMessage : `Countdown to launch`),
4073
- children: [
4074
- !hasLaunched && /* @__PURE__ */ jsx("span", { className: "text-xs font-mono uppercase tracking-wider text-muted-foreground", children: "Launching in" }),
4075
- /* @__PURE__ */ jsx("div", { className: "flex gap-2 sm:gap-3", children: hasLaunched ? /* @__PURE__ */ jsxs(
4076
- "div",
4077
- {
4078
- className: cn(
4079
- "flex min-w-[15rem] items-center justify-center gap-3 rounded-lg border border-primary/30 bg-primary/10 px-6 py-4",
4080
- "text-center"
4081
- ),
4082
- children: [
4083
- /* @__PURE__ */ jsx(Rocket, { className: "h-5 w-5 text-primary", "aria-hidden": "true" }),
4084
- /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold text-foreground", children: launchedMessage })
4085
- ]
4086
- }
4087
- ) : cells.map((cell) => /* @__PURE__ */ jsxs(
4088
- "div",
4089
- {
4090
- className: cn(
4091
- "flex min-w-[3.75rem] flex-col items-center rounded-lg border border-border/60 bg-card px-3 py-2 sm:min-w-[4.5rem] sm:px-4 sm:py-3"
4092
- ),
4093
- children: [
4094
- /* @__PURE__ */ jsx("span", { className: "font-mono text-2xl font-bold tabular-nums text-foreground sm:text-3xl", children: cell.value }),
4095
- /* @__PURE__ */ jsx("span", { className: "mt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs", children: cell.label })
4096
- ]
4097
- },
4098
- cell.label
4099
- )) }),
4100
- !hasLaunched && launchLabel && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: launchLabel })
4101
- ]
4102
- }
4103
- );
4104
- }
4105
- function ProblemsSolvedSection({
4106
- eyebrow,
4107
- title,
4108
- subtitle,
4109
- problems,
4110
- storyBasePath = "/use-cases",
4111
- storyLabel = "See the story",
4112
- className,
4113
- children
4114
- }) {
4115
- return /* @__PURE__ */ jsx(
4116
- "section",
4117
- {
4118
- className: cn(
4119
- "py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-muted/30",
4120
- className
4121
- ),
4122
- children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto", children: [
4123
- (eyebrow || title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto text-center mb-12 md:mb-16", children: [
4124
- eyebrow && /* @__PURE__ */ jsx("p", { className: "text-xs font-mono uppercase tracking-widest text-primary mb-4", children: eyebrow }),
4125
- title && /* @__PURE__ */ jsx("h2", { className: "text-3xl sm:text-4xl font-bold text-foreground mb-4", children: title }),
4126
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: subtitle })
4127
- ] }),
4128
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: problems.map((item, index) => {
4129
- const Icon = item.icon;
4130
- const storyHref = item.storySlug ? `${storyBasePath.replace(/\/$/, "")}/${item.storySlug}` : null;
4131
- return /* @__PURE__ */ jsxs(
4132
- "div",
4133
- {
4134
- className: cn(
4135
- "group relative flex flex-col rounded-2xl border border-border/40 bg-card p-6 transition-colors",
4136
- "hover:border-primary/30"
4137
- ),
4138
- children: [
4139
- /* @__PURE__ */ jsx(
4140
- "div",
4141
- {
4142
- className: cn(
4143
- "mb-5 inline-flex h-12 w-12 items-center justify-center rounded-xl",
4144
- "bg-primary/10 text-primary"
4145
- ),
4146
- children: /* @__PURE__ */ jsx(Icon, { className: "h-6 w-6", "aria-hidden": true })
4147
- }
4148
- ),
4149
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2", children: item.problem }),
4150
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed mb-4 flex-1", children: item.solution }),
4151
- storyHref && /* @__PURE__ */ jsx(
4152
- Button,
4153
- {
4154
- variant: "ghost",
4155
- className: "justify-start px-0 h-auto py-0 text-sm group/link",
4156
- asChild: true,
4157
- children: /* @__PURE__ */ jsxs(
4158
- "a",
4159
- {
4160
- href: storyHref,
4161
- className: "inline-flex items-center gap-1 text-primary hover:underline",
4162
- children: [
4163
- storyLabel,
4164
- /* @__PURE__ */ jsx(
4165
- ArrowRight,
4166
- {
4167
- className: "h-3.5 w-3.5 transition-transform group-hover/link:translate-x-0.5",
4168
- "aria-hidden": "true"
4169
- }
4170
- )
4171
- ]
4172
- }
4173
- )
4174
- }
4175
- )
4176
- ]
4177
- },
4178
- `${item.problem}-${index}`
4179
- );
4180
- }) }),
4181
- children && /* @__PURE__ */ jsx("div", { className: "mt-12", children })
4182
- ] })
4183
- }
4184
- );
4185
- }
4186
- function detectElectronPlatform(userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "") {
4187
- const ua = userAgent.toLowerCase();
4188
- if (ua.includes("win")) return "win";
4189
- if (ua.includes("mac")) return "mac";
4190
- if (ua.includes("linux")) return "linux";
4191
- return "unknown";
4192
- }
4193
- function buildDownloadUrl(base, artifactName, platform, platformMap) {
4194
- const normalizedBase = base.replace(/\/$/, "");
4195
- const platformKey = platformMap[platform] ?? platform;
4196
- const fileName = artifactName.includes("{platform}") ? artifactName.replace(/\{platform\}/g, platformKey) : artifactName;
4197
- return `${normalizedBase}/${fileName}`;
4198
- }
4199
- function ElectronDownloadLink({
4200
- downloadBaseUrl,
4201
- artifactName,
4202
- hidden = true,
4203
- label = "Download desktop app",
4204
- downloadLabel = label,
4205
- platformMap = {},
4206
- className,
4207
- variant = "default",
4208
- size = "default"
4209
- }) {
4210
- if (hidden) return null;
4211
- const platform = detectElectronPlatform();
4212
- const href = buildDownloadUrl(
4213
- downloadBaseUrl,
4214
- artifactName,
4215
- platform,
4216
- platformMap
4217
- );
4218
- return /* @__PURE__ */ jsx(
4219
- Button,
4220
- {
4221
- variant,
4222
- size,
4223
- className: cn("inline-flex items-center gap-2", className),
4224
- asChild: true,
4225
- children: /* @__PURE__ */ jsxs(
4226
- "a",
4227
- {
4228
- href,
4229
- download: true,
4230
- "aria-label": `${downloadLabel}${platform !== "unknown" ? ` for ${platform}` : ""}`,
4231
- children: [
4232
- /* @__PURE__ */ jsx(Download, { className: "h-4 w-4", "aria-hidden": "true" }),
4233
- label
4234
- ]
4235
- }
4236
- )
4237
- }
4238
- );
4239
- }
4240
- function RybbitAnalytics({
4241
- siteId,
4242
- scriptUrl = "https://analytics.burdenoff.com/api/script.js",
4243
- enabled = true
4244
- }) {
4245
- useEffect(() => {
4246
- if (!enabled || !siteId) return;
4247
- const existingScript = document.querySelector(
4248
- `script[data-site-id="${siteId}"]`
4249
- );
4250
- if (existingScript) return;
4251
- const script = document.createElement("script");
4252
- script.src = scriptUrl;
4253
- script.defer = true;
4254
- script.crossOrigin = "anonymous";
4255
- script.setAttribute("data-site-id", siteId);
4256
- document.head.appendChild(script);
4257
- return () => {
4258
- const scriptToRemove = document.querySelector(
4259
- `script[data-site-id="${siteId}"]`
4260
- );
4261
- if (scriptToRemove) {
4262
- document.head.removeChild(scriptToRemove);
4263
- }
4264
- };
4265
- }, [siteId, scriptUrl, enabled]);
4266
- return null;
4267
- }
4268
- var BLOG_POSTS_QUERY = `
4269
- query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
4270
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
4271
- items {
4272
- id
4273
- title
4274
- slug
4275
- lastUpdated
4276
- tags
4277
- seoDescription
4278
- excerpt
4279
- thumbnail
4280
- author
4281
- publishedAt
4282
- readingTime
4283
- featured
4284
- category
4285
- }
4286
- total
4287
- hasMore
4288
- }
4289
- }
4290
- `;
4291
- var BLOG_POST_QUERY = `
4292
- query BlogPost($slug: String!, $productId: String) {
4293
- publicContentPage(slug: $slug, productId: $productId) {
4294
- id
4295
- title
4296
- slug
4297
- content
4298
- contentFormat
4299
- seoTitle
4300
- seoDescription
4301
- seoKeywords
4302
- lastUpdated
4303
- tags
4304
- excerpt
4305
- thumbnail
4306
- author
4307
- publishedAt
4308
- readingTime
4309
- featured
4310
- category
4311
- }
4312
- }
4313
- `;
4314
- function BlogCard({
4315
- post,
4316
- basePath = "/blog"
4317
- }) {
4318
- const date = post.publishedAt || post.lastUpdated;
4319
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
4320
- year: "numeric",
4321
- month: "long",
4322
- day: "numeric"
4323
- }) : "";
4324
- const [imgError, setImgError] = useState(false);
4325
- return /* @__PURE__ */ jsxs(
4326
- "a",
4327
- {
4328
- href: `${basePath}/${post.slug}`,
4329
- className: "group block bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300",
4330
- children: [
4331
- post.thumbnail && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden", children: /* @__PURE__ */ jsx(
4332
- "img",
4333
- {
4334
- src: post.thumbnail,
4335
- alt: post.title,
4336
- className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
4337
- loading: "lazy",
4338
- onError: () => setImgError(true)
4339
- }
4340
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
4341
- "svg",
4342
- {
4343
- className: "w-12 h-12 text-muted-foreground/20",
4344
- fill: "none",
4345
- viewBox: "0 0 24 24",
4346
- stroke: "currentColor",
4347
- children: /* @__PURE__ */ jsx(
4348
- "path",
4349
- {
4350
- strokeLinecap: "round",
4351
- strokeLinejoin: "round",
4352
- strokeWidth: 1.5,
4353
- 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"
4354
- }
4355
- )
4356
- }
4357
- ) }),
4358
- /* @__PURE__ */ jsxs("div", { className: "p-5 sm:p-6", children: [
4359
- 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 }),
4360
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-2", children: post.title }),
4361
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4 line-clamp-3", children: post.excerpt || post.seoDescription || "" }),
4362
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
4363
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: post.author }),
4364
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4365
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
4366
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
4367
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4368
- /* @__PURE__ */ jsxs("span", { children: [
4369
- post.readingTime,
4370
- " min read"
4371
- ] })
4372
- ] })
4373
- ] })
4374
- ] })
4375
- ]
4376
- }
4377
- );
4378
- }
4379
- function BlogListPage(props) {
4380
- const {
4381
- productName,
4382
- heroTitle = "Blog",
4383
- heroSubtitle = "Latest updates, engineering insights, and product news",
4384
- blogPostBasePath = "/blog",
4385
- className = "",
4386
- seoTitle,
4387
- seoDescription,
4388
- seoKeywords
4389
- } = props;
4390
- const client = useWebSDK();
4391
- const config = useWebSDKConfig();
4392
- const { product } = useProduct();
4393
- const resolvedProductId = product?.id || config.productId;
4394
- const [posts, setPosts] = useState([]);
4395
- const [total, setTotal] = useState(0);
4396
- const [loading, setLoading] = useState(true);
4397
- const [error, setError] = useState(null);
4398
- const [offset, setOffset] = useState(0);
4399
- const limit = 12;
4400
- const fetchPosts = useCallback(async () => {
4401
- if (!resolvedProductId) return;
4402
- setLoading(true);
4403
- try {
4404
- const result = await client.query(BLOG_POSTS_QUERY, {
4405
- productId: resolvedProductId,
4406
- limit,
4407
- offset
4408
- });
4409
- if (result.errors?.length) {
4410
- setError(result.errors[0]?.message ?? "Failed to load posts");
4411
- return;
4412
- }
4413
- const blogPosts = (result.data?.publicContentPages?.items || []).filter(
4414
- (p) => p.tags?.includes("blog")
4415
- );
4416
- setPosts(blogPosts);
4417
- setTotal(result.data?.publicContentPages?.total || 0);
4418
- setError(null);
4419
- } catch {
4420
- setError("Failed to load blog posts");
4421
- } finally {
4422
- setLoading(false);
4423
- }
4424
- }, [client, resolvedProductId, offset]);
4425
- useEffect(() => {
4426
- fetchPosts();
4427
- }, [fetchPosts]);
4428
- const featuredPosts = posts.filter((p) => p.featured);
4429
- const regularPosts = posts.filter((p) => !p.featured);
4430
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
4431
- seoTitle && /* @__PURE__ */ jsx(
4432
- PageHead,
4433
- {
4434
- title: seoTitle,
4435
- description: seoDescription || heroSubtitle,
4436
- productName,
4437
- keywords: seoKeywords
4438
- }
4439
- ),
4440
- /* @__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: [
4441
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground", children: heroTitle }),
4442
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle })
4443
- ] }) }) }),
4444
- /* @__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(
4445
- "div",
4446
- {
4447
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
4448
- children: [
4449
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
4450
- /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-3", children: [
4451
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
4452
- /* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded w-3/4" }),
4453
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }),
4454
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
4455
- ] })
4456
- ]
4457
- },
4458
- i
4459
- )) }) : 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: [
4460
- 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(
4461
- BlogCard,
4462
- {
4463
- post,
4464
- basePath: blogPostBasePath
4465
- },
4466
- post.id
4467
- )) }),
4468
- /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: regularPosts.map((post) => /* @__PURE__ */ jsx(
4469
- BlogCard,
4470
- {
4471
- post,
4472
- basePath: blogPostBasePath
4473
- },
4474
- post.id
4475
- )) }),
4476
- total > limit && /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-4 mt-12", children: [
4477
- /* @__PURE__ */ jsx(
4478
- "button",
4479
- {
4480
- onClick: () => setOffset(Math.max(0, offset - limit)),
4481
- disabled: offset === 0,
4482
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
4483
- children: "Previous"
4484
- }
4485
- ),
4486
- /* @__PURE__ */ jsx(
4487
- "button",
4488
- {
4489
- onClick: () => setOffset(offset + limit),
4490
- disabled: offset + limit >= total,
4491
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
4492
- children: "Next"
4493
- }
4494
- )
4495
- ] })
4496
- ] }) }) })
4497
- ] });
4498
- }
4499
- function BlogPostPage(props) {
4500
- const {
4501
- productName,
4502
- blogListUrl = "/blog",
4503
- backLabel = "Back to Blog",
4504
- className = ""
4505
- } = props;
4506
- const client = useWebSDK();
4507
- const config = useWebSDKConfig();
4508
- const { product } = useProduct();
4509
- const resolvedProductId = product?.id || config.productId;
4510
- const [post, setPost] = useState(null);
4511
- const [loading, setLoading] = useState(true);
4512
- const [error, setError] = useState(null);
4513
- const slug = typeof window !== "undefined" ? window.location.pathname.split("/").pop() || "" : "";
4514
- useEffect(() => {
4515
- if (!slug || !resolvedProductId) return;
4516
- async function fetchPost() {
4517
- setLoading(true);
4518
- try {
4519
- const result = await client.query(BLOG_POST_QUERY, {
4520
- slug,
4521
- productId: resolvedProductId
4522
- });
4523
- if (result.errors?.length) {
4524
- setError(result.errors[0]?.message ?? "Failed to load post");
4525
- return;
4526
- }
4527
- setPost(result.data?.publicContentPage ?? null);
4528
- setError(null);
4529
- } catch {
4530
- setError("Failed to load blog post");
4531
- } finally {
4532
- setLoading(false);
4533
- }
4534
- }
4535
- fetchPost();
4536
- }, [client, slug, resolvedProductId]);
4537
- if (loading) {
4538
- 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: [
4539
- /* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded w-2/3" }),
4540
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/3" }),
4541
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted rounded-xl" }),
4542
- /* @__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)) })
4543
- ] }) }) });
4544
- }
4545
- if (error || !post) {
4546
- 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: [
4547
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mb-4", children: "Post not found" }),
4548
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-6", children: error || "This blog post doesn't exist or has been removed." }),
4549
- /* @__PURE__ */ jsxs(
4550
- "a",
4551
- {
4552
- href: blogListUrl,
4553
- className: "text-primary hover:underline font-medium",
4554
- children: [
4555
- "\u2190 ",
4556
- backLabel
4557
- ]
4558
- }
4559
- )
4560
- ] }) });
4561
- }
4562
- const date = post.publishedAt || post.lastUpdated;
4563
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
4564
- year: "numeric",
4565
- month: "long",
4566
- day: "numeric"
4567
- }) : "";
4568
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
4569
- /* @__PURE__ */ jsx(
4570
- PageHead,
4571
- {
4572
- title: post.seoTitle || post.title,
4573
- description: post.seoDescription || post.excerpt || "",
4574
- productName,
4575
- keywords: post.seoKeywords || ""
4576
- }
4577
- ),
4578
- /* @__PURE__ */ jsxs("article", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-20", children: [
4579
- /* @__PURE__ */ jsxs(
4580
- "a",
4581
- {
4582
- href: blogListUrl,
4583
- className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors",
4584
- children: [
4585
- "\u2190 ",
4586
- backLabel
4587
- ]
4588
- }
4589
- ),
4590
- 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 }),
4591
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4", children: post.title }),
4592
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm text-muted-foreground mb-8", children: [
4593
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: post.author }),
4594
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4595
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
4596
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
4597
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4598
- /* @__PURE__ */ jsxs("span", { children: [
4599
- post.readingTime,
4600
- " min read"
4601
- ] })
4602
- ] })
4603
- ] }),
4604
- post.thumbnail && /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] rounded-xl overflow-hidden mb-10", children: /* @__PURE__ */ jsx(
4605
- "img",
4606
- {
4607
- src: post.thumbnail,
4608
- alt: post.title,
4609
- className: "w-full h-full object-cover"
4610
- }
4611
- ) }),
4612
- /* @__PURE__ */ jsx("div", { className: "prose prose-gray dark:prose-invert max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content: post.content, format: post.contentFormat }) })
4613
- ] })
4614
- ] });
4615
- }
4616
- var CASE_STUDY_TAG = "case-study";
4617
- var CASE_STUDIES_QUERY = `
4618
- query CaseStudies($productId: String!, $limit: Int, $offset: Int) {
4619
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
4620
- items {
4621
- id
4622
- title
4623
- slug
4624
- lastUpdated
4625
- tags
4626
- seoDescription
4627
- excerpt
4628
- thumbnail
4629
- author
4630
- publishedAt
4631
- readingTime
4632
- featured
4633
- category
4634
- }
4635
- total
4636
- hasMore
4637
- }
4638
- }
4639
- `;
4640
- var CASE_STUDY_QUERY = `
4641
- query CaseStudy($slug: String!, $productId: String) {
4642
- publicContentPage(slug: $slug, productId: $productId) {
4643
- id
4644
- title
4645
- slug
4646
- content
4647
- contentFormat
4648
- seoTitle
4649
- seoDescription
4650
- seoKeywords
4651
- lastUpdated
4652
- tags
4653
- excerpt
4654
- thumbnail
4655
- author
4656
- publishedAt
4657
- readingTime
4658
- featured
4659
- category
4660
- }
4661
- }
4662
- `;
4663
- function CaseStudyCard({
4664
- post,
4665
- basePath = "/case-studies"
4666
- }) {
4667
- const date = post.publishedAt || post.lastUpdated;
4668
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
4669
- year: "numeric",
4670
- month: "long",
4671
- day: "numeric"
4672
- }) : "";
4673
- const [imgError, setImgError] = useState(false);
4674
- return /* @__PURE__ */ jsxs(
4675
- "a",
4676
- {
4677
- href: `${basePath}/${post.slug}`,
4678
- className: "group block bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300",
4679
- children: [
4680
- post.thumbnail && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden", children: /* @__PURE__ */ jsx(
4681
- "img",
4682
- {
4683
- src: post.thumbnail,
4684
- alt: post.title,
4685
- className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
4686
- loading: "lazy",
4687
- onError: () => setImgError(true)
4688
- }
4689
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
4690
- "svg",
4691
- {
4692
- className: "w-12 h-12 text-muted-foreground/20",
4693
- fill: "none",
4694
- viewBox: "0 0 24 24",
4695
- stroke: "currentColor",
4696
- children: /* @__PURE__ */ jsx(
4697
- "path",
4698
- {
4699
- strokeLinecap: "round",
4700
- strokeLinejoin: "round",
4701
- strokeWidth: 1.5,
4702
- 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"
4703
- }
4704
- )
4705
- }
4706
- ) }),
4707
- /* @__PURE__ */ jsxs("div", { className: "p-5 sm:p-6", children: [
4708
- 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 }),
4709
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-2", children: post.title }),
4710
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4 line-clamp-3", children: post.excerpt || post.seoDescription || "" }),
4711
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
4712
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: post.author }),
4713
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4714
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
4715
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
4716
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4717
- /* @__PURE__ */ jsxs("span", { children: [
4718
- post.readingTime,
4719
- " min read"
4720
- ] })
4721
- ] })
4722
- ] })
4723
- ] })
4724
- ]
4725
- }
4726
- );
4727
- }
4728
- function CaseStudiesListPage(props) {
4729
- const {
4730
- productName,
4731
- heroTitle = "Case Studies",
4732
- heroSubtitle = "Real-world stories of teams building with our platform",
4733
- caseStudyBasePath = "/case-studies",
4734
- className = "",
4735
- seoTitle,
4736
- seoDescription,
4737
- seoKeywords
4738
- } = props;
4739
- const client = useWebSDK();
4740
- const config = useWebSDKConfig();
4741
- const { product } = useProduct();
4742
- const resolvedProductId = product?.id || config.productId;
4743
- const [posts, setPosts] = useState([]);
4744
- const [total, setTotal] = useState(0);
4745
- const [loading, setLoading] = useState(true);
4746
- const [error, setError] = useState(null);
4747
- const [offset, setOffset] = useState(0);
4748
- const limit = 12;
4749
- const fetchPosts = useCallback(async () => {
4750
- if (!resolvedProductId) return;
4751
- setLoading(true);
4752
- try {
4753
- const result = await client.query(CASE_STUDIES_QUERY, {
4754
- productId: resolvedProductId,
4755
- limit,
4756
- offset
4757
- });
4758
- if (result.errors?.length) {
4759
- setError(result.errors[0]?.message ?? "Failed to load case studies");
4760
- return;
4761
- }
4762
- const caseStudies = (result.data?.publicContentPages?.items || []).filter(
4763
- (p) => p.tags?.includes(CASE_STUDY_TAG)
4764
- );
4765
- setPosts(caseStudies);
4766
- setTotal(result.data?.publicContentPages?.total || 0);
4767
- setError(null);
4768
- } catch {
4769
- setError("Failed to load case studies");
4770
- } finally {
4771
- setLoading(false);
4772
- }
4773
- }, [client, resolvedProductId, offset]);
4774
- useEffect(() => {
4775
- fetchPosts();
4776
- }, [fetchPosts]);
4777
- const featuredPosts = posts.filter((p) => p.featured);
4778
- const regularPosts = posts.filter((p) => !p.featured);
4779
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
4780
- seoTitle && /* @__PURE__ */ jsx(
4781
- PageHead,
4782
- {
4783
- title: seoTitle,
4784
- description: seoDescription || heroSubtitle,
4785
- productName,
4786
- keywords: seoKeywords
4787
- }
4788
- ),
4789
- /* @__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: [
4790
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground", children: heroTitle }),
4791
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle })
4792
- ] }) }) }),
4793
- /* @__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(
4794
- "div",
4795
- {
4796
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
4797
- children: [
4798
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
4799
- /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-3", children: [
4800
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
4801
- /* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded w-3/4" }),
4802
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }),
4803
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
4804
- ] })
4805
- ]
4806
- },
4807
- i
4808
- )) }) : 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 case studies yet. Check back soon!" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4809
- 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(
4810
- CaseStudyCard,
4811
- {
4812
- post,
4813
- basePath: caseStudyBasePath
4814
- },
4815
- post.id
4816
- )) }),
4817
- /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: regularPosts.map((post) => /* @__PURE__ */ jsx(
4818
- CaseStudyCard,
4819
- {
4820
- post,
4821
- basePath: caseStudyBasePath
4822
- },
4823
- post.id
4824
- )) }),
4825
- total > limit && /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-4 mt-12", children: [
4826
- /* @__PURE__ */ jsx(
4827
- "button",
4828
- {
4829
- onClick: () => setOffset(Math.max(0, offset - limit)),
4830
- disabled: offset === 0,
4831
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
4832
- children: "Previous"
4833
- }
4834
- ),
4835
- /* @__PURE__ */ jsx(
4836
- "button",
4837
- {
4838
- onClick: () => setOffset(offset + limit),
4839
- disabled: offset + limit >= total,
4840
- className: "px-5 py-2.5 text-sm font-medium border rounded-lg disabled:opacity-40 hover:bg-muted transition-colors",
4841
- children: "Next"
4842
- }
4843
- )
4844
- ] })
4845
- ] }) }) })
4846
- ] });
4847
- }
4848
- function CaseStudyPage(props) {
4849
- const {
4850
- productName,
4851
- caseStudiesListUrl = "/case-studies",
4852
- backLabel = "Back to Case Studies",
4853
- className = ""
4854
- } = props;
4855
- const client = useWebSDK();
4856
- const config = useWebSDKConfig();
4857
- const { product } = useProduct();
4858
- const resolvedProductId = product?.id || config.productId;
4859
- const [post, setPost] = useState(null);
4860
- const [loading, setLoading] = useState(true);
4861
- const [error, setError] = useState(null);
4862
- const slug = typeof window !== "undefined" ? window.location.pathname.split("/").pop() || "" : "";
4863
- useEffect(() => {
4864
- if (!slug || !resolvedProductId) return;
4865
- async function fetchPost() {
4866
- setLoading(true);
4867
- try {
4868
- const result = await client.query(CASE_STUDY_QUERY, {
4869
- slug,
4870
- productId: resolvedProductId
4871
- });
4872
- if (result.errors?.length) {
4873
- setError(result.errors[0]?.message ?? "Failed to load case study");
4874
- return;
4875
- }
4876
- setPost(result.data?.publicContentPage ?? null);
4877
- setError(null);
4878
- } catch {
4879
- setError("Failed to load case study");
4880
- } finally {
4881
- setLoading(false);
4882
- }
4883
- }
4884
- fetchPost();
4885
- }, [client, slug, resolvedProductId]);
4886
- if (loading) {
4887
- 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: [
4888
- /* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded w-2/3" }),
4889
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/3" }),
4890
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted rounded-xl" }),
4891
- /* @__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)) })
4892
- ] }) }) });
4893
- }
4894
- if (error || !post) {
4895
- 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: [
4896
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mb-4", children: "Case study not found" }),
4897
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-6", children: error || "This case study doesn't exist or has been removed." }),
4898
- /* @__PURE__ */ jsxs(
4899
- "a",
4900
- {
4901
- href: caseStudiesListUrl,
4902
- className: "text-primary hover:underline font-medium",
4903
- children: [
4904
- "\u2190 ",
4905
- backLabel
4906
- ]
4907
- }
4908
- )
4909
- ] }) });
4910
- }
4911
- const date = post.publishedAt || post.lastUpdated;
4912
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
4913
- year: "numeric",
4914
- month: "long",
4915
- day: "numeric"
4916
- }) : "";
4917
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
4918
- /* @__PURE__ */ jsx(
4919
- PageHead,
4920
- {
4921
- title: post.seoTitle || post.title,
4922
- description: post.seoDescription || post.excerpt || "",
4923
- productName,
4924
- keywords: post.seoKeywords || ""
4925
- }
4926
- ),
4927
- /* @__PURE__ */ jsxs("article", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-20", children: [
4928
- /* @__PURE__ */ jsxs(
4929
- "a",
4930
- {
4931
- href: caseStudiesListUrl,
4932
- className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors",
4933
- children: [
4934
- "\u2190 ",
4935
- backLabel
4936
- ]
4937
- }
4938
- ),
4939
- 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 }),
4940
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4", children: post.title }),
4941
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm text-muted-foreground mb-8", children: [
4942
- post.author && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: post.author }),
4943
- post.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4944
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
4945
- post.readingTime && /* @__PURE__ */ jsxs(Fragment, { children: [
4946
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
4947
- /* @__PURE__ */ jsxs("span", { children: [
4948
- post.readingTime,
4949
- " min read"
4950
- ] })
4951
- ] })
4952
- ] }),
4953
- post.thumbnail && /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] rounded-xl overflow-hidden mb-10", children: /* @__PURE__ */ jsx(
4954
- "img",
4955
- {
4956
- src: post.thumbnail,
4957
- alt: post.title,
4958
- className: "w-full h-full object-cover"
4959
- }
4960
- ) }),
4961
- /* @__PURE__ */ jsx("div", { className: "prose prose-gray dark:prose-invert max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content: post.content, format: post.contentFormat }) })
4962
- ] })
4963
- ] });
4964
- }
4965
- var CHANGELOG_TAG = "changelog";
4966
- var CHANGELOG_LIST_QUERY = `
4967
- query ChangelogList($productId: String!, $limit: Int, $offset: Int) {
4968
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
4969
- items {
4970
- id
4971
- title
4972
- slug
4973
- lastUpdated
4974
- tags
4975
- excerpt
4976
- seoDescription
4977
- thumbnail
4978
- author
4979
- publishedAt
4980
- readingTime
4981
- featured
4982
- category
4983
- }
4984
- total
4985
- hasMore
4986
- }
4987
- }
4988
- `;
4989
- var CHANGELOG_ENTRY_QUERY = `
4990
- query ChangelogEntry($slug: String!, $productId: String) {
4991
- publicContentPage(slug: $slug, productId: $productId) {
4992
- content
4993
- contentFormat
4994
- }
4995
- }
4996
- `;
4997
- function formatDate(value) {
4998
- if (!value) return "";
4999
- return new Date(value).toLocaleDateString("en-US", {
5000
- year: "numeric",
5001
- month: "long",
5002
- day: "numeric"
5003
- });
5004
- }
5005
- function sortByDateDesc(a, b) {
5006
- const da = new Date(a.publishedAt || a.lastUpdated || 0).getTime();
5007
- const db = new Date(b.publishedAt || b.lastUpdated || 0).getTime();
5008
- return db - da;
5009
- }
5010
- function normalize(value) {
5011
- return value.toLowerCase().trim();
5012
- }
5013
- function paginate(items, page, pageSize) {
5014
- const start = (page - 1) * pageSize;
5015
- return items.slice(start, start + pageSize);
5016
- }
5017
- function parseEntryDate(date) {
5018
- return new Date(date).getTime();
5019
- }
5020
- function useCmsChangelog() {
5021
- const client = useWebSDK();
5022
- const config = useWebSDKConfig();
5023
- const { product } = useProduct();
5024
- const resolvedProductId = product?.id || config.productId;
5025
- const [entries, setEntries] = useState([]);
5026
- const [loading, setLoading] = useState(true);
5027
- const [error, setError] = useState(null);
5028
- const fetchEntries = useCallback(async () => {
5029
- if (!resolvedProductId) {
5030
- setLoading(false);
5031
- return;
5032
- }
5033
- setLoading(true);
5034
- try {
5035
- const listResult = await client.query(CHANGELOG_LIST_QUERY, {
5036
- productId: resolvedProductId,
5037
- limit: 50,
5038
- offset: 0
5039
- });
5040
- if (listResult.errors?.length) {
5041
- setError(listResult.errors[0]?.message ?? "Failed to load changelog");
5042
- return;
5043
- }
5044
- const changelogItems = (listResult.data?.publicContentPages?.items || []).filter((p) => p.tags?.includes(CHANGELOG_TAG)).sort(sortByDateDesc);
5045
- const resolved = await Promise.all(
5046
- changelogItems.map(async (item) => {
5047
- try {
5048
- const detail = await client.query(CHANGELOG_ENTRY_QUERY, {
5049
- slug: item.slug,
5050
- productId: resolvedProductId
5051
- });
5052
- const page = detail.data?.publicContentPage;
5053
- return {
5054
- ...item,
5055
- content: page?.content,
5056
- contentFormat: page?.contentFormat
5057
- };
5058
- } catch {
5059
- return { ...item };
5060
- }
5061
- })
5062
- );
5063
- setEntries(resolved);
5064
- setError(null);
5065
- } catch {
5066
- setError("Failed to load changelog");
5067
- } finally {
5068
- setLoading(false);
5069
- }
5070
- }, [client, resolvedProductId]);
5071
- useEffect(() => {
5072
- fetchEntries();
5073
- }, [fetchEntries]);
5074
- return { entries, loading, error };
5075
- }
5076
- function CmsTimeline(props) {
5077
- const { entries, loading, error } = props;
5078
- if (loading) {
5079
- return /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto space-y-12", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx(
5080
- "div",
5081
- {
5082
- className: "rounded-2xl border border-border/40 bg-card overflow-hidden animate-pulse",
5083
- children: /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8 space-y-4", children: [
5084
- /* @__PURE__ */ jsx("div", { className: "h-3 bg-muted rounded w-1/3" }),
5085
- /* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded w-2/3" }),
5086
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded" }),
5087
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-5/6" })
5088
- ] })
5089
- },
5090
- i
5091
- )) });
5092
- }
5093
- if (error) {
5094
- return /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: error }) });
5095
- }
5096
- if (entries.length === 0) {
5097
- 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!" }) });
5098
- }
5099
- return /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto space-y-12", children: entries.map((entry) => /* @__PURE__ */ jsxs(
5100
- "article",
5101
- {
5102
- id: entry.slug,
5103
- className: "rounded-2xl border border-border/40 bg-card overflow-hidden scroll-mt-24",
5104
- children: [
5105
- entry.thumbnail && /* @__PURE__ */ jsx(
5106
- "img",
5107
- {
5108
- src: entry.thumbnail,
5109
- alt: "",
5110
- width: 1024,
5111
- height: 576,
5112
- loading: "lazy",
5113
- decoding: "async",
5114
- className: "w-full aspect-video object-cover border-b border-border/40"
5115
- }
5116
- ),
5117
- /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8", children: [
5118
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mb-2", children: [
5119
- entry.publishedAt && /* @__PURE__ */ jsx("time", { dateTime: entry.publishedAt, children: formatDate(entry.publishedAt || entry.lastUpdated) }),
5120
- entry.author && /* @__PURE__ */ jsxs(Fragment, { children: [
5121
- " \xB7 ",
5122
- entry.author
5123
- ] })
5124
- ] }),
5125
- /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold text-foreground mb-4", children: entry.title }),
5126
- entry.content ? /* @__PURE__ */ jsx("div", { className: "prose prose-sm prose-gray dark:prose-invert max-w-none", children: /* @__PURE__ */ jsx(
5127
- ContentRenderer,
5128
- {
5129
- content: entry.content,
5130
- format: entry.contentFormat
5131
- }
5132
- ) }) : entry.excerpt && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed", children: entry.excerpt })
5133
- ] })
5134
- ]
5135
- },
5136
- entry.id
5137
- )) });
5138
- }
5139
- function EntryContent({ content }) {
5140
- if (typeof content === "string") {
5141
- return /* @__PURE__ */ jsx("div", { className: "prose prose-sm prose-gray dark:prose-invert max-w-none", children: /* @__PURE__ */ jsx(ContentRenderer, { content, format: "markdown" }) });
5142
- }
5143
- return /* @__PURE__ */ jsx("div", { className: "prose prose-sm max-w-none", children: content });
5144
- }
5145
- function EntriesTimeline(props) {
5146
- const { entries, pageSize, productName } = props;
5147
- const [query, setQuery] = useState("");
5148
- const [selectedCategory, setSelectedCategory] = useState(null);
5149
- const [page, setPage] = useState(1);
5150
- const categories = useMemo(
5151
- () => Array.from(new Set(entries.map((e) => e.category))).sort(),
5152
- [entries]
5153
- );
5154
- const filtered = useMemo(() => {
5155
- const normalized = normalize(query);
5156
- return entries.filter((entry) => {
5157
- if (selectedCategory && entry.category !== selectedCategory) {
5158
- return false;
5159
- }
5160
- if (!normalized) return true;
5161
- const haystack = [
5162
- entry.title,
5163
- entry.version,
5164
- entry.category,
5165
- ...entry.tags ?? [],
5166
- typeof entry.content === "string" ? entry.content : ""
5167
- ].join(" ").toLowerCase();
5168
- return haystack.includes(normalized);
5169
- }).sort((a, b) => parseEntryDate(b.date) - parseEntryDate(a.date));
5170
- }, [entries, query, selectedCategory]);
5171
- const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
5172
- const safePage = Math.min(page, totalPages);
5173
- const pageItems = paginate(filtered, safePage, pageSize);
5174
- useEffect(() => {
5175
- setPage(1);
5176
- }, [query, selectedCategory]);
5177
- if (entries.length === 0) {
5178
- 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!" }) });
5179
- }
5180
- return /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto", children: [
5181
- /* @__PURE__ */ jsxs("div", { className: "mb-8 space-y-4", children: [
5182
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
5183
- /* @__PURE__ */ jsx(
5184
- Search,
5185
- {
5186
- className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground",
5187
- "aria-hidden": "true"
5188
- }
5189
- ),
5190
- /* @__PURE__ */ jsx(
5191
- Input,
5192
- {
5193
- type: "search",
5194
- placeholder: "Search releases, features, fixes\u2026",
5195
- value: query,
5196
- onChange: (e) => setQuery(e.target.value),
5197
- className: "pl-9",
5198
- "aria-label": "Search changelog"
5199
- }
5200
- )
5201
- ] }),
5202
- categories.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
5203
- /* @__PURE__ */ jsx(
5204
- "button",
5205
- {
5206
- type: "button",
5207
- onClick: () => setSelectedCategory(null),
5208
- className: cn(
5209
- "px-3 py-1 rounded-full text-xs font-medium border transition-colors",
5210
- selectedCategory === null ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border/60 hover:border-primary/40"
5211
- ),
5212
- children: "All"
5213
- }
5214
- ),
5215
- categories.map((category) => /* @__PURE__ */ jsx(
5216
- "button",
5217
- {
5218
- type: "button",
5219
- onClick: () => setSelectedCategory(category),
5220
- className: cn(
5221
- "px-3 py-1 rounded-full text-xs font-medium border transition-colors",
5222
- selectedCategory === category ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border/60 hover:border-primary/40"
5223
- ),
5224
- children: category
5225
- },
5226
- category
5227
- ))
5228
- ] })
5229
- ] }),
5230
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
5231
- /* @__PURE__ */ jsx(
5232
- "div",
5233
- {
5234
- className: "absolute left-[1.125rem] top-3 bottom-3 w-px bg-border/60 hidden sm:block",
5235
- "aria-hidden": "true"
5236
- }
5237
- ),
5238
- /* @__PURE__ */ jsx("div", { className: "space-y-8", children: pageItems.map((entry) => /* @__PURE__ */ jsxs(
5239
- "article",
5240
- {
5241
- className: "relative sm:pl-12",
5242
- children: [
5243
- /* @__PURE__ */ jsx(
5244
- "div",
5245
- {
5246
- 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",
5247
- "aria-hidden": "true",
5248
- children: /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-primary-foreground" })
5249
- }
5250
- ),
5251
- /* @__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: [
5252
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-3", children: [
5253
- /* @__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 }),
5254
- /* @__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: [
5255
- "v",
5256
- entry.version
5257
- ] }),
5258
- /* @__PURE__ */ jsx(
5259
- "time",
5260
- {
5261
- className: "text-xs text-muted-foreground",
5262
- dateTime: entry.date,
5263
- children: formatDate(entry.date)
5264
- }
5265
- )
5266
- ] }),
5267
- 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(
5268
- "span",
5269
- {
5270
- className: "text-[10px] uppercase tracking-wide text-muted-foreground bg-background border border-border/40 px-1.5 py-0.5 rounded",
5271
- children: tag
5272
- },
5273
- tag
5274
- )) }),
5275
- /* @__PURE__ */ jsx("h2", { className: "text-xl sm:text-2xl font-bold text-foreground mb-4", children: entry.title }),
5276
- /* @__PURE__ */ jsx(EntryContent, { content: entry.content })
5277
- ] }) })
5278
- ]
5279
- },
5280
- `${entry.version}-${entry.date}`
5281
- )) })
5282
- ] }),
5283
- 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." }) }),
5284
- totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "mt-10 flex items-center justify-between", children: [
5285
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
5286
- "Page ",
5287
- safePage,
5288
- " of ",
5289
- totalPages,
5290
- " \xB7 ",
5291
- filtered.length,
5292
- " entries"
5293
- ] }),
5294
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5295
- /* @__PURE__ */ jsx(
5296
- Button,
5297
- {
5298
- variant: "outline",
5299
- size: "sm",
5300
- onClick: () => setPage((p) => Math.max(1, p - 1)),
5301
- disabled: safePage <= 1,
5302
- "aria-label": "Previous page",
5303
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
5304
- }
5305
- ),
5306
- /* @__PURE__ */ jsx(
5307
- Button,
5308
- {
5309
- variant: "outline",
5310
- size: "sm",
5311
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
5312
- disabled: safePage >= totalPages,
5313
- "aria-label": "Next page",
5314
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
5315
- }
5316
- )
5317
- ] })
5318
- ] }),
5319
- productName && pageItems.length > 0 && /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: JSON.stringify({
5320
- "@context": "https://schema.org",
5321
- "@type": "ItemList",
5322
- name: `${productName} Changelog`,
5323
- itemListElement: pageItems.map((entry, index) => ({
5324
- "@type": "ListItem",
5325
- position: (safePage - 1) * pageSize + index + 1,
5326
- name: entry.title,
5327
- description: typeof entry.content === "string" ? entry.content.slice(0, 200) : void 0,
5328
- datePublished: entry.date
5329
- }))
5330
- }) })
5331
- ] });
5332
- }
5333
- function ChangelogPage(props) {
5334
- const {
5335
- productName,
5336
- heroEyebrow = "What's new",
5337
- heroTitle = "Changelog",
5338
- heroSubtitle = "Platform updates, improvements, and announcements \u2014 newest first.",
5339
- ctaTitle,
5340
- ctaSubtitle,
5341
- ctaHref,
5342
- ctaLabel,
5343
- className = "",
5344
- seoTitle,
5345
- seoDescription,
5346
- seoKeywords,
5347
- entries,
5348
- pageSize = 10
5349
- } = props;
5350
- const { product } = useProduct();
5351
- const resolvedProductName = productName || product?.name;
5352
- const cms = useCmsChangelog();
5353
- const showCta = Boolean(ctaHref && ctaLabel);
5354
- const isPropDriven = entries != null;
5355
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
5356
- /* @__PURE__ */ jsx(
5357
- PageHead,
5358
- {
5359
- title: seoTitle || heroTitle,
5360
- description: seoDescription || heroSubtitle,
5361
- productName: resolvedProductName,
5362
- keywords: seoKeywords
5363
- }
5364
- ),
5365
- /* @__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: [
5366
- /* @__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: [
5367
- /* @__PURE__ */ jsx(History, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
5368
- heroEyebrow
5369
- ] }),
5370
- /* @__PURE__ */ jsx("h1", { className: "text-4xl sm:text-5xl font-bold text-foreground mb-4", children: heroTitle }),
5371
- /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: heroSubtitle })
5372
- ] }) }),
5373
- /* @__PURE__ */ jsx("section", { className: "px-4 sm:px-6 lg:px-8 pb-20", children: isPropDriven ? /* @__PURE__ */ jsx(
5374
- EntriesTimeline,
5375
- {
5376
- entries,
5377
- pageSize,
5378
- productName: resolvedProductName
5379
- }
5380
- ) : /* @__PURE__ */ jsx(
5381
- CmsTimeline,
5382
- {
5383
- entries: cms.entries,
5384
- loading: cms.loading,
5385
- error: cms.error
5386
- }
5387
- ) }),
5388
- 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: [
5389
- ctaTitle && /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-foreground mb-3", children: ctaTitle }),
5390
- ctaSubtitle && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-7", children: ctaSubtitle }),
5391
- /* @__PURE__ */ jsx(
5392
- "a",
5393
- {
5394
- href: ctaHref,
5395
- 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",
5396
- children: ctaLabel
5397
- }
5398
- )
5399
- ] }) })
5400
- ] });
5401
- }
5402
-
5403
- // src/content/utils.ts
5404
- function formatBytes(bytes) {
5405
- if (bytes == null) return "";
5406
- if (bytes < 1024) return `${bytes} B`;
5407
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
5408
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
5409
- }
5410
- function fileExt(file) {
5411
- const name = file.fileName || file.fileUrl.split("/").pop()?.split("?")[0] || "";
5412
- const ext = name.split(".").pop();
5413
- return ext && ext !== name ? ext.toUpperCase() : "FILE";
5414
- }
5415
- async function downloadFile(e, file) {
5416
- e.preventDefault();
5417
- const openInTab = () => window.open(file.fileUrl, "_blank", "noopener,noreferrer");
5418
- const name = file.fileName || file.fileUrl.split("/").pop()?.split("?")[0] || file.title || "download";
5419
- try {
5420
- const res = await fetch(file.fileUrl, { mode: "cors" });
5421
- if (!res.ok) {
5422
- openInTab();
5423
- return;
5424
- }
5425
- const blob = await res.blob();
5426
- const objUrl = URL.createObjectURL(blob);
5427
- const a = document.createElement("a");
5428
- a.href = objUrl;
5429
- a.download = name;
5430
- document.body.appendChild(a);
5431
- a.click();
5432
- a.remove();
5433
- URL.revokeObjectURL(objUrl);
5434
- } catch {
5435
- openInTab();
5436
- }
5437
- }
5438
- var PRESS_KIT_QUERY = `
5439
- query PublicPressKit($productId: String!, $limit: Int, $offset: Int) {
5440
- publicPressKitAssets(productId: $productId, limit: $limit, offset: $offset) {
5441
- items {
5442
- id
5443
- category
5444
- title
5445
- slug
5446
- description
5447
- fileUrl
5448
- fileName
5449
- fileSize
5450
- mimeType
5451
- thumbnailUrl
5452
- version
5453
- tags
5454
- sortOrder
5455
- updatedAt
5456
- }
5457
- total
5458
- hasMore
5459
- }
5460
- }
5461
- `;
5462
- var PRESS_RELEASES_QUERY = `
5463
- query PublicPressReleases($productId: String!, $limit: Int, $offset: Int) {
5464
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
5465
- items {
5466
- id
5467
- title
5468
- slug
5469
- lastUpdated
5470
- tags
5471
- seoDescription
5472
- excerpt
5473
- thumbnail
5474
- author
5475
- publishedAt
5476
- readingTime
5477
- featured
5478
- category
5479
- }
5480
- total
5481
- hasMore
5482
- }
5483
- }
5484
- `;
5485
- var CATEGORY_ORDER = [
5486
- "LOGO",
5487
- "BANNER",
5488
- "SCREENSHOT",
5489
- "BRAND_GUIDE",
5490
- "BROCHURE",
5491
- "DOCUMENT",
5492
- "PRESS_RELEASE"
5493
- ];
5494
- var CATEGORY_LABELS = {
5495
- LOGO: "Logos",
5496
- BANNER: "Banners",
5497
- SCREENSHOT: "Screenshots",
5498
- BRAND_GUIDE: "Brand Guides",
5499
- BROCHURE: "Brochures",
5500
- DOCUMENT: "Documents",
5501
- PRESS_RELEASE: "Press Releases"
5502
- };
5503
- function isImageAsset(a) {
5504
- return (a.mimeType || "").startsWith("image/");
5505
- }
5506
- function PressKitLightbox({
5507
- asset,
5508
- onClose
5509
- }) {
5510
- useEffect(() => {
5511
- if (!asset) return;
5512
- const onKey = (e) => {
5513
- if (e.key === "Escape") onClose();
5514
- };
5515
- document.addEventListener("keydown", onKey);
5516
- const prevOverflow = document.body.style.overflow;
5517
- document.body.style.overflow = "hidden";
5518
- return () => {
5519
- document.removeEventListener("keydown", onKey);
5520
- document.body.style.overflow = prevOverflow;
5521
- };
5522
- }, [asset, onClose]);
5523
- if (!asset) return null;
5524
- const ext = (asset.mimeType || "file").split("/").pop()?.toUpperCase() || "FILE";
5525
- const size = formatBytes(asset.fileSize);
5526
- return /* @__PURE__ */ jsx(
5527
- "div",
5528
- {
5529
- role: "dialog",
5530
- "aria-modal": "true",
5531
- "aria-label": `${asset.title} preview`,
5532
- onClick: onClose,
5533
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4 sm:p-8",
5534
- children: /* @__PURE__ */ jsxs(
5535
- "div",
5536
- {
5537
- onClick: (e) => e.stopPropagation(),
5538
- className: "relative flex max-h-full w-full max-w-4xl flex-col overflow-hidden rounded-xl border bg-card shadow-2xl",
5539
- children: [
5540
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 border-b p-4", children: [
5541
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
5542
- /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-foreground", children: asset.title }),
5543
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
5544
- ext,
5545
- size ? ` \xB7 ${size}` : ""
5546
- ] })
5547
- ] }),
5548
- /* @__PURE__ */ jsx(
5549
- "button",
5550
- {
5551
- type: "button",
5552
- onClick: onClose,
5553
- "aria-label": "Close preview",
5554
- className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
5555
- children: /* @__PURE__ */ jsx(
5556
- "svg",
5557
- {
5558
- className: "h-5 w-5",
5559
- fill: "none",
5560
- viewBox: "0 0 24 24",
5561
- stroke: "currentColor",
5562
- strokeWidth: 2,
5563
- children: /* @__PURE__ */ jsx(
5564
- "path",
5565
- {
5566
- strokeLinecap: "round",
5567
- strokeLinejoin: "round",
5568
- d: "M6 18L18 6M6 6l12 12"
5569
- }
5570
- )
5571
- }
5572
- )
5573
- }
5574
- )
5575
- ] }),
5576
- /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center overflow-auto bg-muted/20 p-4", children: /* @__PURE__ */ jsx(
5577
- "img",
5578
- {
5579
- src: asset.fileUrl,
5580
- alt: asset.title,
5581
- className: "max-h-[70vh] w-auto max-w-full object-contain"
5582
- }
5583
- ) }),
5584
- /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t p-4", children: /* @__PURE__ */ jsxs(
5585
- "a",
5586
- {
5587
- href: asset.fileUrl,
5588
- target: "_blank",
5589
- rel: "noopener noreferrer",
5590
- download: true,
5591
- onClick: (e) => {
5592
- void downloadFile(e, asset);
5593
- },
5594
- 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",
5595
- children: [
5596
- /* @__PURE__ */ jsx(
5597
- "svg",
5598
- {
5599
- className: "h-4 w-4",
5600
- fill: "none",
5601
- viewBox: "0 0 24 24",
5602
- stroke: "currentColor",
5603
- strokeWidth: 2,
5604
- children: /* @__PURE__ */ jsx(
5605
- "path",
5606
- {
5607
- strokeLinecap: "round",
5608
- strokeLinejoin: "round",
5609
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
5610
- }
5611
- )
5612
- }
5613
- ),
5614
- "Download"
5615
- ]
5616
- }
5617
- ) })
5618
- ]
5619
- }
5620
- )
5621
- }
5622
- );
5623
- }
5624
- function AssetCard({
5625
- asset,
5626
- onPreview
5627
- }) {
5628
- const date = asset.updatedAt ? new Date(asset.updatedAt).toLocaleDateString("en-US", {
5629
- year: "numeric",
5630
- month: "short",
5631
- day: "numeric"
5632
- }) : "";
5633
- const ext = (asset.mimeType || "file").split("/").pop()?.toUpperCase() || "FILE";
5634
- const size = formatBytes(asset.fileSize);
5635
- const [imgError, setImgError] = useState(false);
5636
- const thumb = asset.thumbnailUrl && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden bg-muted/30", children: /* @__PURE__ */ jsx(
5637
- "img",
5638
- {
5639
- src: asset.thumbnailUrl,
5640
- alt: asset.title,
5641
- className: "w-full h-full object-contain group-hover:scale-105 transition-transform duration-300",
5642
- loading: "lazy",
5643
- onError: () => setImgError(true)
5644
- }
5645
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
5646
- "svg",
5647
- {
5648
- className: "w-12 h-12 text-muted-foreground/20",
5649
- fill: "none",
5650
- viewBox: "0 0 24 24",
5651
- stroke: "currentColor",
5652
- children: /* @__PURE__ */ jsx(
5653
- "path",
5654
- {
5655
- strokeLinecap: "round",
5656
- strokeLinejoin: "round",
5657
- strokeWidth: 1.5,
5658
- 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"
5659
- }
5660
- )
5661
- }
5662
- ) });
5663
- 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: [
5664
- onPreview && isImageAsset(asset) ? /* @__PURE__ */ jsx(
5665
- "button",
5666
- {
5667
- type: "button",
5668
- onClick: () => onPreview(asset),
5669
- "aria-label": `Preview ${asset.title}`,
5670
- className: "block w-full text-left cursor-zoom-in",
5671
- children: thumb
5672
- }
5673
- ) : /* @__PURE__ */ jsx(
5674
- "a",
5675
- {
5676
- href: asset.fileUrl,
5677
- target: "_blank",
5678
- rel: "noopener noreferrer",
5679
- "aria-label": `Open ${asset.title}`,
5680
- className: "block",
5681
- children: thumb
5682
- }
5683
- ),
5684
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-5", children: [
5685
- /* @__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 }),
5686
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground mb-1 line-clamp-2", children: asset.title }),
5687
- asset.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3 line-clamp-2", children: asset.description }),
5688
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-3", children: [
5689
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
5690
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: ext }),
5691
- size && /* @__PURE__ */ jsxs(Fragment, { children: [
5692
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
5693
- /* @__PURE__ */ jsx("span", { children: size })
5694
- ] }),
5695
- date && /* @__PURE__ */ jsxs(Fragment, { children: [
5696
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
5697
- /* @__PURE__ */ jsx("span", { children: date })
5698
- ] })
5699
- ] }),
5700
- /* @__PURE__ */ jsxs(
5701
- "a",
5702
- {
5703
- href: asset.fileUrl,
5704
- target: "_blank",
5705
- rel: "noopener noreferrer",
5706
- download: true,
5707
- onClick: (e) => {
5708
- void downloadFile(e, asset);
5709
- },
5710
- className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline",
5711
- children: [
5712
- /* @__PURE__ */ jsx(
5713
- "svg",
5714
- {
5715
- className: "w-4 h-4",
5716
- fill: "none",
5717
- viewBox: "0 0 24 24",
5718
- stroke: "currentColor",
5719
- strokeWidth: 2,
5720
- children: /* @__PURE__ */ jsx(
5721
- "path",
5722
- {
5723
- strokeLinecap: "round",
5724
- strokeLinejoin: "round",
5725
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
5726
- }
5727
- )
5728
- }
5729
- ),
5730
- "Download"
5731
- ]
5732
- }
5733
- )
5734
- ] })
5735
- ] })
5736
- ] });
5737
- }
5738
- function PressReleaseCard({
5739
- release,
5740
- basePath
5741
- }) {
5742
- const date = release.publishedAt || release.lastUpdated;
5743
- const formattedDate = date ? new Date(date).toLocaleDateString("en-US", {
5744
- year: "numeric",
5745
- month: "long",
5746
- day: "numeric"
5747
- }) : "";
5748
- const [imgError, setImgError] = useState(false);
5749
- return /* @__PURE__ */ jsxs(
5750
- "a",
5751
- {
5752
- href: `${basePath}/${release.slug}`,
5753
- className: "group flex flex-col bg-card border rounded-xl overflow-hidden hover:shadow-lg hover:border-primary/30 transition-all duration-300",
5754
- children: [
5755
- release.thumbnail && !imgError ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] overflow-hidden bg-muted/30", children: /* @__PURE__ */ jsx(
5756
- "img",
5757
- {
5758
- src: release.thumbnail,
5759
- alt: release.title,
5760
- className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300",
5761
- loading: "lazy",
5762
- onError: () => setImgError(true)
5763
- }
5764
- ) }) : /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx(
5765
- "svg",
5766
- {
5767
- className: "w-12 h-12 text-muted-foreground/20",
5768
- fill: "none",
5769
- viewBox: "0 0 24 24",
5770
- stroke: "currentColor",
5771
- children: /* @__PURE__ */ jsx(
5772
- "path",
5773
- {
5774
- strokeLinecap: "round",
5775
- strokeLinejoin: "round",
5776
- strokeWidth: 1.5,
5777
- 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"
5778
- }
5779
- )
5780
- }
5781
- ) }),
5782
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-5 sm:p-6", children: [
5783
- 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 }),
5784
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-2", children: release.title }),
5785
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4 line-clamp-3", children: release.excerpt || release.seoDescription || "" }),
5786
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center gap-3 text-xs text-muted-foreground", children: [
5787
- release.author && /* @__PURE__ */ jsx("span", { className: "font-medium", children: release.author }),
5788
- release.author && formattedDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
5789
- formattedDate && /* @__PURE__ */ jsx("span", { children: formattedDate }),
5790
- release.readingTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
5791
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
5792
- /* @__PURE__ */ jsxs("span", { children: [
5793
- release.readingTime,
5794
- " min read"
5795
- ] })
5796
- ] }) : null
2502
+ )
2503
+ }
2504
+ ) }),
2505
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground mb-3", children: "Invalid Unsubscribe Link" }),
2506
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "This unsubscribe link is invalid or missing. If you want to unsubscribe, please use the link from your newsletter email." })
5797
2507
  ] })
5798
2508
  ] })
5799
2509
  ]
5800
2510
  }
5801
2511
  );
5802
2512
  }
5803
- function PressKitPage(props) {
5804
- const {
5805
- productName,
5806
- heroTitle = "Press & Media Kit",
5807
- heroSubtitle = "Official logos, brand assets, and media resources. Everything you need to write about us.",
5808
- mediaContactEmail,
5809
- brandKitUrl,
5810
- className = "",
5811
- seoTitle,
5812
- seoDescription,
5813
- seoKeywords,
5814
- showPressReleases = true,
5815
- pressReleaseBasePath = "/press",
5816
- pressReleasesTitle = "Press Releases"
5817
- } = props;
5818
- const client = useWebSDK();
5819
- const config = useWebSDKConfig();
5820
- const { product } = useProduct();
5821
- const resolvedProductId = product?.id || config.productId;
5822
- const contactEmail = mediaContactEmail || product?.contactEmail || void 0;
5823
- const [assets, setAssets] = useState([]);
5824
- const [loading, setLoading] = useState(true);
5825
- const [error, setError] = useState(null);
5826
- const [activeCategory, setActiveCategory] = useState("ALL");
5827
- const [releases, setReleases] = useState([]);
5828
- const [releasesLoading, setReleasesLoading] = useState(true);
5829
- const [preview, setPreview] = useState(null);
5830
- const fetchAssets = useCallback(async () => {
5831
- if (!resolvedProductId) {
5832
- setLoading(false);
5833
- return;
5834
- }
5835
- setLoading(true);
5836
- try {
5837
- const PAGE_SIZE = 100;
5838
- const MAX_PAGES = 10;
5839
- const all = [];
5840
- let offset = 0;
5841
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
5842
- const result = await client.query(PRESS_KIT_QUERY, {
5843
- productId: resolvedProductId,
5844
- limit: PAGE_SIZE,
5845
- offset
5846
- });
5847
- if (result.errors?.length) {
5848
- setError(result.errors[0]?.message ?? "Failed to load press kit");
5849
- return;
5850
- }
5851
- const page = result.data?.publicPressKitAssets;
5852
- const items = page?.items || [];
5853
- all.push(...items);
5854
- if (!page?.hasMore || items.length === 0) break;
5855
- offset += PAGE_SIZE;
5856
- }
5857
- setAssets(all);
5858
- setError(null);
5859
- } catch {
5860
- setError("Failed to load press kit");
5861
- } finally {
5862
- setLoading(false);
5863
- }
5864
- }, [client, resolvedProductId]);
5865
- useEffect(() => {
5866
- fetchAssets();
5867
- }, [fetchAssets]);
5868
- const fetchReleases = useCallback(async () => {
5869
- if (!resolvedProductId || !showPressReleases) {
5870
- setReleasesLoading(false);
5871
- return;
5872
- }
5873
- setReleasesLoading(true);
5874
- try {
5875
- const PAGE_SIZE = 100;
5876
- const MAX_PAGES = 10;
5877
- const all = [];
5878
- let offset = 0;
5879
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
5880
- const result = await client.query(PRESS_RELEASES_QUERY, {
5881
- productId: resolvedProductId,
5882
- limit: PAGE_SIZE,
5883
- offset
5884
- });
5885
- if (result.errors?.length) {
5886
- setReleases([]);
5887
- return;
5888
- }
5889
- const page = result.data?.publicContentPages;
5890
- const items = page?.items || [];
5891
- all.push(...items);
5892
- if (!page?.hasMore || items.length === 0) break;
5893
- offset += PAGE_SIZE;
5894
- }
5895
- const pressOnly = all.filter((p) => p.tags?.includes("press")).sort((a, b) => {
5896
- const da = new Date(a.publishedAt || a.lastUpdated || 0).getTime();
5897
- const db = new Date(b.publishedAt || b.lastUpdated || 0).getTime();
5898
- return db - da;
5899
- });
5900
- setReleases(pressOnly);
5901
- } catch {
5902
- setReleases([]);
5903
- } finally {
5904
- setReleasesLoading(false);
5905
- }
5906
- }, [client, resolvedProductId, showPressReleases]);
2513
+ function RybbitAnalytics({
2514
+ siteId,
2515
+ scriptUrl = "https://analytics.burdenoff.com/api/script.js",
2516
+ enabled = true
2517
+ }) {
5907
2518
  useEffect(() => {
5908
- fetchReleases();
5909
- }, [fetchReleases]);
5910
- const presentCategories = CATEGORY_ORDER.filter(
5911
- (c) => assets.some((a) => a.category === c)
5912
- );
5913
- const visibleAssets = activeCategory === "ALL" ? assets : assets.filter((a) => a.category === activeCategory);
5914
- const grouped = (activeCategory === "ALL" ? presentCategories : [activeCategory]).map((cat) => ({
5915
- cat,
5916
- items: visibleAssets.filter((a) => a.category === cat).sort((a, b) => a.sortOrder - b.sortOrder)
5917
- })).filter((g) => g.items.length > 0);
5918
- return /* @__PURE__ */ jsxs("div", { className: `w-full ${className}`, children: [
5919
- seoTitle && /* @__PURE__ */ jsx(
5920
- PageHead,
5921
- {
5922
- title: seoTitle,
5923
- description: seoDescription || heroSubtitle,
5924
- productName,
5925
- keywords: seoKeywords
2519
+ if (!enabled || !siteId) return;
2520
+ const existingScript = document.querySelector(
2521
+ `script[data-site-id="${siteId}"]`
2522
+ );
2523
+ if (existingScript) return;
2524
+ const script = document.createElement("script");
2525
+ script.src = scriptUrl;
2526
+ script.defer = true;
2527
+ script.setAttribute("data-site-id", siteId);
2528
+ document.head.appendChild(script);
2529
+ return () => {
2530
+ const scriptToRemove = document.querySelector(
2531
+ `script[data-site-id="${siteId}"]`
2532
+ );
2533
+ if (scriptToRemove) {
2534
+ document.head.removeChild(scriptToRemove);
5926
2535
  }
5927
- ),
5928
- /* @__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: [
5929
- /* @__PURE__ */ jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground", children: heroTitle }),
5930
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg text-muted-foreground", children: heroSubtitle }),
5931
- (brandKitUrl || contactEmail) && /* @__PURE__ */ jsxs("div", { className: "mt-8 flex flex-wrap items-center justify-center gap-4", children: [
5932
- brandKitUrl && /* @__PURE__ */ jsx(
5933
- "a",
5934
- {
5935
- href: brandKitUrl,
5936
- target: "_blank",
5937
- rel: "noopener noreferrer",
5938
- 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",
5939
- children: "Download brand kit"
5940
- }
5941
- ),
5942
- contactEmail && /* @__PURE__ */ jsx(
5943
- "a",
5944
- {
5945
- href: `mailto:${contactEmail}`,
5946
- className: "inline-flex items-center gap-2 px-6 py-3 rounded-lg border font-medium text-foreground hover:bg-muted transition-colors",
5947
- children: "Media inquiries"
5948
- }
5949
- )
5950
- ] })
5951
- ] }) }) }),
5952
- 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: [
5953
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground mb-6", children: pressReleasesTitle }),
5954
- /* @__PURE__ */ jsx("div", { className: "grid sm:grid-cols-2 lg:grid-cols-3 gap-6", children: releasesLoading ? [1, 2, 3].map((i) => /* @__PURE__ */ jsxs(
5955
- "div",
5956
- {
5957
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
5958
- children: [
5959
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
5960
- /* @__PURE__ */ jsxs("div", { className: "p-5 space-y-3", children: [
5961
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
5962
- /* @__PURE__ */ jsx("div", { className: "h-5 bg-muted rounded w-3/4" }),
5963
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
5964
- ] })
5965
- ]
5966
- },
5967
- i
5968
- )) : releases.map((r) => /* @__PURE__ */ jsx(
5969
- PressReleaseCard,
5970
- {
5971
- release: r,
5972
- basePath: pressReleaseBasePath
5973
- },
5974
- r.id
5975
- )) })
5976
- ] }) }),
5977
- /* @__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(
5978
- "div",
5979
- {
5980
- className: "bg-card border rounded-xl overflow-hidden animate-pulse",
5981
- children: [
5982
- /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-muted" }),
5983
- /* @__PURE__ */ jsxs("div", { className: "p-5 space-y-3", children: [
5984
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/4" }),
5985
- /* @__PURE__ */ jsx("div", { className: "h-5 bg-muted rounded w-3/4" }),
5986
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded w-1/2" })
5987
- ] })
5988
- ]
5989
- },
5990
- i
5991
- )) }) : 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: [
5992
- "Press materials are coming soon. For media inquiries",
5993
- contactEmail ? /* @__PURE__ */ jsxs(Fragment, { children: [
5994
- ", contact ",
5995
- /* @__PURE__ */ jsx(
5996
- "a",
5997
- {
5998
- href: `mailto:${contactEmail}`,
5999
- className: "text-primary hover:underline",
6000
- children: contactEmail
6001
- }
6002
- )
6003
- ] }) : " please reach out to our team",
6004
- "."
6005
- ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6006
- presentCategories.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mb-10 justify-center", children: ["ALL", ...presentCategories].map((c) => {
6007
- const active = activeCategory === c;
6008
- return /* @__PURE__ */ jsx(
6009
- "button",
6010
- {
6011
- type: "button",
6012
- onClick: () => setActiveCategory(c),
6013
- 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"}`,
6014
- children: c === "ALL" ? "All" : CATEGORY_LABELS[c] ?? c
6015
- },
6016
- c
6017
- );
6018
- }) }),
6019
- grouped.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-12", children: [
6020
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground mb-5", children: CATEGORY_LABELS[group.cat] ?? group.cat }),
6021
- /* @__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)) })
6022
- ] }, group.cat)),
6023
- contactEmail && /* @__PURE__ */ jsxs("div", { className: "mt-8 border-t pt-10 text-center", children: [
6024
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground mb-2", children: "Media Contact" }),
6025
- /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
6026
- "For interviews, partnerships, and press inquiries:",
6027
- " ",
6028
- /* @__PURE__ */ jsx(
6029
- "a",
6030
- {
6031
- href: `mailto:${contactEmail}`,
6032
- className: "text-primary hover:underline",
6033
- children: contactEmail
6034
- }
6035
- )
6036
- ] })
6037
- ] })
6038
- ] }) }) }),
6039
- /* @__PURE__ */ jsx(PressKitLightbox, { asset: preview, onClose: () => setPreview(null) })
6040
- ] });
2536
+ };
2537
+ }, [siteId, scriptUrl, enabled]);
2538
+ return null;
6041
2539
  }
6042
- var CONTRACTS_QUERY = `
6043
- query PublicContracts($productId: String!, $limit: Int, $offset: Int) {
6044
- publicPressKitAssets(productId: $productId, category: CONTRACT, limit: $limit, offset: $offset) {
2540
+ var PUBLIC_CONTENT_PAGE_QUERY = `
2541
+ query PublicContentPage($slug: String!, $productId: String) {
2542
+ publicContentPage(slug: $slug, productId: $productId) {
2543
+ id
2544
+ title
2545
+ slug
2546
+ content
2547
+ contentFormat
2548
+ seoTitle
2549
+ seoDescription
2550
+ seoKeywords
2551
+ lastUpdated
2552
+ version
2553
+ }
2554
+ }
2555
+ `;
2556
+ var PUBLIC_RESOLVED_CONTENT_PAGE_QUERY = `
2557
+ query PublicResolvedContentPage($slug: String!, $productId: String!) {
2558
+ publicResolvedContentPage(slug: $slug, productId: $productId) {
2559
+ id
2560
+ title
2561
+ slug
2562
+ content
2563
+ contentFormat
2564
+ seoTitle
2565
+ seoDescription
2566
+ seoKeywords
2567
+ lastUpdated
2568
+ version
2569
+ }
2570
+ }
2571
+ `;
2572
+ var PUBLIC_CONTENT_PAGES_QUERY = `
2573
+ query PublicContentPages($productId: String!, $limit: Int, $offset: Int) {
2574
+ publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
6045
2575
  items {
6046
2576
  id
6047
2577
  title
6048
2578
  slug
6049
- description
6050
- fileUrl
6051
- fileName
6052
- fileSize
6053
- mimeType
6054
- version
6055
- tags
6056
- sortOrder
6057
- updatedAt
2579
+ lastUpdated
6058
2580
  }
2581
+ total
6059
2582
  hasMore
6060
2583
  }
6061
2584
  }
6062
2585
  `;
6063
- function ContractsPage(props) {
6064
- const {
6065
- productName,
6066
- heroTitle = "Contracts & Legal Agreements",
6067
- heroSubtitle = "Download our standard legal agreements and policies.",
6068
- className = "",
6069
- seoTitle,
6070
- seoDescription,
6071
- seoKeywords
6072
- } = props;
2586
+ function useContentPage(slug, options) {
6073
2587
  const client = useWebSDK();
6074
2588
  const config = useWebSDKConfig();
6075
- const { product } = useProduct();
6076
- const resolvedProductId = product?.id || config.productId;
6077
- const displayName = productName ?? product?.name ?? config.productId;
6078
- const [contracts, setContracts] = useState([]);
2589
+ const [data, setData] = useState(null);
6079
2590
  const [loading, setLoading] = useState(true);
6080
2591
  const [error, setError] = useState(null);
6081
- const fetchContracts = useCallback(async () => {
6082
- if (!resolvedProductId) {
2592
+ const fetchPage = useCallback(async () => {
2593
+ setLoading(true);
2594
+ setError(null);
2595
+ try {
2596
+ const query = options?.resolve ? PUBLIC_RESOLVED_CONTENT_PAGE_QUERY : PUBLIC_CONTENT_PAGE_QUERY;
2597
+ const queryName = options?.resolve ? "publicResolvedContentPage" : "publicContentPage";
2598
+ const result = await client.query(
2599
+ query,
2600
+ {
2601
+ slug,
2602
+ productId: config.productId
2603
+ }
2604
+ );
2605
+ if (result.errors?.length) {
2606
+ setError(result.errors[0]?.message ?? "Unknown error");
2607
+ setData(null);
2608
+ } else {
2609
+ setData(result.data?.[queryName] ?? null);
2610
+ }
2611
+ } catch (err) {
2612
+ setError(err instanceof Error ? err.message : "Failed to fetch content");
2613
+ setData(null);
2614
+ } finally {
6083
2615
  setLoading(false);
6084
- return;
6085
2616
  }
2617
+ }, [client, config.productId, slug, options?.resolve]);
2618
+ useEffect(() => {
2619
+ fetchPage();
2620
+ }, [fetchPage]);
2621
+ return { data, loading, error, refetch: fetchPage };
2622
+ }
2623
+ function useContentPages(options) {
2624
+ const client = useWebSDK();
2625
+ const config = useWebSDKConfig();
2626
+ const [data, setData] = useState(null);
2627
+ const [loading, setLoading] = useState(true);
2628
+ const [error, setError] = useState(null);
2629
+ const fetchPages = useCallback(async () => {
6086
2630
  setLoading(true);
2631
+ setError(null);
6087
2632
  try {
6088
- const PAGE_SIZE = 100;
6089
- const MAX_PAGES = 10;
6090
- const all = [];
6091
- let offset = 0;
6092
- for (let pageNum = 0; pageNum < MAX_PAGES; pageNum++) {
6093
- const result = await client.query(CONTRACTS_QUERY, {
6094
- productId: resolvedProductId,
6095
- limit: PAGE_SIZE,
6096
- offset
6097
- });
6098
- if (result.errors?.length) {
6099
- setError(result.errors[0]?.message ?? "Failed to load contracts");
6100
- return;
6101
- }
6102
- const page = result.data?.publicPressKitAssets;
6103
- const items = page?.items || [];
6104
- all.push(...items);
6105
- if (!page?.hasMore || items.length === 0) break;
6106
- offset += PAGE_SIZE;
2633
+ const result = await client.query(PUBLIC_CONTENT_PAGES_QUERY, {
2634
+ productId: config.productId,
2635
+ limit: options?.limit ?? 50,
2636
+ offset: options?.offset ?? 0
2637
+ });
2638
+ if (result.errors?.length) {
2639
+ setError(result.errors[0]?.message ?? "Unknown error");
2640
+ setData(null);
2641
+ } else {
2642
+ setData(result.data?.publicContentPages ?? null);
6107
2643
  }
6108
- all.sort(
6109
- (a, b) => a.sortOrder - b.sortOrder || a.title.localeCompare(b.title)
2644
+ } catch (err) {
2645
+ setError(
2646
+ err instanceof Error ? err.message : "Failed to fetch content pages"
6110
2647
  );
6111
- setContracts(all);
6112
- setError(null);
6113
- } catch {
6114
- setError("Failed to load contracts");
2648
+ setData(null);
6115
2649
  } finally {
6116
2650
  setLoading(false);
6117
2651
  }
6118
- }, [client, resolvedProductId]);
2652
+ }, [client, config.productId, options?.limit, options?.offset]);
6119
2653
  useEffect(() => {
6120
- fetchContracts();
6121
- }, [fetchContracts]);
6122
- return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-background ${className}`, children: [
6123
- /* @__PURE__ */ jsx(
6124
- PageHead,
2654
+ fetchPages();
2655
+ }, [fetchPages]);
2656
+ return { data, loading, error, refetch: fetchPages };
2657
+ }
2658
+ function ContentRenderer({
2659
+ content,
2660
+ format = "markdown",
2661
+ className
2662
+ }) {
2663
+ if (format === "html" || format === "markdown") {
2664
+ return /* @__PURE__ */ jsx(
2665
+ "div",
6125
2666
  {
6126
- title: seoTitle || `Contracts \u2014 ${displayName}`,
6127
- description: seoDescription || heroSubtitle,
6128
- productName,
6129
- keywords: seoKeywords
2667
+ className,
2668
+ dangerouslySetInnerHTML: { __html: content }
6130
2669
  }
6131
- ),
6132
- /* @__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: [
6133
- /* @__PURE__ */ jsx("h1", { className: "text-4xl md:text-5xl font-bold text-foreground mb-4", children: heroTitle }),
6134
- /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground max-w-2xl mx-auto", children: heroSubtitle })
6135
- ] }) }),
6136
- /* @__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) => {
6137
- const ext = fileExt(c);
6138
- const size = formatBytes(c.fileSize);
6139
- return /* @__PURE__ */ jsxs(
6140
- "li",
6141
- {
6142
- className: "flex items-center justify-between gap-4 p-5 hover:bg-muted/40 transition-colors",
6143
- children: [
6144
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
6145
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
6146
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground truncate", children: c.title }),
6147
- 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 })
6148
- ] }),
6149
- c.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 line-clamp-2", children: c.description }),
6150
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: [
6151
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: ext }),
6152
- size && /* @__PURE__ */ jsxs(Fragment, { children: [
6153
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
6154
- /* @__PURE__ */ jsx("span", { children: size })
6155
- ] })
6156
- ] })
6157
- ] }),
6158
- /* @__PURE__ */ jsxs(
6159
- "a",
6160
- {
6161
- href: c.fileUrl,
6162
- target: "_blank",
6163
- rel: "noopener noreferrer",
6164
- download: true,
6165
- "aria-label": `Download ${c.title}`,
6166
- onClick: (e) => {
6167
- void downloadFile(e, c);
6168
- },
6169
- 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",
6170
- children: [
6171
- /* @__PURE__ */ jsx(
6172
- "svg",
6173
- {
6174
- className: "w-4 h-4",
6175
- fill: "none",
6176
- viewBox: "0 0 24 24",
6177
- stroke: "currentColor",
6178
- "aria-hidden": "true",
6179
- children: /* @__PURE__ */ jsx(
6180
- "path",
6181
- {
6182
- strokeLinecap: "round",
6183
- strokeLinejoin: "round",
6184
- strokeWidth: 2,
6185
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
6186
- }
6187
- )
6188
- }
6189
- ),
6190
- "Download"
6191
- ]
6192
- }
6193
- )
6194
- ]
6195
- },
6196
- c.id
6197
- );
6198
- }) }) })
6199
- ] });
2670
+ );
2671
+ }
2672
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("pre", { style: { whiteSpace: "pre-wrap", fontFamily: "inherit" }, children: content }) });
6200
2673
  }
6201
2674
 
6202
- export { BlogListPage, BlogPostPage, Button, CareersApplyForm, CaseStudiesListPage, CaseStudyPage, ChangelogPage, ContactPage, ContentRenderer, ContractsPage, DEFAULT_CONTACT_CATEGORIES, ElectronDownloadLink, Input, Label, LaunchCountdown, NewsletterForm, NewsletterPage, PageHead, PartnersPage, PressKitPage, PricingPage, PricingSection, ProblemsSolvedSection, ProductProvider, ResourceLinks, RybbitAnalytics, Select, SocialLinks, Textarea, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, WebsiteSwitcher, buttonVariants, cn, detectElectronPlatform, useContentPage, useContentPages, useProduct, useProductConfig, useWebSDK, useWebSDKConfig };
2675
+ export { Button, ContactPage, ContentRenderer, Input, Label, NewsletterForm, NewsletterPage, PageHead, PricingPage, PricingSection, ProductProvider, RybbitAnalytics, Textarea, UnsubscribePage, WaitlistForm, WaitlistPage, WebSDKClient, WebSDKProvider, buttonVariants, cn, useContentPage, useContentPages, useProduct, useProductConfig, useWebSDK, useWebSDKConfig };
6203
2676
  //# sourceMappingURL=index.mjs.map
6204
2677
  //# sourceMappingURL=index.mjs.map