@jotul/jotul-widgets 1.0.5 → 1.2.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 (44) hide show
  1. package/README.md +21 -1
  2. package/dist/JotulWidget.css +1 -1
  3. package/dist/JotulWidget.d.ts +2 -2
  4. package/dist/JotulWidget.js +166 -36
  5. package/dist/api.d.ts +2 -2
  6. package/dist/api.js +13 -2
  7. package/dist/components/FindDealerDrawerWidget.d.ts +39 -0
  8. package/dist/components/FindDealerDrawerWidget.js +318 -0
  9. package/dist/components/ProductPageWidget.d.ts +11 -2
  10. package/dist/components/ProductPageWidget.js +215 -41
  11. package/dist/components/product-page/CampaignStatus.d.ts +6 -0
  12. package/dist/components/product-page/CampaignStatus.js +48 -0
  13. package/dist/components/product-page/DealerList.d.ts +4 -2
  14. package/dist/components/product-page/DealerList.js +12 -4
  15. package/dist/components/product-page/StatusBanner.d.ts +1 -1
  16. package/dist/components/product-page/StatusBanner.js +3 -1
  17. package/dist/constants.d.ts +4 -0
  18. package/dist/constants.js +4 -0
  19. package/dist/i18n/locales/cz.json +5 -1
  20. package/dist/i18n/locales/de.json +5 -1
  21. package/dist/i18n/locales/en.json +5 -1
  22. package/dist/i18n/locales/fi.json +5 -1
  23. package/dist/i18n/locales/fr.json +5 -1
  24. package/dist/i18n/locales/nl.json +5 -1
  25. package/dist/i18n/locales/no.json +5 -1
  26. package/dist/i18n/locales/pl.json +5 -1
  27. package/dist/i18n/locales/se.json +5 -1
  28. package/dist/i18n/widgetStrings.d.ts +4 -0
  29. package/dist/images/dealer-pin-ildstedet.svg +31 -0
  30. package/dist/images/dealer-pin.svg +8 -3
  31. package/dist/images/ildstedet-dealer.svg +702 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/types.d.ts +26 -5
  34. package/dist/utils/cssColor.d.ts +5 -0
  35. package/dist/utils/cssColor.js +31 -0
  36. package/dist/utils/dealerMapClustering.d.ts +28 -0
  37. package/dist/utils/dealerMapClustering.js +71 -0
  38. package/dist/utils/loadLeafletMarkerCluster.d.ts +5 -0
  39. package/dist/utils/loadLeafletMarkerCluster.js +20 -0
  40. package/dist/utils/markerClusterIconHtml.d.ts +2 -0
  41. package/dist/utils/markerClusterIconHtml.js +7 -0
  42. package/dist/utils.d.ts +3 -1
  43. package/dist/utils.js +10 -0
  44. package/package.json +5 -2
