@brandlab-digital/site-next 2.1.0 → 3.1.0
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.
- package/dist/analytics.d.ts +111 -52
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +281 -153
- package/dist/analytics.js.map +1 -1
- package/dist/blog.d.ts +4 -2
- package/dist/blog.d.ts.map +1 -1
- package/dist/blog.js +7 -7
- package/dist/blog.js.map +1 -1
- package/dist/contact/client-entry.d.ts +2 -0
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js +1 -0
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/client.d.ts +12 -22
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +28 -21
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/form-client.d.ts +159 -0
- package/dist/contact/form-client.d.ts.map +1 -0
- package/dist/contact/form-client.js +257 -0
- package/dist/contact/form-client.js.map +1 -0
- package/dist/contact/index.d.ts +1 -1
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +1 -1
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/route-handler.d.ts +32 -0
- package/dist/contact/route-handler.d.ts.map +1 -0
- package/dist/contact/route-handler.js +44 -0
- package/dist/contact/route-handler.js.map +1 -0
- package/dist/contact/server-entry.d.ts +1 -0
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -0
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +5 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +13 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +4 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +29 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +27 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/google-tag-plan.d.ts +1 -4
- package/dist/google-tag-plan.d.ts.map +1 -1
- package/dist/google-tag-plan.js +1 -14
- package/dist/google-tag-plan.js.map +1 -1
- package/dist/google-tag.d.ts +5 -4
- package/dist/google-tag.d.ts.map +1 -1
- package/dist/google-tag.js +5 -4
- package/dist/google-tag.js.map +1 -1
- package/dist/headers.d.ts +54 -27
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +122 -51
- package/dist/headers.js.map +1 -1
- package/dist/instrumentation-client.d.ts +21 -1
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +62 -7
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/lead-registry.d.ts +34 -0
- package/dist/lead-registry.d.ts.map +1 -0
- package/dist/lead-registry.js +30 -0
- package/dist/lead-registry.js.map +1 -0
- package/dist/lead-reporting.d.ts +37 -0
- package/dist/lead-reporting.d.ts.map +1 -0
- package/dist/lead-reporting.js +74 -0
- package/dist/lead-reporting.js.map +1 -0
- package/dist/metadata.d.ts +10 -11
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +17 -19
- package/dist/metadata.js.map +1 -1
- package/dist/next-config.d.ts +113 -0
- package/dist/next-config.d.ts.map +1 -0
- package/dist/next-config.js +213 -0
- package/dist/next-config.js.map +1 -0
- package/dist/robots.d.ts +14 -6
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +26 -17
- package/dist/robots.js.map +1 -1
- package/dist/sitemaps.d.ts +34 -0
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +66 -1
- package/dist/sitemaps.js.map +1 -1
- package/dist/testing/server-only.d.ts +8 -0
- package/dist/testing/server-only.d.ts.map +1 -0
- package/dist/testing/server-only.js +8 -0
- package/dist/testing/server-only.js.map +1 -0
- package/dist/ui-client.d.ts +1 -12
- package/dist/ui-client.d.ts.map +1 -1
- package/dist/ui-client.js +0 -17
- package/dist/ui-client.js.map +1 -1
- package/dist/ui.d.ts +22 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +67 -6
- package/dist/ui.js.map +1 -1
- package/package.json +16 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/contact/client.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport type { GtagTransport } from '@brandlab-digital/site-core/analytics'\nimport {\n resolveContactAttachmentType,\n detectContactTransportAttachmentType,\n type ContactActionResult,\n} from '@brandlab-digital/site-core/contact'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\nimport { getInitialContactAttribution } from '../instrumentation-client.js'\nimport {\n trackDeliveredContactWithEnhancedConversions,\n type SafeContactAdsParams,\n type SafeContactAnalyticsParams,\n} from '../analytics.js'\nimport {\n CONTACT_CONFIRMATION_ID_PATTERN,\n CONTACT_FORM_FIELDS,\n createContactSubmissionId,\n type BrandlabContactDefinition,\n} from './shared.js'\n\nexport { createContactSubmissionId }\n\nexport function normalizeContactUploadFile(original: File, processed: Blob): File {\n if (!(original instanceof File) || !(processed instanceof Blob)) {\n throw new TypeError('Contact image normalisation requires browser File and Blob values.')\n }\n const contentType = processed.type || original.type\n const filenameType = resolveContactAttachmentType(original.name)\n const processedType = resolveContactAttachmentType('', contentType)\n const normalizedFilenameType = filenameType === 'image/jpg' ? 'image/jpeg' : filenameType\n const normalizedProcessedType = processedType === 'image/jpg' ? 'image/jpeg' : processedType\n if (!normalizedFilenameType || normalizedFilenameType !== normalizedProcessedType) {\n throw new TypeError('The processed contact image does not match its reviewed filename.')\n }\n return new File([processed], original.name, {\n type: normalizedFilenameType,\n lastModified: original.lastModified,\n })\n}\n\n/** Explicit format-changing path; never relabel the original HEIC bytes as JPEG. */\nexport async function normalizeContactConvertedUploadFile(original: File, processed: Blob): Promise<File> {\n if (!(original instanceof File) || !(processed instanceof Blob)) {\n throw new TypeError('Contact image conversion requires browser File and Blob values.')\n }\n const selectedType = resolveContactAttachmentType(original.name, original.type)\n if (\n !['image/heic', 'image/heif'].includes(selectedType ?? '') ||\n processed.type !== 'image/jpeg' || processed.size === 0 ||\n detectContactTransportAttachmentType(new Uint8Array(await processed.slice(0, 12).arrayBuffer())) !== 'image/jpeg'\n ) {\n throw new TypeError('The converted contact image must contain JPEG bytes.')\n }\n return new File([processed], `${original.name.replace(/\\.[^.]*$/, '') || 'photo'}.jpg`, {\n type: 'image/jpeg', lastModified: original.lastModified,\n })\n}\n\nconst CONTACT_FIELD_ERROR_KEY_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/\nconst CONTACT_ERROR_CODES = new Set([\n 'validation',\n 'attachment_rejected',\n 'delivery_unavailable',\n])\n\nfunction objectRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction hasOnlyKeys(record: Record<string, unknown>, allowed: readonly string[]) {\n const keys = new Set(allowed)\n return Object.keys(record).every((key) => keys.has(key))\n}\n\nfunction parseFieldErrors(value: unknown) {\n if (value === undefined) return undefined\n const record = objectRecord(value)\n if (!record || Object.keys(record).length > 48) return undefined\n const result: Record<string, string[]> = {}\n for (const [field, messages] of Object.entries(record)) {\n if (\n !CONTACT_FIELD_ERROR_KEY_PATTERN.test(field) ||\n !Array.isArray(messages) ||\n messages.length < 1 ||\n messages.length > 5 ||\n messages.some(\n (message) =>\n typeof message !== 'string' ||\n !message.trim() ||\n message.length > 200 ||\n /[\\u0000-\\u001f\\u007f]/.test(message),\n )\n ) {\n return undefined\n }\n result[field] = [...messages]\n }\n return result\n}\n\nexport function parseContactActionResult(value: unknown): ContactActionResult | undefined {\n const record = objectRecord(value)\n if (!record || typeof record.ok !== 'boolean') return undefined\n if (record.ok) {\n if (record.accepted === true) {\n if (\n !hasOnlyKeys(record, ['ok', 'accepted', 'trackConversion', 'confirmationId']) ||\n record.trackConversion !== true ||\n typeof record.confirmationId !== 'string' ||\n !CONTACT_CONFIRMATION_ID_PATTERN.test(record.confirmationId)\n ) {\n return undefined\n }\n return {\n ok: true,\n accepted: true,\n trackConversion: true,\n confirmationId: record.confirmationId,\n }\n }\n if (\n record.accepted !== false ||\n record.trackConversion !== false ||\n !hasOnlyKeys(record, ['ok', 'accepted', 'trackConversion'])\n ) {\n return undefined\n }\n return { ok: true, accepted: false, trackConversion: false }\n }\n\n if (\n !hasOnlyKeys(record, ['ok', 'code', 'retryable', 'fieldErrors']) ||\n typeof record.code !== 'string' ||\n !CONTACT_ERROR_CODES.has(record.code) ||\n typeof record.retryable !== 'boolean'\n ) {\n return undefined\n }\n const fieldErrors = parseFieldErrors(record.fieldErrors)\n if (record.fieldErrors !== undefined && !fieldErrors) return undefined\n if (record.code !== 'validation' && fieldErrors) return undefined\n return {\n ok: false,\n code: record.code as Extract<ContactActionResult, { ok: false }>['code'],\n retryable: record.retryable,\n ...(fieldErrors ? { fieldErrors } : {}),\n }\n}\n\nexport type ContactSubmissionPhase =\n | 'idle'\n | 'submitting'\n | 'accepted'\n | 'accepted-silently'\n | 'error'\n\nexport interface ContactSubmissionState {\n phase: ContactSubmissionPhase\n pending: boolean\n canRetry: boolean\n result?: ContactActionResult\n}\n\nexport interface ContactHttpSubmissionControllerOptions {\n pagePath: string\n pageLabel?: string\n contactEndpoint?: string\n fetcher?: typeof fetch\n now?: () => Date\n analytics?: Omit<\n Parameters<typeof trackDeliveredContactResult>[1],\n 'formData'\n >\n onStateChange?: (state: ContactSubmissionState) => void\n}\n\nexport interface ContactSubmissionController {\n getState(): ContactSubmissionState\n submit(formData: FormData): Promise<ContactActionResult>\n retry(): Promise<ContactActionResult>\n reset(): void\n}\n\nfunction copyFormData(formData: FormData) {\n if (!formData || typeof formData.entries !== 'function') {\n throw new TypeError('A FormData submission is required.')\n }\n const copy = new FormData()\n for (const [name, value] of formData.entries()) copy.append(name, value)\n return copy\n}\n\nfunction rootRelativeEndpoint(value: string | undefined, fallback: string) {\n const endpoint = value ?? fallback\n if (\n !/^\\/[a-z0-9/_-]+$/.test(endpoint) ||\n endpoint.includes('//') ||\n endpoint.endsWith('/')\n ) {\n throw new TypeError('Contact endpoints must be canonical root-relative paths.')\n }\n return endpoint\n}\n\nasync function boundedJson(response: Response, maximumBytes: number) {\n const contentType = response.headers\n .get('content-type')\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase()\n if (contentType !== 'application/json') {\n throw new Error('The contact response did not use the required JSON media type.')\n }\n const text = await response.text()\n if (new TextEncoder().encode(text).byteLength > maximumBytes) {\n throw new Error('The contact response exceeded its safe bound.')\n }\n return JSON.parse(text) as unknown\n}\n\nconst RETRYABLE_CLIENT_FAILURE: ContactActionResult = {\n ok: false,\n code: 'delivery_unavailable',\n retryable: true,\n}\n\nfunction reviewedPageLabel(value: string | undefined) {\n const label = value?.trim()\n if (\n label !== undefined &&\n (!label || label.length > 160 || /[\\u0000-\\u001f\\u007f]/.test(label))\n ) {\n throw new TypeError('pageLabel must be bounded reviewed text.')\n }\n return label\n}\n\nfunction addSubmissionMetadata(\n formData: FormData,\n options: {\n pagePath: string\n pageLabel?: string\n now: Date\n },\n) {\n const attribution =\n typeof window === 'undefined'\n ? { initialLandingPath: options.pagePath, visitSource: 'direct-or-unknown' as const }\n : getInitialContactAttribution(window)\n formData.set(CONTACT_FORM_FIELDS.clientSubmissionId, createContactSubmissionId())\n formData.set(CONTACT_FORM_FIELDS.submittedAt, options.now.toISOString())\n formData.set(CONTACT_FORM_FIELDS.pagePath, options.pagePath)\n if (options.pageLabel) {\n formData.set(CONTACT_FORM_FIELDS.pageLabel, options.pageLabel)\n }\n formData.set(CONTACT_FORM_FIELDS.initialLandingPath, attribution.initialLandingPath)\n formData.set(CONTACT_FORM_FIELDS.visitSource, attribution.visitSource)\n if (attribution.visitSourceDetail) {\n formData.set(CONTACT_FORM_FIELDS.visitSourceDetail, attribution.visitSourceDetail)\n }\n return formData\n}\n\nexport function createContactHttpSubmissionController(\n options: ContactHttpSubmissionControllerOptions,\n): ContactSubmissionController {\n const contactEndpoint = rootRelativeEndpoint(options.contactEndpoint, '/api/contact')\n const pagePath = normalizeBrandlabRoutePath(options.pagePath)\n const pageLabel = reviewedPageLabel(options.pageLabel)\n const fetcher = options.fetcher ?? fetch\n if (typeof fetcher !== 'function') throw new TypeError('A fetch implementation is required.')\n if (options.onStateChange !== undefined && typeof options.onStateChange !== 'function') {\n throw new TypeError('onStateChange must be a function.')\n }\n\n const trackedConfirmations = new Set<string>()\n let attempt: FormData | undefined\n let inFlight: Promise<ContactActionResult> | undefined\n let state: ContactSubmissionState = {\n phase: 'idle',\n pending: false,\n canRetry: false,\n }\n\n const publish = (next: ContactSubmissionState) => {\n state = Object.freeze({ ...next })\n options.onStateChange?.(state)\n }\n\n const post = async (formData: FormData) => {\n publish({ phase: 'submitting', pending: true, canRetry: false })\n let result: ContactActionResult\n try {\n const response = await fetcher(contactEndpoint, {\n method: 'POST',\n credentials: 'same-origin',\n cache: 'no-store',\n body: copyFormData(formData),\n })\n result =\n parseContactActionResult(await boundedJson(response, 64 * 1_024)) ??\n RETRYABLE_CLIENT_FAILURE\n } catch {\n result = RETRYABLE_CLIENT_FAILURE\n }\n\n if (!result.ok) {\n publish({\n phase: 'error',\n pending: false,\n canRetry: result.retryable,\n result,\n })\n return result\n }\n\n if (result.accepted && !trackedConfirmations.has(result.confirmationId)) {\n trackedConfirmations.add(result.confirmationId)\n try {\n if (options.analytics) {\n trackDeliveredContactResult(result, {\n ...options.analytics,\n formData: copyFormData(formData),\n })\n }\n } catch {\n // Delivery has already succeeded; analytics cannot change that result.\n }\n }\n\n attempt = undefined\n publish({\n phase: result.accepted ? 'accepted' : 'accepted-silently',\n pending: false,\n canRetry: false,\n result,\n })\n return result\n }\n\n const run = (formData: FormData) => {\n if (inFlight) return inFlight\n let wrapped: Promise<ContactActionResult>\n wrapped = post(formData).finally(() => {\n if (inFlight === wrapped) inFlight = undefined\n })\n inFlight = wrapped\n return wrapped\n }\n\n return {\n getState: () => ({ ...state }),\n submit(formData) {\n if (inFlight) return inFlight\n const now = options.now?.() ?? new Date()\n if (!Number.isFinite(now.getTime())) {\n attempt = undefined\n publish({\n phase: 'error',\n pending: false,\n canRetry: false,\n result: RETRYABLE_CLIENT_FAILURE,\n })\n return Promise.resolve(RETRYABLE_CLIENT_FAILURE)\n }\n attempt = addSubmissionMetadata(copyFormData(formData), {\n pagePath,\n ...(pageLabel ? { pageLabel } : {}),\n now,\n })\n return run(attempt)\n },\n retry() {\n if (inFlight) return inFlight\n if (!attempt || state.phase !== 'error' || !state.canRetry) {\n return Promise.reject(new Error('No retryable contact submission is available.'))\n }\n return run(attempt)\n },\n reset() {\n if (inFlight) throw new Error('A pending contact submission cannot be reset.')\n attempt = undefined\n publish({ phase: 'idle', pending: false, canRetry: false })\n },\n }\n}\n\nexport function shouldTrackContactResult(\n result: ContactActionResult,\n): result is Extract<ContactActionResult, { ok: true; accepted: true }> {\n return result.ok && result.accepted && result.trackConversion\n}\n\nexport function trackDeliveredContactResult(\n result: ContactActionResult,\n options: {\n pagePath: string\n definition: BrandlabContactDefinition\n formData: FormData\n enabled: boolean\n enhancedConversionsEnabled?: boolean\n adUserDataConsent: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n params?: SafeContactAnalyticsParams\n adsParams?: SafeContactAdsParams\n },\n) {\n const {\n pagePath,\n definition,\n formData,\n adUserDataConsent,\n params,\n adsParams,\n ...config\n } = options\n return trackDeliveredContactWithEnhancedConversions(result, config, {\n pagePath,\n definition,\n formData,\n adUserDataConsent,\n params,\n adsParams,\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/contact/client.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport {\n resolveContactAttachmentType,\n detectContactTransportAttachmentType,\n type ContactActionResult,\n} from '@brandlab-digital/site-core/contact'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\nimport { getInitialContactAttribution } from '../instrumentation-client.js'\nimport {\n CONTACT_CONFIRMATION_ID_PATTERN,\n CONTACT_FORM_FIELDS,\n createContactSubmissionId,\n} from './shared.js'\n\nexport { createContactSubmissionId }\n\nexport function normalizeContactUploadFile(original: File, processed: Blob): File {\n if (!(original instanceof File) || !(processed instanceof Blob)) {\n throw new TypeError('Contact image normalisation requires browser File and Blob values.')\n }\n const contentType = processed.type || original.type\n const filenameType = resolveContactAttachmentType(original.name)\n const processedType = resolveContactAttachmentType('', contentType)\n const normalizedFilenameType = filenameType === 'image/jpg' ? 'image/jpeg' : filenameType\n const normalizedProcessedType = processedType === 'image/jpg' ? 'image/jpeg' : processedType\n if (!normalizedFilenameType || normalizedFilenameType !== normalizedProcessedType) {\n throw new TypeError('The processed contact image does not match its reviewed filename.')\n }\n return new File([processed], original.name, {\n type: normalizedFilenameType,\n lastModified: original.lastModified,\n })\n}\n\n/** Explicit format-changing path; never relabel the original HEIC bytes as JPEG. */\nexport async function normalizeContactConvertedUploadFile(original: File, processed: Blob): Promise<File> {\n if (!(original instanceof File) || !(processed instanceof Blob)) {\n throw new TypeError('Contact image conversion requires browser File and Blob values.')\n }\n const selectedType = resolveContactAttachmentType(original.name, original.type)\n if (\n !['image/heic', 'image/heif'].includes(selectedType ?? '') ||\n processed.type !== 'image/jpeg' || processed.size === 0 ||\n detectContactTransportAttachmentType(new Uint8Array(await processed.slice(0, 12).arrayBuffer())) !== 'image/jpeg'\n ) {\n throw new TypeError('The converted contact image must contain JPEG bytes.')\n }\n return new File([processed], `${original.name.replace(/\\.[^.]*$/, '') || 'photo'}.jpg`, {\n type: 'image/jpeg', lastModified: original.lastModified,\n })\n}\n\nconst CONTACT_FIELD_ERROR_KEY_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/\nconst CONTACT_ERROR_CODES = new Set([\n 'validation',\n 'attachment_rejected',\n 'delivery_unavailable',\n])\n\nfunction objectRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction hasOnlyKeys(record: Record<string, unknown>, allowed: readonly string[]) {\n const keys = new Set(allowed)\n return Object.keys(record).every((key) => keys.has(key))\n}\n\nfunction parseFieldErrors(value: unknown) {\n if (value === undefined) return undefined\n const record = objectRecord(value)\n if (!record || Object.keys(record).length > 48) return undefined\n const result: Record<string, string[]> = {}\n for (const [field, messages] of Object.entries(record)) {\n if (\n !CONTACT_FIELD_ERROR_KEY_PATTERN.test(field) ||\n !Array.isArray(messages) ||\n messages.length < 1 ||\n messages.length > 5 ||\n messages.some(\n (message) =>\n typeof message !== 'string' ||\n !message.trim() ||\n message.length > 200 ||\n /[\\u0000-\\u001f\\u007f]/.test(message),\n )\n ) {\n return undefined\n }\n result[field] = [...messages]\n }\n return result\n}\n\nexport function parseContactActionResult(value: unknown): ContactActionResult | undefined {\n const record = objectRecord(value)\n if (!record || typeof record.ok !== 'boolean') return undefined\n if (record.ok) {\n if (record.accepted === true) {\n if (\n !hasOnlyKeys(record, ['ok', 'accepted', 'trackConversion', 'confirmationId']) ||\n record.trackConversion !== true ||\n typeof record.confirmationId !== 'string' ||\n !CONTACT_CONFIRMATION_ID_PATTERN.test(record.confirmationId)\n ) {\n return undefined\n }\n return {\n ok: true,\n accepted: true,\n trackConversion: true,\n confirmationId: record.confirmationId,\n }\n }\n if (\n record.accepted !== false ||\n record.trackConversion !== false ||\n !hasOnlyKeys(record, ['ok', 'accepted', 'trackConversion'])\n ) {\n return undefined\n }\n return { ok: true, accepted: false, trackConversion: false }\n }\n\n if (\n !hasOnlyKeys(record, ['ok', 'code', 'retryable', 'fieldErrors']) ||\n typeof record.code !== 'string' ||\n !CONTACT_ERROR_CODES.has(record.code) ||\n typeof record.retryable !== 'boolean'\n ) {\n return undefined\n }\n const fieldErrors = parseFieldErrors(record.fieldErrors)\n if (record.fieldErrors !== undefined && !fieldErrors) return undefined\n if (record.code !== 'validation' && fieldErrors) return undefined\n return {\n ok: false,\n code: record.code as Extract<ContactActionResult, { ok: false }>['code'],\n retryable: record.retryable,\n ...(fieldErrors ? { fieldErrors } : {}),\n }\n}\n\nexport type ContactSubmissionPhase =\n | 'idle'\n | 'submitting'\n | 'accepted'\n | 'accepted-silently'\n | 'error'\n\nexport interface ContactSubmissionState {\n phase: ContactSubmissionPhase\n pending: boolean\n canRetry: boolean\n result?: ContactActionResult\n}\n\nexport interface ContactHttpSubmissionControllerOptions {\n pagePath: string\n pageLabel?: string\n contactEndpoint?: string\n fetcher?: typeof fetch\n now?: () => Date\n /** Reports an accepted enquiry, once per confirmation ID, with the attempt's fields. */\n reportAccepted?: (\n result: Extract<ContactActionResult, { ok: true; accepted: true }>,\n formData: FormData,\n ) => void\n onStateChange?: (state: ContactSubmissionState) => void\n}\n\nexport interface ContactSubmissionController {\n getState(): ContactSubmissionState\n /**\n * Sends the fields. After a retryable failure, fields identical to the failed\n * attempt resend that attempt with its submission ID; changed fields start a\n * new submission.\n */\n submit(formData: FormData): Promise<ContactActionResult>\n /** Resends the failed attempt with its submission ID. */\n retry(): Promise<ContactActionResult>\n reset(): void\n}\n\ntype FormDataFields = readonly (readonly [string, FormDataEntryValue])[]\n\nfunction sameEntryValue(left: FormDataEntryValue, right: FormDataEntryValue) {\n if (typeof left === 'string' || typeof right === 'string') return left === right\n return left === right || (\n left.name === right.name &&\n left.size === right.size &&\n left.type === right.type &&\n left.lastModified === right.lastModified\n )\n}\n\nfunction sameFields(left: FormDataFields, right: FormDataFields) {\n return left.length === right.length && left.every(([name, value], index) =>\n name === right[index]![0] && sameEntryValue(value, right[index]![1]))\n}\n\nfunction copyFormData(formData: FormData) {\n if (!formData || typeof formData.entries !== 'function') {\n throw new TypeError('A FormData submission is required.')\n }\n const copy = new FormData()\n for (const [name, value] of formData.entries()) copy.append(name, value)\n return copy\n}\n\nfunction rootRelativeEndpoint(value: string | undefined, fallback: string) {\n const endpoint = value ?? fallback\n if (\n !/^\\/[a-z0-9/_-]+$/.test(endpoint) ||\n endpoint.includes('//') ||\n endpoint.endsWith('/')\n ) {\n throw new TypeError('Contact endpoints must be canonical root-relative paths.')\n }\n return endpoint\n}\n\nasync function boundedJson(response: Response, maximumBytes: number) {\n const contentType = response.headers\n .get('content-type')\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase()\n if (contentType !== 'application/json') {\n throw new Error('The contact response did not use the required JSON media type.')\n }\n const text = await response.text()\n if (new TextEncoder().encode(text).byteLength > maximumBytes) {\n throw new Error('The contact response exceeded its safe bound.')\n }\n return JSON.parse(text) as unknown\n}\n\nconst RETRYABLE_CLIENT_FAILURE: ContactActionResult = {\n ok: false,\n code: 'delivery_unavailable',\n retryable: true,\n}\n\nfunction reviewedPageLabel(value: string | undefined) {\n const label = value?.trim()\n if (\n label !== undefined &&\n (!label || label.length > 160 || /[\\u0000-\\u001f\\u007f]/.test(label))\n ) {\n throw new TypeError('pageLabel must be bounded reviewed text.')\n }\n return label\n}\n\nfunction addSubmissionMetadata(\n formData: FormData,\n options: {\n pagePath: string\n pageLabel?: string\n now: Date\n },\n) {\n const attribution =\n typeof window === 'undefined'\n ? { initialLandingPath: options.pagePath, visitSource: 'direct-or-unknown' as const }\n : getInitialContactAttribution(window)\n formData.set(CONTACT_FORM_FIELDS.clientSubmissionId, createContactSubmissionId())\n formData.set(CONTACT_FORM_FIELDS.submittedAt, options.now.toISOString())\n formData.set(CONTACT_FORM_FIELDS.pagePath, options.pagePath)\n if (options.pageLabel) {\n formData.set(CONTACT_FORM_FIELDS.pageLabel, options.pageLabel)\n }\n formData.set(CONTACT_FORM_FIELDS.initialLandingPath, attribution.initialLandingPath)\n formData.set(CONTACT_FORM_FIELDS.visitSource, attribution.visitSource)\n if (attribution.visitSourceDetail) {\n formData.set(CONTACT_FORM_FIELDS.visitSourceDetail, attribution.visitSourceDetail)\n }\n return formData\n}\n\nexport function createContactHttpSubmissionController(\n options: ContactHttpSubmissionControllerOptions,\n): ContactSubmissionController {\n const contactEndpoint = rootRelativeEndpoint(options.contactEndpoint, '/api/contact')\n const pagePath = normalizeBrandlabRoutePath(options.pagePath)\n const pageLabel = reviewedPageLabel(options.pageLabel)\n const fetcher = options.fetcher ?? fetch\n if (typeof fetcher !== 'function') throw new TypeError('A fetch implementation is required.')\n if (options.onStateChange !== undefined && typeof options.onStateChange !== 'function') {\n throw new TypeError('onStateChange must be a function.')\n }\n if (options.reportAccepted !== undefined && typeof options.reportAccepted !== 'function') {\n throw new TypeError('reportAccepted must be a function.')\n }\n\n const trackedConfirmations = new Set<string>()\n let attempt: FormData | undefined\n let attemptFields: FormDataFields | undefined\n let inFlight: Promise<ContactActionResult> | undefined\n let state: ContactSubmissionState = {\n phase: 'idle',\n pending: false,\n canRetry: false,\n }\n\n const publish = (next: ContactSubmissionState) => {\n state = Object.freeze({ ...next })\n options.onStateChange?.(state)\n }\n\n const post = async (formData: FormData) => {\n publish({ phase: 'submitting', pending: true, canRetry: false })\n let result: ContactActionResult\n try {\n const response = await fetcher(contactEndpoint, {\n method: 'POST',\n credentials: 'same-origin',\n cache: 'no-store',\n body: copyFormData(formData),\n })\n result =\n parseContactActionResult(await boundedJson(response, 64 * 1_024)) ??\n RETRYABLE_CLIENT_FAILURE\n } catch {\n result = RETRYABLE_CLIENT_FAILURE\n }\n\n if (!result.ok) {\n publish({\n phase: 'error',\n pending: false,\n canRetry: result.retryable,\n result,\n })\n return result\n }\n\n if (result.accepted && !trackedConfirmations.has(result.confirmationId)) {\n trackedConfirmations.add(result.confirmationId)\n try {\n options.reportAccepted?.(result, copyFormData(formData))\n } catch {\n // Delivery has already succeeded; analytics cannot change that result.\n }\n }\n\n attempt = undefined\n attemptFields = undefined\n publish({\n phase: result.accepted ? 'accepted' : 'accepted-silently',\n pending: false,\n canRetry: false,\n result,\n })\n return result\n }\n\n const run = (formData: FormData) => {\n if (inFlight) return inFlight\n let wrapped: Promise<ContactActionResult>\n wrapped = post(formData).finally(() => {\n if (inFlight === wrapped) inFlight = undefined\n })\n inFlight = wrapped\n return wrapped\n }\n\n return {\n getState: () => ({ ...state }),\n submit(formData) {\n if (inFlight) return inFlight\n const fields = [...copyFormData(formData).entries()]\n if (\n attempt &&\n attemptFields &&\n state.phase === 'error' &&\n state.canRetry &&\n sameFields(attemptFields, fields)\n ) {\n return run(attempt)\n }\n const now = options.now?.() ?? new Date()\n if (!Number.isFinite(now.getTime())) {\n attempt = undefined\n attemptFields = undefined\n publish({\n phase: 'error',\n pending: false,\n canRetry: false,\n result: RETRYABLE_CLIENT_FAILURE,\n })\n return Promise.resolve(RETRYABLE_CLIENT_FAILURE)\n }\n attemptFields = fields\n attempt = addSubmissionMetadata(copyFormData(formData), {\n pagePath,\n ...(pageLabel ? { pageLabel } : {}),\n now,\n })\n return run(attempt)\n },\n retry() {\n if (inFlight) return inFlight\n if (!attempt || state.phase !== 'error' || !state.canRetry) {\n return Promise.reject(new Error('No retryable contact submission is available.'))\n }\n return run(attempt)\n },\n reset() {\n if (inFlight) throw new Error('A pending contact submission cannot be reset.')\n attempt = undefined\n attemptFields = undefined\n publish({ phase: 'idle', pending: false, canRetry: false })\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { type BrandlabContactContract, type ContactActionResult } from '@brandlab-digital/site-core/contact';
|
|
2
|
+
import { type FormEvent, type RefObject } from 'react';
|
|
3
|
+
import type { BrandlabAnalytics } from '../analytics.js';
|
|
4
|
+
import { type UseBrandlabContactAttachmentsOptions, type UseBrandlabContactAttachmentsResult } from './attachments-client.js';
|
|
5
|
+
import { type ContactSubmissionPhase } from './client.js';
|
|
6
|
+
/** An accepted enquiry, or a silently accepted spam-protected submission (`accepted: false`). */
|
|
7
|
+
export type BrandlabContactAcceptedResult = Extract<ContactActionResult, {
|
|
8
|
+
ok: true;
|
|
9
|
+
}>;
|
|
10
|
+
export type BrandlabContactFailedResult = Extract<ContactActionResult, {
|
|
11
|
+
ok: false;
|
|
12
|
+
}>;
|
|
13
|
+
export interface BrandlabContactSubmitStart {
|
|
14
|
+
form: HTMLFormElement;
|
|
15
|
+
/**
|
|
16
|
+
* The fields about to be sent, read from the form. Add or replace entries
|
|
17
|
+
* here (for example `service_interest`); prepared photos are appended after.
|
|
18
|
+
*/
|
|
19
|
+
formData: FormData;
|
|
20
|
+
}
|
|
21
|
+
export interface UseBrandlabContactFormOptions {
|
|
22
|
+
/** The contact contract from src/config/contact.ts. */
|
|
23
|
+
contract: BrandlabContactContract;
|
|
24
|
+
/** The site's defineBrandlabAnalytics binding: the declared lead after each accepted enquiry, plus lead diagnostics. */
|
|
25
|
+
analytics?: BrandlabAnalytics;
|
|
26
|
+
/** The current pathname, for example `usePathname()`. */
|
|
27
|
+
pagePath: string;
|
|
28
|
+
/** Reviewed label for the enquiry email's page row. */
|
|
29
|
+
pageLabel?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The form element's id and the lead diagnostics' form_id. Without it the form
|
|
32
|
+
* gets no id (the hook never takes a React id, so the form's own ids are
|
|
33
|
+
* unchanged), diagnostics use `contact-form` and error ids start `contact-form-`.
|
|
34
|
+
* The element keeps the id exactly as given; diagnostics report it as a slug
|
|
35
|
+
* (React `useId()` parts removed, see `contactFormSlug`).
|
|
36
|
+
*/
|
|
37
|
+
formId?: string;
|
|
38
|
+
/** Page to open after an accepted enquiry; `null` or omitted keeps the visitor on the page. */
|
|
39
|
+
redirectTo?: string | null;
|
|
40
|
+
/** Client navigation such as `router.push`; defaults to `window.location.assign`. */
|
|
41
|
+
navigate?: (href: string) => void;
|
|
42
|
+
/** Delay before the redirect, in milliseconds (default 1500); 0 navigates at once. */
|
|
43
|
+
redirectDelayMs?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Runs when the visitor submits, before anything is sent: clear the form's
|
|
46
|
+
* own notices, adjust `formData`, or return `false` to cancel (for example
|
|
47
|
+
* after `form.reportValidity()`). An unchanged resubmission runs it too;
|
|
48
|
+
* `retry()` does not.
|
|
49
|
+
*/
|
|
50
|
+
onSubmitStart?: (submission: BrandlabContactSubmitStart) => void | false;
|
|
51
|
+
/**
|
|
52
|
+
* Runs after every attempt, before onAccepted or onFailed: an accepted
|
|
53
|
+
* enquiry, a silently accepted submission (even when ignored) or a failure.
|
|
54
|
+
*/
|
|
55
|
+
onSettled?: (result: ContactActionResult) => void;
|
|
56
|
+
/** Runs after an accepted enquiry, once the form has been reset and before any redirect. */
|
|
57
|
+
onAccepted?: (result: BrandlabContactAcceptedResult) => void;
|
|
58
|
+
/** Runs after a failed attempt; typed values and prepared photos are kept. */
|
|
59
|
+
onFailed?: (result: BrandlabContactFailedResult) => void;
|
|
60
|
+
/**
|
|
61
|
+
* A silently accepted submission (spam protection): `complete` (default)
|
|
62
|
+
* resets, runs onAccepted and redirects like an accepted enquiry; `ignore`
|
|
63
|
+
* leaves the form as it is and runs only onSettled.
|
|
64
|
+
*/
|
|
65
|
+
silentAcceptance?: 'complete' | 'ignore';
|
|
66
|
+
/** Photo preparation options; the policy follows the contract's attachment mode by default. */
|
|
67
|
+
attachments?: UseBrandlabContactAttachmentsOptions;
|
|
68
|
+
/** Multipart field prefix for prepared files (default `attachments`, sent as `attachments[0]` …). */
|
|
69
|
+
attachmentFieldPrefix?: string;
|
|
70
|
+
/** Live Consent Mode decision for ad_user_data; Enhanced Conversions stay off without it. */
|
|
71
|
+
adUserDataConsent?: boolean;
|
|
72
|
+
contactEndpoint?: string;
|
|
73
|
+
fetcher?: typeof fetch;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Spread onto the `<form>`: `method="post"` with no `action` or `encType`, so a
|
|
77
|
+
* submission before hydration posts back to the page and never puts fields in
|
|
78
|
+
* a URL; the hook submits through fetch. `id` only when `formId` is given.
|
|
79
|
+
*/
|
|
80
|
+
export interface BrandlabContactFormProps {
|
|
81
|
+
id?: string;
|
|
82
|
+
ref: RefObject<HTMLFormElement | null>;
|
|
83
|
+
method: 'post';
|
|
84
|
+
'aria-busy': boolean;
|
|
85
|
+
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
|
86
|
+
onFocusCapture: () => void;
|
|
87
|
+
}
|
|
88
|
+
/** Spread onto the submit button: disabled until hydration, while sending or preparing photos. */
|
|
89
|
+
export interface BrandlabContactSubmitProps {
|
|
90
|
+
type: 'submit';
|
|
91
|
+
disabled: boolean;
|
|
92
|
+
}
|
|
93
|
+
export interface UseBrandlabContactFormResult {
|
|
94
|
+
/** The `formId` option, or `contact-form` when none was given. */
|
|
95
|
+
formId: string;
|
|
96
|
+
formProps: BrandlabContactFormProps;
|
|
97
|
+
submitProps: BrandlabContactSubmitProps;
|
|
98
|
+
/** False during server rendering and hydration. */
|
|
99
|
+
hydrated: boolean;
|
|
100
|
+
/** The submit button's `disabled` (also in submitProps): until hydration, while sending or preparing photos. */
|
|
101
|
+
submitDisabled: boolean;
|
|
102
|
+
pending: boolean;
|
|
103
|
+
phase: ContactSubmissionPhase;
|
|
104
|
+
result: ContactActionResult | undefined;
|
|
105
|
+
/** An accepted enquiry (a silently accepted spam-protected submission looks the same). */
|
|
106
|
+
succeeded: boolean;
|
|
107
|
+
/** A retryable failure: retry(), or submitting the same fields again, reuses its submission ID. */
|
|
108
|
+
canRetry: boolean;
|
|
109
|
+
fieldErrors: Readonly<Record<string, readonly string[]>>;
|
|
110
|
+
fieldError(name: string): string | undefined;
|
|
111
|
+
/** Id for a field's error message element: `${formId}-${name}-error`. */
|
|
112
|
+
errorId(name: string): string;
|
|
113
|
+
/** Attach to the status element; it receives focus when an error has no invalid field. */
|
|
114
|
+
statusRef: RefObject<HTMLElement | null>;
|
|
115
|
+
/** Resends the failed enquiry with the same submission ID. */
|
|
116
|
+
retry(): Promise<ContactActionResult | undefined>;
|
|
117
|
+
attachments: UseBrandlabContactAttachmentsResult;
|
|
118
|
+
/** Contract field keys accepted by contactFieldProps. */
|
|
119
|
+
fieldNames: ReadonlySet<string>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The lead diagnostics' form_id for any formId. A valid slug is kept (trimmed and
|
|
123
|
+
* lowercased). Otherwise React `useId()` parts are removed, so an id made unique for
|
|
124
|
+
* the page still reports as one stable form, every run of other characters becomes
|
|
125
|
+
* one hyphen, and the result is at most 80 characters; `contact-form` when nothing
|
|
126
|
+
* is left. Without this, a formId such as `contact-form-_R_5e35f9db_` silently
|
|
127
|
+
* dropped lead_form_start and lead_form_error.
|
|
128
|
+
*/
|
|
129
|
+
export declare function contactFormSlug(formId: string): string;
|
|
130
|
+
/**
|
|
131
|
+
* Contact form behaviour for project-owned markup and copy. The form posts
|
|
132
|
+
* through fetch with FormData; before hydration the submit button stays
|
|
133
|
+
* disabled. Typed values (uncontrolled fields) and prepared photos are kept
|
|
134
|
+
* after any failure, retry() and an unchanged resubmission reuse the same
|
|
135
|
+
* submission ID (so a resend cannot duplicate the email), the first invalid
|
|
136
|
+
* field is focused, and an accepted enquiry resets the form and redirects.
|
|
137
|
+
* The lead is reported exactly as the site's defineBrandlabAnalytics binding
|
|
138
|
+
* declares, once per accepted enquiry. Browser validation is left exactly as
|
|
139
|
+
* the form's own markup declares it.
|
|
140
|
+
*/
|
|
141
|
+
export declare function useBrandlabContactForm(options: UseBrandlabContactFormOptions): UseBrandlabContactFormResult;
|
|
142
|
+
export interface ContactFieldPropsOptions {
|
|
143
|
+
/** Ids of help text already describing the field; the error id is appended. */
|
|
144
|
+
describedBy?: string;
|
|
145
|
+
/** Defaults to disabled while an enquiry is sending. */
|
|
146
|
+
disabled?: boolean;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Props for one uncontrolled contract field: name, disabled while sending and
|
|
150
|
+
* the error relationship. It never adds value, required or length attributes,
|
|
151
|
+
* so the form's existing validation is unchanged.
|
|
152
|
+
*/
|
|
153
|
+
export declare function contactFieldProps(form: UseBrandlabContactFormResult, name: string, options?: ContactFieldPropsOptions): {
|
|
154
|
+
name: string;
|
|
155
|
+
disabled: boolean;
|
|
156
|
+
'aria-invalid': true | undefined;
|
|
157
|
+
'aria-describedby': string | undefined;
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=form-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/contact/form-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { DEFAULT_CONTACT_ATTACHMENT_POLICY, DOCUMENT_CONTACT_ATTACHMENT_POLICY, defineContactContract, } from '@brandlab-digital/site-core/contact';
|
|
3
|
+
import { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
5
|
+
import { leadBindingFor, safeSlug } from '../lead-registry.js';
|
|
6
|
+
import { reportAcceptedLead } from '../lead-reporting.js';
|
|
7
|
+
import { useBrandlabContactAttachments, } from './attachments-client.js';
|
|
8
|
+
import { createContactHttpSubmissionController, } from './client.js';
|
|
9
|
+
const INITIAL_STATE = {
|
|
10
|
+
phase: 'idle',
|
|
11
|
+
pending: false,
|
|
12
|
+
canRetry: false,
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_REDIRECT_DELAY_MS = 1_500;
|
|
15
|
+
const FOCUSABLE_INVALID_FIELD = [
|
|
16
|
+
'input[aria-invalid="true"]:not([disabled]):not([type="hidden"])',
|
|
17
|
+
'select[aria-invalid="true"]:not([disabled])',
|
|
18
|
+
'textarea[aria-invalid="true"]:not([disabled])',
|
|
19
|
+
'button[aria-invalid="true"]:not([disabled])',
|
|
20
|
+
'[aria-invalid="true"][tabindex]:not([aria-disabled="true"])',
|
|
21
|
+
].join(',');
|
|
22
|
+
const subscribeToNothing = () => () => undefined;
|
|
23
|
+
const getClientSnapshot = () => true;
|
|
24
|
+
const getServerSnapshot = () => false;
|
|
25
|
+
/** React `useId()` values: `_R_1a_`/`_r_1a_` (19.2), `«r1a»` (19.0 and 19.1), `:r1a:` (18). */
|
|
26
|
+
const REACT_ID = /_[rR]_[0-9A-Za-z]*_|«[rR][0-9A-Za-z]*»|:[rR][0-9A-Za-z]*:/g;
|
|
27
|
+
/**
|
|
28
|
+
* The lead diagnostics' form_id for any formId. A valid slug is kept (trimmed and
|
|
29
|
+
* lowercased). Otherwise React `useId()` parts are removed, so an id made unique for
|
|
30
|
+
* the page still reports as one stable form, every run of other characters becomes
|
|
31
|
+
* one hyphen, and the result is at most 80 characters; `contact-form` when nothing
|
|
32
|
+
* is left. Without this, a formId such as `contact-form-_R_5e35f9db_` silently
|
|
33
|
+
* dropped lead_form_start and lead_form_error.
|
|
34
|
+
*/
|
|
35
|
+
export function contactFormSlug(formId) {
|
|
36
|
+
const slug = safeSlug(String(formId));
|
|
37
|
+
if (slug)
|
|
38
|
+
return slug;
|
|
39
|
+
const derived = String(formId)
|
|
40
|
+
.replace(REACT_ID, '-')
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
43
|
+
.replace(/^-+/, '')
|
|
44
|
+
.slice(0, 80)
|
|
45
|
+
.replace(/-+$/, '');
|
|
46
|
+
return derived || 'contact-form';
|
|
47
|
+
}
|
|
48
|
+
function safePagePath(pagePath) {
|
|
49
|
+
try {
|
|
50
|
+
return normalizeBrandlabRoutePath(pagePath.split(/[?#]/, 1)[0] || '/');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return '/';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function errorStage(result) {
|
|
57
|
+
return result.code === 'validation' ? 'validation' : 'delivery';
|
|
58
|
+
}
|
|
59
|
+
function assertLifecycleOptions(options) {
|
|
60
|
+
const delay = options.redirectDelayMs;
|
|
61
|
+
if (delay !== undefined && (!Number.isInteger(delay) || delay < 0 || delay > 60_000)) {
|
|
62
|
+
throw new TypeError('redirectDelayMs must be a whole number of milliseconds from 0 to 60000.');
|
|
63
|
+
}
|
|
64
|
+
if (options.silentAcceptance !== undefined && !['complete', 'ignore'].includes(options.silentAcceptance)) {
|
|
65
|
+
throw new TypeError('silentAcceptance must be "complete" or "ignore".');
|
|
66
|
+
}
|
|
67
|
+
if (options.analytics !== undefined && !leadBindingFor(options.analytics)) {
|
|
68
|
+
throw new TypeError('analytics must be the binding returned by defineBrandlabAnalytics.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Contact form behaviour for project-owned markup and copy. The form posts
|
|
73
|
+
* through fetch with FormData; before hydration the submit button stays
|
|
74
|
+
* disabled. Typed values (uncontrolled fields) and prepared photos are kept
|
|
75
|
+
* after any failure, retry() and an unchanged resubmission reuse the same
|
|
76
|
+
* submission ID (so a resend cannot duplicate the email), the first invalid
|
|
77
|
+
* field is focused, and an accepted enquiry resets the form and redirects.
|
|
78
|
+
* The lead is reported exactly as the site's defineBrandlabAnalytics binding
|
|
79
|
+
* declares, once per accepted enquiry. Browser validation is left exactly as
|
|
80
|
+
* the form's own markup declares it.
|
|
81
|
+
*/
|
|
82
|
+
export function useBrandlabContactForm(options) {
|
|
83
|
+
const contract = useMemo(() => defineContactContract(options.contract), [options.contract]);
|
|
84
|
+
assertLifecycleOptions(options);
|
|
85
|
+
const hydrated = useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
|
|
86
|
+
const formId = options.formId ?? 'contact-form';
|
|
87
|
+
const diagnosticsFormId = contactFormSlug(formId);
|
|
88
|
+
const rawPagePath = options.pagePath;
|
|
89
|
+
const pagePath = safePagePath(rawPagePath);
|
|
90
|
+
const formRef = useRef(null);
|
|
91
|
+
const statusRef = useRef(null);
|
|
92
|
+
const [state, setState] = useState(INITIAL_STATE);
|
|
93
|
+
const lockRef = useRef(false);
|
|
94
|
+
const startedRef = useRef(false);
|
|
95
|
+
const redirectTimerRef = useRef(undefined);
|
|
96
|
+
const latest = useRef(options);
|
|
97
|
+
latest.current = options;
|
|
98
|
+
const policy = options.attachments?.policy ??
|
|
99
|
+
(contract.attachments.mode === 'images-and-pdfs-optional'
|
|
100
|
+
? DOCUMENT_CONTACT_ATTACHMENT_POLICY
|
|
101
|
+
: DEFAULT_CONTACT_ATTACHMENT_POLICY);
|
|
102
|
+
const attachments = useBrandlabContactAttachments({ ...options.attachments, policy });
|
|
103
|
+
const attachmentPrefix = options.attachmentFieldPrefix ?? 'attachments';
|
|
104
|
+
const fieldNames = useMemo(() => new Set(contract.fields.map((field) => field.key)), [contract]);
|
|
105
|
+
const roles = useMemo(() => ({ fields: contract.fields.map((field) => ({ name: field.key, role: field.role })) }), [contract]);
|
|
106
|
+
const { analytics, pageLabel, contactEndpoint, fetcher, adUserDataConsent } = options;
|
|
107
|
+
const controller = useMemo(() => {
|
|
108
|
+
const lead = leadBindingFor(analytics);
|
|
109
|
+
return createContactHttpSubmissionController({
|
|
110
|
+
pagePath,
|
|
111
|
+
...(pageLabel ? { pageLabel } : {}),
|
|
112
|
+
...(contactEndpoint ? { contactEndpoint } : {}),
|
|
113
|
+
...(fetcher ? { fetcher } : {}),
|
|
114
|
+
...(lead
|
|
115
|
+
? {
|
|
116
|
+
reportAccepted: (result, formData) => {
|
|
117
|
+
reportAcceptedLead(lead, result, {
|
|
118
|
+
pagePath: rawPagePath,
|
|
119
|
+
formData,
|
|
120
|
+
fieldRoles: roles,
|
|
121
|
+
adUserDataConsent: adUserDataConsent === true,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
: {}),
|
|
126
|
+
onStateChange: setState,
|
|
127
|
+
});
|
|
128
|
+
}, [adUserDataConsent, analytics, contactEndpoint, fetcher, pageLabel, pagePath, rawPagePath, roles]);
|
|
129
|
+
useEffect(() => () => clearTimeout(redirectTimerRef.current), []);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (state.phase !== 'error')
|
|
132
|
+
return;
|
|
133
|
+
const invalid = formRef.current?.querySelector(FOCUSABLE_INVALID_FIELD);
|
|
134
|
+
(invalid ?? statusRef.current)?.focus();
|
|
135
|
+
}, [state]);
|
|
136
|
+
const trackStart = useCallback(() => {
|
|
137
|
+
if (startedRef.current)
|
|
138
|
+
return;
|
|
139
|
+
startedRef.current = true;
|
|
140
|
+
latest.current.analytics?.trackLeadFormStart({ pagePath: rawPagePath, formId: diagnosticsFormId });
|
|
141
|
+
}, [diagnosticsFormId, rawPagePath]);
|
|
142
|
+
const handleResult = useCallback((result) => {
|
|
143
|
+
const current = latest.current;
|
|
144
|
+
current.onSettled?.(result);
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
current.analytics?.trackLeadFormError({
|
|
147
|
+
pagePath: rawPagePath,
|
|
148
|
+
formId: diagnosticsFormId,
|
|
149
|
+
stage: errorStage(result),
|
|
150
|
+
errorCount: result.code === 'validation'
|
|
151
|
+
? Math.min(50, Math.max(1, Object.keys(result.fieldErrors ?? {}).length))
|
|
152
|
+
: 1,
|
|
153
|
+
});
|
|
154
|
+
current.onFailed?.(result);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!result.accepted && current.silentAcceptance === 'ignore')
|
|
158
|
+
return;
|
|
159
|
+
formRef.current?.reset();
|
|
160
|
+
attachments.clearImages();
|
|
161
|
+
current.onAccepted?.(result);
|
|
162
|
+
const redirectTo = current.redirectTo;
|
|
163
|
+
if (!redirectTo)
|
|
164
|
+
return;
|
|
165
|
+
const navigate = current.navigate ?? ((href) => window.location.assign(href));
|
|
166
|
+
const delay = current.redirectDelayMs ?? DEFAULT_REDIRECT_DELAY_MS;
|
|
167
|
+
clearTimeout(redirectTimerRef.current);
|
|
168
|
+
if (delay === 0) {
|
|
169
|
+
navigate(redirectTo);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
redirectTimerRef.current = setTimeout(() => navigate(redirectTo), delay);
|
|
173
|
+
}, [attachments, diagnosticsFormId, rawPagePath]);
|
|
174
|
+
const send = useCallback(async (attempt) => {
|
|
175
|
+
lockRef.current = true;
|
|
176
|
+
try {
|
|
177
|
+
const result = await attempt();
|
|
178
|
+
handleResult(result);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
lockRef.current = false;
|
|
183
|
+
}
|
|
184
|
+
}, [handleResult]);
|
|
185
|
+
const onSubmit = useCallback((event) => {
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
if (lockRef.current || attachments.isProcessing)
|
|
188
|
+
return;
|
|
189
|
+
const form = event.currentTarget;
|
|
190
|
+
const formData = new FormData(form);
|
|
191
|
+
if (latest.current.onSubmitStart?.({ form, formData }) === false)
|
|
192
|
+
return;
|
|
193
|
+
trackStart();
|
|
194
|
+
attachments.clearErrorMessage();
|
|
195
|
+
attachments.images.forEach((image, index) => {
|
|
196
|
+
formData.append(`${attachmentPrefix}[${index}]`, image.file);
|
|
197
|
+
});
|
|
198
|
+
// The controller reports every failure as a result; nothing is left to handle here.
|
|
199
|
+
send(() => controller.submit(formData)).catch(() => undefined);
|
|
200
|
+
}, [attachmentPrefix, attachments, controller, send, trackStart]);
|
|
201
|
+
const retry = useCallback(async () => {
|
|
202
|
+
if (lockRef.current || !controller.getState().canRetry)
|
|
203
|
+
return undefined;
|
|
204
|
+
return send(() => controller.retry());
|
|
205
|
+
}, [controller, send]);
|
|
206
|
+
const fieldErrors = state.result && !state.result.ok && state.result.code === 'validation'
|
|
207
|
+
? state.result.fieldErrors ?? {}
|
|
208
|
+
: {};
|
|
209
|
+
const submitDisabled = !hydrated || state.pending || attachments.isProcessing;
|
|
210
|
+
return {
|
|
211
|
+
formId,
|
|
212
|
+
formProps: {
|
|
213
|
+
...(options.formId ? { id: options.formId } : {}),
|
|
214
|
+
ref: formRef,
|
|
215
|
+
method: 'post',
|
|
216
|
+
'aria-busy': state.pending || attachments.isProcessing,
|
|
217
|
+
onSubmit,
|
|
218
|
+
onFocusCapture: trackStart,
|
|
219
|
+
},
|
|
220
|
+
submitProps: { type: 'submit', disabled: submitDisabled },
|
|
221
|
+
hydrated,
|
|
222
|
+
submitDisabled,
|
|
223
|
+
pending: state.pending,
|
|
224
|
+
phase: state.phase,
|
|
225
|
+
result: state.result,
|
|
226
|
+
succeeded: state.result?.ok === true,
|
|
227
|
+
canRetry: state.canRetry,
|
|
228
|
+
fieldErrors,
|
|
229
|
+
fieldError: (name) => fieldErrors[name]?.[0],
|
|
230
|
+
errorId: (name) => `${formId}-${name}-error`,
|
|
231
|
+
statusRef,
|
|
232
|
+
retry,
|
|
233
|
+
attachments,
|
|
234
|
+
fieldNames,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Props for one uncontrolled contract field: name, disabled while sending and
|
|
239
|
+
* the error relationship. It never adds value, required or length attributes,
|
|
240
|
+
* so the form's existing validation is unchanged.
|
|
241
|
+
*/
|
|
242
|
+
export function contactFieldProps(form, name, options = {}) {
|
|
243
|
+
if (!form.fieldNames.has(name)) {
|
|
244
|
+
throw new Error(`"${name}" is not a field in the contact contract.`);
|
|
245
|
+
}
|
|
246
|
+
const error = form.fieldError(name);
|
|
247
|
+
const describedBy = [options.describedBy, error ? form.errorId(name) : undefined]
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.join(' ');
|
|
250
|
+
return {
|
|
251
|
+
name,
|
|
252
|
+
disabled: options.disabled ?? form.pending,
|
|
253
|
+
'aria-invalid': error ? true : undefined,
|
|
254
|
+
'aria-describedby': describedBy || undefined,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=form-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-client.js","sourceRoot":"","sources":["../../src/contact/form-client.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n DOCUMENT_CONTACT_ATTACHMENT_POLICY,\n defineContactContract,\n type BrandlabContactContract,\n type ContactActionResult,\n} from '@brandlab-digital/site-core/contact'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type FormEvent,\n type RefObject,\n} from 'react'\nimport type { BrandlabAnalytics, LeadFormErrorStage } from '../analytics.js'\nimport { leadBindingFor, safeSlug } from '../lead-registry.js'\nimport { reportAcceptedLead } from '../lead-reporting.js'\nimport {\n useBrandlabContactAttachments,\n type UseBrandlabContactAttachmentsOptions,\n type UseBrandlabContactAttachmentsResult,\n} from './attachments-client.js'\nimport {\n createContactHttpSubmissionController,\n type ContactSubmissionPhase,\n type ContactSubmissionState,\n} from './client.js'\n\n/** An accepted enquiry, or a silently accepted spam-protected submission (`accepted: false`). */\nexport type BrandlabContactAcceptedResult = Extract<ContactActionResult, { ok: true }>\nexport type BrandlabContactFailedResult = Extract<ContactActionResult, { ok: false }>\n\nexport interface BrandlabContactSubmitStart {\n form: HTMLFormElement\n /**\n * The fields about to be sent, read from the form. Add or replace entries\n * here (for example `service_interest`); prepared photos are appended after.\n */\n formData: FormData\n}\n\nexport interface UseBrandlabContactFormOptions {\n /** The contact contract from src/config/contact.ts. */\n contract: BrandlabContactContract\n /** The site's defineBrandlabAnalytics binding: the declared lead after each accepted enquiry, plus lead diagnostics. */\n analytics?: BrandlabAnalytics\n /** The current pathname, for example `usePathname()`. */\n pagePath: string\n /** Reviewed label for the enquiry email's page row. */\n pageLabel?: string\n /**\n * The form element's id and the lead diagnostics' form_id. Without it the form\n * gets no id (the hook never takes a React id, so the form's own ids are\n * unchanged), diagnostics use `contact-form` and error ids start `contact-form-`.\n * The element keeps the id exactly as given; diagnostics report it as a slug\n * (React `useId()` parts removed, see `contactFormSlug`).\n */\n formId?: string\n /** Page to open after an accepted enquiry; `null` or omitted keeps the visitor on the page. */\n redirectTo?: string | null\n /** Client navigation such as `router.push`; defaults to `window.location.assign`. */\n navigate?: (href: string) => void\n /** Delay before the redirect, in milliseconds (default 1500); 0 navigates at once. */\n redirectDelayMs?: number\n /**\n * Runs when the visitor submits, before anything is sent: clear the form's\n * own notices, adjust `formData`, or return `false` to cancel (for example\n * after `form.reportValidity()`). An unchanged resubmission runs it too;\n * `retry()` does not.\n */\n onSubmitStart?: (submission: BrandlabContactSubmitStart) => void | false\n /**\n * Runs after every attempt, before onAccepted or onFailed: an accepted\n * enquiry, a silently accepted submission (even when ignored) or a failure.\n */\n onSettled?: (result: ContactActionResult) => void\n /** Runs after an accepted enquiry, once the form has been reset and before any redirect. */\n onAccepted?: (result: BrandlabContactAcceptedResult) => void\n /** Runs after a failed attempt; typed values and prepared photos are kept. */\n onFailed?: (result: BrandlabContactFailedResult) => void\n /**\n * A silently accepted submission (spam protection): `complete` (default)\n * resets, runs onAccepted and redirects like an accepted enquiry; `ignore`\n * leaves the form as it is and runs only onSettled.\n */\n silentAcceptance?: 'complete' | 'ignore'\n /** Photo preparation options; the policy follows the contract's attachment mode by default. */\n attachments?: UseBrandlabContactAttachmentsOptions\n /** Multipart field prefix for prepared files (default `attachments`, sent as `attachments[0]` …). */\n attachmentFieldPrefix?: string\n /** Live Consent Mode decision for ad_user_data; Enhanced Conversions stay off without it. */\n adUserDataConsent?: boolean\n contactEndpoint?: string\n fetcher?: typeof fetch\n}\n\n/**\n * Spread onto the `<form>`: `method=\"post\"` with no `action` or `encType`, so a\n * submission before hydration posts back to the page and never puts fields in\n * a URL; the hook submits through fetch. `id` only when `formId` is given.\n */\nexport interface BrandlabContactFormProps {\n id?: string\n ref: RefObject<HTMLFormElement | null>\n method: 'post'\n 'aria-busy': boolean\n onSubmit: (event: FormEvent<HTMLFormElement>) => void\n onFocusCapture: () => void\n}\n\n/** Spread onto the submit button: disabled until hydration, while sending or preparing photos. */\nexport interface BrandlabContactSubmitProps {\n type: 'submit'\n disabled: boolean\n}\n\nexport interface UseBrandlabContactFormResult {\n /** The `formId` option, or `contact-form` when none was given. */\n formId: string\n formProps: BrandlabContactFormProps\n submitProps: BrandlabContactSubmitProps\n /** False during server rendering and hydration. */\n hydrated: boolean\n /** The submit button's `disabled` (also in submitProps): until hydration, while sending or preparing photos. */\n submitDisabled: boolean\n pending: boolean\n phase: ContactSubmissionPhase\n result: ContactActionResult | undefined\n /** An accepted enquiry (a silently accepted spam-protected submission looks the same). */\n succeeded: boolean\n /** A retryable failure: retry(), or submitting the same fields again, reuses its submission ID. */\n canRetry: boolean\n fieldErrors: Readonly<Record<string, readonly string[]>>\n fieldError(name: string): string | undefined\n /** Id for a field's error message element: `${formId}-${name}-error`. */\n errorId(name: string): string\n /** Attach to the status element; it receives focus when an error has no invalid field. */\n statusRef: RefObject<HTMLElement | null>\n /** Resends the failed enquiry with the same submission ID. */\n retry(): Promise<ContactActionResult | undefined>\n attachments: UseBrandlabContactAttachmentsResult\n /** Contract field keys accepted by contactFieldProps. */\n fieldNames: ReadonlySet<string>\n}\n\nconst INITIAL_STATE: ContactSubmissionState = {\n phase: 'idle',\n pending: false,\n canRetry: false,\n}\nconst DEFAULT_REDIRECT_DELAY_MS = 1_500\nconst FOCUSABLE_INVALID_FIELD = [\n 'input[aria-invalid=\"true\"]:not([disabled]):not([type=\"hidden\"])',\n 'select[aria-invalid=\"true\"]:not([disabled])',\n 'textarea[aria-invalid=\"true\"]:not([disabled])',\n 'button[aria-invalid=\"true\"]:not([disabled])',\n '[aria-invalid=\"true\"][tabindex]:not([aria-disabled=\"true\"])',\n].join(',')\n\nconst subscribeToNothing = () => () => undefined\nconst getClientSnapshot = () => true\nconst getServerSnapshot = () => false\n\n/** React `useId()` values: `_R_1a_`/`_r_1a_` (19.2), `«r1a»` (19.0 and 19.1), `:r1a:` (18). */\nconst REACT_ID = /_[rR]_[0-9A-Za-z]*_|«[rR][0-9A-Za-z]*»|:[rR][0-9A-Za-z]*:/g\n\n/**\n * The lead diagnostics' form_id for any formId. A valid slug is kept (trimmed and\n * lowercased). Otherwise React `useId()` parts are removed, so an id made unique for\n * the page still reports as one stable form, every run of other characters becomes\n * one hyphen, and the result is at most 80 characters; `contact-form` when nothing\n * is left. Without this, a formId such as `contact-form-_R_5e35f9db_` silently\n * dropped lead_form_start and lead_form_error.\n */\nexport function contactFormSlug(formId: string): string {\n const slug = safeSlug(String(formId))\n if (slug) return slug\n const derived = String(formId)\n .replace(REACT_ID, '-')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+/, '')\n .slice(0, 80)\n .replace(/-+$/, '')\n return derived || 'contact-form'\n}\n\nfunction safePagePath(pagePath: string) {\n try {\n return normalizeBrandlabRoutePath(pagePath.split(/[?#]/, 1)[0] || '/')\n } catch {\n return '/'\n }\n}\n\nfunction errorStage(result: BrandlabContactFailedResult): LeadFormErrorStage {\n return result.code === 'validation' ? 'validation' : 'delivery'\n}\n\nfunction assertLifecycleOptions(options: UseBrandlabContactFormOptions) {\n const delay = options.redirectDelayMs\n if (delay !== undefined && (!Number.isInteger(delay) || delay < 0 || delay > 60_000)) {\n throw new TypeError('redirectDelayMs must be a whole number of milliseconds from 0 to 60000.')\n }\n if (options.silentAcceptance !== undefined && !['complete', 'ignore'].includes(options.silentAcceptance)) {\n throw new TypeError('silentAcceptance must be \"complete\" or \"ignore\".')\n }\n if (options.analytics !== undefined && !leadBindingFor(options.analytics)) {\n throw new TypeError('analytics must be the binding returned by defineBrandlabAnalytics.')\n }\n}\n\n/**\n * Contact form behaviour for project-owned markup and copy. The form posts\n * through fetch with FormData; before hydration the submit button stays\n * disabled. Typed values (uncontrolled fields) and prepared photos are kept\n * after any failure, retry() and an unchanged resubmission reuse the same\n * submission ID (so a resend cannot duplicate the email), the first invalid\n * field is focused, and an accepted enquiry resets the form and redirects.\n * The lead is reported exactly as the site's defineBrandlabAnalytics binding\n * declares, once per accepted enquiry. Browser validation is left exactly as\n * the form's own markup declares it.\n */\nexport function useBrandlabContactForm(options: UseBrandlabContactFormOptions): UseBrandlabContactFormResult {\n const contract = useMemo(() => defineContactContract(options.contract), [options.contract])\n assertLifecycleOptions(options)\n const hydrated = useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot)\n const formId = options.formId ?? 'contact-form'\n const diagnosticsFormId = contactFormSlug(formId)\n const rawPagePath = options.pagePath\n const pagePath = safePagePath(rawPagePath)\n const formRef = useRef<HTMLFormElement>(null)\n const statusRef = useRef<HTMLElement>(null)\n const [state, setState] = useState<ContactSubmissionState>(INITIAL_STATE)\n const lockRef = useRef(false)\n const startedRef = useRef(false)\n const redirectTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const latest = useRef(options)\n latest.current = options\n\n const policy = options.attachments?.policy ??\n (contract.attachments.mode === 'images-and-pdfs-optional'\n ? DOCUMENT_CONTACT_ATTACHMENT_POLICY\n : DEFAULT_CONTACT_ATTACHMENT_POLICY)\n const attachments = useBrandlabContactAttachments({ ...options.attachments, policy })\n const attachmentPrefix = options.attachmentFieldPrefix ?? 'attachments'\n\n const fieldNames = useMemo(() => new Set(contract.fields.map((field) => field.key)), [contract])\n const roles = useMemo(\n () => ({ fields: contract.fields.map((field) => ({ name: field.key, role: field.role })) }),\n [contract],\n )\n const { analytics, pageLabel, contactEndpoint, fetcher, adUserDataConsent } = options\n const controller = useMemo(() => {\n const lead = leadBindingFor(analytics)\n return createContactHttpSubmissionController({\n pagePath,\n ...(pageLabel ? { pageLabel } : {}),\n ...(contactEndpoint ? { contactEndpoint } : {}),\n ...(fetcher ? { fetcher } : {}),\n ...(lead\n ? {\n reportAccepted: (result, formData) => {\n reportAcceptedLead(lead, result, {\n pagePath: rawPagePath,\n formData,\n fieldRoles: roles,\n adUserDataConsent: adUserDataConsent === true,\n })\n },\n }\n : {}),\n onStateChange: setState,\n })\n }, [adUserDataConsent, analytics, contactEndpoint, fetcher, pageLabel, pagePath, rawPagePath, roles])\n\n useEffect(() => () => clearTimeout(redirectTimerRef.current), [])\n\n useEffect(() => {\n if (state.phase !== 'error') return\n const invalid = formRef.current?.querySelector<HTMLElement>(FOCUSABLE_INVALID_FIELD)\n ;(invalid ?? statusRef.current)?.focus()\n }, [state])\n\n const trackStart = useCallback(() => {\n if (startedRef.current) return\n startedRef.current = true\n latest.current.analytics?.trackLeadFormStart({ pagePath: rawPagePath, formId: diagnosticsFormId })\n }, [diagnosticsFormId, rawPagePath])\n\n const handleResult = useCallback(\n (result: ContactActionResult) => {\n const current = latest.current\n current.onSettled?.(result)\n if (!result.ok) {\n current.analytics?.trackLeadFormError({\n pagePath: rawPagePath,\n formId: diagnosticsFormId,\n stage: errorStage(result),\n errorCount: result.code === 'validation'\n ? Math.min(50, Math.max(1, Object.keys(result.fieldErrors ?? {}).length))\n : 1,\n })\n current.onFailed?.(result)\n return\n }\n if (!result.accepted && current.silentAcceptance === 'ignore') return\n formRef.current?.reset()\n attachments.clearImages()\n current.onAccepted?.(result)\n const redirectTo = current.redirectTo\n if (!redirectTo) return\n const navigate = current.navigate ?? ((href: string) => window.location.assign(href))\n const delay = current.redirectDelayMs ?? DEFAULT_REDIRECT_DELAY_MS\n clearTimeout(redirectTimerRef.current)\n if (delay === 0) {\n navigate(redirectTo)\n return\n }\n redirectTimerRef.current = setTimeout(() => navigate(redirectTo), delay)\n },\n [attachments, diagnosticsFormId, rawPagePath],\n )\n\n const send = useCallback(\n async (attempt: () => Promise<ContactActionResult>) => {\n lockRef.current = true\n try {\n const result = await attempt()\n handleResult(result)\n return result\n } finally {\n lockRef.current = false\n }\n },\n [handleResult],\n )\n\n const onSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (lockRef.current || attachments.isProcessing) return\n const form = event.currentTarget\n const formData = new FormData(form)\n if (latest.current.onSubmitStart?.({ form, formData }) === false) return\n trackStart()\n attachments.clearErrorMessage()\n attachments.images.forEach((image, index) => {\n formData.append(`${attachmentPrefix}[${index}]`, image.file)\n })\n // The controller reports every failure as a result; nothing is left to handle here.\n send(() => controller.submit(formData)).catch(() => undefined)\n },\n [attachmentPrefix, attachments, controller, send, trackStart],\n )\n\n const retry = useCallback(async () => {\n if (lockRef.current || !controller.getState().canRetry) return undefined\n return send(() => controller.retry())\n }, [controller, send])\n\n const fieldErrors = state.result && !state.result.ok && state.result.code === 'validation'\n ? state.result.fieldErrors ?? {}\n : {}\n\n const submitDisabled = !hydrated || state.pending || attachments.isProcessing\n return {\n formId,\n formProps: {\n ...(options.formId ? { id: options.formId } : {}),\n ref: formRef,\n method: 'post',\n 'aria-busy': state.pending || attachments.isProcessing,\n onSubmit,\n onFocusCapture: trackStart,\n },\n submitProps: { type: 'submit', disabled: submitDisabled },\n hydrated,\n submitDisabled,\n pending: state.pending,\n phase: state.phase,\n result: state.result,\n succeeded: state.result?.ok === true,\n canRetry: state.canRetry,\n fieldErrors,\n fieldError: (name: string) => fieldErrors[name]?.[0],\n errorId: (name: string) => `${formId}-${name}-error`,\n statusRef,\n retry,\n attachments,\n fieldNames,\n }\n}\n\nexport interface ContactFieldPropsOptions {\n /** Ids of help text already describing the field; the error id is appended. */\n describedBy?: string\n /** Defaults to disabled while an enquiry is sending. */\n disabled?: boolean\n}\n\n/**\n * Props for one uncontrolled contract field: name, disabled while sending and\n * the error relationship. It never adds value, required or length attributes,\n * so the form's existing validation is unchanged.\n */\nexport function contactFieldProps(\n form: UseBrandlabContactFormResult,\n name: string,\n options: ContactFieldPropsOptions = {},\n) {\n if (!form.fieldNames.has(name)) {\n throw new Error(`\"${name}\" is not a field in the contact contract.`)\n }\n const error = form.fieldError(name)\n const describedBy = [options.describedBy, error ? form.errorId(name) : undefined]\n .filter(Boolean)\n .join(' ')\n return {\n name,\n disabled: options.disabled ?? form.pending,\n 'aria-invalid': error ? (true as const) : undefined,\n 'aria-describedby': describedBy || undefined,\n }\n}\n"]}
|
package/dist/contact/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './shared.js';
|
|
2
2
|
export * from './contract.js';
|
|
3
|
-
export {
|
|
3
|
+
export { normalizeContactUploadFile, normalizeContactConvertedUploadFile, parseContactActionResult, type ContactSubmissionPhase, } from './client.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,0BAA0B,EAC1B,mCAAmC,EACnC,wBAAwB,EACxB,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAA"}
|
package/dist/contact/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './shared.js';
|
|
2
2
|
export * from './contract.js';
|
|
3
|
-
export {
|
|
3
|
+
export { normalizeContactUploadFile, normalizeContactConvertedUploadFile, parseContactActionResult, } from './client.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|