@flamingo-stack/openframe-frontend-core 0.0.265 → 0.0.266

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 (87) hide show
  1. package/dist/{chunk-EXU5N5RM.js → chunk-DTJ32U7J.js} +2457 -2478
  2. package/dist/chunk-DTJ32U7J.js.map +1 -0
  3. package/dist/{chunk-ERFO4JFK.js → chunk-FREG2KXV.js} +30 -6
  4. package/dist/chunk-FREG2KXV.js.map +1 -0
  5. package/dist/{chunk-Z5YEQWLC.js → chunk-FRU4LQJ4.js} +4 -4
  6. package/dist/{chunk-J5R3QIOJ.cjs → chunk-G5GQVAPI.cjs} +8 -6
  7. package/dist/chunk-G5GQVAPI.cjs.map +1 -0
  8. package/dist/{chunk-URJQGMNH.js → chunk-HTR3ARN6.js} +6 -3
  9. package/dist/chunk-HTR3ARN6.js.map +1 -0
  10. package/dist/{chunk-TT2VBR7P.cjs → chunk-JMU4RU5J.cjs} +26 -2
  11. package/dist/chunk-JMU4RU5J.cjs.map +1 -0
  12. package/dist/{chunk-GBUGYHDY.js → chunk-K4K4X62E.js} +6 -4
  13. package/dist/chunk-K4K4X62E.js.map +1 -0
  14. package/dist/{chunk-6A2YQ5CY.js → chunk-KUYYJ2XM.js} +3 -3
  15. package/dist/{chunk-D4TWJNO5.cjs → chunk-Q52JZP3P.cjs} +29 -26
  16. package/dist/chunk-Q52JZP3P.cjs.map +1 -0
  17. package/dist/{chunk-2P6V2EWZ.cjs → chunk-QAQ7AROS.cjs} +298 -319
  18. package/dist/chunk-QAQ7AROS.cjs.map +1 -0
  19. package/dist/{chunk-A56L7UQG.cjs → chunk-QXRXIHY6.cjs} +12 -12
  20. package/dist/{chunk-A56L7UQG.cjs.map → chunk-QXRXIHY6.cjs.map} +1 -1
  21. package/dist/{chunk-266DUZ6D.js → chunk-SRM7HE2A.js} +6 -3
  22. package/dist/chunk-SRM7HE2A.js.map +1 -0
  23. package/dist/{chunk-TZ3PV5NT.js → chunk-TX2VP43R.js} +2 -2
  24. package/dist/{chunk-7MNBE7XA.cjs → chunk-UUARMJJE.cjs} +6 -3
  25. package/dist/chunk-UUARMJJE.cjs.map +1 -0
  26. package/dist/{chunk-HMHMK6U6.cjs → chunk-VYGS24RO.cjs} +16 -16
  27. package/dist/{chunk-HMHMK6U6.cjs.map → chunk-VYGS24RO.cjs.map} +1 -1
  28. package/dist/{chunk-J5BBBDNA.cjs → chunk-WB2JXKEI.cjs} +34 -34
  29. package/dist/{chunk-J5BBBDNA.cjs.map → chunk-WB2JXKEI.cjs.map} +1 -1
  30. package/dist/components/chat/index.cjs +4 -4
  31. package/dist/components/chat/index.js +3 -3
  32. package/dist/components/contact/contact-form.d.ts.map +1 -1
  33. package/dist/components/contact/index.cjs +5 -5
  34. package/dist/components/contact/index.js +4 -4
  35. package/dist/components/faq/faq-section.d.ts.map +1 -1
  36. package/dist/components/faq/index.cjs +3 -3
  37. package/dist/components/faq/index.js +2 -2
  38. package/dist/components/features/entity-video-section.d.ts.map +1 -1
  39. package/dist/components/features/index.cjs +4 -4
  40. package/dist/components/features/index.js +3 -3
  41. package/dist/components/features/video-bites-display.d.ts.map +1 -1
  42. package/dist/components/index.cjs +83 -81
  43. package/dist/components/index.cjs.map +1 -1
  44. package/dist/components/index.js +11 -9
  45. package/dist/components/index.js.map +1 -1
  46. package/dist/components/layout/page-heading.d.ts +8 -0
  47. package/dist/components/layout/page-heading.d.ts.map +1 -1
  48. package/dist/components/navigation/index.cjs +4 -4
  49. package/dist/components/navigation/index.js +3 -3
  50. package/dist/components/onboarding-guides/index.cjs +23 -23
  51. package/dist/components/onboarding-guides/index.js +3 -3
  52. package/dist/components/open-source-features.d.ts.map +1 -1
  53. package/dist/components/related-content/index.cjs +5 -5
  54. package/dist/components/related-content/index.js +4 -4
  55. package/dist/components/shared/onboarding/onboarding-walkthrough.d.ts.map +1 -1
  56. package/dist/components/tickets/index.cjs +65 -65
  57. package/dist/components/tickets/index.js +5 -5
  58. package/dist/components/ui/index.cjs +8 -6
  59. package/dist/components/ui/index.cjs.map +1 -1
  60. package/dist/components/ui/index.js +7 -5
  61. package/dist/components/why-it-matters.d.ts.map +1 -1
  62. package/dist/hooks/use-toast.d.ts +1 -1
  63. package/dist/index.cjs +8 -6
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.js +7 -5
  66. package/package.json +1 -1
  67. package/src/components/contact/contact-form.tsx +2 -1
  68. package/src/components/faq/faq-section.tsx +3 -2
  69. package/src/components/features/entity-video-section.tsx +2 -1
  70. package/src/components/features/video-bites-display.tsx +2 -1
  71. package/src/components/layout/page-heading.tsx +9 -0
  72. package/src/components/open-source-features.tsx +3 -2
  73. package/src/components/shared/onboarding/onboarding-walkthrough.tsx +2 -1
  74. package/src/components/why-it-matters.tsx +3 -2
  75. package/dist/chunk-266DUZ6D.js.map +0 -1
  76. package/dist/chunk-2P6V2EWZ.cjs.map +0 -1
  77. package/dist/chunk-7MNBE7XA.cjs.map +0 -1
  78. package/dist/chunk-D4TWJNO5.cjs.map +0 -1
  79. package/dist/chunk-ERFO4JFK.js.map +0 -1
  80. package/dist/chunk-EXU5N5RM.js.map +0 -1
  81. package/dist/chunk-GBUGYHDY.js.map +0 -1
  82. package/dist/chunk-J5R3QIOJ.cjs.map +0 -1
  83. package/dist/chunk-TT2VBR7P.cjs.map +0 -1
  84. package/dist/chunk-URJQGMNH.js.map +0 -1
  85. /package/dist/{chunk-Z5YEQWLC.js.map → chunk-FRU4LQJ4.js.map} +0 -0
  86. /package/dist/{chunk-6A2YQ5CY.js.map → chunk-KUYYJ2XM.js.map} +0 -0
  87. /package/dist/{chunk-TZ3PV5NT.js.map → chunk-TX2VP43R.js.map} +0 -0
@@ -64,6 +64,27 @@ function StatusBadge({
64
64
  );
65
65
  }
66
66
 
