@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.
Files changed (60) hide show
  1. package/README.md +112 -28
  2. package/dist/JotulWidget.css +1 -1
  3. package/dist/JotulWidget.d.ts +1 -1
  4. package/dist/JotulWidget.js +355 -165
  5. package/dist/analytics/WidgetTrackingContext.d.ts +14 -0
  6. package/dist/analytics/WidgetTrackingContext.js +5 -0
  7. package/dist/analytics/gtm.d.ts +7 -0
  8. package/dist/analytics/gtm.js +17 -0
  9. package/dist/analytics/widgetTracking.d.ts +54 -0
  10. package/dist/analytics/widgetTracking.js +144 -0
  11. package/dist/api.d.ts +27 -1
  12. package/dist/api.js +74 -0
  13. package/dist/components/FindDealerDrawerWidget.d.ts +7 -4
  14. package/dist/components/FindDealerDrawerWidget.js +17 -14
  15. package/dist/components/InquiryField.d.ts +3 -1
  16. package/dist/components/InquiryField.js +19 -2
  17. package/dist/components/InquirySelectField.d.ts +13 -0
  18. package/dist/components/InquirySelectField.js +5 -0
  19. package/dist/components/ProductPageWidget.d.ts +7 -4
  20. package/dist/components/ProductPageWidget.js +12 -14
  21. package/dist/components/TurnstileField.d.ts +7 -0
  22. package/dist/components/TurnstileField.js +48 -0
  23. package/dist/components/WarrantyFormWidget.d.ts +12 -0
  24. package/dist/components/WarrantyFormWidget.js +98 -0
  25. package/dist/components/product-page/DealerList.d.ts +1 -1
  26. package/dist/components/product-page/DealerList.js +13 -5
  27. package/dist/components/product-page/InquiryForm.d.ts +6 -2
  28. package/dist/components/product-page/InquiryForm.js +21 -3
  29. package/dist/constants/turnstile.d.ts +8 -0
  30. package/dist/constants/turnstile.js +19 -0
  31. package/dist/hooks/useTurnstileSiteKey.d.ts +1 -0
  32. package/dist/hooks/useTurnstileSiteKey.js +38 -0
  33. package/dist/i18n/locales/cz.json +34 -1
  34. package/dist/i18n/locales/de.json +34 -1
  35. package/dist/i18n/locales/en.json +34 -1
  36. package/dist/i18n/locales/fi.json +34 -1
  37. package/dist/i18n/locales/fr.json +34 -1
  38. package/dist/i18n/locales/nl.json +34 -1
  39. package/dist/i18n/locales/no.json +34 -1
  40. package/dist/i18n/locales/pl.json +34 -1
  41. package/dist/i18n/locales/se.json +34 -1
  42. package/dist/i18n/widgetStrings.d.ts +33 -0
  43. package/dist/index.d.ts +4 -0
  44. package/dist/index.js +3 -0
  45. package/dist/turnstile.d.ts +18 -0
  46. package/dist/turnstile.js +31 -0
  47. package/dist/types.d.ts +59 -2
  48. package/dist/utils/inquiryCategories.d.ts +8 -0
  49. package/dist/utils/inquiryCategories.js +24 -0
  50. package/dist/utils/inquirySubmit.d.ts +24 -0
  51. package/dist/utils/inquirySubmit.js +35 -0
  52. package/dist/utils/urlDealerId.d.ts +2 -0
  53. package/dist/utils/urlDealerId.js +5 -0
  54. package/dist/utils/usMarket.d.ts +2 -0
  55. package/dist/utils/usMarket.js +9 -0
  56. package/dist/utils/warrantyForm.d.ts +38 -0
  57. package/dist/utils/warrantyForm.js +80 -0
  58. package/dist/utils.d.ts +11 -1
  59. package/dist/utils.js +52 -3
  60. package/package.json +5 -2
