abmp-npm 1.9.2 → 1.9.4

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.
@@ -231,9 +231,17 @@ async function getInterestAll() {
231
231
  throw e;
232
232
  }
233
233
  }
234
+ async function clearCollection(collectionName) {
235
+ try {
236
+ await wixData.truncate(collectionName);
237
+ } catch (err) {
238
+ throw new Error(`Failed to clearCollection ${collectionName} with error: ${err.message}`);
239
+ }
240
+ }
234
241
 
235
242
  module.exports = {
236
243
  buildMembersSearchQuery,
237
244
  fetchAllItemsInParallel,
238
245
  getInterestAll,
246
+ clearCollection,
239
247
  };
@@ -10,6 +10,7 @@ const wixData = {
10
10
  save: auth.elevate(items.save),
11
11
  remove: auth.elevate(items.remove),
12
12
  get: auth.elevate(items.get),
13
+ truncate: auth.elevate(items.truncate),
13
14
  //TODO: add other methods here as needed
14
15
  };
15
16
 
@@ -0,0 +1,72 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { getSecret } = require('../utils');
4
+
5
+ const { migrateInterests } = require('./interests');
6
+
7
+ const createHTTPFunctionsHelpers = wixHTTPFunctionsMethods => {
8
+ const { created, serverError, forbidden, ok, badRequest } = wixHTTPFunctionsMethods;
9
+ const responseOptions = {
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ };
14
+
15
+ const isRequestAuthenticated = async request => {
16
+ const AUTH_TOKEN = await getSecret('migrate-api-key');
17
+ return request.headers.authorization === 'Bearer ' + AUTH_TOKEN;
18
+ };
19
+
20
+ const withAuth = handler => async request => {
21
+ if (!(await isRequestAuthenticated(request))) {
22
+ return forbidden({
23
+ ...responseOptions,
24
+ body: { error: 'Unauthorized' },
25
+ });
26
+ }
27
+ return handler(request);
28
+ };
29
+
30
+ const migrateInterestsHandler = async _request => {
31
+ try {
32
+ const result = await migrateInterests();
33
+ return created({
34
+ ...responseOptions,
35
+ body: {
36
+ result,
37
+ },
38
+ });
39
+ } catch (error) {
40
+ console.error('Error migrating interests:', error);
41
+ return serverError(error);
42
+ }
43
+ };
44
+
45
+ const clearCollectionHandler = async request => {
46
+ try {
47
+ const collectionName = request.query.collectionName;
48
+ if (!collectionName || !Object.values(COLLECTIONS).includes(collectionName)) {
49
+ return badRequest({
50
+ ...responseOptions,
51
+ body: { error: 'Invalid collection name' },
52
+ });
53
+ }
54
+ const result = await clearCollection(collectionName);
55
+ return ok({
56
+ ...responseOptions,
57
+ body: {
58
+ result,
59
+ },
60
+ });
61
+ } catch (error) {
62
+ return serverError(error);
63
+ }
64
+ };
65
+
66
+ return {
67
+ post_migrateInterests: withAuth(migrateInterestsHandler),
68
+ delete_clearCollection: withAuth(clearCollectionHandler),
69
+ };
70
+ };
71
+
72
+ module.exports = { createHTTPFunctionsHelpers };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./httpFunctions'),
3
+ };
@@ -0,0 +1,37 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { CONFIG_KEYS } = require('../consts');
4
+ const { wixData } = require('../elevated-modules');
5
+ const { getHeaders } = require('../pac-api-methods');
6
+ const { getSiteConfigs } = require('../utils');
7
+
8
+ const getInterests = async () => {
9
+ const [url, headers] = await Promise.all([
10
+ getSiteConfigs(CONFIG_KEYS.INTERESTS_API_URL),
11
+ getHeaders(),
12
+ ]);
13
+ const fetchOptions = {
14
+ method: 'get',
15
+ headers: headers,
16
+ };
17
+ try {
18
+ const response = await fetch(url, fetchOptions);
19
+ return await response.json();
20
+ } catch (e) {
21
+ console.error('Error getting interests:', e);
22
+ throw e;
23
+ }
24
+ };
25
+
26
+ async function migrateInterests() {
27
+ const [interests, _] = await Promise.all([
28
+ getInterests(),
29
+ clearCollection(COLLECTIONS.INTERESTS),
30
+ ]);
31
+ const interestData = interests.map(val => ({ title: val.interest }));
32
+ return await wixData.bulkInsert(COLLECTIONS.INTERESTS, interestData);
33
+ }
34
+
35
+ module.exports = {
36
+ migrateInterests,
37
+ };
package/backend/index.js CHANGED
@@ -9,6 +9,8 @@ module.exports = {
9
9
  ...require('./members-area-methods'), //TODO: remove it once we finish NPM movement
10
10
  ...require('./members-data-methods'), //TODO: remove it once we finish NPM movement
11
11
  ...require('./cms-data-methods'), //TODO: remove it once we finish NPM movement
12
+ ...require('./routers'),
12
13
  ...require('./sso-methods'),
13
14
  ...require('./data-hooks'),
15
+ ...require('./http-functions'),
14
16
  };
