@abcagency/hc-ui-components 1.3.23 → 1.3.24

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 (102) hide show
  1. package/package.json +4 -5
  2. package/src/.editorconfig +12 -0
  3. package/src/apis/hcApi.ts +109 -0
  4. package/src/bundleIndex.js +14 -0
  5. package/src/clientToken.js +9 -0
  6. package/src/components/HireControlMap.js +135 -0
  7. package/src/components/containers/accordions/filter-container.js +48 -0
  8. package/src/components/containers/accordions/filter-item-container.js +66 -0
  9. package/src/components/containers/accordions/map-accordion-item-container.js +70 -0
  10. package/src/components/containers/filter/commute-container.js +89 -0
  11. package/src/components/containers/filter/filter-container.js +76 -0
  12. package/src/components/containers/filter/filter-item-container.js +71 -0
  13. package/src/components/containers/filter/location-container.js +45 -0
  14. package/src/components/containers/filter/points-of-interest-container.js +33 -0
  15. package/src/components/containers/filter/points-of-interest-radio-item-container.js +35 -0
  16. package/src/components/containers/filter/search-container.js +50 -0
  17. package/src/components/containers/jobListing/listing-details-container.js +40 -0
  18. package/src/components/containers/list/item-list-container.tsx +81 -0
  19. package/src/components/containers/list/list-item/list-item-container.js +43 -0
  20. package/src/components/containers/maps/info-window-content-container.js +51 -0
  21. package/src/components/containers/maps/map-container.js +204 -0
  22. package/src/components/containers/maps/map-list-container.js +48 -0
  23. package/src/components/containers/maps/map-marker-container.js +78 -0
  24. package/src/components/modules/accordions/MapAccordionItem.js +30 -0
  25. package/src/components/modules/accordions/default.js +171 -0
  26. package/src/components/modules/accordions/filterItem.js +27 -0
  27. package/src/components/modules/accordions/filters.js +32 -0
  28. package/src/components/modules/buttons/button-group-apply.js +123 -0
  29. package/src/components/modules/buttons/commute-pill.js +22 -0
  30. package/src/components/modules/buttons/default.js +194 -0
  31. package/src/components/modules/buttons/items-pill.js +35 -0
  32. package/src/components/modules/buttons/pill-wrapper.js +27 -0
  33. package/src/components/modules/buttons/show-all-button.js +20 -0
  34. package/src/components/modules/cards/default.js +167 -0
  35. package/src/components/modules/cards/filter.js +56 -0
  36. package/src/components/modules/dialogs/apply-dialog.js +48 -0
  37. package/src/components/modules/filter/commute.js +108 -0
  38. package/src/components/modules/filter/index.js +55 -0
  39. package/src/components/modules/filter/item.js +48 -0
  40. package/src/components/modules/filter/location.js +48 -0
  41. package/src/components/modules/filter/radio-item.js +42 -0
  42. package/src/components/modules/filter/search.js +65 -0
  43. package/src/components/modules/filter/sort.js +83 -0
  44. package/src/components/modules/grid.js +54 -0
  45. package/src/components/modules/icon.js +33 -0
  46. package/src/components/modules/jobListing/listing-details.js +109 -0
  47. package/src/components/modules/list/field-mapper.js +114 -0
  48. package/src/components/modules/list/header-item.js +91 -0
  49. package/src/components/modules/list/header.js +49 -0
  50. package/src/components/modules/list/item-expand-card/index.js +22 -0
  51. package/src/components/modules/list/item-expand-card/recruiter-contact-nav.js +50 -0
  52. package/src/components/modules/list/item-expand-card/recruiter-details.js +68 -0
  53. package/src/components/modules/list/item-expand-card/recruiter-headshot.js +22 -0
  54. package/src/components/modules/list/item-list.tsx +84 -0
  55. package/src/components/modules/list/list-item/list-item.js +130 -0
  56. package/src/components/modules/maps/info-window-card.js +17 -0
  57. package/src/components/modules/maps/info-window-content.js +35 -0
  58. package/src/components/modules/maps/map-list.js +28 -0
  59. package/src/components/modules/maps/map-marker.js +29 -0
  60. package/src/components/modules/maps/map.js +76 -0
  61. package/src/components/modules/maps/place-marker.js +41 -0
  62. package/src/components/modules/maps/tabs.js +81 -0
  63. package/src/constants/eventTypes.js +13 -0
  64. package/src/constants/placeTypes.js +8 -0
  65. package/src/contexts/mapContext.tsx +129 -0
  66. package/src/contexts/mapListContext.tsx +311 -0
  67. package/src/contexts/placesContext.js +102 -0
  68. package/src/contexts/trackEventContext.js +14 -0
  69. package/src/enums/SectionType.ts +9 -0
  70. package/src/hooks/useList.js +89 -0
  71. package/src/index.js +3 -0
  72. package/src/services/configService.ts +16 -0
  73. package/src/services/googlePlacesNearbyService.ts +42 -0
  74. package/src/services/listingAggregatorService.ts +76 -0
  75. package/src/services/listingEntityService.ts +16 -0
  76. package/src/services/listingService.ts +40 -0
  77. package/src/services/recruiterService.ts +18 -0
  78. package/src/styles/bundle.css +268 -0
  79. package/src/styles/index.css +24 -0
  80. package/src/types/Address.ts +7 -0
  81. package/src/types/ContentSection.ts +9 -0
  82. package/src/types/GetListingParams.ts +8 -0
  83. package/src/types/LatLng.ts +4 -0
  84. package/src/types/ListingEntity.ts +11 -0
  85. package/src/types/ListingFields.ts +25 -0
  86. package/src/types/Listings.ts +32 -0
  87. package/src/types/Recruiter.ts +9 -0
  88. package/src/types/SimilarListing.ts +24 -0
  89. package/src/types/config/Colors.ts +8 -0
  90. package/src/types/config/MapConfig.ts +31 -0
  91. package/src/types/config/PointsOfInterestConfig.ts +13 -0
  92. package/src/types/config/SearchConfig.ts +4 -0
  93. package/src/util/arrayUtil.js +3 -0
  94. package/src/util/fieldMapper.js +22 -0
  95. package/src/util/filterUtil.js +239 -0
  96. package/src/util/loading.js +17 -0
  97. package/src/util/localStorageUtil.ts +34 -0
  98. package/src/util/mapIconUtil.js +180 -0
  99. package/src/util/mapUtil.js +91 -0
  100. package/src/util/sortUtil.js +33 -0
  101. package/src/util/stringUtils.js +6 -0
  102. package/src/util/urlFilterUtil.js +85 -0
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import * as Tabs from '@radix-ui/react-tabs';
3
+
4
+ import Button from '~/components/modules/buttons/default';
5
+
6
+ import { useMapList } from '~/contexts/mapListContext';
7
+
8
+ const MapTabs = ({
9
+ map,
10
+ list,
11
+ filter,
12
+ className,
13
+ showMap
14
+ }) => {
15
+ const tabButtonClasses =
16
+ "hc-flex hc-items-center hc-gap-2 hc-rounded-none hc-border-x-0 data-[state=active]:hc-bg-primary data-[state=active]:hc-text-white";
17
+ const { mobileTab, setMobileTab } = useMapList();
18
+ return (
19
+ <div
20
+ className={`
21
+ hc-relative hc-overflow-hidden
22
+ ${className ?? ""}
23
+ `}
24
+ >
25
+ <Tabs.Root
26
+ className="hc-flex hc-flex-col hc-h-screen hc-min-h-screen"
27
+ //defaultValue="listTab"
28
+ value={mobileTab}
29
+ >
30
+ <Tabs.List
31
+ className="hc-w-full hc-shrink-0 hc-flex hc-divide-x hc-divide-primary"
32
+ aria-label="Review positions"
33
+ >
34
+ <Tabs.Trigger value="listTab" asChild onClick={() => setMobileTab("listTab")}>
35
+ <Button.Btn variant="outline" isBlock className={tabButtonClasses}>
36
+ <Button.Body className="hc-justify-center">
37
+ <Button.Icon icon="mdi:view-list" size="hc-size-5" />
38
+ List
39
+ </Button.Body>
40
+ </Button.Btn>
41
+ </Tabs.Trigger>
42
+ {showMap && (
43
+ <Tabs.Trigger value="mapTab" asChild onClick={() => setMobileTab("mapTab")}>
44
+ <Button.Btn
45
+ variant="outline"
46
+ isBlock
47
+ className={tabButtonClasses}
48
+ >
49
+ <Button.Body className="hc-justify-center">
50
+ <Button.Icon icon="mdi:map" size="hc-size-5" />
51
+ Map
52
+ </Button.Body>
53
+ </Button.Btn>
54
+ </Tabs.Trigger>
55
+ )}
56
+ <Tabs.Trigger value="filterTab" asChild onClick={() => setMobileTab("filterTab")}>
57
+ <Button.Btn variant="outline" isBlock className={tabButtonClasses}>
58
+ <Button.Body className="hc-justify-center">
59
+ <Button.Icon icon="fluent:search-12-filled" size="hc-size-5" />
60
+ Filter
61
+ </Button.Body>
62
+ </Button.Btn>
63
+ </Tabs.Trigger>
64
+ </Tabs.List>
65
+ <Tabs.Content className="hc-grow hc-bg-white hc-outline-none" value="listTab">
66
+ {list}
67
+ </Tabs.Content>
68
+ {showMap && (
69
+ <Tabs.Content className="hc-grow hc-bg-white hc-outline-none" value="mapTab">
70
+ {map}
71
+ </Tabs.Content>
72
+ )}
73
+ <Tabs.Content className="hc-grow hc-bg-white hc-outline-none hc-p-2" value="filterTab">
74
+ {filter}
75
+ </Tabs.Content>
76
+ </Tabs.Root>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default MapTabs;
@@ -0,0 +1,13 @@
1
+ export const eventTypes = {
2
+ FILTER_APPLIED: "filter_applied",
3
+ MAP_MARKER_CLICKED: "map_marker_clicked",
4
+ JOB_LISTING_SELECTED: "job_listing_selected",
5
+ APPLY_NOW_CLICKED: "apply_now_clicked",
6
+ VIEW_DETAILS_CLICKED: "view_details_clicked",
7
+ COMMUTE_ORIGIN_ADDED: "commute_origin_added",
8
+ POI_APPLIED: "point_of_interest_applied",
9
+ VIEW_JOBS_AT_Location: "view_jobs_at_location_clicked",
10
+ FAVORITE_SELECTED: "favorite_job_selected",
11
+ LIST_SORTED: "jobs_list_sorted",
12
+ FILTERS_RESET: "filter_reset_button_clicked"
13
+ };
@@ -0,0 +1,8 @@
1
+ export const placeTypes = {
2
+ FOOD: "food",
3
+ STORE: "shopping",
4
+ TOURIST_ATTRACTION: "attractions",
5
+ TRANSIT_STATION: "transit",
6
+ SCHOOL: "schools",
7
+ PLACE_OF_WORSHIP: "worship"
8
+ };
@@ -0,0 +1,129 @@
1
+ import React, { createContext, useState, useContext, useEffect, useRef, ReactNode } from 'react';
2
+ import { getStorageObject, setStorageObject } from '~/util/localStorageUtil';
3
+ import { Listing } from '~/types/Listings';
4
+ import { LatLng } from '~/types/LatLng';
5
+ interface IMapContext {
6
+ selectedListItem: Listing | null;
7
+ setSelectedListItem: (item: Listing | null) => void;
8
+ location: any | null;
9
+ setLocation: (location: LatLng | null) => void;
10
+ center: LatLng;
11
+ setCenter: (center: LatLng) => void;
12
+ zoom: number;
13
+ setZoom: (zoom: number) => void;
14
+ selectedPlaces: string[];
15
+ setSelectedPlaces: (places: string[]) => void;
16
+ mapInteracted: boolean;
17
+ setMapInteracted: (interacted: boolean) => void;
18
+ userSetZoom: React.MutableRefObject<boolean>;
19
+ firstLoadListItem: any;
20
+ selectItem: (item: Listing, itemLocation: LatLng | null, zoom: number, center: LatLng) => void;
21
+ filterReset: () => void;
22
+ selectLocationEntity: (location: LatLng) => void;
23
+ }
24
+
25
+ const MapContext = createContext<IMapContext | undefined>(undefined);
26
+
27
+ export const useMap = () => {
28
+ const context = useContext(MapContext);
29
+ if (!context) {
30
+ throw new Error("useMap must be used within a MapProvider");
31
+ }
32
+ return context;
33
+ };
34
+
35
+ interface MapProviderProps {
36
+ children: ReactNode;
37
+ resetFilters: boolean;
38
+ }
39
+
40
+ export const MapProvider: React.FC<MapProviderProps> = ({ children, resetFilters }) => {
41
+ const [selectedListItem, setSelectedListItem] = useState<Listing | null>(getStorageObject('selectedListItem'));
42
+ const [location, setLocation] = useState<any>(getStorageObject('location'));
43
+ const [center, setCenter] = useState<LatLng>(getStorageObject("center", { lat: 39.8283, lng: -98.5795 }) || { lat: 39.8283, lng: -98.5795 });
44
+ const [zoom, setZoom] = useState<number>(getStorageObject("zoom", 10) || 10);
45
+ const [selectedPlaces, setSelectedPlaces] = useState<string[]>([]);
46
+ const [mapInteracted, setMapInteracted] = useState<boolean>(false);
47
+ const [firstLoadListItem] = useState<any>(getStorageObject('selectedListItem', { id: "defaultId" }));
48
+ const userSetZoom = useRef<boolean>(true);
49
+
50
+ useEffect(() => {
51
+ setStorageObject("selectedListItem", selectedListItem);
52
+ }, [selectedListItem]);
53
+
54
+ useEffect(() => {
55
+ localStorage.setItem("zoom", zoom.toString());
56
+ }, [zoom]);
57
+
58
+ useEffect(() => {
59
+ if (location == null) {
60
+ localStorage.removeItem("location");
61
+ } else {
62
+ setStorageObject("location", location);
63
+ }
64
+ }, [location]);
65
+
66
+ useEffect(() => {
67
+ setStorageObject("center", center);
68
+ }, [center]);
69
+
70
+ const selectItem = (item: Listing, itemLocation: LatLng | null, zoom: number, center: LatLng) => {
71
+ setSelectedListItem(item);
72
+ if (mapInteracted === false && itemLocation != null) {
73
+ setLocation(itemLocation);
74
+ }
75
+ if (mapInteracted === false || itemLocation != null) {
76
+ setLocation(itemLocation);
77
+ setCenter(center);
78
+ }
79
+ if (mapInteracted === false) {
80
+ setZoom(zoom);
81
+ }
82
+ };
83
+
84
+ const filterReset = () => {
85
+ setSelectedPlaces([]);
86
+ setSelectedListItem(null);
87
+ setLocation(null);
88
+ setZoom(8);
89
+ setMapInteracted(false);
90
+ };
91
+
92
+ useEffect(() => {
93
+ if (resetFilters === true) {
94
+ filterReset();
95
+ }
96
+ }, [resetFilters]);
97
+
98
+ const selectLocationEntity = (location: LatLng) => {
99
+ localStorage.removeItem("selectedListItem");
100
+ setTimeout(() => setLocation(location), 200);
101
+ setSelectedListItem(null);
102
+ };
103
+
104
+ return (
105
+ <MapContext.Provider
106
+ value={{
107
+ selectedListItem,
108
+ setSelectedListItem,
109
+ location,
110
+ center,
111
+ setCenter,
112
+ zoom,
113
+ setZoom,
114
+ selectItem,
115
+ setSelectedPlaces,
116
+ selectedPlaces,
117
+ selectLocationEntity,
118
+ setLocation,
119
+ setMapInteracted,
120
+ mapInteracted,
121
+ userSetZoom,
122
+ firstLoadListItem,
123
+ filterReset
124
+ }}
125
+ >
126
+ {children}
127
+ </MapContext.Provider>
128
+ );
129
+ };
@@ -0,0 +1,311 @@
1
+ import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
2
+
3
+ import { generateFilterOptions, applyFilters, filterListingsByLocation } from '~/util/filterUtil';
4
+ import { getStorageObject, setStorageObject } from '~/util/localStorageUtil';
5
+ import { updateURLWithFilters, filtersFromURL } from '~/util/urlFilterUtil';
6
+
7
+ import { getListingEntities } from "~/services/listingEntityService";
8
+ import fetchListings from '~/services/listingAggregatorService';
9
+
10
+ import { Listing } from '~/types/Listings';
11
+ import { ListingEntity } from '~/types/ListingEntity';
12
+ import { Recruiter } from '~/types/Recruiter';
13
+ import { MapConfig, MapConfig as SiteConfig } from '~/types/config/MapConfig';
14
+
15
+ interface MapListContextProps {
16
+ loading: boolean;
17
+ allListings: Listing[];
18
+ filteredListings: Listing[];
19
+ mapItems: any;
20
+ query: string | null;
21
+ setNewFilteredListings: (filteredListings: Listing[]) => void;
22
+ setQuery: (query: string | null) => void;
23
+ listingEntities: Record<number, ListingEntity> | null;
24
+ selectedFilters: Record<string, any>;
25
+ setSelectedFilters: (filters: Record<string, any>) => void;
26
+ filterOptions: any;
27
+ recruiters: Record<number, Recruiter>;
28
+ handleFilterListingsByLocation: (selectedLocation: any) => void;
29
+ filterDialogIsOpen: boolean;
30
+ setFilterDialogIsOpen: (isOpen: boolean) => void;
31
+ setMobileTab: (tab: string) => void;
32
+ mobileTab: string;
33
+ siteConfig: SiteConfig;
34
+ favorites: number[];
35
+ handleSettingFavorites: (favorites: number[] | null) => void;
36
+ setFilterByFavorites: (filter: boolean) => void;
37
+ filterByFavorites: boolean;
38
+ commuteLocation: any | null;
39
+ setCommuteLocation: (location: any | null) => void;
40
+ navigateToDetails: (id: number) => void;
41
+ navigateToEasyApply: (id: number) => void;
42
+ Link: React.ComponentType<any>;
43
+ linkFormat: string;
44
+ sortSetting: { field: string; type: string };
45
+ setSortSetting: (setting: { field: string; type: string }) => void;
46
+ trackEvent: (event: string) => void;
47
+ }
48
+
49
+ const MapListContext = createContext<MapListContextProps | undefined>(undefined);
50
+
51
+ export const useMapList = () => {
52
+ const context = useContext(MapListContext);
53
+ if (!context) {
54
+ throw new Error('useMapList must be used within a MapListProvider');
55
+ }
56
+ return context;
57
+ };
58
+
59
+ const getQuery = (): string | null => {
60
+ let query: string | null = null;
61
+ if (typeof window !== 'undefined') {
62
+ query = localStorage.getItem('query');
63
+ }
64
+ return query;
65
+ };
66
+
67
+ interface MapListProviderProps {
68
+ children: ReactNode;
69
+ siteConfig: MapConfig;
70
+ resetFilters: boolean;
71
+ navigateToDetails: (id: number) => void;
72
+ navigateToEasyApply: (id: number) => void;
73
+ Link: React.ComponentType<any>;
74
+ linkFormat: string;
75
+ trackEvent: (event: string) => void;
76
+ listings?: Listing[];
77
+ setFiltersUrl?:boolean;
78
+ }
79
+
80
+ export const MapListProvider: React.FC<MapListProviderProps> = ({
81
+ children,
82
+ siteConfig,
83
+ resetFilters,
84
+ navigateToDetails,
85
+ navigateToEasyApply,
86
+ Link,
87
+ linkFormat,
88
+ trackEvent,
89
+ listings = [],
90
+ setFiltersUrl
91
+ }) => {
92
+ const firstLoadFilters = () =>{
93
+ let urlFilters = filtersFromURL(window.location)?.filters;
94
+ return (setFiltersUrl === true && urlFilters && Object.keys(urlFilters).length > 0) ? urlFilters : getStorageObject('selectedFilters', {}) || {}
95
+ }
96
+ const [allListings, setAllListings] = useState<Listing[]>(getStorageObject("listings", listings) || []);
97
+ const [filteredListings, setFilteredListings] = useState<Listing[]>([]);
98
+ const [loading, setLoading] = useState<boolean>(false);
99
+ const [mapItems, setMapItems] = useState<any>(getStorageObject('mapItems', []) || []);
100
+ const [query, setQuery] = useState<string | null>(() => resetFilters ? null : getQuery());
101
+ const [sortSetting, setSortSetting] = useState<{ field: string; type: string }>(getStorageObject('sortSetting', { field: 'position', type: 'asc' }) || { field: 'position', type: 'asc' });
102
+ const [listingEntities, setListingEntities] = useState<Record<number, ListingEntity> | null>(getStorageObject("listingEntities", null));
103
+ const [firstLoad, setFirstLoad] = useState<boolean>(true);
104
+ const [commuteLocation, setCommuteLocation] = useState<any | null>(getStorageObject('commuteLocation'));
105
+ const [selectedFilters, setSelectedFilters] = useState<Record<string, any>>(() => resetFilters ? {} : firstLoadFilters());
106
+ const [filterOptions, setFilterOptions] = useState<any>();
107
+ const [recruiters, setRecruiters] = useState<Record<number, Recruiter>>(getStorageObject("recruiters", {}) || {});
108
+ const [filterDialogIsOpen, setFilterDialogIsOpen] = useState<boolean>(false);
109
+ const [mobileTab, setMobileTab] = useState<string>("listTab");
110
+ const [favorites, setFavorites] = useState<number[]>([]);
111
+ const [filterByFavorites, setFilterByFavorites] = useState<boolean>(false);
112
+
113
+ const setNewFilteredListings = (filteredListings: Listing[]) => {
114
+ setFilteredListings(filteredListings);
115
+ };
116
+
117
+
118
+
119
+ useEffect(() => {
120
+ if (!sortSetting) return;
121
+ localStorage.setItem('sortSetting', JSON.stringify(sortSetting));
122
+ setNewFilteredListings(filteredListings);
123
+ }, [sortSetting]);
124
+
125
+ useEffect(() => {
126
+ const loadedFavorites = JSON.parse(localStorage.getItem('favorites') || '[]');
127
+ setFavorites(loadedFavorites);
128
+ }, []);
129
+
130
+ useEffect(() => {
131
+ setStorageObject("commuteLocation", commuteLocation);
132
+ }, [commuteLocation]);
133
+
134
+ useEffect(() => {
135
+ if (!commuteLocation) return;
136
+
137
+ async function fetchEntities() {
138
+ const distinctEntityIds = [
139
+ ...new Set(allListings.map(listing => listing.entityId ?? -1))
140
+ ];
141
+ try {
142
+ const fetchedEntities = await getListingEntities(
143
+ distinctEntityIds,
144
+ `${commuteLocation.lat}, ${commuteLocation.lng}`
145
+ );
146
+ setListingEntities(fetchedEntities);
147
+ const newFilteredListings: Listing[] = [...filteredListings] ?? [];
148
+ for (let i = 0; i < allListings.length; i++) {
149
+ const listing = newFilteredListings[i];
150
+ if (
151
+ listing &&
152
+ listing.fields &&
153
+ listing.entityId !== undefined &&
154
+ listing.entityId !== -1
155
+ ) {
156
+ const entityId = listing.entityId;
157
+ const travelTime = fetchedEntities[entityId]?.travelTime;
158
+
159
+ if (travelTime !== undefined && listing.fields) {
160
+ listing.fields.travelTime = travelTime;
161
+ }
162
+ }
163
+ }
164
+ } catch (error) {
165
+ console.error("Failed to fetch listing entities:", error);
166
+ }
167
+ }
168
+
169
+ fetchEntities();
170
+ }, [commuteLocation, allListings, siteConfig.companyId]);
171
+
172
+ useEffect(() => {
173
+ const handleFetchListings = async () => {
174
+ if (!getStorageObject('listings') ?? [].length) {
175
+ setLoading(true);
176
+ }
177
+
178
+ try {
179
+ const {
180
+ listingsResult,
181
+ fetchedRecruiters,
182
+ fetchedEntities,
183
+ distinctItems
184
+ } = await fetchListings(query ?? '', siteConfig, commuteLocation);
185
+ setAllListings(listingsResult);
186
+ setRecruiters(fetchedRecruiters);
187
+ setListingEntities(fetchedEntities);
188
+ setMapItems(distinctItems);
189
+ setStorageObject("mapItems", distinctItems);
190
+ setStorageObject("listingEntities", fetchedEntities);
191
+ setStorageObject("recruiters", fetchedRecruiters);
192
+ setStorageObject("listings", listingsResult);
193
+ } catch (error) {
194
+ console.log(error);
195
+ }
196
+ setLoading(false);
197
+ };
198
+ handleFetchListings();
199
+ }, [query, siteConfig]);
200
+
201
+ useEffect(() => {
202
+ const processListings = () => {
203
+ let filteredListings: Listing[];
204
+ let tempSelectedFilters = selectedFilters;
205
+ let tempQuery = query;
206
+
207
+ const { mapItems, filteredListings: tempFilteredListings } = applyFilters(
208
+ allListings,
209
+ tempSelectedFilters,
210
+ tempQuery,
211
+ listingEntities,
212
+ favorites,
213
+ siteConfig
214
+ );
215
+ filteredListings = tempFilteredListings;
216
+
217
+ if (filterByFavorites) {
218
+ filteredListings = filteredListings.filter((x: Listing) => favorites.includes(x.id));
219
+ }
220
+ setNewFilteredListings(filteredListings);
221
+ if (firstLoad && tempSelectedFilters) {
222
+ // Update URL with filters if needed
223
+ } else if (Object.keys(tempSelectedFilters).length === 0 && !firstLoad) {
224
+ localStorage.removeItem('selectedFilters');
225
+ } else if (!firstLoad) {
226
+ setStorageObject('selectedFilters', tempSelectedFilters);
227
+ }
228
+ if(setFiltersUrl === true)
229
+ {
230
+ updateURLWithFilters(tempSelectedFilters, window.location, tempQuery);
231
+ }
232
+ tempQuery != null ? localStorage.setItem('query', tempQuery) : localStorage.removeItem('query');
233
+ setMapItems(mapItems);
234
+
235
+ if (tempSelectedFilters) {
236
+ const keys = Object.keys(tempSelectedFilters);
237
+ const lastKey = keys[keys.length - 1];
238
+ const options = generateFilterOptions(
239
+ firstLoad ? allListings : filteredListings,
240
+ allListings,
241
+ siteConfig,
242
+ filterOptions,
243
+ lastKey,
244
+ favorites
245
+ );
246
+ if (options) {
247
+ setFilterOptions(options);
248
+ if (firstLoad) setFirstLoad(false);
249
+ }
250
+ }
251
+ };
252
+
253
+ processListings();
254
+ }, [selectedFilters, query, listingEntities, filterByFavorites, favorites]);
255
+
256
+ const handleFilterListingsByLocation = (selectedLocation: any) => {
257
+ const { filteredListings } = filterListingsByLocation(
258
+ allListings,
259
+ selectedLocation,
260
+ listingEntities
261
+ );
262
+ setNewFilteredListings(filteredListings);
263
+ };
264
+
265
+ const handleSettingFavorites = (newFavorites: number[] | null) => {
266
+ if (newFavorites == null) {
267
+ localStorage.removeItem('favorites');
268
+ } else {
269
+ setFavorites(newFavorites);
270
+ localStorage.setItem('favorites', JSON.stringify(newFavorites));
271
+ }
272
+ };
273
+
274
+ return (
275
+ <MapListContext.Provider value={{
276
+ loading,
277
+ allListings,
278
+ filteredListings,
279
+ mapItems,
280
+ query,
281
+ setNewFilteredListings,
282
+ setQuery,
283
+ listingEntities,
284
+ selectedFilters,
285
+ setSelectedFilters,
286
+ filterOptions,
287
+ recruiters,
288
+ handleFilterListingsByLocation,
289
+ filterDialogIsOpen,
290
+ setFilterDialogIsOpen,
291
+ setMobileTab,
292
+ mobileTab,
293
+ siteConfig,
294
+ favorites,
295
+ handleSettingFavorites,
296
+ setFilterByFavorites,
297
+ filterByFavorites,
298
+ commuteLocation,
299
+ setCommuteLocation,
300
+ navigateToDetails,
301
+ navigateToEasyApply,
302
+ Link,
303
+ linkFormat,
304
+ sortSetting,
305
+ setSortSetting,
306
+ trackEvent
307
+ }}>
308
+ {children}
309
+ </MapListContext.Provider>
310
+ );
311
+ };
@@ -0,0 +1,102 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+
3
+ import { markerIconProps } from '~/util/mapIconUtil';
4
+
5
+ import { useMap } from '~/contexts/mapContext';
6
+
7
+ import { searchNearbyPlaces } from '~/services/googlePlacesNearbyService';
8
+
9
+ const PlacesContext = createContext();
10
+
11
+ export const usePlaces = () => useContext(PlacesContext);
12
+
13
+ export const PlacesProvider = ({ children, placeMappings, markerColors }) => {
14
+ const { selectedPlaces, zoom, center } = useMap();
15
+ const [poiMarkers, setPoiMarkers] = useState({ markers: [], icon: null });
16
+ const [currentCenter, setCurrentCenter] = useState(center);
17
+ const [currentZoom, setCurrentZoom] = useState(zoom);
18
+ const [placesWindow, setPlacesWindow] = useState(false);
19
+ const [selectedPlaceMarker, setSelectedPlaceMarker] = useState(null);
20
+
21
+ const getRadiusForZoom = () => {
22
+ if (currentZoom >= 18) return 1000;
23
+ if (currentZoom <= 10) return 0;
24
+
25
+ let tempZoom = Math.pow(19 - currentZoom, 4.85);
26
+ let radius = tempZoom;
27
+ let minRadius = 1500;
28
+ let maxRadius = 800000;
29
+
30
+ if (radius < minRadius) radius = minRadius;
31
+ else if (radius > maxRadius) radius = maxRadius;
32
+
33
+ return radius;
34
+ };
35
+
36
+ useEffect(() => {
37
+ if (!selectedPlaces || (!selectedPlaces.length > 0) || !center || currentZoom < 12) {
38
+ setPoiMarkers({ markers: [], icon: null });
39
+ return;
40
+ }
41
+ const fetchPlaces = async () => {
42
+ let poiTypes = [];
43
+ const selectedPOICategories = selectedPlaces;
44
+ selectedPOICategories.forEach(pointOfInterest => {
45
+ poiTypes = poiTypes.concat(placeMappings[pointOfInterest]);
46
+ });
47
+
48
+ const radius = getRadiusForZoom();
49
+ const location = { latitude: currentCenter.lat, longitude: currentCenter.lng };
50
+
51
+ try {
52
+ const response = await searchNearbyPlaces(poiTypes, location, radius);
53
+ const newMarkers = response.places.map(place => {
54
+ const getParentCategory = types => {
55
+ const selectedTypes = selectedPOICategories.reduce((acc, category) => {
56
+ return acc.concat(placeMappings[category]);
57
+ }, []);
58
+
59
+ for (const type of types) {
60
+ if (!selectedTypes.includes(type)) continue;
61
+ for (const category in placeMappings) {
62
+ if (placeMappings[category].includes(type)) {
63
+ return category;
64
+ }
65
+ }
66
+
67
+ }
68
+ };
69
+
70
+ const icon = markerIconProps(markerColors.placeMarkers, getParentCategory(place.types));
71
+
72
+ return {
73
+ position: { lat: place.location.latitude, lng: place.location.longitude },
74
+ title: place.displayName.text,
75
+ icon: icon
76
+ };
77
+ });
78
+ setPoiMarkers({ markers: newMarkers, icon: null });
79
+ } catch (error) {
80
+ console.error('Failed to fetch places:', error);
81
+ }
82
+ };
83
+
84
+ fetchPlaces();
85
+ }, [selectedPlaces, currentZoom, currentCenter]);
86
+
87
+ return (
88
+ <PlacesContext.Provider value={{
89
+ poiMarkers,
90
+ setCurrentCenter,
91
+ currentCenter,
92
+ setCurrentZoom,
93
+ currentZoom,
94
+ placesWindow,
95
+ setPlacesWindow,
96
+ selectedPlaceMarker,
97
+ setSelectedPlaceMarker
98
+ }}>
99
+ {children}
100
+ </PlacesContext.Provider>
101
+ );
102
+ };
@@ -0,0 +1,14 @@
1
+ import React, { createContext, useContext } from 'react';
2
+ import { eventTypes } from '~/constants/eventTypes';
3
+
4
+ const TrackEventContext = createContext();
5
+
6
+ export const useTrackEvent = () => useContext(TrackEventContext);
7
+
8
+ export const TrackEventProvider = ({ children, trackEvent }) => {
9
+ return (
10
+ <TrackEventContext.Provider value={{ trackEvent, eventTypes }}>
11
+ {children}
12
+ </TrackEventContext.Provider>
13
+ );
14
+ };
@@ -0,0 +1,9 @@
1
+ export enum SectionType {
2
+ Default = -1,
3
+ Company = 1,
4
+ Category = 2,
5
+ CategoryClass = 3,
6
+ Entity = 4,
7
+ CustomVar = 5,
8
+ REGION = 7
9
+ }