@jotul/jotul-widgets 1.2.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -28
- package/dist/JotulWidget.css +1 -1
- package/dist/JotulWidget.d.ts +1 -1
- package/dist/JotulWidget.js +355 -165
- 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 +59 -2
- 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 +11 -1
- package/dist/utils.js +52 -3
- package/package.json +5 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WidgetTracker } from './widgetTracking';
|
|
2
|
+
export declare const WidgetTrackingContext: import("react").Context<{
|
|
3
|
+
resetSession(): void;
|
|
4
|
+
trackWidgetOpened(): void;
|
|
5
|
+
trackDealerSearch(searchTerm: string, resultCount: number): void;
|
|
6
|
+
trackDealerListView(resultCount: number): void;
|
|
7
|
+
trackDealerSelect(dealerId: string | undefined, dealerName: string): void;
|
|
8
|
+
trackDealerPhoneClick(dealerId: string | undefined, dealerName: string): void;
|
|
9
|
+
trackFormStart(dealerId: string | undefined, dealerName: string): void;
|
|
10
|
+
trackFormStepComplete(field: "name" | "email" | "phone" | "comment"): void;
|
|
11
|
+
trackFormError(error: import("./widgetTracking").InquiryValidationError): void;
|
|
12
|
+
trackSuccessfulSubmit(): void;
|
|
13
|
+
} | null>;
|
|
14
|
+
export declare function useWidgetTracking(): WidgetTracker | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
dataLayer?: Array<Record<string, unknown>>;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export declare function ensureDataLayer(): Array<Record<string, unknown>>;
|
|
7
|
+
export declare function pushDataLayerEvent(event: string, payload: Record<string, unknown>): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function ensureDataLayer() {
|
|
2
|
+
if (typeof window === 'undefined') {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
window.dataLayer = window.dataLayer || [];
|
|
6
|
+
return window.dataLayer;
|
|
7
|
+
}
|
|
8
|
+
export function pushDataLayerEvent(event, payload) {
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return;
|
|
11
|
+
ensureDataLayer();
|
|
12
|
+
const entry = {
|
|
13
|
+
event,
|
|
14
|
+
...payload,
|
|
15
|
+
};
|
|
16
|
+
window.dataLayer.push(entry);
|
|
17
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type WidgetFormId = 'findDealerDrawer' | 'productPage';
|
|
2
|
+
export type WidgetStepName = 'dealer_list' | 'form' | 'confirmation';
|
|
3
|
+
export type WidgetTrackingPayload = {
|
|
4
|
+
contact_reason?: string;
|
|
5
|
+
dealer_id?: string;
|
|
6
|
+
dealer_name?: string;
|
|
7
|
+
error_field?: string;
|
|
8
|
+
error_type?: string;
|
|
9
|
+
form_id?: string;
|
|
10
|
+
language?: string;
|
|
11
|
+
market?: string;
|
|
12
|
+
page_type?: string;
|
|
13
|
+
product_id?: string;
|
|
14
|
+
product_name?: string;
|
|
15
|
+
result_count?: number;
|
|
16
|
+
search_term?: string;
|
|
17
|
+
step_name?: WidgetStepName;
|
|
18
|
+
step_number?: number;
|
|
19
|
+
};
|
|
20
|
+
export type WidgetTrackingConfig = {
|
|
21
|
+
formId: WidgetFormId;
|
|
22
|
+
language?: string;
|
|
23
|
+
market?: string;
|
|
24
|
+
pageType: string;
|
|
25
|
+
productId?: string;
|
|
26
|
+
productName?: string;
|
|
27
|
+
};
|
|
28
|
+
export type WidgetTrackingStepState = {
|
|
29
|
+
inquiryValues: unknown | null;
|
|
30
|
+
isInquirySubmitted: boolean;
|
|
31
|
+
selectedDealerId?: string | null;
|
|
32
|
+
selectedDealerName?: string | null;
|
|
33
|
+
};
|
|
34
|
+
export type InquiryValidationError = {
|
|
35
|
+
field: 'name' | 'email' | 'phone';
|
|
36
|
+
type: 'required' | 'invalid_email';
|
|
37
|
+
};
|
|
38
|
+
export declare function getWidgetStep(state: WidgetTrackingStepState): {
|
|
39
|
+
step_name: WidgetStepName;
|
|
40
|
+
step_number: number;
|
|
41
|
+
};
|
|
42
|
+
export declare function createWidgetTracker(config: WidgetTrackingConfig, getState: () => WidgetTrackingStepState): {
|
|
43
|
+
resetSession(): void;
|
|
44
|
+
trackWidgetOpened(): void;
|
|
45
|
+
trackDealerSearch(searchTerm: string, resultCount: number): void;
|
|
46
|
+
trackDealerListView(resultCount: number): void;
|
|
47
|
+
trackDealerSelect(dealerId: string | undefined, dealerName: string): void;
|
|
48
|
+
trackDealerPhoneClick(dealerId: string | undefined, dealerName: string): void;
|
|
49
|
+
trackFormStart(dealerId: string | undefined, dealerName: string): void;
|
|
50
|
+
trackFormStepComplete(field: "name" | "email" | "phone" | "comment"): void;
|
|
51
|
+
trackFormError(error: InquiryValidationError): void;
|
|
52
|
+
trackSuccessfulSubmit(): void;
|
|
53
|
+
};
|
|
54
|
+
export type WidgetTracker = ReturnType<typeof createWidgetTracker>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { pushDataLayerEvent } from './gtm';
|
|
2
|
+
export function getWidgetStep(state) {
|
|
3
|
+
if (state.isInquirySubmitted && state.inquiryValues == null) {
|
|
4
|
+
return { step_name: 'confirmation', step_number: 3 };
|
|
5
|
+
}
|
|
6
|
+
if (state.inquiryValues != null) {
|
|
7
|
+
return { step_name: 'form', step_number: 2 };
|
|
8
|
+
}
|
|
9
|
+
return { step_name: 'dealer_list', step_number: 1 };
|
|
10
|
+
}
|
|
11
|
+
function omitUndefined(payload) {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
14
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
15
|
+
out[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
export function createWidgetTracker(config, getState) {
|
|
21
|
+
const basePayload = (overrides) => {
|
|
22
|
+
const state = getState();
|
|
23
|
+
const step = getWidgetStep(state);
|
|
24
|
+
return {
|
|
25
|
+
form_id: config.formId,
|
|
26
|
+
language: config.language,
|
|
27
|
+
market: config.market,
|
|
28
|
+
page_type: config.pageType,
|
|
29
|
+
product_id: config.productId,
|
|
30
|
+
product_name: config.productName,
|
|
31
|
+
dealer_id: state.selectedDealerId ?? undefined,
|
|
32
|
+
dealer_name: state.selectedDealerName ?? undefined,
|
|
33
|
+
step_name: step.step_name,
|
|
34
|
+
step_number: step.step_number,
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const push = (event, overrides) => {
|
|
39
|
+
pushDataLayerEvent(event, omitUndefined(basePayload(overrides)));
|
|
40
|
+
};
|
|
41
|
+
let lastDealerSearchKey = null;
|
|
42
|
+
let dealerListViewTracked = false;
|
|
43
|
+
return {
|
|
44
|
+
resetSession() {
|
|
45
|
+
lastDealerSearchKey = null;
|
|
46
|
+
dealerListViewTracked = false;
|
|
47
|
+
},
|
|
48
|
+
trackWidgetOpened() {
|
|
49
|
+
push('widget_opened', {
|
|
50
|
+
step_name: 'dealer_list',
|
|
51
|
+
step_number: 1,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
trackDealerSearch(searchTerm, resultCount) {
|
|
55
|
+
const normalizedTerm = searchTerm.trim();
|
|
56
|
+
const dedupeKey = `${normalizedTerm}|${resultCount}`;
|
|
57
|
+
if (lastDealerSearchKey === dedupeKey)
|
|
58
|
+
return;
|
|
59
|
+
lastDealerSearchKey = dedupeKey;
|
|
60
|
+
push('dealer_search', {
|
|
61
|
+
search_term: normalizedTerm || undefined,
|
|
62
|
+
result_count: resultCount,
|
|
63
|
+
step_name: 'dealer_list',
|
|
64
|
+
step_number: 1,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
trackDealerListView(resultCount) {
|
|
68
|
+
if (dealerListViewTracked)
|
|
69
|
+
return;
|
|
70
|
+
dealerListViewTracked = true;
|
|
71
|
+
push('dealer_list_view', {
|
|
72
|
+
result_count: resultCount,
|
|
73
|
+
step_name: 'dealer_list',
|
|
74
|
+
step_number: 1,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
trackDealerSelect(dealerId, dealerName) {
|
|
78
|
+
push('dealer_select', {
|
|
79
|
+
dealer_id: dealerId,
|
|
80
|
+
dealer_name: dealerName,
|
|
81
|
+
step_name: 'dealer_list',
|
|
82
|
+
step_number: 1,
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
trackDealerPhoneClick(dealerId, dealerName) {
|
|
86
|
+
push('dealer_phone_click', {
|
|
87
|
+
dealer_id: dealerId,
|
|
88
|
+
dealer_name: dealerName,
|
|
89
|
+
step_name: 'dealer_list',
|
|
90
|
+
step_number: 1,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
trackFormStart(dealerId, dealerName) {
|
|
94
|
+
push('form_start', {
|
|
95
|
+
dealer_id: dealerId,
|
|
96
|
+
dealer_name: dealerName,
|
|
97
|
+
step_name: 'form',
|
|
98
|
+
step_number: 2,
|
|
99
|
+
});
|
|
100
|
+
if (config.formId === 'findDealerDrawer') {
|
|
101
|
+
push('started_dealer_request', {
|
|
102
|
+
dealer_id: dealerId,
|
|
103
|
+
dealer_name: dealerName,
|
|
104
|
+
step_name: 'form',
|
|
105
|
+
step_number: 2,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
push('started_product_inquiry', {
|
|
110
|
+
dealer_id: dealerId,
|
|
111
|
+
dealer_name: dealerName,
|
|
112
|
+
step_name: 'form',
|
|
113
|
+
step_number: 2,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
trackFormStepComplete(field) {
|
|
118
|
+
pushDataLayerEvent('form_step_complete', {
|
|
119
|
+
...omitUndefined(basePayload({
|
|
120
|
+
step_name: 'form',
|
|
121
|
+
step_number: 2,
|
|
122
|
+
})),
|
|
123
|
+
field,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
trackFormError(error) {
|
|
127
|
+
push('form_error', {
|
|
128
|
+
error_field: error.field,
|
|
129
|
+
error_type: error.type,
|
|
130
|
+
step_name: 'form',
|
|
131
|
+
step_number: 2,
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
trackSuccessfulSubmit() {
|
|
135
|
+
const contactReason = config.formId === 'findDealerDrawer' ? 'dealer_request' : 'product_inquiry';
|
|
136
|
+
const eventName = config.formId === 'findDealerDrawer' ? 'dealer_request' : 'product_inquiry';
|
|
137
|
+
push(eventName, {
|
|
138
|
+
contact_reason: contactReason,
|
|
139
|
+
step_name: 'confirmation',
|
|
140
|
+
step_number: 3,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
|
-
import type { CheckWidgetAuthorizationOptions, DealerSearchResponse, LocationAutocompleteResponse, WidgetAuthClientResponse } from './types';
|
|
1
|
+
import type { CheckWidgetAuthorizationOptions, DealerByIdResponse, DealerSearchResponse, InquirySubmitResponse, LocationAutocompleteResponse, WarrantySubmitResponse, WidgetAuthClientResponse } from './types';
|
|
2
|
+
import type { InquirySubmitPayload } from './utils/inquirySubmit';
|
|
2
3
|
/** Client-side default when JSON parse fails (English; localized in UI). */
|
|
3
4
|
export declare const GENERIC_WIDGET_ERROR = "Dealer finder is currently unavailable. Please try again later.";
|
|
4
5
|
export declare function checkWidgetAuthorization(options?: CheckWidgetAuthorizationOptions): Promise<WidgetAuthClientResponse>;
|
|
5
6
|
export declare function searchDealersByPostalCode(postalCode: string, options?: CheckWidgetAuthorizationOptions, scope?: 'list' | 'map'): Promise<DealerSearchResponse>;
|
|
6
7
|
export declare function searchDealersByCoordinates(latitude: number, longitude: number, options?: CheckWidgetAuthorizationOptions, scope?: 'list' | 'map'): Promise<DealerSearchResponse>;
|
|
8
|
+
export declare function fetchDealerById(dealerId: string, options?: CheckWidgetAuthorizationOptions): Promise<DealerByIdResponse>;
|
|
7
9
|
export declare function searchLocationSuggestions(query: string, options?: CheckWidgetAuthorizationOptions): Promise<LocationAutocompleteResponse>;
|
|
10
|
+
export type WarrantySubmitPayload = {
|
|
11
|
+
name: string;
|
|
12
|
+
address: string;
|
|
13
|
+
city: string;
|
|
14
|
+
zipcode: string;
|
|
15
|
+
telephone: string;
|
|
16
|
+
email: string;
|
|
17
|
+
product: string;
|
|
18
|
+
dealer?: string;
|
|
19
|
+
purchaseDate?: string;
|
|
20
|
+
selfInstalled?: boolean;
|
|
21
|
+
installerName?: string;
|
|
22
|
+
source?: 'widget';
|
|
23
|
+
turnstileToken?: string;
|
|
24
|
+
};
|
|
25
|
+
export type { InquirySubmitPayload } from './utils/inquirySubmit';
|
|
26
|
+
export declare function submitInquiry(body: InquirySubmitPayload, options?: {
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
fetcher?: typeof fetch;
|
|
29
|
+
}): Promise<InquirySubmitResponse>;
|
|
30
|
+
export declare function submitWarranty(body: WarrantySubmitPayload, options?: {
|
|
31
|
+
endpoint?: string;
|
|
32
|
+
fetcher?: typeof fetch;
|
|
33
|
+
}): Promise<WarrantySubmitResponse>;
|
package/dist/api.js
CHANGED
|
@@ -113,6 +113,32 @@ export async function searchDealersByCoordinates(latitude, longitude, options, s
|
|
|
113
113
|
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
export async function fetchDealerById(dealerId, options) {
|
|
117
|
+
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
118
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
119
|
+
const params = new URLSearchParams({
|
|
120
|
+
type: 'dealer',
|
|
121
|
+
dealerId: dealerId.trim(),
|
|
122
|
+
});
|
|
123
|
+
appendLocaleAndMarket(params, options);
|
|
124
|
+
let response;
|
|
125
|
+
try {
|
|
126
|
+
response = await fetcher(`${endpoint}?${params.toString()}`, {
|
|
127
|
+
method: 'GET',
|
|
128
|
+
headers: { Accept: 'application/json' },
|
|
129
|
+
cache: 'no-store',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
return (await response.json());
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
116
142
|
export async function searchLocationSuggestions(query, options) {
|
|
117
143
|
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
118
144
|
const fetcher = options?.fetcher ?? fetch;
|
|
@@ -142,3 +168,51 @@ export async function searchLocationSuggestions(query, options) {
|
|
|
142
168
|
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
143
169
|
}
|
|
144
170
|
}
|
|
171
|
+
export async function submitInquiry(body, options) {
|
|
172
|
+
const endpoint = options?.endpoint ?? '/api/jotul/submission';
|
|
173
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
174
|
+
let response;
|
|
175
|
+
try {
|
|
176
|
+
response = await fetcher(endpoint, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: {
|
|
179
|
+
Accept: 'application/json',
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify(body),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return (await response.json());
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function submitWarranty(body, options) {
|
|
196
|
+
const endpoint = options?.endpoint ?? '/api/jotul/warranty';
|
|
197
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
198
|
+
let response;
|
|
199
|
+
try {
|
|
200
|
+
response = await fetcher(endpoint, {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
Accept: 'application/json',
|
|
204
|
+
'Content-Type': 'application/json',
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify(body),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
return (await response.json());
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'leaflet/dist/leaflet.css';
|
|
2
2
|
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
|
3
3
|
import type { WidgetStrings } from '../i18n/widgetStrings';
|
|
4
|
-
import type { DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, JotulWidgetScope, LocationSuggestion } from '../types';
|
|
4
|
+
import type { DealerRecord, DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, JotulWidgetScope, LocationSuggestion } from '../types';
|
|
5
5
|
export type FindDealerDrawerWidgetProps = {
|
|
6
6
|
t: WidgetStrings;
|
|
7
7
|
buttonStyling?: JotulWidgetButtonStyling;
|
|
@@ -14,6 +14,9 @@ export type FindDealerDrawerWidgetProps = {
|
|
|
14
14
|
mapSearchResult?: DealerSearchResponse | null;
|
|
15
15
|
inquiryValues: InquiryFormValues | null;
|
|
16
16
|
inquiryError: string | null;
|
|
17
|
+
isSubmittingInquiry?: boolean;
|
|
18
|
+
turnstileSiteKey?: string;
|
|
19
|
+
turnstileResetKey?: number;
|
|
17
20
|
isInquirySubmitted: boolean;
|
|
18
21
|
selectedDealerName: string | null;
|
|
19
22
|
isManualSearchEnabled: boolean;
|
|
@@ -26,9 +29,9 @@ export type FindDealerDrawerWidgetProps = {
|
|
|
26
29
|
onSuggestionSelect: (suggestion: LocationSuggestion) => void;
|
|
27
30
|
onDismissSuggestions: () => void;
|
|
28
31
|
onInquiryClose: () => void;
|
|
29
|
-
onInquirySubmit: () => void;
|
|
32
|
+
onInquirySubmit: (turnstileToken: string | null) => void;
|
|
30
33
|
onInquiryFieldChange: (key: keyof InquiryFormValues, value: string) => void;
|
|
31
|
-
onStartInquiry: (
|
|
34
|
+
onStartInquiry: (dealer: DealerRecord) => void;
|
|
32
35
|
onMapDealerSelect?: (dealer: {
|
|
33
36
|
dealerName: string;
|
|
34
37
|
latitude: number;
|
|
@@ -36,4 +39,4 @@ export type FindDealerDrawerWidgetProps = {
|
|
|
36
39
|
}) => void;
|
|
37
40
|
onClose: () => void;
|
|
38
41
|
};
|
|
39
|
-
export declare function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClose, }: FindDealerDrawerWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isSubmittingInquiry, turnstileSiteKey, turnstileResetKey, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClose, }: FindDealerDrawerWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,7 +18,7 @@ import { MARKER_CLUSTER_LINK_KM, MARKER_CLUSTER_MIN_GROUP, partitionDealersForMa
|
|
|
18
18
|
import { loadLeafletMarkerCluster } from '../utils/loadLeafletMarkerCluster';
|
|
19
19
|
import { markerClusterCountIconHtml } from '../utils/markerClusterIconHtml';
|
|
20
20
|
import { JOTUL_BRAND_PRIMARY_HEX } from '../constants';
|
|
21
|
-
import { isExclusiveDealer } from '../utils';
|
|
21
|
+
import { isExclusiveDealer, getDealerName } from '../utils';
|
|
22
22
|
const OSM_MINIMAL_TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
|
|
23
23
|
const EMPTY_DEALERS = [];
|
|
24
24
|
function mapPointsBoundsKey(points, defaultCenter) {
|
|
@@ -43,17 +43,18 @@ function readNumber(value) {
|
|
|
43
43
|
}
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
const raw = dealer.name;
|
|
48
|
-
return typeof raw === 'string' && raw.trim() ? raw.trim() : 'Unknown dealer';
|
|
49
|
-
}
|
|
50
|
-
function getDealerMapPoint(dealer) {
|
|
46
|
+
function getDealerMapPoint(dealer, unknownDealerLabel) {
|
|
51
47
|
const latitude = readNumber(dealer.latitude);
|
|
52
48
|
const longitude = readNumber(dealer.longitude);
|
|
53
49
|
if (latitude == null || longitude == null)
|
|
54
50
|
return null;
|
|
55
51
|
const isExclusive = isExclusiveDealer(dealer);
|
|
56
|
-
return {
|
|
52
|
+
return {
|
|
53
|
+
dealerName: getDealerName(dealer, unknownDealerLabel),
|
|
54
|
+
latitude,
|
|
55
|
+
longitude,
|
|
56
|
+
isExclusive,
|
|
57
|
+
};
|
|
57
58
|
}
|
|
58
59
|
function toAssetSrc(value) {
|
|
59
60
|
return typeof value === 'string' ? value : value.src;
|
|
@@ -224,7 +225,7 @@ function ClusteredMapMarkers({ points, pointsBoundsKey, clusterThemeKey, cluster
|
|
|
224
225
|
}, [map, pointsBoundsKey, clusterThemeKey]);
|
|
225
226
|
return null;
|
|
226
227
|
}
|
|
227
|
-
export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClose, }) {
|
|
228
|
+
export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isSubmittingInquiry = false, turnstileSiteKey, turnstileResetKey = 0, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClose, }) {
|
|
228
229
|
const rawDealers = (searchResult?.dealers ?? EMPTY_DEALERS);
|
|
229
230
|
const dealers = useMemo(() => (scope === 'ildstedet' ? rawDealers.filter(isExclusiveDealer) : rawDealers), [rawDealers, scope]);
|
|
230
231
|
const rawMapDealers = (mapSearchResult?.dealers ?? rawDealers);
|
|
@@ -246,8 +247,8 @@ export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, market
|
|
|
246
247
|
return () => media.removeEventListener('change', update);
|
|
247
248
|
}, []);
|
|
248
249
|
useEffect(() => {
|
|
249
|
-
setActiveDealerName(dealers.length > 0 ? getDealerName(dealers[0]) : null);
|
|
250
|
-
}, [dealers]);
|
|
250
|
+
setActiveDealerName(dealers.length > 0 ? getDealerName(dealers[0], t.unknownDealer) : null);
|
|
251
|
+
}, [dealers, t.unknownDealer]);
|
|
251
252
|
useEffect(() => {
|
|
252
253
|
setVisibleDealerCount(10);
|
|
253
254
|
}, [dealers]);
|
|
@@ -255,13 +256,15 @@ export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, market
|
|
|
255
256
|
useEffect(() => {
|
|
256
257
|
if (activeDealerName == null)
|
|
257
258
|
return;
|
|
258
|
-
const activeIndex = dealers.findIndex((dealer) => getDealerName(dealer) === activeDealerName);
|
|
259
|
+
const activeIndex = dealers.findIndex((dealer) => getDealerName(dealer, t.unknownDealer) === activeDealerName);
|
|
259
260
|
if (activeIndex < 0 || activeIndex < visibleDealerCount)
|
|
260
261
|
return;
|
|
261
262
|
const nextCount = Math.ceil((activeIndex + 1) / 10) * 10;
|
|
262
263
|
setVisibleDealerCount(nextCount);
|
|
263
264
|
}, [activeDealerName, dealers, visibleDealerCount]);
|
|
264
|
-
const mapPoints = useMemo(() => mapDealers
|
|
265
|
+
const mapPoints = useMemo(() => mapDealers
|
|
266
|
+
.map((dealer) => getDealerMapPoint(dealer, t.unknownDealer))
|
|
267
|
+
.filter((v) => v != null), [mapDealers, t.unknownDealer]);
|
|
265
268
|
useEffect(() => {
|
|
266
269
|
setClusterUnavailable(false);
|
|
267
270
|
}, [mapPoints]);
|
|
@@ -289,7 +292,7 @@ export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, market
|
|
|
289
292
|
const showInquirySuccessScreen = isInquirySubmitted && !inquiryFormOpen;
|
|
290
293
|
const successPanel = (_jsx("div", { className: "jwi-flex jwi-w-full jwi-items-center jwi-justify-center jwi-bg-white jwi-p-2", children: _jsxs("div", { className: "jwi-flex jwi-w-full jwi-max-w-[520px] jwi-flex-col jwi-items-center jwi-gap-4 jwi-rounded-[10px] jwi-bg-white jwi-p-8 jwi-text-center", children: [_jsx("div", { className: "jwi-text-2xl jwi-font-semibold jwi-text-[#111111]", children: t.inquiryThankYouTitle }), _jsx("div", { className: "jwi-text-sm jwi-leading-6 jwi-text-[#333333]", children: t.inquirySentSuccess }), _jsx("button", { type: "button", onClick: onClose, className: "jwi-mt-2 jwi-inline-flex jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-md jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-5 jwi-py-2.5 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: t.goBack })] }) }));
|
|
291
294
|
const canShowMore = visibleDealerCount < dealers.length;
|
|
292
|
-
const showMoreButton = canShowMore ? (_jsx("button", { type: "button", onClick: () => setVisibleDealerCount((count) => Math.min(count + 10, dealers.length)), className: "jwi-mt-3 jwi-inline-flex jwi-w-full jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-md jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-3 jwi-py-2 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children:
|
|
295
|
+
const showMoreButton = canShowMore ? (_jsx("button", { type: "button", onClick: () => setVisibleDealerCount((count) => Math.min(count + 10, dealers.length)), className: "jwi-mt-3 jwi-inline-flex jwi-w-full jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-md jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-3 jwi-py-2 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: t.showMore })) : null;
|
|
293
296
|
const mapCanvas = (_jsxs(MapContainer, { center: defaultCenter ?? [59.9139, 10.7522], zoom: 6, className: "jwi-h-full jwi-w-full", zoomControl: true, children: [_jsx(TileLayer, { attribution: '\u00A9 <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noreferrer">OpenStreetMap</a> contributors \u00A9 <a href="https://carto.com/attributions" target="_blank" rel="noreferrer">CARTO</a>', url: OSM_MINIMAL_TILE_URL, maxZoom: 19 }), _jsx(FitMapBounds, { boundsKey: mapBoundsKey, points: mapPoints, defaultCenter: defaultCenter }), _jsx(FocusActiveDealer, { point: activeMapPoint }), _jsx(ClusteredMapMarkers, { points: mapPoints, pointsBoundsKey: mapBoundsKey, clusterThemeKey: mapClusterTheme.key, clusterBrandFill: mapClusterTheme.fill, clusterBrandLabel: mapClusterTheme.label, markets: markets, activeDealerName: activeDealerName, onUnavailable: () => setClusterUnavailable(true), onSelectDealer: (dealer) => {
|
|
294
297
|
setActiveDealerName(dealer.dealerName);
|
|
295
298
|
onMapDealerSelect?.(dealer);
|
|
@@ -307,7 +310,7 @@ export function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, market
|
|
|
307
310
|
},
|
|
308
311
|
}, children: _jsx(Tooltip, { children: point.dealerName }) }, `${point.dealerName}-${point.latitude}-${point.longitude}`));
|
|
309
312
|
})] }));
|
|
310
|
-
const leftContent = (_jsx(_Fragment, { children: showInquirySuccessScreen ? (successPanel) : inquiryFormOpen ? (_jsx(InquiryForm, { t: t, buttonStyling: buttonStyling, inquiryValues: inquiryValues, inquiryError: inquiryError, embedded: true, onInquiryClose: onInquiryClose, onInquirySubmit: onInquirySubmit, onInquiryFieldChange: onInquiryFieldChange })) : (_jsxs(_Fragment, { children: [_jsx(LocationSearch, { t: t, isManualSearchEnabled: isManualSearchEnabled, query: query, suggestions: suggestions, suggestionsOpen: suggestionsOpen, isSuggestionsLoading: isSuggestionsLoading, onQueryChange: onQueryChange, onQuerySubmit: onQuerySubmit, onSuggestionSelect: onSuggestionSelect, onDismissSuggestions: onDismissSuggestions }), locationError != null && !isManualSearchEnabled && (_jsx(StatusBanner, { tone: "error", children: locationError })), searchResult?.ok === false && (_jsx(StatusBanner, { tone: "error", children: searchResult.error ?? '' })), isSearching && (_jsxs("div", { className: "jwi-flex jwi-flex-col", children: [_jsx("div", { className: "jwi-w-full jwi-flex-shrink-0 jwi-border-b jwi-border-[#e6e1d7] jwi-pb-3", children: _jsx("div", { className: "jwi-h-5 jwi-w-48 jwi-animate-[pulse_2s_ease-in-out_infinite] jwi-rounded-full jwi-bg-[#ece8df]" }) }), _jsxs("div", { className: "jwi-mt-3 jwi-mr-[-12px] jwi-flex jwi-flex-col jwi-gap-4 jwi-overflow-y-auto jwi-pr-[12px]", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })), searchResult?.ok && !isSearching && (_jsxs(_Fragment, { children: [_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, fitAvailableHeight: !isMobileViewport, autoScrollToActive: true, enableInternalScroll: !isMobileViewport, maxHeightClassName: "jwi-max-h-none", t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: setActiveDealerName }), showMoreButton] }))] })) }));
|
|
313
|
+
const leftContent = (_jsx(_Fragment, { children: showInquirySuccessScreen ? (successPanel) : inquiryFormOpen ? (_jsx(InquiryForm, { t: t, buttonStyling: buttonStyling, inquiryValues: inquiryValues, inquiryError: inquiryError, isSubmitting: isSubmittingInquiry, turnstileSiteKey: turnstileSiteKey, turnstileResetKey: turnstileResetKey, embedded: true, onInquiryClose: onInquiryClose, onInquirySubmit: onInquirySubmit, onInquiryFieldChange: onInquiryFieldChange })) : (_jsxs(_Fragment, { children: [_jsx(LocationSearch, { t: t, isManualSearchEnabled: isManualSearchEnabled, query: query, suggestions: suggestions, suggestionsOpen: suggestionsOpen, isSuggestionsLoading: isSuggestionsLoading, onQueryChange: onQueryChange, onQuerySubmit: onQuerySubmit, onSuggestionSelect: onSuggestionSelect, onDismissSuggestions: onDismissSuggestions }), locationError != null && !isManualSearchEnabled && (_jsx(StatusBanner, { tone: "error", children: locationError })), searchResult?.ok === false && (_jsx(StatusBanner, { tone: "error", children: searchResult.error ?? '' })), isSearching && (_jsxs("div", { className: "jwi-flex jwi-flex-col", children: [_jsx("div", { className: "jwi-w-full jwi-flex-shrink-0 jwi-border-b jwi-border-[#e6e1d7] jwi-pb-3", children: _jsx("div", { className: "jwi-h-5 jwi-w-48 jwi-animate-[pulse_2s_ease-in-out_infinite] jwi-rounded-full jwi-bg-[#ece8df]" }) }), _jsxs("div", { className: "jwi-mt-3 jwi-mr-[-12px] jwi-flex jwi-flex-col jwi-gap-4 jwi-overflow-y-auto jwi-pr-[12px]", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })), searchResult?.ok && !isSearching && (_jsxs(_Fragment, { children: [_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, fitAvailableHeight: !isMobileViewport, autoScrollToActive: true, enableInternalScroll: !isMobileViewport, maxHeightClassName: "jwi-max-h-none", t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: setActiveDealerName }), showMoreButton] }))] })) }));
|
|
311
314
|
if (isMobileViewport) {
|
|
312
315
|
return (_jsxs("div", { className: "jwi-relative jwi-flex jwi-h-full jwi-flex-col jwi-bg-white", children: [_jsx("div", { className: "jwi-flex jwi-justify-end jwi-bg-white jwi-px-4 jwi-pt-3", children: _jsx("button", { type: "button", onClick: onClose, className: "jwi-inline-flex jwi-h-9 jwi-w-9 jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-full jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-text-xl jwi-leading-none jwi-text-[#111111]", "aria-label": t.closeMap, children: "\u00D7" }) }), _jsx("div", { className: "jwi-min-h-0 jwi-flex-1 jwi-overflow-y-auto jwi-bg-white jwi-p-4 jwi-pb-24", children: leftContent }), !inquiryFormOpen && !showInquirySuccessScreen && !mobileMapExpanded && (_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded(true), className: "jwi-absolute jwi-inset-x-0 jwi-bottom-0 jwi-z-20 jwi-flex jwi-h-14 jwi-items-center jwi-justify-center jwi-gap-2 jwi-rounded-t-[16px] jwi-border-t jwi-border-[#e6e1d7] jwi-bg-white jwi-text-sm jwi-font-semibold jwi-text-[#111111] jwi-shadow-[0_-6px_20px_rgba(0,0,0,0.12)]", children: [t.openMap, _jsx("span", { className: "jwi-inline-flex", style: { transform: 'rotate(-90deg)' }, children: _jsx(ArrowRightIcon, { className: "jwi-h-4 jwi-w-4 jwi-shrink-0" }) })] })), mobileMapExpanded && !inquiryFormOpen && !showInquirySuccessScreen && (_jsxs("div", { className: "jwi-absolute jwi-inset-x-0 jwi-bottom-0 jwi-z-30 jwi-h-[78vh] jwi-overflow-hidden jwi-rounded-t-[16px] jwi-bg-white jwi-shadow-[0_-12px_36px_rgba(0,0,0,0.22)]", children: [_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded(false), className: "jwi-flex jwi-h-12 jwi-w-full jwi-items-center jwi-justify-center jwi-gap-2 jwi-border-b jwi-border-[#e6e1d7] jwi-bg-white jwi-text-sm jwi-font-semibold jwi-text-[#111111]", children: [t.closeMapMobile, _jsx("span", { className: "jwi-inline-flex", style: { transform: 'rotate(90deg)' }, children: _jsx(ArrowRightIcon, { className: "jwi-h-4 jwi-w-4 jwi-shrink-0" }) })] }), _jsx("div", { className: "jwi-h-[calc(78vh-48px)] jwi-w-full", children: mapCanvas })] }))] }));
|
|
313
316
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InquiryFormValues } from '../types';
|
|
1
2
|
type InquiryFieldProps = {
|
|
2
3
|
label: string;
|
|
3
4
|
value: string;
|
|
@@ -5,6 +6,7 @@ type InquiryFieldProps = {
|
|
|
5
6
|
type?: 'text' | 'email' | 'tel';
|
|
6
7
|
readOnly?: boolean;
|
|
7
8
|
multiline?: boolean;
|
|
9
|
+
fieldName?: keyof InquiryFormValues;
|
|
8
10
|
};
|
|
9
|
-
export declare function InquiryField({ label, value, onChange, type, readOnly, multiline, }: InquiryFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function InquiryField({ label, value, onChange, type, readOnly, multiline, fieldName, }: InquiryFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
10
12
|
export {};
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { R10 } from '../constants';
|
|
3
|
-
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { useWidgetTracking } from '../analytics/WidgetTrackingContext';
|
|
5
|
+
export function InquiryField({ label, value, onChange, type = 'text', readOnly = false, multiline = false, fieldName, }) {
|
|
6
|
+
const tracking = useWidgetTracking();
|
|
7
|
+
const completedRef = useRef(false);
|
|
4
8
|
const sharedClassName = `jwi-w-full ${R10} jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-4 jwi-py-3 jwi-text-sm jwi-leading-[1.4] jwi-text-[#111111] jwi-outline-none placeholder:jwi-text-[#767676] focus:jwi-border-[#111111]`;
|
|
5
9
|
const readOnlyClassName = readOnly ? ' jwi-bg-[#f7f5ef]' : '';
|
|
6
|
-
|
|
10
|
+
const handleBlur = () => {
|
|
11
|
+
if (readOnly || !fieldName || completedRef.current)
|
|
12
|
+
return;
|
|
13
|
+
if (!value.trim())
|
|
14
|
+
return;
|
|
15
|
+
completedRef.current = true;
|
|
16
|
+
if (fieldName === 'name' ||
|
|
17
|
+
fieldName === 'email' ||
|
|
18
|
+
fieldName === 'phone' ||
|
|
19
|
+
fieldName === 'comment') {
|
|
20
|
+
tracking?.trackFormStepComplete(fieldName);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return (_jsxs("label", { className: "jwi-flex jwi-flex-col jwi-gap-1.5", children: [_jsx("span", { className: "jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: label }), multiline ? (_jsx("textarea", { value: value, onChange: (event) => onChange(event.target.value), onBlur: handleBlur, readOnly: readOnly, rows: 4, className: `${sharedClassName}${readOnlyClassName} jwi-resize-y` })) : (_jsx("input", { type: type, value: value, onChange: (event) => onChange(event.target.value), onBlur: handleBlur, readOnly: readOnly, className: `${sharedClassName}${readOnlyClassName}` }))] }));
|
|
7
24
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InquiryRequestCategory } from '../types';
|
|
2
|
+
type InquirySelectOption = {
|
|
3
|
+
value: InquiryRequestCategory;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
type InquirySelectFieldProps = {
|
|
7
|
+
label: string;
|
|
8
|
+
value: InquiryRequestCategory;
|
|
9
|
+
options: InquirySelectOption[];
|
|
10
|
+
onChange: (value: InquiryRequestCategory) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function InquirySelectField({ label, value, options, onChange, }: InquirySelectFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { R10 } from '../constants';
|
|
3
|
+
export function InquirySelectField({ label, value, options, onChange, }) {
|
|
4
|
+
return (_jsxs("label", { className: "jwi-flex jwi-flex-col jwi-gap-1.5", children: [_jsx("span", { className: "jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: label }), _jsx("select", { value: value, onChange: (event) => onChange(event.target.value), className: `jwi-w-full ${R10} jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-4 jwi-py-3 jwi-text-sm jwi-leading-[1.4] jwi-text-[#111111] jwi-outline-none focus:jwi-border-[#111111]`, children: options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) })] }));
|
|
5
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'leaflet/dist/leaflet.css';
|
|
2
2
|
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
|
3
3
|
import type { WidgetStrings } from '../i18n/widgetStrings';
|
|
4
|
-
import type { DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, JotulWidgetScope, LocationSuggestion } from '../types';
|
|
4
|
+
import type { DealerRecord, DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, JotulWidgetScope, LocationSuggestion } from '../types';
|
|
5
5
|
export type ProductPageWidgetProps = {
|
|
6
6
|
t: WidgetStrings;
|
|
7
7
|
buttonStyling?: JotulWidgetButtonStyling;
|
|
@@ -14,6 +14,9 @@ export type ProductPageWidgetProps = {
|
|
|
14
14
|
mapSearchResult?: DealerSearchResponse | null;
|
|
15
15
|
inquiryValues: InquiryFormValues | null;
|
|
16
16
|
inquiryError: string | null;
|
|
17
|
+
isSubmittingInquiry?: boolean;
|
|
18
|
+
turnstileSiteKey?: string;
|
|
19
|
+
turnstileResetKey?: number;
|
|
17
20
|
isInquirySubmitted: boolean;
|
|
18
21
|
selectedDealerName: string | null;
|
|
19
22
|
isManualSearchEnabled: boolean;
|
|
@@ -26,9 +29,9 @@ export type ProductPageWidgetProps = {
|
|
|
26
29
|
onSuggestionSelect: (suggestion: LocationSuggestion) => void;
|
|
27
30
|
onDismissSuggestions: () => void;
|
|
28
31
|
onInquiryClose: () => void;
|
|
29
|
-
onInquirySubmit: () => void;
|
|
32
|
+
onInquirySubmit: (turnstileToken: string | null) => void;
|
|
30
33
|
onInquiryFieldChange: (key: keyof InquiryFormValues, value: string) => void;
|
|
31
|
-
onStartInquiry: (
|
|
34
|
+
onStartInquiry: (dealer: DealerRecord) => void;
|
|
32
35
|
onMapDealerSelect?: (dealer: {
|
|
33
36
|
dealerName: string;
|
|
34
37
|
latitude: number;
|
|
@@ -36,4 +39,4 @@ export type ProductPageWidgetProps = {
|
|
|
36
39
|
}) => void;
|
|
37
40
|
onClosePopup?: () => void;
|
|
38
41
|
};
|
|
39
|
-
export declare function ProductPageWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClosePopup, }: ProductPageWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare function ProductPageWidget({ t, buttonStyling, borderStyling, markets, scope, isSearching, locationError, searchResult, mapSearchResult, inquiryValues, inquiryError, isSubmittingInquiry, turnstileSiteKey, turnstileResetKey, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onMapDealerSelect, onClosePopup, }: ProductPageWidgetProps): import("react/jsx-runtime").JSX.Element;
|