@abcagency/hc-ui-components 1.3.23 → 1.3.25
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/package.json +4 -5
- package/src/.editorconfig +12 -0
- package/src/apis/hcApi.ts +109 -0
- package/src/bundleIndex.js +14 -0
- package/src/clientToken.js +9 -0
- package/src/components/HireControlMap.js +135 -0
- package/src/components/containers/accordions/filter-container.js +48 -0
- package/src/components/containers/accordions/filter-item-container.js +66 -0
- package/src/components/containers/accordions/map-accordion-item-container.js +70 -0
- package/src/components/containers/filter/commute-container.js +89 -0
- package/src/components/containers/filter/filter-container.js +76 -0
- package/src/components/containers/filter/filter-item-container.js +71 -0
- package/src/components/containers/filter/location-container.js +45 -0
- package/src/components/containers/filter/points-of-interest-container.js +33 -0
- package/src/components/containers/filter/points-of-interest-radio-item-container.js +35 -0
- package/src/components/containers/filter/search-container.js +50 -0
- package/src/components/containers/jobListing/listing-details-container.js +40 -0
- package/src/components/containers/list/item-list-container.tsx +81 -0
- package/src/components/containers/list/list-item/list-item-container.js +43 -0
- package/src/components/containers/maps/info-window-content-container.js +51 -0
- package/src/components/containers/maps/map-container.js +204 -0
- package/src/components/containers/maps/map-list-container.js +48 -0
- package/src/components/containers/maps/map-marker-container.js +78 -0
- package/src/components/modules/accordions/MapAccordionItem.js +30 -0
- package/src/components/modules/accordions/default.js +171 -0
- package/src/components/modules/accordions/filterItem.js +27 -0
- package/src/components/modules/accordions/filters.js +32 -0
- package/src/components/modules/buttons/button-group-apply.js +123 -0
- package/src/components/modules/buttons/commute-pill.js +22 -0
- package/src/components/modules/buttons/default.js +194 -0
- package/src/components/modules/buttons/items-pill.js +35 -0
- package/src/components/modules/buttons/pill-wrapper.js +27 -0
- package/src/components/modules/buttons/show-all-button.js +20 -0
- package/src/components/modules/cards/default.js +167 -0
- package/src/components/modules/cards/filter.js +56 -0
- package/src/components/modules/dialogs/apply-dialog.js +48 -0
- package/src/components/modules/filter/commute.js +108 -0
- package/src/components/modules/filter/index.js +55 -0
- package/src/components/modules/filter/item.js +48 -0
- package/src/components/modules/filter/location.js +48 -0
- package/src/components/modules/filter/radio-item.js +42 -0
- package/src/components/modules/filter/search.js +65 -0
- package/src/components/modules/filter/sort.js +83 -0
- package/src/components/modules/grid.js +54 -0
- package/src/components/modules/icon.js +33 -0
- package/src/components/modules/jobListing/listing-details.js +109 -0
- package/src/components/modules/list/field-mapper.js +114 -0
- package/src/components/modules/list/header-item.js +91 -0
- package/src/components/modules/list/header.js +49 -0
- package/src/components/modules/list/item-expand-card/index.js +22 -0
- package/src/components/modules/list/item-expand-card/recruiter-contact-nav.js +50 -0
- package/src/components/modules/list/item-expand-card/recruiter-details.js +68 -0
- package/src/components/modules/list/item-expand-card/recruiter-headshot.js +22 -0
- package/src/components/modules/list/item-list.tsx +84 -0
- package/src/components/modules/list/list-item/list-item.js +130 -0
- package/src/components/modules/maps/info-window-card.js +17 -0
- package/src/components/modules/maps/info-window-content.js +35 -0
- package/src/components/modules/maps/map-list.js +28 -0
- package/src/components/modules/maps/map-marker.js +29 -0
- package/src/components/modules/maps/map.js +76 -0
- package/src/components/modules/maps/place-marker.js +41 -0
- package/src/components/modules/maps/tabs.js +81 -0
- package/src/constants/eventTypes.js +13 -0
- package/src/constants/placeTypes.js +8 -0
- package/src/contexts/mapContext.tsx +129 -0
- package/src/contexts/mapListContext.tsx +311 -0
- package/src/contexts/placesContext.js +102 -0
- package/src/contexts/trackEventContext.js +14 -0
- package/src/enums/SectionType.ts +9 -0
- package/src/hooks/useList.js +89 -0
- package/src/index.js +3 -0
- package/src/services/configService.ts +16 -0
- package/src/services/googlePlacesNearbyService.ts +42 -0
- package/src/services/listingAggregatorService.ts +76 -0
- package/src/services/listingEntityService.ts +16 -0
- package/src/services/listingService.ts +40 -0
- package/src/services/recruiterService.ts +18 -0
- package/src/styles/bundle.css +268 -0
- package/src/styles/index.css +24 -0
- package/src/types/Address.ts +7 -0
- package/src/types/ContentSection.ts +9 -0
- package/src/types/GetListingParams.ts +8 -0
- package/src/types/LatLng.ts +4 -0
- package/src/types/ListingEntity.ts +11 -0
- package/src/types/ListingFields.ts +25 -0
- package/src/types/Listings.ts +32 -0
- package/src/types/Recruiter.ts +9 -0
- package/src/types/SimilarListing.ts +24 -0
- package/src/types/config/Colors.ts +8 -0
- package/src/types/config/MapConfig.ts +31 -0
- package/src/types/config/PointsOfInterestConfig.ts +13 -0
- package/src/types/config/SearchConfig.ts +4 -0
- package/src/util/arrayUtil.js +3 -0
- package/src/util/fieldMapper.js +22 -0
- package/src/util/filterUtil.js +239 -0
- package/src/util/loading.js +17 -0
- package/src/util/localStorageUtil.ts +34 -0
- package/src/util/mapIconUtil.js +180 -0
- package/src/util/mapUtil.js +91 -0
- package/src/util/sortUtil.js +33 -0
- package/src/util/stringUtils.js +6 -0
- package/src/util/urlFilterUtil.js +85 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abcagency/hc-ui-components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.25",
|
|
4
4
|
"description": "UI Components for HireControl",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
7
|
-
"dist"
|
|
7
|
+
"dist",
|
|
8
|
+
"src"
|
|
8
9
|
],
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "cross-env NODE_ENV=production rollup -c",
|
|
@@ -30,9 +31,7 @@
|
|
|
30
31
|
"ajv-keywords": "^5.1.0",
|
|
31
32
|
"framer-motion": "^10.18.0",
|
|
32
33
|
"fuse.js": "^7.0.0",
|
|
33
|
-
"npm": "^10.8.1",
|
|
34
|
-
"react": "^18.3.1",
|
|
35
|
-
"react-dom": "^18.3.1",
|
|
34
|
+
"npm": "^10.8.1",
|
|
36
35
|
"react-router-dom": "^6.23.1",
|
|
37
36
|
"react-scripts": "5.0.1",
|
|
38
37
|
"react-wrap-balancer": "^1.1.1",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getClientAuthKey } from '~/clientToken';
|
|
2
|
+
const baseURL = process.env.HC_API_BASE_URL as string;
|
|
3
|
+
|
|
4
|
+
interface MemoryStorage {
|
|
5
|
+
authToken: string | null;
|
|
6
|
+
tokenExpiration: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const memoryStorage: MemoryStorage = {
|
|
10
|
+
authToken: null,
|
|
11
|
+
tokenExpiration: null
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function setStorage(key: keyof MemoryStorage, value: string): void {
|
|
15
|
+
try {
|
|
16
|
+
sessionStorage.setItem(key, value);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
memoryStorage[key] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getStorage(key: keyof MemoryStorage): string | null {
|
|
23
|
+
try {
|
|
24
|
+
return sessionStorage.getItem(key) || memoryStorage[key];
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return memoryStorage[key];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface AuthResponse {
|
|
31
|
+
token: string;
|
|
32
|
+
expiration: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const login = async (): Promise<AuthResponse> => {
|
|
36
|
+
const clientAuthKey = getClientAuthKey();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(`${baseURL}/auth/login`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json'
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
clientAuthKey: clientAuthKey
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error('Login failed');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
|
|
55
|
+
if (data.token && data.expiration) {
|
|
56
|
+
setStorage('authToken', data.token);
|
|
57
|
+
setStorage('tokenExpiration', data.expiration);
|
|
58
|
+
return { token: data.token, expiration: data.expiration };
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error('Invalid login response');
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Login failed:', error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const fetchWithAuth = async (url: string, options: RequestInit = {}): Promise<Response> => {
|
|
69
|
+
let token = getStorage('authToken');
|
|
70
|
+
|
|
71
|
+
const expirationDateTime = getStorage('tokenExpiration');
|
|
72
|
+
const currentTime = new Date();
|
|
73
|
+
|
|
74
|
+
if (!token || !expirationDateTime || new Date(expirationDateTime) <= currentTime) {
|
|
75
|
+
const authResponse = await login();
|
|
76
|
+
token = authResponse.token;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const headers = new Headers(options.headers || {});
|
|
80
|
+
headers.append('Authorization', `Bearer ${token}`);
|
|
81
|
+
|
|
82
|
+
const finalOptions = {
|
|
83
|
+
...options,
|
|
84
|
+
headers
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const response = await fetch(`${baseURL}${url}`, finalOptions);
|
|
88
|
+
|
|
89
|
+
if (!response.ok) throw new Error('Network response was not ok.');
|
|
90
|
+
|
|
91
|
+
return response;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default {
|
|
95
|
+
get: async <T>(url: string): Promise<T> => {
|
|
96
|
+
const response = await fetchWithAuth(url);
|
|
97
|
+
return await response.json() as T;
|
|
98
|
+
},
|
|
99
|
+
post: async <T>(url: string, data: any): Promise<T> => {
|
|
100
|
+
const response = await fetchWithAuth(url, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
'Content-Type': 'application/json'
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(data)
|
|
106
|
+
});
|
|
107
|
+
return await response.json() as T;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import HireControlMap from '~/components/HireControlMap';
|
|
4
|
+
|
|
5
|
+
const initializeComponent = (elementId, props) => {
|
|
6
|
+
const element = document.getElementById(elementId);
|
|
7
|
+
if (element) {
|
|
8
|
+
ReactDOM.render(<div className='hc-bundle'><HireControlMap {...props} /></div>, element);
|
|
9
|
+
} else {
|
|
10
|
+
console.error(`Element with id ${elementId} not found`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { HireControlMap, initializeComponent };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useLoadScript } from '@react-google-maps/api';
|
|
3
|
+
|
|
4
|
+
import Grid from '~/components/modules/grid';
|
|
5
|
+
import Filter from '~/components/containers/filter/filter-container';
|
|
6
|
+
import MapList from '~/components/containers/maps/map-list-container';
|
|
7
|
+
|
|
8
|
+
import { MapProvider } from '~/contexts/mapContext';
|
|
9
|
+
import { PlacesProvider } from '~/contexts/placesContext';
|
|
10
|
+
import { MapListProvider } from '~/contexts/mapListContext';
|
|
11
|
+
import { TrackEventProvider } from '~/contexts/trackEventContext';
|
|
12
|
+
|
|
13
|
+
import { getMapConfig } from '~/services/configService';
|
|
14
|
+
import { setClientAuthKey } from '~/clientToken.js';
|
|
15
|
+
|
|
16
|
+
import '../styles/index.css';
|
|
17
|
+
|
|
18
|
+
const libraries = ['places'];
|
|
19
|
+
|
|
20
|
+
export const HireControlMap = ({
|
|
21
|
+
clientToken,
|
|
22
|
+
navigateToDetails = null,
|
|
23
|
+
navigateToEasyApply = null,
|
|
24
|
+
Link = null,
|
|
25
|
+
linkFormat = '/jobs/[slug]',
|
|
26
|
+
trackEvent = (eventType, eventObj) => {console.log(eventType); console.log(eventObj);},
|
|
27
|
+
listings = [],
|
|
28
|
+
setFiltersUrl = null
|
|
29
|
+
}) => {
|
|
30
|
+
const [siteConfig, setSiteconfig] = useState(null);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setClientAuthKey(clientToken);
|
|
34
|
+
const fetchSiteConfig = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const configData = await getMapConfig(clientToken);
|
|
37
|
+
setSiteconfig(configData);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Failed to fetch site configuration:', error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
fetchSiteConfig();
|
|
44
|
+
}, [clientToken]);
|
|
45
|
+
|
|
46
|
+
const { isLoaded } = useLoadScript({
|
|
47
|
+
googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY,
|
|
48
|
+
version: 'quarterly',
|
|
49
|
+
libraries: libraries
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
{isLoaded && siteConfig && (
|
|
55
|
+
<HomeBody
|
|
56
|
+
siteConfig={siteConfig}
|
|
57
|
+
navigateToDetails={navigateToDetails}
|
|
58
|
+
navigateToEasyApply={navigateToEasyApply}
|
|
59
|
+
Link={Link}
|
|
60
|
+
linkFormat={linkFormat}
|
|
61
|
+
trackEvent={trackEvent}
|
|
62
|
+
listings={listings}
|
|
63
|
+
setFiltersUrl={setFiltersUrl}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const HomeBody = ({ siteConfig, navigateToDetails, navigateToEasyApply, Link, linkFormat, trackEvent, listings, setFiltersUrl }) => {
|
|
71
|
+
const resetFilters = false;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<TrackEventProvider trackEvent={trackEvent}>
|
|
75
|
+
<MapListProvider
|
|
76
|
+
siteConfig={siteConfig}
|
|
77
|
+
resetFilters={resetFilters}
|
|
78
|
+
avigateToDetails={navigateToDetails}
|
|
79
|
+
navigateToEasyApply={navigateToEasyApply}
|
|
80
|
+
Link={Link}
|
|
81
|
+
linkFormat={linkFormat}
|
|
82
|
+
listings={listings}
|
|
83
|
+
setFiltersUrl={setFiltersUrl}
|
|
84
|
+
>
|
|
85
|
+
<MapProvider resetFilters={resetFilters}>
|
|
86
|
+
<PlacesProvider
|
|
87
|
+
placeMappings={siteConfig.pointsOfInterestConfig.placeMappings ?? {}}
|
|
88
|
+
markerColors={{
|
|
89
|
+
fillColor: siteConfig.colors.primary,
|
|
90
|
+
strokeColor: siteConfig.colors.primaryDark,
|
|
91
|
+
selectedFillColor: siteConfig.colors.secondary,
|
|
92
|
+
selectedStrokeColor: siteConfig.colors.secondaryDark,
|
|
93
|
+
placeMarkers: {
|
|
94
|
+
colors: siteConfig.pointsOfInterestConfig.placeMarkerColors,
|
|
95
|
+
size: siteConfig.pointsOfInterestConfig.placeMarkerSize
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<Grid
|
|
100
|
+
as='section'
|
|
101
|
+
id='job-search-interface'
|
|
102
|
+
columns='md:hc-grid-cols-[1fr_2.5fr] lg:hc-grid-cols-[1fr_3.5fr]'
|
|
103
|
+
gap='hc-gap-0'
|
|
104
|
+
autoRows={false}
|
|
105
|
+
className='hc-bundle hc-items-stretch hc-h-screen hc-min-h-[30rem] hc-divide-x hc-divide-uiAccent/20'
|
|
106
|
+
>
|
|
107
|
+
<Grid.Item className='hc-bg-gray-100'>
|
|
108
|
+
<Filter showMap={siteConfig.showMap} className='hc-hidden md:hc-block' />
|
|
109
|
+
</Grid.Item>
|
|
110
|
+
<MapList
|
|
111
|
+
markerConfigs={{
|
|
112
|
+
fillColor: siteConfig.colors.primary,
|
|
113
|
+
strokeColor: siteConfig.colors.primaryDark,
|
|
114
|
+
selectedFillColor: siteConfig.colors.secondary,
|
|
115
|
+
selectedStrokeColor: siteConfig.colors.secondaryDark,
|
|
116
|
+
placeMarkers: {
|
|
117
|
+
colors: siteConfig.pointsOfInterestConfig.placeMarkerColors,
|
|
118
|
+
size: siteConfig.pointsOfInterestConfig.placeMarkerSize
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
showMap={siteConfig.showMap}
|
|
122
|
+
fieldsShown={siteConfig.fieldsShown}
|
|
123
|
+
specialFeatures={siteConfig.specialFeatures}
|
|
124
|
+
fieldNames={siteConfig.fieldNames}
|
|
125
|
+
placeMappings={siteConfig.pointsOfInterestConfig.placeMappings ?? {}}
|
|
126
|
+
/>
|
|
127
|
+
</Grid>
|
|
128
|
+
</PlacesProvider>
|
|
129
|
+
</MapProvider>
|
|
130
|
+
</MapListProvider>
|
|
131
|
+
</TrackEventProvider>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export default HireControlMap;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
3
|
+
import AccordionFilters from '~/components/modules/accordions/filters';
|
|
4
|
+
import AccordionFilterItem from '~/components/containers/accordions/filter-item-container';
|
|
5
|
+
|
|
6
|
+
const AccordionFiltersContainer = ({
|
|
7
|
+
className,
|
|
8
|
+
defaultValue,
|
|
9
|
+
setDefaultValue,
|
|
10
|
+
setLocation,
|
|
11
|
+
setSelectedListItem,
|
|
12
|
+
SubcategoryRequireCategory
|
|
13
|
+
}) => {
|
|
14
|
+
const { filterOptions, selectedFilters, setSelectedFilters, siteConfig } = useMapList();
|
|
15
|
+
|
|
16
|
+
const handleSetSelectedFilters = prevFilters => {
|
|
17
|
+
setSelectedFilters(prevFilters);
|
|
18
|
+
setLocation(null);
|
|
19
|
+
setSelectedListItem(null);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<AccordionFilters
|
|
24
|
+
className={className}
|
|
25
|
+
defaultValue={defaultValue}
|
|
26
|
+
filterOptions={filterOptions}
|
|
27
|
+
>
|
|
28
|
+
{filterOptions?.filters?.map(filter => {
|
|
29
|
+
if(filter.id === 'category' && SubcategoryRequireCategory === true && (!selectedFilters.categoryClass || Object.keys(selectedFilters.categoryClass).length < 1)){
|
|
30
|
+
return;
|
|
31
|
+
} else if (filter.id === 'category' && SubcategoryRequireCategory === true && filter.items.length > 0) {
|
|
32
|
+
filter.items = filter.items.filter(item => item.count > 0);
|
|
33
|
+
}
|
|
34
|
+
return (<AccordionFilterItem
|
|
35
|
+
key={filter.id}
|
|
36
|
+
filter={filter}
|
|
37
|
+
setDefaultValue={setDefaultValue}
|
|
38
|
+
selectedFilters={selectedFilters}
|
|
39
|
+
setSelectedFilters={handleSetSelectedFilters}
|
|
40
|
+
subcategoryRequireCategory={siteConfig.subcategoryRequireCategory}
|
|
41
|
+
/>);
|
|
42
|
+
}
|
|
43
|
+
)}
|
|
44
|
+
</AccordionFilters>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default AccordionFiltersContainer;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import AccordionFilterItem from '~/components/modules/accordions/filterItem';
|
|
3
|
+
import FilterItem from '~/components/containers/filter/filter-item-container';
|
|
4
|
+
import ItemsPill from '~/components/modules/buttons/items-pill';
|
|
5
|
+
|
|
6
|
+
const FilterItemContainer = ({
|
|
7
|
+
filter,
|
|
8
|
+
setDefaultValue,
|
|
9
|
+
setSelectedFilters,
|
|
10
|
+
selectedFilters,
|
|
11
|
+
subcategoryRequireCategory = false
|
|
12
|
+
}) => {
|
|
13
|
+
const fieldKey = filter.id;
|
|
14
|
+
const activeItemsCount = selectedFilters != null && selectedFilters[fieldKey]
|
|
15
|
+
? Object.keys(selectedFilters[fieldKey]).length
|
|
16
|
+
: 0;
|
|
17
|
+
|
|
18
|
+
const handleClearFilters = event => {
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
setSelectedFilters(prevFilters => {
|
|
21
|
+
const updatedFilters = { ...prevFilters };
|
|
22
|
+
if(subcategoryRequireCategory && fieldKey == 'categoryClass'){
|
|
23
|
+
delete updatedFilters['category'];
|
|
24
|
+
}
|
|
25
|
+
delete updatedFilters[fieldKey];
|
|
26
|
+
return updatedFilters;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const header = (
|
|
31
|
+
<>
|
|
32
|
+
{filter.title}
|
|
33
|
+
{activeItemsCount > 0 && (
|
|
34
|
+
<ItemsPill
|
|
35
|
+
activeItemsCount={activeItemsCount}
|
|
36
|
+
onClick={handleClearFilters}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const body = (
|
|
43
|
+
<>
|
|
44
|
+
{filter.items.sort().map(item => (
|
|
45
|
+
<FilterItem
|
|
46
|
+
key={item.name}
|
|
47
|
+
item={item}
|
|
48
|
+
field={filter.id}
|
|
49
|
+
selectedFilters={selectedFilters}
|
|
50
|
+
setSelectedFilters={setSelectedFilters}
|
|
51
|
+
/>
|
|
52
|
+
))}
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<AccordionFilterItem
|
|
58
|
+
id={filter.id}
|
|
59
|
+
setDefaultValue={setDefaultValue}
|
|
60
|
+
header={header}
|
|
61
|
+
body={body}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default memo(FilterItemContainer);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useMap } from '~/contexts/mapContext';
|
|
3
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
4
|
+
import { useTrackEvent } from '~/contexts/trackEventContext';
|
|
5
|
+
import { setStorageObject } from '~/util/localStorageUtil';
|
|
6
|
+
import MapAccordionItem from '~/components/modules/accordions/MapAccordionItem';
|
|
7
|
+
import ListItemContainer from '~/components/containers/list/list-item/list-item-container';
|
|
8
|
+
|
|
9
|
+
const MapAccordionItemContainer = ({
|
|
10
|
+
showMap,
|
|
11
|
+
item,
|
|
12
|
+
itemRefs,
|
|
13
|
+
fieldsShown,
|
|
14
|
+
itemExpandedContent,
|
|
15
|
+
specialFeatures,
|
|
16
|
+
isActive,
|
|
17
|
+
hasListItemSelected
|
|
18
|
+
}) => {
|
|
19
|
+
const { mapItems, recruiters } = useMapList();
|
|
20
|
+
const { selectItem } = useMap();
|
|
21
|
+
const { trackEvent, eventTypes } = useTrackEvent();
|
|
22
|
+
|
|
23
|
+
const setSelectedItemAndZoomMap = (item, isActive) => {
|
|
24
|
+
if (isActive) {
|
|
25
|
+
localStorage.removeItem("selectedListItem");
|
|
26
|
+
const location = mapItems.find(x => Object.prototype.hasOwnProperty.call(x.items, item.id)) || null;
|
|
27
|
+
selectItem(null, null, 9, { lat: 39.8283, lng: -98.5795 });
|
|
28
|
+
} else {
|
|
29
|
+
setStorageObject("selectedListItem", item);
|
|
30
|
+
const location = mapItems.find(x => Object.prototype.hasOwnProperty.call(x.items, item.id)) || null;
|
|
31
|
+
selectItem(item, location, 12, {
|
|
32
|
+
lat: location?.latitude,
|
|
33
|
+
lng: location?.longitude
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleItemClick = item => {
|
|
39
|
+
trackEvent(eventTypes.JOB_LISTING_SELECTED, {
|
|
40
|
+
jobTitle: item.fields.position,
|
|
41
|
+
jobCategory: item.fields.category,
|
|
42
|
+
entityDisplayName: item?.mapDetails?.entityDisplayName
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
setSelectedItemAndZoomMap(item, isActive);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<MapAccordionItem
|
|
50
|
+
item={item}
|
|
51
|
+
itemRefs={itemRefs}
|
|
52
|
+
itemExpandedContent={itemExpandedContent}
|
|
53
|
+
isActive={isActive}
|
|
54
|
+
recruiter={recruiters[item.recruiterId]}
|
|
55
|
+
>
|
|
56
|
+
<ListItemContainer
|
|
57
|
+
showMap={showMap}
|
|
58
|
+
item={item}
|
|
59
|
+
itemRefs={itemRefs}
|
|
60
|
+
fieldsShown={fieldsShown}
|
|
61
|
+
specialFeatures={specialFeatures}
|
|
62
|
+
isActive={isActive}
|
|
63
|
+
hasListItemSelected={hasListItemSelected}
|
|
64
|
+
onClick={() => handleItemClick(item)}
|
|
65
|
+
/>
|
|
66
|
+
</MapAccordionItem>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default MapAccordionItemContainer;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import usePlacesAutocomplete, { getGeocode, getLatLng } from 'use-places-autocomplete';
|
|
3
|
+
|
|
4
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
5
|
+
import { useTrackEvent } from '~/contexts/trackEventContext';
|
|
6
|
+
import { getStorageItem } from '~/util/localStorageUtil';
|
|
7
|
+
import FilterCommute from '~/components/modules/filter/commute';
|
|
8
|
+
|
|
9
|
+
const FilterCommuteContainer = ({ className }) => {
|
|
10
|
+
const {
|
|
11
|
+
ready,
|
|
12
|
+
suggestions: { status, data },
|
|
13
|
+
setValue,
|
|
14
|
+
clearSuggestions
|
|
15
|
+
} = usePlacesAutocomplete();
|
|
16
|
+
|
|
17
|
+
const [selected, setSelected] = useState(getStorageItem('selectedCommute', ''));
|
|
18
|
+
const inputRef = useRef(null);
|
|
19
|
+
const { setCommuteLocation, commuteLocation } = useMapList();
|
|
20
|
+
const [isCurrentLocation, setIsCurrentLocation] = useState(getStorageItem('isCurrentLocation', false));
|
|
21
|
+
const { trackEvent, eventTypes } = useTrackEvent();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (commuteLocation !== null && commuteLocation !== '') return;
|
|
25
|
+
setIsCurrentLocation(false);
|
|
26
|
+
localStorage.removeItem('isCurrentLocation');
|
|
27
|
+
localStorage.removeItem('selectedCommute');
|
|
28
|
+
setSelected("");
|
|
29
|
+
}, [commuteLocation]);
|
|
30
|
+
|
|
31
|
+
const handleSelect = async (val, isCurrLocation = false) => {
|
|
32
|
+
setValue(val, false);
|
|
33
|
+
setSelected(val);
|
|
34
|
+
localStorage.setItem('selectedCommute', val);
|
|
35
|
+
clearSuggestions();
|
|
36
|
+
if (isCurrLocation) return;
|
|
37
|
+
try {
|
|
38
|
+
const results = await getGeocode({ address: val });
|
|
39
|
+
const { lat, lng } = await getLatLng(results[0]);
|
|
40
|
+
trackEvent(eventTypes.COMMUTE_ORIGIN_ADDED, { commuteLocation: { lat, lng }, isCurrentLocation: false });
|
|
41
|
+
setCommuteLocation({ lat, lng });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// no-op
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const fetchLocation = () => {
|
|
48
|
+
if (!navigator.geolocation) {
|
|
49
|
+
console.error("Geolocation is not supported by this browser.");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
navigator.geolocation.getCurrentPosition(
|
|
53
|
+
position => {
|
|
54
|
+
const location = {
|
|
55
|
+
lat: position.coords.latitude,
|
|
56
|
+
lng: position.coords.longitude
|
|
57
|
+
};
|
|
58
|
+
setCommuteLocation(location);
|
|
59
|
+
trackEvent(eventTypes.COMMUTE_ORIGIN_ADDED, { commuteLocation: location, isCurrentLocation: true });
|
|
60
|
+
handleSelect("Current Location");
|
|
61
|
+
},
|
|
62
|
+
error => {
|
|
63
|
+
console.error("Error fetching location", error);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<FilterCommute
|
|
70
|
+
className={className}
|
|
71
|
+
ready={ready}
|
|
72
|
+
status={status}
|
|
73
|
+
data={data}
|
|
74
|
+
selected={selected}
|
|
75
|
+
isCurrentLocation={isCurrentLocation}
|
|
76
|
+
inputRef={inputRef}
|
|
77
|
+
handleSelect={handleSelect}
|
|
78
|
+
setValue={setValue}
|
|
79
|
+
setSelected={setSelected}
|
|
80
|
+
clearSuggestions={clearSuggestions}
|
|
81
|
+
commuteLocation={commuteLocation}
|
|
82
|
+
setIsCurrentLocation={setIsCurrentLocation}
|
|
83
|
+
fetchLocation={fetchLocation}
|
|
84
|
+
setCommuteLocation={setCommuteLocation}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default FilterCommuteContainer;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useMap } from '~/contexts/mapContext';
|
|
3
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
4
|
+
import { useTrackEvent } from '~/contexts/trackEventContext';
|
|
5
|
+
import Filter from '~/components/modules/filter/index';
|
|
6
|
+
import FilterSearch from '~/components/containers/filter/search-container';
|
|
7
|
+
import FiltersAccordion from '~/components/containers/accordions/filter-container';
|
|
8
|
+
import FilterLocations from '~/components/containers/filter/location-container';
|
|
9
|
+
|
|
10
|
+
const FilterContainer = ({
|
|
11
|
+
className,
|
|
12
|
+
showMap
|
|
13
|
+
}) => {
|
|
14
|
+
const { trackEvent, eventTypes } = useTrackEvent();
|
|
15
|
+
const [hasActiveFilters, setHasActiveFilters] = useState(false);
|
|
16
|
+
const [defaultValue, setDefaultValue] = useState(null);
|
|
17
|
+
const { setSelectedListItem, setLocation, filterReset } = useMap();
|
|
18
|
+
const {
|
|
19
|
+
filteredListings,
|
|
20
|
+
selectedFilters,
|
|
21
|
+
setSelectedFilters,
|
|
22
|
+
setMobileTab,
|
|
23
|
+
handleSettingFavorites,
|
|
24
|
+
setQuery,
|
|
25
|
+
siteConfig
|
|
26
|
+
} = useMapList();
|
|
27
|
+
|
|
28
|
+
const handleReset = () => {
|
|
29
|
+
trackEvent(eventTypes.FILTERS_RESET, { filtersRemoved: selectedFilters });
|
|
30
|
+
filterReset();
|
|
31
|
+
setSelectedFilters({});
|
|
32
|
+
setQuery(null);
|
|
33
|
+
handleSettingFavorites(null);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Filter
|
|
38
|
+
className={className}
|
|
39
|
+
showMap={showMap}
|
|
40
|
+
hasActiveFilters={hasActiveFilters}
|
|
41
|
+
setHasActiveFilters={setHasActiveFilters}
|
|
42
|
+
defaultValue={defaultValue}
|
|
43
|
+
setDefaultValue={setDefaultValue}
|
|
44
|
+
setSelectedListItem={setSelectedListItem}
|
|
45
|
+
setLocation={setLocation}
|
|
46
|
+
filteredListings={filteredListings}
|
|
47
|
+
selectedFilters={selectedFilters}
|
|
48
|
+
setSelectedFilters={setSelectedFilters}
|
|
49
|
+
setMobileTab={setMobileTab}
|
|
50
|
+
handleReset={handleReset}
|
|
51
|
+
siteConfig={siteConfig}
|
|
52
|
+
>
|
|
53
|
+
<FiltersAccordion
|
|
54
|
+
setHasActiveFilters={setHasActiveFilters}
|
|
55
|
+
defaultValue={defaultValue}
|
|
56
|
+
setDefaultValue={value => { setDefaultValue(value === defaultValue ? "" : value); }}
|
|
57
|
+
setLocation={setLocation}
|
|
58
|
+
setSelectedListItem={setSelectedListItem}
|
|
59
|
+
SubcategoryRequireCategory={siteConfig.subcategoryRequireCategory}
|
|
60
|
+
/>
|
|
61
|
+
<FilterSearch />
|
|
62
|
+
{siteConfig.hideLocations !== true &&
|
|
63
|
+
<FilterLocations
|
|
64
|
+
setHasActiveFilters={setHasActiveFilters}
|
|
65
|
+
defaultValue={defaultValue}
|
|
66
|
+
showMap={showMap}
|
|
67
|
+
setDefaultValue={value => { setDefaultValue(value === defaultValue ? "" : value); }}
|
|
68
|
+
setLocation={setLocation}
|
|
69
|
+
setSelectedListItem={setSelectedListItem}
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
72
|
+
</Filter>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default FilterContainer;
|