@jotul/jotul-widgets 1.2.6 → 2.0.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 +112 -28
- package/dist/JotulWidget.css +1 -1
- package/dist/JotulWidget.d.ts +1 -1
- package/dist/JotulWidget.js +356 -163
- package/dist/analytics/WidgetTrackingContext.d.ts +14 -0
- package/dist/analytics/WidgetTrackingContext.js +5 -0
- package/dist/analytics/gtm.d.ts +7 -0
- package/dist/analytics/gtm.js +17 -0
- package/dist/analytics/widgetTracking.d.ts +54 -0
- package/dist/analytics/widgetTracking.js +144 -0
- package/dist/api.d.ts +27 -1
- package/dist/api.js +74 -0
- package/dist/components/FindDealerDrawerWidget.d.ts +7 -4
- package/dist/components/FindDealerDrawerWidget.js +17 -14
- package/dist/components/InquiryField.d.ts +3 -1
- package/dist/components/InquiryField.js +19 -2
- package/dist/components/InquirySelectField.d.ts +13 -0
- package/dist/components/InquirySelectField.js +5 -0
- package/dist/components/ProductPageWidget.d.ts +7 -4
- package/dist/components/ProductPageWidget.js +12 -14
- package/dist/components/TurnstileField.d.ts +7 -0
- package/dist/components/TurnstileField.js +48 -0
- package/dist/components/WarrantyFormWidget.d.ts +12 -0
- package/dist/components/WarrantyFormWidget.js +98 -0
- package/dist/components/product-page/DealerList.d.ts +1 -1
- package/dist/components/product-page/DealerList.js +13 -5
- package/dist/components/product-page/InquiryForm.d.ts +6 -2
- package/dist/components/product-page/InquiryForm.js +21 -3
- package/dist/constants/turnstile.d.ts +8 -0
- package/dist/constants/turnstile.js +19 -0
- package/dist/hooks/useTurnstileSiteKey.d.ts +1 -0
- package/dist/hooks/useTurnstileSiteKey.js +38 -0
- package/dist/i18n/locales/cz.json +34 -1
- package/dist/i18n/locales/de.json +34 -1
- package/dist/i18n/locales/en.json +34 -1
- package/dist/i18n/locales/fi.json +34 -1
- package/dist/i18n/locales/fr.json +34 -1
- package/dist/i18n/locales/nl.json +34 -1
- package/dist/i18n/locales/no.json +34 -1
- package/dist/i18n/locales/pl.json +34 -1
- package/dist/i18n/locales/se.json +34 -1
- package/dist/i18n/widgetStrings.d.ts +33 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/turnstile.d.ts +18 -0
- package/dist/turnstile.js +31 -0
- package/dist/types.d.ts +56 -0
- package/dist/utils/inquiryCategories.d.ts +8 -0
- package/dist/utils/inquiryCategories.js +24 -0
- package/dist/utils/inquirySubmit.d.ts +24 -0
- package/dist/utils/inquirySubmit.js +35 -0
- package/dist/utils/urlDealerId.d.ts +2 -0
- package/dist/utils/urlDealerId.js +5 -0
- package/dist/utils/usMarket.d.ts +2 -0
- package/dist/utils/usMarket.js +9 -0
- package/dist/utils/warrantyForm.d.ts +38 -0
- package/dist/utils/warrantyForm.js +80 -0
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +46 -3
- package/package.json +5 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { WarrantyFormValues } from '../types';
|
|
2
|
+
export declare function createWarrantyFormValues(): WarrantyFormValues;
|
|
3
|
+
export type WarrantyValidationError = {
|
|
4
|
+
field: keyof WarrantyFormValues;
|
|
5
|
+
type: 'required';
|
|
6
|
+
} | {
|
|
7
|
+
field: 'email';
|
|
8
|
+
type: 'invalid_email';
|
|
9
|
+
} | {
|
|
10
|
+
field: 'consent';
|
|
11
|
+
type: 'required';
|
|
12
|
+
};
|
|
13
|
+
export declare function validateWarrantyFormValues(values: WarrantyFormValues, options?: {
|
|
14
|
+
market?: string;
|
|
15
|
+
locale?: string;
|
|
16
|
+
}): WarrantyValidationError | null;
|
|
17
|
+
export declare function warrantyFormValuesToApiPayload(values: WarrantyFormValues, options?: {
|
|
18
|
+
turnstileToken?: string | null;
|
|
19
|
+
market?: string;
|
|
20
|
+
domain?: string;
|
|
21
|
+
}): {
|
|
22
|
+
turnstileToken?: string | undefined;
|
|
23
|
+
domain?: string | undefined;
|
|
24
|
+
market?: string | undefined;
|
|
25
|
+
telephone: string;
|
|
26
|
+
email: string;
|
|
27
|
+
product: string;
|
|
28
|
+
dealer: string;
|
|
29
|
+
purchaseDate: string;
|
|
30
|
+
selfInstalled: boolean;
|
|
31
|
+
installerName: string | undefined;
|
|
32
|
+
source: "widget";
|
|
33
|
+
county?: string | undefined;
|
|
34
|
+
name: string;
|
|
35
|
+
address: string;
|
|
36
|
+
zipcode: string;
|
|
37
|
+
city: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isValidEmail } from '../utils';
|
|
2
|
+
import { isUsWarrantyMarket } from './usMarket';
|
|
3
|
+
export function createWarrantyFormValues() {
|
|
4
|
+
return {
|
|
5
|
+
firstName: '',
|
|
6
|
+
lastName: '',
|
|
7
|
+
address: '',
|
|
8
|
+
zipcode: '',
|
|
9
|
+
city: '',
|
|
10
|
+
county: '',
|
|
11
|
+
phone: '',
|
|
12
|
+
email: '',
|
|
13
|
+
productName: '',
|
|
14
|
+
purchaseDate: '',
|
|
15
|
+
purchasedFromDealer: '',
|
|
16
|
+
selfInstalled: '',
|
|
17
|
+
installerName: '',
|
|
18
|
+
consent: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function validateWarrantyFormValues(values, options) {
|
|
22
|
+
const requiredFields = [
|
|
23
|
+
'firstName',
|
|
24
|
+
'lastName',
|
|
25
|
+
'address',
|
|
26
|
+
'zipcode',
|
|
27
|
+
'city',
|
|
28
|
+
'phone',
|
|
29
|
+
'email',
|
|
30
|
+
'productName',
|
|
31
|
+
'purchaseDate',
|
|
32
|
+
'purchasedFromDealer',
|
|
33
|
+
];
|
|
34
|
+
if (isUsWarrantyMarket(options?.market, options?.locale)) {
|
|
35
|
+
requiredFields.push('county');
|
|
36
|
+
}
|
|
37
|
+
for (const field of requiredFields) {
|
|
38
|
+
const value = values[field];
|
|
39
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
40
|
+
return { field, type: 'required' };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!isValidEmail(values.email.trim())) {
|
|
44
|
+
return { field: 'email', type: 'invalid_email' };
|
|
45
|
+
}
|
|
46
|
+
if (values.selfInstalled !== 'yes' && values.selfInstalled !== 'no') {
|
|
47
|
+
return { field: 'selfInstalled', type: 'required' };
|
|
48
|
+
}
|
|
49
|
+
if (values.selfInstalled === 'no' && !values.installerName.trim()) {
|
|
50
|
+
return { field: 'installerName', type: 'required' };
|
|
51
|
+
}
|
|
52
|
+
if (!values.consent) {
|
|
53
|
+
return { field: 'consent', type: 'required' };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
export function warrantyFormValuesToApiPayload(values, options) {
|
|
58
|
+
const market = options?.market?.trim().toUpperCase();
|
|
59
|
+
const county = values.county.trim();
|
|
60
|
+
return {
|
|
61
|
+
name: `${values.firstName.trim()} ${values.lastName.trim()}`.trim(),
|
|
62
|
+
address: values.address.trim(),
|
|
63
|
+
zipcode: values.zipcode.trim(),
|
|
64
|
+
city: values.city.trim(),
|
|
65
|
+
...(county ? { county } : {}),
|
|
66
|
+
telephone: values.phone.trim(),
|
|
67
|
+
email: values.email.trim(),
|
|
68
|
+
product: values.productName.trim(),
|
|
69
|
+
dealer: values.purchasedFromDealer.trim(),
|
|
70
|
+
purchaseDate: values.purchaseDate.trim(),
|
|
71
|
+
selfInstalled: values.selfInstalled === 'yes',
|
|
72
|
+
installerName: values.selfInstalled === 'no' ? values.installerName.trim() : undefined,
|
|
73
|
+
source: 'widget',
|
|
74
|
+
...(market ? { market } : {}),
|
|
75
|
+
...(options?.domain?.trim() ? { domain: options.domain.trim() } : {}),
|
|
76
|
+
...(options?.turnstileToken?.trim()
|
|
77
|
+
? { turnstileToken: options.turnstileToken.trim() }
|
|
78
|
+
: {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -5,12 +5,21 @@ export declare function isWidgetType(value: string | undefined): value is JotulW
|
|
|
5
5
|
export declare function asText(value: unknown): string | null;
|
|
6
6
|
export declare function formatDistance(dealer: DealerRecord): string | null;
|
|
7
7
|
export declare function isExclusiveDealer(dealer: DealerRecord): boolean;
|
|
8
|
+
/** Normalize optional widget prop arrays: undefined or empty means no filter. */
|
|
9
|
+
export declare function normalizeWidgetFilterList(values: string[] | undefined, transform?: (value: string) => string): string[] | undefined;
|
|
10
|
+
export declare function getDealerId(dealer: DealerRecord): string | undefined;
|
|
8
11
|
export declare function getDealerKey(dealer: DealerRecord, index: number): string;
|
|
9
12
|
export declare function getDealerName(dealer: DealerRecord, unknownLabel: string): string;
|
|
13
|
+
export declare function getDealerEmail(dealer: DealerRecord): string | undefined;
|
|
14
|
+
export declare function getDealerSalesRep(dealer: DealerRecord): string | undefined;
|
|
10
15
|
/** True when the tapped map pin matches a dealer already in the current successful search payload. */
|
|
11
16
|
export declare function isDealerInSearchResult(dealerLabel: string, searchResult: DealerSearchResponse | null, unknownDealerLabel: string): boolean;
|
|
12
17
|
export declare function getDealerAddressLines(dealer: DealerRecord): string[];
|
|
13
|
-
export declare function createInquiryFormValues(productName: string | undefined,
|
|
18
|
+
export declare function createInquiryFormValues(productName: string | undefined, dealer: DealerRecord, unknownLabel: string, market?: string): InquiryFormValues;
|
|
19
|
+
export declare function validateInquiryFormValues(values: InquiryFormValues): {
|
|
20
|
+
field: 'name' | 'email' | 'phone';
|
|
21
|
+
type: 'required' | 'invalid_email';
|
|
22
|
+
} | null;
|
|
14
23
|
export declare function isValidEmail(value: string): boolean;
|
|
15
24
|
export declare function getSafeWidgetErrorMessage(error: unknown, t: WidgetStrings): string;
|
|
16
25
|
export declare function renderReadyState(type: JotulWidgetType, t: WidgetStrings): string | null;
|
package/dist/utils.js
CHANGED
|
@@ -36,12 +36,35 @@ export function isExclusiveDealer(dealer) {
|
|
|
36
36
|
rawExclusive === 'y' ||
|
|
37
37
|
rawExclusive === 'Y');
|
|
38
38
|
}
|
|
39
|
+
/** Normalize optional widget prop arrays: undefined or empty means no filter. */
|
|
40
|
+
export function normalizeWidgetFilterList(values, transform) {
|
|
41
|
+
if (values == null || !Array.isArray(values))
|
|
42
|
+
return undefined;
|
|
43
|
+
const normalized = values
|
|
44
|
+
.map((value) => {
|
|
45
|
+
const trimmed = value?.trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return '';
|
|
48
|
+
return transform ? transform(trimmed) : trimmed;
|
|
49
|
+
})
|
|
50
|
+
.filter((value) => value.length > 0);
|
|
51
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
52
|
+
}
|
|
53
|
+
export function getDealerId(dealer) {
|
|
54
|
+
return asText(dealer.dealerId) ?? asText(dealer._id) ?? undefined;
|
|
55
|
+
}
|
|
39
56
|
export function getDealerKey(dealer, index) {
|
|
40
57
|
return String(dealer.dealerId ?? dealer.name ?? index);
|
|
41
58
|
}
|
|
42
59
|
export function getDealerName(dealer, unknownLabel) {
|
|
43
60
|
return asText(dealer.name) ?? asText(dealer.dealerId) ?? unknownLabel;
|
|
44
61
|
}
|
|
62
|
+
export function getDealerEmail(dealer) {
|
|
63
|
+
return asText(dealer.email) ?? undefined;
|
|
64
|
+
}
|
|
65
|
+
export function getDealerSalesRep(dealer) {
|
|
66
|
+
return asText(dealer.salesRep) ?? undefined;
|
|
67
|
+
}
|
|
45
68
|
/** True when the tapped map pin matches a dealer already in the current successful search payload. */
|
|
46
69
|
export function isDealerInSearchResult(dealerLabel, searchResult, unknownDealerLabel) {
|
|
47
70
|
if (searchResult?.ok !== true || !Array.isArray(searchResult.dealers))
|
|
@@ -64,16 +87,36 @@ export function getDealerAddressLines(dealer) {
|
|
|
64
87
|
.join(', ');
|
|
65
88
|
return [address, locationLine || null].filter((value) => Boolean(value));
|
|
66
89
|
}
|
|
67
|
-
export function createInquiryFormValues(productName,
|
|
90
|
+
export function createInquiryFormValues(productName, dealer, unknownLabel, market) {
|
|
68
91
|
return {
|
|
69
92
|
productName: productName?.trim() ?? '',
|
|
70
|
-
dealerName,
|
|
93
|
+
dealerName: getDealerName(dealer, unknownLabel),
|
|
94
|
+
dealerId: getDealerId(dealer),
|
|
95
|
+
dealerEmail: getDealerEmail(dealer),
|
|
96
|
+
salesRepresentative: getDealerSalesRep(dealer),
|
|
97
|
+
market: market?.trim().toUpperCase(),
|
|
98
|
+
requestCategory: 'price_quote',
|
|
71
99
|
name: '',
|
|
72
100
|
email: '',
|
|
73
101
|
phone: '',
|
|
74
102
|
comment: '',
|
|
75
103
|
};
|
|
76
104
|
}
|
|
105
|
+
export function validateInquiryFormValues(values) {
|
|
106
|
+
if (!values.name.trim()) {
|
|
107
|
+
return { field: 'name', type: 'required' };
|
|
108
|
+
}
|
|
109
|
+
if (!values.email.trim()) {
|
|
110
|
+
return { field: 'email', type: 'required' };
|
|
111
|
+
}
|
|
112
|
+
if (!isValidEmail(values.email.trim())) {
|
|
113
|
+
return { field: 'email', type: 'invalid_email' };
|
|
114
|
+
}
|
|
115
|
+
if (!values.phone.trim()) {
|
|
116
|
+
return { field: 'phone', type: 'required' };
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
77
120
|
export function isValidEmail(value) {
|
|
78
121
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
79
122
|
}
|
|
@@ -102,6 +145,6 @@ export function renderReadyState(type, t) {
|
|
|
102
145
|
case 'dealerFinder':
|
|
103
146
|
return t.readyDealerFinder;
|
|
104
147
|
case 'warrantyForm':
|
|
105
|
-
return
|
|
148
|
+
return null;
|
|
106
149
|
}
|
|
107
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jotul/jotul-widgets",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"sideEffects": [
|
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
"@types/leaflet": "^1.9.21",
|
|
24
24
|
"@types/leaflet.markercluster": "^1.5.6",
|
|
25
25
|
"autoprefixer": "^10.4.21",
|
|
26
|
+
"esbuild": "^0.25.12",
|
|
26
27
|
"postcss": "^8.5.3",
|
|
27
|
-
"tailwindcss": "^3.4.17"
|
|
28
|
+
"tailwindcss": "^3.4.17",
|
|
29
|
+
"xlsx": "^0.18.5"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"leaflet": "^1.9.4",
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
"build:css": "tailwindcss -i ./src/tw-entry.css -o ./dist/JotulWidget.css --minify",
|
|
36
38
|
"build:assets": "mkdir -p ./dist/images ./dist/i18n/locales && cp -R ./src/images/. ./dist/images/ && cp -R ./src/i18n/locales/. ./dist/i18n/locales/",
|
|
37
39
|
"build": "tsc -p tsconfig.build.json && npm run build:css && npm run build:assets",
|
|
40
|
+
"export-translations": "node scripts/exportWidgetStringsForTranslation.mjs",
|
|
38
41
|
"clean": "rm -rf dist",
|
|
39
42
|
"watch:css": "tailwindcss -i ./src/tw-entry.css -o ./dist/JotulWidget.css --watch",
|
|
40
43
|
"watch:ts": "tsc -p tsconfig.build.json -w --preserveWatchOutput",
|