67
+ // src/components/layout/page-heading.tsx
68
+
69
+ var PAGE_HEADING_CLASS = "text-h1 text-ods-text-primary";
70
+ var SECTION_HEADING_CLASS = "text-h2 text-ods-text-primary";
71
+ var DESCRIPTION_CLASS = "mt-6 max-w-[640px] font-['DM_Sans'] text-[16px] md:text-[18px] leading-[24px] md:leading-[28px] text-ods-text-secondary";
72
+ function PageHeading({
73
+ children,
74
+ as: Tag = "h1",
75
+ description,
76
+ className,
77
+ descriptionClassName
78
+ }) {
79
+ const headingClass = className ? `${PAGE_HEADING_CLASS} ${className}` : PAGE_HEADING_CLASS;
80
+ const descClass = descriptionClassName ? `${DESCRIPTION_CLASS} ${descriptionClassName}` : DESCRIPTION_CLASS;
81
+ const hasDescription = description != null && description !== "" && typeof description !== "boolean";
82
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
83
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Tag, { className: headingClass, children }),
84
+ hasDescription && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: descClass, children: description })
85
+ ] });
86
+ }
87
+
67
88
  // src/components/features/entity-tag-badges.tsx
68
89
  var _link = require('next/link'); var _link2 = _interopRequireDefault(_link);
69
90
 
