abmp-npm 2.0.14 → 2.0.16

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.
@@ -8,6 +8,7 @@ const {
8
8
  GEO_HASH_PRECISION,
9
9
  MAX__MEMBERS_SEARCH_RESULTS,
10
10
  WIX_QUERY_MAX_LIMIT,
11
+ MEMBERSHIPS_TYPES,
11
12
  } = require('./consts.js');
12
13
  const { wixData } = require('./elevated-modules');
13
14
 
@@ -31,6 +32,7 @@ function buildMembersSearchQuery(data) {
31
32
  .query(COLLECTIONS.MEMBERS_DATA)
32
33
  .ne('optOut', true)
33
34
  .ne('action', 'drop')
35
+ .ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
34
36
  .eq('isVisible', true);
35
37
  let filterConfig = [
36
38
  {
@@ -105,7 +107,7 @@ function buildMembersSearchQuery(data) {
105
107
  query = query.contains('fullName', filter.searchText);
106
108
  }
107
109
  if (!includeStudents) {
108
- query = query.ne('memberships.membertype', 'Student');
110
+ query = query.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT);
109
111
  }
110
112
  return query;
111
113
  },
@@ -35,7 +35,17 @@ const extractBaseUrl = url => {
35
35
  return url;
36
36
  };
37
37
  const incrementUrlCounter = (existingUrl, baseUrl) => {
38
- if (existingUrl && existingUrl === baseUrl) {
38
+ if (!existingUrl || !baseUrl) {
39
+ return baseUrl;
40
+ }
41
+ // Normalize for comparison (case-insensitive)
42
+ const normalizedExisting = existingUrl.toLowerCase();
43
+ const normalizedBase = baseUrl.toLowerCase();
44
+
45
+ if (
46
+ normalizedExisting === normalizedBase ||
47
+ normalizedExisting.startsWith(`${normalizedBase}-`)
48
+ ) {
39
49
  console.log(
40
50
  `Found member with same url ${existingUrl} for baseUrl ${baseUrl}, increasing counter by 1`
41
51
  );
@@ -44,6 +54,9 @@ const incrementUrlCounter = (existingUrl, baseUrl) => {
44
54
  const lastCounter = isNumeric ? parseInt(lastSegment, 10) : 0;
45
55
  return `${baseUrl}-${lastCounter + 1}`;
46
56
  }
57
+
58
+ // No conflict, return baseUrl with counter 1 to be safe
59
+ return `${baseUrl}-1`;
47
60
  };
48
61
  /**
49
62
  * Validates core member data requirements
@@ -4,7 +4,7 @@ const { MEMBERSHIPS_TYPES } = require('./consts');
4
4
  const { updateMemberContactInfo } = require('./contacts-methods');
5
5
  const { MEMBER_ACTIONS } = require('./daily-pull/consts');
6
6
  const { wixData } = require('./elevated-modules');
7
- const { createSiteMember } = require('./members-area-methods');
7
+ const { createSiteMember, getCurrentMember } = require('./members-area-methods');
8
8
  const {
9
9
  chunkArray,
10
10
  normalizeUrlForComparison,
@@ -477,6 +477,51 @@ async function getSiteMemberId(data) {
477
477
  }
478
478
  }
479
479
 
480
+ /**
481
+ * Tracks a button click with member and location info.
482
+ * @param {Object} params - Parameters
483
+ * @param {string} params.pageName - Name of the page/popup where button was clicked
484
+ * @param {string} params.buttonName - Name/ID of the button that was clicked
485
+ * @returns {Promise<Object>} - Saved record or null if member not found
486
+ */
487
+ async function trackButtonClick({ pageName, buttonName }) {
488
+ const wixMember = await getCurrentMember();
489
+
490
+ if (!wixMember) {
491
+ console.warn('[trackButtonClick]: No logged in member found');
492
+ return null;
493
+ }
494
+
495
+ const dbMember = await getMemberByContactId(wixMember._id);
496
+
497
+ if (!dbMember) {
498
+ console.warn(
499
+ `[trackButtonClick]: Member not found in MembersDataLatest for contactId: ${wixMember._id}`
500
+ );
501
+ return null;
502
+ }
503
+
504
+ const memberName = dbMember.fullName || 'Unknown';
505
+ const memberId = dbMember.memberId;
506
+
507
+ const clickData = {
508
+ memberName,
509
+ memberId,
510
+ pageName,
511
+ buttonName,
512
+ clickedAt: new Date(),
513
+ };
514
+
515
+ try {
516
+ const result = await wixData.insert(COLLECTIONS.BUTTON_CLICKS, clickData);
517
+ console.log(`Tracked ${buttonName} click on ${pageName} for member ${memberId}`);
518
+ return result;
519
+ } catch (error) {
520
+ console.error(`Error tracking ${buttonName} click:`, error);
521
+ throw error;
522
+ }
523
+ }
524
+
480
525
  module.exports = {
481
526
  findMemberByWixDataId,
482
527
  createContactAndMemberIfNew,
@@ -496,4 +541,5 @@ module.exports = {
496
541
  getQAUsers,
497
542
  getSiteMemberId,
498
543
  checkUrlUniqueness,
544
+ trackButtonClick,
499
545
  };
@@ -9,7 +9,8 @@ const { queryAllItems, chunkArray } = require('../utils');
9
9
  const { TASKS_NAMES } = require('./consts');
10
10
 
11
11
  const COLLECTION_WITH_URLS = 'MembersDataWithUrls';
12
- const CHUNK_SIZE = 5000; // 5k members per task
12
+ const CHUNK_SIZE = 5000; // 5k members per task for migration
13
+ const GENERATION_CHUNK_SIZE = 1000; // 1k members per task for URL generation
13
14
 
14
15
  /**
15
16
  * Step 1: Migrate existing URLs from backup collection
@@ -201,7 +202,7 @@ async function scheduleGenerateMissingUrls() {
201
202
  };
202
203
  }
203
204
 
204
- const chunks = chunkArray(membersToUpdate, CHUNK_SIZE);
205
+ const chunks = chunkArray(membersToUpdate, GENERATION_CHUNK_SIZE);
205
206
 
206
207
  for (let i = 0; i < chunks.length; i++) {
207
208
  const chunk = chunks[i];
@@ -255,11 +256,25 @@ async function generateUrlsChunk(data) {
255
256
  };
256
257
 
257
258
  try {
258
- // Fetch all members at once using hasSome
259
- console.log(`Fetching ${memberIds.length} members from database...`);
260
- const members = await queryAllItems(
261
- wixData.query(COLLECTIONS.MEMBERS_DATA).hasSome('_id', memberIds)
259
+ // Fetch members in smaller batches to avoid cursor size limits
260
+ // hasSome with too many IDs creates cursors that exceed Wix's 150KB limit
261
+ const FETCH_BATCH_SIZE = 200;
262
+ console.log(
263
+ `Fetching ${memberIds.length} members from database in batches of ${FETCH_BATCH_SIZE}...`
262
264
  );
265
+
266
+ const members = [];
267
+ const idBatches = chunkArray(memberIds, FETCH_BATCH_SIZE);
268
+
269
+ for (let i = 0; i < idBatches.length; i++) {
270
+ const idBatch = idBatches[i];
271
+ const batchMembers = await queryAllItems(
272
+ wixData.query(COLLECTIONS.MEMBERS_DATA).hasSome('_id', idBatch)
273
+ );
274
+ members.push(...batchMembers);
275
+ console.log(`Fetched batch ${i + 1}/${idBatches.length}: ${batchMembers.length} members`);
276
+ }
277
+
263
278
  console.log(`Found ${members.length} members in database`);
264
279
 
265
280
  // Create a map of _id -> member for quick lookup
@@ -292,7 +307,7 @@ async function generateUrlsChunk(data) {
292
307
  try {
293
308
  const uniqueUrl = await ensureUniqueUrl({
294
309
  url: '',
295
- memberId: member._id,
310
+ memberId: member.memberId,
296
311
  fullName: name || '', // Let ensureUniqueUrl handle fallback for empty names
297
312
  });
298
313
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "2.0.14",
3
+ "version": "2.0.16",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "check-cycles": "madge --circular .",
@@ -0,0 +1,27 @@
1
+ const PAGE_NAME = 'Learn More';
2
+ const BUTTON_NAME = 'Upgrade Now';
3
+
4
+ /**
5
+ * Creates the Learn More popup handler
6
+ * @param {Object} params - Parameters
7
+ * @param {Function} params.$w - Wix $w selector
8
+ * @param {Function} params.trackClick - Backend function to track the click (handles member lookup internally)
9
+ */
10
+ function learnMoreOnReady({ $w: _$w, trackClick }) {
11
+ _$w('#learnMoreBtn').onClick(async () => {
12
+ try {
13
+ await trackClick({
14
+ pageName: PAGE_NAME,
15
+ buttonName: BUTTON_NAME,
16
+ });
17
+
18
+ console.log(`Tracked ${BUTTON_NAME} click on ${PAGE_NAME}`);
19
+ } catch (error) {
20
+ console.error('Error tracking button click:', error);
21
+ }
22
+ });
23
+ }
24
+
25
+ module.exports = {
26
+ learnMoreOnReady,
27
+ };
package/pages/index.js CHANGED
@@ -8,4 +8,5 @@ module.exports = {
8
8
  ...require('./SelectBannerImages.js'),
9
9
  ...require('./deleteConfirm.js'),
10
10
  ...require('./SaveAlerts.js'),
11
+ ...require('./LearnMore.js'),
11
12
  };
@@ -1294,6 +1294,7 @@ async function personalDetailsOnReady({
1294
1294
  streetAddress: {
1295
1295
  name: extractStreetName(address.line1),
1296
1296
  number: extractStreetNumber(address.line1),
1297
+ apt: address.line2 || '',
1297
1298
  },
1298
1299
  city: address.city || '',
1299
1300
  subdivision: address.state || '',
@@ -1309,20 +1310,49 @@ async function personalDetailsOnReady({
1309
1310
  if (!addressInputValue) return null;
1310
1311
 
1311
1312
  let line1 = '';
1313
+ let line2 = '';
1314
+
1312
1315
  if (addressInputValue.streetAddress) {
1313
1316
  const number = addressInputValue.streetAddress.number || '';
1314
1317
  const name = addressInputValue.streetAddress.name || '';
1315
1318
  line1 = `${number} ${name}`.trim();
1319
+
1320
+ // Capture apartment/suite/building info from streetAddress.apt (undocumented but exists)
1321
+ if (addressInputValue.streetAddress.apt) {
1322
+ line2 = addressInputValue.streetAddress.apt;
1323
+ }
1316
1324
  }
1317
1325
 
1318
1326
  if (!line1 && addressInputValue.formatted) {
1319
1327
  line1 = addressInputValue.formatted.split(',')[0]?.trim() || '';
1320
1328
  }
1321
1329
 
1330
+ // If line2 is still empty, try to extract building/suite info from formatted address
1331
+ if (!line2 && addressInputValue.formatted) {
1332
+ const formattedParts = addressInputValue.formatted.split(',').map(part => part.trim());
1333
+ // Look for BLDG/STE/APT/UNIT/SUITE info in the formatted parts
1334
+ for (let i = 1; i < formattedParts.length; i++) {
1335
+ const part = formattedParts[i];
1336
+ const lowerPart = part.toLowerCase();
1337
+ if (
1338
+ lowerPart.includes('bldg') ||
1339
+ lowerPart.includes('ste') ||
1340
+ lowerPart.includes('apt') ||
1341
+ lowerPart.includes('unit') ||
1342
+ lowerPart.includes('suite') ||
1343
+ lowerPart.includes('#') ||
1344
+ lowerPart.includes('building')
1345
+ ) {
1346
+ line2 = part;
1347
+ break;
1348
+ }
1349
+ }
1350
+ }
1351
+
1322
1352
  return {
1323
1353
  key: existingAddress?.key || generateId(),
1324
1354
  line1,
1325
- line2: existingAddress?.line2 || '',
1355
+ line2: line2 || existingAddress?.line2 || '',
1326
1356
  city: addressInputValue.city || '',
1327
1357
  state: addressInputValue.subdivision || '',
1328
1358
  postalcode: addressInputValue.postalCode || '',
@@ -1705,6 +1735,7 @@ async function personalDetailsOnReady({
1705
1735
  const parts = [];
1706
1736
 
1707
1737
  if (addr.line1) parts.push(addr.line1);
1738
+ if (addr.line2) parts.push(addr.line2); // Include building/suite info
1708
1739
  if (addr.city) parts.push(addr.city);
1709
1740
  if (addr.state && addr.postalcode) {
1710
1741
  parts.push(`${addr.state} ${addr.postalcode}`);
@@ -91,7 +91,7 @@ function formatAddress(item) {
91
91
  const limitedPostalCode = item.postalcode.slice(0, 5); //show only 5 digits to not show full user address
92
92
  switch (item.addressStatus) {
93
93
  case ADDRESS_STATUS_TYPES.FULL_ADDRESS:
94
- addressParts = [item.line1, item.city, item.state, limitedPostalCode];
94
+ addressParts = [item.line1, item.line2, item.city, item.state, limitedPostalCode];
95
95
  break;
96
96
  case ADDRESS_STATUS_TYPES.STATE_CITY_ZIP:
97
97
  addressParts = [item.city, item.state, limitedPostalCode];
package/public/consts.js CHANGED
@@ -13,6 +13,7 @@ const COLLECTIONS = {
13
13
  STATE_CITY_MAP: 'City',
14
14
  UPDATED_LOGIN_EMAILS: 'updatedLoginEmails',
15
15
  QA_USERS: 'QA_Users', //Make QA users configurable per site
16
+ BUTTON_CLICKS: 'ButtonClicks',
16
17
  };
17
18
 
18
19
  /**