{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-ZNYJD27G.cjs","../src/components/contact/contact-form.tsx","../src/schemas/contact-schema.ts"],"names":[],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACHA,8BAAyC;AACzC,gDAAoC;AACpC,8CAA4B;ADK5B;AACA;AE/BA,2BAAkB;AAKX,IAAM,mBAAA,EAAqB;AAAA,EAChC,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA;AAEO,IAAM,sBAAA,EAAwB;AAAA,EACnC,QAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,oBAAA;AAAA,EACA;AACF,CAAA;AAKO,IAAM,2BAAA,EAA6B;AAAA,EACxC,0BAAA;AAAA,EACA,uBAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA;AAWO,IAAM,kBAAA,EAAoB,OAAA,CAC9B,MAAA,CAAO,CAAA,CACP,GAAA,CAAI,EAAE,OAAA,EAAS,oCAAoC,CAAC,CAAA,CACpD,MAAA;AAAA,EACC,CAAC,GAAA,EAAA,GAAQ;AACP,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,EAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA,CAAS,WAAA,CAAY,CAAA;AAC/C,MAAA,OAAO,KAAA,IAAS,eAAA,GAAkB,IAAA,CAAK,QAAA,CAAS,eAAe,CAAA;AAAA,IACjE,EAAA,UAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF,CAAA;AAAA,EACA;AAAA,IACE,OAAA,EAAS;AAAA,EACX;AACF,CAAA,CACC,QAAA,CAAS,CAAA,CACT,EAAA,CAAG,OAAA,CAAE,OAAA,CAAQ,EAAE,CAAC,CAAA;AASZ,IAAM,kBAAA,EAAoB,OAAA,CAAE,MAAA,CAAO;AAAA,EACxC,IAAA,EAAM,OAAA,CACH,MAAA,CAAO,CAAA,CACP,GAAA,CAAI,CAAA,EAAG,EAAE,OAAA,EAAS,qCAAqC,CAAC,CAAA,CACxD,GAAA,CAAI,GAAA,EAAK,EAAE,OAAA,EAAS,mBAAmB,CAAC,CAAA;AAAA,EAC3C,KAAA,EAAO,OAAA,CACJ,MAAA,CAAO,CAAA,CACP,KAAA,CAAM,EAAE,OAAA,EAAS,qCAAqC,CAAC,CAAA,CACvD,GAAA,CAAI,GAAG,CAAA;AAAA,EACV,YAAA,EAAc,iBAAA;AAAA,EACd,YAAA,EAAc,OAAA,CACX,MAAA,CAAO,CAAA,CACP,GAAA,CAAI,CAAA,EAAG,EAAE,OAAA,EAAS,0CAA0C,CAAC,CAAA,CAC7D,GAAA,CAAI,GAAA,EAAK,EAAE,OAAA,EAAS,4BAA4B,CAAC,CAAA;AAAA,EACpD,OAAA,EAAS,OAAA,CACN,MAAA,CAAO,CAAA,CACP,GAAA,CAAI,EAAA,EAAI,EAAE,OAAA,EAAS,yCAAyC,CAAC,CAAA,CAC7D,GAAA,CAAI,GAAA,EAAM,EAAE,OAAA,EAAS,8CAA8C,CAAC,CAAA;AAAA,EACvE,OAAA,EAAS,OAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS;AAC/B,CAAC,CAAA;AAKM,IAAM,cAAA,EAAgB,iBAAA,CAAkB,MAAA,CAAO;AAAA,EACpD,WAAA,EAAa,OAAA,CACV,MAAA,CAAO,CAAA,CACP,QAAA,CAAS,CAAA,CACT,MAAA,CAAO,CAAC,GAAA,EAAA,GAAQ,CAAC,IAAA,GAAO,kBAAA,CAAmB,QAAA,CAAS,GAA0C,CAAA,EAAG;AAAA,IAChG,OAAA,EAAS;AAAA,EACX,CAAC,CAAA;AAAA,EACH,cAAA,EAAgB,OAAA,CACb,MAAA,CAAO,CAAA,CACP,QAAA,CAAS,CAAA,CACT,MAAA,CAAO,CAAC,GAAA,EAAA,GAAQ,CAAC,IAAA,GAAO,qBAAA,CAAsB,QAAA,CAAS,GAA6C,CAAA,EAAG;AAAA,IACtG,OAAA,EAAS;AAAA,EACX,CAAC;AACL,CAAC,CAAA;AFrBD;AACA;ACsKQ,+CAAA;AAhHD,SAAS,WAAA,CAAY;AAAA,EAC1B,MAAA;AAAA,EACA,oBAAA,EAAsB,0BAAA;AAAA,EACtB,MAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,WAAA,EAAa,CAAC,CAAA;AAAA,EACd,aAAA,EAAe,iBAAA;AAAA,EACf,cAAA;AAAA,EACA,aAAA;AAAA,EACA,mBAAA,EAAqB,KAAA;AAAA,EACrB,MAAA,EAAQ,WAAA;AAAA,EACR,QAAA;AAAA,EACA,WAAA,EAAa,qFAAA;AAAA,EACb,SAAA,EAAW,KAAA;AAAA,EACX,UAAA,EAAY,KAAA;AAAA,EACZ,cAAA,EAAgB,QAAA;AAAA,EAChB,gBAAA,EAAkB,EAAA;AAAA,EAClB,YAAA,EAAc,cAAA;AAAA,EACd,mBAAA,EAAqB,eAAA;AAAA,EACrB,mBAAA,EAAqB,iBAAA;AAAA,EACrB,oBAAA,EAAsB;AACxB,EAAA,EAAsB,CAAC,CAAA,EAAG;AAMxB,EAAA,MAAM,YAAA,EAAc,kDAAA,CAAmB;AAKvC,EAAA,MAAM,kBAAA,EAAoB,oDAAA;AAAqB,IAC7C,MAAA;AAAA,IACA,kBAAA;AAAA,IACA;AAAA,EACF,CAAC,CAAA;AAID,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,EAAA,EAAI,6BAAA,KAAc,CAAA;AAI9D,EAAA,MAAM,EAAE,kBAAA,EAAoB,UAAA,EAAY,aAAa,EAAA,EAAI,kDAAA,CAAmB;AAE5E,EAAA,MAAM,aAAA,EAAe,eAAA,EAAiB,iBAAA,EAAmB,iBAAA,CAAkB,YAAA;AAG3E,EAAA,MAAM,UAAA,EAAY,eAAA,EAAiB,MAAA,EAAQ,iBAAA,CAAkB,SAAA;AAE7D,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA,EAAW,EAAE,OAAO,CAAA;AAAA,IACpB;AAAA,EACF,EAAA,EAAI,oCAAA;AAAyB,IAC3B,QAAA,EAAU,8BAAA,aAAyB,CAAA;AAAA,IACnC,aAAA,EAAe;AAAA,MACb,GAAI,gBAAA,GAAmB,EAAE,YAAA,EAAc,gBAAgB,CAAA;AAAA,MACvD,GAAI,iBAAA,GAAoB,EAAE,OAAA,EAAS,iBAAiB,CAAA;AAAA;AAAA;AAAA,MAGpD,GAAG;AAAA,IACL;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,iBAAA,EAAmB,MAAA,CAAO,IAAA,EAAA,GAA0B;AACxD,IAAA,GAAA,CAAI,YAAA,EAAc,MAAA;AAClB,IAAA,GAAA,CAAI,mBAAA,GAAsB,WAAA,CAAY,kBAAA,EAAoB,MAAA;AAC1D,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,EAAE,GAAG,IAAA,EAAM,GAAI,OAAA,GAAU,EAAE,OAAA,EAAS,OAAO,CAAA,EAAI,GAAG,UAAA,CAAW,EAAE,CAAA;AAC/E,MAAA,MAAM,iBAAA,EAAmB,mBAAA,EAAqB,WAAA,CAAY,iBAAA,EAAmB,CAAC,CAAA;AAC9E,MAAA,GAAA,CAAI,cAAA,EAAgB;AAClB,QAAA,mBAAA,CAAoB,IAAI,CAAA;AACxB,QAAA,IAAI;AACF,UAAA,MAAM,cAAA,CAAe,OAAA,EAAS,gBAAgB,CAAA;AAAA,QAChD,EAAA,QAAE;AACA,UAAA,mBAAA,CAAoB,KAAK,CAAA;AAAA,QAC3B;AAAA,MACF,EAAA,KAAO;AACL,QAAA,MAAM,iBAAA,CAAkB,MAAA,CAAO,OAAO,CAAA;AAAA,MACxC;AACA,sBAAA,eAAA,wBAAA,CAAkB,GAAA;AAClB,MAAA,KAAA,CAAM,CAAA;AACN,MAAA,YAAA,CAAa,CAAA;AACb,MAAA,GAAA,CAAI,kBAAA,EAAoB,WAAA,CAAY,KAAA,CAAM,CAAA;AAAA,IAC5C,EAAA,WAAQ;AAAA,IAMR;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAA,EAAW,CAAC,UAAA,CAAW,QAAA,CAAS,MAAM,CAAA;AAC5C,EAAA,MAAM,UAAA,EAAY,CAAC,UAAA,CAAW,QAAA,CAAS,OAAO,CAAA;AAC9C,EAAA,MAAM,iBAAA,EAAmB,SAAA,GAAY,SAAA;AACrC,EAAA,MAAM,gBAAA,EAAkB,CAAC,UAAA,CAAW,QAAA,CAAS,aAAa,CAAA;AAC1D,EAAA,MAAM,mBAAA,EAAqB,CAAC,UAAA,CAAW,QAAA,CAAS,gBAAgB,CAAA;AAChE,EAAA,MAAM,iBAAA,EAAmB,CAAC,UAAA,CAAW,QAAA,CAAS,cAAc,CAAA;AAC5D,EAAA,MAAM,YAAA,EAAc,CAAC,UAAA,CAAW,QAAA,CAAS,SAAS,CAAA;AAElD,EAAA,uBACE,8BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,CAAA,qBAAA,EAAwB,CAAC,SAAA,EAAW,sDAAA,EAAwD,EAAE,CAAA,CAAA,EAAI,CAAC,UAAA,EAAY,qBAAA,EAAuB,EAAE,CAAA,CAAA;AAEjJ,MAAA;AAEG,QAAA;AAGC,UAAA;AAKA,UAAA;AAEJ,QAAA;AAGF,wBAAA;AAAC,UAAA;AAAA,UAAA;AACgE,YAAA;AAOrD,cAAA;AACN,gBAAA;AACO,gBAAA;AACgE,kBAAA;AACvE,gBAAA;AACF,cAAA;AACD,YAAA;AACS,YAAA;AAUT,YAAA;AAAwD,cAAA;AACE,cAAA;AACc,cAAA;AACV,cAAA;AAGxB,8BAAA;AAKtC,cAAA;AAII,cAAA;AAEG,gBAAA;AAAsB,kCAAA;AAAA,oBAAA;AACwB,oCAAA;AAC9C,kBAAA;AACA,kCAAA;AAAC,oBAAA;AAAA,oBAAA;AACI,sBAAA;AACE,sBAAA;AACc,sBAAA;AACP,sBAAA;AACW,sBAAA;AACN,sBAAA;AACP,sBAAA;AAAA,oBAAA;AACZ,kBAAA;AAIE,kBAAA;AAEJ,gBAAA;AAIE,gBAAA;AAAuB,kCAAA;AAAA,oBAAA;AACmB,oCAAA;AAC1C,kBAAA;AACA,kCAAA;AAAC,oBAAA;AAAA,oBAAA;AACI,sBAAA;AACE,sBAAA;AACe,sBAAA;AACR,sBAAA;AACW,sBAAA;AACN,sBAAA;AACP,sBAAA;AAAA,oBAAA;AACZ,kBAAA;AAGkB,kBAAA;AAGpB,gBAAA;AAEJ,cAAA;AAKG,cAAA;AAEG,gBAAA;AAAyC,kCAAA;AACzC,kCAAA;AAAC,oBAAA;AAAA,oBAAA;AACC,sBAAA;AACK,sBAAA;AAGD,sBAAA;AAAA,wCAAA;AAAC,0BAAA;AAAA,0BAAA;AACI,4BAAA;AACQ,4BAAA;AACD,4BAAA;AAEqC,4BAAA;AAAA,0BAAA;AACjD,wBAAA;AAIO,wCAAA;AAIT,sBAAA;AAAA,oBAAA;AAEJ,kBAAA;AAGY,kBAAA;AAGd,gBAAA;AAIE,gBAAA;AAA0D,kCAAA;AAC1D,kCAAA;AAAC,oBAAA;AAAA,oBAAA;AACC,sBAAA;AACK,sBAAA;AAGD,sBAAA;AAAA,wCAAA;AAAC,0BAAA;AAAA,0BAAA;AACI,4BAAA;AACQ,4BAAA;AACD,4BAAA;AAEkC,4BAAA;AAAA,0BAAA;AAC9C,wBAAA;AAIO,wCAAA;AAIT,sBAAA;AAAA,oBAAA;AAEJ,kBAAA;AAGK,kBAAA;AAGP,gBAAA;AAEJ,cAAA;AAKE,cAAA;AAA8B,gCAAA;AAAA,kBAAA;AACgC,kCAAA;AAC9D,gBAAA;AACA,gCAAA;AAAC,kBAAA;AAAA,kBAAA;AACC,oBAAA;AACK,oBAAA;AAGD,oBAAA;AAAA,sCAAA;AAAC,wBAAA;AAAA,wBAAA;AACI,0BAAA;AACQ,0BAAA;AACD,0BAAA;AAE2C,0BAAA;AAAA,wBAAA;AACvD,sBAAA;AAIO,sCAAA;AAIT,oBAAA;AAAA,kBAAA;AAEJ,gBAAA;AAGY,gBAAA;AAGd,cAAA;AAKE,cAAA;AAAyB,gCAAA;AAAA,kBAAA;AACwB,kCAAA;AACjD,gBAAA;AACA,gCAAA;AAAC,kBAAA;AAAA,kBAAA;AACI,oBAAA;AACmB,oBAAA;AACV,oBAAA;AACW,oBAAA;AACN,oBAAA;AACP,oBAAA;AAAA,kBAAA;AACZ,gBAAA;AAGoB,gBAAA;AAGtB,cAAA;AAUE,cAAA;AAAA,gCAAA;AAAC,kBAAA;AAAA,kBAAA;AAC0B,oBAAA;AACH,oBAAA;AACZ,oBAAA;AAAA,kBAAA;AACZ,gBAAA;AAEE,gCAAA;AAAA,kCAAA;AAAC,oBAAA;AAAA,oBAAA;AACmB,sBAAA;AACwB,sBAAA;AAClB,sBAAA;AACd,sBAAA;AAAA,oBAAA;AACZ,kBAAA;AAGA,kCAAA;AACF,gBAAA;AACF,cAAA;AAIC,8BAAA;AAGC,gBAAA;AAEF,gCAAA;AAAC,kBAAA;AAAA,kBAAA;AACM,oBAAA;AACI,oBAAA;AAI4B,oBAAA;AAE5B,oBAAA;AACqC,oBAAA;AAEZ,oBAAA;AAAA,kBAAA;AACpC,gBAAA;AACF,cAAA;AAAA,YAAA;AAAA,UAAA;AACF,QAAA;AAAA,MAAA;AAAA,IAAA;AACF,EAAA;AAEJ;AD1J0J;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-ZNYJD27G.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=\"text-h4 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-h6 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-h6 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-h6 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-h6 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-h6 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-h6 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-h6 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=\"text-h6 text-ods-text-secondary 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"]}