package/README.md CHANGED
@@ -40,7 +40,20 @@ Custom trigger button:
40
40
  ```tsx
41
41
  <JotulWidget
42
42
  type="productPage"
43
- productPageTrigger={({ onOpen, label, className, style }) => (
43
+ trigger={({ onOpen, label, className, style }) => (
44
+ <button type="button" onClick={onOpen} className={className} style={style}>
45
+ {label}
46
+ </button>
47
+ )}
48
+ />
49
+ ```
50
+
51
+ Find dealer drawer with custom trigger:
52
+
53
+ ```tsx
54
+ <JotulWidget
55
+ type="findDealerDrawer"
56
+ trigger={({ onOpen, label, className, style }) => (
44
57
  <button type="button" onClick={onOpen} className={className} style={style}>
45
58
  {label}
46
59
  </button>
@@ -54,3 +67,10 @@ Current behavior:
54
67
  - missing permission -> `Insufficient permissions`
55
68
  - invalid widget type -> `Invalid widget type`
56
69
  - `productPage` renders zipcode input + search button + dealer list
70
+ - `findDealerDrawer` opens the dealer experience in a right-side drawer
71
+
72
+ List/map data behavior:
73
+
74
+ - List uses closest dealers and paginates 10 + 10 via "Show more".
75
+ - Map uses a broader dealer dataset for marker coverage.
76
+ - Widget route calls dealer search with `scope=list` (list) and `scope=map` (map).
@@ -1 +1 @@
1
- .jwi-pointer-events-none{pointer-events:none}.jwi-fixed{position:fixed}.jwi-absolute{position:absolute}.jwi-relative{position:relative}.jwi-inset-0{inset:0}.jwi-inset-x-0{left:0;right:0}.jwi-bottom-0{bottom:0}.jwi-left-0{left:0}.jwi-right-0{right:0}.jwi-right-3{right:.75rem}.jwi-top-3{top:.75rem}.jwi-z-20{z-index:20}.jwi-z-30{z-index:30}.jwi-z-\[1200\]{z-index:1200}.jwi-z-\[9999\]{z-index:9999}.jwi-m-0{margin:0}.jwi--mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.-jwi-mt-px{margin-top:-1px}.jwi-mb-3{margin-bottom:.75rem}.jwi-mr-\[-12px\]{margin-right:-12px}.jwi-mt-2{margin-top:.5rem}.jwi-mt-3{margin-top:.75rem}.jwi-mt-4{margin-top:1rem}.jwi-box-border{box-sizing:border-box}.jwi-flex{display:flex}.jwi-inline-flex{display:inline-flex}.jwi-h-10{height:2.5rem}.jwi-h-12{height:3rem}.jwi-h-14{height:3.5rem}.jwi-h-3\.5{height:.875rem}.jwi-h-4{height:1rem}.jwi-h-5{height:1.25rem}.jwi-h-6{height:1.5rem}.jwi-h-9{height:2.25rem}.jwi-h-\[14px\]{height:14px}.jwi-h-\[18px\]{height:18px}.jwi-h-\[22px\]{height:22px}.jwi-h-\[45\%\]{height:45%}.jwi-h-\[60px\]{height:60px}.jwi-h-\[78vh\]{height:78vh}.jwi-h-\[calc\(78vh-48px\)\]{height:calc(78vh - 48px)}.jwi-h-\[min\(85vh\,860px\)\]{height:min(85vh,860px)}.jwi-h-auto{height:auto}.jwi-h-full{height:100%}.jwi-max-h-\[min\(60vh\,480px\)\]{max-height:min(60vh,480px)}.jwi-max-h-none{max-height:none}.jwi-min-h-0{min-height:0}.jwi-min-h-\[48px\]{min-height:48px}.jwi-min-h-\[56px\]{min-height:56px}.jwi-w-14{width:3.5rem}.jwi-w-2\/3{width:66.666667%}.jwi-w-24{width:6rem}.jwi-w-28{width:7rem}.jwi-w-3\.5{width:.875rem}.jwi-w-4{width:1rem}.jwi-w-48{width:12rem}.jwi-w-5{width:1.25rem}.jwi-w-9{width:2.25rem}.jwi-w-\[14px\]{width:14px}.jwi-w-\[18px\]{width:18px}.jwi-w-\[22px\]{width:22px}.jwi-w-\[40px\]{width:40px}.jwi-w-\[540px\]{width:540px}.jwi-w-\[min\(96vw\,1200px\)\]{width:min(96vw,1200px)}.jwi-w-auto{width:auto}.jwi-w-fit{width:-moz-fit-content;width:fit-content}.jwi-w-full{width:100%}.jwi-min-w-0{min-width:0}.jwi-max-w-\[220px\]{max-width:220px}.jwi-max-w-\[70\%\]{max-width:70%}.jwi-max-w-\[calc\(100\%-5rem\)\]{max-width:calc(100% - 5rem)}.jwi-max-w-full{max-width:100%}.jwi-flex-1{flex:1 1 0%}.jwi-flex-shrink-0,.jwi-shrink-0{flex-shrink:0}.jwi-scale-100{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes jwi-pulse{50%{opacity:.5}}.jwi-animate-pulse{animation:jwi-pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes jwi-spin{to{transform:rotate(1turn)}}.jwi-animate-spin{animation:jwi-spin 1s linear infinite}.jwi-cursor-pointer{cursor:pointer}.jwi-resize-y{resize:vertical}.jwi-flex-row{flex-direction:row}.jwi-flex-col{flex-direction:column}.jwi-items-start{align-items:flex-start}.jwi-items-center{align-items:center}.jwi-items-stretch{align-items:stretch}.jwi-justify-end{justify-content:flex-end}.jwi-justify-center{justify-content:center}.jwi-justify-between{justify-content:space-between}.jwi-gap-0\.5{gap:.125rem}.jwi-gap-1{gap:.25rem}.jwi-gap-1\.5{gap:.375rem}.jwi-gap-2{gap:.5rem}.jwi-gap-3{gap:.75rem}.jwi-gap-4{gap:1rem}.jwi-space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.jwi-self-stretch{align-self:stretch}.jwi-overflow-hidden{overflow:hidden}.jwi-overflow-y-auto{overflow-y:auto}.jwi-overscroll-y-contain{overscroll-behavior-y:contain}.jwi-whitespace-nowrap{white-space:nowrap}.jwi-break-all{word-break:break-all}.jwi-rounded-\[10px\]{border-radius:10px}.jwi-rounded-full{border-radius:9999px}.jwi-rounded-b-\[10px\]{border-bottom-right-radius:10px;border-bottom-left-radius:10px}.jwi-rounded-t-\[10px\]{border-top-left-radius:10px;border-top-right-radius:10px}.jwi-rounded-t-\[16px\]{border-top-left-radius:16px;border-top-right-radius:16px}.jwi-border{border-width:1px}.jwi-border-0{border-width:0}.jwi-border-b{border-bottom-width:1px}.jwi-border-t{border-top-width:1px}.jwi-border-t-0{border-top-width:0}.jwi-border-\[\#b7e5c2\]{--tw-border-opacity:1;border-color:rgb(183 229 194/var(--tw-border-opacity,1))}.jwi-border-\[\#d8d2c7\]{--tw-border-opacity:1;border-color:rgb(216 210 199/var(--tw-border-opacity,1))}.jwi-border-\[\#e6e1d7\]{--tw-border-opacity:1;border-color:rgb(230 225 215/var(--tw-border-opacity,1))}.jwi-border-\[\#ef2b18\]{--tw-border-opacity:1;border-color:rgb(239 43 24/var(--tw-border-opacity,1))}.jwi-border-\[\#f0c7c2\]{--tw-border-opacity:1;border-color:rgb(240 199 194/var(--tw-border-opacity,1))}.jwi-bg-\[\#FCFCFC\]{--tw-bg-opacity:1;background-color:rgb(252 252 252/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ece8df\]{--tw-bg-opacity:1;background-color:rgb(236 232 223/var(--tw-bg-opacity,1))}.jwi-bg-\[\#eefbf2\]{--tw-bg-opacity:1;background-color:rgb(238 251 242/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ef2b18\]{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}.jwi-bg-\[\#f0f0f0\]{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity,1))}.jwi-bg-\[\#f7f5ef\]{--tw-bg-opacity:1;background-color:rgb(247 245 239/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fbf3db\]{--tw-bg-opacity:1;background-color:rgb(251 243 219/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fff3f1\]{--tw-bg-opacity:1;background-color:rgb(255 243 241/var(--tw-bg-opacity,1))}.jwi-bg-black\/45{background-color:rgba(0,0,0,.45)}.jwi-bg-transparent{background-color:transparent}.jwi-bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.jwi-bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.jwi-from-white{--tw-gradient-from:#fff var(--tw-gradient-from-position);--tw-gradient-to:hsla(0,0%,100%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.jwi-to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.jwi-p-0{padding:0}.jwi-p-1{padding:.25rem}.jwi-p-4{padding:1rem}.jwi-p-6{padding:1.5rem}.jwi-px-2\.5{padding-left:.625rem;padding-right:.625rem}.jwi-px-4{padding-left:1rem;padding-right:1rem}.jwi-px-5{padding-left:1.25rem;padding-right:1.25rem}.jwi-px-6{padding-left:1.5rem;padding-right:1.5rem}.jwi-px-7{padding-left:1.75rem;padding-right:1.75rem}.jwi-py-1{padding-top:.25rem;padding-bottom:.25rem}.jwi-py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.jwi-py-3{padding-top:.75rem;padding-bottom:.75rem}.jwi-py-4{padding-top:1rem;padding-bottom:1rem}.jwi-py-8{padding-top:2rem;padding-bottom:2rem}.jwi-pb-24{padding-bottom:6rem}.jwi-pb-3{padding-bottom:.75rem}.jwi-pl-5{padding-left:1.25rem}.jwi-pr-1{padding-right:.25rem}.jwi-pr-3{padding-right:.75rem}.jwi-pr-\[12px\]{padding-right:12px}.jwi-pt-3{padding-top:.75rem}.jwi-text-left{text-align:left}.jwi-font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.jwi-text-\[13px\]{font-size:13px}.jwi-text-base{font-size:1rem;line-height:1.5rem}.jwi-text-sm{font-size:.875rem;line-height:1.25rem}.jwi-text-xl{font-size:1.25rem;line-height:1.75rem}.jwi-text-xs{font-size:.75rem;line-height:1rem}.jwi-font-medium{font-weight:500}.jwi-font-normal{font-weight:400}.jwi-font-semibold{font-weight:600}.jwi-tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.jwi-leading-\[1\.35\]{line-height:1.35}.jwi-leading-\[1\.4\]{line-height:1.4}.jwi-leading-none{line-height:1}.jwi-leading-snug{line-height:1.375}.jwi-text-\[\#000000\]{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.jwi-text-\[\#111111\]{--tw-text-opacity:1;color:rgb(17 17 17/var(--tw-text-opacity,1))}.jwi-text-\[\#1b5e20\]{--tw-text-opacity:1;color:rgb(27 94 32/var(--tw-text-opacity,1))}.jwi-text-\[\#555555\]{--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity,1))}.jwi-text-\[\#767676\]{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.jwi-text-\[\#8f2d21\]{--tw-text-opacity:1;color:rgb(143 45 33/var(--tw-text-opacity,1))}.jwi-text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.jwi-text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.jwi-opacity-25{opacity:.25}.jwi-opacity-75{opacity:.75}.jwi-opacity-95{opacity:.95}.jwi-shadow-\[0_-12px_36px_rgba\(0\,0\,0\,0\.22\)\]{--tw-shadow:0 -12px 36px rgba(0,0,0,.22);--tw-shadow-colored:0 -12px 36px var(--tw-shadow-color)}.jwi-shadow-\[0_-12px_36px_rgba\(0\,0\,0\,0\.22\)\],.jwi-shadow-\[0_-6px_20px_rgba\(0\,0\,0\,0\.12\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_-6px_20px_rgba\(0\,0\,0\,0\.12\)\]{--tw-shadow:0 -6px 20px rgba(0,0,0,.12);--tw-shadow-colored:0 -6px 20px var(--tw-shadow-color)}.jwi-shadow-\[0_1px_2px_rgba\(17\,17\,17\,0\.03\)\]{--tw-shadow:0 1px 2px hsla(0,0%,7%,.03);--tw-shadow-colored:0 1px 2px var(--tw-shadow-color)}.jwi-shadow-\[0_1px_2px_rgba\(17\,17\,17\,0\.03\)\],.jwi-shadow-\[0_20px_60px_rgba\(0\,0\,0\,0\.25\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_20px_60px_rgba\(0\,0\,0\,0\.25\)\]{--tw-shadow:0 20px 60px rgba(0,0,0,.25);--tw-shadow-colored:0 20px 60px var(--tw-shadow-color)}.jwi-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.12\)\]{--tw-shadow:0 2px 8px rgba(0,0,0,.12);--tw-shadow-colored:0 2px 8px var(--tw-shadow-color)}.jwi-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.12\)\],.jwi-shadow-\[0_8px_24px_rgba\(17\,17\,17\,0\.08\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_8px_24px_rgba\(17\,17\,17\,0\.08\)\]{--tw-shadow:0 8px 24px hsla(0,0%,7%,.08);--tw-shadow-colored:0 8px 24px var(--tw-shadow-color)}.jwi-outline-none{outline:2px solid transparent;outline-offset:2px}.jwi-transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.placeholder\:jwi-text-\[13px\]::-moz-placeholder{font-size:13px}.placeholder\:jwi-text-\[13px\]::placeholder{font-size:13px}.placeholder\:jwi-text-\[\#767676\]::-moz-placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.placeholder\:jwi-text-\[\#767676\]::placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.hover\:jwi-bg-\[\#d92817\]:hover{--tw-bg-opacity:1;background-color:rgb(217 40 23/var(--tw-bg-opacity,1))}.hover\:jwi-bg-\[\#f7f5f0\]:hover{--tw-bg-opacity:1;background-color:rgb(247 245 240/var(--tw-bg-opacity,1))}.hover\:jwi-text-\[\#444444\]:hover{--tw-text-opacity:1;color:rgb(68 68 68/var(--tw-text-opacity,1))}.hover\:jwi-underline:hover{text-decoration-line:underline}.focus\:jwi-border-\[\#111111\]:focus{--tw-border-opacity:1;border-color:rgb(17 17 17/var(--tw-border-opacity,1))}.disabled\:jwi-cursor-wait:disabled{cursor:wait}.disabled\:hover\:jwi-bg-\[\#ef2b18\]:hover:disabled{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}@media (min-width:768px){.md\:jwi-h-full{height:100%}.md\:jwi-w-\[48\%\]{width:48%}.md\:jwi-w-\[52\%\]{width:52%}.md\:jwi-max-w-\[220px\]{max-width:220px}.md\:jwi-flex-row{flex-direction:row}.md\:jwi-items-center{align-items:center}.md\:jwi-justify-between{justify-content:space-between}.md\:jwi-border-r{border-right-width:1px}.md\:jwi-border-\[\#ece8df\]{--tw-border-opacity:1;border-color:rgb(236 232 223/var(--tw-border-opacity,1))}.md\:jwi-text-\[14px\]{font-size:14px}.md\:jwi-text-base{font-size:1rem;line-height:1.5rem}.md\:placeholder\:jwi-text-\[14px\]::-moz-placeholder{font-size:14px}.md\:placeholder\:jwi-text-\[14px\]::placeholder{font-size:14px}}
1
+ .jwi-pointer-events-none{pointer-events:none}.jwi-fixed{position:fixed}.jwi-absolute{position:absolute}.jwi-relative{position:relative}.jwi-inset-0{inset:0}.jwi-inset-x-0{left:0;right:0}.jwi-bottom-0{bottom:0}.jwi-left-0{left:0}.jwi-right-0{right:0}.jwi-right-3{right:.75rem}.jwi-top-0{top:0}.jwi-top-3{top:.75rem}.jwi-z-20{z-index:20}.jwi-z-30{z-index:30}.jwi-z-\[1200\]{z-index:1200}.jwi-z-\[9998\]{z-index:9998}.jwi-z-\[9999\]{z-index:9999}.jwi-m-0{margin:0}.jwi--mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.-jwi-mt-px{margin-top:-1px}.jwi-mb-3{margin-bottom:.75rem}.jwi-mr-\[-12px\]{margin-right:-12px}.jwi-mt-1{margin-top:.25rem}.jwi-mt-2{margin-top:.5rem}.jwi-mt-3{margin-top:.75rem}.jwi-mt-4{margin-top:1rem}.jwi-box-border{box-sizing:border-box}.jwi-flex{display:flex}.jwi-inline-flex{display:inline-flex}.jwi-h-10{height:2.5rem}.jwi-h-12{height:3rem}.jwi-h-14{height:3.5rem}.jwi-h-3\.5{height:.875rem}.jwi-h-4{height:1rem}.jwi-h-5{height:1.25rem}.jwi-h-6{height:1.5rem}.jwi-h-9{height:2.25rem}.jwi-h-\[14px\]{height:14px}.jwi-h-\[18px\]{height:18px}.jwi-h-\[22px\]{height:22px}.jwi-h-\[45\%\]{height:45%}.jwi-h-\[60px\]{height:60px}.jwi-h-\[78vh\]{height:78vh}.jwi-h-\[calc\(78vh-48px\)\]{height:calc(78vh - 48px)}.jwi-h-\[min\(85vh\,860px\)\]{height:min(85vh,860px)}.jwi-h-auto{height:auto}.jwi-h-full{height:100%}.jwi-max-h-\[min\(60vh\,480px\)\]{max-height:min(60vh,480px)}.jwi-max-h-none{max-height:none}.jwi-min-h-0{min-height:0}.jwi-min-h-\[48px\]{min-height:48px}.jwi-min-h-\[56px\]{min-height:56px}.jwi-w-1\/2{width:50%}.jwi-w-14{width:3.5rem}.jwi-w-2\/3{width:66.666667%}.jwi-w-24{width:6rem}.jwi-w-28{width:7rem}.jwi-w-3\.5{width:.875rem}.jwi-w-4{width:1rem}.jwi-w-48{width:12rem}.jwi-w-5{width:1.25rem}.jwi-w-9{width:2.25rem}.jwi-w-\[14px\]{width:14px}.jwi-w-\[18px\]{width:18px}.jwi-w-\[22px\]{width:22px}.jwi-w-\[40px\]{width:40px}.jwi-w-\[540px\]{width:540px}.jwi-w-\[min\(100vw\,1200px\)\]{width:min(100vw,1200px)}.jwi-w-\[min\(92vw\,620px\)\]{width:min(92vw,620px)}.jwi-w-\[min\(96vw\,1200px\)\]{width:min(96vw,1200px)}.jwi-w-auto{width:auto}.jwi-w-fit{width:-moz-fit-content;width:fit-content}.jwi-w-full{width:100%}.jwi-min-w-0{min-width:0}.jwi-max-w-\[220px\]{max-width:220px}.jwi-max-w-\[520px\]{max-width:520px}.jwi-max-w-\[70\%\]{max-width:70%}.jwi-max-w-\[calc\(100\%-5rem\)\]{max-width:calc(100% - 5rem)}.jwi-max-w-full{max-width:100%}.jwi-flex-1{flex:1 1 0%}.jwi-flex-shrink-0,.jwi-shrink-0{flex-shrink:0}.jwi-scale-100{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes jwi-pulse{50%{opacity:.5}}.jwi-animate-pulse{animation:jwi-pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes jwi-spin{to{transform:rotate(1turn)}}.jwi-animate-spin{animation:jwi-spin 1s linear infinite}.jwi-cursor-pointer{cursor:pointer}.jwi-resize-y{resize:vertical}.jwi-flex-row{flex-direction:row}.jwi-flex-col{flex-direction:column}.jwi-items-start{align-items:flex-start}.jwi-items-center{align-items:center}.jwi-items-stretch{align-items:stretch}.jwi-justify-end{justify-content:flex-end}.jwi-justify-center{justify-content:center}.jwi-justify-between{justify-content:space-between}.jwi-gap-0\.5{gap:.125rem}.jwi-gap-1\.5{gap:.375rem}.jwi-gap-2{gap:.5rem}.jwi-gap-3{gap:.75rem}.jwi-gap-4{gap:1rem}.jwi-space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.jwi-self-stretch{align-self:stretch}.jwi-overflow-hidden{overflow:hidden}.jwi-overflow-y-auto{overflow-y:auto}.jwi-overscroll-y-contain{overscroll-behavior-y:contain}.jwi-whitespace-nowrap{white-space:nowrap}.jwi-break-all{word-break:break-all}.jwi-rounded-\[10px\]{border-radius:10px}.jwi-rounded-\[12px\]{border-radius:12px}.jwi-rounded-full{border-radius:9999px}.jwi-rounded-md{border-radius:.375rem}.jwi-rounded-b-\[10px\]{border-bottom-right-radius:10px;border-bottom-left-radius:10px}.jwi-rounded-t-\[10px\]{border-top-left-radius:10px;border-top-right-radius:10px}.jwi-rounded-t-\[16px\]{border-top-left-radius:16px;border-top-right-radius:16px}.jwi-border{border-width:1px}.jwi-border-0{border-width:0}.jwi-border-b{border-bottom-width:1px}.jwi-border-t{border-top-width:1px}.jwi-border-t-0{border-top-width:0}.jwi-border-\[\#b7e5c2\]{--tw-border-opacity:1;border-color:rgb(183 229 194/var(--tw-border-opacity,1))}.jwi-border-\[\#b8d8aa\]{--tw-border-opacity:1;border-color:rgb(184 216 170/var(--tw-border-opacity,1))}.jwi-border-\[\#d8d2c7\]{--tw-border-opacity:1;border-color:rgb(216 210 199/var(--tw-border-opacity,1))}.jwi-border-\[\#e6e1d7\]{--tw-border-opacity:1;border-color:rgb(230 225 215/var(--tw-border-opacity,1))}.jwi-border-\[\#ef2b18\]{--tw-border-opacity:1;border-color:rgb(239 43 24/var(--tw-border-opacity,1))}.jwi-border-\[\#f0c7c2\]{--tw-border-opacity:1;border-color:rgb(240 199 194/var(--tw-border-opacity,1))}.jwi-bg-\[\#FCFCFC\]{--tw-bg-opacity:1;background-color:rgb(252 252 252/var(--tw-bg-opacity,1))}.jwi-bg-\[\#e8eef1\]{--tw-bg-opacity:1;background-color:rgb(232 238 241/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ece8df\]{--tw-bg-opacity:1;background-color:rgb(236 232 223/var(--tw-bg-opacity,1))}.jwi-bg-\[\#eefbf2\]{--tw-bg-opacity:1;background-color:rgb(238 251 242/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ef2b18\]{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}.jwi-bg-\[\#eff9e9\]{--tw-bg-opacity:1;background-color:rgb(239 249 233/var(--tw-bg-opacity,1))}.jwi-bg-\[\#f7f5ef\]{--tw-bg-opacity:1;background-color:rgb(247 245 239/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fbf3db\]{--tw-bg-opacity:1;background-color:rgb(251 243 219/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fff3f1\]{--tw-bg-opacity:1;background-color:rgb(255 243 241/var(--tw-bg-opacity,1))}.jwi-bg-black\/35{background-color:rgba(0,0,0,.35)}.jwi-bg-black\/45{background-color:rgba(0,0,0,.45)}.jwi-bg-transparent{background-color:transparent}.jwi-bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.jwi-bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.jwi-from-white{--tw-gradient-from:#fff var(--tw-gradient-from-position);--tw-gradient-to:hsla(0,0%,100%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.jwi-to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.jwi-p-0{padding:0}.jwi-p-2{padding:.5rem}.jwi-p-4{padding:1rem}.jwi-p-6{padding:1.5rem}.jwi-p-8{padding:2rem}.jwi-px-2\.5{padding-left:.625rem;padding-right:.625rem}.jwi-px-3{padding-left:.75rem;padding-right:.75rem}.jwi-px-4{padding-left:1rem;padding-right:1rem}.jwi-px-5{padding-left:1.25rem;padding-right:1.25rem}.jwi-px-6{padding-left:1.5rem;padding-right:1.5rem}.jwi-px-7{padding-left:1.75rem;padding-right:1.75rem}.jwi-py-1{padding-top:.25rem;padding-bottom:.25rem}.jwi-py-2{padding-top:.5rem;padding-bottom:.5rem}.jwi-py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.jwi-py-3{padding-top:.75rem;padding-bottom:.75rem}.jwi-py-4{padding-top:1rem;padding-bottom:1rem}.jwi-py-8{padding-top:2rem;padding-bottom:2rem}.jwi-pb-24{padding-bottom:6rem}.jwi-pb-3{padding-bottom:.75rem}.jwi-pl-5{padding-left:1.25rem}.jwi-pr-1{padding-right:.25rem}.jwi-pr-3{padding-right:.75rem}.jwi-pr-\[12px\]{padding-right:12px}.jwi-pt-3{padding-top:.75rem}.jwi-text-left{text-align:left}.jwi-text-center{text-align:center}.jwi-font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.jwi-text-2xl{font-size:1.5rem;line-height:2rem}.jwi-text-\[13px\]{font-size:13px}.jwi-text-base{font-size:1rem;line-height:1.5rem}.jwi-text-sm{font-size:.875rem;line-height:1.25rem}.jwi-text-xl{font-size:1.25rem;line-height:1.75rem}.jwi-text-xs{font-size:.75rem;line-height:1rem}.jwi-font-medium{font-weight:500}.jwi-font-normal{font-weight:400}.jwi-font-semibold{font-weight:600}.jwi-uppercase{text-transform:uppercase}.jwi-tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.jwi-leading-6{line-height:1.5rem}.jwi-leading-\[1\.35\]{line-height:1.35}.jwi-leading-\[1\.4\]{line-height:1.4}.jwi-leading-none{line-height:1}.jwi-leading-snug{line-height:1.375}.jwi-leading-tight{line-height:1.25}.jwi-tracking-\[0\.06em\]{letter-spacing:.06em}.jwi-text-\[\#111111\]{--tw-text-opacity:1;color:rgb(17 17 17/var(--tw-text-opacity,1))}.jwi-text-\[\#16330f\]{--tw-text-opacity:1;color:rgb(22 51 15/var(--tw-text-opacity,1))}.jwi-text-\[\#1b5e20\]{--tw-text-opacity:1;color:rgb(27 94 32/var(--tw-text-opacity,1))}.jwi-text-\[\#333333\]{--tw-text-opacity:1;color:rgb(51 51 51/var(--tw-text-opacity,1))}.jwi-text-\[\#767676\]{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.jwi-text-\[\#8f2d21\]{--tw-text-opacity:1;color:rgb(143 45 33/var(--tw-text-opacity,1))}.jwi-text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.jwi-text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.jwi-opacity-25{opacity:.25}.jwi-opacity-75{opacity:.75}.jwi-opacity-95{opacity:.95}.jwi-shadow-\[-8px_0_32px_rgba\(0\,0\,0\,0\.2\)\]{--tw-shadow:-8px 0 32px rgba(0,0,0,.2);--tw-shadow-colored:-8px 0 32px var(--tw-shadow-color)}.jwi-shadow-\[-8px_0_32px_rgba\(0\,0\,0\,0\.2\)\],.jwi-shadow-\[0_-12px_36px_rgba\(0\,0\,0\,0\.22\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_-12px_36px_rgba\(0\,0\,0\,0\.22\)\]{--tw-shadow:0 -12px 36px rgba(0,0,0,.22);--tw-shadow-colored:0 -12px 36px var(--tw-shadow-color)}.jwi-shadow-\[0_-6px_20px_rgba\(0\,0\,0\,0\.12\)\]{--tw-shadow:0 -6px 20px rgba(0,0,0,.12);--tw-shadow-colored:0 -6px 20px var(--tw-shadow-color)}.jwi-shadow-\[0_-6px_20px_rgba\(0\,0\,0\,0\.12\)\],.jwi-shadow-\[0_1px_2px_rgba\(17\,17\,17\,0\.03\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_1px_2px_rgba\(17\,17\,17\,0\.03\)\]{--tw-shadow:0 1px 2px hsla(0,0%,7%,.03);--tw-shadow-colored:0 1px 2px var(--tw-shadow-color)}.jwi-shadow-\[0_20px_60px_rgba\(0\,0\,0\,0\.25\)\]{--tw-shadow:0 20px 60px rgba(0,0,0,.25);--tw-shadow-colored:0 20px 60px var(--tw-shadow-color)}.jwi-shadow-\[0_20px_60px_rgba\(0\,0\,0\,0\.25\)\],.jwi-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.12\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.12\)\]{--tw-shadow:0 2px 8px rgba(0,0,0,.12);--tw-shadow-colored:0 2px 8px var(--tw-shadow-color)}.jwi-shadow-\[0_6px_16px_rgba\(22\,51\,15\,0\.08\)\]{--tw-shadow:0 6px 16px rgba(22,51,15,.08);--tw-shadow-colored:0 6px 16px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_8px_24px_rgba\(17\,17\,17\,0\.08\)\]{--tw-shadow:0 8px 24px hsla(0,0%,7%,.08);--tw-shadow-colored:0 8px 24px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-outline-none{outline:2px solid transparent;outline-offset:2px}.jwi-transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.placeholder\:jwi-text-\[13px\]::-moz-placeholder{font-size:13px}.placeholder\:jwi-text-\[13px\]::placeholder{font-size:13px}.placeholder\:jwi-text-\[\#767676\]::-moz-placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.placeholder\:jwi-text-\[\#767676\]::placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.hover\:jwi-bg-\[\#d92817\]:hover{--tw-bg-opacity:1;background-color:rgb(217 40 23/var(--tw-bg-opacity,1))}.hover\:jwi-bg-\[\#f7f5f0\]:hover{--tw-bg-opacity:1;background-color:rgb(247 245 240/var(--tw-bg-opacity,1))}.hover\:jwi-text-\[\#444444\]:hover{--tw-text-opacity:1;color:rgb(68 68 68/var(--tw-text-opacity,1))}.hover\:jwi-underline:hover{text-decoration-line:underline}.focus\:jwi-border-\[\#111111\]:focus{--tw-border-opacity:1;border-color:rgb(17 17 17/var(--tw-border-opacity,1))}.disabled\:jwi-cursor-wait:disabled{cursor:wait}.disabled\:hover\:jwi-bg-\[\#ef2b18\]:hover:disabled{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}@media (min-width:768px){.md\:jwi-h-full{height:100%}.md\:jwi-w-\[48\%\]{width:48%}.md\:jwi-w-\[52\%\]{width:52%}.md\:jwi-max-w-\[220px\]{max-width:220px}.md\:jwi-flex-row{flex-direction:row}.md\:jwi-items-center{align-items:center}.md\:jwi-justify-between{justify-content:space-between}.md\:jwi-border-r{border-right-width:1px}.md\:jwi-border-\[\#ece8df\]{--tw-border-opacity:1;border-color:rgb(236 232 223/var(--tw-border-opacity,1))}.md\:jwi-text-\[14px\]{font-size:14px}.md\:jwi-text-base{font-size:1rem;line-height:1.5rem}.md\:jwi-text-sm{font-size:.875rem;line-height:1.25rem}.md\:placeholder\:jwi-text-\[14px\]::-moz-placeholder{font-size:14px}.md\:placeholder\:jwi-text-\[14px\]::placeholder{font-size:14px}}
@@ -4,5 +4,5 @@ import type { JotulWidgetProps } from './types';
4
4
  export { DEFAULT_WIDGET_LOCALE_TAG, normalizeWidgetLocale, resolveWidgetUiLocale, } from './i18n/widgetStrings';
5
5
  export type { JotulWidgetLocale } from './i18n/widgetStrings';
6
6
  export { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, };
7
- export type { CheckWidgetAuthorizationOptions, DealerSearchResponse, JotulWidgetButtonStyling, JotulWidgetProps, ProductPageTriggerRenderProps, JotulWidgetStyling, JotulWidgetType, WidgetAuthClientResponse, } from './types';
8
- export declare function JotulWidget({ type, endpoint, className, productName, locale: localeProp, market: marketProp, brands, styling, productPageTrigger, }: JotulWidgetProps): import("react/jsx-runtime").JSX.Element;
7
+ export type { CheckWidgetAuthorizationOptions, DealerSearchResponse, JotulWidgetBorderStyling, JotulWidgetButtonStyling, JotulWidgetProps, WidgetTriggerRenderProps, JotulWidgetStyling, JotulWidgetType, WidgetAuthClientResponse, } from './types';
8
+ export declare function JotulWidget({ type, endpoint, className, productName, locale: localeProp, market: marketProp, brands, campaignSlug, styling, trigger, productPageTrigger, findDealerDrawerTrigger, }: JotulWidgetProps): import("react/jsx-runtime").JSX.Element;
@@ -3,11 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import './JotulWidget.css';
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
5
  import { FinderSearchRowSkeleton } from './components/FinderSearchRowSkeleton';
6
+ import { DealerCardSkeleton } from './components/DealerCardSkeleton';
6
7
  import { ProductPageWidget } from './widgets/ProductPageWidget';
8
+ import { FindDealerDrawerWidget } from './components/FindDealerDrawerWidget';
7
9
  import { ButtonSpinner } from './icons/ButtonSpinner';
8
10
  import { DEFAULT_WIDGET_LOCALE_TAG, resolveWidgetUiLocale, WIDGET_STRINGS, } from './i18n/widgetStrings';
9
11
  import { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, } from './api';
10
- import { createInquiryFormValues, getSafeWidgetErrorMessage, isValidEmail, isWidgetType, renderReadyState, } from './utils';
12
+ import { createInquiryFormValues, getSafeWidgetErrorMessage, isDealerInSearchResult, isValidEmail, isWidgetType, renderReadyState, } from './utils';
11
13
  import { getWidgetPrimaryButtonPresentation } from './utils/widgetPrimaryButtonPresentation';
12
14
  export { DEFAULT_WIDGET_LOCALE_TAG, normalizeWidgetLocale, resolveWidgetUiLocale, } from './i18n/widgetStrings';
13
15
  export { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, };
@@ -19,7 +21,14 @@ const GEOLOCATION_OPTIONS = {
19
21
  timeout: 15000,
20
22
  maximumAge: 300000,
21
23
  };
22
- export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, productName, locale: localeProp, market: marketProp, brands, styling, productPageTrigger, }) {
24
+ const MARKET_FALLBACK_CENTER = {
25
+ NO: [59.9139, 10.7522],
26
+ SE: [59.3293, 18.0686],
27
+ DK: [55.6761, 12.5683],
28
+ FI: [60.1699, 24.9384],
29
+ DE: [52.52, 13.405],
30
+ };
31
+ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, productName, locale: localeProp, market: marketProp, brands, campaignSlug, styling, trigger, productPageTrigger, findDealerDrawerTrigger, }) {
23
32
  const resolvedUiLocale = useMemo(() => resolveWidgetUiLocale(localeProp, marketProp), [localeProp, marketProp]);
24
33
  const t = WIDGET_STRINGS[resolvedUiLocale];
25
34
  const apiLocaleTag = useMemo(() => (localeProp?.trim() ? localeProp.trim() : DEFAULT_WIDGET_LOCALE_TAG), [localeProp]);
@@ -36,6 +45,7 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
36
45
  const [auth, setAuth] = useState(null);
37
46
  const [isLoading, setIsLoading] = useState(false);
38
47
  const [searchResult, setSearchResult] = useState(null);
48
+ const [mapSearchResult, setMapSearchResult] = useState(null);
39
49
  const [isSearching, setIsSearching] = useState(false);
40
50
  const [isSearchingSuggestions, setIsSearchingSuggestions] = useState(false);
41
51
  const [locationError, setLocationError] = useState(null);
@@ -50,23 +60,32 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
50
60
  const [inquiryError, setInquiryError] = useState(null);
51
61
  const [isInquirySubmitted, setIsInquirySubmitted] = useState(false);
52
62
  const autocompleteCacheRef = useRef(new Map());
63
+ const productPageCampaignSlug = type === 'productPage' ? campaignSlug : undefined;
53
64
  const dealerSearchOptions = useMemo(() => ({
54
65
  endpoint,
55
66
  locale: apiLocaleTag,
56
67
  market: apiMarket,
57
68
  brands,
58
- }), [apiLocaleTag, apiMarket, brands, endpoint]);
69
+ campaignSlug: productPageCampaignSlug,
70
+ }), [apiLocaleTag, apiMarket, brands, endpoint, productPageCampaignSlug]);
59
71
  const runDealerSearchByCoordinates = useCallback(async (latitude, longitude) => {
60
72
  setLocationError(null);
61
73
  setIsSearching(true);
62
74
  try {
63
- const result = await searchDealersByCoordinates(latitude, longitude, dealerSearchOptions);
75
+ const result = await searchDealersByCoordinates(latitude, longitude, dealerSearchOptions, 'list');
64
76
  setSearchResult(result);
77
+ const mapResult = await searchDealersByCoordinates(latitude, longitude, dealerSearchOptions, 'map');
78
+ setMapSearchResult(mapResult);
65
79
  }
66
80
  finally {
67
81
  setIsSearching(false);
68
82
  }
69
83
  }, [dealerSearchOptions]);
84
+ const runFallbackDealerSearch = useCallback(() => {
85
+ const fallbackCenter = (apiMarket != null ? MARKET_FALLBACK_CENTER[apiMarket] : undefined) ??
86
+ MARKET_FALLBACK_CENTER.NO;
87
+ return runDealerSearchByCoordinates(fallbackCenter[0], fallbackCenter[1]);
88
+ }, [apiMarket, runDealerSearchByCoordinates]);
70
89
  const runLocationSearch = useCallback(() => {
71
90
  const messages = WIDGET_STRINGS[resolvedUiLocale];
72
91
  setShouldAutoLocateAfterAuth(false);
@@ -77,6 +96,7 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
77
96
  setIsSuggestionListOpen(false);
78
97
  if (typeof navigator === 'undefined' || !navigator.geolocation) {
79
98
  setLocationError(messages.locationUnavailableBrowser);
99
+ void runFallbackDealerSearch();
80
100
  return;
81
101
  }
82
102
  setIsSearching(true);
@@ -99,8 +119,9 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
99
119
  else {
100
120
  setLocationError(messages.locationGenericFailure);
101
121
  }
122
+ void runFallbackDealerSearch();
102
123
  }, GEOLOCATION_OPTIONS);
103
- }, [resolvedUiLocale, runDealerSearchByCoordinates]);
124
+ }, [resolvedUiLocale, runDealerSearchByCoordinates, runFallbackDealerSearch]);
104
125
  useEffect(() => {
105
126
  const query = locationQuery.trim();
106
127
  if (query.length < 3) {
@@ -108,7 +129,7 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
108
129
  setLocationSuggestions([]);
109
130
  return;
110
131
  }
111
- const cacheKey = `${apiLocaleTag}|${apiMarket ?? ''}|${query.toLowerCase()}`;
132
+ const cacheKey = `${apiLocaleTag}|${apiMarket ?? ''}|${productPageCampaignSlug ?? ''}|${query.toLowerCase()}`;
112
133
  const cached = autocompleteCacheRef.current.get(cacheKey);
113
134
  if (cached != null) {
114
135
  setLocationSuggestions(cached);
@@ -129,9 +150,9 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
129
150
  cancelled = true;
130
151
  clearTimeout(timer);
131
152
  };
132
- }, [apiLocaleTag, apiMarket, dealerSearchOptions, locationQuery]);
153
+ }, [apiLocaleTag, apiMarket, productPageCampaignSlug, dealerSearchOptions, locationQuery]);
133
154
  useEffect(() => {
134
- if (type === 'productPage' && !isOpen)
155
+ if ((type === 'productPage' || type === 'findDealerDrawer') && !isOpen)
135
156
  return;
136
157
  if (auth != null)
137
158
  return;
@@ -152,7 +173,7 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
152
173
  useEffect(() => {
153
174
  if (!shouldAutoLocateAfterAuth)
154
175
  return;
155
- if (type !== 'productPage' || !isOpen)
176
+ if ((type !== 'productPage' && type !== 'findDealerDrawer') || !isOpen)
156
177
  return;
157
178
  if (auth == null || isLoading)
158
179
  return;
@@ -174,6 +195,7 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
174
195
  const openProductPageWidget = useCallback(() => {
175
196
  setLocationError(null);
176
197
  setSearchResult(null);
198
+ setMapSearchResult(null);
177
199
  setLocationQuery('');
178
200
  setLocationSuggestions([]);
179
201
  setIsSuggestionListOpen(false);
@@ -186,43 +208,72 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
186
208
  setShouldAutoLocateAfterAuth(true);
187
209
  }
188
210
  }, [auth, isLoading, runLocationSearch]);
211
+ const closeDealerWidget = useCallback(() => {
212
+ setIsOpen(false);
213
+ setLocationSuggestions([]);
214
+ setIsSuggestionListOpen(false);
215
+ setIsSearchingSuggestions(false);
216
+ setMapSearchResult(null);
217
+ }, []);
189
218
  const shellClass = 'jwi-box-border jwi-flex jwi-w-[540px] jwi-max-w-full jwi-flex-col jwi-font-sans jwi-text-[#111111]';
190
219
  const rootClass = className != null && className !== '' ? `${shellClass} ${className}` : shellClass;
191
220
  if (typeState !== 'typeReady') {
192
221
  return _jsx("div", { className: rootClass, children: t.invalidWidgetTypeError });
193
222
  }
223
+ const resolvedTrigger = trigger ?? productPageTrigger ?? findDealerDrawerTrigger;
224
+ const resolvedTriggerNode = typeof resolvedTrigger === 'function'
225
+ ? resolvedTrigger({
226
+ onOpen: openProductPageWidget,
227
+ isLoading,
228
+ label: t.findDealer,
229
+ className: heroPrimaryButton.className,
230
+ style: heroPrimaryButton.style,
231
+ })
232
+ : resolvedTrigger;
233
+ const renderTrigger = (alwaysClickable = false) => {
234
+ if (resolvedTriggerNode == null) {
235
+ return (_jsx("button", { type: "button", onClick: openProductPageWidget, className: heroPrimaryButton.className, style: heroPrimaryButton.style, children: t.findDealer }));
236
+ }
237
+ if (typeof resolvedTrigger === 'function' && !alwaysClickable) {
238
+ return resolvedTriggerNode;
239
+ }
240
+ return (_jsx("div", { role: "button", tabIndex: 0, onClick: openProductPageWidget, onKeyDown: (event) => {
241
+ if (event.key === 'Enter' || event.key === ' ') {
242
+ event.preventDefault();
243
+ openProductPageWidget();
244
+ }
245
+ }, children: resolvedTriggerNode }));
246
+ };
194
247
  if (widgetType === 'productPage' && !isOpen) {
195
- const trigger = typeof productPageTrigger === 'function'
196
- ? productPageTrigger({
197
- onOpen: openProductPageWidget,
198
- isLoading,
199
- label: t.findDealer,
200
- className: heroPrimaryButton.className,
201
- style: heroPrimaryButton.style,
202
- })
203
- : productPageTrigger;
204
- return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-py-8 jwi-items-center jwi-justify-center", children: trigger != null ? (_jsx("div", { role: "button", tabIndex: 0, onClick: openProductPageWidget, onKeyDown: (event) => {
205
- if (event.key === 'Enter' || event.key === ' ') {
206
- event.preventDefault();
207
- openProductPageWidget();
208
- }
209
- }, children: trigger })) : (_jsx("button", { type: "button", onClick: openProductPageWidget, className: heroPrimaryButton.className, style: heroPrimaryButton.style, children: t.findDealer })) }) }));
248
+ return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-py-8 jwi-items-center jwi-justify-center", children: renderTrigger() }) }));
210
249
  }
211
- const productPageAuthPending = widgetType === 'productPage' && isOpen && (auth === null || isLoading);
250
+ const productPageAuthPending = widgetType === 'productPage' &&
251
+ isOpen &&
252
+ (auth === null || isLoading);
212
253
  if (productPageAuthPending) {
213
254
  return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-py-8 jwi-items-center jwi-justify-center", children: _jsx("button", { type: "button", disabled: true, className: `${heroPrimaryButton.className} jwi-opacity-95`, style: heroPrimaryButton.style, children: _jsxs("span", { className: "jwi-inline-flex jwi-items-center jwi-gap-2", children: [_jsx(ButtonSpinner, {}), t.loading] }) }) }) }));
214
255
  }
215
- const waitingForAuth = auth === null && !(widgetType === 'productPage' && !isOpen);
216
- if ((isLoading || waitingForAuth) && type !== 'productPage') {
256
+ const waitingForAuth = auth === null &&
257
+ !((widgetType === 'productPage' || widgetType === 'findDealerDrawer') &&
258
+ !isOpen);
259
+ if ((isLoading || waitingForAuth) &&
260
+ type !== 'productPage' &&
261
+ type !== 'findDealerDrawer') {
217
262
  return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-w-full jwi-flex-col", children: _jsx(FinderSearchRowSkeleton, {}) }) }));
218
263
  }
219
- if (!auth?.ok || !auth.authorized) {
264
+ const deferAuthErrorUntilDrawerOpens = widgetType === 'findDealerDrawer' && !isOpen && auth === null;
265
+ const deferAuthUntilFindDealerDrawerResolves = widgetType === 'findDealerDrawer' && isOpen && (auth === null || isLoading);
266
+ if (!deferAuthErrorUntilDrawerOpens &&
267
+ !deferAuthUntilFindDealerDrawerResolves &&
268
+ (!auth?.ok || !auth.authorized)) {
220
269
  return _jsx("div", { className: rootClass, children: getSafeWidgetErrorMessage(auth?.error, t) });
221
270
  }
222
271
  if (widgetType === 'productPage') {
223
- return (_jsx("div", { className: rootClass, children: _jsx(ProductPageWidget, { t: t, buttonStyling: styling?.button, isSearching: isSearching, locationError: locationError, searchResult: searchResult?.ok === false
272
+ return (_jsx("div", { className: rootClass, children: _jsx(ProductPageWidget, { t: t, buttonStyling: styling?.button, borderStyling: styling?.border, market: apiMarket, isSearching: isSearching, locationError: locationError, searchResult: searchResult?.ok === false
224
273
  ? { ...searchResult, error: getSafeWidgetErrorMessage(searchResult.error, t) }
225
- : searchResult, inquiryValues: inquiryValues, inquiryError: inquiryError, isInquirySubmitted: isInquirySubmitted, selectedDealerName: selectedDealerName, isManualSearchEnabled: isManualLocationSearchEnabled, query: locationQuery, suggestions: locationSuggestions, suggestionsOpen: isSuggestionListOpen, isSuggestionsLoading: isSearchingSuggestions, onQueryChange: (value) => {
274
+ : searchResult, mapSearchResult: mapSearchResult?.ok === false
275
+ ? { ...mapSearchResult, error: getSafeWidgetErrorMessage(mapSearchResult.error, t) }
276
+ : mapSearchResult, inquiryValues: inquiryValues, inquiryError: inquiryError, isInquirySubmitted: isInquirySubmitted, selectedDealerName: selectedDealerName, isManualSearchEnabled: isManualLocationSearchEnabled, query: locationQuery, suggestions: locationSuggestions, suggestionsOpen: isSuggestionListOpen, isSuggestionsLoading: isSearchingSuggestions, onQueryChange: (value) => {
226
277
  setLocationQuery(value);
227
278
  const trimmed = value.trim();
228
279
  setIsSuggestionListOpen(trimmed.length > 0);
@@ -283,12 +334,91 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, p
283
334
  setInquiryValues(createInquiryFormValues(productName, dealerName));
284
335
  setInquiryError(null);
285
336
  setIsInquirySubmitted(false);
286
- }, onClosePopup: () => {
287
- setIsOpen(false);
288
- setLocationSuggestions([]);
289
- setIsSuggestionListOpen(false);
290
- setIsSearchingSuggestions(false);
291
- } }) }));
337
+ }, onMapDealerSelect: (dealer) => {
338
+ if (isDealerInSearchResult(dealer.dealerName, searchResult, t.unknownDealer))
339
+ return;
340
+ void runDealerSearchByCoordinates(dealer.latitude, dealer.longitude);
341
+ }, onClosePopup: closeDealerWidget }) }));
342
+ }
343
+ if (widgetType === 'findDealerDrawer') {
344
+ const drawerLoading = isOpen &&
345
+ (auth === null ||
346
+ isLoading ||
347
+ (isSearching && searchResult == null && mapSearchResult == null));
348
+ return (_jsxs("div", { className: rootClass, children: [_jsx("div", { className: "jwi-flex jwi-items-center jwi-justify-center jwi-py-8", children: renderTrigger() }), isOpen && (_jsx("div", { className: "jwi-fixed jwi-inset-0 jwi-z-[9998] jwi-bg-black/35", onClick: closeDealerWidget })), _jsx("div", { className: "jwi-fixed jwi-right-0 jwi-top-0 jwi-z-[9999] jwi-h-full jwi-w-[min(100vw,1200px)] jwi-bg-white jwi-shadow-[-8px_0_32px_rgba(0,0,0,0.2)]", style: {
349
+ transform: isOpen ? 'translateX(0)' : 'translateX(100%)',
350
+ transition: 'transform 300ms ease-out',
351
+ willChange: 'transform',
352
+ }, "aria-hidden": !isOpen, children: drawerLoading ? (_jsxs("div", { className: "jwi-flex jwi-h-full jwi-w-full jwi-bg-white", children: [_jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-1/2 jwi-flex-col jwi-overflow-hidden", children: _jsxs("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: [_jsx("div", { className: "jwi-h-12 jwi-w-full jwi-animate-pulse jwi-rounded-[10px] jwi-bg-[#ece8df]" }), _jsx("div", { className: "jwi-h-5 jwi-w-48 jwi-animate-pulse jwi-rounded-full jwi-bg-[#ece8df]" }), _jsxs("div", { className: "jwi-flex jwi-flex-col jwi-gap-4", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] }) }), _jsx("div", { className: "jwi-h-full jwi-w-1/2 jwi-bg-[#e8eef1]" })] })) : (_jsx(FindDealerDrawerWidget, { t: t, buttonStyling: styling?.button, borderStyling: styling?.border, market: apiMarket, isSearching: isSearching, locationError: locationError, searchResult: searchResult?.ok === false
353
+ ? { ...searchResult, error: getSafeWidgetErrorMessage(searchResult.error, t) }
354
+ : searchResult, mapSearchResult: mapSearchResult?.ok === false
355
+ ? { ...mapSearchResult, error: getSafeWidgetErrorMessage(mapSearchResult.error, t) }
356
+ : mapSearchResult, inquiryValues: inquiryValues, inquiryError: inquiryError, isInquirySubmitted: isInquirySubmitted, selectedDealerName: selectedDealerName, isManualSearchEnabled: isManualLocationSearchEnabled, query: locationQuery, suggestions: locationSuggestions, suggestionsOpen: isSuggestionListOpen, isSuggestionsLoading: isSearchingSuggestions, onQueryChange: (value) => {
357
+ setLocationQuery(value);
358
+ const trimmed = value.trim();
359
+ setIsSuggestionListOpen(trimmed.length > 0);
360
+ if (trimmed.length < 3) {
361
+ setLocationSuggestions([]);
362
+ }
363
+ }, onQuerySubmit: async (value) => {
364
+ const query = value.trim();
365
+ if (query.length < 3)
366
+ return;
367
+ setIsSearchingSuggestions(true);
368
+ const result = await searchLocationSuggestions(query, dealerSearchOptions);
369
+ const resolvedSuggestions = result.ok && Array.isArray(result.suggestions) ? result.suggestions : [];
370
+ setLocationSuggestions(resolvedSuggestions);
371
+ setIsSearchingSuggestions(false);
372
+ const suggestion = resolvedSuggestions[0];
373
+ if (!suggestion)
374
+ return;
375
+ setLocationQuery(suggestion.label);
376
+ setLocationSuggestions([]);
377
+ setIsSearchingSuggestions(false);
378
+ setIsSuggestionListOpen(false);
379
+ await runDealerSearchByCoordinates(suggestion.latitude, suggestion.longitude);
380
+ }, onSuggestionSelect: (suggestion) => {
381
+ setLocationQuery(suggestion.label);
382
+ setLocationSuggestions([]);
383
+ setIsSearchingSuggestions(false);
384
+ setIsSuggestionListOpen(false);
385
+ void runDealerSearchByCoordinates(suggestion.latitude, suggestion.longitude);
386
+ }, onDismissSuggestions: () => {
387
+ setLocationSuggestions([]);
388
+ setIsSearchingSuggestions(false);
389
+ setIsSuggestionListOpen(false);
390
+ }, onInquiryClose: () => {
391
+ setSelectedDealerName(null);
392
+ setInquiryValues(null);
393
+ setInquiryError(null);
394
+ }, onInquirySubmit: () => {
395
+ if (inquiryValues == null)
396
+ return;
397
+ const trimmedName = inquiryValues.name.trim();
398
+ const trimmedEmail = inquiryValues.email.trim();
399
+ const trimmedPhone = inquiryValues.phone.trim();
400
+ if (!trimmedName || !trimmedEmail || !trimmedPhone) {
401
+ setInquiryError(t.formValidationRequired);
402
+ return;
403
+ }
404
+ if (!isValidEmail(trimmedEmail)) {
405
+ setInquiryError(t.formValidationEmail);
406
+ return;
407
+ }
408
+ setInquiryError(null);
409
+ setIsInquirySubmitted(true);
410
+ setSelectedDealerName(null);
411
+ setInquiryValues(null);
412
+ }, onInquiryFieldChange: (key, value) => setInquiryValues((current) => current == null ? current : { ...current, [key]: value }), onStartInquiry: (dealerName) => {
413
+ setSelectedDealerName(dealerName);
414
+ setInquiryValues(createInquiryFormValues(productName, dealerName));
415
+ setInquiryError(null);
416
+ setIsInquirySubmitted(false);
417
+ }, onMapDealerSelect: (dealer) => {
418
+ if (isDealerInSearchResult(dealer.dealerName, searchResult, t.unknownDealer))
419
+ return;
420
+ void runDealerSearchByCoordinates(dealer.latitude, dealer.longitude);
421
+ }, onClose: closeDealerWidget })) })] }));
292
422
  }
293
423
  return _jsx("div", { className: rootClass, children: renderReadyState(widgetType, t) });
294
424
  }
package/dist/api.d.ts CHANGED
@@ -2,6 +2,6 @@ import type { CheckWidgetAuthorizationOptions, DealerSearchResponse, LocationAut
2
2
  /** Client-side default when JSON parse fails (English; localized in UI). */
3
3
  export declare const GENERIC_WIDGET_ERROR = "Dealer finder is currently unavailable. Please try again later.";
4
4
  export declare function checkWidgetAuthorization(options?: CheckWidgetAuthorizationOptions): Promise<WidgetAuthClientResponse>;
5
- export declare function searchDealersByPostalCode(postalCode: string, options?: CheckWidgetAuthorizationOptions): Promise<DealerSearchResponse>;
6
- export declare function searchDealersByCoordinates(latitude: number, longitude: number, options?: CheckWidgetAuthorizationOptions): Promise<DealerSearchResponse>;
5
+ export declare function searchDealersByPostalCode(postalCode: string, options?: CheckWidgetAuthorizationOptions, scope?: 'list' | 'map'): Promise<DealerSearchResponse>;
6
+ export declare function searchDealersByCoordinates(latitude: number, longitude: number, options?: CheckWidgetAuthorizationOptions, scope?: 'list' | 'map'): Promise<DealerSearchResponse>;
7
7
  export declare function searchLocationSuggestions(query: string, options?: CheckWidgetAuthorizationOptions): Promise<LocationAutocompleteResponse>;
package/dist/api.js CHANGED
@@ -35,10 +35,11 @@ export async function checkWidgetAuthorization(options) {
35
35
  return { ok: false, error: GENERIC_WIDGET_ERROR };
36
36
  }
37
37
  }
38
- export async function searchDealersByPostalCode(postalCode, options) {
38
+ export async function searchDealersByPostalCode(postalCode, options, scope = 'list') {
39
39
  const endpoint = options?.endpoint ?? '/api/jotul/widget';
40
40
  const fetcher = options?.fetcher ?? fetch;
41
41
  const params = new URLSearchParams({ postalCode: postalCode.trim() });
42
+ params.set('scope', scope);
42
43
  appendLocaleAndMarket(params, options);
43
44
  if (Array.isArray(options?.brands)) {
44
45
  for (const brand of options.brands) {
@@ -47,6 +48,9 @@ export async function searchDealersByPostalCode(postalCode, options) {
47
48
  params.append('brands', value);
48
49
  }
49
50
  }
51
+ const campaignSlug = options?.campaignSlug?.trim();
52
+ if (campaignSlug)
53
+ params.set('campaignSlug', campaignSlug);
50
54
  let response;
51
55
  try {
52
56
  response = await fetcher(`${endpoint}?${params.toString()}`, {
@@ -65,13 +69,14 @@ export async function searchDealersByPostalCode(postalCode, options) {
65
69
  return { ok: false, error: GENERIC_WIDGET_ERROR };
66
70
  }
67
71
  }
68
- export async function searchDealersByCoordinates(latitude, longitude, options) {
72
+ export async function searchDealersByCoordinates(latitude, longitude, options, scope = 'list') {
69
73
  const endpoint = options?.endpoint ?? '/api/jotul/widget';
70
74
  const fetcher = options?.fetcher ?? fetch;
71
75
  const params = new URLSearchParams({
72
76
  latitude: String(latitude),
73
77
  longitude: String(longitude),
74
78
  });
79
+ params.set('scope', scope);
75
80
  appendLocaleAndMarket(params, options);
76
81
  if (Array.isArray(options?.brands)) {
77
82
  for (const brand of options.brands) {
@@ -80,6 +85,9 @@ export async function searchDealersByCoordinates(latitude, longitude, options) {
80
85
  params.append('brands', value);
81
86
  }
82
87
  }
88
+ const campaignSlug = options?.campaignSlug?.trim();
89
+ if (campaignSlug)
90
+ params.set('campaignSlug', campaignSlug);
83
91
  let response;
84
92
  try {
85
93
  response = await fetcher(`${endpoint}?${params.toString()}`, {
@@ -106,6 +114,9 @@ export async function searchLocationSuggestions(query, options) {
106
114
  q: query.trim(),
107
115
  });
108
116
  appendLocaleAndMarket(params, options);
117
+ const campaignSlug = options?.campaignSlug?.trim();
118
+ if (campaignSlug)
119
+ params.set('campaignSlug', campaignSlug);
109
120
  let response;
110
121
  try {
111
122
  response = await fetcher(`${endpoint}?${params.toString()}`, {
@@ -0,0 +1,39 @@
1
+ import 'leaflet/dist/leaflet.css';
2
+ import 'leaflet.markercluster/dist/MarkerCluster.css';
3
+ import type { WidgetStrings } from '../i18n/widgetStrings';
4
+ import type { DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, LocationSuggestion } from '../types';
5
+ type FindDealerDrawerWidgetProps = {
6
+ t: WidgetStrings;
7
+ buttonStyling?: JotulWidgetButtonStyling;
8
+ borderStyling?: JotulWidgetBorderStyling;
9
+ market?: string;
10
+ isSearching: boolean;
11
+ locationError: string | null;
12
+ searchResult: DealerSearchResponse | null;
13
+ mapSearchResult?: DealerSearchResponse | null;
14
+ inquiryValues: InquiryFormValues | null;
15
+ inquiryError: string | null;
16
+ isInquirySubmitted: boolean;
17
+ selectedDealerName: string | null;
18
+ isManualSearchEnabled: boolean;
19
+ query: string;
20
+ suggestions: LocationSuggestion[];
21
+ suggestionsOpen: boolean;
22
+ isSuggestionsLoading: boolean;
23
+ onQueryChange: (value: string) => void;
24
+ onQuerySubmit: (value: string) => void | Promise<void>;
25
+ onSuggestionSelect: (suggestion: LocationSuggestion) => void;
26
+ onDismissSuggestions: () => void;
27
+ onInquiryClose: () => void;
28
+ onInquirySubmit: () => void;
29
+ onInquiryFieldChange: (key: keyof InquiryFormValues, value: string) => void;
30
+ onStartInquiry: (dealerName: string) => void;
31
+ onMapDealerSelect?: (dealer: {
32
+ dealerName: string;
33
+ latitude: number;
34
+ longitude: number;
35
+ }) => void;
36
+ onClose: () => void;
37
+ };
38
+ export declare function FindDealerDrawerWidget({ t, buttonStyling, borderStyling, market, 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;
39
+ export {};