abmp-npm 1.8.1 → 1.8.2
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/backend/cms-data-methods.js +209 -0
- package/backend/consts.js +7 -0
- package/backend/forms-methods.js +1 -1
- package/backend/index.js +1 -0
- package/backend/members-data-methods.js +1 -1
- package/backend/search-filters-methods.js +121 -0
- package/backend/utils.js +11 -0
- package/eslint.config.js +8 -1
- package/package.json +6 -2
- package/pages/Home.js +763 -0
- package/pages/Profile.js +12 -14
- package/pages/index.js +1 -0
- package/public/Utils/homePage.js +763 -0
- package/public/Utils/sharedUtils.js +162 -0
- package/public/consts.js +70 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const geohash = require('ngeohash');
|
|
2
|
+
|
|
3
|
+
const { COLLECTIONS, MEMBERS_FIELDS } = require('../public/consts.js');
|
|
4
|
+
const { findMainAddress } = require('../public/Utils/sharedUtils.js');
|
|
5
|
+
const { calculateDistance, shuffleArray } = require('../public/Utils/sharedUtils.js');
|
|
6
|
+
|
|
7
|
+
const { PRECISION, MAX__MEMBERS_SEARCH_RESULTS, WIX_QUERY_MAX_LIMIT } = require('./consts.js');
|
|
8
|
+
const { wixData } = require('./elevated-modules');
|
|
9
|
+
|
|
10
|
+
function buildMembersSearchQuery(data) {
|
|
11
|
+
console.log('data: ', JSON.stringify(data));
|
|
12
|
+
const { filter, isSearchingNearby, includeStudents = false } = data;
|
|
13
|
+
const isUserLocationEnabled = filter.latitude !== 0 || filter.longitude !== 0;
|
|
14
|
+
filter.searchText = filter.searchText || '';
|
|
15
|
+
filter.stateSearch = filter.stateSearch || '';
|
|
16
|
+
filter.practiceAreasSearch = filter.practiceAreasSearch || '';
|
|
17
|
+
filter.practiceAreas = filter.practiceAreas || [];
|
|
18
|
+
filter.state = filter.state || [];
|
|
19
|
+
filter.citySearch = filter.citySearch || '';
|
|
20
|
+
filter.city = filter.city || [];
|
|
21
|
+
filter.latitude = filter.latitude || 0;
|
|
22
|
+
filter.longitude = filter.longitude || 0;
|
|
23
|
+
filter.postalcode = filter.postalcode || '';
|
|
24
|
+
return {
|
|
25
|
+
get: () => {
|
|
26
|
+
let query = wixData
|
|
27
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
28
|
+
.ne('optOut', true)
|
|
29
|
+
.ne('action', 'drop')
|
|
30
|
+
.eq('isVisible', true);
|
|
31
|
+
let filterConfig = [
|
|
32
|
+
{
|
|
33
|
+
filterKey: 'practiceAreas',
|
|
34
|
+
queryMethod: 'hasSome',
|
|
35
|
+
queryField: 'areasOfPractices',
|
|
36
|
+
condition: value => value && value.length > 0,
|
|
37
|
+
fallback: {
|
|
38
|
+
filterKey: 'practiceAreasSearch',
|
|
39
|
+
queryMethod: 'contains',
|
|
40
|
+
queryField: 'areasOfPractices',
|
|
41
|
+
condition: value => value && value.trim() !== '',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
filterKey: 'postalcode',
|
|
46
|
+
queryMethod: 'contains',
|
|
47
|
+
queryField: 'addresses.postalcode',
|
|
48
|
+
condition: value => value && value.trim() !== '',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
filterKey: 'state',
|
|
52
|
+
queryMethod: 'hasSome',
|
|
53
|
+
queryField: 'addresses.state',
|
|
54
|
+
condition: value => value && value.length > 0,
|
|
55
|
+
fallback: {
|
|
56
|
+
filterKey: 'stateSearch',
|
|
57
|
+
queryMethod: 'contains',
|
|
58
|
+
queryField: 'addresses.state',
|
|
59
|
+
condition: value => value && value.trim() !== '',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
filterKey: 'city',
|
|
64
|
+
queryMethod: 'hasSome',
|
|
65
|
+
queryField: 'addresses.city',
|
|
66
|
+
condition: value => value && value.length > 0,
|
|
67
|
+
fallback: {
|
|
68
|
+
filterKey: 'citySearch',
|
|
69
|
+
queryMethod: 'contains',
|
|
70
|
+
queryField: 'addresses.city',
|
|
71
|
+
condition: value => value && value.trim() !== '',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
//Ignore state, city and postal code when isSearchingNearby is true
|
|
76
|
+
if (isSearchingNearby) {
|
|
77
|
+
filterConfig = filterConfig.filter(
|
|
78
|
+
config => !['state', 'city', 'postalcode'].includes(config.filterKey)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const applyFilterToQuery = (query, config, filter) => {
|
|
82
|
+
const filterValue = filter[config.filterKey];
|
|
83
|
+
if (config.condition(filterValue)) {
|
|
84
|
+
return query[config.queryMethod](config.queryField, filterValue);
|
|
85
|
+
} else if (config.fallback) {
|
|
86
|
+
return applyFilterToQuery(query, config.fallback, filter);
|
|
87
|
+
}
|
|
88
|
+
return query;
|
|
89
|
+
};
|
|
90
|
+
// Apply filters using the configuration
|
|
91
|
+
filterConfig.forEach(config => {
|
|
92
|
+
query = applyFilterToQuery(query, config, filter);
|
|
93
|
+
});
|
|
94
|
+
if (isUserLocationEnabled && isSearchingNearby) {
|
|
95
|
+
const userGeohash = geohash.encode(filter.latitude, filter.longitude, PRECISION);
|
|
96
|
+
const neighborGeohashes = geohash.neighbors(userGeohash);
|
|
97
|
+
const geohashList = [userGeohash, ...neighborGeohashes];
|
|
98
|
+
query = query.hasSome('locHash', geohashList);
|
|
99
|
+
}
|
|
100
|
+
if (filter.searchText.trim() !== '') {
|
|
101
|
+
query = query.contains('fullName', filter.searchText);
|
|
102
|
+
}
|
|
103
|
+
if (!includeStudents) {
|
|
104
|
+
query = query.ne('memberships.membertype', 'Student');
|
|
105
|
+
}
|
|
106
|
+
return query;
|
|
107
|
+
},
|
|
108
|
+
run: async query => {
|
|
109
|
+
const baseQuery = query.ascending('firstName').fields(...Object.values(MEMBERS_FIELDS));
|
|
110
|
+
const getRandomSkip = totalCount => {
|
|
111
|
+
let randomSkip = 0;
|
|
112
|
+
if (totalCount > MAX__MEMBERS_SEARCH_RESULTS) {
|
|
113
|
+
const maxSkip = totalCount - MAX__MEMBERS_SEARCH_RESULTS;
|
|
114
|
+
randomSkip = Math.floor(Math.random() * (maxSkip + 1));
|
|
115
|
+
}
|
|
116
|
+
return randomSkip;
|
|
117
|
+
};
|
|
118
|
+
const getResult = async query => {
|
|
119
|
+
if (isSearchingNearby) {
|
|
120
|
+
return fetchAllItemsInParallel(baseQuery);
|
|
121
|
+
}
|
|
122
|
+
const totalCount = await query.count();
|
|
123
|
+
const randomSkip = getRandomSkip(totalCount);
|
|
124
|
+
const result = await query
|
|
125
|
+
.skip(randomSkip)
|
|
126
|
+
.limit(MAX__MEMBERS_SEARCH_RESULTS)
|
|
127
|
+
.find({ omitTotalCount: true });
|
|
128
|
+
|
|
129
|
+
// Shuffle the result items for additional randomization
|
|
130
|
+
return {
|
|
131
|
+
...result,
|
|
132
|
+
items: shuffleArray(result.items),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const result = await getResult(baseQuery);
|
|
137
|
+
if (isUserLocationEnabled) {
|
|
138
|
+
const withDistances = result.items.map(item => ({
|
|
139
|
+
...item,
|
|
140
|
+
distance: calculateDistance(
|
|
141
|
+
{
|
|
142
|
+
latitude: filter.latitude,
|
|
143
|
+
longitude: filter.longitude,
|
|
144
|
+
},
|
|
145
|
+
findMainAddress(item.addressDisplayOption, item.addresses)
|
|
146
|
+
),
|
|
147
|
+
}));
|
|
148
|
+
const resultWithDistances = {
|
|
149
|
+
...result,
|
|
150
|
+
items: withDistances,
|
|
151
|
+
};
|
|
152
|
+
if (isSearchingNearby) {
|
|
153
|
+
return {
|
|
154
|
+
...resultWithDistances,
|
|
155
|
+
items: withDistances
|
|
156
|
+
.filter(item => item.distance !== null)
|
|
157
|
+
.sort((a, b) => a.distance - b.distance)
|
|
158
|
+
.slice(0, MAX__MEMBERS_SEARCH_RESULTS),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return resultWithDistances;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Generic parallel fetch function for large datasets
|
|
169
|
+
async function fetchAllItemsInParallel(query) {
|
|
170
|
+
const batchSize = WIX_QUERY_MAX_LIMIT;
|
|
171
|
+
const allItems = [];
|
|
172
|
+
|
|
173
|
+
const firstResult = await query.skip(0).limit(batchSize).find();
|
|
174
|
+
|
|
175
|
+
const totalBatches = firstResult.totalPages;
|
|
176
|
+
allItems.push(...firstResult.items);
|
|
177
|
+
|
|
178
|
+
if (totalBatches > 1) {
|
|
179
|
+
// Create parallel promises for all remaining batches
|
|
180
|
+
const batchPromises = [];
|
|
181
|
+
for (let i = 1; i < totalBatches; i++) {
|
|
182
|
+
const skip = i * batchSize;
|
|
183
|
+
const promise = query
|
|
184
|
+
.skip(skip)
|
|
185
|
+
.limit(batchSize)
|
|
186
|
+
.find()
|
|
187
|
+
.then(result => result.items);
|
|
188
|
+
batchPromises.push(promise);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Execute all batches in parallel
|
|
192
|
+
const batchResults = await Promise.all(batchPromises);
|
|
193
|
+
for (const items of batchResults) {
|
|
194
|
+
if (items.length > 0) {
|
|
195
|
+
allItems.push(...items);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...firstResult,
|
|
202
|
+
items: allItems,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
buildMembersSearchQuery,
|
|
208
|
+
fetchAllItemsInParallel,
|
|
209
|
+
};
|
package/backend/consts.js
CHANGED
|
@@ -8,6 +8,13 @@ const CONFIG_KEYS = {
|
|
|
8
8
|
SITE_ASSOCIATION: 'SITE_ASSOCIATION',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const PRECISION = 3;
|
|
12
|
+
const MAX__MEMBERS_SEARCH_RESULTS = 120;
|
|
13
|
+
const WIX_QUERY_MAX_LIMIT = 1000;
|
|
14
|
+
|
|
11
15
|
module.exports = {
|
|
12
16
|
CONFIG_KEYS,
|
|
17
|
+
PRECISION,
|
|
18
|
+
MAX__MEMBERS_SEARCH_RESULTS,
|
|
19
|
+
WIX_QUERY_MAX_LIMIT,
|
|
13
20
|
};
|
package/backend/forms-methods.js
CHANGED
package/backend/index.js
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const { COLLECTIONS } = require('../public/consts');
|
|
2
|
+
|
|
3
|
+
const { buildMembersSearchQuery } = require('./cms-data-methods');
|
|
4
|
+
const { wixData } = require('./elevated-modules');
|
|
5
|
+
const { retrieveAllItems } = require('./utils');
|
|
6
|
+
|
|
7
|
+
const getCompiledFiltersOptions = () =>
|
|
8
|
+
wixData.get(COLLECTIONS.COMPILED_STATE_CITY_MAP, 'SINGLE_ITEM_ID');
|
|
9
|
+
|
|
10
|
+
const getNonCompiledFiltersOptions = async () => {
|
|
11
|
+
const [completeStateList, areasOfPracticesList, stateCityMapList] = await Promise.all([
|
|
12
|
+
getCompleteStateList(),
|
|
13
|
+
getAreasOfPracticeList(),
|
|
14
|
+
getStateCityMap(),
|
|
15
|
+
]);
|
|
16
|
+
return { completeStateList, areasOfPracticesList, stateCityMapList };
|
|
17
|
+
};
|
|
18
|
+
const filterProfiles = async data => {
|
|
19
|
+
const membersSearchQuery = buildMembersSearchQuery({ ...data, includeStudents: false });
|
|
20
|
+
const query = await membersSearchQuery.get();
|
|
21
|
+
return membersSearchQuery.run(query);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function getAreasOfPracticeList() {
|
|
25
|
+
const interestsData = await retrieveAllItems(COLLECTIONS.INTERESTS);
|
|
26
|
+
return interestsData.map(({ title }) => ({ label: title, value: title }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getStateCityMap() {
|
|
30
|
+
const getAllCitiesMap = async () => {
|
|
31
|
+
const totalCount = await wixData.query(COLLECTIONS.STATE_CITY_MAP).count();
|
|
32
|
+
const baseQuery = wixData.query(COLLECTIONS.STATE_CITY_MAP).limit(1000);
|
|
33
|
+
const batchedQueries = Array.from({ length: Math.ceil(totalCount / 1000) }, (_, i) =>
|
|
34
|
+
baseQuery.skip(i * 1000)
|
|
35
|
+
);
|
|
36
|
+
const allCities = await Promise.all(
|
|
37
|
+
batchedQueries.map(query => query.find({ omitTotalCount: true }).then(res => res.items))
|
|
38
|
+
);
|
|
39
|
+
return allCities.flat();
|
|
40
|
+
};
|
|
41
|
+
const allCities = await getAllCitiesMap();
|
|
42
|
+
const stateCityMap = new Map();
|
|
43
|
+
allCities.forEach(cityObj => {
|
|
44
|
+
const state = cityObj.stateText;
|
|
45
|
+
const city = cityObj.title;
|
|
46
|
+
const cityArray = stateCityMap.has(state) ? [...stateCityMap.get(state), city] : [city];
|
|
47
|
+
stateCityMap.set(state, cityArray);
|
|
48
|
+
});
|
|
49
|
+
return Object.fromEntries(stateCityMap);
|
|
50
|
+
}
|
|
51
|
+
//Besan comments on moved code: Below code assumes that Velo can cache value, however this isn't valid so it needs adjustment
|
|
52
|
+
// Cache for state list
|
|
53
|
+
let stateListCache = null;
|
|
54
|
+
let lastCacheTime = 0;
|
|
55
|
+
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
56
|
+
|
|
57
|
+
async function getCompleteStateList() {
|
|
58
|
+
// Check if we have a valid cache
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
if (stateListCache && now - lastCacheTime < CACHE_DURATION) {
|
|
61
|
+
return stateListCache;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 1. fire both queries at once:
|
|
65
|
+
const [stateRecords, memberStates] = await Promise.all([
|
|
66
|
+
retrieveAllItems(COLLECTIONS.STATE),
|
|
67
|
+
getMembersStateList(), //this method a
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const fullNameByCode = new Map(stateRecords.map(st => [st.title, st.name]));
|
|
71
|
+
|
|
72
|
+
const states = memberStates.map(({ value: code }) => ({
|
|
73
|
+
value: code,
|
|
74
|
+
label: fullNameByCode.get(code) || code,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
// Update cache
|
|
78
|
+
stateListCache = states;
|
|
79
|
+
lastCacheTime = now;
|
|
80
|
+
|
|
81
|
+
return states;
|
|
82
|
+
}
|
|
83
|
+
//Besan Comments on moved code: THIS WILL IGNORE SOME STATES VALUES while we should return all states AFAIU
|
|
84
|
+
async function getMembersStateList() {
|
|
85
|
+
try {
|
|
86
|
+
// Use a more efficient query to get only the addresses field
|
|
87
|
+
const results = await wixData
|
|
88
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
89
|
+
.fields('addresses')
|
|
90
|
+
.limit(1000)
|
|
91
|
+
.find();
|
|
92
|
+
|
|
93
|
+
// Extract all state codes from addresses
|
|
94
|
+
const allStates = new Set();
|
|
95
|
+
results.items.forEach(member => {
|
|
96
|
+
if (Array.isArray(member.addresses)) {
|
|
97
|
+
member.addresses.forEach(addr => {
|
|
98
|
+
if (addr.state) allStates.add(addr.state);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Convert to array and sort
|
|
104
|
+
const uniqueStates = Array.from(allStates).sort();
|
|
105
|
+
|
|
106
|
+
// Map to label/value format
|
|
107
|
+
return uniqueStates.map(stateCode => ({
|
|
108
|
+
label: stateCode,
|
|
109
|
+
value: stateCode,
|
|
110
|
+
}));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error in getMembersStateList:', error);
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
getCompiledFiltersOptions,
|
|
119
|
+
getNonCompiledFiltersOptions,
|
|
120
|
+
filterProfiles,
|
|
121
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -29,6 +29,17 @@ const getSiteConfigs = async configKey => {
|
|
|
29
29
|
return siteConfigs;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
const retrieveAllItems = async collectionName => {
|
|
33
|
+
let results = await wixData.query(collectionName).limit(1000).find();
|
|
34
|
+
let allItems = results.items;
|
|
35
|
+
while (results.hasNext()) {
|
|
36
|
+
results = await results.next();
|
|
37
|
+
allItems = allItems.concat(results.items);
|
|
38
|
+
}
|
|
39
|
+
return allItems;
|
|
40
|
+
};
|
|
41
|
+
|
|
32
42
|
module.exports = {
|
|
33
43
|
getSiteConfigs,
|
|
44
|
+
retrieveAllItems,
|
|
34
45
|
};
|
package/eslint.config.js
CHANGED
|
@@ -54,7 +54,13 @@ module.exports = [
|
|
|
54
54
|
alphabetize: { order: 'asc', caseInsensitive: true },
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
|
-
'import/no-unresolved':
|
|
57
|
+
'import/no-unresolved': [
|
|
58
|
+
'error',
|
|
59
|
+
{
|
|
60
|
+
commonjs: true,
|
|
61
|
+
caseSensitive: true,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
58
64
|
'import/no-duplicates': 'error',
|
|
59
65
|
'import/no-commonjs': 'off',
|
|
60
66
|
'import/no-dynamic-require': 'off',
|
|
@@ -88,6 +94,7 @@ module.exports = [
|
|
|
88
94
|
'import/resolver': {
|
|
89
95
|
node: {
|
|
90
96
|
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
97
|
+
moduleDirectory: ['node_modules', 'public', 'backend', 'pages'],
|
|
91
98
|
},
|
|
92
99
|
},
|
|
93
100
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abmp-npm",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"@eslint/js": "^9.12.0",
|
|
18
18
|
"eslint": "^9.12.0",
|
|
19
19
|
"eslint-config-prettier": "^9.1.0",
|
|
20
|
+
"eslint-import-resolver-node": "^0.3.9",
|
|
20
21
|
"eslint-plugin-import": "^2.30.0",
|
|
21
22
|
"eslint-plugin-prettier": "^5.2.1",
|
|
22
23
|
"eslint-plugin-promise": "^7.1.0",
|
|
@@ -30,7 +31,10 @@
|
|
|
30
31
|
"@wix/data": "^1.0.303",
|
|
31
32
|
"@wix/essentials": "^0.1.28",
|
|
32
33
|
"@wix/members": "^1.0.330",
|
|
34
|
+
"@wix/site-location": "^1.31.0",
|
|
33
35
|
"@wix/site-window": "^1.44.0",
|
|
34
|
-
"
|
|
36
|
+
"ngeohash": "^0.6.3",
|
|
37
|
+
"phone": "^3.1.67",
|
|
38
|
+
"psdev-utils": "1.1.1"
|
|
35
39
|
}
|
|
36
40
|
}
|