abmp-npm 2.0.76 → 2.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.
Files changed (99) hide show
  1. package/package.json +8 -1
  2. package/pages/Home.js +27 -55
  3. package/pages/index.js +0 -1
  4. package/public/Utils/personalDetailsUtils.js +5 -1
  5. package/public/Utils/sharedUtils.js +32 -0
  6. package/public/consts.js +0 -1
  7. package/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +0 -19
  8. package/.claude/worktrees/fix-save-button-disable/.prettierignore +0 -7
  9. package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +0 -16
  10. package/.claude/worktrees/fix-save-button-disable/README.md +0 -1
  11. package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +0 -136
  12. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +0 -124
  13. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
  14. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +0 -49
  15. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
  16. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +0 -194
  17. package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +0 -13
  18. package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +0 -251
  19. package/.claude/worktrees/fix-save-button-disable/backend/consts.js +0 -58
  20. package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +0 -67
  21. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +0 -222
  22. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +0 -39
  23. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +0 -5
  24. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +0 -213
  25. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +0 -41
  26. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +0 -161
  27. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +0 -151
  28. package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +0 -29
  29. package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +0 -58
  30. package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +0 -18
  31. package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +0 -52
  32. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +0 -86
  33. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +0 -3
  34. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +0 -35
  35. package/.claude/worktrees/fix-save-button-disable/backend/index.js +0 -19
  36. package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +0 -158
  37. package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +0 -27
  38. package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +0 -8
  39. package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +0 -54
  40. package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +0 -159
  41. package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +0 -111
  42. package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +0 -88
  43. package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +0 -789
  44. package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +0 -54
  45. package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +0 -3
  46. package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +0 -191
  47. package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +0 -128
  48. package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +0 -124
  49. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +0 -256
  50. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +0 -160
  51. package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +0 -34
  52. package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +0 -78
  53. package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +0 -134
  54. package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +0 -10
  55. package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +0 -46
  56. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +0 -251
  57. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +0 -433
  58. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +0 -606
  59. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +0 -378
  60. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +0 -174
  61. package/.claude/worktrees/fix-save-button-disable/backend/utils.js +0 -277
  62. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
  63. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +0 -159
  64. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +0 -201
  65. package/.claude/worktrees/fix-save-button-disable/eslint.config.js +0 -120
  66. package/.claude/worktrees/fix-save-button-disable/index.js +0 -5
  67. package/.claude/worktrees/fix-save-button-disable/package-lock.json +0 -12160
  68. package/.claude/worktrees/fix-save-button-disable/package.json +0 -60
  69. package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +0 -129
  70. package/.claude/worktrees/fix-save-button-disable/pages/Home.js +0 -779
  71. package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +0 -27
  72. package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +0 -29
  73. package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +0 -354
  74. package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +0 -39
  75. package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +0 -13
  76. package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +0 -46
  77. package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +0 -19
  78. package/.claude/worktrees/fix-save-button-disable/pages/index.js +0 -12
  79. package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +0 -2486
  80. package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +0 -798
  81. package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +0 -25
  82. package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +0 -235
  83. package/.claude/worktrees/fix-save-button-disable/public/consts.js +0 -113
  84. package/.claude/worktrees/fix-save-button-disable/public/index.js +0 -5
  85. package/.claude/worktrees/fix-save-button-disable/public/messages.js +0 -16
  86. package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +0 -43
  87. package/.husky/pre-commit +0 -19
  88. package/.prettierignore +0 -7
  89. package/.prettierrc.json +0 -16
  90. package/backend/__tests__/daily-pull-execution-check.test.js +0 -124
  91. package/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
  92. package/backend/__tests__/login-email-sync.test.js +0 -49
  93. package/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
  94. package/backend/__tests__/url-uniqueness.test.js +0 -194
  95. package/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
  96. package/dev-only-scripts/find-duplicate-ids.js +0 -159
  97. package/dev-only-scripts/find-duplicate-urls.js +0 -201
  98. package/eslint.config.js +0 -120
  99. package/pages/contactForLocation.js +0 -28