@@ -100,5 +121,8 @@ function EntityTagBadges({ tags, basePath, max, className }) {
100
121
 
101
122
 
102
123
 
103
- exports.statusBadgeVariants = statusBadgeVariants; exports.StatusBadge = StatusBadge; exports.TAG_BADGE_CLASS = TAG_BADGE_CLASS; exports.EntityTagBadges = EntityTagBadges;
104
- //# sourceMappingURL=chunk-TT2VBR7P.cjs.map
124
+
125
+
126
+
127
+ exports.statusBadgeVariants = statusBadgeVariants; exports.StatusBadge = StatusBadge; exports.PAGE_HEADING_CLASS = PAGE_HEADING_CLASS; exports.SECTION_HEADING_CLASS = SECTION_HEADING_CLASS; exports.PageHeading = PageHeading; exports.TAG_BADGE_CLASS = TAG_BADGE_CLASS; exports.EntityTagBadges = EntityTagBadges;
128
+ //# sourceMappingURL=chunk-JMU4RU5J.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-JMU4RU5J.cjs","../src/components/ui/status-badge.tsx","../src/components/layout/page-heading.tsx","../src/components/features/entity-tag-badges.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-JMU4RU5J.cjs","sourcesContent":[null,"\"use client\"\n\nimport React from 'react';\nimport { cn } from '../../utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nconst statusBadgeVariants = cva(\n \"inline-flex items-center justify-center rounded font-mono font-medium uppercase tracking-wide\",\n {\n variants: {\n variant: {\n card: \"px-3 py-1.5 text-sm\",\n button: \"px-2 py-0.5 text-[10px] leading-none\",\n },\n colorScheme: {\n cyan: \"bg-[var(--ods-flamingo-cyan-base)] text-ods-text-on-accent\",\n pink: \"bg-[var(--ods-flamingo-pink-base)] text-ods-text-on-accent\",\n yellow: \"bg-[var(--ods-flamingo-yellow-base)] text-ods-text-on-accent border border-[var(--ods-system-greys-black)]\",\n green: \"bg-[var(--ods-flamingo-green-base)] text-ods-text-on-accent\",\n purple: \"bg-[var(--ods-flamingo-purple-base)] text-ods-text-on-accent\",\n success: \"bg-[var(--ods-attention-green-success-secondary)] text-[var(--ods-attention-green-success)]\",\n error: \"bg-[var(--ods-attention-red-error-secondary)] text-[var(--ods-attention-red-error)]\",\n warning: \"bg-[var(--ods-attention-yellow-warning-secondary)] text-[var(--ods-attention-yellow-warning)]\",\n default: \"bg-ods-bg-secondary text-ods-text-primary\",\n // Border-only variants (no background) - for task type badges\n accentBorder: \"bg-transparent border-2 text-ods-accent border-ods-accent\",\n errorBorder: \"bg-transparent border-2 text-[var(--ods-attention-red-error)] border-[var(--ods-attention-red-error)]\",\n whiteBorder: \"bg-transparent border-2 text-ods-text-primary border-ods-text-primary\",\n },\n },\n defaultVariants: {\n variant: \"card\",\n colorScheme: \"default\",\n },\n }\n);\n\nexport interface StatusBadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof statusBadgeVariants> {\n text: string;\n /**\n * When true, renders `text` verbatim on a single line, bypassing the\n * default multi-word vertical-stack behavior used by `variant=\"button\"`.\n * Use this for compact inline contexts (e.g. chat-inline roadmap cards)\n * where the stamp-like stacked layout is undesirable.\n */\n singleLine?: boolean;\n}\n\nfunction StatusBadge({\n text,\n variant,\n colorScheme,\n className,\n singleLine,\n ...props\n}: StatusBadgeProps) {\n // Outer element is `<span>` so the badge is HTML-valid in any inline\n // context (e.g. inside a markdown `<p>` next to a compact chat card,\n // or inside an `<a>`). The `inline-flex` base class in\n // `statusBadgeVariants` keeps the layout identical to the previous\n // `<div>` outer — only the element name changed.\n //\n // Escape hatch: callers can pass `singleLine` to opt out of the\n // multi-word stacking applied for `variant=\"button\"`. This is needed\n // for compact inline contexts (chat-inline roadmap cards) where the\n // default stamp-like vertical stack (\"TO\" / \"DO\") breaks layout.\n const renderText = () => {\n if (singleLine) return text;\n if (variant === 'button' && text.includes(' ')) {\n const words = text.split(' ');\n return (\n <span className=\"flex flex-col items-center justify-center text-center gap-0\">\n {words.map((word, index) => (\n <span key={index} className=\"block\">{word}</span>\n ))}\n </span>\n );\n }\n return text;\n };\n\n return (\n <span\n className={cn(statusBadgeVariants({ variant, colorScheme }), className)}\n {...props}\n >\n {renderText()}\n </span>\n );\n}\n\nexport { StatusBadge, statusBadgeVariants };","import type { ReactNode } from 'react'\n\n/**\n * THE page-title style — the ODS `text-h1` token (`--font-size-h1-title` =\n * 40 / 48 / 56px, Azeret Mono semibold), which already owns the responsive\n * letter-spacing (-0.02em). Mirrors the global `h1 { @apply text-h1 }` base, so\n * every page heading is the SAME size across the lib and every consuming app.\n *\n * Single source of truth — do NOT hardcode a px size or re-assert the token's\n * tracking; render `<PageHeading>` (or, for the rare non-h1 caller, apply\n * `PAGE_HEADING_CLASS`).\n */\nexport const PAGE_HEADING_CLASS = 'text-h1 text-ods-text-primary'\n\n/**\n * THE section-heading style — the ODS `text-h2` sub-title token\n * (`--font-size-h2-sub-title` = 24 / 32 / 32px), one step below the page\n * title so a section `<h2>`/`<h3>` is always visually distinguishable from\n * the page's `<h1>`. Same single-source rule as PAGE_HEADING_CLASS: never\n * hardcode a px ramp or re-assert the token's tracking on a section heading.\n */\nexport const SECTION_HEADING_CLASS = 'text-h2 text-ods-text-primary'\n\nconst DESCRIPTION_CLASS =\n \"mt-6 max-w-[640px] font-['DM_Sans'] text-[16px] md:text-[18px] leading-[24px] md:leading-[28px] text-ods-text-secondary\"\n\nexport interface PageHeadingProps {\n /** Heading content — plain text or nodes (e.g. an accent <span>). */\n children: ReactNode\n /**\n * Heading level. Defaults to 'h1' (exactly one per page). Pass 'h2' where the\n * page already renders an <h1> above (e.g. a hero/featured item).\n */\n as?: 'h1' | 'h2'\n /** Optional supporting copy rendered as a <p> beneath the heading. */\n description?: ReactNode\n /** Extra classes merged onto the heading (margins, width, truncate, etc.). */\n className?: string\n /** Extra classes merged onto the description <p>. */\n descriptionClassName?: string\n}\n\n/**\n * Unified page heading. Renders the canonical page-title <h1> (or <h2>) plus an\n * optional description, so every page shares one consistent style instead of\n * duplicating the class string. Layout (PageContainer, margins, surrounding\n * sections) stays with the caller — this owns only the heading + description.\n */\nexport function PageHeading({\n children,\n as: Tag = 'h1',\n description,\n className,\n descriptionClassName,\n}: PageHeadingProps) {\n const headingClass = className ? `${PAGE_HEADING_CLASS} ${className}` : PAGE_HEADING_CLASS\n const descClass = descriptionClassName ? `${DESCRIPTION_CLASS} ${descriptionClassName}` : DESCRIPTION_CLASS\n // `description` is a ReactNode, so `description={cond && '...'}` can pass a\n // boolean `false` — exclude it (and empty string) so we never render an empty\n // <p> that adds phantom vertical gap beneath the heading.\n const hasDescription =\n description != null && description !== '' && typeof description !== 'boolean'\n return (\n <>\n <Tag className={headingClass}>{children}</Tag>\n {hasDescription && <p className={descClass}>{description}</p>}\n </>\n )\n}\n","import Link from 'next/link'\nimport { StatusBadge } from '../ui/status-badge'\nimport type { TagAssoc } from '../../types/blog'\n\ninterface EntityTagBadgesProps {\n /** Flat `<entity>_tags[]` association array (from entity_tags hydration). */\n tags?: TagAssoc[] | null\n /**\n * When set, each badge links to `${basePath}?tags=${slug}` (clickable, SPA nav\n * via next/link). Omit for a non-interactive display (badges with no slug also\n * render non-interactive even when basePath is set).\n */\n basePath?: string\n /** Cap visible badges; the remainder collapse into a \"+N\" badge. */\n max?: number\n /** Extra classes on the wrapper row. */\n className?: string\n}\n\n// The ONE tag-badge skin (OpenFrame design): rounded-rect, font-mono uppercase\n// StatusBadge on ods-card + ods-border. Clickable badges add the accent hover.\n// Exported so other tag-shaped chips (e.g. FaqAccordion's section badge)\n// render the IDENTICAL skin without re-declaring it.\nexport const TAG_BADGE_CLASS = 'bg-ods-card border border-ods-border'\nconst BADGE_CLASS = TAG_BADGE_CLASS\n\n/**\n * THE single tag-badge renderer for the whole product (hub + lib). Renders the\n * OpenFrame `StatusBadge` chip skin for a flat `<entity>_tags[]` array, optionally\n * clickable (links each tag to its filtered list via `basePath`). The hub's\n * `EntityTagList` delegates here so there is exactly one tag-display design.\n * Renders nothing when there are no tags.\n */\nexport function EntityTagBadges({ tags, basePath, max, className }: EntityTagBadgesProps) {\n const items = (tags || []).filter((t): t is TagAssoc => !!t && !!t.name)\n if (items.length === 0) return null\n\n // Only null/undefined means \"no cap\"; max={0} shows none, negatives clamp to 0.\n const visibleLimit = max == null ? items.length : Math.max(0, max)\n const shown = items.slice(0, visibleLimit)\n const overflow = items.length - shown.length\n\n return (\n <div className={`flex flex-wrap items-center gap-2 w-full ${className || ''}`}>\n {shown.map((tag) => {\n const key = tag.tag_id ?? tag.id ?? tag.slug ?? tag.name\n const label = (tag.name || '').toUpperCase()\n if (basePath && tag.slug) {\n return (\n <Link key={key} href={`${basePath}?tags=${tag.slug}`} className=\"inline-flex\">\n <StatusBadge\n text={label}\n variant=\"card\"\n className={`${BADGE_CLASS} cursor-pointer transition-colors hover:border-ods-accent`}\n />\n </Link>\n )\n }\n return <StatusBadge key={key} text={label} variant=\"card\" className={BADGE_CLASS} />\n })}\n {overflow > 0 && (\n <StatusBadge text={`+${overflow}`} variant=\"card\" className={`${BADGE_CLASS} text-ods-text-secondary`} />\n )}\n </div>\n )\n}\n"]}
@@ -8,9 +8,11 @@ import {
8
8
  serializeJsonLd
9
9
  } from "./chunk-NR535XIY.js";
10
10
  import {
11
+ PAGE_HEADING_CLASS,
12
+ SECTION_HEADING_CLASS,
11
13
  StatusBadge,
12
14
  TAG_BADGE_CLASS
13
- } from "./chunk-ERFO4JFK.js";
15
+ } from "./chunk-FREG2KXV.js";
14
16
  import {
15
17
  cn,
16
18
  init_cn
@@ -186,8 +188,8 @@ function FaqSection({
186
188
  // Embedded default is an <h2> — a host page already owns the <h1>,
187
189
  // and the schema/heading pair must read "Frequently Asked Questions"
188
190
  // (one FAQ heading per page, never tag/section names).
189
- /* @__PURE__ */ jsx2("h2", { className: "text-h2 tracking-[-0.04em] text-ods-text-primary", children: DEFAULT_HEADING_TEXT })
190
- ) : /* @__PURE__ */ jsx2("h1", { className: "text-h1 tracking-[-0.04em] text-ods-text-primary", children: DEFAULT_HEADING_TEXT }) : heading;
191
+ /* @__PURE__ */ jsx2("h2", { className: SECTION_HEADING_CLASS, children: DEFAULT_HEADING_TEXT })
192
+ ) : /* @__PURE__ */ jsx2("h1", { className: PAGE_HEADING_CLASS, children: DEFAULT_HEADING_TEXT }) : heading;
191
193
  if (error && isEmbedded) {
192
194
  return null;
193
195
  }
@@ -228,4 +230,4 @@ export {
228
230
  FaqAccordion,
229
231
  FaqSection
230
232
  };
231
- //# sourceMappingURL=chunk-GBUGYHDY.js.map
233
+ //# sourceMappingURL=chunk-K4K4X62E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts"],"sourcesContent":["\"use client\"\n\nimport React, { useMemo } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { PAGE_HEADING_CLASS, SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node. `undefined` → variant default (page: <h1>, embedded: <h2>\n * \"Frequently Asked Questions\"); `null` → no heading. React node so\n * platforms can drill their local <PageHeading> component without the lib\n * referencing platform state.\n */\n heading?: React.ReactNode | null\n /**\n * Shell variant — the FAQ LIST itself renders identically everywhere (ONE\n * flat accordion, `faq.section` as a per-row chip, never a heading):\n * - 'page' — the standalone /faqs surface: <main> shell + page gutters,\n * default <h1> heading.\n * - 'embedded' — a section inside a host page (entity detail rails, the\n * global nested-page block): bare <section>, default <h2> heading.\n * Back-compat default: `heading === null` → 'embedded', else 'page' (the\n * pre-variant discriminator).\n */\n variant?: 'page' | 'embedded'\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\n/** ONE list shape for every surface — standalone /faqs and embeds render the\n * IDENTICAL flat accordion (`faq.section` = per-row chip, never a heading),\n * so the two can't drift visually. */\nfunction toAccordionItems(faqs: Faq[]): FaqItem[] {\n return faqs.map((faq) => ({\n id: faq.id,\n question: faq.question,\n answer: faq.answer,\n badge: faq.section || undefined,\n }))\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-3xl border border-ods-border overflow-hidden bg-ods-card divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between px-6 md:px-8 py-6\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"h-10 w-10 rounded-md bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * Generic, embeddable FAQ display surface.\n *\n * - Standalone /faqs page: pass `initialFaqs` from the server + `heading` +\n * `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches and the public /api/faqs endpoint picks override-vs-fallback.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. Without it,\n * useSelfFetch reports `error:true` (logged once); in embedded mode (heading\n * === null) this component renders nothing so the host page isn't disfigured.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n variant,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n const isEmbedded = variant ? variant === 'embedded' : heading === null\n const headingNode =\n heading === undefined ? (\n isEmbedded ? (\n // Embedded default is an <h2> — a host page already owns the <h1>,\n // and the schema/heading pair must read \"Frequently Asked Questions\"\n // (one FAQ heading per page, never tag/section names).\n <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2>\n ) : (\n <h1 className={PAGE_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h1>\n )\n ) : (\n heading\n )\n\n // Embedded mode: degrade silently on error.\n if (error && isEmbedded) {\n return null\n }\n\n // Embedded mode, zero FAQs after load: render nothing — an embedded host\n // page (blog/case-study detail) must not show an empty section shell.\n // The standalone /faqs page keeps its existing behavior.\n if (!isLoading && !error && faqs.length === 0 && isEmbedded) {\n return null\n }\n\n if (isLoading && faqs.length === 0) {\n // Embedded mode renders skeletons WITHOUT the standalone page shell —\n // a host detail page already provides <main> + gutters; nesting them\n // here would emit invalid markup and double padding.\n if (isEmbedded) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n return (\n <main className={className ?? 'bg-ods-bg'}>\n <div className=\"max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto\">\n <FaqSkeleton />\n </div>\n </main>\n )\n }\n\n if (error && faqs.length === 0) {\n return (\n <main className={className ?? 'bg-ods-bg flex items-center justify-center py-20'}>\n <p className=\"text-ods-text-primary font-medium\">Failed to load FAQs. Please try again later.</p>\n </main>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n // ONE flat accordion on EVERY surface — `faq.section` becomes a per-row\n // chip, NOT a heading, so the page outline stays heading + h3(questions).\n // Order is the server's (post-specific first, then reused) — grouping by\n // section here would destroy it. The variants differ ONLY in shell:\n // standalone /faqs keeps its <main> + page gutters, embeds render bare.\n const accordion = <FaqAccordion items={toAccordionItems(faqs)} />\n const body = isEmbedded ? (\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n {accordion}\n </section>\n ) : (\n <main className={className ?? 'bg-ods-bg'}>\n <div className=\"max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10\">\n {headingNode}\n {accordion}\n </div>\n </main>\n )\n\n return (\n <>\n {body}\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { ChevronButton } from './ui/chevron-button'\nimport { StatusBadge } from './ui/status-badge'\nimport { TAG_BADGE_CLASS } from './features/entity-tag-badges'\nimport { cn } from \"../utils/cn\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n /** Optional category label (faq.section = tag name) rendered under the\n * question as the product's standard tag badge — the same StatusBadge\n * card skin EntityTagBadges uses everywhere (never a heading, so the\n * document outline stays h2/h3 only). */\n badge?: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center justify-between px-6 md:px-8 py-6 text-left focus:outline-none transition-colors cursor-pointer\"\n >\n <div className=\"flex min-w-0 flex-col items-start gap-2 pr-4\">\n <h3>\n {item.question}\n </h3>\n {item.badge && (\n <StatusBadge\n text={item.badge.toUpperCase()}\n variant=\"card\"\n className={TAG_BADGE_CLASS}\n />\n )}\n </div>\n <div className=\"flex-shrink-0\">\n <ChevronButton\n aria-label={isOpen ? 'Collapse question' : 'Expand question'}\n size=\"md\"\n isExpanded={isOpen}\n backgroundColor=\"transparent\"\n borderColor=\"#3A3A3A\"\n />\n </div>\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden group-hover:bg-[#1E1E1E]/30\"\n >\n <div ref={ref} className=\"px-6 md:px-8 pb-6 text-ods-text-primary text-h4\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -27,7 +27,7 @@ import {
27
27
  getContentRefLabelOrTitleCase,
28
28
  init_pagination,
29
29
  orderContentRefTypes
30
- } from "./chunk-EXU5N5RM.js";
30
+ } from "./chunk-DTJ32U7J.js";
31
31
  import {
32
32
  buildSuggestionUrl
33
33
  } from "./chunk-NR535XIY.js";
