@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.
Files changed (70) hide show
  1. package/dist/components/HireControlMap.js +97 -166
  2. package/dist/components/HireControlMap.js.map +1 -1
  3. package/dist/components/containers/accordions/map-accordion-item-container.js +3 -1
  4. package/dist/components/containers/accordions/map-accordion-item-container.js.map +1 -1
  5. package/dist/components/containers/filter/commute-container.js +11 -3
  6. package/dist/components/containers/filter/commute-container.js.map +1 -1
  7. package/dist/components/containers/maps/map-container.js +2 -0
  8. package/dist/components/containers/maps/map-container.js.map +1 -1
  9. package/dist/components/containers/maps/map-list-container.js +34 -3
  10. package/dist/components/containers/maps/map-list-container.js.map +1 -1
  11. package/dist/components/modules/accordions/default.js +1 -1
  12. package/dist/components/modules/buttons/button-group-apply.js +1 -1
  13. package/dist/components/modules/buttons/default.js +1 -1
  14. package/dist/components/modules/cards/default.js +1 -1
  15. package/dist/components/modules/filter/commute.js +3 -1
  16. package/dist/components/modules/filter/commute.js.map +1 -1
  17. package/dist/components/modules/filter/index.js +1 -0
  18. package/dist/components/modules/filter/index.js.map +1 -1
  19. package/dist/components/modules/filter/item.js +1 -0
  20. package/dist/components/modules/filter/item.js.map +1 -1
  21. package/dist/components/modules/filter/sort.js +1 -1
  22. package/dist/components/modules/grid.js +1 -1
  23. package/dist/components/modules/list/header.js +1 -1
  24. package/dist/components/modules/list/item-expand-card/index.js +1 -1
  25. package/dist/components/modules/list/list-item/list-item.js +85 -14
  26. package/dist/components/modules/list/list-item/list-item.js.map +1 -1
  27. package/dist/contexts/mapListContext.js +40 -39
  28. package/dist/contexts/mapListContext.js.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/styles/index.css +1 -1
  31. package/dist/util/algoliaSearchUtil.js +1 -1
  32. package/dist/util/filterUtil.js +1 -1
  33. package/dist/util/twMerge.js +1 -1
  34. package/package.json +4 -1
  35. package/src/apis/hcApi.ts +9 -2
  36. package/src/components/HireControlMap.js +19 -56
  37. package/src/components/containers/accordions/map-accordion-item-container.js +3 -1
  38. package/src/components/containers/filter/commute-container.js +11 -3
  39. package/src/components/containers/maps/map-container.js +2 -0
  40. package/src/components/containers/maps/map-list-container.js +30 -3
  41. package/src/components/modules/filter/commute.js +7 -5
  42. package/src/components/modules/filter/index.js +2 -0
  43. package/src/components/modules/filter/item.js +1 -0
  44. package/src/components/modules/list/list-item/list-item.jsx +97 -24
  45. package/src/contexts/mapListContext.tsx +39 -38
  46. package/src/index.js +1 -0
  47. package/dist/apis/hcApi.js +0 -91
  48. package/dist/apis/hcApi.js.map +0 -1
  49. package/dist/clientToken.js +0 -10
  50. package/dist/clientToken.js.map +0 -1
  51. package/dist/components/modules/skeleton/map-skeleton.js +0 -50
  52. package/dist/components/modules/skeleton/map-skeleton.js.map +0 -1
  53. package/dist/node_modules/@algolia/client-common/dist/common.js +0 -541
  54. package/dist/node_modules/@algolia/client-common/dist/common.js.map +0 -1
  55. package/dist/node_modules/@algolia/requester-browser-xhr/dist/requester.xhr.js +0 -4
  56. package/dist/node_modules/@algolia/requester-browser-xhr/dist/requester.xhr.js.map +0 -1
  57. package/dist/node_modules/algoliasearch/dist/lite/builds/browser.js +0 -272
  58. package/dist/node_modules/algoliasearch/dist/lite/builds/browser.js.map +0 -1
  59. package/dist/node_modules/fuse.js/dist/fuse.js +0 -1779
  60. package/dist/node_modules/fuse.js/dist/fuse.js.map +0 -1
  61. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +0 -2580
  62. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +0 -1
  63. package/dist/services/configService.js +0 -15
  64. package/dist/services/configService.js.map +0 -1
  65. package/dist/services/listingAggregatorService.js +0 -41
  66. package/dist/services/listingAggregatorService.js.map +0 -1
  67. package/dist/services/listingEntityService.js +0 -16
  68. package/dist/services/listingEntityService.js.map +0 -1
  69. package/dist/services/listingService.js +0 -15
  70. package/dist/services/listingService.js.map +0 -1
