@abcagency/hc-ui-components 1.7.4 → 1.7.6
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/dist/components/HireControlMap.js +97 -166
- package/dist/components/HireControlMap.js.map +1 -1
- package/dist/components/containers/accordions/map-accordion-item-container.js +3 -1
- package/dist/components/containers/accordions/map-accordion-item-container.js.map +1 -1
- package/dist/components/containers/filter/commute-container.js +11 -3
- package/dist/components/containers/filter/commute-container.js.map +1 -1
- package/dist/components/containers/maps/map-container.js +2 -0
- package/dist/components/containers/maps/map-container.js.map +1 -1
- package/dist/components/containers/maps/map-list-container.js +34 -3
- package/dist/components/containers/maps/map-list-container.js.map +1 -1
- package/dist/components/modules/accordions/default.js +1 -1
- package/dist/components/modules/buttons/button-group-apply.js +1 -1
- package/dist/components/modules/buttons/default.js +1 -1
- package/dist/components/modules/cards/default.js +1 -1
- package/dist/components/modules/filter/commute.js +3 -1
- package/dist/components/modules/filter/commute.js.map +1 -1
- package/dist/components/modules/filter/index.js +1 -0
- package/dist/components/modules/filter/index.js.map +1 -1
- package/dist/components/modules/filter/item.js +1 -0
- package/dist/components/modules/filter/item.js.map +1 -1
- package/dist/components/modules/filter/sort.js +1 -1
- package/dist/components/modules/grid.js +1 -1
- package/dist/components/modules/list/header.js +1 -1
- package/dist/components/modules/list/item-expand-card/index.js +1 -1
- package/dist/components/modules/list/list-item/list-item.js +85 -14
- package/dist/components/modules/list/list-item/list-item.js.map +1 -1
- package/dist/contexts/mapListContext.js +40 -39
- package/dist/contexts/mapListContext.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/styles/index.css +1 -1
- package/dist/util/algoliaSearchUtil.js +1 -1
- package/dist/util/filterUtil.js +1 -1
- package/dist/util/twMerge.js +1 -1
- package/package.json +4 -1
- package/src/apis/hcApi.ts +9 -2
- package/src/components/HireControlMap.js +19 -56
- package/src/components/containers/accordions/map-accordion-item-container.js +3 -1
- package/src/components/containers/filter/commute-container.js +11 -3
- package/src/components/containers/maps/map-container.js +2 -0
- package/src/components/containers/maps/map-list-container.js +30 -3
- package/src/components/modules/filter/commute.js +7 -5
- package/src/components/modules/filter/index.js +2 -0
- package/src/components/modules/filter/item.js +1 -0
- package/src/components/modules/list/list-item/list-item.jsx +97 -24
- package/src/contexts/mapListContext.tsx +39 -38
- package/src/index.js +1 -0
- package/dist/apis/hcApi.js +0 -91
- package/dist/apis/hcApi.js.map +0 -1
- package/dist/clientToken.js +0 -10
- package/dist/clientToken.js.map +0 -1
- package/dist/components/modules/skeleton/map-skeleton.js +0 -50
- package/dist/components/modules/skeleton/map-skeleton.js.map +0 -1
- package/dist/node_modules/@algolia/client-common/dist/common.js +0 -541
- package/dist/node_modules/@algolia/client-common/dist/common.js.map +0 -1
- package/dist/node_modules/@algolia/requester-browser-xhr/dist/requester.xhr.js +0 -4
- package/dist/node_modules/@algolia/requester-browser-xhr/dist/requester.xhr.js.map +0 -1
- package/dist/node_modules/algoliasearch/dist/lite/builds/browser.js +0 -272
- package/dist/node_modules/algoliasearch/dist/lite/builds/browser.js.map +0 -1
- package/dist/node_modules/fuse.js/dist/fuse.js +0 -1779
- package/dist/node_modules/fuse.js/dist/fuse.js.map +0 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +0 -2580
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +0 -1
- package/dist/services/configService.js +0 -15
- package/dist/services/configService.js.map +0 -1
- package/dist/services/listingAggregatorService.js +0 -41
- package/dist/services/listingAggregatorService.js.map +0 -1
- package/dist/services/listingEntityService.js +0 -16
- package/dist/services/listingEntityService.js.map +0 -1
- package/dist/services/listingService.js +0 -15
- package/dist/services/listingService.js.map +0 -1
package/dist/util/filterUtil.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { slicedToArray as _slicedToArray, typeof as _typeof, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, asyncToGenerator as _asyncToGenerator, regeneratorRuntime as _regeneratorRuntime, defineProperty as _defineProperty } from '../_virtual/_rollupPluginBabelHelpers.js';
|
|
2
2
|
import { getDistinctItemsByProximity } from './mapUtil.js';
|
|
3
3
|
import { isAlgoliaAvailable, filterListingsByAlgoliaSearch } from './algoliaSearchUtil.js';
|
|
4
|
-
import Fuse from '
|
|
4
|
+
import Fuse from 'fuse.js';
|
|
5
5
|
|
|
6
6
|
var getFilterOptions = function getFilterOptions(listings, filteredListings, field) {
|
|
7
7
|
var excludeZeroCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
package/dist/util/twMerge.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abcagency/hc-ui-components",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"description": "UI Components for HireControl",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
6
9
|
"files": [
|
|
7
10
|
"dist",
|
|
8
11
|
"src"
|
package/src/apis/hcApi.ts
CHANGED
|
@@ -13,7 +13,11 @@ const memoryStorage: MemoryStorage = {
|
|
|
13
13
|
|
|
14
14
|
function setStorage(key: keyof MemoryStorage, value: string): void {
|
|
15
15
|
try {
|
|
16
|
-
|
|
16
|
+
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
17
|
+
sessionStorage.setItem(key, value);
|
|
18
|
+
} else {
|
|
19
|
+
memoryStorage[key] = value;
|
|
20
|
+
}
|
|
17
21
|
} catch (error) {
|
|
18
22
|
memoryStorage[key] = value;
|
|
19
23
|
}
|
|
@@ -21,7 +25,10 @@ function setStorage(key: keyof MemoryStorage, value: string): void {
|
|
|
21
25
|
|
|
22
26
|
function getStorage(key: keyof MemoryStorage): string | null {
|
|
23
27
|
try {
|
|
24
|
-
|
|
28
|
+
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
29
|
+
return sessionStorage.getItem(key) || memoryStorage[key];
|
|
30
|
+
}
|
|
31
|
+
return memoryStorage[key];
|
|
25
32
|
} catch (error) {
|
|
26
33
|
return memoryStorage[key];
|
|
27
34
|
}
|
|
@@ -12,43 +12,12 @@ import { MapListProvider } from '~/contexts/mapListContext';
|
|
|
12
12
|
import { TrackEventProvider } from '~/contexts/trackEventContext';
|
|
13
13
|
import { ThemeProvider } from '~/contexts/themeContext';
|
|
14
14
|
|
|
15
|
-
import { getMapConfig } from '~/services/configService';
|
|
16
|
-
import { setClientAuthKey } from '~/clientToken.js';
|
|
17
15
|
import { initializeAlgoliaSearch } from '~/util/algoliaSearchUtil';
|
|
18
16
|
|
|
19
|
-
import '../styles/components.css';
|
|
20
|
-
|
|
21
17
|
// Libraries for Google Maps
|
|
22
18
|
const LIBRARIES = ['places'];
|
|
23
19
|
|
|
24
|
-
//
|
|
25
|
-
function useSiteConfig(clientToken, siteConfiguration = null) {
|
|
26
|
-
const [siteConfig, setSiteConfig] = useState(siteConfiguration);
|
|
27
|
-
const [isLoading, setIsLoading] = useState(!siteConfiguration); // If config provided, not loading
|
|
28
|
-
const [error, setError] = useState(null);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
const fetchSiteConfig = async () => {
|
|
32
|
-
try {
|
|
33
|
-
setClientAuthKey(clientToken);
|
|
34
|
-
const configData = await getMapConfig(clientToken);
|
|
35
|
-
setSiteConfig(configData);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.error('Failed to fetch site configuration:', err);
|
|
38
|
-
setError(err);
|
|
39
|
-
} finally {
|
|
40
|
-
setIsLoading(false);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
if(!siteConfig){
|
|
44
|
-
fetchSiteConfig();
|
|
45
|
-
} else {
|
|
46
|
-
setIsLoading(false); // Config already provided
|
|
47
|
-
}
|
|
48
|
-
}, [clientToken, siteConfig]);
|
|
49
|
-
|
|
50
|
-
return { siteConfig, isLoading, error };
|
|
51
|
-
}
|
|
20
|
+
// Since all data is now passed in statically, no need to fetch config
|
|
52
21
|
|
|
53
22
|
// Component to handle all context providers
|
|
54
23
|
const ContextProviders = ({ children, siteConfig, trackEvent, googleMapsApiKey, ...mapListProps }) => { return(
|
|
@@ -171,9 +140,6 @@ export const HireControlMap = ({
|
|
|
171
140
|
}
|
|
172
141
|
}
|
|
173
142
|
}) => {
|
|
174
|
-
// Load site configuration (non-blocking)
|
|
175
|
-
const { siteConfig, isLoading: isConfigLoading, error } = useSiteConfig(clientToken, siteConfiguration);
|
|
176
|
-
|
|
177
143
|
// Initialize Algolia immediately if credentials are provided
|
|
178
144
|
const [algoliaInitialized, setAlgoliaInitialized] = useState(() => {
|
|
179
145
|
if (algoliaAppId && algoliaApiKey && algoliaIndexName) {
|
|
@@ -204,13 +170,10 @@ export const HireControlMap = ({
|
|
|
204
170
|
return <div className="hc-p-4">Error loading Google Maps</div>;
|
|
205
171
|
}
|
|
206
172
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Render immediately even if config is loading - use defaults or provided siteConfiguration
|
|
212
|
-
if (!siteConfig) {
|
|
213
|
-
return null; // Brief flash, config should load very quickly
|
|
173
|
+
// siteConfiguration is now required - all data passed in statically
|
|
174
|
+
if (!siteConfiguration) {
|
|
175
|
+
console.error('Site configuration is required');
|
|
176
|
+
return <div className="hc-p-4">Site configuration is required</div>;
|
|
214
177
|
}
|
|
215
178
|
|
|
216
179
|
// Prepare props for MapListProvider
|
|
@@ -224,7 +187,7 @@ export const HireControlMap = ({
|
|
|
224
187
|
easyApplyText,
|
|
225
188
|
listings,
|
|
226
189
|
entities,
|
|
227
|
-
siteConfig,
|
|
190
|
+
siteConfig: siteConfiguration,
|
|
228
191
|
getListingEntitiesCallback,
|
|
229
192
|
setFiltersUrl,
|
|
230
193
|
hiddenFilters,
|
|
@@ -243,17 +206,17 @@ export const HireControlMap = ({
|
|
|
243
206
|
};
|
|
244
207
|
|
|
245
208
|
// Calculate effective showMap value
|
|
246
|
-
const effectiveShowMap = hideMap ? false :
|
|
209
|
+
const effectiveShowMap = hideMap ? false : siteConfiguration.showMap;
|
|
247
210
|
|
|
248
211
|
// Prepare marker configuration
|
|
249
212
|
const markerConfigs = {
|
|
250
|
-
fillColor:
|
|
251
|
-
strokeColor:
|
|
252
|
-
selectedFillColor:
|
|
253
|
-
selectedStrokeColor:
|
|
213
|
+
fillColor: siteConfiguration.colors.primary,
|
|
214
|
+
strokeColor: siteConfiguration.colors.primaryDark,
|
|
215
|
+
selectedFillColor: siteConfiguration.colors.secondary,
|
|
216
|
+
selectedStrokeColor: siteConfiguration.colors.secondaryDark,
|
|
254
217
|
placeMarkers: {
|
|
255
|
-
colors:
|
|
256
|
-
size:
|
|
218
|
+
colors: siteConfiguration.pointsOfInterestConfig.placeMarkerColors,
|
|
219
|
+
size: siteConfiguration.pointsOfInterestConfig.placeMarkerSize
|
|
257
220
|
}
|
|
258
221
|
};
|
|
259
222
|
|
|
@@ -261,7 +224,7 @@ export const HireControlMap = ({
|
|
|
261
224
|
// Only Google Maps components will wait for isMapsLoaded
|
|
262
225
|
return (
|
|
263
226
|
<ContextProviders
|
|
264
|
-
siteConfig={
|
|
227
|
+
siteConfig={siteConfiguration}
|
|
265
228
|
trackEvent={trackEvent}
|
|
266
229
|
googleMapsApiKey={googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY}
|
|
267
230
|
{...mapListProps}
|
|
@@ -281,12 +244,12 @@ export const HireControlMap = ({
|
|
|
281
244
|
)}
|
|
282
245
|
<MapList
|
|
283
246
|
containerStyle={containerStyle}
|
|
284
|
-
mapPosition={
|
|
247
|
+
mapPosition={siteConfiguration.mapPosition}
|
|
285
248
|
showMap={effectiveShowMap}
|
|
286
|
-
fieldsShown={
|
|
287
|
-
specialFeatures={
|
|
288
|
-
fieldNames={
|
|
289
|
-
placeMappings={
|
|
249
|
+
fieldsShown={siteConfiguration.fieldsShown}
|
|
250
|
+
specialFeatures={siteConfiguration.specialFeatures}
|
|
251
|
+
fieldNames={siteConfiguration.fieldNames}
|
|
252
|
+
placeMappings={siteConfiguration.pointsOfInterestConfig.placeMappings ?? {}}
|
|
290
253
|
markerConfigs={markerConfigs}
|
|
291
254
|
/>
|
|
292
255
|
</Grid>
|
|
@@ -21,7 +21,9 @@ const MapAccordionItemContainer = ({
|
|
|
21
21
|
|
|
22
22
|
const setSelectedItemAndZoomMap = (item, isActive) => {
|
|
23
23
|
if (isActive) {
|
|
24
|
-
|
|
24
|
+
if (typeof window !== 'undefined') {
|
|
25
|
+
localStorage.removeItem("selectedListItem");
|
|
26
|
+
}
|
|
25
27
|
selectItem(null, null, 9, { lat: 39.8283, lng: -98.5795 });
|
|
26
28
|
} else {
|
|
27
29
|
setStorageObject("selectedListItem", item);
|
|
@@ -23,15 +23,19 @@ const FilterCommuteContainer = ({ className }) => {
|
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
if (commuteLocation !== null && commuteLocation !== '') return;
|
|
25
25
|
setIsCurrentLocation(false);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (typeof window !== 'undefined') {
|
|
27
|
+
localStorage.removeItem('isCurrentLocation');
|
|
28
|
+
localStorage.removeItem('selectedCommute');
|
|
29
|
+
}
|
|
28
30
|
setSelected("");
|
|
29
31
|
}, [commuteLocation]);
|
|
30
32
|
|
|
31
33
|
const handleSelect = async (val, isCurrLocation = false) => {
|
|
32
34
|
setValue(val, false);
|
|
33
35
|
setSelected(val);
|
|
34
|
-
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
localStorage.setItem('selectedCommute', val);
|
|
38
|
+
}
|
|
35
39
|
clearSuggestions();
|
|
36
40
|
if (isCurrLocation) return;
|
|
37
41
|
try {
|
|
@@ -45,6 +49,10 @@ const FilterCommuteContainer = ({ className }) => {
|
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const fetchLocation = () => {
|
|
52
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
53
|
+
console.error("Geolocation is not available in this environment.");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
48
56
|
if (!navigator.geolocation) {
|
|
49
57
|
console.error("Geolocation is not supported by this browser.");
|
|
50
58
|
return;
|
|
@@ -145,6 +145,7 @@ const MapContainer = ({ markerConfigs, infoWindowClasses, clusterGridSize = 60 }
|
|
|
145
145
|
|
|
146
146
|
const fitBounds = (map, overload = false) => {
|
|
147
147
|
if ((mapInteracted === false || overload) && mapItems != null) {
|
|
148
|
+
if (typeof window === 'undefined' || !window.google) return;
|
|
148
149
|
const bounds = new window.google.maps.LatLngBounds();
|
|
149
150
|
|
|
150
151
|
mapItems.forEach(item => {
|
|
@@ -229,6 +230,7 @@ const MapContainer = ({ markerConfigs, infoWindowClasses, clusterGridSize = 60 }
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
setTimeout(() => {
|
|
233
|
+
if (typeof window === 'undefined' || !window.google) return;
|
|
232
234
|
const bounds = new window.google.maps.LatLngBounds();
|
|
233
235
|
|
|
234
236
|
markers.forEach(marker => {
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect, lazy, Suspense } from 'react';
|
|
2
2
|
|
|
3
3
|
import List from '~/components/containers/list/item-list-container';
|
|
4
|
-
import Map from '~/components/containers/maps/map-container';
|
|
5
4
|
import Filter from '~/components/containers/filter/filter-container';
|
|
6
5
|
import MapList from '~/components/modules/maps/map-list';
|
|
7
6
|
|
|
7
|
+
// Dynamically import the Map container (Google Maps is heavy)
|
|
8
|
+
const Map = lazy(() => import('~/components/containers/maps/map-container'));
|
|
9
|
+
|
|
10
|
+
// Loading placeholder that maintains layout
|
|
11
|
+
const MapLoadingPlaceholder = () => (
|
|
12
|
+
<div className="hc-w-full hc-h-full hc-bg-gray-100 hc-flex hc-items-center hc-justify-center">
|
|
13
|
+
<div className="hc-text-center hc-p-8">
|
|
14
|
+
<div className="hc-animate-spin hc-rounded-full hc-h-12 hc-w-12 hc-border-b-2 hc-border-gray-900 hc-mx-auto"></div>
|
|
15
|
+
<p className="hc-mt-4 hc-text-gray-600">Loading map...</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
|
|
8
20
|
const MapListContainer = ({
|
|
9
21
|
loading = false,
|
|
10
22
|
containerStyle,
|
|
@@ -18,6 +30,12 @@ const MapListContainer = ({
|
|
|
18
30
|
placeMappings,
|
|
19
31
|
mapPosition
|
|
20
32
|
}) => {
|
|
33
|
+
const [isClient, setIsClient] = useState(false);
|
|
34
|
+
|
|
35
|
+
// Only load map on client side
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setIsClient(true);
|
|
38
|
+
}, []);
|
|
21
39
|
|
|
22
40
|
const listProps = {
|
|
23
41
|
fieldsShown,
|
|
@@ -36,13 +54,22 @@ const MapListContainer = ({
|
|
|
36
54
|
showMap
|
|
37
55
|
};
|
|
38
56
|
|
|
57
|
+
// Render map only after client-side hydration
|
|
58
|
+
const mapComponent = isClient && showMap ? (
|
|
59
|
+
<Suspense fallback={<MapLoadingPlaceholder />}>
|
|
60
|
+
<Map {...mapProps} />
|
|
61
|
+
</Suspense>
|
|
62
|
+
) : (
|
|
63
|
+
showMap ? <MapLoadingPlaceholder /> : null
|
|
64
|
+
);
|
|
65
|
+
|
|
39
66
|
return (
|
|
40
67
|
<MapList
|
|
41
68
|
showMap={showMap}
|
|
42
69
|
containerStyle={containerStyle}
|
|
43
70
|
loading={loading}
|
|
44
71
|
list={<List {...listProps} />}
|
|
45
|
-
map={
|
|
72
|
+
map={mapComponent}
|
|
46
73
|
filter={<Filter showMap={showMap} className="md:hc-hidden" />}
|
|
47
74
|
mapPosition={mapPosition}
|
|
48
75
|
/>
|
|
@@ -57,12 +57,14 @@ const FilterCommute = ({
|
|
|
57
57
|
size="sqsm"
|
|
58
58
|
onClick={() => {
|
|
59
59
|
setIsCurrentLocation(!isCurrentLocation);
|
|
60
|
+
if (typeof window !== 'undefined') {
|
|
60
61
|
localStorage.setItem('isCurrentLocation', !isCurrentLocation);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}}
|
|
62
|
+
}
|
|
63
|
+
if (isCurrentLocation || commuteLocation) {
|
|
64
|
+
setCommuteLocation("");
|
|
65
|
+
setSelected("");
|
|
66
|
+
} else if (!commuteLocation) { fetchLocation(); }
|
|
67
|
+
}}
|
|
66
68
|
|
|
67
69
|
className=""
|
|
68
70
|
>
|
|
@@ -23,6 +23,8 @@ const Filter = ({
|
|
|
23
23
|
const transitionTimeoutRef = useRef(null);
|
|
24
24
|
|
|
25
25
|
useEffect(() => {
|
|
26
|
+
if (typeof window === 'undefined') return;
|
|
27
|
+
|
|
26
28
|
const checkOverflow = () => {
|
|
27
29
|
if (contentRef.current) {
|
|
28
30
|
// Add a small tolerance (5px) to avoid false positives from rounding/padding
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react';
|
|
1
|
+
import React, { forwardRef, useState, useEffect, useRef } from 'react';
|
|
2
2
|
import Grid from '~/components/modules/grid';
|
|
3
3
|
import Icon from '~/components/modules/icon';
|
|
4
4
|
import FieldMapper from '~/components/modules/list/field-mapper-desktop';
|
|
@@ -19,35 +19,104 @@ const ListItem = forwardRef(
|
|
|
19
19
|
favorites,
|
|
20
20
|
includeFavorite = true,
|
|
21
21
|
siteConfig,
|
|
22
|
-
|
|
22
|
+
index,
|
|
23
23
|
...props
|
|
24
24
|
},
|
|
25
25
|
ref
|
|
26
26
|
) => {
|
|
27
27
|
const mapPinColor = !showMap ? null : siteConfig.colors.primary.replace("#", "");
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// Mobile detection for bandwidth optimization
|
|
30
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
31
|
+
|
|
32
|
+
// Individual item loading based on scroll position
|
|
33
|
+
const [isMapLoaded, setIsMapLoaded] = useState(false);
|
|
34
|
+
const itemRef = useRef(null);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (typeof window === 'undefined') return;
|
|
38
|
+
|
|
39
|
+
const checkIsMobile = () => {
|
|
40
|
+
setIsMobile(window.innerWidth < 768); // md breakpoint in Tailwind
|
|
33
41
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
checkIsMobile();
|
|
44
|
+
window.addEventListener('resize', checkIsMobile);
|
|
45
|
+
|
|
46
|
+
return () => window.removeEventListener('resize', checkIsMobile);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Load individual map when item comes into view
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (typeof window === 'undefined') return;
|
|
52
|
+
if (!isMobile || !showMap || !itemRef.current || isMapLoaded) return;
|
|
53
|
+
|
|
54
|
+
const checkIfInView = () => {
|
|
55
|
+
if (!itemRef.current || isMapLoaded) return;
|
|
56
|
+
|
|
57
|
+
const rect = itemRef.current.getBoundingClientRect();
|
|
58
|
+
const windowHeight = window.innerHeight;
|
|
59
|
+
|
|
60
|
+
// Load only when item is actually visible or very close to viewport
|
|
61
|
+
// Much more conservative - only load what's immediately needed
|
|
62
|
+
const isInView = rect.top < windowHeight && rect.bottom > 0;
|
|
63
|
+
|
|
64
|
+
if (isInView) {
|
|
65
|
+
setIsMapLoaded(true);
|
|
44
66
|
}
|
|
45
|
-
isFavorite = !isFavorite;
|
|
46
|
-
handleSettingFavorites(updatedFavorites);
|
|
47
67
|
};
|
|
68
|
+
|
|
69
|
+
// Check immediately
|
|
70
|
+
checkIfInView();
|
|
71
|
+
|
|
72
|
+
// Only check on scroll, not continuously
|
|
73
|
+
let scrollTimeout;
|
|
74
|
+
const handleScroll = () => {
|
|
75
|
+
clearTimeout(scrollTimeout);
|
|
76
|
+
scrollTimeout = setTimeout(checkIfInView, 300); // Much less frequent
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
window.removeEventListener('scroll', handleScroll);
|
|
83
|
+
clearTimeout(scrollTimeout);
|
|
84
|
+
};
|
|
85
|
+
}, [isMobile, showMap, isMapLoaded]);
|
|
86
|
+
|
|
87
|
+
const handleClick = () => {
|
|
88
|
+
if (onItemSelected) {
|
|
89
|
+
onItemSelected(item);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
let isFavorite = favorites.includes(item.id);
|
|
93
|
+
|
|
94
|
+
const handleFavouriteClick = (event, item) => {
|
|
95
|
+
if(!includeFavorite)return;
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
let updatedFavorites;
|
|
98
|
+
if (isFavorite) {
|
|
99
|
+
updatedFavorites = favorites.filter(fav => fav !== item.id);
|
|
100
|
+
} else {
|
|
101
|
+
updatedFavorites = [...favorites, item.id];
|
|
102
|
+
}
|
|
103
|
+
isFavorite = !isFavorite;
|
|
104
|
+
handleSettingFavorites(updatedFavorites);
|
|
105
|
+
};
|
|
48
106
|
return (
|
|
49
107
|
<button
|
|
50
|
-
ref={
|
|
108
|
+
ref={(el) => {
|
|
109
|
+
// Forward the ref to the parent component
|
|
110
|
+
if (ref) {
|
|
111
|
+
if (typeof ref === 'function') {
|
|
112
|
+
ref(el);
|
|
113
|
+
} else {
|
|
114
|
+
ref.current = el;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Also store our own ref for scroll detection
|
|
118
|
+
itemRef.current = el;
|
|
119
|
+
}}
|
|
51
120
|
onClick={() => { handleClick(); }}
|
|
52
121
|
className={`
|
|
53
122
|
hc-group hc-relative hc-flex md:hc-flex-col hc-w-full hc-text-left hc-bg-clip-border hc-border hc-border-transparent hc-break-words hc-overflow-hidden hc-cursor-pointer hc-transition-colors hover:hc-bg-uiAccent/5 focus:hover:hc-bg-uiAccent/5
|
|
@@ -111,12 +180,16 @@ const ListItem = forwardRef(
|
|
|
111
180
|
}
|
|
112
181
|
</Grid>
|
|
113
182
|
{/* MOBILE ONLY: This map section is visible on mobile and hidden on desktop (md:hc-hidden) */}
|
|
114
|
-
{showMap && item.mapDetails &&
|
|
115
|
-
<div
|
|
183
|
+
{showMap && item.mapDetails?.staticMapUrl && (
|
|
184
|
+
<div
|
|
185
|
+
onClick={() => { setMobileTab("mapTab"); handleClick(); }}
|
|
186
|
+
className="md:hc-hidden hc-w-2/5 sm:hc-w-1/3 hc-p-1.5 hc-my-1 hc-bg-uiAccent/5 hc-border hc-border-uiAccent/10 hc-rounded-sm"
|
|
187
|
+
>
|
|
116
188
|
<img
|
|
117
|
-
src={
|
|
118
|
-
alt={`Map of location for ${item.fields
|
|
189
|
+
src={item.mapDetails.staticMapUrl}
|
|
190
|
+
alt={`Map of location for ${item.fields.position}`}
|
|
119
191
|
className="hc-w-full hc-h-full hc-object-cover"
|
|
192
|
+
loading="lazy"
|
|
120
193
|
/>
|
|
121
194
|
</div>
|
|
122
195
|
)}
|
|
@@ -137,4 +210,4 @@ export default React.memo(ListItem, (prevProps, nextProps) => {
|
|
|
137
210
|
prevProps.bodyClassName === nextProps.bodyClassName &&
|
|
138
211
|
prevProps.className === nextProps.className
|
|
139
212
|
);
|
|
140
|
-
});
|
|
213
|
+
});
|
|
@@ -4,9 +4,6 @@ import { generateFilterOptions, applyFilters, filterListingsByLocation } from '~
|
|
|
4
4
|
import { getStorageObject, setStorageObject } from '~/util/localStorageUtil';
|
|
5
5
|
import { updateURLWithFilters, filtersFromURL } from '~/util/urlFilterUtil';
|
|
6
6
|
|
|
7
|
-
import { getListingEntities } from "~/services/listingEntityService";
|
|
8
|
-
import fetchListings from '~/services/listingAggregatorService';
|
|
9
|
-
|
|
10
7
|
import { Listing } from '~/types/Listings';
|
|
11
8
|
import { ListingEntity } from '~/types/ListingEntity';
|
|
12
9
|
import { Recruiter } from '~/types/Recruiter';
|
|
@@ -252,10 +249,13 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
252
249
|
|
|
253
250
|
useEffect(() => {
|
|
254
251
|
if (!sortSetting) return;
|
|
255
|
-
|
|
252
|
+
if (typeof window !== 'undefined') {
|
|
253
|
+
localStorage.setItem(localStorageKey + 'sortSetting', JSON.stringify(sortSetting));
|
|
254
|
+
}
|
|
256
255
|
}, [sortSetting, localStorageKey]);
|
|
257
256
|
|
|
258
257
|
useEffect(() => {
|
|
258
|
+
if (typeof window === 'undefined') return;
|
|
259
259
|
const loadedFavorites = JSON.parse(localStorage.getItem(localStorageKey + 'favorites') || '[]');
|
|
260
260
|
setFavorites(loadedFavorites);
|
|
261
261
|
}, [localStorageKey]);
|
|
@@ -270,12 +270,12 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
270
270
|
|
|
271
271
|
async function fetchEntities() {
|
|
272
272
|
try {
|
|
273
|
-
|
|
274
|
-
if (getListingEntitiesCallback) {
|
|
275
|
-
|
|
276
|
-
} else {
|
|
277
|
-
fetchedEntities = await getListingEntities(`${commuteLocation.lat}, ${commuteLocation.lng}`);
|
|
273
|
+
// Only fetch if callback is provided
|
|
274
|
+
if (!getListingEntitiesCallback) {
|
|
275
|
+
return;
|
|
278
276
|
}
|
|
277
|
+
|
|
278
|
+
const fetchedEntities = await getListingEntitiesCallback(`${commuteLocation.lat}, ${commuteLocation.lng}`);
|
|
279
279
|
setListingEntities(fetchedEntities);
|
|
280
280
|
// Update travelTime on listings
|
|
281
281
|
const newFilteredListings: Listing[] = [...filteredListings];
|
|
@@ -303,42 +303,42 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
303
303
|
fetchEntities();
|
|
304
304
|
}, [commuteLocation, allListings, siteConfig.companyId, getListingEntitiesCallback, noEntities]);
|
|
305
305
|
|
|
306
|
+
// Set listings from passed-in data
|
|
306
307
|
useEffect(() => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
if (!listings || listings.length === 0) {
|
|
309
|
+
setLoading(false);
|
|
310
|
+
return;
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
setLoading(true);
|
|
314
|
+
|
|
312
315
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
entitiesByKey,
|
|
316
|
-
distinctItems
|
|
317
|
-
} = await fetchListings(commuteLocation, entities, listings, getListingEntitiesCallback, noEntities);
|
|
316
|
+
// Apply default filters if provided
|
|
317
|
+
let processedListings = listings;
|
|
318
318
|
if (defaultFilters) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
319
|
+
processedListings = listings.filter(listing => {
|
|
320
|
+
if (!listing.fields) return false;
|
|
321
|
+
|
|
322
|
+
return Object.keys(defaultFilters).every(filterKey => {
|
|
323
|
+
const filterValues = defaultFilters[filterKey as keyof typeof defaultFilters];
|
|
324
|
+
const listingValue = listing.fields ? listing.fields[filterKey as keyof typeof listing.fields] : null;
|
|
325
|
+
return filterValues.includes(listingValue);
|
|
326
|
+
});
|
|
326
327
|
});
|
|
327
|
-
});
|
|
328
|
-
setAllListings(filteredListings);
|
|
329
|
-
} else {
|
|
330
|
-
setAllListings(listingsResult);
|
|
331
|
-
}
|
|
332
|
-
console.log('handleFetchListings: Set allListings to', listingsResult.length, 'items');
|
|
333
|
-
setListingEntities(entitiesByKey);
|
|
334
|
-
setMapItems(distinctItems);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.log(error);
|
|
337
328
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
|
|
330
|
+
setAllListings(processedListings);
|
|
331
|
+
console.log('Set allListings to', processedListings.length, 'items');
|
|
332
|
+
|
|
333
|
+
// Map items will be set when entities are processed
|
|
334
|
+
// For now, just set empty object - will be populated when entities arrive
|
|
335
|
+
setMapItems({});
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error('Error processing listings:', error);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
setLoading(false);
|
|
341
|
+
}, [listings, defaultFilters]);
|
|
342
342
|
|
|
343
343
|
useEffect(() => {
|
|
344
344
|
const processListings = async () => {
|
|
@@ -451,6 +451,7 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
const handleSettingFavorites = (newFavorites: number[] | null) => {
|
|
454
|
+
if (typeof window === 'undefined') return;
|
|
454
455
|
if (newFavorites == null) {
|
|
455
456
|
localStorage.removeItem(localStorageKey + 'favorites');
|
|
456
457
|
} else {
|
package/src/index.js
CHANGED