@brandlab-digital/site-next 3.9.0 → 3.10.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/README.md +33 -0
- package/dist/contact/attachments-client.d.ts +3 -0
- package/dist/contact/attachments-client.d.ts.map +1 -1
- package/dist/contact/attachments-client.js +31 -8
- package/dist/contact/attachments-client.js.map +1 -1
- package/dist/contact/client-entry.d.ts +1 -1
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/contract.d.ts.map +1 -1
- package/dist/contact/contract.js +2 -2
- package/dist/contact/contract.js.map +1 -1
- package/dist/contact/email.d.ts.map +1 -1
- package/dist/contact/email.js +9 -0
- package/dist/contact/email.js.map +1 -1
- package/dist/contact/form-client.d.ts +4 -2
- package/dist/contact/form-client.d.ts.map +1 -1
- package/dist/contact/form-client.js +49 -13
- package/dist/contact/form-client.js.map +1 -1
- package/dist/contact/rate-limit.d.ts +59 -0
- package/dist/contact/rate-limit.d.ts.map +1 -0
- package/dist/contact/rate-limit.js +109 -0
- package/dist/contact/rate-limit.js.map +1 -0
- package/dist/contact/route-handler.d.ts +6 -0
- package/dist/contact/route-handler.d.ts.map +1 -1
- package/dist/contact/route-handler.js +4 -0
- package/dist/contact/route-handler.js.map +1 -1
- package/dist/contact/server-entry.d.ts +1 -1
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -1
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +9 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +71 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +11 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js +11 -0
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +5 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +1 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/lead-reporting.d.ts +6 -0
- package/dist/lead-reporting.d.ts.map +1 -1
- package/dist/lead-reporting.js +13 -1
- package/dist/lead-reporting.js.map +1 -1
- package/dist/ui-kit/brandlab-skin.d.ts +12 -0
- package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
- package/dist/ui-kit/brandlab-skin.js.map +1 -1
- package/dist/ui-kit/form/copy.d.ts +3 -0
- package/dist/ui-kit/form/copy.d.ts.map +1 -0
- package/dist/ui-kit/form/copy.js +5 -0
- package/dist/ui-kit/form/copy.js.map +1 -0
- package/dist/ui-kit/form/dialog.d.ts +24 -0
- package/dist/ui-kit/form/dialog.d.ts.map +1 -0
- package/dist/ui-kit/form/dialog.js +17 -0
- package/dist/ui-kit/form/dialog.js.map +1 -0
- package/dist/ui-kit/form/field.d.ts +30 -0
- package/dist/ui-kit/form/field.d.ts.map +1 -0
- package/dist/ui-kit/form/field.js +11 -0
- package/dist/ui-kit/form/field.js.map +1 -0
- package/dist/ui-kit/form/index.d.ts +7 -0
- package/dist/ui-kit/form/index.d.ts.map +1 -0
- package/dist/ui-kit/form/index.js +7 -0
- package/dist/ui-kit/form/index.js.map +1 -0
- package/dist/ui-kit/form/photo-upload.d.ts +37 -0
- package/dist/ui-kit/form/photo-upload.d.ts.map +1 -0
- package/dist/ui-kit/form/photo-upload.js +19 -0
- package/dist/ui-kit/form/photo-upload.js.map +1 -0
- package/dist/ui-kit/form/quote-dialog.d.ts +15 -0
- package/dist/ui-kit/form/quote-dialog.d.ts.map +1 -0
- package/dist/ui-kit/form/quote-dialog.js +68 -0
- package/dist/ui-kit/form/quote-dialog.js.map +1 -0
- package/dist/ui-kit/form/quote-form.d.ts +165 -0
- package/dist/ui-kit/form/quote-form.d.ts.map +1 -0
- package/dist/ui-kit/form/quote-form.js +235 -0
- package/dist/ui-kit/form/quote-form.js.map +1 -0
- package/dist/ui-kit/form/thank-you.d.ts +37 -0
- package/dist/ui-kit/form/thank-you.d.ts.map +1 -0
- package/dist/ui-kit/form/thank-you.js +36 -0
- package/dist/ui-kit/form/thank-you.js.map +1 -0
- package/dist/ui-kit/structure.css +197 -0
- package/dist/ui.d.ts +10 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +3 -3
- package/dist/ui.js.map +1 -1
- package/package.json +6 -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;
|
|
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
|
|
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
|
|
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
|
|
103
|
+
throw attachmentError('total', maximumTotalSizeMessage(policy));
|
|
92
104
|
if (imagesRef.current.length >= policy.maxFiles)
|
|
93
|
-
throw
|
|
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
|
|
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
|
-
|
|
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: () =>
|
|
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":"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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\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,
|
|
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,
|
|
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"}
|
package/dist/contact/contract.js
CHANGED
|
@@ -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 (
|
|
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":"AAAA,OAAO,EACL,iCAAiC,EACjC,kCAAkC,EAClC,qBAAqB,EACrB,4BAA4B,EAC5B,8BAA8B,GAI/B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,qBAAqB,GAGtB,MAAM,aAAa,CAAA;AAWpB,MAAM,QAAQ,GAAgF;IAC5F,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,OAAO;IACZ,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACrB,CAAA;AAED,SAAS,+BAA+B,CAAC,KAAa;IACpD,IAAI,CAAC,
|
|
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;
|
|
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"}
|
package/dist/contact/email.js
CHANGED
|
@@ -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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\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
|
|
139
|
-
*
|
|
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":"AAEA,OAAO,EAIL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAA;AAE5C,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,iBAAiB,EAA2C,MAAM,iBAAiB,CAAA;AAGjG,OAAO,EAEL,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,EACzC,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAGL,KAAK,sBAAsB,EAE5B,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/contact/form-client.ts"],"names":[],"mappings":"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"}
|