@brandlab-digital/site-next 3.9.0 → 3.11.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.
Files changed (143) hide show
  1. package/README.md +33 -0
  2. package/dist/contact/attachments-client.d.ts +3 -0
  3. package/dist/contact/attachments-client.d.ts.map +1 -1
  4. package/dist/contact/attachments-client.js +31 -8
  5. package/dist/contact/attachments-client.js.map +1 -1
  6. package/dist/contact/client-entry.d.ts +1 -1
  7. package/dist/contact/client-entry.d.ts.map +1 -1
  8. package/dist/contact/client-entry.js.map +1 -1
  9. package/dist/contact/contract.d.ts.map +1 -1
  10. package/dist/contact/contract.js +2 -2
  11. package/dist/contact/contract.js.map +1 -1
  12. package/dist/contact/email.d.ts.map +1 -1
  13. package/dist/contact/email.js +9 -0
  14. package/dist/contact/email.js.map +1 -1
  15. package/dist/contact/form-client.d.ts +4 -2
  16. package/dist/contact/form-client.d.ts.map +1 -1
  17. package/dist/contact/form-client.js +49 -13
  18. package/dist/contact/form-client.js.map +1 -1
  19. package/dist/contact/rate-limit.d.ts +59 -0
  20. package/dist/contact/rate-limit.d.ts.map +1 -0
  21. package/dist/contact/rate-limit.js +109 -0
  22. package/dist/contact/rate-limit.js.map +1 -0
  23. package/dist/contact/route-handler.d.ts +6 -0
  24. package/dist/contact/route-handler.d.ts.map +1 -1
  25. package/dist/contact/route-handler.js +4 -0
  26. package/dist/contact/route-handler.js.map +1 -1
  27. package/dist/contact/server-entry.d.ts +1 -1
  28. package/dist/contact/server-entry.d.ts.map +1 -1
  29. package/dist/contact/server-entry.js +1 -1
  30. package/dist/contact/server-entry.js.map +1 -1
  31. package/dist/contact/server.d.ts +9 -0
  32. package/dist/contact/server.d.ts.map +1 -1
  33. package/dist/contact/server.js +71 -7
  34. package/dist/contact/server.js.map +1 -1
  35. package/dist/contact/shared.d.ts +11 -0
  36. package/dist/contact/shared.d.ts.map +1 -1
  37. package/dist/contact/shared.js +11 -0
  38. package/dist/contact/shared.js.map +1 -1
  39. package/dist/contact/testing.d.ts +5 -0
  40. package/dist/contact/testing.d.ts.map +1 -1
  41. package/dist/contact/testing.js +1 -0
  42. package/dist/contact/testing.js.map +1 -1
  43. package/dist/lead-reporting.d.ts +6 -0
  44. package/dist/lead-reporting.d.ts.map +1 -1
  45. package/dist/lead-reporting.js +13 -1
  46. package/dist/lead-reporting.js.map +1 -1
  47. package/dist/ui-kit/brandlab-skin.d.ts +34 -0
  48. package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
  49. package/dist/ui-kit/brandlab-skin.js.map +1 -1
  50. package/dist/ui-kit/form/copy.d.ts +3 -0
  51. package/dist/ui-kit/form/copy.d.ts.map +1 -0
  52. package/dist/ui-kit/form/copy.js +5 -0
  53. package/dist/ui-kit/form/copy.js.map +1 -0
  54. package/dist/ui-kit/form/dialog.d.ts +24 -0
  55. package/dist/ui-kit/form/dialog.d.ts.map +1 -0
  56. package/dist/ui-kit/form/dialog.js +17 -0
  57. package/dist/ui-kit/form/dialog.js.map +1 -0
  58. package/dist/ui-kit/form/field.d.ts +30 -0
  59. package/dist/ui-kit/form/field.d.ts.map +1 -0
  60. package/dist/ui-kit/form/field.js +11 -0
  61. package/dist/ui-kit/form/field.js.map +1 -0
  62. package/dist/ui-kit/form/index.d.ts +7 -0
  63. package/dist/ui-kit/form/index.d.ts.map +1 -0
  64. package/dist/ui-kit/form/index.js +7 -0
  65. package/dist/ui-kit/form/index.js.map +1 -0
  66. package/dist/ui-kit/form/photo-upload.d.ts +37 -0
  67. package/dist/ui-kit/form/photo-upload.d.ts.map +1 -0
  68. package/dist/ui-kit/form/photo-upload.js +19 -0
  69. package/dist/ui-kit/form/photo-upload.js.map +1 -0
  70. package/dist/ui-kit/form/quote-dialog.d.ts +15 -0
  71. package/dist/ui-kit/form/quote-dialog.d.ts.map +1 -0
  72. package/dist/ui-kit/form/quote-dialog.js +68 -0
  73. package/dist/ui-kit/form/quote-dialog.js.map +1 -0
  74. package/dist/ui-kit/form/quote-form.d.ts +165 -0
  75. package/dist/ui-kit/form/quote-form.d.ts.map +1 -0
  76. package/dist/ui-kit/form/quote-form.js +235 -0
  77. package/dist/ui-kit/form/quote-form.js.map +1 -0
  78. package/dist/ui-kit/form/thank-you.d.ts +37 -0
  79. package/dist/ui-kit/form/thank-you.d.ts.map +1 -0
  80. package/dist/ui-kit/form/thank-you.js +36 -0
  81. package/dist/ui-kit/form/thank-you.js.map +1 -0
  82. package/dist/ui-kit/sections/accordion.d.ts +25 -0
  83. package/dist/ui-kit/sections/accordion.d.ts.map +1 -0
  84. package/dist/ui-kit/sections/accordion.js +14 -0
  85. package/dist/ui-kit/sections/accordion.js.map +1 -0
  86. package/dist/ui-kit/sections/contact-block.d.ts +51 -0
  87. package/dist/ui-kit/sections/contact-block.d.ts.map +1 -0
  88. package/dist/ui-kit/sections/contact-block.js +15 -0
  89. package/dist/ui-kit/sections/contact-block.js.map +1 -0
  90. package/dist/ui-kit/sections/cta-band.d.ts +59 -0
  91. package/dist/ui-kit/sections/cta-band.d.ts.map +1 -0
  92. package/dist/ui-kit/sections/cta-band.js +11 -0
  93. package/dist/ui-kit/sections/cta-band.js.map +1 -0
  94. package/dist/ui-kit/sections/faqs.d.ts +43 -0
  95. package/dist/ui-kit/sections/faqs.d.ts.map +1 -0
  96. package/dist/ui-kit/sections/faqs.js +40 -0
  97. package/dist/ui-kit/sections/faqs.js.map +1 -0
  98. package/dist/ui-kit/sections/index.d.ts +13 -0
  99. package/dist/ui-kit/sections/index.d.ts.map +1 -0
  100. package/dist/ui-kit/sections/index.js +13 -0
  101. package/dist/ui-kit/sections/index.js.map +1 -0
  102. package/dist/ui-kit/sections/page-header.d.ts +29 -0
  103. package/dist/ui-kit/sections/page-header.d.ts.map +1 -0
  104. package/dist/ui-kit/sections/page-header.js +9 -0
  105. package/dist/ui-kit/sections/page-header.js.map +1 -0
  106. package/dist/ui-kit/sections/price-guide.d.ts +56 -0
  107. package/dist/ui-kit/sections/price-guide.d.ts.map +1 -0
  108. package/dist/ui-kit/sections/price-guide.js +28 -0
  109. package/dist/ui-kit/sections/price-guide.js.map +1 -0
  110. package/dist/ui-kit/sections/price-table.d.ts +44 -0
  111. package/dist/ui-kit/sections/price-table.d.ts.map +1 -0
  112. package/dist/ui-kit/sections/price-table.js +29 -0
  113. package/dist/ui-kit/sections/price-table.js.map +1 -0
  114. package/dist/ui-kit/sections/proof-strip.d.ts +57 -0
  115. package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -0
  116. package/dist/ui-kit/sections/proof-strip.js +20 -0
  117. package/dist/ui-kit/sections/proof-strip.js.map +1 -0
  118. package/dist/ui-kit/sections/rating-chip.d.ts +25 -0
  119. package/dist/ui-kit/sections/rating-chip.d.ts.map +1 -0
  120. package/dist/ui-kit/sections/rating-chip.js +15 -0
  121. package/dist/ui-kit/sections/rating-chip.js.map +1 -0
  122. package/dist/ui-kit/sections/reviews-band.d.ts +110 -0
  123. package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -0
  124. package/dist/ui-kit/sections/reviews-band.js +104 -0
  125. package/dist/ui-kit/sections/reviews-band.js.map +1 -0
  126. package/dist/ui-kit/sections/section-sequence.d.ts +39 -0
  127. package/dist/ui-kit/sections/section-sequence.d.ts.map +1 -0
  128. package/dist/ui-kit/sections/section-sequence.js +34 -0
  129. package/dist/ui-kit/sections/section-sequence.js.map +1 -0
  130. package/dist/ui-kit/sections/site-footer-columns.d.ts +12 -0
  131. package/dist/ui-kit/sections/site-footer-columns.d.ts.map +1 -0
  132. package/dist/ui-kit/sections/site-footer-columns.js +63 -0
  133. package/dist/ui-kit/sections/site-footer-columns.js.map +1 -0
  134. package/dist/ui-kit/sections/site-footer.d.ts +59 -0
  135. package/dist/ui-kit/sections/site-footer.d.ts.map +1 -0
  136. package/dist/ui-kit/sections/site-footer.js +41 -0
  137. package/dist/ui-kit/sections/site-footer.js.map +1 -0
  138. package/dist/ui-kit/structure.css +766 -0
  139. package/dist/ui.d.ts +10 -1
  140. package/dist/ui.d.ts.map +1 -1
  141. package/dist/ui.js +3 -3
  142. package/dist/ui.js.map +1 -1
  143. package/package.json +10 -2
package/README.md CHANGED
@@ -1,3 +1,36 @@
1
1
  # `@brandlab-digital/site-next`
2
2
 
3
3
  Proprietary software © Brandlab Digital Marketing. All rights reserved. Published for Brandlab client sites.