@@ -35,7 +35,7 @@ import {
35
35
  OnboardingGuideCard,
36
36
  OnboardingGuideCardSkeleton,
37
37
  decideNewTab
38
- } from "./chunk-URJQGMNH.js";
38
+ } from "./chunk-HTR3ARN6.js";
39
39
 
40
40
  // src/components/related-content/related-content-section.tsx
41
41
  import { useEffect, useMemo, useRef, useState } from "react";
@@ -334,4 +334,4 @@ export {
334
334
  GROUP_PAGE_SIZE,
335
335
  RelatedContentSection
336
336
  };
337
- //# sourceMappingURL=chunk-6A2YQ5CY.js.map
337
+ //# sourceMappingURL=chunk-KUYYJ2XM.js.map
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
 
14
- var _chunk2P6V2EWZcjs = require('./chunk-2P6V2EWZ.cjs');
14
+ var _chunkQAQ7AROScjs = require('./chunk-QAQ7AROS.cjs');
15
15
 
16
16
 
17
17
 
@@ -21,6 +21,9 @@ var _chunkQLWBBCI5cjs = require('./chunk-QLWBBCI5.cjs');
21
21
  var _chunk6JINAOI7cjs = require('./chunk-6JINAOI7.cjs');
22
22
 
23
23
 
24
+ var _chunkJMU4RU5Jcjs = require('./chunk-JMU4RU5J.cjs');
25
+
26
+
24
27
  var _chunkN5UOJC3Vcjs = require('./chunk-N5UOJC3V.cjs');
25
28
 
26
29
  // src/components/contact/contact-form.tsx
@@ -110,7 +113,7 @@ function ContactForm({
110
113
  successRedirectUrl = "/blog#community",
111
114
  successToastMessage = "Redirecting you to join our community..."
112
115
  } = {}) {
113
- const attachments = _chunk2P6V2EWZcjs.useChatAttachments.call(void 0, );
116
+ const attachments = _chunkQAQ7AROScjs.useChatAttachments.call(void 0, );
114
117
  const builtInSubmission = _chunkQLWBBCI5cjs.useContactSubmission.call(void 0, {
115
118
  userId,
116
119
  successRedirectUrl,
@@ -172,7 +175,7 @@ function ContactForm({
172
175
  className: `h-full flex flex-col ${!noBorder ? "border border-ods-border rounded-2xl md:rounded-3xl" : ""} ${!noPadding ? "p-6 md:p-8 lg:p-10" : ""}`,
173
176
  children: [
174
177
  (title || subtitle) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "mb-6 md:mb-8", children: [
175
- title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "text-h2 tracking-[-0.04em] text-ods-text-primary mb-3 md:mb-4", children: title }),
178
+ title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: `${_chunkJMU4RU5Jcjs.SECTION_HEADING_CLASS} mb-3 md:mb-4`, children: title }),
176
179
  subtitle && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "font-['DM_Sans'] font-medium text-[16px] md:text-[18px] leading-[24px] text-ods-text-primary", children: subtitle })
177
180
  ] }),
