homeflowjs 1.0.77 → 1.0.78
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/actions/branches.actions.js +10 -0
- package/actions/branches.types.js +2 -0
- package/branches/branches-search-form/branches-polygon-search-input.component.jsx +216 -0
- package/branches/index.js +2 -0
- package/package.json +1 -1
- package/reducers/branches.reducer.js +12 -0
- package/utils/requests-handler.js +78 -0
@@ -14,3 +14,13 @@ export const setBranchesPagination = (payload) => ({
|
|
14
14
|
type: BranchesActionTypes.SET_BRANCHES_PAGINATION,
|
15
15
|
payload,
|
16
16
|
});
|
17
|
+
|
18
|
+
export const setSearchedPolygonBranchesIDs = (payload) => ({
|
19
|
+
type: BranchesActionTypes.SET_SEARCHED_POLYGON_BRANCHES_IDS,
|
20
|
+
payload,
|
21
|
+
});
|
22
|
+
|
23
|
+
export const setBranchDepartmentFilter = (payload) => ({
|
24
|
+
type: BranchesActionTypes.SET_BRANCH_DEPARTMENT_FILTER,
|
25
|
+
payload,
|
26
|
+
});
|
@@ -2,6 +2,8 @@ const BranchesActionTypes = {
|
|
2
2
|
SET_BRANCHES: 'SET_BRANCHES',
|
3
3
|
SET_BRANCHES_SEARCH: 'SET_BRANCHES_SEARCH',
|
4
4
|
SET_BRANCHES_PAGINATION: 'SET_BRANCHES_PAGINATION',
|
5
|
+
SET_SEARCHED_POLYGON_BRANCHES_IDS: 'SET_SEARCHED_POLYGON_BRANCHES_IDS',
|
6
|
+
SET_BRANCH_DEPARTMENT_FILTER: 'SET_BRANCH_DEPARTMENT_FILTER',
|
5
7
|
};
|
6
8
|
|
7
9
|
export default BranchesActionTypes;
|
@@ -0,0 +1,216 @@
|
|
1
|
+
import React, {
|
2
|
+
useState, useRef, useEffect, useCallback,
|
3
|
+
useMemo,
|
4
|
+
} from 'react';
|
5
|
+
import PropTypes from 'prop-types';
|
6
|
+
import { useDispatch } from 'react-redux';
|
7
|
+
import Autosuggest from 'react-autosuggest';
|
8
|
+
import debounce from 'lodash.debounce';
|
9
|
+
import { notify } from 'homeflowjs';
|
10
|
+
|
11
|
+
import { DEBOUNCE_DELAY } from '../../utils';
|
12
|
+
import branchSearchUtils from '../../utils/requests-handler';
|
13
|
+
import { setSearchedPolygonBranchesIDs } from '../../actions/branches.actions';
|
14
|
+
import LoadingIcon from '../../shared/loading-icon/loading-icon.component';
|
15
|
+
|
16
|
+
const { fetchAddressSuggestions, fetchBranchDataBySearchedAddress, ERROR_DEFAULT_MESSAGE } = branchSearchUtils;
|
17
|
+
|
18
|
+
const BranchesPolygonSearchInput = ({
|
19
|
+
placeholder,
|
20
|
+
inputProps,
|
21
|
+
channel,
|
22
|
+
isDepartmentSearch,
|
23
|
+
hiddenBranchIDs,
|
24
|
+
icon: Icon,
|
25
|
+
loadingIcon: CustomLoadingIcon,
|
26
|
+
handleInputClick,
|
27
|
+
handleSuggestionSelected,
|
28
|
+
}) => {
|
29
|
+
const dispatch = useDispatch();
|
30
|
+
|
31
|
+
const [suggestions, setSuggestions] = useState([]);
|
32
|
+
const [searchedAddress, setSearchedAddress] = useState('');
|
33
|
+
const [requestIsLoading, setRequestIsLoading] = useState(false);
|
34
|
+
const [isFieldDisabled, setIsFieldDisabled] = useState(false);
|
35
|
+
const [selectedSuggestion, setSelectedSuggestion] = useState(null);
|
36
|
+
|
37
|
+
useEffect(() => {
|
38
|
+
dispatch(setSearchedPolygonBranchesIDs(null));
|
39
|
+
}, []);
|
40
|
+
|
41
|
+
const handleSearchedBranchData = (branchesIDs) => {
|
42
|
+
const searchedBranchesIDs = branchesIDs.filter(
|
43
|
+
(branchID) => !hiddenBranchIDs.includes(branchID.toString())
|
44
|
+
);
|
45
|
+
dispatch(setSearchedPolygonBranchesIDs(searchedBranchesIDs));
|
46
|
+
};
|
47
|
+
|
48
|
+
const resetLoadingStatus = () => {
|
49
|
+
setRequestIsLoading(false);
|
50
|
+
setIsFieldDisabled(false);
|
51
|
+
}
|
52
|
+
|
53
|
+
const handleError = (err) => {
|
54
|
+
let errorData = err;
|
55
|
+
if (
|
56
|
+
errorData instanceof SyntaxError
|
57
|
+
|| errorData instanceof TypeError
|
58
|
+
|| errorData instanceof ReferenceError
|
59
|
+
) {
|
60
|
+
errorData = new Error(ERROR_DEFAULT_MESSAGE);
|
61
|
+
}
|
62
|
+
notify(errorData.message, 'error')
|
63
|
+
}
|
64
|
+
|
65
|
+
const getSearchedBranch = () => {
|
66
|
+
if (channel) {
|
67
|
+
fetchBranchDataBySearchedAddress({
|
68
|
+
addressID: selectedSuggestion,
|
69
|
+
usingPolygon: true,
|
70
|
+
channel,
|
71
|
+
isDepartmentSearch,
|
72
|
+
})
|
73
|
+
.then(handleSearchedBranchData)
|
74
|
+
.catch(handleError)
|
75
|
+
.finally(resetLoadingStatus)
|
76
|
+
} else {
|
77
|
+
Promise.all([
|
78
|
+
fetchBranchDataBySearchedAddress({
|
79
|
+
addressID: selectedSuggestion,
|
80
|
+
usingPolygon: true,
|
81
|
+
channel: 'sales',
|
82
|
+
isDepartmentSearch,
|
83
|
+
}),
|
84
|
+
fetchBranchDataBySearchedAddress({
|
85
|
+
addressID: selectedSuggestion,
|
86
|
+
usingPolygon: true,
|
87
|
+
channel: 'lettings',
|
88
|
+
isDepartmentSearch,
|
89
|
+
})
|
90
|
+
])
|
91
|
+
.then(([salesBranchIDList, lettingsBranchIDList]) => {
|
92
|
+
handleSearchedBranchData([
|
93
|
+
...salesBranchIDList,
|
94
|
+
...lettingsBranchIDList,
|
95
|
+
])
|
96
|
+
})
|
97
|
+
.catch(handleError)
|
98
|
+
.finally(resetLoadingStatus);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
useEffect(() => {
|
103
|
+
if (selectedSuggestion) {
|
104
|
+
setRequestIsLoading(true);
|
105
|
+
setIsFieldDisabled(true);
|
106
|
+
|
107
|
+
getSearchedBranch();
|
108
|
+
}
|
109
|
+
}, [channel, selectedSuggestion]);
|
110
|
+
|
111
|
+
const getSuggestions = useCallback(
|
112
|
+
(searchedAddress) => {
|
113
|
+
setSelectedSuggestion(null);
|
114
|
+
setRequestIsLoading(true);
|
115
|
+
fetchAddressSuggestions({ searchedAddress })
|
116
|
+
.then((suggestions) => {
|
117
|
+
setRequestIsLoading(false);
|
118
|
+
setSuggestions(suggestions)
|
119
|
+
})
|
120
|
+
.catch((err) => notify(err.message, 'error'))
|
121
|
+
}, [])
|
122
|
+
|
123
|
+
const debouncedGetSuggestions = useCallback(debounce(({ value }) => {
|
124
|
+
setSearchedAddress(value);
|
125
|
+
getSuggestions(value);
|
126
|
+
}, DEBOUNCE_DELAY), []);
|
127
|
+
|
128
|
+
const onSuggestionSelected = useCallback((_, { suggestion }) => {
|
129
|
+
if (suggestion.id) {
|
130
|
+
if (handleSuggestionSelected) {
|
131
|
+
handleSuggestionSelected();
|
132
|
+
}
|
133
|
+
setSelectedSuggestion(suggestion.id);
|
134
|
+
}
|
135
|
+
}, [channel]);
|
136
|
+
|
137
|
+
const onSuggestionsClear = useCallback(() => setSuggestions([]), []);
|
138
|
+
|
139
|
+
const getSuggestionValue = useCallback((suggestion) => suggestion.address, []);
|
140
|
+
|
141
|
+
const renderSuggestion = useCallback((suggestion) => (
|
142
|
+
<span className="react-autosuggest_span">{suggestion.address}</span>
|
143
|
+
), []);
|
144
|
+
|
145
|
+
const onInputChange = useCallback((_, { newValue }) => {
|
146
|
+
if (!newValue) {
|
147
|
+
dispatch(setSearchedPolygonBranchesIDs(null));
|
148
|
+
}
|
149
|
+
setSearchedAddress(newValue);
|
150
|
+
setSelectedSuggestion(null);
|
151
|
+
}, []);
|
152
|
+
|
153
|
+
const onInputClick = useCallback(() => {
|
154
|
+
if (handleInputClick) {
|
155
|
+
handleInputClick();
|
156
|
+
}
|
157
|
+
}, [handleInputClick]);
|
158
|
+
|
159
|
+
const additionalInputProps = useMemo(() => ({
|
160
|
+
placeholder,
|
161
|
+
name: 'react-autosuggest__input',
|
162
|
+
id: 'react-autosuggest__input',
|
163
|
+
value: searchedAddress,
|
164
|
+
onClick: onInputClick,
|
165
|
+
onChange: onInputChange,
|
166
|
+
...((isFieldDisabled && requestIsLoading) ? { disabled: true } : {}),
|
167
|
+
}), [searchedAddress, isFieldDisabled, requestIsLoading, onInputClick]);
|
168
|
+
|
169
|
+
const SearchLoadingIcon = CustomLoadingIcon || LoadingIcon;
|
170
|
+
|
171
|
+
return (
|
172
|
+
<>
|
173
|
+
<input type="hidden" name="location" value={searchedAddress} />
|
174
|
+
<Autosuggest
|
175
|
+
suggestions={suggestions}
|
176
|
+
onSuggestionsClearRequested={onSuggestionsClear}
|
177
|
+
onSuggestionsFetchRequested={debouncedGetSuggestions}
|
178
|
+
onSuggestionSelected={onSuggestionSelected}
|
179
|
+
getSuggestionValue={getSuggestionValue}
|
180
|
+
renderSuggestion={renderSuggestion}
|
181
|
+
inputProps={{
|
182
|
+
...additionalInputProps,
|
183
|
+
...inputProps,
|
184
|
+
}}
|
185
|
+
highlightFirstSuggestion
|
186
|
+
/>
|
187
|
+
{requestIsLoading ? <SearchLoadingIcon /> : <Icon />}
|
188
|
+
</>
|
189
|
+
);
|
190
|
+
};
|
191
|
+
|
192
|
+
BranchesPolygonSearchInput.propTypes = {
|
193
|
+
placeholder: PropTypes.string,
|
194
|
+
inputProps: PropTypes.object,
|
195
|
+
channel: PropTypes.string,
|
196
|
+
isDepartmentSearch: PropTypes.bool,
|
197
|
+
icon: PropTypes.func,
|
198
|
+
loadingIcon: PropTypes.func,
|
199
|
+
handleInputClick: PropTypes.func,
|
200
|
+
handleSuggestionSelected: PropTypes.func,
|
201
|
+
hiddenBranchIds: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
202
|
+
};
|
203
|
+
|
204
|
+
BranchesPolygonSearchInput.defaultProps = {
|
205
|
+
placeholder: '',
|
206
|
+
inputProps: {},
|
207
|
+
channel: null,
|
208
|
+
isDepartmentSearch: true,
|
209
|
+
icon: () => <div/>,
|
210
|
+
loadingIcon: null,
|
211
|
+
handleInputClick: null,
|
212
|
+
handleSuggestionSelected: null,
|
213
|
+
hiddenBranchIds: [],
|
214
|
+
};
|
215
|
+
|
216
|
+
export default BranchesPolygonSearchInput;
|
package/branches/index.js
CHANGED
@@ -3,6 +3,7 @@ import BranchMap from './branch-map/branch-map.component';
|
|
3
3
|
import BranchStreetview from './branch-streetview/branch-streetview.component';
|
4
4
|
import BranchesSearchForm from './branches-search-form/branches-search-form.component';
|
5
5
|
import BranchesSearchInput from './branches-search-form/branches-search-input.component';
|
6
|
+
import BranchesPolygonSearchInput from './branches-search-form/branches-polygon-search-input.component';
|
6
7
|
|
7
8
|
export {
|
8
9
|
BranchesMap,
|
@@ -10,4 +11,5 @@ export {
|
|
10
11
|
BranchStreetview,
|
11
12
|
BranchesSearchForm,
|
12
13
|
BranchesSearchInput,
|
14
|
+
BranchesPolygonSearchInput
|
13
15
|
};
|
package/package.json
CHANGED
@@ -4,6 +4,8 @@ const INITIAL_STATE = {
|
|
4
4
|
branches: [],
|
5
5
|
branchesSearch: '',
|
6
6
|
pagination: {},
|
7
|
+
searchedBranchesIDs: null,
|
8
|
+
branchDepartmentFilter: 'all',
|
7
9
|
};
|
8
10
|
|
9
11
|
const branchesReducer = (state = INITIAL_STATE, action) => {
|
@@ -23,6 +25,16 @@ const branchesReducer = (state = INITIAL_STATE, action) => {
|
|
23
25
|
...state,
|
24
26
|
pagination: action.payload,
|
25
27
|
};
|
28
|
+
case BranchesActionTypes.SET_SEARCHED_POLYGON_BRANCHES_IDS:
|
29
|
+
return {
|
30
|
+
...state,
|
31
|
+
searchedBranchesIDs: action.payload,
|
32
|
+
};
|
33
|
+
case BranchesActionTypes.SET_BRANCH_DEPARTMENT_FILTER:
|
34
|
+
return {
|
35
|
+
...state,
|
36
|
+
branchDepartmentFilter: action.payload,
|
37
|
+
};
|
26
38
|
default: return state;
|
27
39
|
}
|
28
40
|
};
|
@@ -0,0 +1,78 @@
|
|
1
|
+
const ERROR_MESSAGE_SERVICE_NOT_AVAILABLE = 'Sorry, the service is temporarily unavailable. Please try again later.';
|
2
|
+
const ERROR_MESSAGE_NOT_FOUND_POSTCODE = 'Sorry, no postcode was found for the searched address.';
|
3
|
+
const ERROR_DEFAULT_MESSAGE = 'Sorry, something went wrong. Please try again later.';
|
4
|
+
|
5
|
+
const handleRequest = async(response) => {
|
6
|
+
if (response.ok) {
|
7
|
+
const responseData = await response.json();
|
8
|
+
return responseData;
|
9
|
+
} else {
|
10
|
+
throw new Error(ERROR_MESSAGE_SERVICE_NOT_AVAILABLE);
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
const fetchAddressSuggestions = ({ searchedAddress }) => fetch(`/address_lookup?autocomplete=${searchedAddress}`)
|
15
|
+
.then(handleRequest)
|
16
|
+
.then((data) => data?.suggestions
|
17
|
+
? data?.suggestions?.filter((_item, index) => index < 15)
|
18
|
+
: []
|
19
|
+
)
|
20
|
+
.catch((err) => {
|
21
|
+
console.log(err);
|
22
|
+
return err;
|
23
|
+
})
|
24
|
+
|
25
|
+
const fetchBranchDataByPostcode = ({
|
26
|
+
postcode,
|
27
|
+
usingPolygon = false,
|
28
|
+
channel,
|
29
|
+
isDepartmentSearch
|
30
|
+
}) => {
|
31
|
+
const polygonPrm = (usingPolygon && channel) ? '&using_polygon=true' : '';
|
32
|
+
const channelPrm = channel ? `&channel=${channel}` : '';
|
33
|
+
const departmentPrm = (isDepartmentSearch && channel) ? `&department=true` : '';
|
34
|
+
|
35
|
+
return fetch(`/postcode_branch_lookup?postcode=${postcode}${departmentPrm}${channelPrm}${polygonPrm}`)
|
36
|
+
.then(handleRequest)
|
37
|
+
.catch((err) => {
|
38
|
+
console.log(err);
|
39
|
+
return err;
|
40
|
+
})
|
41
|
+
}
|
42
|
+
|
43
|
+
const fetchDetailedAddressData = ({ addressID }) => fetch(`/address_lookup?id=${addressID}`)
|
44
|
+
.then(handleRequest)
|
45
|
+
.catch((err) => {
|
46
|
+
console.log(err);
|
47
|
+
return err;
|
48
|
+
});
|
49
|
+
|
50
|
+
const fetchBranchDataBySearchedAddress = ({
|
51
|
+
addressID,
|
52
|
+
usingPolygon,
|
53
|
+
channel,
|
54
|
+
isDepartmentSearch,
|
55
|
+
}) => fetchDetailedAddressData({ addressID })
|
56
|
+
.then((data) => {
|
57
|
+
if (data?.postcode) {
|
58
|
+
return fetchBranchDataByPostcode({
|
59
|
+
postcode: data.postcode,
|
60
|
+
usingPolygon,
|
61
|
+
channel,
|
62
|
+
isDepartmentSearch
|
63
|
+
})
|
64
|
+
} else {
|
65
|
+
throw new Error(ERROR_MESSAGE_NOT_FOUND_POSTCODE);
|
66
|
+
}
|
67
|
+
})
|
68
|
+
.then((data) => data?.branch && data?.branch?.id ? [data?.branch?.id.toString()] : [])
|
69
|
+
.catch((err) => {
|
70
|
+
console.log(err);
|
71
|
+
return err;
|
72
|
+
});
|
73
|
+
|
74
|
+
export default {
|
75
|
+
fetchAddressSuggestions,
|
76
|
+
fetchBranchDataBySearchedAddress,
|
77
|
+
ERROR_DEFAULT_MESSAGE,
|
78
|
+
};
|