4
+
5
+ ## Contact route: abuse limits and the test marker (3.10.0)
6
+
7
+ `createBrandlabContactRouteHandler` limits distinct enquiries per client IP and per email address where enquiries are
8
+ delivered (production and Preview): 10 per IP in 10 minutes and 5 per address in an hour by default
9
+ (`BRANDLAB_CONTACT_RATE_LIMITS`), held in memory per server instance. A resend of the same enquiry never counts, an
10
+ enquiry that fails validation never counts, and a refused one gets a retryable `429` with `Retry-After` and is never
11
+ sent. Adjust or turn them off with `rateLimit: { perIp, perAddress } | false`.
12
+
13
+ The in-memory limiter is the first layer. The second is a Vercel Firewall rate-limit rule on the route, which holds
14
+ across every instance. Template (Vercel CLI, run in the linked project; check the project's Firewall tab for the
15
+ plan's rate-limit support first, and keep the limit above the route's own):
16
+
17
+ ```bash
18
+ vercel firewall rules add "Contact form rate limit" \
19
+ --condition '{"type":"path","op":"eq","value":"/api/contact"}' \
20
+ --condition '{"type":"method","op":"eq","value":"POST"}' \
21
+ --action rate_limit \
22
+ --rate-limit-window 600 \
23
+ --rate-limit-requests 20 \
24
+ --rate-limit-keys ip \
25
+ --rate-limit-action rate_limit --yes
26
+ ```
27
+
28
+ A Brandlab test enquiry (a launch proof or canary) opens the page with `?bl_test=1` (remembered for the tab; `?bl_test=0`
29
+ ends it). Each enquiry then carries `bl_test=1`: the server delivers it to the configured recipient with `[Test]` in
30
+ the subject, never writes the marker into the email and answers without a confirmation, so the browser never sends
31
+ `contact_form` or an Ads conversion for it, and a test visit sends no `lead_form_start` or `lead_form_error`. Canary
32
+ previews deliver only to the Brandlab test inbox.
33
+
34
+ Canary and launch-proof runs count against the per-address limit (5 per address per hour), so use plus-addressed test
35
+ inboxes, one per run or per site (`test+abloom-1@…`, `test+abloom-2@…`), and spread repeated runs across them. A refused
36
+ enquiry logs one line: `{"event":"brandlab_contact_rate_limited","rule":"address","retryAfterSeconds":1800}`.
@@ -10,11 +10,14 @@ export interface UseBrandlabContactAttachmentsOptions {
10
10
  createObjectUrl?: (file: File) => string;
11
11
  revokeObjectUrl?: (url: string) => void;
12
12
  }