@@ -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 '../node_modules/fuse.js/dist/fuse.js';
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;
@@ -1,4 +1,4 @@
1
- import { extendTailwindMerge } from '../node_modules/tailwind-merge/dist/bundle-mjs.js';
1
+ import { extendTailwindMerge } from 'tailwind-merge';
2
2
 
3
3
  // Create a custom tailwind-merge that understands the hc- prefix
4
4
  var twMerge = extendTailwindMerge({
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@abcagency/hc-ui-components",
3
- "version": "1.7.4",
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
- sessionStorage.setItem(key, value);
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
- return sessionStorage.getItem(key) || memoryStorage[key];
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
- // Custom hook to fetch site configuration
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
- if (error) {
208
- return <div className="hc-p-4">Error loading configuration</div>;
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 : siteConfig.showMap;
209
+ const effectiveShowMap = hideMap ? false : siteConfiguration.showMap;
247
210
 
248
211
  // Prepare marker configuration
249
212
  const markerConfigs = {
250
- fillColor: siteConfig.colors.primary,
251
- strokeColor: siteConfig.colors.primaryDark,
252
- selectedFillColor: siteConfig.colors.secondary,
253
- selectedStrokeColor: siteConfig.colors.secondaryDark,
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: siteConfig.pointsOfInterestConfig.placeMarkerColors,
256
- size: siteConfig.pointsOfInterestConfig.placeMarkerSize
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={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={siteConfig.mapPosition}
247
+ mapPosition={siteConfiguration.mapPosition}
285
248
  showMap={effectiveShowMap}
286
- fieldsShown={siteConfig.fieldsShown}
287
- specialFeatures={siteConfig.specialFeatures}
288
- fieldNames={siteConfig.fieldNames}
289
- placeMappings={siteConfig.pointsOfInterestConfig.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
- localStorage.removeItem("selectedListItem");
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
- localStorage.removeItem('isCurrentLocation');
27
- localStorage.removeItem('selectedCommute');
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
- localStorage.setItem('selectedCommute', val);
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={<Map {...mapProps} />}
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
- if (isCurrentLocation || commuteLocation) {
62
- setCommuteLocation("");
63
- setSelected("");
64
- } else if (!commuteLocation) { fetchLocation(); }
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
@@ -28,6 +28,7 @@ const FilterItem = ({
28
28
  onClick={() =>
29
29
  {
30
30
  if(!isExternalLink) return;
31
+ if (typeof window === 'undefined') return;
31
32
  trackEvent(eventTypes.EXTERNAL_LINK_CLICKED, { link: externalLinkUrl });
32
33
  window.location.href = externalLinkUrl;
33
34
  }}
@@ -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
- googleMapsApiKey,
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
- const handleClick = () => {
30
- if (onItemSelected) {
31
- onItemSelected(item);
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
- let isFavorite = favorites.includes(item.id);
35
-
36
- const handleFavouriteClick = (event, item) => {
37
- if(!includeFavorite)return;
38
- event.stopPropagation();
39
- let updatedFavorites;
40
- if (isFavorite) {
41
- updatedFavorites = favorites.filter(fav => fav !== item.id);
42
- } else {
43
- updatedFavorites = [...favorites, item.id];
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={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 && googleMapsApiKey && (
115
- <div onClick={() => { setMobileTab("mapTab"); handleClick(); }} 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">
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={`https://maps.googleapis.com/maps/api/staticmap?scale=2&center=${item.mapDetails.latitude},${item.mapDetails.longitude}&zoom=10&size=240x180&maptype=roadmap&markers=color:0x${mapPinColor}%7Clabel:•%7C${item.mapDetails.latitude},${item.mapDetails.longitude}&key=${googleMapsApiKey}`}
118
- alt={`Map of location for ${item.fields?.position || item.fields?.title || 'job'}`}
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
- localStorage.setItem(localStorageKey + 'sortSetting', JSON.stringify(sortSetting));
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
- let fetchedEntities;
274
- if (getListingEntitiesCallback) {
275
- fetchedEntities = await getListingEntitiesCallback(`${commuteLocation.lat}, ${commuteLocation.lng}`);
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
- const handleFetchListings = async () => {
308
- if (!(getStorageObject(localStorageKey + 'listings', []) || []).length) {
309
- setLoading(true);
308
+ if (!listings || listings.length === 0) {
309
+ setLoading(false);
310
+ return;
310
311
  }
311
312
 
313
+ setLoading(true);
314
+
312
315
  try {
313
- const {
314
- listingsResult,
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
- const filteredListings = listingsResult.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);
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
- setLoading(false);
339
- };
340
- handleFetchListings();
341
- }, [siteConfig, commuteLocation]);
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
@@ -1,3 +1,4 @@
1
1
  import HireControlMap from '~/components/HireControlMap';
2
+ import '~/styles/index.css';
2
3
 
3
4
  export { HireControlMap };