@@ -421,6 +421,38 @@ const getMembersByIds = async memberIds => {
421
421
  }
422
422
  };
423
423
 
424
+ const getMemberByEmail = async email => {
425
+ try {
426
+ const members = await wixData
427
+ .query(COLLECTIONS.MEMBERS_DATA)
428
+ .eq('email', email)
429
+ .limit(2)
430
+ .find()
431
+ .then(res => res.items);
432
+ if (members.length > 1) {
433
+ throw new Error(
434
+ `[getMemberByEmail] Multiple members found with email ${email} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
435
+ );
436
+ }
437
+ return members[0] || null;
438
+ } catch (error) {
439
+ console.error('Error getting member by email:', error);
440
+ throw new Error(`Failed to get member by email: ${error.message}`);
441
+ }
442
+ };
443
+
444
+ const getQAUsers = async () => {
445
+ try {
446
+ return await wixData
447
+ .query(COLLECTIONS.QA_USERS)
448
+ .include('member')
449
+ .find()
450
+ .then(res => res.items.map(item => item.member));
451
+ } catch (error) {
452
+ console.error('Error getting QA users:', error);
453
+ throw new Error(`Failed to get QA users: ${error.message}`);
454
+ }
455
+ };
424
456
  async function getSiteMemberId(data) {
425
457
  try {
426
458
  console.log('data', data);
@@ -472,6 +504,8 @@ module.exports = {
472
504
  getAllMembersWithoutContactFormEmail,
473
505
  getAllUpdatedLoginEmails,
474
506
  getMembersByIds,
507
+ getMemberByEmail,
508
+ getQAUsers,
475
509
  getSiteMemberId,
476
510
  checkUrlUniqueness,
477
511
  };
@@ -0,0 +1,67 @@
1
+ const { authentication } = require('@wix/members');
2
+
3
+ const { getMemberByEmail, getQAUsers } = require('./members-data-methods');
4
+ const { getSecret } = require('./utils');
5
+
6
+ const validateQAUser = async userEmail => {
7
+ const qaUsers = await getQAUsers();
8
+ const matchingUser = qaUsers.find(user => user.email === userEmail);
9
+ if (!matchingUser) {
10
+ return { error: `Invalid user email: ${userEmail}` };
11
+ }
12
+ return { valid: true, user: matchingUser };
13
+ };
14
+
15
+ const loginQAMember = async (userEmail, secret) => {
16
+ try {
17
+ const userValidation = await validateQAUser(userEmail);
18
+ if (userValidation.error) {
19
+ return { success: false, error: userValidation.error };
20
+ }
21
+
22
+ const qaSecret = await getSecret('ABMP_QA_SECRET');
23
+ if (secret !== qaSecret) {
24
+ return { success: false, error: 'Invalid secret' };
25
+ }
26
+
27
+ //TODO: this code still needs fixes, as there is no generateSessionToken method on
28
+ const token = await authentication.generateSessionToken(userValidation.user, qaSecret);
29
+
30
+ const result = await getMemberCMSId(userEmail);
31
+ if (!result.success) {
32
+ return { success: false, error: result.error };
33
+ }
34
+
35
+ return {
36
+ success: true,
37
+ token,
38
+ memberCMSId: result.memberCMSId,
39
+ };
40
+ } catch (error) {
41
+ console.error('QA login error:', error);
42
+ return { error: 'Failed to generate session token' };
43
+ }
44
+ };
45
+
46
+ async function getMemberCMSId(userEmail) {
47
+ try {
48
+ const userValidation = await validateQAUser(userEmail);
49
+ if (userValidation.error) {
50
+ return { success: false, error: userValidation.error };
51
+ }
52
+
53
+ const member = await getMemberByEmail(userEmail);
54
+
55
+ if (!member) {
56
+ return { success: false, error: `No Member found in DB matching email: ${userEmail}` };
57
+ }
58
+ return { success: true, memberCMSId: member._id };
59
+ } catch (error) {
60
+ console.error('Error getting member CMS ID:', error);
61
+ return { success: false, error: 'Failed to retrieve member data' };
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ loginQAMember,
67
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./methods'),
3
+ };
@@ -0,0 +1,177 @@
1
+ const { PAGES_PATHS } = require('../../public/consts');
2
+ //const { fetchAllItemsInParallel } = require('../cms-data-methods'); unused at host site
3
+ const { CONFIG_KEYS } = require('../consts');
4
+ const { getSiteConfigs } = require('../utils');
5
+
6
+ const { generateSEOTitle, stripHtmlTags, getMemberProfileData } = require('./utils');
7
+
8
+ const createRoutersHandlers = wixRouterMethods => {
9
+ const {
10
+ redirect,
11
+ ok,
12
+ notFound,
13
+ sendStatus,
14
+ WixRouterSitemapEntry: _WixRouterSitemapEntry,
15
+ } = wixRouterMethods; // These dependencies needs to be injected as they do not have an SDK equivalent for now
16
+
17
+ async function profileRouter(request) {
18
+ const slug = request.path[0];
19
+ if (!slug) {
20
+ return redirect(request.baseUrl);
21
+ }
22
+ try {
23
+ const siteConfigs = await getSiteConfigs();
24
+ const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
25
+ const defaultSEODescription = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_SEO_DESCRIPTION];
26
+ const siteLogoUrl = siteConfigs[CONFIG_KEYS.SITE_LOGO_URL];
27
+ const defaultProfileImage = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_IMAGE];
28
+ const profileData = await getMemberProfileData(slug, siteAssociation);
29
+ if (profileData && profileData.showWixUrl) {
30
+ const ogImage = profileData.profileImage || profileData.logoImage || siteLogoUrl;
31
+ const seoTitle = generateSEOTitle({
32
+ fullName: profileData.fullName,
33
+ areasOfPractices: profileData.areasOfPractices,
34
+ siteAssociation,
35
+ });
36
+ // Use stripped HTML from aboutService rich text content
37
+ let description = stripHtmlTags(profileData.aboutService) || defaultSEODescription;
38
+
39
+ // Limit description to 160 characters for optimal SEO
40
+ if (description.length > 160) {
41
+ description = description.substring(0, 157) + '...';
42
+ }
43
+ const profileUrl = `${request.baseUrl}/${PAGES_PATHS.PROFILE}/${profileData.url}`;
44
+ const isPrivateMember = profileData.isPrivateMember;
45
+ const seoData = {
46
+ title: seoTitle,
47
+ description: description,
48
+ noIndex: isPrivateMember,
49
+ metaTags: [
50
+ {
51
+ name: 'description',
52
+ content: description,
53
+ },
54
+ {
55
+ name: 'keywords',
56
+ content:
57
+ `${profileData.fullName}, ${profileData.areasOfPractices ? profileData.areasOfPractices.slice(0, 3).join(', ') : ''}, ${siteAssociation}, ${profileData.city || ''}, ${profileData.state || ''}`
58
+ .replace(/,\s*,/g, ',')
59
+ .replace(/^,|,$/g, ''),
60
+ },
61
+ {
62
+ name: 'author',
63
+ content: profileData.fullName,
64
+ },
65
+ {
66
+ name: 'robots',
67
+ content: isPrivateMember ? 'noindex, nofollow' : 'index, follow',
68
+ },
69
+ // Open Graph tags
70
+ {
71
+ property: 'og:type',
72
+ content: 'profile',
73
+ },
74
+ {
75
+ property: 'og:title',
76
+ content: seoTitle,
77
+ },
78
+ {
79
+ property: 'og:description',
80
+ content: description,
81
+ },
82
+ {
83
+ property: 'og:image',
84
+ content: ogImage,
85
+ },
86
+ {
87
+ property: 'og:url',
88
+ content: profileUrl,
89
+ },
90
+ {
91
+ property: 'og:site_name',
92
+ content: `${siteAssociation} Members`,
93
+ },
94
+ // Twitter Card tags
95
+ {
96
+ name: 'twitter:card',
97
+ content: 'summary_large_image',
98
+ },
99
+ {
100
+ name: 'twitter:title',
101
+ content: seoTitle,
102
+ },
103
+ {
104
+ name: 'twitter:description',
105
+ content: description,
106
+ },
107
+ {
108
+ name: 'twitter:image',
109
+ content: ogImage,
110
+ },
111
+ // Additional SEO tags
112
+ {
113
+ name: 'geo.region',
114
+ content: profileData.state || '',
115
+ },
116
+ {
117
+ name: 'geo.placename',
118
+ content: profileData.city || '',
119
+ },
120
+ ].filter(tag => tag.content && tag.content.trim() !== ''), // Remove empty tags
121
+ };
122
+ return ok('profile', { ...profileData, defaultProfileImage }, seoData);
123
+ }
124
+ return notFound();
125
+ } catch (error) {
126
+ console.error(error);
127
+ return sendStatus('500', 'Internal Server Error');
128
+ }
129
+ }
130
+ function profileSiteMap(_sitemapRequest) {
131
+ return [];
132
+ // Commented out - currently disabled in host site
133
+ // try {
134
+ // const membersQuery = wixData
135
+ // .query(COLLECTIONS.MEMBERS_DATA)
136
+ // .eq('showWixUrl', true)
137
+ // .isNotEmpty('url')
138
+ // .ne('action', 'drop')
139
+ // .fields('url', 'fullName');
140
+
141
+ // const allMembers = await fetchAllItemsInParallel(membersQuery);
142
+
143
+ // const batchSize = 1000;
144
+ // const sitemapEntries = [];
145
+ // const totalItems = allMembers.items.length;
146
+
147
+ // for (let i = 0; i < totalItems; i += batchSize) {
148
+ // const batch = allMembers.items.slice(i, i + batchSize);
149
+ // const batchEntries = batch.map(member => {
150
+ // const entry = new WixRouterSitemapEntry(member.fullName);
151
+ // entry.pageName = 'profile';
152
+ // entry.url = `${PAGES_PATHS.PROFILE}/${member.url}`;
153
+ // entry.title = member.fullName;
154
+ // entry.changeFrequency = 'monthly';
155
+ // entry.priority = 1.0;
156
+ // return entry;
157
+ // });
158
+ // sitemapEntries.push(...batchEntries);
159
+ // }
160
+
161
+ // return sitemapEntries;
162
+ // } catch (error) {
163
+ // console.error('Error generating profile sitemap:', error);
164
+ // return [];
165
+ // }
166
+ }
167
+ //Add other routers here
168
+ return {
169
+ profileRouter,
170
+ profileSiteMap,
171
+ //Add other routers here
172
+ };
173
+ };
174
+
175
+ module.exports = {
176
+ createRoutersHandlers,
177
+ };
@@ -0,0 +1,118 @@
1
+ const { getMainAddress } = require('../../public/Utils/sharedUtils');
2
+ const { getMemberBySlug } = require('../members-data-methods');
3
+ const {
4
+ getAddressesByStatus,
5
+ formatDateToMonthYear,
6
+ hasStudentMembership,
7
+ isPAC_STAFF,
8
+ } = require('../utils');
9
+
10
+ function generateSEOTitle({ fullName, areasOfPractices, siteAssociation }) {
11
+ return `${fullName}${
12
+ areasOfPractices && areasOfPractices.length > 0
13
+ ? ` | ${areasOfPractices.slice(0, 3).join(', ')}`
14
+ : ''
15
+ } | ${siteAssociation} Member`;
16
+ }
17
+
18
+ function stripHtmlTags(html) {
19
+ if (!html) return '';
20
+ // Remove HTML tags and decode HTML entities
21
+ return html
22
+ .replace(/<[^>]*>/g, '') // Remove HTML tags
23
+ .replace(/&nbsp;/g, ' ') // Replace non-breaking spaces
24
+ .replace(/&amp;/g, '&') // Replace encoded ampersands
25
+ .replace(/&lt;/g, '<') // Replace encoded less than
26
+ .replace(/&gt;/g, '>') // Replace encoded greater than
27
+ .replace(/&quot;/g, '"') // Replace encoded quotes
28
+ .replace(/&#39;/g, "'") // Replace encoded apostrophes
29
+ .replace(/\s+/g, ' ') // Replace multiple whitespace with single space
30
+ .trim(); // Remove leading/trailing whitespace
31
+ }
32
+
33
+ function shouldHaveStudentBadge(member, siteAssociation) {
34
+ return hasStudentMembership({
35
+ member,
36
+ checkAssociation: true,
37
+ siteAssociation,
38
+ });
39
+ }
40
+
41
+ function transformMemberToProfileData(member, siteAssociation) {
42
+ if (!member) {
43
+ throw new Error('member is required');
44
+ }
45
+ const addresses = member.addresses || [];
46
+ const mainAddress = getMainAddress(member.addressDisplayOption, addresses);
47
+ const licenceNo = member.licenses
48
+ ?.map(val => val.license)
49
+ .filter(Boolean)
50
+ .join(', ');
51
+ const processedAddresses = getAddressesByStatus(member.addresses, member.addressDisplayOption);
52
+
53
+ const memberships = member.memberships || [];
54
+ const siteAssociationMembership = memberships.find(m => m.association === siteAssociation);
55
+
56
+ const areasOfPractices =
57
+ member.areasOfPractices
58
+ ?.filter(item => typeof item === 'string' && item.trim().length > 0)
59
+ .map(item => item.trim())
60
+ .sort((a, b) =>
61
+ a.localeCompare(b, undefined, {
62
+ sensitivity: 'base',
63
+ numeric: true,
64
+ })
65
+ ) || [];
66
+ return {
67
+ mainAddress,
68
+ testimonials: member.testimonial || [],
69
+ licenceNo,
70
+ processedAddresses,
71
+ memberSince:
72
+ (member.showABMP &&
73
+ siteAssociationMembership &&
74
+ formatDateToMonthYear(siteAssociationMembership?.membersince)) ||
75
+ '',
76
+ shouldHaveStudentBadge: shouldHaveStudentBadge(member, siteAssociation),
77
+ logoImage: member.logoImage,
78
+ fullName: member.fullName,
79
+ profileImage: member.profileImage,
80
+ showContactForm: member.showContactForm,
81
+ bookingUrl: member.bookingUrl,
82
+ aboutService: member.aboutService,
83
+ businessName: (member.showBusinessName && member.businessName) || '',
84
+ phone: member.toShowPhone || '',
85
+ areasOfPractices,
86
+ gallery: member.gallery,
87
+ bannerImages: member.bannerImages,
88
+ showWixUrl: member.showWixUrl,
89
+ _id: member._id,
90
+ url: member.url,
91
+ isPrivateMember: isPAC_STAFF(member),
92
+ };
93
+ }
94
+
95
+ const getMemberProfileData = async (slug, siteAssociation) => {
96
+ try {
97
+ const member = await getMemberBySlug({
98
+ slug,
99
+ excludeDropped: true,
100
+ excludeSearchedMember: false,
101
+ });
102
+
103
+ if (!member) {
104
+ return null;
105
+ }
106
+
107
+ return transformMemberToProfileData(member, siteAssociation);
108
+ } catch (error) {
109
+ console.error(error);
110
+ throw error;
111
+ }
112
+ };
113
+
114
+ module.exports = {
115
+ generateSEOTitle,
116
+ stripHtmlTags,
117
+ getMemberProfileData,
118
+ };
package/backend/utils.js CHANGED
@@ -3,17 +3,16 @@ const { secrets } = require('@wix/secrets');
3
3
  const { site } = require('@wix/urls');
4
4
  const { encode } = require('ngeohash');
5
5
 
6
- const { COLLECTIONS } = require('../public/consts');
6
+ const { COLLECTIONS, ADDRESS_STATUS_TYPES } = require('../public/consts');
7
+ const { formatAddress, generateId } = require('../public/Utils/sharedUtils');
7
8
 
8
- const { CONFIG_KEYS, GEO_HASH_PRECISION } = require('./consts');
9
+ const { CONFIG_KEYS, GEO_HASH_PRECISION, MEMBERSHIPS_TYPES } = require('./consts');
9
10
  const { wixData } = require('./elevated-modules');
10
11
  const elevatedGetSecretValue = auth.elevate(secrets.getSecretValue);
11
12
 
12
13
  /**
13
14
  * Retrieves site configuration values from the database
14
- * @param {string} [configKey] - The configuration key to retrieve. Must be one of:
15
- * - 'AUTOMATION_EMAIL_TRIGGER_ID' - Email template ID for triggered emails
16
- * - 'SITE_ASSOCIATION' - Site association configuration
15
+ * @param {keyof typeof CONFIG_KEYS} [configKey] - The configuration key to retrieve
17
16
  * @returns {Promise<any>} The configuration value for the specified key, or all configs if no key provided
18
17
  * @example
19
18
  * // Get specific config
@@ -59,18 +58,26 @@ function formatDateToMonthYear(dateString) {
59
58
  return date.toLocaleDateString('en-US', options);
60
59
  }
61
60
 
62
- /**
63
- * Check if member is a student
64
- * @param {Object} member - The member object
65
- * @returns {boolean} True if member has student membership
66
- */
67
- function isStudent(member) {
61
+ function hasStudentMembership({ member, checkAssociation = false, siteAssociation = null }) {
68
62
  const memberships = member?.memberships;
69
63
  if (!Array.isArray(memberships)) return false;
70
64
 
71
- return memberships.some(membership => membership.membertype === 'student');
65
+ return memberships.some(membership => {
66
+ const isStudent = membership.membertype === MEMBERSHIPS_TYPES.STUDENT;
67
+ const hasCorrectAssociation = !checkAssociation || membership.association === siteAssociation;
68
+ return isStudent && hasCorrectAssociation;
69
+ });
70
+ }
71
+
72
+ function isStudent(member) {
73
+ return hasStudentMembership({ member, checkAssociation: false });
72
74
  }
73
75
 
76
+ function isPAC_STAFF(member) {
77
+ return Boolean(
78
+ member?.memberships?.some(membership => membership.membertype === MEMBERSHIPS_TYPES.PAC_STAFF)
79
+ );
80
+ }
74
81
  /**
75
82
  * Get address display options for member
76
83
  * @param {Object} member - The member object
@@ -84,7 +91,22 @@ function getAddressDisplayOptions(member) {
84
91
  }
85
92
  return displayOptions;
86
93
  }
87
-
94
+ function getAddressesByStatus(addresses = [], addressDisplayOption = []) {
95
+ const visible = addresses.filter(addr => addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW);
96
+ if (visible.length < 2) {
97
+ return [];
98
+ }
99
+ const opts = Array.isArray(addressDisplayOption) ? addressDisplayOption : [];
100
+ const mainOpt = opts.find(o => o.isMain);
101
+ const mainKey = mainOpt ? mainOpt.key : visible[0].key; // fallback to the first visible if none marked
102
+ return visible
103
+ .filter(addr => addr?.key !== mainKey)
104
+ .map(addr => {
105
+ const addressString = formatAddress(addr);
106
+ return addressString ? { _id: generateId(), address: addressString } : null;
107
+ })
108
+ .filter(Boolean);
109
+ }
88
110
  const queryAllItems = async query => {
89
111
  console.log('start query');
90
112
  let oldResults = await query.find();
@@ -176,9 +198,12 @@ module.exports = {
176
198
  queryAllItems,
177
199
  formatDateToMonthYear,
178
200
  isStudent,
201
+ hasStudentMembership,
179
202
  getAddressDisplayOptions,
180
203
  getSecret,
181
204
  getSiteBaseUrl,
182
205
  encodeXml,
183
206
  formatDateOnly,
207
+ getAddressesByStatus,
208
+ isPAC_STAFF,
184
209
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "check-cycles": "madge --circular .",
@@ -0,0 +1,39 @@
1
+ const { location: wixLocationFrontend } = require('@wix/site-location');
2
+ const { authentication } = require('@wix/site-members');
3
+ const { local } = require('@wix/site-storage');
4
+
5
+ async function qaPageOnReady({ $w: _$w, loginQAMember }) {
6
+ try {
7
+ const { userEmail, secret, redirectTo, ...restQueryParams } = await wixLocationFrontend.query();
8
+
9
+ if (!userEmail || !secret) {
10
+ throw new Error('Missing required parameters: userEmail and/or secret');
11
+ }
12
+
13
+ const result = await loginQAMember(userEmail, secret);
14
+
15
+ if (!result.success || !result.token) {
16
+ throw new Error(result.error || 'Login failed');
17
+ }
18
+
19
+ await authentication.applySessionToken(result.token);
20
+ console.log('QA user logged in successfully');
21
+
22
+ await local.setItem('memberId', result.memberCMSId);
23
+ const queryParams = new URLSearchParams({ ...restQueryParams, token: result.memberCMSId });
24
+ const redirectUrl = redirectTo ? `/${redirectTo}?${queryParams.toString()}` : '/';
25
+
26
+ await wixLocationFrontend.to(redirectUrl);
27
+ } catch (error) {
28
+ console.error('QA login failed:', error);
29
+
30
+ const qaTextElement = _$w('#qaText');
31
+ if (qaTextElement) {
32
+ qaTextElement.text = 'Login failed: ' + (error.message || 'Unknown error');
33
+ }
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ qaPageOnReady,
39
+ };
@@ -0,0 +1,19 @@
1
+ const { window } = require('@wix/site-window');
2
+
3
+ function deleteConfirmOnReady({ $w: _$w }) {
4
+ _$w('#delete').onClick(() => {
5
+ window.lightbox.close({
6
+ toDelete: true,
7
+ });
8
+ });
9
+
10
+ _$w('#cancel').onClick(() => {
11
+ window.lightbox.close({
12
+ toDelete: false,
13
+ });
14
+ });
15
+ }
16
+
17
+ module.exports = {
18
+ deleteConfirmOnReady,
19
+ };
package/pages/index.js CHANGED
@@ -3,6 +3,6 @@ module.exports = {
3
3
  ...require('./Profile.js'),
4
4
  ...require('./Home.js'),
5
5
  ...require('./personalDetails.js'),
6
+ ...require('./QAPage.js'),
6
7
  ...require('./LoadingPage.js'),
7
- ...require('./SaveAlerts.js'),
8
8
  };
@@ -168,4 +168,5 @@ module.exports = {
168
168
  calculateDistance,
169
169
  toRadians,
170
170
  generateId,
171
+ formatAddress,
171
172
  };
package/public/consts.js CHANGED
@@ -12,6 +12,7 @@ const COLLECTIONS = {
12
12
  INTERESTS: 'interests',
13
13
  STATE_CITY_MAP: 'City',
14
14
  UPDATED_LOGIN_EMAILS: 'updatedLoginEmails',
15
+ QA_Users: 'QA_Users', //Make QA users configurable per site
15
16
  };
16
17
 
17
18
  /**
@@ -95,8 +96,6 @@ const PAGES_PATHS = {
95
96
  MEMBERS_FORM: 'directory-website-update',
96
97
  };
97
98
 
98
- const ABMP_MEMBERS_HOME_URL = 'https://www.abmp.com/members';
99
-
100
99
  module.exports = {
101
100
  REGEX,
102
101
  COLLECTIONS,
@@ -109,5 +108,4 @@ module.exports = {
109
108
  FREE_WEBSITE_TEXT_STATES,
110
109
  DEFAULT_BUSINESS_NAME_TEXT,
111
110
  PAGES_PATHS,
112
- ABMP_MEMBERS_HOME_URL,
113
111
  };
package/public/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  module.exports = {
2
2
  ...require('./consts'),
3
3
  ...require('./messages'),
4
+ ...require('./Utils/sharedUtils'),
4
5
  };
@@ -1,13 +0,0 @@
1
- const { lightbox } = require('@wix/site-window');
2
-
3
- function saveAlertsOnReady({ $w: _$w }) {
4
- const receivedData = lightbox.getContext();
5
- _$w('#closeButton').onClick(async () => await lightbox.close());
6
- _$w('#cancelButton').onClick(async () => await lightbox.close());
7
- _$w('#leaveButton').link = receivedData?.membersExternalPortalUrl;
8
- _$w('#leaveButton').target = '_blank';
9
- }
10
-
11
- module.exports = {
12
- saveAlertsOnReady,
13
- };