abmp-npm 1.8.3 → 1.8.5
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/index.js +1 -0
- package/backend/personal-details-form-methods.js +399 -0
- package/package.json +3 -1
- package/pages/PersonalDetailsForm.js +2008 -0
- package/pages/index.js +1 -1
- package/public/Utils/formValidation.js +32 -0
- package/public/consts.js +21 -0
- package/public/index.js +2 -1
- package/pages/Profile.js +0 -348
- package/public/utils.js +0 -57
package/backend/index.js
CHANGED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
const { crm } = require('@wix/crm');
|
|
2
|
+
const { items } = require('@wix/data');
|
|
3
|
+
const { auth } = require('@wix/essentials');
|
|
4
|
+
const { members: membersSDK, authentication } = require('@wix/members');
|
|
5
|
+
const { encode } = require('ngeohash');
|
|
6
|
+
|
|
7
|
+
const { COLLECTIONS } = require('../public/consts');
|
|
8
|
+
|
|
9
|
+
const { wixData } = require('./elevated-modules');
|
|
10
|
+
const { findMemberByWixDataId } = require('./members-data-methods');
|
|
11
|
+
const { retrieveAllItems } = require('./utils');
|
|
12
|
+
|
|
13
|
+
// Elevated functions
|
|
14
|
+
const elevatedGetCurrentMember = auth.elevate(authentication.currentMember.getCurrentMember);
|
|
15
|
+
const elevatedUpdateContact = auth.elevate(crm.contacts.updateContact);
|
|
16
|
+
|
|
17
|
+
// Constants
|
|
18
|
+
const MEMBER_ACTIONS = {
|
|
19
|
+
DROP: 'drop',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const PRECISION = 3;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Helper functions
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
function generateGeoHash(addresses) {
|
|
29
|
+
const geohash = addresses
|
|
30
|
+
?.filter(address => (isNaN(address?.latitude) && isNaN(address?.longitude) ? false : address))
|
|
31
|
+
?.map(address => encode(address.latitude, address.longitude, PRECISION));
|
|
32
|
+
return geohash && geohash.length > 0 ? geohash : [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatDateToMonthYear(dateString) {
|
|
36
|
+
if (!dateString) return '';
|
|
37
|
+
|
|
38
|
+
const date = new Date(dateString);
|
|
39
|
+
if (isNaN(date.getTime())) return '';
|
|
40
|
+
const options = { year: 'numeric', month: 'long' };
|
|
41
|
+
return date.toLocaleDateString(undefined, options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasStudentMembership(member, checkAssociation = false) {
|
|
45
|
+
const memberships = member?.memberships;
|
|
46
|
+
if (!Array.isArray(memberships)) return false;
|
|
47
|
+
|
|
48
|
+
const SITE_ASSOCIATION = 'ABMP';
|
|
49
|
+
const MEMBERSHIPS_TYPES = {
|
|
50
|
+
STUDENT: 'Student',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return memberships.some(membership => {
|
|
54
|
+
const isStudent = membership.membertype === MEMBERSHIPS_TYPES.STUDENT;
|
|
55
|
+
const hasCorrectAssociation = !checkAssociation || membership.association === SITE_ASSOCIATION;
|
|
56
|
+
return isStudent && hasCorrectAssociation;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isStudent(member) {
|
|
61
|
+
return hasStudentMembership(member, false);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getAddressDisplayOptions(member) {
|
|
65
|
+
const addresses = member.addresses || [];
|
|
66
|
+
const displayOptions = member.addressDisplayOption || [];
|
|
67
|
+
if (addresses.length === 1 && addresses[0].key) {
|
|
68
|
+
return [{ key: addresses[0].key, isMain: true }];
|
|
69
|
+
}
|
|
70
|
+
return displayOptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Retrieves all interests from the database
|
|
75
|
+
* @returns {Promise<Array<string>>} Sorted array of interest titles
|
|
76
|
+
*/
|
|
77
|
+
const getInterestAll = async () => {
|
|
78
|
+
try {
|
|
79
|
+
const interests = await retrieveAllItems(COLLECTIONS.INTERESTS);
|
|
80
|
+
const titles = interests.map(x => x.title);
|
|
81
|
+
|
|
82
|
+
// Sort the interests alphabetically (case-insensitive)
|
|
83
|
+
return titles.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error('Error in getInterestAll:', e);
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get member by slug with various filtering options
|
|
92
|
+
* @param {Object} options - Query options
|
|
93
|
+
* @param {string} options.slug - The URL slug to search for
|
|
94
|
+
* @param {boolean} options.excludeDropped - Whether to exclude dropped members
|
|
95
|
+
* @param {boolean} options.excludeSearchedMember - Whether to exclude a specific member
|
|
96
|
+
* @param {string} options.memberId - Member ID to exclude
|
|
97
|
+
* @returns {Promise<Object|null>} Member data or null
|
|
98
|
+
*/
|
|
99
|
+
async function getMemberBySlug({
|
|
100
|
+
slug,
|
|
101
|
+
excludeDropped = true,
|
|
102
|
+
excludeSearchedMember = false,
|
|
103
|
+
memberId = null,
|
|
104
|
+
}) {
|
|
105
|
+
if (!slug) return null;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
let query = wixData.query(COLLECTIONS.MEMBERS_DATA).contains('url', slug);
|
|
109
|
+
|
|
110
|
+
if (excludeDropped) {
|
|
111
|
+
query = query.ne('action', 'drop');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (excludeSearchedMember && memberId) {
|
|
115
|
+
query = query.ne('memberId', memberId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = await query.find();
|
|
119
|
+
const matchingMembers = result.items.filter(
|
|
120
|
+
item => item.url && item.url.toLowerCase() === slug.toLowerCase()
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (matchingMembers.length > 1) {
|
|
124
|
+
console.warn(`Multiple members found with same slug ${slug}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return matchingMembers[0] || null;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Error getting member by slug:', error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Checks if a URL already exists for a different member
|
|
136
|
+
* @param {string} url - The URL to check
|
|
137
|
+
* @param {string} excludeMemberId - Member ID to exclude from check
|
|
138
|
+
* @returns {Promise<boolean>} True if URL exists for another member
|
|
139
|
+
*/
|
|
140
|
+
async function urlExists(url, excludeMemberId) {
|
|
141
|
+
if (!url) return false;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const member = await getMemberBySlug({
|
|
145
|
+
slug: url,
|
|
146
|
+
excludeDropped: true,
|
|
147
|
+
excludeSearchedMember: true,
|
|
148
|
+
memberId: excludeMemberId,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return member !== null;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Error checking URL existence:', error);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Validates member token and returns member data
|
|
160
|
+
* @param {string} memberIdInput - The member ID to validate
|
|
161
|
+
* @returns {Promise<Object>} Object with memberData and isValid flag
|
|
162
|
+
*/
|
|
163
|
+
const validateMemberToken = async memberIdInput => {
|
|
164
|
+
const invalidTokenResponse = { memberData: null, isValid: false };
|
|
165
|
+
|
|
166
|
+
if (!memberIdInput) {
|
|
167
|
+
return invalidTokenResponse;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const member = await elevatedGetCurrentMember();
|
|
172
|
+
|
|
173
|
+
if (!member || !member.contactId) {
|
|
174
|
+
console.log('member not found from getCurrentMember for memberIdInput', memberIdInput);
|
|
175
|
+
return invalidTokenResponse;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const result = await wixData
|
|
179
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
180
|
+
.eq('contactId', member.contactId)
|
|
181
|
+
.find();
|
|
182
|
+
|
|
183
|
+
console.log('items', result.items[0]);
|
|
184
|
+
console.log('member.contactId', member.contactId);
|
|
185
|
+
|
|
186
|
+
if (!result.items[0]?._id) {
|
|
187
|
+
const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members?] - There is no match in DB for currentMember: ${JSON.stringify({ memberIdInput, currentMemberId: member.contactId })}`;
|
|
188
|
+
console.error(errorMessage);
|
|
189
|
+
throw new Error('CORRUPTED_MEMBER_DATA');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${result.items[0]?._id}`);
|
|
193
|
+
|
|
194
|
+
const memberData = result.items[0];
|
|
195
|
+
memberData.memberships = memberData.memberships.map(membership => ({
|
|
196
|
+
...membership,
|
|
197
|
+
membersince: formatDateToMonthYear(membership.membersince),
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const savedMemberId = memberData?._id;
|
|
201
|
+
const isValid = savedMemberId === memberIdInput;
|
|
202
|
+
|
|
203
|
+
if (!savedMemberId || !isValid) {
|
|
204
|
+
return invalidTokenResponse;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (memberData.action === MEMBER_ACTIONS.DROP) {
|
|
208
|
+
return invalidTokenResponse;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
|
|
212
|
+
console.log('memberData', memberData);
|
|
213
|
+
memberData.isStudent = isStudent(memberData);
|
|
214
|
+
|
|
215
|
+
return { memberData, isValid };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error in validateMemberToken:', error);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Checks if URL is unique for the member
|
|
224
|
+
* @param {string} url - The URL to check
|
|
225
|
+
* @param {string} memberId - The member ID
|
|
226
|
+
* @returns {Promise<Object>} Object with isUnique flag
|
|
227
|
+
*/
|
|
228
|
+
const checkUrlUniqueness = async (url, memberId) => {
|
|
229
|
+
if (!url || !memberId) {
|
|
230
|
+
throw new Error('Missing required parameters: url and memberId are required');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const trimmedUrl = url.trim();
|
|
235
|
+
const exists = await urlExists(trimmedUrl, memberId);
|
|
236
|
+
|
|
237
|
+
return { isUnique: !exists };
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error('Error checking URL uniqueness:', error);
|
|
240
|
+
throw new Error(`Failed to check URL uniqueness: ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Updates contact email in Wix CRM
|
|
246
|
+
* @param {string} contactId - The contact ID
|
|
247
|
+
* @param {string} newEmail - The new email
|
|
248
|
+
*/
|
|
249
|
+
async function updateContactEmail(contactId, newEmail) {
|
|
250
|
+
if (!newEmail) {
|
|
251
|
+
throw new Error('New email is required');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const contact = await crm.contacts.getContact(contactId);
|
|
256
|
+
const currentInfo = contact.info || {};
|
|
257
|
+
|
|
258
|
+
await elevatedUpdateContact(contactId, {
|
|
259
|
+
info: {
|
|
260
|
+
...currentInfo,
|
|
261
|
+
emails: {
|
|
262
|
+
items: [
|
|
263
|
+
{
|
|
264
|
+
email: newEmail,
|
|
265
|
+
primary: true,
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('Error updating contact email:', error);
|
|
273
|
+
throw new Error(`Failed to update contact email: ${error.message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Updates contact names in Wix CRM
|
|
279
|
+
* @param {string} contactId - The contact ID
|
|
280
|
+
* @param {string} firstName - The new first name
|
|
281
|
+
* @param {string} lastName - The new last name
|
|
282
|
+
*/
|
|
283
|
+
async function updateContactNames(contactId, firstName, lastName) {
|
|
284
|
+
if (!firstName && !lastName) {
|
|
285
|
+
throw new Error('At least one name field is required');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const contact = await crm.contacts.getContact(contactId);
|
|
290
|
+
const currentInfo = contact.info || {};
|
|
291
|
+
|
|
292
|
+
await elevatedUpdateContact(contactId, {
|
|
293
|
+
info: {
|
|
294
|
+
...currentInfo,
|
|
295
|
+
name: {
|
|
296
|
+
first: firstName || currentInfo?.name?.first || '',
|
|
297
|
+
last: lastName || currentInfo?.name?.last || '',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('Error updating contact names:', error);
|
|
303
|
+
throw new Error(`Failed to update contact names: ${error.message}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update fields if they have changed
|
|
309
|
+
*/
|
|
310
|
+
const updateIfChanged = (existingValues, newValues, updater, argsBuilder) => {
|
|
311
|
+
const hasChanged = !existingValues.every((val, idx) => val === newValues[idx]);
|
|
312
|
+
if (!hasChanged) return null;
|
|
313
|
+
return updater(...argsBuilder(newValues));
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Updates member contact information in CRM if fields have changed
|
|
318
|
+
* @param {string} id - Member ID
|
|
319
|
+
* @param {Object} data - New member data
|
|
320
|
+
*/
|
|
321
|
+
const updateMemberContactInfo = async (id, data) => {
|
|
322
|
+
const existing = await findMemberByWixDataId(id);
|
|
323
|
+
const { contactId } = existing;
|
|
324
|
+
|
|
325
|
+
const updateConfig = [
|
|
326
|
+
{
|
|
327
|
+
fields: ['contactFormEmail'],
|
|
328
|
+
updater: updateContactEmail,
|
|
329
|
+
args: ([email]) => [contactId, email],
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
fields: ['firstName', 'lastName'],
|
|
333
|
+
updater: updateContactNames,
|
|
334
|
+
args: ([firstName, lastName]) => [contactId, firstName, lastName],
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
const updatePromises = updateConfig
|
|
339
|
+
.map(({ fields, updater, args }) => {
|
|
340
|
+
const existingValues = fields.map(field => existing[field]);
|
|
341
|
+
const newValues = fields.map(field => data[field]);
|
|
342
|
+
return updateIfChanged(existingValues, newValues, updater, args);
|
|
343
|
+
})
|
|
344
|
+
.filter(Boolean);
|
|
345
|
+
|
|
346
|
+
await Promise.all(updatePromises);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Saves registration data for a member
|
|
351
|
+
* @param {Object} data - Member data to save
|
|
352
|
+
* @param {string} id - Member ID
|
|
353
|
+
* @returns {Promise<Object>} Result object with type and data
|
|
354
|
+
*/
|
|
355
|
+
const saveRegistrationData = async (data, id) => {
|
|
356
|
+
try {
|
|
357
|
+
console.log(' saveRegistrationData data._id', data._id);
|
|
358
|
+
console.log(' saveRegistrationData id', id);
|
|
359
|
+
|
|
360
|
+
if (data._id !== id) return { type: 'notAuthorized' };
|
|
361
|
+
|
|
362
|
+
if (data.url) {
|
|
363
|
+
const isDuplicate = await urlExists(data.url, data.memberId);
|
|
364
|
+
|
|
365
|
+
if (isDuplicate) {
|
|
366
|
+
return {
|
|
367
|
+
type: 'error',
|
|
368
|
+
error: 'URL slug is already taken. Please choose a different one.',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (data.addresses && Array.isArray(data.addresses)) {
|
|
374
|
+
data.locHash = generateGeoHash(data.addresses);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await updateMemberContactInfo(id, data);
|
|
378
|
+
|
|
379
|
+
const saveData = await wixData.update(COLLECTIONS.MEMBERS_DATA, data);
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
type: 'success',
|
|
383
|
+
saveData,
|
|
384
|
+
};
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(error);
|
|
387
|
+
return {
|
|
388
|
+
type: 'error',
|
|
389
|
+
error,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
module.exports = {
|
|
395
|
+
getInterestAll,
|
|
396
|
+
validateMemberToken,
|
|
397
|
+
checkUrlUniqueness,
|
|
398
|
+
saveRegistrationData,
|
|
399
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abmp-npm",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"@wix/essentials": "^0.1.28",
|
|
33
33
|
"@wix/members": "^1.0.330",
|
|
34
34
|
"@wix/site-location": "^1.31.0",
|
|
35
|
+
"@wix/site-storage": "^1.22.0",
|
|
35
36
|
"@wix/site-window": "^1.44.0",
|
|
37
|
+
"lodash": "^4.17.21",
|
|
36
38
|
"ngeohash": "^0.6.3",
|
|
37
39
|
"phone": "^3.1.67",
|
|
38
40
|
"psdev-utils": "1.1.1"
|