@@ -19,7 +19,7 @@ import { MARKER_CLUSTER_LINK_KM, MARKER_CLUSTER_MIN_GROUP, partitionDealersForMa
19
19
  import { loadLeafletMarkerCluster } from '../utils/loadLeafletMarkerCluster';
20
20
  import { markerClusterCountIconHtml } from '../utils/markerClusterIconHtml';
21
21
  import { JOTUL_BRAND_PRIMARY_HEX, R10 } from '../constants';
22
- import { isExclusiveDealer } from '../utils';
22
+ import { isExclusiveDealer, getDealerName } from '../utils';
23
23
  const OSM_MINIMAL_TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
24
24
  const EMPTY_DEALERS = [];
25
25
  function mapPointsBoundsKey(points, defaultCenter) {
@@ -46,11 +46,7 @@ function readNumber(value) {
46
46
  }
47
47
  return null;
48
48
  }
49
- function getDealerName(dealer) {
50
- const raw = dealer.name;
51
- return typeof raw === 'string' && raw.trim() ? raw.trim() : 'Unknown dealer';
52
- }
53
- function getDealerMapPoint(dealer) {
49
+ function getDealerMapPoint(dealer, unknownDealerLabel) {
54
50
  const latitude = readNumber(dealer.latitude);
55
51
  const longitude = readNumber(dealer.longitude);
56
52
  if (latitude == null || longitude == null)
@@ -58,7 +54,7 @@ function getDealerMapPoint(dealer) {
58
54
  const isExclusive = isExclusiveDealer(dealer);
59
55
  return {
60
56
  dealer,
61
- dealerName: getDealerName(dealer),
57
+ dealerName: getDealerName(dealer, unknownDealerLabel),
62
58
  latitude,
63
59
  longitude,
64
60
  isExclusive,
@@ -231,7 +227,7 @@ function ClusteredMapMarkers({ points, pointsBoundsKey, clusterThemeKey, cluster
231
227
  }, [map, pointsBoundsKey, clusterThemeKey]);
232
228
  return null;
233
229
  }
234
- export 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, }) {
230
+ export function ProductPageWidget({ 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, onClosePopup, }) {
235
231
  const rawDealers = (searchResult?.dealers ?? EMPTY_DEALERS);
236
232
  const dealers = useMemo(() => (scope === 'ildstedet' ? rawDealers.filter(isExclusiveDealer) : rawDealers), [rawDealers, scope]);
237
233
  const rawMapDealers = (mapSearchResult?.dealers ?? rawDealers);
@@ -266,13 +262,15 @@ export function ProductPageWidget({ t, buttonStyling, borderStyling, markets, sc
266
262
  useEffect(() => {
267
263
  if (activeDealerName == null)
268
264
  return;
269
- const activeIndex = dealers.findIndex((dealer) => getDealerName(dealer) === activeDealerName);
265
+ const activeIndex = dealers.findIndex((dealer) => getDealerName(dealer, t.unknownDealer) === activeDealerName);
270
266
  if (activeIndex < 0 || activeIndex < visibleDealerCount)
271
267
  return;
272
268
  const nextCount = Math.ceil((activeIndex + 1) / 10) * 10;
273
269
  setVisibleDealerCount(nextCount);
274
270
  }, [activeDealerName, dealers, visibleDealerCount]);
275
- const mapPoints = useMemo(() => mapDealers.map((dealer) => getDealerMapPoint(dealer)).filter((v) => v != null), [mapDealers]);
271
+ const mapPoints = useMemo(() => mapDealers
272
+ .map((dealer) => getDealerMapPoint(dealer, t.unknownDealer))
273
+ .filter((v) => v != null), [mapDealers, t.unknownDealer]);
276
274
  useEffect(() => {
277
275
  setClusterUnavailable(false);
278
276
  }, [mapPoints]);
@@ -288,8 +286,8 @@ export function ProductPageWidget({ t, buttonStyling, borderStyling, markets, sc
288
286
  }, [mapPoints, searchResult?.origin?.latitude, searchResult?.origin?.longitude]);
289
287
  const mapBoundsKey = useMemo(() => mapPointsBoundsKey(mapPoints, defaultCenter), [mapPoints, defaultCenter]);
290
288
  useEffect(() => {
291
- setActiveDealerName(dealers.length > 0 ? getDealerName(dealers[0]) : null);
292
- }, [dealers]);
289
+ setActiveDealerName(dealers.length > 0 ? getDealerName(dealers[0], t.unknownDealer) : null);
290
+ }, [dealers, t.unknownDealer]);
293
291
  const activeMapPoint = useMemo(() => mapPoints.find((p) => p.dealerName === activeDealerName) ?? null, [activeDealerName, mapPoints]);
294
292
  const mapClusterTheme = useMemo(() => {
295
293
  const fill = buttonStyling?.backgroundColor?.trim() || JOTUL_BRAND_PRIMARY_HEX;
@@ -320,7 +318,7 @@ export function ProductPageWidget({ t, buttonStyling, borderStyling, markets, sc
320
318
  const showInquiryInMapPopup = inquiryFormOpen && searchResult?.ok;
321
319
  const showInquirySuccessScreen = isInquirySubmitted && !inquiryFormOpen;
322
320
  const canShowMore = visibleDealerCount < dealers.length;
323
- 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: "Show more" })) : null;
321
+ 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;
324
322
  const handleDealerFocus = (dealerName) => {
325
323
  setActiveDealerName(dealerName);
326
324
  };
@@ -335,5 +333,5 @@ export function ProductPageWidget({ t, buttonStyling, borderStyling, markets, sc
335
333
  }
336
334
  return (_jsxs(_Fragment, { children: [!isMobileViewport && (_jsx(_Fragment, { children: _jsx("div", { className: "jwi-fixed jwi-inset-0 jwi-z-[2147483647] jwi-flex jwi-items-center jwi-justify-center jwi-bg-black/45 jwi-p-4", onClick: closeMapPopup, children: _jsxs("div", { className: `jwi-relative jwi-flex jwi-scale-100 jwi-flex-col jwi-overflow-hidden ${R10} jwi-bg-white jwi-shadow-[0_20px_60px_rgba(0,0,0,0.25)] jwi-transition-all ${showInquirySuccessScreen
337
335
  ? 'jwi-h-auto jwi-w-[min(92vw,620px)]'
338
- : 'jwi-h-[min(85vh,860px)] jwi-w-[min(96vw,1200px)] md:jwi-flex-row'}`, onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", onClick: closeMapPopup, className: "jwi-absolute jwi-right-3 jwi-top-3 jwi-z-[1200] 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] jwi-shadow-[0_2px_8px_rgba(0,0,0,0.12)]", "aria-label": t.closeMap, children: "\u00D7" }), showInquirySuccessScreen ? (renderSuccessPanel()) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-full jwi-flex-col jwi-overflow-hidden md:jwi-w-[48%] md:jwi-border-r md:jwi-border-[#ece8df]", children: _jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-full jwi-flex-col jwi-gap-3 jwi-overflow-hidden jwi-bg-white jwi-p-6", children: showInquiryInMapPopup ? (_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 }), _jsx(CampaignStatus, { campaign: searchResult?.campaign, t: t }), 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-h-full jwi-flex-col jwi-gap-4 jwi-overflow-y-auto jwi-pr-[12px]", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })) : (_jsxs(_Fragment, { children: [searchResult?.ok && (_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, fitAvailableHeight: true, autoScrollToActive: true, maxHeightClassName: "jwi-max-h-none", t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: handleDealerFocus })), showMoreButton] }))] })) }) }), _jsx("div", { className: "jwi-relative jwi-h-[45%] jwi-w-full md:jwi-h-full md:jwi-w-[52%]", children: mapCanvas })] }))] }) }) })), isMobileViewport && (_jsx("div", { className: "jwi-fixed jwi-inset-0 jwi-z-[2147483647] jwi-bg-white", children: _jsxs("div", { className: "jwi-relative jwi-flex jwi-h-full jwi-flex-col", children: [_jsx("div", { className: "jwi-flex jwi-justify-end jwi-bg-white jwi-px-4 jwi-pt-3", children: _jsx("button", { type: "button", onClick: closeMapPopup, 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: showInquirySuccessScreen ? (renderSuccessPanel()) : showInquiryInMapPopup ? (_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 }), _jsx(CampaignStatus, { campaign: searchResult?.campaign, t: t }), 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-flex jwi-flex-col jwi-gap-4", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })) : (_jsxs(_Fragment, { children: [searchResult?.ok && (_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, maxHeightClassName: "jwi-max-h-none", autoScrollToActive: true, enableInternalScroll: false, t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: handleDealerFocus })), showMoreButton] }))] })) }), !showInquiryInMapPopup && !showInquirySuccessScreen && !mobileMapExpanded && (_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded((open) => !open), 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 && !showInquiryInMapPopup && !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 })] }))] }) }))] }));
336
+ : 'jwi-h-[min(85vh,860px)] jwi-w-[min(96vw,1200px)] md:jwi-flex-row'}`, onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", onClick: closeMapPopup, className: "jwi-absolute jwi-right-3 jwi-top-3 jwi-z-[1200] 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] jwi-shadow-[0_2px_8px_rgba(0,0,0,0.12)]", "aria-label": t.closeMap, children: "\u00D7" }), showInquirySuccessScreen ? (renderSuccessPanel()) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-full jwi-flex-col jwi-overflow-hidden md:jwi-w-[48%] md:jwi-border-r md:jwi-border-[#ece8df]", children: _jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-full jwi-flex-col jwi-gap-3 jwi-overflow-hidden jwi-bg-white jwi-p-6", children: showInquiryInMapPopup ? (_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 }), _jsx(CampaignStatus, { campaign: searchResult?.campaign, t: t }), 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-h-full jwi-flex-col jwi-gap-4 jwi-overflow-y-auto jwi-pr-[12px]", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })) : (_jsxs(_Fragment, { children: [searchResult?.ok && (_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, fitAvailableHeight: true, autoScrollToActive: true, maxHeightClassName: "jwi-max-h-none", t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: handleDealerFocus })), showMoreButton] }))] })) }) }), _jsx("div", { className: "jwi-relative jwi-h-[45%] jwi-w-full md:jwi-h-full md:jwi-w-[52%]", children: mapCanvas })] }))] }) }) })), isMobileViewport && (_jsx("div", { className: "jwi-fixed jwi-inset-0 jwi-z-[2147483647] jwi-bg-white", children: _jsxs("div", { className: "jwi-relative jwi-flex jwi-h-full jwi-flex-col", children: [_jsx("div", { className: "jwi-flex jwi-justify-end jwi-bg-white jwi-px-4 jwi-pt-3", children: _jsx("button", { type: "button", onClick: closeMapPopup, 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: showInquirySuccessScreen ? (renderSuccessPanel()) : showInquiryInMapPopup ? (_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 }), _jsx(CampaignStatus, { campaign: searchResult?.campaign, t: t }), 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-flex jwi-flex-col jwi-gap-4", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })) : (_jsxs(_Fragment, { children: [searchResult?.ok && (_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, maxHeightClassName: "jwi-max-h-none", autoScrollToActive: true, enableInternalScroll: false, t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, markets: markets, onStartInquiry: onStartInquiry, onSelectDealer: handleDealerFocus })), showMoreButton] }))] })) }), !showInquiryInMapPopup && !showInquirySuccessScreen && !mobileMapExpanded && (_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded((open) => !open), 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 && !showInquiryInMapPopup && !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 })] }))] }) }))] }));
339
337
  }
@@ -0,0 +1,7 @@
1
+ type TurnstileFieldProps = {
2
+ siteKey: string;
3
+ resetKey?: number;
4
+ onTokenChange: (token: string | null) => void;
5
+ };
6
+ export declare function TurnstileField({ siteKey, resetKey, onTokenChange }: TurnstileFieldProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { loadTurnstileScript } from '../turnstile';
4
+ export function TurnstileField({ siteKey, resetKey = 0, onTokenChange }) {
5
+ const containerRef = useRef(null);
6
+ const widgetIdRef = useRef(null);
7
+ const onTokenChangeRef = useRef(onTokenChange);
8
+ useEffect(() => {
9
+ onTokenChangeRef.current = onTokenChange;
10
+ }, [onTokenChange]);
11
+ useEffect(() => {
12
+ const trimmedSiteKey = siteKey.trim();
13
+ if (!trimmedSiteKey) {
14
+ onTokenChangeRef.current(null);
15
+ return;
16
+ }
17
+ let cancelled = false;
18
+ void loadTurnstileScript()
19
+ .then(() => {
20
+ if (cancelled || containerRef.current == null || window.turnstile == null) {
21
+ return;
22
+ }
23
+ if (widgetIdRef.current) {
24
+ window.turnstile.remove(widgetIdRef.current);
25
+ widgetIdRef.current = null;
26
+ }
27
+ widgetIdRef.current = window.turnstile.render(containerRef.current, {
28
+ sitekey: trimmedSiteKey,
29
+ theme: 'light',
30
+ size: 'flexible',
31
+ callback: (token) => onTokenChangeRef.current(token),
32
+ 'expired-callback': () => onTokenChangeRef.current(null),
33
+ 'error-callback': () => onTokenChangeRef.current(null),
34
+ });
35
+ })
36
+ .catch(() => onTokenChangeRef.current(null));
37
+ return () => {
38
+ cancelled = true;
39
+ if (widgetIdRef.current && window.turnstile) {
40
+ window.turnstile.remove(widgetIdRef.current);
41
+ widgetIdRef.current = null;
42
+ }
43
+ };
44
+ }, [siteKey, resetKey]);
45
+ if (!siteKey.trim())
46
+ return null;
47
+ return _jsx("div", { ref: containerRef, className: "jwi-mt-4 jwi-min-h-[65px]" });
48
+ }
@@ -0,0 +1,12 @@
1
+ import type { JotulWidgetStyling } from '../types';
2
+ export type WarrantyFormWidgetProps = {
3
+ endpoint?: string;
4
+ className?: string;
5
+ locale?: string;
6
+ /** ISO 3166-1 alpha-2 market code (singular). */
7
+ market?: string;
8
+ styling?: JotulWidgetStyling;
9
+ turnstileSiteKey?: string;
10
+ turnstileConfigEndpoint?: string;
11
+ };
12
+ export declare function WarrantyFormWidget({ endpoint, className, locale: localeProp, market: marketProp, styling, turnstileSiteKey: turnstileSiteKeyProp, turnstileConfigEndpoint, }: WarrantyFormWidgetProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useMemo, useState } from 'react';
4
+ import { submitWarranty } from '../api';
5
+ import { R10 } from '../constants';
6
+ import { InquiryField } from './InquiryField';
7
+ import { TurnstileField } from './TurnstileField';
8
+ import { DEFAULT_WIDGET_LOCALE_TAG, resolveWidgetUiLocale, WIDGET_STRINGS, } from '../i18n/widgetStrings';
9
+ import { useTurnstileSiteKey } from '../hooks/useTurnstileSiteKey';
10
+ import { getWidgetPrimaryButtonPresentation } from '../utils/widgetPrimaryButtonPresentation';
11
+ import { isUsWarrantyMarket } from '../utils/usMarket';
12
+ import { createWarrantyFormValues, validateWarrantyFormValues, warrantyFormValuesToApiPayload, } from '../utils/warrantyForm';
13
+ export function WarrantyFormWidget({ endpoint = '/api/jotul/warranty', className, locale: localeProp, market: marketProp, styling, turnstileSiteKey: turnstileSiteKeyProp, turnstileConfigEndpoint, }) {
14
+ const normalizedMarket = useMemo(() => {
15
+ const trimmed = marketProp?.trim().toUpperCase();
16
+ return trimmed && /^[A-Z]{2}$/.test(trimmed) ? trimmed : undefined;
17
+ }, [marketProp]);
18
+ const resolvedUiLocale = useMemo(() => resolveWidgetUiLocale(localeProp, normalizedMarket), [localeProp, normalizedMarket]);
19
+ const showCounty = isUsWarrantyMarket(normalizedMarket, localeProp);
20
+ const t = WIDGET_STRINGS[resolvedUiLocale];
21
+ const resolvedTurnstileSiteKey = useTurnstileSiteKey(turnstileSiteKeyProp, turnstileConfigEndpoint);
22
+ const [values, setValues] = useState(createWarrantyFormValues);
23
+ const [formError, setFormError] = useState(null);
24
+ const [isSubmitting, setIsSubmitting] = useState(false);
25
+ const [isSubmitted, setIsSubmitted] = useState(false);
26
+ const [turnstileToken, setTurnstileToken] = useState(null);
27
+ const [turnstileResetKey, setTurnstileResetKey] = useState(0);
28
+ const handleTurnstileTokenChange = useCallback((token) => {
29
+ setTurnstileToken(token);
30
+ if (token) {
31
+ setFormError((current) => current === t.formValidationCaptcha ? null : current);
32
+ }
33
+ }, [t.formValidationCaptcha]);
34
+ const submitButton = getWidgetPrimaryButtonPresentation(styling?.button, 'panel', {
35
+ extraClassName: 'jwi-mt-4 jwi-w-full',
36
+ });
37
+ const fieldClassName = `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]`;
38
+ const updateField = useCallback((key, value) => {
39
+ setValues((current) => ({ ...current, [key]: value }));
40
+ }, []);
41
+ const handleSubmit = useCallback(async () => {
42
+ const validationError = validateWarrantyFormValues(values, {
43
+ market: normalizedMarket,
44
+ locale: localeProp,
45
+ });
46
+ if (validationError) {
47
+ if (validationError.type === 'invalid_email') {
48
+ setFormError(t.formValidationEmail);
49
+ }
50
+ else if (validationError.field === 'consent') {
51
+ setFormError(t.formValidationConsent);
52
+ }
53
+ else if (validationError.field === 'installerName') {
54
+ setFormError(t.formValidationInstaller);
55
+ }
56
+ else {
57
+ setFormError(t.formValidationRequired);
58
+ }
59
+ return;
60
+ }
61
+ if (resolvedTurnstileSiteKey && !turnstileToken) {
62
+ setFormError(t.formValidationCaptcha);
63
+ return;
64
+ }
65
+ setFormError(null);
66
+ setIsSubmitting(true);
67
+ try {
68
+ const result = await submitWarranty(warrantyFormValuesToApiPayload(values, {
69
+ turnstileToken,
70
+ market: normalizedMarket,
71
+ domain: typeof window !== 'undefined' ? window.location.hostname : undefined,
72
+ }), { endpoint });
73
+ if (result.ok !== true) {
74
+ setFormError(typeof result.error === 'string' ? result.error : t.genericWidgetError);
75
+ setTurnstileResetKey((current) => current + 1);
76
+ return;
77
+ }
78
+ setIsSubmitted(true);
79
+ setTurnstileResetKey((current) => current + 1);
80
+ }
81
+ catch {
82
+ setFormError(t.genericWidgetError);
83
+ setTurnstileResetKey((current) => current + 1);
84
+ }
85
+ finally {
86
+ setIsSubmitting(false);
87
+ }
88
+ }, [endpoint, localeProp, normalizedMarket, resolvedTurnstileSiteKey, t, turnstileToken, values]);
89
+ const shellClass = 'jwi-box-border jwi-flex jwi-w-full jwi-max-w-[540px] jwi-flex-col jwi-font-sans jwi-text-[#111111]';
90
+ const rootClass = className != null && className !== '' ? `${shellClass} ${className}` : shellClass;
91
+ if (isSubmitted) {
92
+ return (_jsx("div", { className: rootClass, children: _jsxs("div", { className: `jwi-flex jwi-w-full jwi-flex-col jwi-items-center jwi-gap-4 ${R10} jwi-border jwi-border-[#e6e1d7] jwi-bg-white jwi-p-8 jwi-text-center`, children: [_jsx("div", { className: "jwi-text-2xl jwi-font-semibold jwi-text-[#111111]", children: t.warrantySuccessTitle }), _jsx("div", { className: "jwi-text-sm jwi-leading-6 jwi-text-[#333333]", children: t.warrantySuccessMessage })] }) }));
93
+ }
94
+ return (_jsx("div", { className: rootClass, children: _jsxs("form", { onSubmit: (event) => {
95
+ event.preventDefault();
96
+ void handleSubmit();
97
+ }, className: `jwi-w-full ${R10} jwi-border jwi-border-[#e6e1d7] jwi-bg-white jwi-p-4 jwi-shadow-[0_1px_2px_rgba(17,17,17,0.03)]`, children: [_jsx("h3", { className: "jwi-m-0 jwi-text-base jwi-font-semibold jwi-leading-snug jwi-text-[#111111]", children: t.warrantyCustomerSectionTitle }), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3", children: [_jsxs("div", { className: "jwi-grid jwi-grid-cols-1 jwi-gap-3 md:jwi-grid-cols-2", children: [_jsx(InquiryField, { label: t.fieldFirstName, value: values.firstName, onChange: (value) => updateField('firstName', value) }), _jsx(InquiryField, { label: t.fieldLastName, value: values.lastName, onChange: (value) => updateField('lastName', value) })] }), _jsx(InquiryField, { label: t.fieldAddress, value: values.address, onChange: (value) => updateField('address', value) }), _jsxs("div", { className: "jwi-grid jwi-grid-cols-1 jwi-gap-3 md:jwi-grid-cols-2", children: [_jsx(InquiryField, { label: t.fieldZipcode, value: values.zipcode, onChange: (value) => updateField('zipcode', value) }), _jsx(InquiryField, { label: t.fieldCity, value: values.city, onChange: (value) => updateField('city', value) })] }), showCounty ? (_jsx(InquiryField, { label: t.fieldCounty, value: values.county, onChange: (value) => updateField('county', value) })) : null, _jsx(InquiryField, { label: t.fieldPhone, type: "tel", value: values.phone, onChange: (value) => updateField('phone', value) }), _jsx(InquiryField, { label: t.fieldEmail, type: "email", value: values.email, onChange: (value) => updateField('email', value) })] }), _jsx("h3", { className: "jwi-m-0 jwi-mt-6 jwi-text-base jwi-font-semibold jwi-leading-snug jwi-text-[#111111]", children: t.warrantyProductSectionTitle }), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3", children: [_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: t.fieldProductName }), _jsx("input", { type: "text", value: values.productName, onChange: (event) => updateField('productName', event.target.value), className: fieldClassName }), _jsx("span", { className: "jwi-text-xs jwi-leading-[1.4] jwi-text-[#767676]", children: t.fieldProductNameHint })] }), _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: t.fieldPurchaseDate }), _jsx("input", { type: "date", value: values.purchaseDate, onChange: (event) => updateField('purchaseDate', event.target.value), className: fieldClassName })] }), _jsx(InquiryField, { label: t.fieldPurchasedFromDealer, value: values.purchasedFromDealer, onChange: (value) => updateField('purchasedFromDealer', value) }), _jsxs("fieldset", { className: "jwi-m-0 jwi-border-0 jwi-p-0", children: [_jsx("legend", { className: "jwi-mb-2 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: t.fieldSelfInstalled }), _jsxs("div", { className: "jwi-flex jwi-flex-col jwi-gap-2", children: [_jsxs("label", { className: "jwi-inline-flex jwi-cursor-pointer jwi-items-center jwi-gap-2 jwi-text-sm jwi-text-[#111111]", children: [_jsx("input", { type: "radio", name: "selfInstalled", value: "yes", checked: values.selfInstalled === 'yes', onChange: () => updateField('selfInstalled', 'yes') }), t.fieldSelfInstalledYes] }), _jsxs("label", { className: "jwi-inline-flex jwi-cursor-pointer jwi-items-center jwi-gap-2 jwi-text-sm jwi-text-[#111111]", children: [_jsx("input", { type: "radio", name: "selfInstalled", value: "no", checked: values.selfInstalled === 'no', onChange: () => updateField('selfInstalled', 'no') }), t.fieldSelfInstalledNo] })] })] }), values.selfInstalled === 'no' ? (_jsx(InquiryField, { label: t.fieldInstallerName, value: values.installerName, onChange: (value) => updateField('installerName', value) })) : null, _jsxs("label", { className: "jwi-flex jwi-cursor-pointer jwi-items-start jwi-gap-2", children: [_jsx("input", { type: "checkbox", checked: values.consent, onChange: (event) => updateField('consent', event.target.checked), className: "jwi-mt-1" }), _jsx("span", { className: "jwi-text-sm jwi-leading-[1.4] jwi-text-[#111111]", children: t.fieldConsent })] })] }), formError ? (_jsx("div", { className: `jwi-mt-4 ${R10} jwi-border jwi-border-[#f0c7c2] jwi-bg-[#fff3f1] jwi-px-4 jwi-py-3 jwi-text-sm jwi-leading-[1.4] jwi-text-[#8f2d21]`, children: formError })) : null, resolvedTurnstileSiteKey ? (_jsx(TurnstileField, { siteKey: resolvedTurnstileSiteKey, resetKey: turnstileResetKey, onTokenChange: handleTurnstileTokenChange })) : null, _jsx("button", { type: "submit", disabled: isSubmitting, className: submitButton.className, style: submitButton.style, children: isSubmitting ? t.warrantySubmitting : t.warrantySubmitCta })] }) }));
98
+ }
@@ -15,7 +15,7 @@ type DealerListProps = {
15
15
  buttonStyling?: JotulWidgetButtonStyling;
16
16
  borderStyling?: JotulWidgetBorderStyling;
17
17
  markets?: string[];
18
- onStartInquiry: (dealerName: string) => void;
18
+ onStartInquiry: (dealer: DealerRecord) => void;
19
19
  onSelectDealer?: (dealerName: string) => void;
20
20
  };
21
21
  export declare function DealerList({ dealers, total, selectedDealerName, activeDealerName, maxHeightClassName, fitAvailableHeight, autoScrollToActive, enableInternalScroll, headerRight, t, buttonStyling, borderStyling, markets, onStartInquiry, onSelectDealer, }: DealerListProps): import("react/jsx-runtime").JSX.Element;
@@ -6,8 +6,10 @@ import { TelephoneIcon } from '../../icons/TelephoneIcon';
6
6
  import { getWidgetPrimaryButtonPresentation } from '../../utils/widgetPrimaryButtonPresentation';
7
7
  import exclusiveDealerBadge from '../../images/jotul-exclusive-dealer.png';
8
8
  import ildstedetDealer from '../../images/ildstedet-dealer.svg';
9
- import { asText, formatDistance, getDealerAddressLines, getDealerKey, getDealerName, isExclusiveDealer, } from '../../utils';
9
+ import { asText, formatDistance, getDealerAddressLines, getDealerId, getDealerKey, getDealerName, isExclusiveDealer, } from '../../utils';
10
+ import { useWidgetTracking } from '../../analytics/WidgetTrackingContext';
10
11
  export function DealerList({ dealers, total, selectedDealerName, activeDealerName = null, maxHeightClassName = 'jwi-max-h-[min(60vh,480px)]', fitAvailableHeight = false, autoScrollToActive = false, enableInternalScroll = true, headerRight, t, buttonStyling, borderStyling, markets, onStartInquiry, onSelectDealer, }) {
12
+ const tracking = useWidgetTracking();
11
13
  const cardRefs = useRef({});
12
14
  useEffect(() => {
13
15
  if (!autoScrollToActive || !activeDealerName)
@@ -35,16 +37,22 @@ export function DealerList({ dealers, total, selectedDealerName, activeDealerNam
35
37
  cardRefs.current[dealerName] = element;
36
38
  }, className: `jwi-w-full jwi-cursor-pointer ${R10} jwi-border jwi-border-[#e6e1d7] jwi-bg-white jwi-p-4 jwi-shadow-[0_1px_2px_rgba(17,17,17,0.03)] ${isActiveDealer ? 'jwi-border-[#ef2b18]' : ''}`, style: isActiveDealer && activeBorderColor
37
39
  ? { borderColor: activeBorderColor }
38
- : undefined, onClick: () => onSelectDealer?.(dealerName), children: [_jsxs("div", { className: "jwi-flex jwi-items-start jwi-justify-between jwi-gap-3", children: [_jsx("div", { className: "jwi-min-w-0 jwi-max-w-[calc(100%-5rem)] jwi-pr-1", children: _jsxs("div", { className: "jwi-flex jwi-items-center jwi-gap-2", children: [isExclusive && (() => {
40
+ : undefined, onClick: () => {
41
+ onSelectDealer?.(dealerName);
42
+ tracking?.trackDealerSelect(getDealerId(dealer), dealerName);
43
+ }, children: [_jsxs("div", { className: "jwi-flex jwi-items-start jwi-justify-between jwi-gap-3", children: [_jsx("div", { className: "jwi-min-w-0 jwi-max-w-[calc(100%-5rem)] jwi-pr-1", children: _jsxs("div", { className: "jwi-flex jwi-items-center jwi-gap-2", children: [isExclusive && (() => {
39
44
  const isNorway = markets?.includes('NO') ?? false;
40
45
  const badgeImage = isNorway ? ildstedetDealer : exclusiveDealerBadge;
41
- const badgeAlt = isNorway ? 'Ildstedet dealer' : 'Jotul exclusive dealer';
46
+ const badgeAlt = isNorway ? t.ildstedetDealerBadgeAlt : t.exclusiveDealerBadgeAlt;
42
47
  return (_jsx("img", { src: typeof badgeImage === 'string'
43
48
  ? badgeImage
44
49
  : badgeImage.src, alt: badgeAlt, className: "jwi-h-auto jwi-w-[40px] jwi-shrink-0 jwi-rounded-full jwi-m-0" }));
45
50
  })(), _jsx("h3", { className: "jwi-m-0 jwi-text-sm md:jwi-text-base jwi-font-semibold jwi-leading-snug jwi-text-[#111111]", children: dealerName })] }) }), distance && (_jsx("div", { className: `jwi-shrink-0 jwi-whitespace-nowrap ${R10} jwi-bg-[#fbf3db] jwi-px-2.5 jwi-py-1 jwi-text-xs jwi-font-medium jwi-leading-none jwi-text-[#111111]`, children: distance }))] }), addressLines.length > 0 && (_jsx("div", { className: "jwi-mt-3 jwi-flex jwi-flex-col jwi-gap-0.5 jwi-text-sm jwi-leading-snug jwi-text-[#111111]", children: addressLines.map((line) => (_jsx("div", { children: line }, line))) })), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3 md:jwi-flex-row md:jwi-items-center md:jwi-justify-between", children: [_jsxs("button", { type: "button", onClick: (event) => {
46
51
  event.stopPropagation();
47
- onStartInquiry(dealerName);
48
- }, className: inquiryCta.className, style: inquiryCta.style, children: [_jsx("span", { children: isSelectedDealer ? t.sendInquiryEditing : t.sendInquiryCta }), _jsx(ArrowRightIcon, { className: "jwi-h-[18px] jwi-w-[18px] jwi-shrink-0" })] }), phone && (_jsxs("a", { href: `tel:${phone.replace(/\s+/g, '')}`, onClick: (event) => event.stopPropagation(), className: "jwi-inline-flex jwi-min-w-0 jwi-items-center jwi-gap-2 jwi-text-sm jwi-font-normal jwi-tabular-nums jwi-text-[#111111] hover:jwi-underline", children: [_jsx(TelephoneIcon, {}), _jsx("span", { className: "jwi-break-all", children: phone })] }))] })] }, getDealerKey(dealer, index)));
52
+ onStartInquiry(dealer);
53
+ }, className: inquiryCta.className, style: inquiryCta.style, children: [_jsx("span", { children: isSelectedDealer ? t.sendInquiryEditing : t.sendInquiryCta }), _jsx(ArrowRightIcon, { className: "jwi-h-[18px] jwi-w-[18px] jwi-shrink-0" })] }), phone && (_jsxs("a", { href: `tel:${phone.replace(/\s+/g, '')}`, onClick: (event) => {
54
+ event.stopPropagation();
55
+ tracking?.trackDealerPhoneClick(getDealerId(dealer), dealerName);
56
+ }, className: "jwi-inline-flex jwi-min-w-0 jwi-items-center jwi-gap-2 jwi-text-sm jwi-font-normal jwi-tabular-nums jwi-text-[#111111] hover:jwi-underline", children: [_jsx(TelephoneIcon, {}), _jsx("span", { className: "jwi-break-all", children: phone })] }))] })] }, getDealerKey(dealer, index)));
49
57
  }) }), _jsx("div", { "aria-hidden": true, className: "jwi-pointer-events-none jwi-absolute jwi-bottom-0 jwi-left-0 jwi-right-0 jwi-h-10 jwi-bg-gradient-to-t jwi-from-white jwi-to-transparent" })] })] }));