178
181
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -192,11 +195,11 @@ function ContactForm({
192
195
  !showEmail && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("email") }),
193
196
  !showHelpCategory && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("helpCategory") }),
194
197
  !showMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("message") }),
195
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.HoneypotField, { ...honeypotInputProps }),
198
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.HoneypotField, { ...honeypotInputProps }),
196
199
  extraTopField,
197
200
  showNameEmailRow && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6", children: [
198
201
  showName && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
199
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "name", children: [
202
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "name", children: [
200
203
  "Your Name",
201
204
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-accent", children: "*" })
202
205
  ] }),
@@ -215,7 +218,7 @@ function ContactForm({
215
218
  errors.name && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { id: "name-error", className: "text-ods-error text-xs font-['DM_Sans'] mt-1", children: errors.name.message })
216
219
  ] }),
217
220
  showEmail && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
218
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "email", children: [
221
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "email", children: [
219
222
  "Email",
220
223
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-accent", children: "*" })
221
224
  ] }),
@@ -236,46 +239,46 @@ function ContactForm({
236
239
  ] }),
237
240
  (showCompanySize || showReferralSource) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6", children: [
238
241
  showCompanySize && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
239
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "companySize", children: "Company Size" }),
242
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "companySize", children: "Company Size" }),
240
243
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
241
244
  _reacthookform.Controller,
242
245
  {
243
246
  control,
244
247
  name: "companySize",
245
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
248
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
246
249
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
247
- _chunk2P6V2EWZcjs.SelectTrigger,
250
+ _chunkQAQ7AROScjs.SelectTrigger,
248
251
  {
249
252
  id: "companySize",
250
253
  "aria-label": "Company Size",
251
254
  className: "bg-ods-card border-ods-border text-ods-text-primary h-12 px-3",
252
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectValue, { placeholder: "Select company size" })
255
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectValue, { placeholder: "Select company size" })
253
256
  }
254
257
  ),
255
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectContent, { children: companySizeOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectItem, { value: opt, children: opt }, opt)) })
258
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectContent, { children: companySizeOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectItem, { value: opt, children: opt }, opt)) })
256
259
  ] })
257
260
  }
258
261
  ),
259
262
  errors.companySize && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { id: "companySize-error", className: "text-ods-error text-xs font-['DM_Sans'] mt-1", children: errors.companySize.message })
260
263
  ] }),
261
264
  showReferralSource && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
262
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "referralSource", children: "How did you hear about us?" }),
265
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "referralSource", children: "How did you hear about us?" }),
263
266
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
264
267
  _reacthookform.Controller,
265
268
  {
266
269
  control,
267
270
  name: "referralSource",
268
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
271
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
269
272
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
270
- _chunk2P6V2EWZcjs.SelectTrigger,
273
+ _chunkQAQ7AROScjs.SelectTrigger,
271
274
  {
272
275
  id: "referralSource",
273
276
  "aria-label": "Referral Source",
274
277
  className: "bg-ods-card border-ods-border text-ods-text-primary h-12 px-3",
275
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectValue, { placeholder: "Select an option" })
278
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectValue, { placeholder: "Select an option" })
276
279
  }
277
280
  ),
278
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectContent, { children: referralSourceOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectItem, { value: opt, children: opt }, opt)) })
281
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectContent, { children: referralSourceOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectItem, { value: opt, children: opt }, opt)) })
279
282
  ] })
280
283
  }
281
284
  ),
@@ -283,7 +286,7 @@ function ContactForm({
283
286
  ] })
284
287
  ] }),
285
288
  showHelpCategory && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
286
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "helpCategory", children: [
289
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "helpCategory", children: [
287
290
  "Choose your main interest",
288
291
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-accent", children: "*" })
289
292
  ] }),
@@ -292,29 +295,29 @@ function ContactForm({
292
295
  {
293
296
  control,
294
297
  name: "helpCategory",
295
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
298
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Select, { onValueChange: field.onChange, defaultValue: field.value, children: [
296
299
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
297
- _chunk2P6V2EWZcjs.SelectTrigger,
300
+ _chunkQAQ7AROScjs.SelectTrigger,
298
301
  {
299
302
  id: "helpCategory",
300
303
  "aria-label": "Help Category",
301
304
  className: "bg-ods-card border-ods-border text-ods-text-primary h-12 px-3",
302
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectValue, { placeholder: "Choose your main interest" })
305
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectValue, { placeholder: "Choose your main interest" })
303
306
  }
304
307
  ),
305
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectContent, { children: helpCategoryOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2P6V2EWZcjs.SelectItem, { value: opt, children: opt }, opt)) })
308
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectContent, { children: helpCategoryOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.SelectItem, { value: opt, children: opt }, opt)) })
306
309
  ] })
307
310
  }
308
311
  ),
309
312
  errors.helpCategory && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { id: "helpCategory-error", className: "text-ods-error text-xs font-['DM_Sans'] mt-1", children: errors.helpCategory.message })
310
313
  ] }),
311
314
  showMessage && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col flex-grow", children: [
312
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2P6V2EWZcjs.Label, { htmlFor: "message", children: [
315
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "message", children: [
313
316
  "Your Message",
314
317
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-accent", children: "*" })
315
318
  ] }),
316
319
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
317
- _chunk2P6V2EWZcjs.Textarea,
320
+ _chunkQAQ7AROScjs.Textarea,
318
321
  {
319
322
  id: "message",
320
323
  ...register("message"),
@@ -328,7 +331,7 @@ function ContactForm({
328
331
  ] }),
329
332
  attachmentsEnabled && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-2", children: [