@@ -1,789 +0,0 @@
1
- const { COLLECTIONS } = require('../public/consts');
2
- const { isWixHostedImage, emailsMatch, normalizeEmail } = require('../public/Utils/sharedUtils');
3
-
4
- const { MEMBERSHIPS_TYPES } = require('./consts');
5
- const { createSiteContact } = require('./contacts-methods');
6
- const { MEMBER_ACTIONS } = require('./daily-pull/consts');
7
- const { wixData } = require('./elevated-modules');
8
- const { updateMemberContactInfo } = require('./member-contact-orchestration');
9
- const { createSiteMember, getCurrentMember } = require('./members-area-methods');
10
- const {
11
- chunkArray,
12
- normalizeUrlForComparison,
13
- sortByUrlCounterDescending,
14
- queryAllItems,
15
- generateGeoHash,
16
- searchAllItems,
17
- runIf,
18
- withTransientErrorRetry,
19
- } = require('./utils');
20
-
21
- /**
22
- * Retrieves member data by member ID
23
- * @param {string} memberId - The member ID to search for
24
- * @returns {Promise<Object|null>} - Member data or null if not found
25
- */
26
- async function findMemberByWixDataId(memberId) {
27
- if (!memberId) {
28
- throw new Error('Member ID is required');
29
- }
30
- try {
31
- const member = await wixData.get(COLLECTIONS.MEMBERS_DATA, memberId);
32
- return member;
33
- } catch (error) {
34
- throw new Error(`Failed to retrieve member data: ${error.message}`);
35
- }
36
- }
37
-
38
- const hasDifferentEmails = memberData =>
39
- Boolean(memberData.contactFormEmail) &&
40
- !emailsMatch(memberData.contactFormEmail, memberData.email);
41
-
42
- /**
43
- * Returns a shallow copy of a member record with its email fields normalized
44
- * (lowercased + trimmed). Wix CRM and our uniqueness checks treat emails
45
- * case-insensitively, so we persist them in canonical form to keep `.eq` lookups
46
- * reliable. Only rewrites string values that are actually present.
47
- * @param {Object} memberData
48
- * @returns {Object}
49
- */
50
- const normalizeMemberEmailFields = memberData => {
51
- if (!memberData || typeof memberData !== 'object') return memberData;
52
- const normalized = { ...memberData };
53
- if (typeof normalized.email === 'string') {
54
- normalized.email = normalizeEmail(normalized.email);
55
- }
56
- if (typeof normalized.contactFormEmail === 'string') {
57
- normalized.contactFormEmail = normalizeEmail(normalized.contactFormEmail);
58
- }
59
- return normalized;
60
- };
61
-
62
- /**
63
- * Whether a member's stored email fields are not already in canonical form
64
- * (lowercased + trimmed) and therefore need the normalization backfill.
65
- * @param {Object} member
66
- * @returns {boolean}
67
- */
68
- const memberNeedsEmailNormalization = member =>
69
- (typeof member.email === 'string' &&
70
- member.email.length > 0 &&
71
- member.email !== normalizeEmail(member.email)) ||
72
- (typeof member.contactFormEmail === 'string' &&
73
- member.contactFormEmail.length > 0 &&
74
- member.contactFormEmail !== normalizeEmail(member.contactFormEmail));
75
-
76
- async function createContactAndMemberIfNew(memberData) {
77
- if (!memberData) {
78
- throw new Error('Member data is required');
79
- }
80
- try {
81
- const toCreateMemberData = {
82
- firstName: memberData.firstName,
83
- lastName: memberData.lastName,
84
- email: memberData.email,
85
- phones: memberData.phones,
86
- contactFormEmail: memberData.contactFormEmail,
87
- };
88
- const needsWixMember = !memberData.wixMemberId;
89
- const needsWixContact = !memberData.wixContactId;
90
- const hasContactEmailDifferentFromLogin = hasDifferentEmails(memberData);
91
- console.log('needsWixMember', needsWixMember);
92
- console.log('needsWixContact', needsWixContact);
93
- console.log('hasContactEmailDifferentFromLogin', hasContactEmailDifferentFromLogin);
94
-
95
- const [newWixMemberId, createdWixContactId] = await Promise.all([
96
- runIf(needsWixMember, () => createSiteMember(toCreateMemberData)),
97
- runIf(needsWixContact && hasContactEmailDifferentFromLogin, () =>
98
- createSiteContact(toCreateMemberData)
99
- ),
100
- ]);
101
- const memberContactId = newWixMemberId;
102
- const newWixContactId = createdWixContactId || memberContactId;
103
- console.log('newWixMemberId', newWixMemberId);
104
- console.log('memberContactId', memberContactId);
105
- console.log('newWixContactId', newWixContactId);
106
- let memberDataWithContactId = {
107
- ...memberData,
108
- wixMemberId: newWixMemberId || memberData.wixMemberId,
109
- wixContactId: newWixContactId || memberData.wixContactId,
110
- };
111
- console.log('latest WixMemberId', memberDataWithContactId.wixMemberId);
112
- console.log('latest WixContactId', memberDataWithContactId.wixContactId);
113
- const updatedResult = await updateMember(memberDataWithContactId);
114
- memberDataWithContactId = {
115
- ...memberDataWithContactId,
116
- ...updatedResult,
117
- };
118
- return memberDataWithContactId;
119
- } catch (error) {
120
- console.error('Error creating contact and member if new:', error);
121
- throw new Error(`Failed to create contact and member if new: ${error.message}`);
122
- }
123
- }
124
-
125
- /** Performs bulk save operation for member data
126
- * @param { Array } memberDataList - Array of member data objects to save
127
- * @param { string } [collectionName] - The collection name to save the members to (default: COLLECTIONS.MEMBERS_DATA)
128
- * @returns { Promise < Object >} - Bulk save operation result
129
- */
130
- async function bulkSaveMembers(memberDataList, collectionName = COLLECTIONS.MEMBERS_DATA) {
131
- if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
132
- throw new Error('Invalid member data list provided for bulk save');
133
- }
134
-
135
- try {
136
- // Normalize email fields only for the members collection; other collections passed here
137
- // (e.g. staging copies) don't have these fields and must be saved untouched.
138
- const listToSave =
139
- collectionName === COLLECTIONS.MEMBERS_DATA
140
- ? memberDataList.map(normalizeMemberEmailFields)
141
- : memberDataList;
142
- // bulkSave all with batches of 1000 items as this is the Velo limit for bulkSave
143
- const batches = chunkArray(listToSave, 1000);
144
- return await Promise.all(batches.map(batch => wixData.bulkSave(collectionName, batch)));
145
- } catch (error) {
146
- console.error('Error bulk saving members:', error);
147
- throw new Error(`Bulk save failed: ${error.message}`);
148
- }
149
- }
150
-
151
- /**
152
- * Retrieves member data by member ID
153
- * @param {string} memberId - The member ID to search for
154
- * @returns {Promise<Object|null>} - Member data or null if not found
155
- */
156
- async function findMemberById(memberId) {
157
- if (!memberId) {
158
- throw new Error('Member ID is required');
159
- }
160
-
161
- try {
162
- const queryResult = await withTransientErrorRetry(() =>
163
- wixData.query(COLLECTIONS.MEMBERS_DATA).eq('memberId', memberId).limit(2).find()
164
- );
165
- if (queryResult.items.length > 1) {
166
- throw new Error(
167
- `Multiple members found with memberId ${memberId} members _ids are : [${queryResult.items.map(member => member._id).join(', ')}]`
168
- );
169
- }
170
- return queryResult.items.length === 1 ? queryResult.items[0] : null;
171
- } catch (error) {
172
- console.error('Error finding member by ID:', error);
173
- throw new Error(`Failed to retrieve member data: ${error.message}`);
174
- }
175
- }
176
-
177
- /**
178
- * Retrieves existing members for a list of member IDs in bulk.
179
- * Uses chunked `hasSome` queries so a full page of members costs a handful of
180
- * requests instead of one query per member (the per-member fan-out made a whole
181
- * page fail whenever a single query hit a transient "fetch failed").
182
- * @param {Array<string|number>} memberIds - Member IDs to look up
183
- * @returns {Promise<Map<string, Object>>} - Map of String(memberId) to member record (missing IDs are absent)
184
- */
185
- async function findMembersByIds(memberIds) {
186
- const uniqueIds = [...new Set((memberIds || []).filter(id => id !== undefined && id !== null))];
187
- const membersById = new Map();
188
- if (uniqueIds.length === 0) {
189
- return membersById;
190
- }
191
-
192
- try {
193
- const idChunks = chunkArray(uniqueIds, 100);
194
- const chunkResults = await Promise.all(
195
- idChunks.map(idsChunk =>
196
- withTransientErrorRetry(() =>
197
- queryAllItems(
198
- wixData.query(COLLECTIONS.MEMBERS_DATA).hasSome('memberId', idsChunk).limit(1000)
199
- )
200
- )
201
- )
202
- );
203
-
204
- const duplicateIds = new Set();
205
- chunkResults.flat().forEach(member => {
206
- const key = String(member.memberId);
207
- if (membersById.has(key)) {
208
- duplicateIds.add(key);
209
- }
210
- membersById.set(key, member);
211
- });
212
- if (duplicateIds.size > 0) {
213
- throw new Error(`Multiple members found with memberId(s): [${[...duplicateIds].join(', ')}]`);
214
- }
215
- return membersById;
216
- } catch (error) {
217
- console.error('Error finding members by IDs:', error);
218
- throw new Error(`Failed to retrieve member data: ${error.message}`);
219
- }
220
- }
221
-
222
- /**
223
- * Method to get member by slug with flexible filtering options
224
- * @param {Object} options - Query options
225
- * @param {string} options.slug - The slug to search for
226
- * @param {boolean} options.excludeDropped - Whether to exclude dropped members (default: true)
227
- * @param {boolean} options.excludeSearchedMember - Whether to exclude a specific member (default: false)
228
- * @param {string|number} [options.memberId] - Member ID to exclude when excludeSearchedMember is true (optional)
229
- * @param {boolean} [options.normalizeSlugForComparison=false] - Whether to normalize the slug for comparison (default: false)
230
- * @returns {Promise<Object|null>} - Member data or null if not found
231
- */
232
- async function getMemberBySlug({
233
- slug,
234
- excludeDropped = true,
235
- excludeSearchedMember = false,
236
- memberId = null,
237
- normalizeSlugForComparison = false,
238
- }) {
239
- if (!slug) return null;
240
-
241
- try {
242
- let query = wixData.search(COLLECTIONS.MEMBERS_DATA).expression(slug);
243
-
244
- if (excludeDropped) {
245
- query = query.ne('action', 'drop');
246
- }
247
-
248
- if (excludeSearchedMember && memberId) {
249
- query = query.ne('memberId', memberId);
250
- }
251
- query = query.limit(1000);
252
- const searchResult = await searchAllItems(query);
253
- const membersList = searchResult.filter(
254
- item => item.url && item.url.toLowerCase().includes(slug.toLowerCase())
255
- ); //replacement for contains - case insensitive
256
- let matchingMembers = membersList.filter(
257
- item => item.url && item.url.toLowerCase() === slug.toLowerCase()
258
- );
259
- if (normalizeSlugForComparison) {
260
- matchingMembers = membersList
261
- .filter(
262
- //remove trailing "-1", "-2", etc.
263
- item => item.url && normalizeUrlForComparison(item.url) === slug.toLowerCase()
264
- )
265
- .sort(sortByUrlCounterDescending);
266
- }
267
- if (matchingMembers.length > 1) {
268
- const queryResultMsg = `Multiple members found with same slug ${slug} membersIds are : [${matchingMembers
269
- .map(member => member.memberId)
270
- .join(', ')}]`;
271
- if (!normalizeSlugForComparison) {
272
- throw new Error(queryResultMsg);
273
- } else {
274
- console.log(queryResultMsg);
275
- }
276
- }
277
- return matchingMembers[0] || null;
278
- } catch (error) {
279
- const errorMessage = `Error getting member by slug: ${slug} : ${error.message}`;
280
- console.error(errorMessage);
281
- throw new Error(errorMessage);
282
- }
283
- }
284
-
285
- async function getCMSMemberByWixMemberId(wixMemberId) {
286
- if (!wixMemberId) {
287
- throw new Error('Wix Member ID is required');
288
- }
289
- try {
290
- const members = await wixData
291
- .query(COLLECTIONS.MEMBERS_DATA)
292
- .eq('wixMemberId', wixMemberId)
293
- .limit(2)
294
- .find()
295
- .then(res => res.items);
296
- if (members.length > 1) {
297
- throw new Error(
298
- `[getCMSMemberByWixMemberId] Multiple members found with wixMemberId ${wixMemberId} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
299
- );
300
- }
301
- return members[0] || null;
302
- } catch (error) {
303
- throw new Error(
304
- `[getCMSMemberByWixMemberId] Failed to retrieve member by wixMemberId ${wixMemberId} data: ${error.message}`
305
- );
306
- }
307
- }
308
- /**
309
- * Gets all members with aboutyoustatus as null
310
- * @returns {Promise<import('wix-data').WixDataQueryResult>} - WixDataQueryResult of member data
311
- */
312
- const getAllEmptyAboutYouMembers = async () => {
313
- try {
314
- const membersQuery = wixData
315
- .query(COLLECTIONS.MEMBERS_DATA)
316
- .isEmpty('aboutYourSelf')
317
- .isNotEmpty('aboutYouHtml');
318
- return await queryAllItems(membersQuery);
319
- } catch (error) {
320
- console.error('Error getting empty about you members:', error);
321
- throw new Error(`Failed to get empty about you members: ${error.message}`);
322
- }
323
- };
324
-
325
- /**
326
- * updates member data
327
- * @param {Object} memberToUpdate - The member data to update
328
- * @returns {Promise<Object|null>} - Member data or null if not found
329
- */
330
- async function updateMember(memberToUpdate) {
331
- try {
332
- const updatedMember = await wixData.update(
333
- COLLECTIONS.MEMBERS_DATA,
334
- normalizeMemberEmailFields(memberToUpdate)
335
- );
336
- return updatedMember;
337
- } catch (error) {
338
- throw new Error(`Failed to update member data: ${error.message}`);
339
- }
340
- }
341
- /**
342
- * Whether the given email is already used by a DIFFERENT member (case-insensitive).
343
- * Returns false when the email is free or belongs to the same member, so a member can
344
- * always keep/normalize their own email.
345
- * @param {string} email
346
- * @param {string|number} memberId - The member requesting the change
347
- * @returns {Promise<boolean>}
348
- */
349
- async function isEmailAlreadyUsed(email, memberId) {
350
- const member = await getMemberByContactEmail(email);
351
- return member !== null && String(member.memberId) !== String(memberId);
352
- }
353
- /**
354
- * Finds the member that owns an email (in either the login or contact-form field),
355
- * matching case-insensitively. Emails are normalized (lowercased + trimmed) on write and
356
- * backfilled by the email-normalization migration, so stored values are canonical: a single
357
- * `.eq` against the normalized email is an exact, case-insensitive lookup.
358
- * Throws if two DIFFERENT members share the email (a data-integrity violation).
359
- * @param {string} email
360
- * @returns {Promise<Object|null>}
361
- */
362
- async function getMemberByContactEmail(email) {
363
- const normalized = normalizeEmail(email);
364
- if (!normalized) return null;
365
-
366
- const members = await wixData
367
- .query(COLLECTIONS.MEMBERS_DATA)
368
- .eq('contactFormEmail', normalized)
369
- .or(wixData.query(COLLECTIONS.MEMBERS_DATA).eq('email', normalized))
370
- .limit(2)
371
- .find()
372
- .then(res => res.items);
373
-
374
- if (members.length > 1) {
375
- throw new Error(
376
- `[getMemberByContactEmail] Multiple members found with same loginemail or contactFormEmail ${email} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
377
- );
378
- }
379
- return members[0] || null;
380
- }
381
- /**
382
- * Saves member registration data (supports partial updates)
383
- * @param {Object} data - Member data to save (can be partial - only fields being updated)
384
- * @param {string} id - Member ID
385
- * @returns {Promise<Object>} Result object with type and data/error
386
- */
387
- async function saveRegistrationData(data, id) {
388
- try {
389
- console.log(' saveRegistrationData data._id', data._id);
390
- console.log(' saveRegistrationData id', id);
391
- if (data._id !== id) return { type: 'notAuthorized' };
392
-
393
- if (data.url) {
394
- const isDuplicate = await urlExists(data.url, data.memberId);
395
-
396
- if (isDuplicate) {
397
- return {
398
- type: 'error',
399
- error: 'URL slug is already taken. Please choose a different one.',
400
- };
401
- }
402
- }
403
- if (data.contactFormEmail) {
404
- const isDuplicate = await isEmailAlreadyUsed(data.contactFormEmail, data.memberId);
405
- if (isDuplicate) {
406
- return {
407
- type: 'error',
408
- error: 'Contact Email is already taken. Please choose a different one.',
409
- };
410
- }
411
- }
412
- // Fetch existing data to merge with partial update
413
- const existingMemberData = await findMemberByWixDataId(id);
414
-
415
- if (!existingMemberData) {
416
- return {
417
- type: 'error',
418
- error: 'Member not found',
419
- };
420
- }
421
-
422
- // Merge partial data with existing data (incoming data takes precedence)
423
- const mergedData = {
424
- ...existingMemberData,
425
- ...data,
426
- };
427
-
428
- if (data.addresses && Array.isArray(data.addresses)) {
429
- mergedData.locHash = generateGeoHash(data.addresses);
430
- }
431
-
432
- const dataToSave = await updateMemberContactInfo(mergedData, existingMemberData);
433
- const saveData = await updateMember(dataToSave);
434
- return {
435
- type: 'success',
436
- saveData,
437
- };
438
- } catch (error) {
439
- console.error(error);
440
- return {
441
- type: 'error',
442
- error,
443
- };
444
- }
445
- }
446
-
447
- /**
448
- * Checks if a URL already exists in the database for a different member (case-insensitive)
449
- * @param {string} url - The URL to check
450
- * @param {string|number} excludeMemberId - Member ID to exclude from the check
451
- * @returns {Promise<boolean>} - True if URL exists for another member
452
- */
453
- async function urlExists(url, excludeMemberId) {
454
- if (!url) return false;
455
-
456
- try {
457
- const member = await getMemberBySlug({
458
- slug: url,
459
- excludeDropped: false,
460
- excludeSearchedMember: true,
461
- memberId: excludeMemberId,
462
- });
463
- return member !== null;
464
- } catch (error) {
465
- console.error('Error checking URL existence:', error);
466
- return false;
467
- }
468
- }
469
-
470
- /**
471
- * Checks URL uniqueness for a member
472
- * @param {string} url - The URL to check
473
- * @param {string} memberId - The member ID to exclude from the check
474
- * @returns {Promise<Object>} Result object with isUnique boolean
475
- */
476
- async function checkUrlUniqueness(url, memberId) {
477
- if (!url || !memberId) {
478
- throw new Error('Missing required parameters: url and memberId are required');
479
- }
480
-
481
- try {
482
- const trimmedUrl = url.trim();
483
- const exists = await urlExists(trimmedUrl, memberId);
484
-
485
- return { isUnique: !exists };
486
- } catch (error) {
487
- console.error('Error checking URL uniqueness:', error);
488
- throw new Error(`Failed to check URL uniqueness: ${error.message}`);
489
- }
490
- }
491
- /**
492
- * Get all members with external profile images
493
- * @returns {Promise<Array>} - Array of member IDs
494
- */
495
- async function getAllMembersWithExternalImages() {
496
- try {
497
- const membersQuery = await wixData
498
- .query(COLLECTIONS.MEMBERS_DATA)
499
- .isNotEmpty('profileImage')
500
- .ne('profileImage', null);
501
-
502
- const allItems = await queryAllItems(membersQuery);
503
-
504
- // Filter for external images (not 'wix hosted images')
505
- const membersWithExternalImages = allItems.filter(
506
- member => member.profileImage && !isWixHostedImage(member.profileImage)
507
- );
508
-
509
- return membersWithExternalImages;
510
- } catch (error) {
511
- console.error('Error getting members with external images:', error);
512
- return [];
513
- }
514
- }
515
-
516
- async function getMembersWithWixUrl() {
517
- const membersQuery = wixData
518
- .query(COLLECTIONS.MEMBERS_DATA)
519
- .eq('isVisible', true)
520
- .eq('showWixUrl', true)
521
- .ne('action', MEMBER_ACTIONS.DROP)
522
- .ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
523
- .ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT)
524
- //.isNotEmpty('url') - not used because it's not working as expected
525
- .limit(1000);
526
- let currentResults = await membersQuery.find();
527
- let i = 0;
528
- const allItems = currentResults.items;
529
- while (currentResults.hasNext()) {
530
- if (i % 50 === 0) console.log(`page ${i}`);
531
- currentResults = await currentResults.next();
532
- allItems.push(...currentResults.items);
533
- i++;
534
- }
535
- console.log('i is ', i);
536
- const filtered = allItems.filter(
537
- item => typeof item.url === 'string' && !item.url.includes('/') && item.url !== ''
538
- );
539
- console.log('filtered is ', filtered.length);
540
- return filtered;
541
- }
542
-
543
- /**
544
- * Gets all members who need contactFormEmail migration (missing contactFormEmail field)
545
- * @returns {Promise<Array>} - Array of member data
546
- */
547
- const getAllMembersWithoutContactFormEmail = async () => {
548
- try {
549
- const membersQuery = wixData
550
- .query(COLLECTIONS.MEMBERS_DATA)
551
- .isEmpty('contactFormEmail')
552
- .isNotEmpty('email')
553
- .limit(1000);
554
-
555
- const allItems = await queryAllItems(membersQuery);
556
- return allItems;
557
- } catch (error) {
558
- console.error('Error getting members without contactFormEmail:', error);
559
- throw new Error(`Failed to get members without contactFormEmail: ${error.message}`);
560
- }
561
- };
562
-
563
- /**
564
- * Gets all members whose email or contactFormEmail is stored with non-canonical casing
565
- * (or surrounding whitespace) and therefore needs the normalization backfill.
566
- * Wix Data cannot compare a field to its own lowercase form, so we fetch members that have
567
- * an email set and filter in memory.
568
- * @returns {Promise<Array>} - Array of member data
569
- */
570
- const getAllMembersNeedingEmailNormalization = async () => {
571
- try {
572
- const membersQuery = wixData
573
- .query(COLLECTIONS.MEMBERS_DATA)
574
- .isNotEmpty('email')
575
- .or(wixData.query(COLLECTIONS.MEMBERS_DATA).isNotEmpty('contactFormEmail'))
576
- .limit(1000);
577
-
578
- const allItems = await queryAllItems(membersQuery);
579
- return allItems.filter(memberNeedsEmailNormalization);
580
- } catch (error) {
581
- console.error('Error getting members needing email normalization:', error);
582
- throw new Error(`Failed to get members needing email normalization: ${error.message}`);
583
- }
584
- };
585
-
586
- /* Gets all updated login emails from the updated emails database
587
- * @returns {Promise<Array>} - Array of updated email data
588
- */
589
- const getAllUpdatedLoginEmails = async () => {
590
- try {
591
- const updatedEmailsQuery = await wixData
592
- .query(COLLECTIONS.UPDATED_LOGIN_EMAILS)
593
- .isNotEmpty('memberId')
594
- .isNotEmpty('loginEmail')
595
- .limit(1000);
596
- return await queryAllItems(updatedEmailsQuery);
597
- } catch (error) {
598
- console.error('Error getting updated login emails:', error);
599
- throw new Error(`Failed to get updated login emails: ${error.message}`);
600
- }
601
- };
602
- /**
603
- * Gets members by their member IDs for email sync
604
- * @param {Array} memberIds - Array of member IDs to fetch
605
- * @returns {Promise<Array>} - Array of member data
606
- */
607
- const getMembersByIds = async memberIds => {
608
- try {
609
- const membersQuery = wixData
610
- .query(COLLECTIONS.MEMBERS_DATA)
611
- .hasSome('memberId', memberIds)
612
- .limit(1000);
613
-
614
- return await queryAllItems(membersQuery);
615
- } catch (error) {
616
- console.error('Error getting members by IDs:', error);
617
- throw new Error(`Failed to get members by IDs: ${error.message}`);
618
- }
619
- };
620
-
621
- const getMemberByEmail = async email => {
622
- try {
623
- // Login emails are normalized on write, so match against the normalized value (see
624
- // getMemberByContactEmail) — an exact `.eq` is a case-insensitive lookup.
625
- const normalized = normalizeEmail(email);
626
- if (!normalized) return null;
627
- const members = await wixData
628
- .query(COLLECTIONS.MEMBERS_DATA)
629
- .eq('email', normalized)
630
- .limit(2)
631
- .find()
632
- .then(res => res.items);
633
- if (members.length > 1) {
634
- throw new Error(
635
- `[getMemberByEmail] Multiple members found with email ${email} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
636
- );
637
- }
638
- return members[0] || null;
639
- } catch (error) {
640
- console.error('Error getting member by email:', error);
641
- throw new Error(`Failed to get member by email: ${error.message}`);
642
- }
643
- };
644
-
645
- const getQAUsers = async () => {
646
- try {
647
- return await wixData
648
- .query(COLLECTIONS.QA_USERS)
649
- .include('member')
650
- .find()
651
- .then(res => res.items.map(item => item.member));
652
- } catch (error) {
653
- console.error('Error getting QA users:', error);
654
- throw new Error(`Failed to get QA users: ${error.message}`);
655
- }
656
- };
657
- /**
658
- * Ensures member has a contact - creates one if missing
659
- * @param {Object} memberData - Member data from DB
660
- * @returns {Promise<Object>} - Member data with contact and member IDs
661
- */
662
- async function ensureWixMemberAndContactExist(memberData) {
663
- if (!memberData) {
664
- throw new Error('Member data is required');
665
- }
666
- if (!memberData.wixContactId || !memberData.wixMemberId) {
667
- const memberDataWithContactId = await createContactAndMemberIfNew(memberData);
668
- return memberDataWithContactId;
669
- }
670
- return memberData;
671
- }
672
- async function prepareMemberForSSOLogin(data) {
673
- try {
674
- console.log('data', data);
675
- const memberId = data?.pac?.cst_recno;
676
- if (!memberId) {
677
- throw new Error(`Member ID is missing in passed data ${JSON.stringify(data)}`);
678
- }
679
- const memberData = await findMemberById(Number(memberId));
680
- if (!memberData) {
681
- throw new Error(`Member data not found for memberId ${memberId}`);
682
- }
683
- console.log('memberData', memberData);
684
- return await ensureWixMemberAndContactExist(memberData);
685
- } catch (error) {
686
- console.error(`Error in prepareMemberForSSOLogin: ${error.message}`);
687
- throw error;
688
- }
689
- }
690
- async function prepareMemberForQALogin(email) {
691
- try {
692
- console.log('qa email:', email);
693
- if (!email) {
694
- throw new Error(`Email is missing in passed data ${email}`);
695
- }
696
- const memberData = await getMemberByEmail(email);
697
- if (!memberData) {
698
- throw new Error(`Member data not found for email ${email}`);
699
- }
700
- console.log('memberData', memberData);
701
- return await ensureWixMemberAndContactExist(memberData);
702
- } catch (error) {
703
- const errMsg = `[prepareMemberForQALogin] QA Login failed with error: ${error.message} for email: ${email}`;
704
- console.error(errMsg);
705
- throw new Error(errMsg);
706
- }
707
- }
708
-
709
- /**
710
- * Tracks a button click with member and location info.
711
- * @param {Object} params - Parameters
712
- * @param {string} params.pageName - Name of the page/popup where button was clicked
713
- * @param {string} params.buttonName - Name/ID of the button that was clicked
714
- * @param {Object} [params.data] - Optional data object to store with the click (e.g., form data being saved)
715
- * @returns {Promise<Object>} - Saved record or null if member not found
716
- */
717
- async function trackButtonClick({ pageName, buttonName, data }) {
718
- const wixMember = await getCurrentMember();
719
-
720
- if (!wixMember) {
721
- console.warn('[trackButtonClick]: No logged in member found');
722
- return null;
723
- }
724
-
725
- const dbMember = await getCMSMemberByWixMemberId(wixMember._id);
726
-
727
- if (!dbMember) {
728
- console.warn(
729
- `[trackButtonClick]: Member not found in MembersDataLatest for wixMemberId: ${wixMember._id}`
730
- );
731
- return null;
732
- }
733
-
734
- const memberName = dbMember.fullName || 'Unknown';
735
- const memberId = dbMember.memberId;
736
- const memberEmail = dbMember.email;
737
-
738
- const clickData = {
739
- memberName,
740
- memberId,
741
- pageName,
742
- buttonName,
743
- memberEmail,
744
- clickedAt: new Date(),
745
- ...(data && { data }),
746
- };
747
-
748
- try {
749
- const result = await wixData.insert(COLLECTIONS.BUTTON_CLICKS, clickData);
750
- console.log(`Tracked ${buttonName} click on ${pageName} for member ${memberId}`);
751
- return result;
752
- } catch (error) {
753
- console.error(`Error tracking ${buttonName} click:`, error);
754
- throw error;
755
- }
756
- }
757
-
758
- async function getAllMembersWithWixMemberId() {
759
- const membersQuery = wixData.query(COLLECTIONS.MEMBERS_DATA).isNotEmpty('wixMemberId');
760
- return await queryAllItems(membersQuery);
761
- }
762
-
763
- module.exports = {
764
- findMemberByWixDataId,
765
- createContactAndMemberIfNew,
766
- getAllMembersWithWixMemberId,
767
- saveRegistrationData,
768
- bulkSaveMembers,
769
- findMemberById,
770
- findMembersByIds,
771
- getMemberBySlug,
772
- getCMSMemberByWixMemberId,
773
- getAllEmptyAboutYouMembers,
774
- updateMember,
775
- getAllMembersWithExternalImages,
776
- getMembersWithWixUrl,
777
- getAllMembersWithoutContactFormEmail,
778
- getAllMembersNeedingEmailNormalization,
779
- memberNeedsEmailNormalization,
780
- getAllUpdatedLoginEmails,
781
- getMembersByIds,
782
- getMemberByEmail,
783
- getQAUsers,
784
- prepareMemberForSSOLogin,
785
- prepareMemberForQALogin,
786
- checkUrlUniqueness,
787
- trackButtonClick,
788
- isEmailAlreadyUsed,
789
- };