50
58
  }
@@ -5,10 +5,14 @@ type InquiryFormProps = {
5
5
  buttonStyling?: JotulWidgetButtonStyling;
6
6
  inquiryValues: InquiryFormValues;
7
7
  inquiryError: string | null;
8
+ isSubmitting?: boolean;
9
+ turnstileSiteKey?: string;
10
+ turnstileResetKey?: number;
8
11
  embedded?: boolean;
12
+ showBackButton?: boolean;
9
13
  onInquiryClose: () => void;
10
- onInquirySubmit: () => void;
14
+ onInquirySubmit: (turnstileToken: string | null) => void;
11
15
  onInquiryFieldChange: (key: keyof InquiryFormValues, value: string) => void;
12
16
  };
13
- export declare function InquiryForm({ t, buttonStyling, inquiryValues, inquiryError, embedded, onInquiryClose, onInquirySubmit, onInquiryFieldChange, }: InquiryFormProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function InquiryForm({ t, buttonStyling, inquiryValues, inquiryError, isSubmitting, turnstileSiteKey, turnstileResetKey, embedded, showBackButton, onInquiryClose, onInquirySubmit, onInquiryFieldChange, }: InquiryFormProps): import("react/jsx-runtime").JSX.Element;
14
18
  export {};
@@ -1,9 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useState } from 'react';
2
3
  import { InquiryField } from '../InquiryField';