13
+ /** Why the last photo choice was refused, so a form can show its own copy instead of errorMessage. */
14
+ export type BrandlabContactAttachmentErrorReason = 'type' | 'size' | 'total' | 'count' | 'prepare';
13
15
  export interface UseBrandlabContactAttachmentsResult {
14
16
  images: BrandlabContactAttachmentPreview[];
15
17
  isDragging: boolean;
16
18
  isProcessing: boolean;
17
19
  errorMessage: string | null;
20
+ errorReason: BrandlabContactAttachmentErrorReason | null;
18
21
  maxImages: number;
19
22
  addFiles: (files: readonly File[]) => Promise<void>;
20
23
  clearErrorMessage: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"attachments-client.d.ts","sourceRoot":"","sources":["../../src/contact/attachments-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAGd,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oCAAoC;IACnD,MAAM,CAAC,EAAE,uBAAuB,CAAA;IAChC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACxC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,mCAAmC;IAClD,MAAM,EAAE,gCAAgC,EAAE,CAAA;IAC1C,UAAU,EAAE,OAAO,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,iBAAiB,EAAE,MAAM,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,iBAAiB,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,eAAe,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IAC1C,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAID,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC5E;AAWD,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,oCAAyC,GACjD,mCAAmC,CAiJrC"}
1
+ {"version":3,"file":"attachments-client.d.ts","sourceRoot":"","sources":["../../src/contact/attachments-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAGd,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oCAAoC;IACnD,MAAM,CAAC,EAAE,uBAAuB,CAAA;IAChC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACxC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,sGAAsG;AACtG,MAAM,MAAM,oCAAoC,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAA;AAElG,MAAM,WAAW,mCAAmC;IAClD,MAAM,EAAE,gCAAgC,EAAE,CAAA;IAC1C,UAAU,EAAE,OAAO,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,EAAE,oCAAoC,GAAG,IAAI,CAAA;IACxD,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,iBAAiB,EAAE,MAAM,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,iBAAiB,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,eAAe,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IAC3C,cAAc,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;IAC1C,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAaD,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC5E;AAWD,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,oCAAyC,GACjD,mCAAmC,CAmKrC"}
@@ -3,6 +3,13 @@ import { DEFAULT_CONTACT_ATTACHMENT_POLICY, deriveContactAttachmentTransportPoli
3
3
  import { useCallback, useEffect, useRef, useState, } from 'react';
4
4
  import { normalizeContactConvertedUploadFile, normalizeContactUploadFile } from './client.js';
5
5
  const DEFAULT_COMPRESSED_IMAGE_SIZE_MB = 0.7;
6
+ function attachmentError(reason, message) {
7
+ return Object.assign(new Error(message), { reason });
8
+ }
9
+ function reasonOf(error) {
10
+ const reason = error?.reason;
11
+ return reason === 'type' || reason === 'size' || reason === 'total' || reason === 'count' ? reason : 'prepare';
12
+ }
6
13
  export async function compressBrandlabContactImage(file) {
7
14
  const contentType = resolveContactAttachmentType(file.name, file.type);
8
15
  const needsConversion = contentType === 'image/heic' || contentType === 'image/heif';
@@ -47,6 +54,7 @@ export function useBrandlabContactAttachments(options = {}) {
47
54
  const [isDragging, setIsDragging] = useState(false);
48
55
  const [isProcessing, setIsProcessing] = useState(false);
49
56
  const [errorMessage, setErrorMessage] = useState(null);
57
+ const [errorReason, setErrorReason] = useState(null);
50
58
  const imagesRef = useRef([]);
51
59
  const processingRef = useRef(false);
52
60
  const selectionSequenceRef = useRef(0);
@@ -65,32 +73,36 @@ export function useBrandlabContactAttachments(options = {}) {
65
73
  const availableSlots = policy.maxFiles - imagesRef.current.length;
66
74
  if (availableSlots <= 0) {
67
75
  setErrorMessage(maximumImagesMessage(policy));
76
+ setErrorReason('count');
68
77
  return;
69
78
  }
70
79
  const sequence = selectionSequenceRef.current;
71
80
  processingRef.current = true;
72
81
  setIsProcessing(true);
73
82
  setErrorMessage(null);
83
+ setErrorReason(null);
74
84
  const transportPolicy = deriveContactAttachmentTransportPolicy(policy);
75
85
  let firstError = null;
86
+ let firstReason = null;
76
87
  try {
77
88
  const selectedFiles = files.slice(0, availableSlots);
78
89
  for (const file of selectedFiles) {
79
90
  try {
80
91
  if (!resolveContactAttachmentType(file.name, file.type, policy)) {
81
- throw new Error(`Please upload ${contactAttachmentTypeLabel(policy)} images only.`);
92
+ throw attachmentError('type', `Please upload ${contactAttachmentTypeLabel(policy)} images only.`);
82
93
  }
83
94
  const processed = await compressImage(file);
84
95
  if (sequence !== selectionSequenceRef.current)
85
96
  return;
86
97
  const validation = validateContactAttachment({ filename: processed.name, size: processed.size, type: processed.type }, transportPolicy);
87
- if (!validation.ok)
88
- throw new Error(validation.message);
98
+ if (!validation.ok) {
99
+ throw attachmentError(processed.size > transportPolicy.maxFileSizeBytes ? 'size' : 'type', validation.message);
100
+ }
89
101
  const totalSize = imagesRef.current.reduce((total, image) => total + image.file.size, processed.size);
90
102
  if (totalSize > policy.maxTotalSizeBytes)
91
- throw new Error(maximumTotalSizeMessage(policy));
103
+ throw attachmentError('total', maximumTotalSizeMessage(policy));
92
104
  if (imagesRef.current.length >= policy.maxFiles)
93
- throw new Error(maximumImagesMessage(policy));
105
+ throw attachmentError('count', maximumImagesMessage(policy));
94
106
  const nextImages = [...imagesRef.current, {
95
107
  file: processed,
96
108
  preview: createObjectUrl(processed),
@@ -101,10 +113,15 @@ export function useBrandlabContactAttachments(options = {}) {
101
113
  catch (error) {
102
114
  if (sequence !== selectionSequenceRef.current)
103
115
  return;
104
- firstError ??= error instanceof Error ? error.message : 'We could not prepare this photo. Please try again.';
116
+ if (firstError === null) {
117
+ firstError = error instanceof Error ? error.message : 'We could not prepare this photo. Please try again.';
118
+ firstReason = reasonOf(error);
119
+ }
105
120
  }
106
121
  }
107
- setErrorMessage(firstError ?? (files.length > availableSlots ? maximumImagesMessage(policy) : null));
122
+ const overflow = files.length > availableSlots;
123
+ setErrorMessage(firstError ?? (overflow ? maximumImagesMessage(policy) : null));
124
+ setErrorReason(firstReason ?? (overflow ? 'count' : null));
108
125
  }
109
126
  finally {
110
127
  processingRef.current = false;
@@ -138,6 +155,7 @@ export function useBrandlabContactAttachments(options = {}) {
138
155
  imagesRef.current = nextImages;
139
156
  setImages(nextImages);
140
157
  setErrorMessage(null);
158
+ setErrorReason(null);
141
159
  }, [revokeObjectUrl]);
142
160
  const clearImages = useCallback(() => {
143
161
  selectionSequenceRef.current += 1;
@@ -145,15 +163,20 @@ export function useBrandlabContactAttachments(options = {}) {
145
163
  imagesRef.current = [];
146
164
  setImages([]);
147
165
  setErrorMessage(null);
166
+ setErrorReason(null);
148
167
  }, [revokeObjectUrl]);
149
168
  return {
150
169
  images,
151
170
  isDragging,
152
171
  isProcessing,
153
172
  errorMessage,
173
+ errorReason,
154
174
  maxImages: policy.maxFiles,
155
175
  addFiles,
156
- clearErrorMessage: () => setErrorMessage(null),
176
+ clearErrorMessage: () => {
177
+ setErrorMessage(null);
178
+ setErrorReason(null);
179
+ },
157
180
  clearImages,
158
181
  handleImageChange,
159
182
  handleDragLeave,
@@ -1 +1 @@
1
- {"version":3,"file":"attachments-client.js","sourceRoot":"","sources":["../../src/contact/attachments-client.ts"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n deriveContactAttachmentTransportPolicy,\n contactAttachmentTypeLabel,\n resolveContactAttachmentType,\n validateContactAttachment,\n type ContactAttachmentPolicy,\n} from '@brandlab-digital/site-core/contact'\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n} from 'react'\nimport { normalizeContactConvertedUploadFile, normalizeContactUploadFile } from './client.js'\n\nexport interface BrandlabContactAttachmentPreview {\n file: File\n preview: string\n}\n\nexport interface UseBrandlabContactAttachmentsOptions {\n policy?: ContactAttachmentPolicy\n compressImage?: (file: File) => Promise<File>\n createObjectUrl?: (file: File) => string\n revokeObjectUrl?: (url: string) => void\n}\n\nexport interface UseBrandlabContactAttachmentsResult {\n images: BrandlabContactAttachmentPreview[]\n isDragging: boolean\n isProcessing: boolean\n errorMessage: string | null\n maxImages: number\n addFiles: (files: readonly File[]) => Promise<void>\n clearErrorMessage: () => void\n clearImages: () => void\n handleImageChange: (event: ChangeEvent<HTMLInputElement>) => Promise<void>\n handleDragLeave: (event: DragEvent) => void\n handleDragOver: (event: DragEvent) => void\n handleDrop: (event: DragEvent) => Promise<void>\n removeImage: (index: number) => void\n}\n\nconst DEFAULT_COMPRESSED_IMAGE_SIZE_MB = 0.7\n\nexport async function compressBrandlabContactImage(file: File): Promise<File> {\n const contentType = resolveContactAttachmentType(file.name, file.type)\n const needsConversion = contentType === 'image/heic' || contentType === 'image/heif'\n try {\n if (!contentType) return file\n const imported = await import('browser-image-compression')\n const imageCompression = imported.default as unknown as (\n image: File,\n options: {\n maxSizeMB: number\n maxWidthOrHeight: number\n useWebWorker: boolean\n fileType: string\n initialQuality: number\n },\n ) => Promise<File>\n const processed = await imageCompression(file, {\n maxSizeMB: DEFAULT_COMPRESSED_IMAGE_SIZE_MB,\n maxWidthOrHeight: 1920,\n // Native browser image decoding is available on the main thread. Do not\n // ask the dependency's remote worker fallback to decode HEIC.\n useWebWorker: !needsConversion,\n fileType: needsConversion ? 'image/jpeg' : contentType,\n initialQuality: 0.7,\n })\n return needsConversion\n ? await normalizeContactConvertedUploadFile(file, processed)\n : normalizeContactUploadFile(file, processed)\n } catch {\n if (needsConversion) {\n throw new Error('We could not prepare this photo. Please try adding it again, choose another photo, or continue without it.')\n }\n return file\n }\n}\n\nfunction maximumImagesMessage(policy: ContactAttachmentPolicy) {\n return `You can add up to ${policy.maxFiles} images.`\n}\n\nfunction maximumTotalSizeMessage(policy: ContactAttachmentPolicy) {\n const maximumMegabytes = Math.round((policy.maxTotalSizeBytes / 1_000_000) * 10) / 10\n return `Your images are too large. Please use fewer images or keep the total under ${maximumMegabytes} MB.`\n}\n\nexport function useBrandlabContactAttachments(\n options: UseBrandlabContactAttachmentsOptions = {},\n): UseBrandlabContactAttachmentsResult {\n const policy = options.policy ?? DEFAULT_CONTACT_ATTACHMENT_POLICY\n const compressImage = options.compressImage ?? compressBrandlabContactImage\n const createObjectUrl = options.createObjectUrl ?? URL.createObjectURL\n const revokeObjectUrl = options.revokeObjectUrl ?? URL.revokeObjectURL\n const [images, setImages] = useState<BrandlabContactAttachmentPreview[]>([])\n const [isDragging, setIsDragging] = useState(false)\n const [isProcessing, setIsProcessing] = useState(false)\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n const imagesRef = useRef<BrandlabContactAttachmentPreview[]>([])\n const processingRef = useRef(false)\n const selectionSequenceRef = useRef(0)\n const revokeObjectUrlRef = useRef(revokeObjectUrl)\n\n imagesRef.current = images\n revokeObjectUrlRef.current = revokeObjectUrl\n\n useEffect(() => {\n return () => {\n selectionSequenceRef.current += 1\n imagesRef.current.forEach((image) => revokeObjectUrlRef.current(image.preview))\n }\n }, [])\n\n const addFiles = useCallback(\n async (files: readonly File[]) => {\n if (files.length === 0 || processingRef.current) return\n\n const availableSlots = policy.maxFiles - imagesRef.current.length\n if (availableSlots <= 0) {\n setErrorMessage(maximumImagesMessage(policy))\n return\n }\n\n const sequence = selectionSequenceRef.current\n processingRef.current = true\n setIsProcessing(true)\n setErrorMessage(null)\n const transportPolicy = deriveContactAttachmentTransportPolicy(policy)\n let firstError: string | null = null\n\n try {\n const selectedFiles = files.slice(0, availableSlots)\n for (const file of selectedFiles) {\n try {\n if (!resolveContactAttachmentType(file.name, file.type, policy)) {\n throw new Error(\n `Please upload ${contactAttachmentTypeLabel(policy)} images only.`,\n )\n }\n\n const processed = await compressImage(file)\n if (sequence !== selectionSequenceRef.current) return\n const validation = validateContactAttachment(\n { filename: processed.name, size: processed.size, type: processed.type },\n transportPolicy,\n )\n if (!validation.ok) throw new Error(validation.message)\n const totalSize = imagesRef.current.reduce((total, image) => total + image.file.size, processed.size)\n if (totalSize > policy.maxTotalSizeBytes) throw new Error(maximumTotalSizeMessage(policy))\n if (imagesRef.current.length >= policy.maxFiles) throw new Error(maximumImagesMessage(policy))\n const nextImages = [...imagesRef.current, {\n file: processed,\n preview: createObjectUrl(processed),\n }]\n imagesRef.current = nextImages\n setImages(nextImages)\n } catch (error) {\n if (sequence !== selectionSequenceRef.current) return\n firstError ??= error instanceof Error ? error.message : 'We could not prepare this photo. Please try again.'\n }\n }\n setErrorMessage(firstError ?? (files.length > availableSlots ? maximumImagesMessage(policy) : null))\n } finally {\n processingRef.current = false\n setIsProcessing(false)\n }\n },\n [compressImage, createObjectUrl, policy],\n )\n\n const handleImageChange = useCallback(\n async (event: ChangeEvent<HTMLInputElement>) => {\n const files = event.target.files ? Array.from(event.target.files) : []\n await addFiles(files)\n event.target.value = ''\n },\n [addFiles],\n )\n\n const handleDragOver = useCallback((event: DragEvent) => {\n event.preventDefault()\n setIsDragging(true)\n }, [])\n\n const handleDragLeave = useCallback((event: DragEvent) => {\n event.preventDefault()\n setIsDragging(false)\n }, [])\n\n const handleDrop = useCallback(\n async (event: DragEvent) => {\n event.preventDefault()\n setIsDragging(false)\n await addFiles(Array.from(event.dataTransfer.files))\n },\n [addFiles],\n )\n\n const removeImage = useCallback(\n (index: number) => {\n const removed = imagesRef.current[index]\n if (!removed) return\n revokeObjectUrl(removed.preview)\n const nextImages = imagesRef.current.filter((_, imageIndex) => imageIndex !== index)\n imagesRef.current = nextImages\n setImages(nextImages)\n setErrorMessage(null)\n },\n [revokeObjectUrl],\n )\n\n const clearImages = useCallback(() => {\n selectionSequenceRef.current += 1\n imagesRef.current.forEach((image) => revokeObjectUrl(image.preview))\n imagesRef.current = []\n setImages([])\n setErrorMessage(null)\n }, [revokeObjectUrl])\n\n return {\n images,\n isDragging,\n isProcessing,\n errorMessage,\n maxImages: policy.maxFiles,\n addFiles,\n clearErrorMessage: () => setErrorMessage(null),\n clearImages,\n handleImageChange,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n removeImage,\n }\n}\n"]}
1
+ {"version":3,"file":"attachments-client.js","sourceRoot":"","sources":["../../src/contact/attachments-client.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EACL,iCAAiC,EACjC,sCAAsC,EACtC,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,GAE1B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,WAAW,EACX,SAAS,EACT,MAAM,EACN,QAAQ,GAGT,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,mCAAmC,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAkC7F,MAAM,gCAAgC,GAAG,GAAG,CAAA;AAE5C,SAAS,eAAe,CAAC,MAA4C,EAAE,OAAe;IACpF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,MAAM,MAAM,GAAI,KAAqC,EAAE,MAAM,CAAA;IAC7D,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AAChH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,IAAU;IAC3D,MAAM,WAAW,GAAG,4BAA4B,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACtE,MAAM,eAAe,GAAG,WAAW,KAAK,YAAY,IAAI,WAAW,KAAK,YAAY,CAAA;IACpF,IAAI,CAAC;QACH,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAA;QAC7B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAA;QAC1D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAShB,CAAA;QAClB,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE;YAC7C,SAAS,EAAE,gCAAgC;YAC3C,gBAAgB,EAAE,IAAI;YACtB,wEAAwE;YACxE,8DAA8D;YAC9D,YAAY,EAAE,CAAC,eAAe;YAC9B,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW;YACtD,cAAc,EAAE,GAAG;SACpB,CAAC,CAAA;QACF,OAAO,eAAe;YACpB,CAAC,CAAC,MAAM,mCAAmC,CAAC,IAAI,EAAE,SAAS,CAAC;YAC5D,CAAC,CAAC,0BAA0B,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,4GAA4G,CAAC,CAAA;QAC/H,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,MAA+B;IAC3D,OAAO,qBAAqB,MAAM,CAAC,QAAQ,UAAU,CAAA;AACvD,CAAC;AAED,SAAS,uBAAuB,CAAC,MAA+B;IAC9D,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAA;IACrF,OAAO,8EAA8E,gBAAgB,MAAM,CAAA;AAC7G,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,UAAgD,EAAE;IAElD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,iCAAiC,CAAA;IAClE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,4BAA4B,CAAA;IAC3E,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,GAAG,CAAC,eAAe,CAAA;IACtE,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,GAAG,CAAC,eAAe,CAAA;IACtE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAqC,EAAE,CAAC,CAAA;IAC5E,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACrE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAA8C,IAAI,CAAC,CAAA;IACjG,MAAM,SAAS,GAAG,MAAM,CAAqC,EAAE,CAAC,CAAA;IAChE,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACnC,MAAM,oBAAoB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IACtC,MAAM,kBAAkB,GAAG,MAAM,CAAC,eAAe,CAAC,CAAA;IAElD,SAAS,CAAC,OAAO,GAAG,MAAM,CAAA;IAC1B,kBAAkB,CAAC,OAAO,GAAG,eAAe,CAAA;IAE5C,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,oBAAoB,CAAC,OAAO,IAAI,CAAC,CAAA;YACjC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QACjF,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,QAAQ,GAAG,WAAW,CAC1B,KAAK,EAAE,KAAsB,EAAE,EAAE;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,OAAO;YAAE,OAAM;QAEvD,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAA;QACjE,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;YACxB,eAAe,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;YAC7C,cAAc,CAAC,OAAO,CAAC,CAAA;YACvB,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAA;QAC7C,aAAa,CAAC,OAAO,GAAG,IAAI,CAAA;QAC5B,eAAe,CAAC,IAAI,CAAC,CAAA;QACrB,eAAe,CAAC,IAAI,CAAC,CAAA;QACrB,cAAc,CAAC,IAAI,CAAC,CAAA;QACpB,MAAM,eAAe,GAAG,sCAAsC,CAAC,MAAM,CAAC,CAAA;QACtE,IAAI,UAAU,GAAkB,IAAI,CAAA;QACpC,IAAI,WAAW,GAAgD,IAAI,CAAA;QAEnE,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;YACpD,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;wBAChE,MAAM,eAAe,CACnB,MAAM,EACN,iBAAiB,0BAA0B,CAAC,MAAM,CAAC,eAAe,CACnE,CAAA;oBACH,CAAC;oBAED,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAA;oBAC3C,IAAI,QAAQ,KAAK,oBAAoB,CAAC,OAAO;wBAAE,OAAM;oBACrD,MAAM,UAAU,GAAG,yBAAyB,CAC1C,EAAE,QAAQ,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,EACxE,eAAe,CAChB,CAAA;oBACD,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;wBACnB,MAAM,eAAe,CAAC,SAAS,CAAC,IAAI,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;oBAChH,CAAC;oBACD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;oBACrG,IAAI,SAAS,GAAG,MAAM,CAAC,iBAAiB;wBAAE,MAAM,eAAe,CAAC,OAAO,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAA;oBACzG,IAAI,SAAS,CAAC,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ;wBAAE,MAAM,eAAe,CAAC,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAA;oBAC7G,MAAM,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE;4BACxC,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC;yBACpC,CAAC,CAAA;oBACF,SAAS,CAAC,OAAO,GAAG,UAAU,CAAA;oBAC9B,SAAS,CAAC,UAAU,CAAC,CAAA;gBACvB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,QAAQ,KAAK,oBAAoB,CAAC,OAAO;wBAAE,OAAM;oBACrD,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;wBACxB,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oDAAoD,CAAA;wBAC1G,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;oBAC/B,CAAC;gBACH,CAAC;YACH,CAAC;YACD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,cAAc,CAAA;YAC9C,eAAe,CAAC,UAAU,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;YAC/E,cAAc,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5D,CAAC;gBAAS,CAAC;YACT,aAAa,CAAC,OAAO,GAAG,KAAK,CAAA;YAC7B,eAAe,CAAC,KAAK,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,EACD,CAAC,aAAa,EAAE,eAAe,EAAE,MAAM,CAAC,CACzC,CAAA;IAED,MAAM,iBAAiB,GAAG,WAAW,CACnC,KAAK,EAAE,KAAoC,EAAE,EAAE;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACtE,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrB,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAA;IACzB,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,KAAgB,EAAE,EAAE;QACtD,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,aAAa,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,KAAgB,EAAE,EAAE;QACvD,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,aAAa,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,UAAU,GAAG,WAAW,CAC5B,KAAK,EAAE,KAAgB,EAAE,EAAE;QACzB,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,aAAa,CAAC,KAAK,CAAC,CAAA;QACpB,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;IACtD,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,KAAa,EAAE,EAAE;QAChB,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QACxC,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAChC,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,KAAK,KAAK,CAAC,CAAA;QACpF,SAAS,CAAC,OAAO,GAAG,UAAU,CAAA;QAC9B,SAAS,CAAC,UAAU,CAAC,CAAA;QACrB,eAAe,CAAC,IAAI,CAAC,CAAA;QACrB,cAAc,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,EACD,CAAC,eAAe,CAAC,CAClB,CAAA;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,oBAAoB,CAAC,OAAO,IAAI,CAAC,CAAA;QACjC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QACpE,SAAS,CAAC,OAAO,GAAG,EAAE,CAAA;QACtB,SAAS,CAAC,EAAE,CAAC,CAAA;QACb,eAAe,CAAC,IAAI,CAAC,CAAA;QACrB,cAAc,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAA;IAErB,OAAO;QACL,MAAM;QACN,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,SAAS,EAAE,MAAM,CAAC,QAAQ;QAC1B,QAAQ;QACR,iBAAiB,EAAE,GAAG,EAAE;YACtB,eAAe,CAAC,IAAI,CAAC,CAAA;YACrB,cAAc,CAAC,IAAI,CAAC,CAAA;QACtB,CAAC;QACD,WAAW;QACX,iBAAiB;QACjB,eAAe;QACf,cAAc;QACd,UAAU;QACV,WAAW;KACZ,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n deriveContactAttachmentTransportPolicy,\n contactAttachmentTypeLabel,\n resolveContactAttachmentType,\n validateContactAttachment,\n type ContactAttachmentPolicy,\n} from '@brandlab-digital/site-core/contact'\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n} from 'react'\nimport { normalizeContactConvertedUploadFile, normalizeContactUploadFile } from './client.js'\n\nexport interface BrandlabContactAttachmentPreview {\n file: File\n preview: string\n}\n\nexport interface UseBrandlabContactAttachmentsOptions {\n policy?: ContactAttachmentPolicy\n compressImage?: (file: File) => Promise<File>\n createObjectUrl?: (file: File) => string\n revokeObjectUrl?: (url: string) => void\n}\n\n/** Why the last photo choice was refused, so a form can show its own copy instead of errorMessage. */\nexport type BrandlabContactAttachmentErrorReason = 'type' | 'size' | 'total' | 'count' | 'prepare'\n\nexport interface UseBrandlabContactAttachmentsResult {\n images: BrandlabContactAttachmentPreview[]\n isDragging: boolean\n isProcessing: boolean\n errorMessage: string | null\n errorReason: BrandlabContactAttachmentErrorReason | null\n maxImages: number\n addFiles: (files: readonly File[]) => Promise<void>\n clearErrorMessage: () => void\n clearImages: () => void\n handleImageChange: (event: ChangeEvent<HTMLInputElement>) => Promise<void>\n handleDragLeave: (event: DragEvent) => void\n handleDragOver: (event: DragEvent) => void\n handleDrop: (event: DragEvent) => Promise<void>\n removeImage: (index: number) => void\n}\n\nconst DEFAULT_COMPRESSED_IMAGE_SIZE_MB = 0.7\n\nfunction attachmentError(reason: BrandlabContactAttachmentErrorReason, message: string) {\n return Object.assign(new Error(message), { reason })\n}\n\nfunction reasonOf(error: unknown): BrandlabContactAttachmentErrorReason {\n const reason = (error as { reason?: unknown } | null)?.reason\n return reason === 'type' || reason === 'size' || reason === 'total' || reason === 'count' ? reason : 'prepare'\n}\n\nexport async function compressBrandlabContactImage(file: File): Promise<File> {\n const contentType = resolveContactAttachmentType(file.name, file.type)\n const needsConversion = contentType === 'image/heic' || contentType === 'image/heif'\n try {\n if (!contentType) return file\n const imported = await import('browser-image-compression')\n const imageCompression = imported.default as unknown as (\n image: File,\n options: {\n maxSizeMB: number\n maxWidthOrHeight: number\n useWebWorker: boolean\n fileType: string\n initialQuality: number\n },\n ) => Promise<File>\n const processed = await imageCompression(file, {\n maxSizeMB: DEFAULT_COMPRESSED_IMAGE_SIZE_MB,\n maxWidthOrHeight: 1920,\n // Native browser image decoding is available on the main thread. Do not\n // ask the dependency's remote worker fallback to decode HEIC.\n useWebWorker: !needsConversion,\n fileType: needsConversion ? 'image/jpeg' : contentType,\n initialQuality: 0.7,\n })\n return needsConversion\n ? await normalizeContactConvertedUploadFile(file, processed)\n : normalizeContactUploadFile(file, processed)\n } catch {\n if (needsConversion) {\n throw new Error('We could not prepare this photo. Please try adding it again, choose another photo, or continue without it.')\n }\n return file\n }\n}\n\nfunction maximumImagesMessage(policy: ContactAttachmentPolicy) {\n return `You can add up to ${policy.maxFiles} images.`\n}\n\nfunction maximumTotalSizeMessage(policy: ContactAttachmentPolicy) {\n const maximumMegabytes = Math.round((policy.maxTotalSizeBytes / 1_000_000) * 10) / 10\n return `Your images are too large. Please use fewer images or keep the total under ${maximumMegabytes} MB.`\n}\n\nexport function useBrandlabContactAttachments(\n options: UseBrandlabContactAttachmentsOptions = {},\n): UseBrandlabContactAttachmentsResult {\n const policy = options.policy ?? DEFAULT_CONTACT_ATTACHMENT_POLICY\n const compressImage = options.compressImage ?? compressBrandlabContactImage\n const createObjectUrl = options.createObjectUrl ?? URL.createObjectURL\n const revokeObjectUrl = options.revokeObjectUrl ?? URL.revokeObjectURL\n const [images, setImages] = useState<BrandlabContactAttachmentPreview[]>([])\n const [isDragging, setIsDragging] = useState(false)\n const [isProcessing, setIsProcessing] = useState(false)\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n const [errorReason, setErrorReason] = useState<BrandlabContactAttachmentErrorReason | null>(null)\n const imagesRef = useRef<BrandlabContactAttachmentPreview[]>([])\n const processingRef = useRef(false)\n const selectionSequenceRef = useRef(0)\n const revokeObjectUrlRef = useRef(revokeObjectUrl)\n\n imagesRef.current = images\n revokeObjectUrlRef.current = revokeObjectUrl\n\n useEffect(() => {\n return () => {\n selectionSequenceRef.current += 1\n imagesRef.current.forEach((image) => revokeObjectUrlRef.current(image.preview))\n }\n }, [])\n\n const addFiles = useCallback(\n async (files: readonly File[]) => {\n if (files.length === 0 || processingRef.current) return\n\n const availableSlots = policy.maxFiles - imagesRef.current.length\n if (availableSlots <= 0) {\n setErrorMessage(maximumImagesMessage(policy))\n setErrorReason('count')\n return\n }\n\n const sequence = selectionSequenceRef.current\n processingRef.current = true\n setIsProcessing(true)\n setErrorMessage(null)\n setErrorReason(null)\n const transportPolicy = deriveContactAttachmentTransportPolicy(policy)\n let firstError: string | null = null\n let firstReason: BrandlabContactAttachmentErrorReason | null = null\n\n try {\n const selectedFiles = files.slice(0, availableSlots)\n for (const file of selectedFiles) {\n try {\n if (!resolveContactAttachmentType(file.name, file.type, policy)) {\n throw attachmentError(\n 'type',\n `Please upload ${contactAttachmentTypeLabel(policy)} images only.`,\n )\n }\n\n const processed = await compressImage(file)\n if (sequence !== selectionSequenceRef.current) return\n const validation = validateContactAttachment(\n { filename: processed.name, size: processed.size, type: processed.type },\n transportPolicy,\n )\n if (!validation.ok) {\n throw attachmentError(processed.size > transportPolicy.maxFileSizeBytes ? 'size' : 'type', validation.message)\n }\n const totalSize = imagesRef.current.reduce((total, image) => total + image.file.size, processed.size)\n if (totalSize > policy.maxTotalSizeBytes) throw attachmentError('total', maximumTotalSizeMessage(policy))\n if (imagesRef.current.length >= policy.maxFiles) throw attachmentError('count', maximumImagesMessage(policy))\n const nextImages = [...imagesRef.current, {\n file: processed,\n preview: createObjectUrl(processed),\n }]\n imagesRef.current = nextImages\n setImages(nextImages)\n } catch (error) {\n if (sequence !== selectionSequenceRef.current) return\n if (firstError === null) {\n firstError = error instanceof Error ? error.message : 'We could not prepare this photo. Please try again.'\n firstReason = reasonOf(error)\n }\n }\n }\n const overflow = files.length > availableSlots\n setErrorMessage(firstError ?? (overflow ? maximumImagesMessage(policy) : null))\n setErrorReason(firstReason ?? (overflow ? 'count' : null))\n } finally {\n processingRef.current = false\n setIsProcessing(false)\n }\n },\n [compressImage, createObjectUrl, policy],\n )\n\n const handleImageChange = useCallback(\n async (event: ChangeEvent<HTMLInputElement>) => {\n const files = event.target.files ? Array.from(event.target.files) : []\n await addFiles(files)\n event.target.value = ''\n },\n [addFiles],\n )\n\n const handleDragOver = useCallback((event: DragEvent) => {\n event.preventDefault()\n setIsDragging(true)\n }, [])\n\n const handleDragLeave = useCallback((event: DragEvent) => {\n event.preventDefault()\n setIsDragging(false)\n }, [])\n\n const handleDrop = useCallback(\n async (event: DragEvent) => {\n event.preventDefault()\n setIsDragging(false)\n await addFiles(Array.from(event.dataTransfer.files))\n },\n [addFiles],\n )\n\n const removeImage = useCallback(\n (index: number) => {\n const removed = imagesRef.current[index]\n if (!removed) return\n revokeObjectUrl(removed.preview)\n const nextImages = imagesRef.current.filter((_, imageIndex) => imageIndex !== index)\n imagesRef.current = nextImages\n setImages(nextImages)\n setErrorMessage(null)\n setErrorReason(null)\n },\n [revokeObjectUrl],\n )\n\n const clearImages = useCallback(() => {\n selectionSequenceRef.current += 1\n imagesRef.current.forEach((image) => revokeObjectUrl(image.preview))\n imagesRef.current = []\n setImages([])\n setErrorMessage(null)\n setErrorReason(null)\n }, [revokeObjectUrl])\n\n return {\n images,\n isDragging,\n isProcessing,\n errorMessage,\n errorReason,\n maxImages: policy.maxFiles,\n addFiles,\n clearErrorMessage: () => {\n setErrorMessage(null)\n setErrorReason(null)\n },\n clearImages,\n handleImageChange,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n removeImage,\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { compressBrandlabContactImage, useBrandlabContactAttachments, type BrandlabContactAttachmentPreview, type UseBrandlabContactAttachmentsOptions, type UseBrandlabContactAttachmentsResult, } from './attachments-client.js';
1
+ export { compressBrandlabContactImage, useBrandlabContactAttachments, type BrandlabContactAttachmentErrorReason, type BrandlabContactAttachmentPreview, type UseBrandlabContactAttachmentsOptions, type UseBrandlabContactAttachmentsResult, } from './attachments-client.js';
2
2
  export { contactFieldProps, useBrandlabContactForm, type BrandlabContactAcceptedResult, type BrandlabContactFailedResult, type BrandlabContactFormProps, type BrandlabContactSubmitProps, type BrandlabContactSubmitStart, type ContactFieldPropsOptions, type UseBrandlabContactFormOptions, type UseBrandlabContactFormResult, } from './form-client.js';
3
3
  export type { ContactSubmissionPhase } from './client.js';
4
4
  //# sourceMappingURL=client-entry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-entry.d.ts","sourceRoot":"","sources":["../../src/contact/client-entry.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,EAC7B,KAAK,gCAAgC,EACrC,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,GACzC,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,GAClC,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"client-entry.d.ts","sourceRoot":"","sources":["../../src/contact/client-entry.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,EAC7B,KAAK,oCAAoC,EACzC,KAAK,gCAAgC,EACrC,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,GACzC,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,GAClC,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-entry.js","sourceRoot":"","sources":["../../src/contact/client-entry.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAI9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GASvB,MAAM,kBAAkB,CAAA","sourcesContent":["'use client'\n\nexport {\n compressBrandlabContactImage,\n useBrandlabContactAttachments,\n type BrandlabContactAttachmentPreview,\n type UseBrandlabContactAttachmentsOptions,\n type UseBrandlabContactAttachmentsResult,\n} from './attachments-client.js'\nexport {\n contactFieldProps,\n useBrandlabContactForm,\n type BrandlabContactAcceptedResult,\n type BrandlabContactFailedResult,\n type BrandlabContactFormProps,\n type BrandlabContactSubmitProps,\n type BrandlabContactSubmitStart,\n type ContactFieldPropsOptions,\n type UseBrandlabContactFormOptions,\n type UseBrandlabContactFormResult,\n} from './form-client.js'\nexport type { ContactSubmissionPhase } from './client.js'\n"]}
1
+ {"version":3,"file":"client-entry.js","sourceRoot":"","sources":["../../src/contact/client-entry.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAK9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GASvB,MAAM,kBAAkB,CAAA","sourcesContent":["'use client'\n\nexport {\n compressBrandlabContactImage,\n useBrandlabContactAttachments,\n type BrandlabContactAttachmentErrorReason,\n type BrandlabContactAttachmentPreview,\n type UseBrandlabContactAttachmentsOptions,\n type UseBrandlabContactAttachmentsResult,\n} from './attachments-client.js'\nexport {\n contactFieldProps,\n useBrandlabContactForm,\n type BrandlabContactAcceptedResult,\n type BrandlabContactFailedResult,\n type BrandlabContactFormProps,\n type BrandlabContactSubmitProps,\n type BrandlabContactSubmitStart,\n type ContactFieldPropsOptions,\n type UseBrandlabContactFormOptions,\n type UseBrandlabContactFormResult,\n} from './form-client.js'\nexport type { ContactSubmissionPhase } from './client.js'\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAE7B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAEL,KAAK,yBAAyB,EAE/B,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAChD,yBAAyB,EACzB,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,KAAK,CACnD,GAAG;IACF,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,KAAK,CAAC,EAAE,WAAW,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAA;CACxD,CAAA;AAuBD,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,uBAAuB,EACjC,OAAO,EAAE,+BAA+B,qCAgDzC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAE7B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAGL,KAAK,yBAAyB,EAE/B,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAChD,yBAAyB,EACzB,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,KAAK,CACnD,GAAG;IACF,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,KAAK,CAAC,EAAE,WAAW,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAA;CACxD,CAAA;AAuBD,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,uBAAuB,EACjC,OAAO,EAAE,+BAA+B,qCAgDzC"}
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_CONTACT_ATTACHMENT_POLICY, DOCUMENT_CONTACT_ATTACHMENT_POLICY, defineContactContract, isValidAustralianPhoneNumber, normalizeAustralianPhoneDigits, } from '@brandlab-digital/site-core/contact';
2
- import { defineBrandlabContact, } from './shared.js';
2
+ import { CONTACT_PHONE_CHARACTERS, defineBrandlabContact, } from './shared.js';
3
3
  const KIND_MAP = {
4
4
  text: 'text',
5
5
  email: 'email',
@@ -9,7 +9,7 @@ const KIND_MAP = {
9
9
  checkbox: 'checkbox',
10
10
  };
11
11
  function normalizeAustralianContactPhone(value) {
12
- if (!/^[+()\d\s.-]+$/.test(value)) {
12
+ if (!CONTACT_PHONE_CHARACTERS.test(value)) {
13
13
  throw new Error('The phone value contains unsupported characters.');
14
14
  }
15
15
  return normalizeAustralianPhoneDigits(value);
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n DOCUMENT_CONTACT_ATTACHMENT_POLICY,\n defineContactContract,\n isValidAustralianPhoneNumber,\n normalizeAustralianPhoneDigits,\n type BrandlabContactContract,\n type ContactAttachmentPolicy,\n type ContactFieldNormalization,\n} from '@brandlab-digital/site-core/contact'\nimport {\n defineBrandlabContact,\n type BrandlabContactDefinition,\n type ContactFieldKind,\n} from './shared.js'\n\nexport type ContactRuntimeDefinitionOptions = Pick<\n BrandlabContactDefinition,\n 'brandName' | 'siteUrl' | 'deploymentMode' | 'bot'\n> & {\n labels: Readonly<Record<string, string>>\n attachment?: Partial<ContactAttachmentPolicy> & { fieldPrefix?: string }\n email?: NonNullable<BrandlabContactDefinition['email']>\n}\n\nconst KIND_MAP: Record<BrandlabContactContract['fields'][number]['kind'], ContactFieldKind> = {\n text: 'text',\n email: 'email',\n tel: 'phone',\n textarea: 'textarea',\n select: 'select',\n checkbox: 'checkbox',\n}\n\nfunction normalizeAustralianContactPhone(value: string) {\n if (!/^[+()\\d\\s.-]+$/.test(value)) {\n throw new Error('The phone value contains unsupported characters.')\n }\n return normalizeAustralianPhoneDigits(value)\n}\n\nconst NORMALIZERS: Record<ContactFieldNormalization, (value: string) => string> = {\n lowercase: (value) => value.toLowerCase(),\n 'australian-phone-digits': normalizeAustralianContactPhone,\n}\n\nexport function createContactDefinitionFromContract(\n contract: BrandlabContactContract,\n options: ContactRuntimeDefinitionOptions,\n) {\n defineContactContract(contract)\n const { labels, attachment, email, ...runtime } = options\n const fields = contract.fields.map((field) => {\n const label = labels[field.key]?.trim()\n if (!label) throw new Error(`A customer-facing label is required for contact field \"${field.key}\".`)\n return {\n name: field.key,\n label,\n role: field.role,\n kind: KIND_MAP[field.kind],\n required: field.required,\n ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),\n ...(field.kind === 'checkbox'\n ? { options: [field.checkedValue!] }\n : field.options\n ? { options: field.options }\n : {}),\n ...(field.normalization\n ? { normalize: NORMALIZERS[field.normalization] }\n : {}),\n ...(field.phoneRule === 'australian'\n ? {\n validate: (value: string) =>\n isValidAustralianPhoneNumber(value)\n ? undefined\n : 'Enter a valid Australian phone number.',\n }\n : {}),\n }\n })\n const attachments =\n contract.attachments.mode === 'none'\n ? false\n : {\n ...(contract.attachments.mode === 'images-and-pdfs-optional' ? DOCUMENT_CONTACT_ATTACHMENT_POLICY : DEFAULT_CONTACT_ATTACHMENT_POLICY),\n ...attachment,\n required: contract.attachments.mode === 'images-required',\n }\n\n return defineBrandlabContact({\n ...runtime,\n projectId: contract.projectId,\n fields,\n attachments,\n ...(email ? { email } : {}),\n })\n}\n"]}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n DOCUMENT_CONTACT_ATTACHMENT_POLICY,\n defineContactContract,\n isValidAustralianPhoneNumber,\n normalizeAustralianPhoneDigits,\n type BrandlabContactContract,\n type ContactAttachmentPolicy,\n type ContactFieldNormalization,\n} from '@brandlab-digital/site-core/contact'\nimport {\n CONTACT_PHONE_CHARACTERS,\n defineBrandlabContact,\n type BrandlabContactDefinition,\n type ContactFieldKind,\n} from './shared.js'\n\nexport type ContactRuntimeDefinitionOptions = Pick<\n BrandlabContactDefinition,\n 'brandName' | 'siteUrl' | 'deploymentMode' | 'bot'\n> & {\n labels: Readonly<Record<string, string>>\n attachment?: Partial<ContactAttachmentPolicy> & { fieldPrefix?: string }\n email?: NonNullable<BrandlabContactDefinition['email']>\n}\n\nconst KIND_MAP: Record<BrandlabContactContract['fields'][number]['kind'], ContactFieldKind> = {\n text: 'text',\n email: 'email',\n tel: 'phone',\n textarea: 'textarea',\n select: 'select',\n checkbox: 'checkbox',\n}\n\nfunction normalizeAustralianContactPhone(value: string) {\n if (!CONTACT_PHONE_CHARACTERS.test(value)) {\n throw new Error('The phone value contains unsupported characters.')\n }\n return normalizeAustralianPhoneDigits(value)\n}\n\nconst NORMALIZERS: Record<ContactFieldNormalization, (value: string) => string> = {\n lowercase: (value) => value.toLowerCase(),\n 'australian-phone-digits': normalizeAustralianContactPhone,\n}\n\nexport function createContactDefinitionFromContract(\n contract: BrandlabContactContract,\n options: ContactRuntimeDefinitionOptions,\n) {\n defineContactContract(contract)\n const { labels, attachment, email, ...runtime } = options\n const fields = contract.fields.map((field) => {\n const label = labels[field.key]?.trim()\n if (!label) throw new Error(`A customer-facing label is required for contact field \"${field.key}\".`)\n return {\n name: field.key,\n label,\n role: field.role,\n kind: KIND_MAP[field.kind],\n required: field.required,\n ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),\n ...(field.kind === 'checkbox'\n ? { options: [field.checkedValue!] }\n : field.options\n ? { options: field.options }\n : {}),\n ...(field.normalization\n ? { normalize: NORMALIZERS[field.normalization] }\n : {}),\n ...(field.phoneRule === 'australian'\n ? {\n validate: (value: string) =>\n isValidAustralianPhoneNumber(value)\n ? undefined\n : 'Enter a valid Australian phone number.',\n }\n : {}),\n }\n })\n const attachments =\n contract.attachments.mode === 'none'\n ? false\n : {\n ...(contract.attachments.mode === 'images-and-pdfs-optional' ? DOCUMENT_CONTACT_ATTACHMENT_POLICY : DEFAULT_CONTACT_ATTACHMENT_POLICY),\n ...attachment,\n required: contract.attachments.mode === 'images-required',\n }\n\n return defineBrandlabContact({\n ...runtime,\n projectId: contract.projectId,\n fields,\n attachments,\n ...(email ? { email } : {}),\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/contact/email.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,yBAAyB,CAAA;IACrC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,OAAO,EAAE,kBAAkB,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAA;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,UAOpD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,UAExD;AAoCD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,uBAAuB,UAOtE;AA4FD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,oBAAoB,CA+EvF"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/contact/email.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,yBAAyB,CAAA;IACrC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,OAAO,EAAE,kBAAkB,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAA;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,UAOpD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,UAExD;AAoCD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,uBAAuB,UAOtE;AAoGD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,oBAAoB,CA+EvF"}
@@ -59,12 +59,21 @@ function formatAustralianPhone(value) {
59
59
  if (/^614\d{8}$/.test(digits)) {
60
60
  return `+61 ${digits.slice(2, 5)} ${digits.slice(5, 8)} ${digits.slice(8)}`;
61
61
  }
62
+ if (/^1[38]00\d{6}$/.test(digits)) {
63
+ return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;
64
+ }
65
+ if (/^13\d{4}$/.test(digits)) {
66
+ return `${digits.slice(0, 2)} ${digits.slice(2, 4)} ${digits.slice(4)}`;
67
+ }
62
68
  return value;
63
69
  }
64
70
  function australianPhoneHref(value) {
65
71
  if (!isValidAustralianPhoneNumber(value))
66
72
  return undefined;
67
73
  const digits = normalizeAustralianPhoneDigits(value);
74
+ // 13, 1300 and 1800 numbers can't be dialled with +61: they keep their national digits.
75
+ if (digits.startsWith('1'))
76
+ return `tel:${digits}`;
68
77
  return digits.startsWith('0')
69
78
  ? `tel:+61${digits.slice(1)}`
70
79
  : `tel:+${digits}`;
@@ -1 +1 @@
1
- {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/contact/email.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n contactVisitSourceLabel,\n isValidAustralianPhoneNumber,\n normalizeAustralianPhoneDigits,\n type ContactFieldRole,\n} from '@brandlab-digital/site-core/contact'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\nimport type {\n BrandlabContactDefinition,\n ContactDeliveryAttachment,\n ContactPageContext,\n} from './shared.js'\n\nexport interface RenderContactEmailInput {\n definition: BrandlabContactDefinition\n values: Readonly<Record<string, string>>\n context: ContactPageContext\n submittedAt: string\n confirmationId: string\n attachments?: readonly ContactDeliveryAttachment[]\n}\n\nexport interface RenderedContactEmail {\n subject: string\n html: string\n text: string\n replyTo?: string\n}\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst SAFE_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/\nconst CONFIRMATION_PATTERN = /^[a-f0-9]{64}$/\n\nexport function escapeContactEmailHtml(value: unknown) {\n return String(value)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nexport function sanitizeContactEmailSubject(value: string) {\n return value.replace(/[\\r\\n]+/g, ' ').replace(/\\s{2,}/g, ' ').trim().slice(0, 120)\n}\n\nfunction safeColor(value: string | undefined, fallback: string) {\n return value && SAFE_COLOR_PATTERN.test(value) ? value : fallback\n}\n\nfunction safeSingleLine(value: string | undefined, maximum = 160) {\n const normalized = value?.replace(/[\\r\\n]+/g, ' ').replace(/\\s{2,}/g, ' ').trim()\n return normalized && normalized.length <= maximum && !/[\\u0000-\\u001f\\u007f]/.test(normalized)\n ? normalized\n : undefined\n}\n\nfunction safePath(value: string | undefined) {\n if (!value) return undefined\n try {\n return normalizeBrandlabRoutePath(value)\n } catch {\n return undefined\n }\n}\n\nfunction roleValue(\n input: RenderContactEmailInput,\n role: ContactFieldRole,\n) {\n const field = input.definition.fields.find((candidate) => candidate.role === role)\n return field ? input.values[field.name]?.trim() : undefined\n}\n\nfunction subjectFragment(value: string | undefined, maximum: number) {\n const firstLine = value?.split(/[\\r\\n]/, 1)[0]\n const safe = safeSingleLine(firstLine, maximum)\n return safe?.replace(/[,:;|]+$/g, '').trim()\n}\n\nexport function buildContactEmailSubject(input: RenderContactEmailInput) {\n const name = subjectFragment(roleValue(input, 'name'), 60)\n const location = subjectFragment(roleValue(input, 'location'), 60)\n const detail = [name, location].filter(Boolean).join(', ')\n return sanitizeContactEmailSubject(\n detail ? `New website enquiry — ${detail}` : 'New website enquiry',\n )\n}\n\nfunction formatAustralianPhone(value: string) {\n const digits = value.replace(/\\D/g, '')\n if (/^04\\d{8}$/.test(digits)) {\n return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`\n }\n if (/^0[2378]\\d{8}$/.test(digits)) {\n return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)} ${digits.slice(6)}`\n }\n if (/^614\\d{8}$/.test(digits)) {\n return `+61 ${digits.slice(2, 5)} ${digits.slice(5, 8)} ${digits.slice(8)}`\n }\n return value\n}\n\nfunction australianPhoneHref(value: string) {\n if (!isValidAustralianPhoneNumber(value)) return undefined\n const digits = normalizeAustralianPhoneDigits(value)\n return digits.startsWith('0')\n ? `tel:+61${digits.slice(1)}`\n : `tel:+${digits}`\n}\n\nfunction formatSubmittedAt(value: string) {\n const date = new Date(value)\n if (!Number.isFinite(date.getTime())) return 'Time unavailable'\n return new Intl.DateTimeFormat('en-AU', {\n timeZone: 'Australia/Sydney',\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n }).format(date)\n}\n\nfunction shortReference(value: string) {\n return CONFIRMATION_PATTERN.test(value) ? value.slice(0, 8).toUpperCase() : 'Unavailable'\n}\n\nfunction renderEmailLogo(definition: BrandlabContactDefinition, muted: string) {\n const logo = definition.email?.logo\n if (!logo) return ''\n\n try {\n const parsed = new URL(logo.url)\n if (\n parsed.protocol !== 'https:' ||\n parsed.username ||\n parsed.password ||\n parsed.search ||\n parsed.hash ||\n !logo.alt.trim()\n ) {\n return ''\n }\n } catch {\n return ''\n }\n\n const width =\n Number.isInteger(logo.width) && logo.width! >= 32 && logo.width! <= 240\n ? logo.width\n : 144\n return `<img src=\"${escapeContactEmailHtml(logo.url)}\" alt=\"${escapeContactEmailHtml(logo.alt)}\" width=\"${width}\" style=\"display:block;width:${width}px;max-width:100%;height:auto;border:0;outline:none;text-decoration:none;color:${muted};margin:0 0 14px\">`\n}\n\nfunction row(\n label: string,\n value: string,\n colors: { text: string; muted: string },\n href?: string,\n) {\n const renderedValue = href\n ? `<a href=\"${escapeContactEmailHtml(href)}\" style=\"color:#1d4ed8;text-decoration:underline;font-weight:600\">${escapeContactEmailHtml(value)}</a>`\n : escapeContactEmailHtml(value)\n return `<tr><td class=\"brandlab-email-label\" style=\"padding:0 12px 14px 0;width:118px;vertical-align:top;color:${colors.muted};font-size:13px;font-weight:700;line-height:20px\">${escapeContactEmailHtml(label)}</td><td class=\"brandlab-email-value\" style=\"padding:0 0 14px;vertical-align:top;color:${colors.text};font-size:15px;font-weight:600;line-height:20px;word-break:break-all;overflow-wrap:anywhere\">${renderedValue}</td></tr>`\n}\n\nfunction contextRow(label: string, value: string, colors: { text: string; muted: string }) {\n return `<tr><td class=\"brandlab-email-label\" style=\"padding:0 12px 10px 0;width:118px;vertical-align:top;color:${colors.muted};font-size:12px;font-weight:700;line-height:18px\">${escapeContactEmailHtml(label)}</td><td class=\"brandlab-email-value\" style=\"padding:0 0 10px;vertical-align:top;color:${colors.text};font-size:13px;line-height:18px;word-break:break-all;overflow-wrap:anywhere\">${escapeContactEmailHtml(value)}</td></tr>`\n}\n\nfunction sectionLabel(value: string, accent: string) {\n return `<p style=\"margin:0 0 16px;color:${accent};font-size:11px;font-weight:800;line-height:16px;letter-spacing:1.2px;text-transform:uppercase\">${escapeContactEmailHtml(value)}</p>`\n}\n\nfunction textLines(rows: readonly { label: string; value: string }[]) {\n return rows.map(({ label, value }) => `${label}: ${value}`).join('\\n')\n}\n\nexport function renderContactEmail(input: RenderContactEmailInput): RenderedContactEmail {\n const colors = {\n accent: safeColor(input.definition.email?.style?.accentColor, '#1d4ed8'),\n background: safeColor(input.definition.email?.style?.backgroundColor, '#f3f4f6'),\n surface: safeColor(input.definition.email?.style?.surfaceColor, '#ffffff'),\n text: safeColor(input.definition.email?.style?.textColor, '#111827'),\n muted: safeColor(input.definition.email?.style?.mutedTextColor, '#64748b'),\n }\n const email = roleValue(input, 'email')\n const message = roleValue(input, 'message')\n const contactRows = input.definition.fields.flatMap((field) => {\n if (field.role === 'message') return []\n const value = input.values[field.name]?.trim()\n if (!value) return []\n return [{\n label: field.label,\n value: field.role === 'phone' ? formatAustralianPhone(value) : value,\n ...(field.role === 'phone' ? { href: australianPhoneHref(value) } : {}),\n }]\n })\n const landingPath = safePath(input.context.initialLandingPath) ?? '/'\n const submittedPath = safePath(input.context.pagePath) ?? '/'\n const pageLabel = safeSingleLine(input.context.pageLabel)\n const submittedPage = pageLabel ? `${pageLabel} — ${submittedPath}` : submittedPath\n const source = contactVisitSourceLabel(\n input.context.visitSource,\n input.context.visitSourceDetail,\n )\n const submittedAt = formatSubmittedAt(input.submittedAt)\n const reference = shortReference(input.confirmationId)\n const attachmentCount = input.attachments?.length ?? 0\n const attachmentSummary = attachmentCount === 1\n ? '1 image attached'\n : `${attachmentCount} images attached`\n const contextRows = [\n { label: 'Visit source', value: source },\n { label: 'Landing page', value: landingPath },\n { label: 'Submitted from', value: submittedPage },\n { label: 'Submitted', value: submittedAt },\n { label: 'Reference', value: reference },\n ]\n const logoHtml = renderEmailLogo(input.definition, colors.muted)\n const subject = buildContactEmailSubject(input)\n const heading = `New enquiry for ${input.definition.brandName}`\n const htmlContactRows = contactRows\n .map((item) => row(item.label, item.value, colors, item.href))\n .join('')\n const htmlContextRows = contextRows.map((item) => contextRow(item.label, item.value, colors)).join('')\n const messageHtml = message\n ? `<div style=\"padding:18px 20px;border-left:4px solid ${colors.accent};background:${colors.background};color:${colors.text};font-size:15px;line-height:24px;white-space:pre-wrap;word-break:break-word\">${escapeContactEmailHtml(message)}</div>`\n : ''\n const attachmentsHtml = attachmentCount > 0\n ? `<tr><td style=\"padding:0 32px 28px\"><div style=\"padding:14px 16px;border:1px solid ${colors.accent};border-radius:6px;color:${colors.text};font-size:13px;line-height:20px\"><strong>${escapeContactEmailHtml(attachmentSummary)}</strong><br><span style=\"color:${colors.muted}\">The images are attached to this email.</span></div></td></tr>`\n : ''\n\n const html = `<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><meta name=\"color-scheme\" content=\"light\"><meta name=\"supported-color-schemes\" content=\"light\"><title>${escapeContactEmailHtml(subject)}</title><style>@media only screen and (max-width:620px){.brandlab-email-shell{display:block!important;width:auto!important;padding:12px!important}.brandlab-email-card{width:100%!important;max-width:100%!important}.brandlab-email-cell{padding-left:22px!important;padding-right:22px!important}.brandlab-email-title{font-size:24px!important;line-height:31px!important;word-break:break-word!important}.brandlab-email-label,.brandlab-email-value{display:block!important;width:auto!important;padding-right:0!important;word-break:break-word!important}.brandlab-email-label{padding-bottom:3px!important}.brandlab-email-value{padding-bottom:12px!important}}</style></head><body style=\"margin:0;padding:0;background:${colors.background};color:${colors.text};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;-webkit-text-size-adjust:100%\"><div style=\"display:none;max-height:0;overflow:hidden;opacity:0;color:transparent\">A new website enquiry has been received.</div><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" bgcolor=\"${colors.background}\" style=\"width:100%;background:${colors.background};table-layout:fixed\"><tr><td class=\"brandlab-email-shell\" align=\"center\" style=\"padding:28px 16px\"><table class=\"brandlab-email-card\" role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" bgcolor=\"${colors.surface}\" style=\"width:100%;max-width:680px;table-layout:fixed;background:${colors.surface};border-collapse:separate;border-spacing:0;border-top:5px solid ${colors.accent};border-radius:8px;overflow:hidden;box-shadow:0 8px 28px rgba(15,23,42,.08)\"><tr><td class=\"brandlab-email-cell\" style=\"padding:32px 32px 26px\">${logoHtml}<p style=\"margin:0 0 8px;color:${colors.accent};font-size:11px;font-weight:800;line-height:16px;letter-spacing:1.3px;text-transform:uppercase\">Website enquiry</p><h1 class=\"brandlab-email-title\" style=\"margin:0;color:${colors.text};font-size:28px;font-weight:750;line-height:36px;overflow-wrap:anywhere\">${escapeContactEmailHtml(heading)}</h1><p style=\"margin:8px 0 0;color:${colors.muted};font-size:13px;line-height:20px\">Received ${escapeContactEmailHtml(submittedAt)}</p></td></tr><tr><td style=\"height:1px;background:#e5e7eb\"></td></tr><tr><td class=\"brandlab-email-cell\" style=\"padding:28px 32px 22px\">${sectionLabel('Contact details', colors.accent)}<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"width:100%;table-layout:fixed;border-collapse:collapse\"><tbody>${htmlContactRows}</tbody></table></td></tr><tr><td class=\"brandlab-email-cell\" style=\"padding:0 32px 28px\">${sectionLabel('Customer message', colors.accent)}${messageHtml}</td></tr>${attachmentsHtml}<tr><td class=\"brandlab-email-cell\" style=\"padding:26px 32px;background:${colors.background};border-top:1px solid #e5e7eb\">${sectionLabel('Enquiry context', colors.accent)}<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"width:100%;table-layout:fixed;border-collapse:collapse\"><tbody>${htmlContextRows}</tbody></table></td></tr></table><p style=\"margin:14px 0 0;color:${colors.muted};font-size:11px;line-height:16px;text-align:center\">Sent securely from the ${escapeContactEmailHtml(input.definition.brandName)} website.</p></td></tr></table></body></html>`\n\n const text = [\n heading,\n `Received ${submittedAt}`,\n '',\n 'CONTACT DETAILS',\n textLines(contactRows),\n '',\n 'CUSTOMER MESSAGE',\n message ?? '',\n ...(attachmentCount > 0 ? ['', 'ATTACHMENTS', attachmentSummary] : []),\n '',\n 'ENQUIRY CONTEXT',\n textLines(contextRows),\n ].join('\\n')\n const replyTo = email?.trim().toLowerCase()\n\n return {\n subject,\n html,\n text,\n ...(replyTo && EMAIL_PATTERN.test(replyTo) ? { replyTo } : {}),\n }\n}\n"]}
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/contact/email.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n contactVisitSourceLabel,\n isValidAustralianPhoneNumber,\n normalizeAustralianPhoneDigits,\n type ContactFieldRole,\n} from '@brandlab-digital/site-core/contact'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\nimport type {\n BrandlabContactDefinition,\n ContactDeliveryAttachment,\n ContactPageContext,\n} from './shared.js'\n\nexport interface RenderContactEmailInput {\n definition: BrandlabContactDefinition\n values: Readonly<Record<string, string>>\n context: ContactPageContext\n submittedAt: string\n confirmationId: string\n attachments?: readonly ContactDeliveryAttachment[]\n}\n\nexport interface RenderedContactEmail {\n subject: string\n html: string\n text: string\n replyTo?: string\n}\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst SAFE_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/\nconst CONFIRMATION_PATTERN = /^[a-f0-9]{64}$/\n\nexport function escapeContactEmailHtml(value: unknown) {\n return String(value)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nexport function sanitizeContactEmailSubject(value: string) {\n return value.replace(/[\\r\\n]+/g, ' ').replace(/\\s{2,}/g, ' ').trim().slice(0, 120)\n}\n\nfunction safeColor(value: string | undefined, fallback: string) {\n return value && SAFE_COLOR_PATTERN.test(value) ? value : fallback\n}\n\nfunction safeSingleLine(value: string | undefined, maximum = 160) {\n const normalized = value?.replace(/[\\r\\n]+/g, ' ').replace(/\\s{2,}/g, ' ').trim()\n return normalized && normalized.length <= maximum && !/[\\u0000-\\u001f\\u007f]/.test(normalized)\n ? normalized\n : undefined\n}\n\nfunction safePath(value: string | undefined) {\n if (!value) return undefined\n try {\n return normalizeBrandlabRoutePath(value)\n } catch {\n return undefined\n }\n}\n\nfunction roleValue(\n input: RenderContactEmailInput,\n role: ContactFieldRole,\n) {\n const field = input.definition.fields.find((candidate) => candidate.role === role)\n return field ? input.values[field.name]?.trim() : undefined\n}\n\nfunction subjectFragment(value: string | undefined, maximum: number) {\n const firstLine = value?.split(/[\\r\\n]/, 1)[0]\n const safe = safeSingleLine(firstLine, maximum)\n return safe?.replace(/[,:;|]+$/g, '').trim()\n}\n\nexport function buildContactEmailSubject(input: RenderContactEmailInput) {\n const name = subjectFragment(roleValue(input, 'name'), 60)\n const location = subjectFragment(roleValue(input, 'location'), 60)\n const detail = [name, location].filter(Boolean).join(', ')\n return sanitizeContactEmailSubject(\n detail ? `New website enquiry — ${detail}` : 'New website enquiry',\n )\n}\n\nfunction formatAustralianPhone(value: string) {\n const digits = value.replace(/\\D/g, '')\n if (/^04\\d{8}$/.test(digits)) {\n return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`\n }\n if (/^0[2378]\\d{8}$/.test(digits)) {\n return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)} ${digits.slice(6)}`\n }\n if (/^614\\d{8}$/.test(digits)) {\n return `+61 ${digits.slice(2, 5)} ${digits.slice(5, 8)} ${digits.slice(8)}`\n }\n if (/^1[38]00\\d{6}$/.test(digits)) {\n return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`\n }\n if (/^13\\d{4}$/.test(digits)) {\n return `${digits.slice(0, 2)} ${digits.slice(2, 4)} ${digits.slice(4)}`\n }\n return value\n}\n\nfunction australianPhoneHref(value: string) {\n if (!isValidAustralianPhoneNumber(value)) return undefined\n const digits = normalizeAustralianPhoneDigits(value)\n // 13, 1300 and 1800 numbers can't be dialled with +61: they keep their national digits.\n if (digits.startsWith('1')) return `tel:${digits}`\n return digits.startsWith('0')\n ? `tel:+61${digits.slice(1)}`\n : `tel:+${digits}`\n}\n\nfunction formatSubmittedAt(value: string) {\n const date = new Date(value)\n if (!Number.isFinite(date.getTime())) return 'Time unavailable'\n return new Intl.DateTimeFormat('en-AU', {\n timeZone: 'Australia/Sydney',\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n }).format(date)\n}\n\nfunction shortReference(value: string) {\n return CONFIRMATION_PATTERN.test(value) ? value.slice(0, 8).toUpperCase() : 'Unavailable'\n}\n\nfunction renderEmailLogo(definition: BrandlabContactDefinition, muted: string) {\n const logo = definition.email?.logo\n if (!logo) return ''\n\n try {\n const parsed = new URL(logo.url)\n if (\n parsed.protocol !== 'https:' ||\n parsed.username ||\n parsed.password ||\n parsed.search ||\n parsed.hash ||\n !logo.alt.trim()\n ) {\n return ''\n }\n } catch {\n return ''\n }\n\n const width =\n Number.isInteger(logo.width) && logo.width! >= 32 && logo.width! <= 240\n ? logo.width\n : 144\n return `<img src=\"${escapeContactEmailHtml(logo.url)}\" alt=\"${escapeContactEmailHtml(logo.alt)}\" width=\"${width}\" style=\"display:block;width:${width}px;max-width:100%;height:auto;border:0;outline:none;text-decoration:none;color:${muted};margin:0 0 14px\">`\n}\n\nfunction row(\n label: string,\n value: string,\n colors: { text: string; muted: string },\n href?: string,\n) {\n const renderedValue = href\n ? `<a href=\"${escapeContactEmailHtml(href)}\" style=\"color:#1d4ed8;text-decoration:underline;font-weight:600\">${escapeContactEmailHtml(value)}</a>`\n : escapeContactEmailHtml(value)\n return `<tr><td class=\"brandlab-email-label\" style=\"padding:0 12px 14px 0;width:118px;vertical-align:top;color:${colors.muted};font-size:13px;font-weight:700;line-height:20px\">${escapeContactEmailHtml(label)}</td><td class=\"brandlab-email-value\" style=\"padding:0 0 14px;vertical-align:top;color:${colors.text};font-size:15px;font-weight:600;line-height:20px;word-break:break-all;overflow-wrap:anywhere\">${renderedValue}</td></tr>`\n}\n\nfunction contextRow(label: string, value: string, colors: { text: string; muted: string }) {\n return `<tr><td class=\"brandlab-email-label\" style=\"padding:0 12px 10px 0;width:118px;vertical-align:top;color:${colors.muted};font-size:12px;font-weight:700;line-height:18px\">${escapeContactEmailHtml(label)}</td><td class=\"brandlab-email-value\" style=\"padding:0 0 10px;vertical-align:top;color:${colors.text};font-size:13px;line-height:18px;word-break:break-all;overflow-wrap:anywhere\">${escapeContactEmailHtml(value)}</td></tr>`\n}\n\nfunction sectionLabel(value: string, accent: string) {\n return `<p style=\"margin:0 0 16px;color:${accent};font-size:11px;font-weight:800;line-height:16px;letter-spacing:1.2px;text-transform:uppercase\">${escapeContactEmailHtml(value)}</p>`\n}\n\nfunction textLines(rows: readonly { label: string; value: string }[]) {\n return rows.map(({ label, value }) => `${label}: ${value}`).join('\\n')\n}\n\nexport function renderContactEmail(input: RenderContactEmailInput): RenderedContactEmail {\n const colors = {\n accent: safeColor(input.definition.email?.style?.accentColor, '#1d4ed8'),\n background: safeColor(input.definition.email?.style?.backgroundColor, '#f3f4f6'),\n surface: safeColor(input.definition.email?.style?.surfaceColor, '#ffffff'),\n text: safeColor(input.definition.email?.style?.textColor, '#111827'),\n muted: safeColor(input.definition.email?.style?.mutedTextColor, '#64748b'),\n }\n const email = roleValue(input, 'email')\n const message = roleValue(input, 'message')\n const contactRows = input.definition.fields.flatMap((field) => {\n if (field.role === 'message') return []\n const value = input.values[field.name]?.trim()\n if (!value) return []\n return [{\n label: field.label,\n value: field.role === 'phone' ? formatAustralianPhone(value) : value,\n ...(field.role === 'phone' ? { href: australianPhoneHref(value) } : {}),\n }]\n })\n const landingPath = safePath(input.context.initialLandingPath) ?? '/'\n const submittedPath = safePath(input.context.pagePath) ?? '/'\n const pageLabel = safeSingleLine(input.context.pageLabel)\n const submittedPage = pageLabel ? `${pageLabel} — ${submittedPath}` : submittedPath\n const source = contactVisitSourceLabel(\n input.context.visitSource,\n input.context.visitSourceDetail,\n )\n const submittedAt = formatSubmittedAt(input.submittedAt)\n const reference = shortReference(input.confirmationId)\n const attachmentCount = input.attachments?.length ?? 0\n const attachmentSummary = attachmentCount === 1\n ? '1 image attached'\n : `${attachmentCount} images attached`\n const contextRows = [\n { label: 'Visit source', value: source },\n { label: 'Landing page', value: landingPath },\n { label: 'Submitted from', value: submittedPage },\n { label: 'Submitted', value: submittedAt },\n { label: 'Reference', value: reference },\n ]\n const logoHtml = renderEmailLogo(input.definition, colors.muted)\n const subject = buildContactEmailSubject(input)\n const heading = `New enquiry for ${input.definition.brandName}`\n const htmlContactRows = contactRows\n .map((item) => row(item.label, item.value, colors, item.href))\n .join('')\n const htmlContextRows = contextRows.map((item) => contextRow(item.label, item.value, colors)).join('')\n const messageHtml = message\n ? `<div style=\"padding:18px 20px;border-left:4px solid ${colors.accent};background:${colors.background};color:${colors.text};font-size:15px;line-height:24px;white-space:pre-wrap;word-break:break-word\">${escapeContactEmailHtml(message)}</div>`\n : ''\n const attachmentsHtml = attachmentCount > 0\n ? `<tr><td style=\"padding:0 32px 28px\"><div style=\"padding:14px 16px;border:1px solid ${colors.accent};border-radius:6px;color:${colors.text};font-size:13px;line-height:20px\"><strong>${escapeContactEmailHtml(attachmentSummary)}</strong><br><span style=\"color:${colors.muted}\">The images are attached to this email.</span></div></td></tr>`\n : ''\n\n const html = `<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><meta name=\"color-scheme\" content=\"light\"><meta name=\"supported-color-schemes\" content=\"light\"><title>${escapeContactEmailHtml(subject)}</title><style>@media only screen and (max-width:620px){.brandlab-email-shell{display:block!important;width:auto!important;padding:12px!important}.brandlab-email-card{width:100%!important;max-width:100%!important}.brandlab-email-cell{padding-left:22px!important;padding-right:22px!important}.brandlab-email-title{font-size:24px!important;line-height:31px!important;word-break:break-word!important}.brandlab-email-label,.brandlab-email-value{display:block!important;width:auto!important;padding-right:0!important;word-break:break-word!important}.brandlab-email-label{padding-bottom:3px!important}.brandlab-email-value{padding-bottom:12px!important}}</style></head><body style=\"margin:0;padding:0;background:${colors.background};color:${colors.text};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;-webkit-text-size-adjust:100%\"><div style=\"display:none;max-height:0;overflow:hidden;opacity:0;color:transparent\">A new website enquiry has been received.</div><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" bgcolor=\"${colors.background}\" style=\"width:100%;background:${colors.background};table-layout:fixed\"><tr><td class=\"brandlab-email-shell\" align=\"center\" style=\"padding:28px 16px\"><table class=\"brandlab-email-card\" role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" bgcolor=\"${colors.surface}\" style=\"width:100%;max-width:680px;table-layout:fixed;background:${colors.surface};border-collapse:separate;border-spacing:0;border-top:5px solid ${colors.accent};border-radius:8px;overflow:hidden;box-shadow:0 8px 28px rgba(15,23,42,.08)\"><tr><td class=\"brandlab-email-cell\" style=\"padding:32px 32px 26px\">${logoHtml}<p style=\"margin:0 0 8px;color:${colors.accent};font-size:11px;font-weight:800;line-height:16px;letter-spacing:1.3px;text-transform:uppercase\">Website enquiry</p><h1 class=\"brandlab-email-title\" style=\"margin:0;color:${colors.text};font-size:28px;font-weight:750;line-height:36px;overflow-wrap:anywhere\">${escapeContactEmailHtml(heading)}</h1><p style=\"margin:8px 0 0;color:${colors.muted};font-size:13px;line-height:20px\">Received ${escapeContactEmailHtml(submittedAt)}</p></td></tr><tr><td style=\"height:1px;background:#e5e7eb\"></td></tr><tr><td class=\"brandlab-email-cell\" style=\"padding:28px 32px 22px\">${sectionLabel('Contact details', colors.accent)}<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"width:100%;table-layout:fixed;border-collapse:collapse\"><tbody>${htmlContactRows}</tbody></table></td></tr><tr><td class=\"brandlab-email-cell\" style=\"padding:0 32px 28px\">${sectionLabel('Customer message', colors.accent)}${messageHtml}</td></tr>${attachmentsHtml}<tr><td class=\"brandlab-email-cell\" style=\"padding:26px 32px;background:${colors.background};border-top:1px solid #e5e7eb\">${sectionLabel('Enquiry context', colors.accent)}<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"width:100%;table-layout:fixed;border-collapse:collapse\"><tbody>${htmlContextRows}</tbody></table></td></tr></table><p style=\"margin:14px 0 0;color:${colors.muted};font-size:11px;line-height:16px;text-align:center\">Sent securely from the ${escapeContactEmailHtml(input.definition.brandName)} website.</p></td></tr></table></body></html>`\n\n const text = [\n heading,\n `Received ${submittedAt}`,\n '',\n 'CONTACT DETAILS',\n textLines(contactRows),\n '',\n 'CUSTOMER MESSAGE',\n message ?? '',\n ...(attachmentCount > 0 ? ['', 'ATTACHMENTS', attachmentSummary] : []),\n '',\n 'ENQUIRY CONTEXT',\n textLines(contextRows),\n ].join('\\n')\n const replyTo = email?.trim().toLowerCase()\n\n return {\n subject,\n html,\n text,\n ...(replyTo && EMAIL_PATTERN.test(replyTo) ? { replyTo } : {}),\n }\n}\n"]}
@@ -135,8 +135,10 @@ export declare function contactFormSlug(formId: string): string;
135
135
  * submission ID (so a resend cannot duplicate the email), the first invalid
136
136
  * field is focused, and an accepted enquiry resets the form and redirects.
137
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.
138
+ * declares, once per accepted enquiry, with contact_form's `has_photos`. On a
139
+ * Brandlab test visit (`?bl_test=1`) each enquiry carries the `bl_test` marker
140
+ * and is never reported (no lead diagnostics either). Browser validation is left exactly as the form's own
141
+ * markup declares it.
140
142
  */
141
143
  export declare function useBrandlabContactForm(options: UseBrandlabContactFormOptions): UseBrandlabContactFormResult;
142
144
  export interface ContactFieldPropsOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/contact/form-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/contact/form-client.ts"],"names":[],"mappings":"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"}