330
333
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
331
- _chunk2P6V2EWZcjs.ChatAttachmentChipStrip,
334
+ _chunkQAQ7AROScjs.ChatAttachmentChipStrip,
332
335
  {
333
336
  attachments: attachments.attachments,
334
337
  onRemove: attachments.removeAttachment,
@@ -337,7 +340,7 @@ function ContactForm({
337
340
  ),
338
341
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
339
342
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
340
- _chunk2P6V2EWZcjs.ChatAttachmentAddButton,
343
+ _chunkQAQ7AROScjs.ChatAttachmentAddButton,
341
344
  {
342
345
  attachmentsEnabled: true,
343
346
  attachmentsCount: attachments.attachments.length,
@@ -373,4 +376,4 @@ function ContactForm({
373
376
 
374
377
 
375
378
  exports.ContactForm = ContactForm;
376
- //# sourceMappingURL=chunk-D4TWJNO5.cjs.map
379
+ //# sourceMappingURL=chunk-Q52JZP3P.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-Q52JZP3P.cjs","../src/components/contact/contact-form.tsx","../src/schemas/contact-schema.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-Q52JZP3P.cjs","sourcesContent":[null,"'use client'\n\n/**\n * `<ContactForm />` — the canonical contact form used by every public\n * surface (TMCG join, case-study pitch, generic /contact, Help Center\n * ticket creation, etc.).\n *\n * Self-contained inside the lib — host-specific values (user id for\n * tracking, platform-specific contact reasons, reddit-click attribution\n * id) flow IN via props. The hub passes them via a thin\n * `<ContactForm>` wrapper that resolves them from `useAuth` /\n * `getAppConfig` / `getStoredRedditClickId`. Other embedders pass\n * whatever they have (or omit).\n *\n * Field-hide + custom-submit + extra-top-field knobs let one form\n * serve both contact and ticket-creation flows without forking:\n * - Contact page: rendered with all fields visible, built-in submit\n * flow to `/api/contact` via `useContactSubmission`.\n * - Ticket page: hides name/email/companySize/referralSource/\n * helpCategory; supplies `extraTopField` (a Subject input) +\n * `onCustomSubmit` wired to `useTicketActions.submitTicket`.\n */\n\nimport { useState, type ReactNode } from 'react'\nimport { useForm, Controller } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport {\n ContactSchema,\n type ContactFormData,\n companySizeOptions,\n referralSourceOptions,\n defaultHelpCategoryOptions,\n} from '../../schemas/contact-schema'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\nimport {\n Button,\n type ButtonProps,\n Input,\n Textarea,\n Select,\n SelectTrigger,\n SelectValue,\n SelectContent,\n SelectItem,\n Label,\n} from '../ui'\nimport { useContactSubmission } from '../../hooks/use-contact-submission'\nimport { useHumanitySignals } from '../../hooks/use-humanity-signals'\nimport { HoneypotField } from '../ui/honeypot-field'\nimport {\n ChatAttachmentAddButton,\n ChatAttachmentChipStrip,\n} from '../chat/chat-attachment-bar'\nimport { useChatAttachments } from '../chat/hooks/use-chat-attachments'\nimport type { ChatAttachment } from '../chat/utils/chat-attachment-markdown'\n\n/**\n * Fields the caller can suppress. Six values — every primary form\n * field plus `name` and `email` (newly hideable so ticket-creation\n * surfaces can hide them; they still need to validate, so the caller\n * MUST supply pre-filled values via `defaultValues` when hiding them).\n */\nexport type ContactFormHideableField =\n | 'name'\n | 'email'\n | 'companySize'\n | 'referralSource'\n | 'helpCategory'\n | 'message'\n\nexport interface ContactFormProps {\n /** Host-side user id passed to `useContactSubmission` for attribution.\n * Hub wrapper passes `useAuth().user?.id`; lib's Help Center surface\n * passes `useChatIdentity().user?.id`. Omit for anon flows. */\n userId?: string\n /** Platform-specific help-category dropdown options. Hub wrapper\n * passes `getAppConfig().contact.contactReasons`. Defaults to the\n * lib's `defaultHelpCategoryOptions`. */\n helpCategoryOptions?: readonly string[]\n /** Reddit click attribution id. Caller resolves from wherever they\n * stash it (hub: sessionStorage via `getStoredRedditClickId`). When\n * set, it's spread into the submission payload. */\n rdtCid?: string\n /** Called after a successful submit so the caller can clear their\n * attribution storage (hub wrapper calls `clearStoredRedditClickId`).\n * Fires for BOTH the built-in and custom submit paths. */\n onSubmitSuccess?: () => void\n\n prefilledReason?: string\n prefilledMessage?: string\n hideFields?: ContactFormHideableField[]\n /** Authoritative pre-fill for any field the caller hides. Merged\n * into react-hook-form's `defaultValues` AFTER the legacy\n * `prefilledReason` / `prefilledMessage` props (caller-supplied\n * wins). REQUIRED when hiding `name` / `email` / `helpCategory` —\n * those fields are still validated by Zod even when not rendered. */\n defaultValues?: Partial<ContactFormData>\n /** Optional custom submit handler. When provided, the form bypasses\n * the built-in `useContactSubmission` flow (no /api/contact call,\n * no success-redirect, no built-in toast) — the caller owns the\n * entire side-effect chain. Reset + `onSubmitSuccess` still fire\n * on a successful await.\n *\n * Receives the schema-validated form payload PLUS the ready\n * attachments array (empty when `attachmentsEnabled === false` or\n * the user hasn't picked any). Caller forwards `attachments` to\n * whichever sink owns the upload (e.g. `actions.submitTicket`'s\n * `attachments` field for HubSpot Note engagements). */\n onCustomSubmit?: (data: ContactFormData, attachments: ChatAttachment[]) => Promise<void>\n /** Turn on the attachments bar (file `+` button + chip strip) using\n * the same lib primitives the chat composer uses\n * (`<ChatAttachmentAddButton>` + `<ChatAttachmentChipStrip>` +\n * `useChatAttachments`). When `false` (the default), the form\n * doesn't render the bar AND the attachments array passed to\n * `onCustomSubmit` is always empty. */\n attachmentsEnabled?: boolean\n /** Render slot for an EXTRA field at the very top of the form,\n * ABOVE the name/email row. Use this for ticket surfaces that need\n * a Subject input — the field is NOT part of `ContactSchema`, so\n * the caller manages its own state + validation and reads the\n * value back inside `onCustomSubmit`. */\n extraTopField?: ReactNode\n\n title?: string\n subtitle?: string\n footerText?: string\n noBorder?: boolean\n noPadding?: boolean\n buttonVariant?: ButtonProps['variant']\n buttonClassName?: string\n /** Submit-button label. Defaults to \"Send Message\". Override for\n * ticket surfaces (e.g. \"Open ticket\"). */\n submitLabel?: string\n /** Success-state submit-button label (shown briefly after submit on\n * the built-in flow). Defaults to \"Message Sent!\". Has no effect\n * when `onCustomSubmit` is provided — the caller owns success UX. */\n submitSuccessLabel?: string\n successRedirectUrl?: string\n successToastMessage?: string\n}\n\nexport function ContactForm({\n userId,\n helpCategoryOptions = defaultHelpCategoryOptions,\n rdtCid,\n onSubmitSuccess,\n prefilledReason,\n prefilledMessage,\n hideFields = [],\n defaultValues: defaultValuesProp,\n onCustomSubmit,\n extraTopField,\n attachmentsEnabled = false,\n title = 'Hit Us Up',\n subtitle,\n footerText = 'We typically respond within 24 hours. We respect your privacy – no spam, ever.',\n noBorder = false,\n noPadding = false,\n buttonVariant = 'accent',\n buttonClassName = '',\n submitLabel = 'Send Message',\n submitSuccessLabel = 'Message Sent!',\n successRedirectUrl = '/blog#community',\n successToastMessage = 'Redirecting you to join our community...',\n}: ContactFormProps = {}) {\n // Attachments staging — same hook the chat composer + ticket\n // detail-drawer composer use. Files upload to Supabase as soon as\n // the user picks them; `readyAttachments` is the wire-shape array\n // ready for the next submit. `hasInflightUploads` disables Send\n // until every upload settles.\n const attachments = useChatAttachments()\n // Built-in contact-API flow. Hook is called unconditionally (rules\n // of hooks); we just don't dispatch its `submit` when the caller\n // passes `onCustomSubmit`. The hook owns its own toast + redirect\n // chain so bypassing it cleanly hands all side-effects to the caller.\n const builtInSubmission = useContactSubmission({\n userId,\n successRedirectUrl,\n successToastMessage,\n })\n // Independent in-flight tracker for the custom path — we can't reuse\n // `builtInSubmission.isSubmitting` because that hook never sees a\n // request when `onCustomSubmit` is active.\n const [customSubmitting, setCustomSubmitting] = useState(false)\n\n // Invisible bot-protection signals (honeypot + timing). Spread into the\n // submit payload for BOTH the built-in and custom paths; reset on success.\n const { honeypotInputProps, getSignals, resetSignals } = useHumanitySignals()\n\n const isSubmitting = onCustomSubmit ? customSubmitting : builtInSubmission.isSubmitting\n // `isSuccess` only ever fires on the built-in path; custom callers\n // own their own UX (no \"Message Sent!\" button-label flicker).\n const isSuccess = onCustomSubmit ? false : builtInSubmission.isSuccess\n\n const {\n register,\n handleSubmit,\n control,\n formState: { errors },\n reset,\n } = useForm<ContactFormData>({\n resolver: zodResolver(ContactSchema),\n defaultValues: {\n ...(prefilledReason && { helpCategory: prefilledReason }),\n ...(prefilledMessage && { message: prefilledMessage }),\n // Caller-supplied defaults win over the legacy `prefilled*` props\n // (they're the authoritative seed for hidden fields).\n ...defaultValuesProp,\n },\n })\n\n const handleFormSubmit = async (data: ContactFormData) => {\n if (isSubmitting) return\n if (attachmentsEnabled && attachments.hasInflightUploads) return\n try {\n const payload = { ...data, ...(rdtCid && { rdt_cid: rdtCid }), ...getSignals() }\n const readyAttachments = attachmentsEnabled ? attachments.readyAttachments : []\n if (onCustomSubmit) {\n setCustomSubmitting(true)\n try {\n await onCustomSubmit(payload, readyAttachments)\n } finally {\n setCustomSubmitting(false)\n }\n } else {\n await builtInSubmission.submit(payload)\n }\n onSubmitSuccess?.()\n reset()\n resetSignals()\n if (attachmentsEnabled) attachments.clear()\n } catch {\n // Error toast is owned by the active flow:\n // - built-in: `useContactSubmission` toasts inside `submit()`.\n // - custom: the caller toasts inside `onCustomSubmit`.\n // Either way we swallow here so a thrown error doesn't crash the\n // form tree (react-hook-form's onSubmit handler rejects upward).\n }\n }\n\n const showName = !hideFields.includes('name')\n const showEmail = !hideFields.includes('email')\n const showNameEmailRow = showName || showEmail\n const showCompanySize = !hideFields.includes('companySize')\n const showReferralSource = !hideFields.includes('referralSource')\n const showHelpCategory = !hideFields.includes('helpCategory')\n const showMessage = !hideFields.includes('message')\n\n return (\n <div\n className={`h-full flex flex-col ${!noBorder ? 'border border-ods-border rounded-2xl md:rounded-3xl' : ''} ${!noPadding ? 'p-6 md:p-8 lg:p-10' : ''}`}\n >\n {(title || subtitle) && (\n <div className=\"mb-6 md:mb-8\">\n {title && (\n <h2 className={`${SECTION_HEADING_CLASS} mb-3 md:mb-4`}>\n {title}\n </h2>\n )}\n {subtitle && (\n <p className=\"font-['DM_Sans'] font-medium text-[16px] md:text-[18px] leading-[24px] text-ods-text-primary\">\n {subtitle}\n </p>\n )}\n </div>\n )}\n\n <form\n onSubmit={handleSubmit(handleFormSubmit, (validationErrors) => {\n // When validation fails on a HIDDEN field (e.g. ticket form\n // hides name/email/helpCategory and seeds them via\n // `defaultValues`), there's no visible error UI for the user\n // — the submit button just appears dead. Log so the broken\n // defaultValues wiring is at least discoverable in DevTools.\n // eslint-disable-next-line no-console\n console.warn(\n '[ContactForm] submit blocked by validation:',\n Object.fromEntries(\n Object.entries(validationErrors).map(([k, v]) => [k, v?.message ?? v]),\n ),\n )\n })}\n className=\"flex flex-col flex-grow space-y-4 md:space-y-6\"\n >\n {/* Hidden inputs for fields that are required by `ContactSchema`\n but suppressed from the visible UI via `hideFields`. Without\n these, `register('name')` never runs, react-hook-form skips\n the field at submit time, and Zod's required-string check\n fails silently — the user clicks Submit and NOTHING visible\n happens (no error, no network call). The caller-supplied\n `defaultValues` seed the values; the hidden inputs just tell\n RHF to include them in the submit payload. */}\n {!showName && <input type=\"hidden\" {...register('name')} />}\n {!showEmail && <input type=\"hidden\" {...register('email')} />}\n {!showHelpCategory && <input type=\"hidden\" {...register('helpCategory')} />}\n {!showMessage && <input type=\"hidden\" {...register('message')} />}\n\n {/* Invisible honeypot — real users never fill it; bots that fill every field trip it. */}\n <HoneypotField {...honeypotInputProps} />\n\n {/* Extra top field (e.g. Subject for ticket forms). Rendered\n outside the schema-driven layout so the caller fully owns\n label / placeholder / state. */}\n {extraTopField}\n\n {showNameEmailRow && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6\">\n {showName && (\n <div className=\"flex flex-col\">\n <Label htmlFor=\"name\">\n Your Name<span className=\"text-ods-accent\">*</span>\n </Label>\n <Input\n id=\"name\"\n type=\"text\"\n {...register('name')}\n placeholder=\"Jane Doe\"\n aria-invalid={!!errors.name}\n aria-describedby=\"name-error\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12\"\n />\n {errors.name && (\n <span id=\"name-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.name.message}\n </span>\n )}\n </div>\n )}\n {showEmail && (\n <div className=\"flex flex-col\">\n <Label htmlFor=\"email\">\n Email<span className=\"text-ods-accent\">*</span>\n </Label>\n <Input\n id=\"email\"\n type=\"email\"\n {...register('email')}\n placeholder=\"jane@company.com\"\n aria-invalid={!!errors.email}\n aria-describedby=\"email-error\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12\"\n />\n {errors.email && (\n <span id=\"email-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.email.message}\n </span>\n )}\n </div>\n )}\n </div>\n )}\n\n {(showCompanySize || showReferralSource) && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6\">\n {showCompanySize && (\n <div className=\"flex flex-col\">\n <Label htmlFor=\"companySize\">Company Size</Label>\n <Controller\n control={control}\n name=\"companySize\"\n render={({ field }) => (\n <Select onValueChange={field.onChange} defaultValue={field.value}>\n <SelectTrigger\n id=\"companySize\"\n aria-label=\"Company Size\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary h-12 px-3\"\n >\n <SelectValue placeholder=\"Select company size\" />\n </SelectTrigger>\n <SelectContent>\n {companySizeOptions.map((opt) => (\n <SelectItem key={opt} value={opt}>\n {opt}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n />\n {errors.companySize && (\n <span id=\"companySize-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.companySize.message}\n </span>\n )}\n </div>\n )}\n {showReferralSource && (\n <div className=\"flex flex-col\">\n <Label htmlFor=\"referralSource\">How did you hear about us?</Label>\n <Controller\n control={control}\n name=\"referralSource\"\n render={({ field }) => (\n <Select onValueChange={field.onChange} defaultValue={field.value}>\n <SelectTrigger\n id=\"referralSource\"\n aria-label=\"Referral Source\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary h-12 px-3\"\n >\n <SelectValue placeholder=\"Select an option\" />\n </SelectTrigger>\n <SelectContent>\n {referralSourceOptions.map((opt) => (\n <SelectItem key={opt} value={opt}>\n {opt}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n />\n {errors.referralSource && (\n <span id=\"referralSource-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.referralSource.message}\n </span>\n )}\n </div>\n )}\n </div>\n )}\n\n {showHelpCategory && (\n <div className=\"flex flex-col\">\n <Label htmlFor=\"helpCategory\">\n Choose your main interest<span className=\"text-ods-accent\">*</span>\n </Label>\n <Controller\n control={control}\n name=\"helpCategory\"\n render={({ field }) => (\n <Select onValueChange={field.onChange} defaultValue={field.value}>\n <SelectTrigger\n id=\"helpCategory\"\n aria-label=\"Help Category\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary h-12 px-3\"\n >\n <SelectValue placeholder=\"Choose your main interest\" />\n </SelectTrigger>\n <SelectContent>\n {helpCategoryOptions.map((opt) => (\n <SelectItem key={opt} value={opt}>\n {opt}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n />\n {errors.helpCategory && (\n <span id=\"helpCategory-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.helpCategory.message}\n </span>\n )}\n </div>\n )}\n\n {showMessage && (\n <div className=\"flex flex-col flex-grow\">\n <Label htmlFor=\"message\">\n Your Message<span className=\"text-ods-accent\">*</span>\n </Label>\n <Textarea\n id=\"message\"\n {...register('message')}\n placeholder=\"Share your current challenges or questions about open-source alternatives...\"\n aria-invalid={!!errors.message}\n aria-describedby=\"message-error\"\n className=\"bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary h-full flex-grow\"\n />\n {errors.message && (\n <span id=\"message-error\" className=\"text-ods-error text-xs font-['DM_Sans'] mt-1\">\n {errors.message.message}\n </span>\n )}\n </div>\n )}\n\n {/* Attachments — only renders when `attachmentsEnabled` is on.\n Uses the SAME chip strip + add button + staging hook the\n chat composer and ticket-drawer composer use, so the visual\n chip styling + upload-progress UX are identical everywhere\n attachments appear. */}\n {attachmentsEnabled && (\n <div className=\"flex flex-col gap-2\">\n <ChatAttachmentChipStrip\n attachments={attachments.attachments}\n onRemove={attachments.removeAttachment}\n disabled={isSubmitting}\n />\n <div className=\"flex items-center gap-2\">\n <ChatAttachmentAddButton\n attachmentsEnabled\n attachmentsCount={attachments.attachments.length}\n onAddFiles={attachments.addFiles}\n disabled={isSubmitting}\n />\n <span className=\"text-xs text-ods-text-secondary\">\n Attach files (optional)\n </span>\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col md:flex-row gap-4 md:gap-6 items-center justify-end w-full pt-2 mt-auto\">\n {footerText && (\n <p className=\"font-['DM_Sans'] text-ods-text-secondary text-xs md:text-sm leading-relaxed text-center md:text-left\">\n {footerText}\n </p>\n )}\n <Button\n type=\"submit\"\n loading={isSubmitting}\n disabled={\n isSubmitting ||\n isSuccess ||\n (attachmentsEnabled && attachments.hasInflightUploads)\n }\n variant={buttonVariant}\n className={`w-full md:w-auto ${buttonClassName}`}\n >\n {isSuccess ? submitSuccessLabel : submitLabel}\n </Button>\n </div>\n </form>\n </div>\n )\n}\n","import { z } from 'zod';\n\n// Dropdown option constants — re-exported by `<ContactForm>` consumers\n// that want to surface their own custom Select widgets keyed on the\n// same allowed-value set.\nexport const companySizeOptions = [\n '1-10',\n '11-50',\n '51-200',\n '201-500',\n '501-1000',\n '1001+',\n] as const;\n\nexport const referralSourceOptions = [\n 'Google',\n 'LinkedIn',\n 'Twitter/X',\n 'Reddit',\n 'Friend / Colleague',\n 'Other',\n] as const;\n\n// Default fallback options — used when the embedder doesn't supply\n// platform-specific help-category options via the `helpCategoryOptions`\n// prop on `<ContactForm>`.\nexport const defaultHelpCategoryOptions = [\n 'Open-Source Alternatives',\n 'Vendor Cost Reduction',\n 'MSP Best Practices',\n 'Partnerships',\n 'Press',\n 'Other',\n] as const;\n\n// Reusable LinkedIn URL validator — the single source of truth. Every\n// public form schema, every admin update schema, every HubSpot push\n// validator MUST reference this so validation rules cannot drift\n// across boundaries.\n//\n// Host validation parses the URL and checks the hostname suffix so an\n// adversarial input like `https://evil.com/linkedin.com/x` is rejected\n// (substring match would have accepted it — CodeQL alert\n// \"Incomplete URL substring sanitization\").\nexport const LinkedInUrlSchema = z\n .string()\n .url({ message: 'Please enter a valid LinkedIn URL' })\n .refine(\n (url) => {\n try {\n const host = new URL(url).hostname.toLowerCase()\n return host === 'linkedin.com' || host.endsWith('.linkedin.com')\n } catch {\n return false\n }\n },\n {\n message: 'Please enter a valid LinkedIn profile URL',\n },\n )\n .optional()\n .or(z.literal(''));\n\n/**\n * Base schema — fields shared by every contact-style form (main contact\n * form, TMCG join, data-room request, case-study pitch, etc.). Any\n * field that exists on a form but NOT on this schema is silently\n * stripped by `safeParse` — that's exactly the bug the LinkedIn field\n * hit historically.\n */\nexport const ContactBaseSchema = z.object({\n name: z\n .string()\n .min(2, { message: 'Name must be at least 2 characters' })\n .max(255, { message: 'Name is too long' }),\n email: z\n .string()\n .email({ message: 'Please enter a valid email address' })\n .max(255),\n linkedin_url: LinkedInUrlSchema,\n helpCategory: z\n .string()\n .min(1, { message: 'Please select what we can help you with' })\n .max(255, { message: 'Help category is too long' }),\n message: z\n .string()\n .min(10, { message: 'Message must be at least 10 characters' })\n .max(5000, { message: 'Message is too long (5,000 character limit)' }),\n rdt_cid: z.string().optional(),\n});\n\n// Public POST /api/contact validator — base + dropdown fields used by\n// the generic contact form. Other form-specific schemas extend\n// `ContactBaseSchema`.\nexport const ContactSchema = ContactBaseSchema.extend({\n companySize: z\n .string()\n .optional()\n .refine((val) => !val || companySizeOptions.includes(val as (typeof companySizeOptions)[number]), {\n message: 'Please select a valid company size',\n }),\n referralSource: z\n .string()\n .optional()\n .refine((val) => !val || referralSourceOptions.includes(val as (typeof referralSourceOptions)[number]), {\n message: 'Please select a valid referral source',\n }),\n});\n\nexport type ContactFormData = z.infer<typeof ContactSchema>;\n\nexport interface ContactApiResponse {\n success: boolean;\n error?: string;\n}\n"]}