4
+ import { InquirySelectField } from '../InquirySelectField';
5
+ import { TurnstileField } from '../TurnstileField';
3
6
  import { R10 } from '../../constants';
4
7
  import { ArrowRightIcon } from '../../icons/ArrowRightIcon';
5
8
  import { getWidgetPrimaryButtonPresentation } from '../../utils/widgetPrimaryButtonPresentation';
6
- export function InquiryForm({ t, buttonStyling, inquiryValues, inquiryError, embedded = false, onInquiryClose, onInquirySubmit, onInquiryFieldChange, }) {
9
+ import { inquiryCategoryOptions } from '../../utils/inquiryCategories';
10
+ export function InquiryForm({ t, buttonStyling, inquiryValues, inquiryError, isSubmitting = false, turnstileSiteKey, turnstileResetKey = 0, embedded = false, showBackButton = true, onInquiryClose, onInquirySubmit, onInquiryFieldChange, }) {
11
+ const [turnstileToken, setTurnstileToken] = useState(null);
12
+ const [captchaError, setCaptchaError] = useState(null);
13
+ const handleTurnstileTokenChange = useCallback((token) => {
14
+ setTurnstileToken(token);
15
+ if (token)
16
+ setCaptchaError(null);
17
+ }, []);
7
18
  const submitButton = getWidgetPrimaryButtonPresentation(buttonStyling, 'panel', {
8
19
  extraClassName: 'jwi-mt-4 jwi-w-full',
9
20
  });
@@ -11,10 +22,17 @@ export function InquiryForm({ t, buttonStyling, inquiryValues, inquiryError, emb
11
22
  const title = trimmedProductName !== ''
12
23
  ? t.sendInquiryTitleWithProduct.replace('{product}', trimmedProductName)
13
24
  : t.sendInquiryTitle;
25
+ const displayError = inquiryError ?? captchaError;
26
+ const turnstileEnabled = Boolean(turnstileSiteKey?.trim());
14
27
  return (_jsxs("form", { onSubmit: (event) => {
15
28
  event.preventDefault();
16
- onInquirySubmit();
29
+ if (turnstileEnabled && !turnstileToken) {
30
+ setCaptchaError(t.formValidationCaptcha);
31
+ return;
32
+ }
33
+ setCaptchaError(null);
34
+ onInquirySubmit(turnstileToken);
17
35
  }, className: `jwi-w-full ${R10} jwi-bg-white jwi-p-4 ${embedded
18
36
  ? ''
19
- : 'jwi-border jwi-border-[#e6e1d7] jwi-shadow-[0_1px_2px_rgba(17,17,17,0.03)]'}`, children: [_jsxs("button", { type: "button", onClick: onInquiryClose, className: "jwi-mb-3 jwi-inline-flex jwi-w-fit jwi-cursor-pointer jwi-items-center jwi-gap-1.5 jwi-border-0 jwi-bg-transparent jwi-p-0 jwi-text-left jwi-text-sm jwi-font-medium jwi-text-[#767676] hover:jwi-text-[#444444]", children: [_jsx("span", { className: "jwi-inline-flex jwi-shrink-0", style: { transform: 'scaleX(-1)' }, "aria-hidden": true, children: _jsx(ArrowRightIcon, { className: "jwi-h-[14px] jwi-w-[14px]" }) }), t.goBack] }), _jsx("input", { type: "hidden", name: "product", value: inquiryValues.productName }), _jsx("input", { type: "hidden", name: "dealer", value: inquiryValues.dealerName }), _jsxs("div", { className: "jwi-min-w-0", children: [_jsx("h3", { className: "jwi-m-0 jwi-text-base jwi-font-semibold jwi-leading-snug jwi-text-[#111111]", children: title }), _jsx("p", { className: "jwi-m-0 jwi-mt-2 jwi-text-sm jwi-leading-[1.4] jwi-text-[#767676]", children: t.dealerWillContact.replace('{dealer}', inquiryValues.dealerName) })] }), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3", children: [_jsx(InquiryField, { label: t.fieldName, value: inquiryValues.name, onChange: (value) => onInquiryFieldChange('name', value) }), _jsx(InquiryField, { label: t.fieldEmail, type: "email", value: inquiryValues.email, onChange: (value) => onInquiryFieldChange('email', value) }), _jsx(InquiryField, { label: t.fieldPhone, type: "tel", value: inquiryValues.phone, onChange: (value) => onInquiryFieldChange('phone', value) }), _jsx(InquiryField, { label: t.fieldComment, multiline: true, value: inquiryValues.comment, onChange: (value) => onInquiryFieldChange('comment', value) })] }), inquiryError && (_jsx("div", { className: `jwi-mt-4 ${R10} jwi-border jwi-border-[#f0c7c2] jwi-bg-[#fff3f1] jwi-px-4 jwi-py-3 jwi-text-sm jwi-leading-[1.4] jwi-text-[#8f2d21]`, children: inquiryError })), _jsx("button", { type: "submit", className: submitButton.className, style: submitButton.style, children: t.sendInquiryCta })] }));
37
+ : 'jwi-border jwi-border-[#e6e1d7] jwi-shadow-[0_1px_2px_rgba(17,17,17,0.03)]'}`, children: [showBackButton ? (_jsxs("button", { type: "button", onClick: onInquiryClose, className: "jwi-mb-3 jwi-inline-flex jwi-w-fit jwi-cursor-pointer jwi-items-center jwi-gap-1.5 jwi-border-0 jwi-bg-transparent jwi-p-0 jwi-text-left jwi-text-sm jwi-font-medium jwi-text-[#767676] hover:jwi-text-[#444444]", children: [_jsx("span", { className: "jwi-inline-flex jwi-shrink-0", style: { transform: 'scaleX(-1)' }, "aria-hidden": true, children: _jsx(ArrowRightIcon, { className: "jwi-h-[14px] jwi-w-[14px]" }) }), t.goBack] })) : null, _jsx("input", { type: "hidden", name: "product", value: inquiryValues.productName }), _jsx("input", { type: "hidden", name: "dealer", value: inquiryValues.dealerName }), _jsxs("div", { className: "jwi-min-w-0", children: [_jsx("h3", { className: "jwi-m-0 jwi-text-base jwi-font-semibold jwi-leading-snug jwi-text-[#111111]", children: title }), _jsx("p", { className: "jwi-m-0 jwi-mt-2 jwi-text-sm jwi-leading-[1.4] jwi-text-[#767676]", children: t.dealerWillContact.replace('{dealer}', inquiryValues.dealerName) })] }), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3", children: [_jsx(InquirySelectField, { label: t.fieldRequestCategory, value: inquiryValues.requestCategory, options: inquiryCategoryOptions(t), onChange: (value) => onInquiryFieldChange('requestCategory', value) }), _jsx(InquiryField, { label: t.fieldName, fieldName: "name", value: inquiryValues.name, onChange: (value) => onInquiryFieldChange('name', value) }), _jsx(InquiryField, { label: t.fieldEmail, fieldName: "email", type: "email", value: inquiryValues.email, onChange: (value) => onInquiryFieldChange('email', value) }), _jsx(InquiryField, { label: t.fieldPhone, fieldName: "phone", type: "tel", value: inquiryValues.phone, onChange: (value) => onInquiryFieldChange('phone', value) }), _jsx(InquiryField, { label: t.fieldComment, fieldName: "comment", multiline: true, value: inquiryValues.comment, onChange: (value) => onInquiryFieldChange('comment', value) })] }), turnstileEnabled && turnstileSiteKey ? (_jsx(TurnstileField, { siteKey: turnstileSiteKey, resetKey: turnstileResetKey, onTokenChange: handleTurnstileTokenChange })) : null, displayError && (_jsx("div", { className: `jwi-mt-4 ${R10} jwi-border jwi-border-[#f0c7c2] jwi-bg-[#fff3f1] jwi-px-4 jwi-py-3 jwi-text-sm jwi-leading-[1.4] jwi-text-[#8f2d21]`, children: displayError })), _jsx("button", { type: "submit", className: submitButton.className, style: submitButton.style, disabled: isSubmitting, children: isSubmitting ? t.warrantySubmitting : t.sendInquiryCta })] }));
20
38
  }
@@ -0,0 +1,8 @@
1
+ /** Public Turnstile site-key endpoint on api.jotul.com (no auth; site keys are public). */
2
+ export declare const JOTUL_TURNSTILE_CONFIG_URL = "https://api.jotul.com/api/jotul/turnstile-config";
3
+ /**
4
+ * Where the widget loads the Turnstile site key from.
5
+ * Partners do not configure this — only Jotul sets keys on api.jotul.com.
6
+ * Uses same-origin config on localhost so local docs/dev work without CORS.
7
+ */
8
+ export declare function resolveTurnstileConfigEndpoint(override?: string): string;
@@ -0,0 +1,19 @@
1
+ /** Public Turnstile site-key endpoint on api.jotul.com (no auth; site keys are public). */
2
+ export const JOTUL_TURNSTILE_CONFIG_URL = 'https://api.jotul.com/api/jotul/turnstile-config';
3
+ /**
4
+ * Where the widget loads the Turnstile site key from.
5
+ * Partners do not configure this — only Jotul sets keys on api.jotul.com.
6
+ * Uses same-origin config on localhost so local docs/dev work without CORS.
7
+ */
8
+ export function resolveTurnstileConfigEndpoint(override) {
9
+ const trimmed = override?.trim();
10
+ if (trimmed)
11
+ return trimmed;
12
+ if (typeof window !== 'undefined') {
13
+ const { hostname, protocol, host } = window.location;
14
+ if (hostname === 'localhost' || hostname === '127.0.0.1') {
15
+ return `${protocol}//${host}/api/jotul/turnstile-config`;
16
+ }
17
+ }
18
+ return JOTUL_TURNSTILE_CONFIG_URL;
19
+ }
@@ -0,0 +1 @@
1
+ export declare function useTurnstileSiteKey(explicitSiteKey?: string, configEndpoint?: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { resolveTurnstileConfigEndpoint } from '../constants/turnstile';
3
+ export function useTurnstileSiteKey(explicitSiteKey, configEndpoint) {
4
+ const trimmedExplicit = explicitSiteKey?.trim();
5
+ const resolvedEndpoint = resolveTurnstileConfigEndpoint(configEndpoint);
6
+ const [fetchedSiteKey, setFetchedSiteKey] = useState();
7
+ useEffect(() => {
8
+ if (trimmedExplicit) {
9
+ setFetchedSiteKey(undefined);
10
+ return;
11
+ }
12
+ let cancelled = false;
13
+ void fetch(resolvedEndpoint, {
14
+ method: 'GET',
15
+ headers: { Accept: 'application/json' },
16
+ cache: 'no-store',
17
+ })
18
+ .then(async (response) => {
19
+ if (!response.ok)
20
+ return null;
21
+ return (await response.json());
22
+ })
23
+ .then((data) => {
24
+ if (cancelled)
25
+ return;
26
+ const siteKey = data?.siteKey?.trim();
27
+ setFetchedSiteKey(siteKey || undefined);
28
+ })
29
+ .catch(() => {
30
+ if (!cancelled)
31
+ setFetchedSiteKey(undefined);
32
+ });
33
+ return () => {
34
+ cancelled = true;
35
+ };
36
+ }, [trimmedExplicit, resolvedEndpoint]);
37
+ return trimmedExplicit || fetchedSiteKey;
38
+ }
@@ -26,12 +26,45 @@
26
26
  "fieldEmail": "E-mail",
27
27
  "fieldPhone": "Telefon",
28
28
  "fieldComment": "Komentář",
29
- "formValidationRequired": "Vyplňte jméno, e-mail a telefon.",
29
+ "dealerNotFound": "Prodejce nebyl nalezen.",
30
+ "fieldRequestCategory": "Kategorie poptávky",
31
+ "requestCategoryPriceQuote": "Cenová nabídka",
32
+ "requestCategoryProductHelp": "Pomoc s produktem / dotazy",
33
+ "requestCategoryService": "Servis",
34
+ "requestCategoryOther": "Jiné",
35
+ "warrantyCustomerSectionTitle": "Informace o zákazníkovi",
36
+ "warrantyProductSectionTitle": "Informace o produktu",
37
+ "fieldFirstName": "Jméno",
38
+ "fieldLastName": "Příjmení",
39
+ "fieldAddress": "Adresa",
40
+ "fieldCity": "Město",
41
+ "fieldZipcode": "PSČ",
42
+ "fieldCounty": "Okres",
43
+ "fieldProductName": "Název produktu",
44
+ "fieldProductNameHint": "Například Jøtul F 602",
45
+ "fieldPurchaseDate": "Datum nákupu",
46
+ "fieldPurchasedFromDealer": "Zakoupeno u prodejce",
47
+ "fieldSelfInstalled": "Instalovali jste produkt sami?",
48
+ "fieldSelfInstalledYes": "Ano",
49
+ "fieldSelfInstalledNo": "Ne",
50
+ "fieldInstallerName": "Pokud ne, kdo produkt instaloval?",
51
+ "fieldConsent": "Souhlasím s odesláním mých údajů společnosti Jøtul pro informační a obchodní účely. Mám právo na nahlížení a výmaz svých údajů",
52
+ "warrantySubmitCta": "Odeslat",
53
+ "warrantySubmitting": "Odesílání …",
54
+ "warrantySuccessTitle": "Děkujeme za registraci",
55
+ "warrantySuccessMessage": "Registrace záruky byla odeslána. Zkontrolujte e-mail pro potvrzení.",
56
+ "formValidationConsent": "Pro pokračování musíte udělit souhlas.",
57
+ "formValidationInstaller": "Uveďte, kdo produkt instaloval.",
58
+ "formValidationCaptcha": "Dokončete bezpečnostní kontrolu.",
59
+ "formValidationRequired": "Vyplňte všechna povinná pole.",
30
60
  "formValidationEmail": "Zadejte platnou e-mailovou adresu.",
31
61
  "dealersNearYou": "{count} prodejců v okolí",
32
62
  "unknownDealer": "Neznámý prodejce",
33
63
  "sendInquiryCta": "Odeslat poptávku",
34
64
  "sendInquiryEditing": "Úprava poptávky",
65
+ "showMore": "Zobrazit další",
66
+ "exclusiveDealerBadgeAlt": "Jotul exclusive prodejce",
67
+ "ildstedetDealerBadgeAlt": "Ildstedet prodejce",
35
68
  "readyDealerFinder": "JotulWidget připraven: dealerFinder",
36
69
  "readyWarrantyForm": "JotulWidget připraven: warrantyForm",
37
70
  "listView": "Seznam",
@@ -26,12 +26,45 @@
26
26
  "fieldEmail": "E-Mail",
27
27
  "fieldPhone": "Telefon",
28
28
  "fieldComment": "Kommentar",
29
- "formValidationRequired": "Bitte Name, E-Mail und Telefon eingeben.",
29
+ "dealerNotFound": "Händler nicht gefunden.",
30
+ "fieldRequestCategory": "Anfragekategorie",
31
+ "requestCategoryPriceQuote": "Preisangebot",
32
+ "requestCategoryProductHelp": "Produkthilfe / Fragen",
33
+ "requestCategoryService": "Service",
34
+ "requestCategoryOther": "Sonstiges",
35
+ "warrantyCustomerSectionTitle": "Kundeninformation",
36
+ "warrantyProductSectionTitle": "Produktinformation",
37
+ "fieldFirstName": "Vorname",
38
+ "fieldLastName": "Nachname",
39
+ "fieldAddress": "Adresse",
40
+ "fieldCity": "Stadt",
41
+ "fieldZipcode": "Postleitzahl",
42
+ "fieldCounty": "Bundesland",
43
+ "fieldProductName": "Produktname",
44
+ "fieldProductNameHint": "Zum Beispiel Jøtul F 602",
45
+ "fieldPurchaseDate": "Kaufdatum",
46
+ "fieldPurchasedFromDealer": "Gekauft beim Händler",
47
+ "fieldSelfInstalled": "Haben Sie das Produkt selbst installiert?",
48
+ "fieldSelfInstalledYes": "Ja",
49
+ "fieldSelfInstalledNo": "Nein",
50
+ "fieldInstallerName": "Wenn nein, wer hat das Produkt installiert?",
51
+ "fieldConsent": "Ich stimme zu, dass meine Informationen zu Informations- und Werbezwecken an Jøtul gesendet werden. Ich habe das Recht, meine Informationen einzusehen und zu löschen",
52
+ "warrantySubmitCta": "Senden",
53
+ "warrantySubmitting": "Wird gesendet …",
54
+ "warrantySuccessTitle": "Vielen Dank für Ihre Registrierung",
55
+ "warrantySuccessMessage": "Ihre Garantieregistrierung wurde gesendet. Prüfen Sie Ihre E-Mail zur Bestätigung.",
56
+ "formValidationConsent": "Sie müssen zustimmen, um fortzufahren.",
57
+ "formValidationInstaller": "Geben Sie an, wer das Produkt installiert hat.",
58
+ "formValidationCaptcha": "Bitte schließen Sie die Sicherheitsprüfung ab.",
59
+ "formValidationRequired": "Bitte füllen Sie alle Pflichtfelder aus.",
30
60
  "formValidationEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
31
61
  "dealersNearYou": "{count} Händler in Ihrer Nähe",
32
62
  "unknownDealer": "Unbekannter Händler",
33
63
  "sendInquiryCta": "Anfrage senden",
34
64
  "sendInquiryEditing": "Anfrage bearbeiten",
65
+ "showMore": "Mehr anzeigen",
66
+ "exclusiveDealerBadgeAlt": "Jotul Exclusive-Händler",
67
+ "ildstedetDealerBadgeAlt": "Ildstedet-Händler",
35
68
  "readyDealerFinder": "JotulWidget bereit: dealerFinder",
36
69
  "readyWarrantyForm": "JotulWidget bereit: warrantyForm",
37
70
  "listView": "Liste",