@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.
- package/README.md +21 -1
- package/dist/JotulWidget.css +1 -1
- package/dist/JotulWidget.d.ts +2 -2
- package/dist/JotulWidget.js +166 -36
- package/dist/api.d.ts +2 -2
- package/dist/api.js +13 -2
- package/dist/components/FindDealerDrawerWidget.d.ts +39 -0
- package/dist/components/FindDealerDrawerWidget.js +318 -0
- package/dist/components/ProductPageWidget.d.ts +11 -2
- package/dist/components/ProductPageWidget.js +215 -41
- package/dist/components/product-page/CampaignStatus.d.ts +6 -0
- package/dist/components/product-page/CampaignStatus.js +48 -0
- package/dist/components/product-page/DealerList.d.ts +4 -2
- package/dist/components/product-page/DealerList.js +12 -4
- package/dist/components/product-page/StatusBanner.d.ts +1 -1
- package/dist/components/product-page/StatusBanner.js +3 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/i18n/locales/cz.json +5 -1
- package/dist/i18n/locales/de.json +5 -1
- package/dist/i18n/locales/en.json +5 -1
- package/dist/i18n/locales/fi.json +5 -1
- package/dist/i18n/locales/fr.json +5 -1
- package/dist/i18n/locales/nl.json +5 -1
- package/dist/i18n/locales/no.json +5 -1
- package/dist/i18n/locales/pl.json +5 -1
- package/dist/i18n/locales/se.json +5 -1
- package/dist/i18n/widgetStrings.d.ts +4 -0
- package/dist/images/dealer-pin-ildstedet.svg +31 -0
- package/dist/images/dealer-pin.svg +8 -3
- package/dist/images/ildstedet-dealer.svg +702 -0
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +26 -5
- package/dist/utils/cssColor.d.ts +5 -0
- package/dist/utils/cssColor.js +31 -0
- package/dist/utils/dealerMapClustering.d.ts +28 -0
- package/dist/utils/dealerMapClustering.js +71 -0
- package/dist/utils/loadLeafletMarkerCluster.d.ts +5 -0
- package/dist/utils/loadLeafletMarkerCluster.js +20 -0
- package/dist/utils/markerClusterIconHtml.d.ts +2 -0
- package/dist/utils/markerClusterIconHtml.js +7 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +10 -0
- package/package.json +5 -2
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import 'leaflet/dist/leaflet.css';
|
|
3
|
+
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { icon, latLng } from 'leaflet';
|
|
6
|
+
import { MapContainer, Marker, TileLayer, Tooltip, useMap } from 'react-leaflet';
|
|
7
|
+
import { DealerCardSkeleton } from './DealerCardSkeleton';
|
|
8
|
+
import { DealerList } from './product-page/DealerList';
|
|
9
|
+
import { InquiryForm } from './product-page/InquiryForm';
|
|
10
|
+
import { LocationSearch } from './product-page/LocationSearch';
|
|
11
|
+
import { StatusBanner } from './product-page/StatusBanner';
|
|
12
|
+
import { ArrowRightIcon } from '../icons/ArrowRightIcon';
|
|
13
|
+
import dealerPin from '../images/dealer-pin.svg';
|
|
14
|
+
import dealerPinExclusive from '../images/dealer-pin-exclusive.svg';
|
|
15
|
+
import dealerPinIldstedet from '../images/dealer-pin-ildstedet.svg';
|
|
16
|
+
import { MARKER_CLUSTER_LINK_KM, MARKER_CLUSTER_MIN_GROUP, partitionDealersForMarkerClusterPlugin, } from '../utils/dealerMapClustering';
|
|
17
|
+
import { loadLeafletMarkerCluster } from '../utils/loadLeafletMarkerCluster';
|
|
18
|
+
import { markerClusterCountIconHtml } from '../utils/markerClusterIconHtml';
|
|
19
|
+
import { JOTUL_BRAND_PRIMARY_HEX } from '../constants';
|
|
20
|
+
const OSM_MINIMAL_TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
|
|
21
|
+
function mapPointsBoundsKey(points, defaultCenter) {
|
|
22
|
+
const body = points.length === 0
|
|
23
|
+
? ''
|
|
24
|
+
: [...points]
|
|
25
|
+
.sort((a, b) => a.latitude !== b.latitude
|
|
26
|
+
? a.latitude - b.latitude
|
|
27
|
+
: a.longitude - b.longitude || a.dealerName.localeCompare(b.dealerName))
|
|
28
|
+
.map((p) => `${p.dealerName}\t${p.latitude}\t${p.longitude}`)
|
|
29
|
+
.join('\n');
|
|
30
|
+
const dc = defaultCenter != null ? `${defaultCenter[0]},${defaultCenter[1]}` : '';
|
|
31
|
+
return `${body}|${dc}`;
|
|
32
|
+
}
|
|
33
|
+
function readNumber(value) {
|
|
34
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
const parsed = Number(value);
|
|
38
|
+
if (Number.isFinite(parsed))
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function getDealerName(dealer) {
|
|
44
|
+
const raw = dealer.name;
|
|
45
|
+
return typeof raw === 'string' && raw.trim() ? raw.trim() : 'Unknown dealer';
|
|
46
|
+
}
|
|
47
|
+
function getDealerMapPoint(dealer) {
|
|
48
|
+
const latitude = readNumber(dealer.latitude);
|
|
49
|
+
const longitude = readNumber(dealer.longitude);
|
|
50
|
+
if (latitude == null || longitude == null)
|
|
51
|
+
return null;
|
|
52
|
+
const rawExclusive = dealer.exclusive;
|
|
53
|
+
const isExclusive = rawExclusive === true ||
|
|
54
|
+
rawExclusive === 1 ||
|
|
55
|
+
rawExclusive === '1' ||
|
|
56
|
+
rawExclusive === 'true' ||
|
|
57
|
+
rawExclusive === 'TRUE' ||
|
|
58
|
+
rawExclusive === 'yes' ||
|
|
59
|
+
rawExclusive === 'YES' ||
|
|
60
|
+
rawExclusive === 'y' ||
|
|
61
|
+
rawExclusive === 'Y';
|
|
62
|
+
return { dealerName: getDealerName(dealer), latitude, longitude, isExclusive };
|
|
63
|
+
}
|
|
64
|
+
function toAssetSrc(value) {
|
|
65
|
+
return typeof value === 'string' ? value : value.src;
|
|
66
|
+
}
|
|
67
|
+
function createDealerPinIcon(isExclusive, active, market) {
|
|
68
|
+
const size = active ? 48 : 42;
|
|
69
|
+
const width = Math.round((16 / 20) * size);
|
|
70
|
+
const exclusivePin = market === 'NO' ? dealerPinIldstedet : dealerPinExclusive;
|
|
71
|
+
const pinUrl = isExclusive ? toAssetSrc(exclusivePin) : toAssetSrc(dealerPin);
|
|
72
|
+
return icon({
|
|
73
|
+
iconUrl: pinUrl,
|
|
74
|
+
iconRetinaUrl: pinUrl,
|
|
75
|
+
iconSize: [width, size],
|
|
76
|
+
iconAnchor: [Math.round(width / 2), size],
|
|
77
|
+
tooltipAnchor: [0, -Math.round(size * 0.85)],
|
|
78
|
+
className: 'jwi-map-pin',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function FitMapBounds({ boundsKey, points, defaultCenter, }) {
|
|
82
|
+
const map = useMap();
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const all = points.map((p) => [p.latitude, p.longitude]);
|
|
85
|
+
if (defaultCenter != null)
|
|
86
|
+
all.push(defaultCenter);
|
|
87
|
+
if (all.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
if (all.length === 1) {
|
|
90
|
+
map.setView(all[0], 12, { animate: false });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// No animation: programmatic fit must finish immediately so FocusActiveDealer can center the selected pin;
|
|
94
|
+
// otherwise fitBounds completes after Focus and pulls the viewport away from the active dealer (common with geo).
|
|
95
|
+
map.fitBounds(all, { padding: [50, 50], animate: false, maxZoom: 19 });
|
|
96
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- boundsKey encodes points + defaultCenter; avoid refit on array identity churn
|
|
97
|
+
}, [boundsKey, map]);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function FocusActiveDealer({ point }) {
|
|
101
|
+
const map = useMap();
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (point == null)
|
|
104
|
+
return;
|
|
105
|
+
const target = latLng(point.latitude, point.longitude);
|
|
106
|
+
// Always pan to active dealer — after fitBounds it may be merely inside the hull, not centered.
|
|
107
|
+
map.setView(target, Math.max(map.getZoom(), 13), { animate: true });
|
|
108
|
+
}, [map, point]);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function ClusteredMapMarkers({ points, pointsBoundsKey, clusterThemeKey, clusterBrandFill, clusterBrandLabel, market, activeDealerName, onSelectDealer, onUnavailable, }) {
|
|
112
|
+
const map = useMap();
|
|
113
|
+
const markersByDealerRef = useRef(new Map());
|
|
114
|
+
const pointsRef = useRef(points);
|
|
115
|
+
const activeNameRef = useRef(activeDealerName);
|
|
116
|
+
const onSelectRef = useRef(onSelectDealer);
|
|
117
|
+
const onUnavailableRef = useRef(onUnavailable);
|
|
118
|
+
pointsRef.current = points;
|
|
119
|
+
activeNameRef.current = activeDealerName;
|
|
120
|
+
onSelectRef.current = onSelectDealer;
|
|
121
|
+
onUnavailableRef.current = onUnavailable;
|
|
122
|
+
function applyPinsForSelection() {
|
|
123
|
+
const active = activeNameRef.current;
|
|
124
|
+
for (const p of pointsRef.current) {
|
|
125
|
+
const marker = markersByDealerRef.current.get(p.dealerName);
|
|
126
|
+
if (marker != null) {
|
|
127
|
+
marker.setIcon(createDealerPinIcon(p.isExclusive, active === p.dealerName, market));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
applyPinsForSelection();
|
|
133
|
+
}, [activeDealerName]);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
let disposed = false;
|
|
136
|
+
markersByDealerRef.current.clear();
|
|
137
|
+
let clusterGroup = null;
|
|
138
|
+
let plainGroup = null;
|
|
139
|
+
async function mountClusters() {
|
|
140
|
+
try {
|
|
141
|
+
const leafletModule = await import('leaflet');
|
|
142
|
+
if (disposed)
|
|
143
|
+
return;
|
|
144
|
+
const { clustered, standalone } = partitionDealersForMarkerClusterPlugin(pointsRef.current, {
|
|
145
|
+
linkKm: MARKER_CLUSTER_LINK_KM,
|
|
146
|
+
minGroupSize: MARKER_CLUSTER_MIN_GROUP,
|
|
147
|
+
});
|
|
148
|
+
const leafletMaybeDefault = leafletModule;
|
|
149
|
+
const leaf = (leafletMaybeDefault.default ??
|
|
150
|
+
leafletMaybeDefault);
|
|
151
|
+
if (typeof leaf.featureGroup !== 'function') {
|
|
152
|
+
onUnavailableRef.current?.();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
function makeMarker(point) {
|
|
156
|
+
const marker = leaf.marker([point.latitude, point.longitude], {
|
|
157
|
+
icon: createDealerPinIcon(point.isExclusive, activeNameRef.current === point.dealerName, market),
|
|
158
|
+
});
|
|
159
|
+
marker.on('click', () => {
|
|
160
|
+
onSelectRef.current?.({
|
|
161
|
+
dealerName: point.dealerName,
|
|
162
|
+
latitude: point.latitude,
|
|
163
|
+
longitude: point.longitude,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
marker.bindTooltip(point.dealerName);
|
|
167
|
+
markersByDealerRef.current.set(point.dealerName, marker);
|
|
168
|
+
return marker;
|
|
169
|
+
}
|
|
170
|
+
if (standalone.length > 0) {
|
|
171
|
+
plainGroup = leaf.featureGroup();
|
|
172
|
+
for (const point of standalone) {
|
|
173
|
+
plainGroup.addLayer(makeMarker(point));
|
|
174
|
+
}
|
|
175
|
+
plainGroup.addTo(map);
|
|
176
|
+
}
|
|
177
|
+
if (clustered.length > 0) {
|
|
178
|
+
await loadLeafletMarkerCluster(leaf);
|
|
179
|
+
if (disposed)
|
|
180
|
+
return;
|
|
181
|
+
if (typeof leaf.markerClusterGroup !== 'function') {
|
|
182
|
+
onUnavailableRef.current?.();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
clusterGroup = leaf.markerClusterGroup({
|
|
186
|
+
showCoverageOnHover: false,
|
|
187
|
+
spiderfyOnMaxZoom: true,
|
|
188
|
+
spiderLegPolylineOptions: {
|
|
189
|
+
weight: 1.75,
|
|
190
|
+
color: clusterBrandFill,
|
|
191
|
+
opacity: 0.38,
|
|
192
|
+
},
|
|
193
|
+
iconCreateFunction: (cluster) => {
|
|
194
|
+
const n = cluster.getChildCount();
|
|
195
|
+
return leaf.divIcon({
|
|
196
|
+
html: markerClusterCountIconHtml(n, clusterBrandFill, clusterBrandLabel),
|
|
197
|
+
className: '',
|
|
198
|
+
iconSize: leaf.point(40, 40),
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
maxClusterRadius: (zoom) => zoom <= 6 ? 140 : zoom <= 8 ? 125 : zoom <= 10 ? 100 : zoom <= 11 ? 72 : 45,
|
|
202
|
+
disableClusteringAtZoom: 12,
|
|
203
|
+
chunkedLoading: true,
|
|
204
|
+
});
|
|
205
|
+
for (const point of clustered) {
|
|
206
|
+
clusterGroup.addLayer(makeMarker(point));
|
|
207
|
+
}
|
|
208
|
+
clusterGroup.addTo(map);
|
|
209
|
+
}
|
|
210
|
+
applyPinsForSelection();
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
onUnavailableRef.current?.();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
void mountClusters();
|
|
217
|
+
return () => {
|
|
218
|
+
disposed = true;
|
|
219
|
+
if (clusterGroup != null) {
|
|
220
|
+
clusterGroup.clearLayers();
|
|
221
|
+
clusterGroup.removeFrom(map);
|
|
222
|
+
}
|
|
223
|
+
if (plainGroup != null) {
|
|
224
|
+
plainGroup.clearLayers();
|
|
225
|
+
plainGroup.removeFrom(map);
|
|
226
|
+
}
|
|
227
|
+
markersByDealerRef.current.clear();
|
|
228
|
+
};
|
|
229
|
+
}, [map, pointsBoundsKey, clusterThemeKey]);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
export 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, }) {
|
|
233
|
+
const dealers = (searchResult?.dealers ?? []);
|
|
234
|
+
const mapDealers = (mapSearchResult?.dealers ?? dealers);
|
|
235
|
+
const total = dealers.length;
|
|
236
|
+
const inquiryFormOpen = inquiryValues != null;
|
|
237
|
+
const [activeDealerName, setActiveDealerName] = useState(null);
|
|
238
|
+
const [mobileMapExpanded, setMobileMapExpanded] = useState(false);
|
|
239
|
+
const [isMobileViewport, setIsMobileViewport] = useState(false);
|
|
240
|
+
const [visibleDealerCount, setVisibleDealerCount] = useState(10);
|
|
241
|
+
const [clusterUnavailable, setClusterUnavailable] = useState(false);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (typeof window === 'undefined')
|
|
244
|
+
return;
|
|
245
|
+
const media = window.matchMedia('(max-width: 767px)');
|
|
246
|
+
const update = () => setIsMobileViewport(media.matches);
|
|
247
|
+
update();
|
|
248
|
+
media.addEventListener('change', update);
|
|
249
|
+
return () => media.removeEventListener('change', update);
|
|
250
|
+
}, []);
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
setActiveDealerName(dealers.length > 0 ? getDealerName(dealers[0]) : null);
|
|
253
|
+
}, [dealers]);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
setVisibleDealerCount(10);
|
|
256
|
+
}, [dealers]);
|
|
257
|
+
const visibleDealers = useMemo(() => dealers.slice(0, Math.max(10, visibleDealerCount)), [dealers, visibleDealerCount]);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (activeDealerName == null)
|
|
260
|
+
return;
|
|
261
|
+
const activeIndex = dealers.findIndex((dealer) => getDealerName(dealer) === activeDealerName);
|
|
262
|
+
if (activeIndex < 0 || activeIndex < visibleDealerCount)
|
|
263
|
+
return;
|
|
264
|
+
const nextCount = Math.ceil((activeIndex + 1) / 10) * 10;
|
|
265
|
+
setVisibleDealerCount(nextCount);
|
|
266
|
+
}, [activeDealerName, dealers, visibleDealerCount]);
|
|
267
|
+
const mapPoints = useMemo(() => mapDealers.map((dealer) => getDealerMapPoint(dealer)).filter((v) => v != null), [mapDealers]);
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
setClusterUnavailable(false);
|
|
270
|
+
}, [mapPoints]);
|
|
271
|
+
const defaultCenter = useMemo(() => {
|
|
272
|
+
const first = mapPoints[0];
|
|
273
|
+
if (first != null)
|
|
274
|
+
return [first.latitude, first.longitude];
|
|
275
|
+
const latitude = readNumber(searchResult?.origin?.latitude);
|
|
276
|
+
const longitude = readNumber(searchResult?.origin?.longitude);
|
|
277
|
+
if (latitude == null || longitude == null)
|
|
278
|
+
return null;
|
|
279
|
+
return [latitude, longitude];
|
|
280
|
+
}, [mapPoints, searchResult?.origin?.latitude, searchResult?.origin?.longitude]);
|
|
281
|
+
const mapBoundsKey = useMemo(() => mapPointsBoundsKey(mapPoints, defaultCenter), [mapPoints, defaultCenter]);
|
|
282
|
+
const activeMapPoint = useMemo(() => mapPoints.find((p) => p.dealerName === activeDealerName) ?? null, [activeDealerName, mapPoints]);
|
|
283
|
+
const mapClusterTheme = useMemo(() => {
|
|
284
|
+
const fill = buttonStyling?.backgroundColor?.trim() || JOTUL_BRAND_PRIMARY_HEX;
|
|
285
|
+
const label = buttonStyling?.textColor?.trim() || '#ffffff';
|
|
286
|
+
return {
|
|
287
|
+
fill,
|
|
288
|
+
label,
|
|
289
|
+
key: `${fill}\0${label}`,
|
|
290
|
+
};
|
|
291
|
+
}, [buttonStyling?.backgroundColor, buttonStyling?.textColor]);
|
|
292
|
+
const showInquirySuccessScreen = isInquirySubmitted && !inquiryFormOpen;
|
|
293
|
+
const successPanel = (_jsx("div", { className: "jwi-flex jwi-w-full jwi-items-center jwi-justify-center jwi-bg-white jwi-p-2", children: _jsxs("div", { className: "jwi-flex jwi-w-full jwi-max-w-[520px] jwi-flex-col jwi-items-center jwi-gap-4 jwi-rounded-[10px] jwi-bg-white jwi-p-8 jwi-text-center", children: [_jsx("div", { className: "jwi-text-2xl jwi-font-semibold jwi-text-[#111111]", children: t.inquiryThankYouTitle }), _jsx("div", { className: "jwi-text-sm jwi-leading-6 jwi-text-[#333333]", children: t.inquirySentSuccess }), _jsx("button", { type: "button", onClick: onClose, className: "jwi-mt-2 jwi-inline-flex jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-md jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-5 jwi-py-2.5 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: t.goBack })] }) }));
|
|
294
|
+
const canShowMore = visibleDealerCount < dealers.length;
|
|
295
|
+
const showMoreButton = canShowMore ? (_jsx("button", { type: "button", onClick: () => setVisibleDealerCount((count) => Math.min(count + 10, dealers.length)), className: "jwi-mt-3 jwi-inline-flex jwi-w-full jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-md jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-px-3 jwi-py-2 jwi-text-sm jwi-font-medium jwi-text-[#111111]", children: "Show more" })) : null;
|
|
296
|
+
const mapCanvas = (_jsxs(MapContainer, { center: defaultCenter ?? [59.9139, 10.7522], zoom: 6, className: "jwi-h-full jwi-w-full", zoomControl: true, children: [_jsx(TileLayer, { attribution: '\u00A9 <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noreferrer">OpenStreetMap</a> contributors \u00A9 <a href="https://carto.com/attributions" target="_blank" rel="noreferrer">CARTO</a>', url: OSM_MINIMAL_TILE_URL, maxZoom: 19 }), _jsx(FitMapBounds, { boundsKey: mapBoundsKey, points: mapPoints, defaultCenter: defaultCenter }), _jsx(FocusActiveDealer, { point: activeMapPoint }), _jsx(ClusteredMapMarkers, { points: mapPoints, pointsBoundsKey: mapBoundsKey, clusterThemeKey: mapClusterTheme.key, clusterBrandFill: mapClusterTheme.fill, clusterBrandLabel: mapClusterTheme.label, market: market, activeDealerName: activeDealerName, onUnavailable: () => setClusterUnavailable(true), onSelectDealer: (dealer) => {
|
|
297
|
+
setActiveDealerName(dealer.dealerName);
|
|
298
|
+
onMapDealerSelect?.(dealer);
|
|
299
|
+
} }), clusterUnavailable &&
|
|
300
|
+
mapPoints.map((point) => {
|
|
301
|
+
const isActive = activeDealerName === point.dealerName;
|
|
302
|
+
return (_jsx(Marker, { position: [point.latitude, point.longitude], icon: createDealerPinIcon(point.isExclusive, isActive, market), eventHandlers: {
|
|
303
|
+
click: () => {
|
|
304
|
+
setActiveDealerName(point.dealerName);
|
|
305
|
+
onMapDealerSelect?.({
|
|
306
|
+
dealerName: point.dealerName,
|
|
307
|
+
latitude: point.latitude,
|
|
308
|
+
longitude: point.longitude,
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
}, children: _jsx(Tooltip, { children: point.dealerName }) }, `${point.dealerName}-${point.latitude}-${point.longitude}`));
|
|
312
|
+
})] }));
|
|
313
|
+
const leftContent = (_jsx(_Fragment, { children: showInquirySuccessScreen ? (successPanel) : inquiryFormOpen ? (_jsx(InquiryForm, { t: t, buttonStyling: buttonStyling, inquiryValues: inquiryValues, inquiryError: inquiryError, embedded: true, onInquiryClose: onInquiryClose, onInquirySubmit: onInquirySubmit, onInquiryFieldChange: onInquiryFieldChange })) : (_jsxs(_Fragment, { children: [_jsx(LocationSearch, { t: t, isManualSearchEnabled: isManualSearchEnabled, query: query, suggestions: suggestions, suggestionsOpen: suggestionsOpen, isSuggestionsLoading: isSuggestionsLoading, onQueryChange: onQueryChange, onQuerySubmit: onQuerySubmit, onSuggestionSelect: onSuggestionSelect, onDismissSuggestions: onDismissSuggestions }), locationError != null && !isManualSearchEnabled && (_jsx(StatusBanner, { tone: "error", children: locationError })), searchResult?.ok === false && (_jsx(StatusBanner, { tone: "error", children: searchResult.error ?? '' })), isSearching && (_jsxs("div", { className: "jwi-flex jwi-flex-col", children: [_jsx("div", { className: "jwi-w-full jwi-flex-shrink-0 jwi-border-b jwi-border-[#e6e1d7] jwi-pb-3", children: _jsx("div", { className: "jwi-h-5 jwi-w-48 jwi-animate-pulse jwi-rounded-full jwi-bg-[#ece8df]" }) }), _jsxs("div", { className: "jwi-mt-3 jwi-mr-[-12px] jwi-flex jwi-flex-col jwi-gap-4 jwi-overflow-y-auto jwi-pr-[12px]", children: [_jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {}), _jsx(DealerCardSkeleton, {})] })] })), searchResult?.ok && !isSearching && (_jsxs(_Fragment, { children: [_jsx(DealerList, { dealers: visibleDealers, total: total, selectedDealerName: selectedDealerName, activeDealerName: activeDealerName, fitAvailableHeight: !isMobileViewport, autoScrollToActive: true, enableInternalScroll: !isMobileViewport, maxHeightClassName: "jwi-max-h-none", t: t, buttonStyling: buttonStyling, borderStyling: borderStyling, market: market, onStartInquiry: onStartInquiry, onSelectDealer: setActiveDealerName }), showMoreButton] }))] })) }));
|
|
314
|
+
if (isMobileViewport) {
|
|
315
|
+
return (_jsxs("div", { className: "jwi-relative jwi-flex jwi-h-full jwi-flex-col jwi-bg-white", children: [_jsx("div", { className: "jwi-flex jwi-justify-end jwi-bg-white jwi-px-4 jwi-pt-3", children: _jsx("button", { type: "button", onClick: onClose, className: "jwi-inline-flex jwi-h-9 jwi-w-9 jwi-cursor-pointer jwi-items-center jwi-justify-center jwi-rounded-full jwi-border jwi-border-[#d8d2c7] jwi-bg-white jwi-text-xl jwi-leading-none jwi-text-[#111111]", "aria-label": t.closeMap, children: "\u00D7" }) }), _jsx("div", { className: "jwi-min-h-0 jwi-flex-1 jwi-overflow-y-auto jwi-bg-white jwi-p-4 jwi-pb-24", children: leftContent }), !inquiryFormOpen && !showInquirySuccessScreen && !mobileMapExpanded && (_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded(true), className: "jwi-absolute jwi-inset-x-0 jwi-bottom-0 jwi-z-20 jwi-flex jwi-h-14 jwi-items-center jwi-justify-center jwi-gap-2 jwi-rounded-t-[16px] jwi-border-t jwi-border-[#e6e1d7] jwi-bg-white jwi-text-sm jwi-font-semibold jwi-text-[#111111] jwi-shadow-[0_-6px_20px_rgba(0,0,0,0.12)]", children: [t.openMap, _jsx("span", { className: "jwi-inline-flex", style: { transform: 'rotate(-90deg)' }, children: _jsx(ArrowRightIcon, { className: "jwi-h-4 jwi-w-4 jwi-shrink-0" }) })] })), mobileMapExpanded && !inquiryFormOpen && !showInquirySuccessScreen && (_jsxs("div", { className: "jwi-absolute jwi-inset-x-0 jwi-bottom-0 jwi-z-30 jwi-h-[78vh] jwi-overflow-hidden jwi-rounded-t-[16px] jwi-bg-white jwi-shadow-[0_-12px_36px_rgba(0,0,0,0.22)]", children: [_jsxs("button", { type: "button", onClick: () => setMobileMapExpanded(false), className: "jwi-flex jwi-h-12 jwi-w-full jwi-items-center jwi-justify-center jwi-gap-2 jwi-border-b jwi-border-[#e6e1d7] jwi-bg-white jwi-text-sm jwi-font-semibold jwi-text-[#111111]", children: [t.closeMapMobile, _jsx("span", { className: "jwi-inline-flex", style: { transform: 'rotate(90deg)' }, children: _jsx(ArrowRightIcon, { className: "jwi-h-4 jwi-w-4 jwi-shrink-0" }) })] }), _jsx("div", { className: "jwi-h-[calc(78vh-48px)] jwi-w-full", children: mapCanvas })] }))] }));
|
|
316
|
+
}
|
|
317
|
+
return (_jsxs("div", { className: "jwi-flex jwi-h-full jwi-w-full jwi-bg-white", children: [_jsx("button", { type: "button", onClick: onClose, 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" }), _jsx("div", { className: "jwi-flex jwi-h-full jwi-min-h-0 jwi-w-1/2 jwi-flex-col jwi-overflow-hidden", 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: leftContent }) }), _jsx("div", { className: "jwi-h-full jwi-w-1/2", children: mapCanvas })] }));
|
|
318
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import 'leaflet/dist/leaflet.css';
|
|
2
|
+
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
|
2
3
|
import type { WidgetStrings } from '../i18n/widgetStrings';
|
|
3
|
-
import type { DealerSearchResponse, InquiryFormValues, JotulWidgetButtonStyling, LocationSuggestion } from '../types';
|
|
4
|
+
import type { DealerSearchResponse, InquiryFormValues, JotulWidgetBorderStyling, JotulWidgetButtonStyling, LocationSuggestion } from '../types';
|
|
4
5
|
type ProductPageWidgetProps = {
|
|
5
6
|
t: WidgetStrings;
|
|
6
7
|
buttonStyling?: JotulWidgetButtonStyling;
|
|
8
|
+
borderStyling?: JotulWidgetBorderStyling;
|
|
9
|
+
market?: string;
|
|
7
10
|
isSearching: boolean;
|
|
8
11
|
locationError: string | null;
|
|
9
12
|
searchResult: DealerSearchResponse | null;
|
|
13
|
+
mapSearchResult?: DealerSearchResponse | null;
|
|
10
14
|
inquiryValues: InquiryFormValues | null;
|
|
11
15
|
inquiryError: string | null;
|
|
12
16
|
isInquirySubmitted: boolean;
|
|
@@ -24,7 +28,12 @@ type ProductPageWidgetProps = {
|
|
|
24
28
|
onInquirySubmit: () => void;
|
|
25
29
|
onInquiryFieldChange: (key: keyof InquiryFormValues, value: string) => void;
|
|
26
30
|
onStartInquiry: (dealerName: string) => void;
|
|
31
|
+
onMapDealerSelect?: (dealer: {
|
|
32
|
+
dealerName: string;
|
|
33
|
+
latitude: number;
|
|
34
|
+
longitude: number;
|
|
35
|
+
}) => void;
|
|
27
36
|
onClosePopup?: () => void;
|
|
28
37
|
};
|
|
29
|
-
export declare function ProductPageWidget({ t, buttonStyling, isSearching, locationError, searchResult, inquiryValues, inquiryError, isInquirySubmitted, selectedDealerName, isManualSearchEnabled, query, suggestions, suggestionsOpen, isSuggestionsLoading, onQueryChange, onQuerySubmit, onSuggestionSelect, onDismissSuggestions, onInquiryClose, onInquirySubmit, onInquiryFieldChange, onStartInquiry, onClosePopup, }: ProductPageWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
export declare function ProductPageWidget({ 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, onClosePopup, }: ProductPageWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
30
39
|
export {};
|