abmp-npm 1.8.43 → 1.9.0
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/consts.js +24 -7
- package/backend/daily-pull/consts.js +0 -3
- package/backend/daily-pull/sync-to-cms-methods.js +10 -6
- package/backend/daily-pull/utils.js +3 -3
- package/backend/data-hooks.js +29 -0
- package/backend/index.js +3 -1
- package/backend/jobs.js +14 -3
- package/backend/members-data-methods.js +231 -83
- package/backend/pac-api-methods.js +3 -4
- package/backend/search-filters-methods.js +3 -0
- package/backend/sso-methods.js +161 -0
- package/backend/tasks/consts.js +19 -0
- package/backend/tasks/index.js +6 -0
- package/backend/tasks/migration-methods.js +26 -0
- package/backend/tasks/tasks-configs.js +124 -0
- package/backend/tasks/tasks-helpers-methods.js +419 -0
- package/backend/tasks/tasks-process-methods.js +545 -0
- package/backend/utils.js +47 -28
- package/package.json +13 -2
- package/pages/LoadingPage.js +20 -0
- package/pages/Profile.js +2 -2
- package/pages/Save Alerts.js +14 -0
- package/pages/index.js +1 -0
- package/pages/personalDetails.js +12 -8
- package/public/Utils/sharedUtils.js +0 -1
- package/public/consts.js +8 -23
- package/public/index.js +0 -1
- package/public/sso-auth-methods.js +43 -0
- package/backend/routers-methods.js +0 -186
- package/backend/routers-utils.js +0 -158
- package/backend/tasks.js +0 -37
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { window: wixWindow, rendering } = require('@wix/site-window');
|
|
2
|
+
|
|
3
|
+
const { LIGHTBOX_NAMES } = require('../public/consts');
|
|
4
|
+
const { checkAndLogin } = require('../public/sso-auth-methods');
|
|
5
|
+
|
|
6
|
+
async function loadingPageOnReady(authenticateSSOToken) {
|
|
7
|
+
const renderingEnv = await rendering.env();
|
|
8
|
+
//This calls needs to triggered on client side, otherwise PAC API will return 401 error
|
|
9
|
+
if (renderingEnv === 'browser') {
|
|
10
|
+
//Need to pass processSubmission to checkAndLogin so it will run as a web method not a public one.
|
|
11
|
+
await checkAndLogin(authenticateSSOToken).catch(error => {
|
|
12
|
+
wixWindow.openLightbox(LIGHTBOX_NAMES.LOGIN_ERROR_ALERT);
|
|
13
|
+
console.error(`Something went wrong while logging in: ${error}`);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
loadingPageOnReady,
|
|
20
|
+
};
|
package/pages/Profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { location: wixLocation } = require('@wix/site-location');
|
|
2
2
|
const { window: wixWindow } = require('@wix/site-window');
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { LIGHTBOX_NAMES } = require('../public/consts');
|
|
5
5
|
const { generateId, formatPracticeAreasForDisplay } = require('../public/Utils/sharedUtils');
|
|
6
6
|
|
|
7
7
|
const TESTIMONIALS_PER_PAGE_CONFIG = {
|
|
@@ -120,7 +120,7 @@ async function profileOnReady({ $w: _$w }) {
|
|
|
120
120
|
if (profileData.profileImage) {
|
|
121
121
|
_$w('#profileImage').src = profileData.profileImage;
|
|
122
122
|
} else {
|
|
123
|
-
_$w('#profileImage').src =
|
|
123
|
+
_$w('#profileImage').src = profileData.defaultProfileImage;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { lightbox } = require('@wix/site-window');
|
|
2
|
+
|
|
3
|
+
const { ABMP_MEMBERS_HOME_URL } = require('../public/consts');
|
|
4
|
+
|
|
5
|
+
function saveAlertsOnReady({ $w: _$w }) {
|
|
6
|
+
_$w('#closeButton').onClick(async () => await lightbox.close());
|
|
7
|
+
_$w('#cancelButton').onClick(async () => await lightbox.close());
|
|
8
|
+
_$w('#leaveButton').link = ABMP_MEMBERS_HOME_URL;
|
|
9
|
+
_$w('#leaveButton').target = '_blank';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
saveAlertsOnReady,
|
|
14
|
+
};
|
package/pages/index.js
CHANGED
package/pages/personalDetails.js
CHANGED
|
@@ -3,7 +3,6 @@ const { window: wixWindow } = require('@wix/site-window');
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
|
-
ABMP_MEMBERS_HOME_URL,
|
|
7
6
|
ADDRESS_STATUS_TYPES,
|
|
8
7
|
DEFAULT_BUSINESS_NAME_TEXT,
|
|
9
8
|
FREE_WEBSITE_TEXT_STATES,
|
|
@@ -34,6 +33,7 @@ const MAIN_STATE_BOX_STATES = {
|
|
|
34
33
|
FORM_STATE: 'formState',
|
|
35
34
|
UNAUTHORIZED_STATE: 'unauthorizedState',
|
|
36
35
|
ERROR_STATE: 'errorState',
|
|
36
|
+
LOADING_STATE: 'loading',
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const FALLBACK_ADDRESS_STATUS = ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
@@ -99,7 +99,7 @@ async function personalDetailsOnReady({
|
|
|
99
99
|
_$w('#mainMultiStateBox').changeState(MAIN_STATE_BOX_STATES.UNAUTHORIZED_STATE);
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
let memberData, isValid, isStudent;
|
|
102
|
+
let memberData, isValid, isStudent, membersExternalPortalUrl;
|
|
103
103
|
|
|
104
104
|
// Main initialization
|
|
105
105
|
const queryParams = await wixLocation.query();
|
|
@@ -117,10 +117,12 @@ async function personalDetailsOnReady({
|
|
|
117
117
|
const {
|
|
118
118
|
memberData: { isStudent: _isStudent, ...memberDataResponse },
|
|
119
119
|
isValid: isValidResponse,
|
|
120
|
+
membersExternalPortalUrl: _membersExternalPortalUrl,
|
|
120
121
|
} = await validateMemberToken(memberTokenId);
|
|
121
122
|
memberData = memberDataResponse;
|
|
122
123
|
isValid = isValidResponse;
|
|
123
124
|
isStudent = _isStudent;
|
|
125
|
+
membersExternalPortalUrl = _membersExternalPortalUrl;
|
|
124
126
|
} catch (error) {
|
|
125
127
|
console.error(`Error in validateMemberToken memberTokenId : ${memberTokenId}`, error);
|
|
126
128
|
_$w('#mainMultiStateBox').changeState(MAIN_STATE_BOX_STATES.ERROR_STATE);
|
|
@@ -141,9 +143,11 @@ async function personalDetailsOnReady({
|
|
|
141
143
|
try {
|
|
142
144
|
const isFormHasUnsavedChanges = Object.values(formHasUnsavedChanges).some(Boolean);
|
|
143
145
|
if (isFormHasUnsavedChanges) {
|
|
144
|
-
wixWindow.openLightbox(LIGHTBOX_NAMES.SAVE_ALERT
|
|
146
|
+
wixWindow.openLightbox(LIGHTBOX_NAMES.SAVE_ALERT, {
|
|
147
|
+
membersExternalPortalUrl,
|
|
148
|
+
});
|
|
145
149
|
} else {
|
|
146
|
-
await wixLocation.to(
|
|
150
|
+
await wixLocation.to(membersExternalPortalUrl);
|
|
147
151
|
}
|
|
148
152
|
} catch (error) {
|
|
149
153
|
console.error('Logout failed:', error);
|
|
@@ -617,11 +621,11 @@ async function personalDetailsOnReady({
|
|
|
617
621
|
|
|
618
622
|
// Get memberships array
|
|
619
623
|
const memberships = Array.isArray(itemMemberObj.memberships) ? itemMemberObj.memberships : [];
|
|
620
|
-
// Find
|
|
621
|
-
const
|
|
624
|
+
// Find Site Association Member Since
|
|
625
|
+
const siteAssociationMemberSince = memberships.find(m => m.isSiteAssociation)?.membersince;
|
|
622
626
|
// Set yearJoinedText
|
|
623
|
-
if (
|
|
624
|
-
_$w('#yearJoinedText').text =
|
|
627
|
+
if (siteAssociationMemberSince) {
|
|
628
|
+
_$w('#yearJoinedText').text = siteAssociationMemberSince;
|
|
625
629
|
} else {
|
|
626
630
|
_$w('#yearJoinedText').text = 'Year joined not provided';
|
|
627
631
|
}
|
package/public/consts.js
CHANGED
|
@@ -11,6 +11,7 @@ const COLLECTIONS = {
|
|
|
11
11
|
STATE: 'State',
|
|
12
12
|
INTERESTS: 'interests',
|
|
13
13
|
STATE_CITY_MAP: 'City',
|
|
14
|
+
UPDATED_LOGIN_EMAILS: 'updatedLoginEmails',
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -83,31 +84,19 @@ const LIGHTBOX_NAMES = {
|
|
|
83
84
|
CONTACT_US: 'Contact Us',
|
|
84
85
|
};
|
|
85
86
|
|
|
86
|
-
const ABMP_MEMBERS_HOME_URL = 'https://www.abmp.com/members';
|
|
87
|
-
|
|
88
87
|
const FREE_WEBSITE_TEXT_STATES = {
|
|
89
88
|
ENABLED: 'This is the default and will auto-populate with the information entered on this page.',
|
|
90
89
|
DISABLED: 'To deactivate, please opt in via Personal Details.',
|
|
91
90
|
};
|
|
92
91
|
|
|
93
92
|
const DEFAULT_BUSINESS_NAME_TEXT = 'Business name not provided';
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const DEFAULT_PROFILE_IMAGE =
|
|
99
|
-
'https://static.wixstatic.com/media/1d7134_e052e9b1d0a543d0980650e16dd6d374~mv2.jpg';
|
|
100
|
-
|
|
101
|
-
const ABMP_LOGO_URL =
|
|
102
|
-
'https://static.wixstatic.com/media/3eb9c9_b7447dc19d1b48cc99348a828cf77278~mv2.png';
|
|
103
|
-
|
|
104
|
-
const SITE_ASSOCIATION = 'ABMP';
|
|
105
|
-
|
|
106
|
-
const MEMBERSHIPS_TYPES = {
|
|
107
|
-
STUDENT: 'Student',
|
|
108
|
-
PAC_STAFF: 'PAC STAFF',
|
|
93
|
+
const PAGES_PATHS = {
|
|
94
|
+
PROFILE: 'profile',
|
|
95
|
+
MEMBERS_FORM: 'directory-website-update',
|
|
109
96
|
};
|
|
110
97
|
|
|
98
|
+
const ABMP_MEMBERS_HOME_URL = 'https://www.abmp.com/members';
|
|
99
|
+
|
|
111
100
|
module.exports = {
|
|
112
101
|
REGEX,
|
|
113
102
|
COLLECTIONS,
|
|
@@ -117,12 +106,8 @@ module.exports = {
|
|
|
117
106
|
DEBOUNCE_DELAY,
|
|
118
107
|
MEMBERS_FIELDS,
|
|
119
108
|
LIGHTBOX_NAMES,
|
|
120
|
-
ABMP_MEMBERS_HOME_URL,
|
|
121
109
|
FREE_WEBSITE_TEXT_STATES,
|
|
122
110
|
DEFAULT_BUSINESS_NAME_TEXT,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ABMP_LOGO_URL,
|
|
126
|
-
SITE_ASSOCIATION,
|
|
127
|
-
MEMBERSHIPS_TYPES,
|
|
111
|
+
PAGES_PATHS,
|
|
112
|
+
ABMP_MEMBERS_HOME_URL,
|
|
128
113
|
};
|
package/public/index.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { location: wixLocationFrontend } = require('@wix/site-location');
|
|
2
|
+
const { authentication } = require('@wix/site-members');
|
|
3
|
+
const { local } = require('@wix/site-storage');
|
|
4
|
+
|
|
5
|
+
const { PAGES_PATHS } = require('./consts');
|
|
6
|
+
|
|
7
|
+
const checkAndLogin = async authenticateSSOToken => {
|
|
8
|
+
const query = await wixLocationFrontend.query();
|
|
9
|
+
const token = query['token']?.trim();
|
|
10
|
+
try {
|
|
11
|
+
if (token) {
|
|
12
|
+
const authObj = await authenticateSSOToken(token);
|
|
13
|
+
console.log('authObj', authObj);
|
|
14
|
+
if (authObj.type == 'success') {
|
|
15
|
+
console.log('success');
|
|
16
|
+
await Promise.all([
|
|
17
|
+
authentication.applySessionToken(authObj?.sessionToken),
|
|
18
|
+
local.setItem('memberId', authObj.memberId),
|
|
19
|
+
]);
|
|
20
|
+
console.log('memberId', authObj.memberId);
|
|
21
|
+
const queryParams = {
|
|
22
|
+
...query,
|
|
23
|
+
token: authObj?.memberId,
|
|
24
|
+
};
|
|
25
|
+
const redirectTo = `${PAGES_PATHS.MEMBERS_FORM}?${new URLSearchParams(queryParams).toString()}`;
|
|
26
|
+
await wixLocationFrontend.to(`/${redirectTo}`);
|
|
27
|
+
} else {
|
|
28
|
+
console.error('Something went wrong while logging in');
|
|
29
|
+
throw new Error('Authentication failed - invalid response from server');
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
console.log('checkAndLogin: No token found');
|
|
33
|
+
throw new Error('No authentication token found in URL');
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error in checkAndLogin', error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
checkAndLogin,
|
|
43
|
+
};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
const { DEFAULT_SEO_DESCRIPTION, ABMP_LOGO_URL } = require('../public');
|
|
2
|
-
|
|
3
|
-
const { fetchAllItemsInParallel: _fetchAllItemsInParallel } = require('./cms-data-methods');
|
|
4
|
-
const { getMemberBySlug } = require('./members-data-methods');
|
|
5
|
-
const { generateSEOTitle, stripHtmlTags, getMemberProfileData } = require('./routers-utils');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Profile router handler
|
|
9
|
-
* @param {Object} request - Router request object
|
|
10
|
-
* @param {Object} dependencies - Dependencies (ok, notFound, redirect, sendStatus)
|
|
11
|
-
* @returns {Promise} Router response
|
|
12
|
-
*/
|
|
13
|
-
async function profileRouter(request, dependencies) {
|
|
14
|
-
const { ok, notFound, redirect, sendStatus } = dependencies;
|
|
15
|
-
|
|
16
|
-
const slug = request.path[0];
|
|
17
|
-
if (!slug) {
|
|
18
|
-
return redirect(request.baseUrl);
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const member = await getMemberBySlug({
|
|
22
|
-
slug,
|
|
23
|
-
excludeDropped: true,
|
|
24
|
-
excludeSearchedMember: false,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
if (!member) {
|
|
28
|
-
return notFound();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const profileData = getMemberProfileData(member);
|
|
32
|
-
|
|
33
|
-
if (profileData && profileData.showWixUrl) {
|
|
34
|
-
const ogImage = profileData.profileImage || profileData.logoImage || ABMP_LOGO_URL;
|
|
35
|
-
const seoTitle = generateSEOTitle(profileData.fullName, profileData.areasOfPractices);
|
|
36
|
-
// Use stripped HTML from aboutService rich text content
|
|
37
|
-
let description = stripHtmlTags(profileData.aboutService) || DEFAULT_SEO_DESCRIPTION;
|
|
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 = `https://www.abmpmembers.com/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(', ') : ''}, ABMP, ${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: 'ABMP 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, seoData);
|
|
123
|
-
}
|
|
124
|
-
return notFound();
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error(error);
|
|
127
|
-
return sendStatus('500', 'Internal Server Error');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Profile sitemap generator
|
|
133
|
-
* @param {Object} _sitemapRequest - Sitemap request object
|
|
134
|
-
* @param {Object} _dependencies - Dependencies (WixRouterSitemapEntry)
|
|
135
|
-
* @returns {Promise<Array>} Sitemap entries
|
|
136
|
-
*/
|
|
137
|
-
async function profileSiteMap(_sitemapRequest, _dependencies) {
|
|
138
|
-
return [];
|
|
139
|
-
// Commented out - currently disabled in host site
|
|
140
|
-
/*
|
|
141
|
-
const { WixRouterSitemapEntry, wixData } = dependencies;
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const membersQuery = wixData
|
|
145
|
-
.query(COLLECTIONS.MEMBERS_DATA)
|
|
146
|
-
.eq('showWixUrl', true)
|
|
147
|
-
.isNotEmpty('url')
|
|
148
|
-
.ne('action', 'drop')
|
|
149
|
-
.fields('url', 'fullName');
|
|
150
|
-
|
|
151
|
-
const allMembers = await _fetchAllItemsInParallel(membersQuery);
|
|
152
|
-
|
|
153
|
-
const batchSize = 1000;
|
|
154
|
-
const sitemapEntries = [];
|
|
155
|
-
const totalItems = allMembers.items.length;
|
|
156
|
-
|
|
157
|
-
for (let i = 0; i < totalItems; i += batchSize) {
|
|
158
|
-
const batch = allMembers.items.slice(i, i + batchSize);
|
|
159
|
-
const batchEntries = batch.map(member => {
|
|
160
|
-
const entry = new WixRouterSitemapEntry(member.fullName);
|
|
161
|
-
entry.pageName = 'profile';
|
|
162
|
-
entry.url = `profile/${member.url}`;
|
|
163
|
-
entry.title = member.fullName;
|
|
164
|
-
entry.changeFrequency = 'monthly';
|
|
165
|
-
entry.priority = 1.0;
|
|
166
|
-
return entry;
|
|
167
|
-
});
|
|
168
|
-
sitemapEntries.push(...batchEntries);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return sitemapEntries;
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('Error generating profile sitemap:', error);
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
*/
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
module.exports = {
|
|
180
|
-
profileRouter,
|
|
181
|
-
profileSiteMap,
|
|
182
|
-
// Re-export utilities for backward compatibility
|
|
183
|
-
getMemberProfileData,
|
|
184
|
-
generateSEOTitle,
|
|
185
|
-
stripHtmlTags,
|
|
186
|
-
};
|
package/backend/routers-utils.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
const { ADDRESS_STATUS_TYPES, SITE_ASSOCIATION, MEMBERSHIPS_TYPES } = require('../public');
|
|
2
|
-
const { formatAddress, generateId, getMainAddress } = require('../public/Utils/sharedUtils');
|
|
3
|
-
|
|
4
|
-
const { formatDateToMonthYear } = require('./utils');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generates SEO title for member profile
|
|
8
|
-
* @param {string} fullName - Member's full name
|
|
9
|
-
* @param {Array<string>} areasOfPractices - Member's areas of practice
|
|
10
|
-
* @returns {string} SEO title
|
|
11
|
-
*/
|
|
12
|
-
function generateSEOTitle(fullName, areasOfPractices) {
|
|
13
|
-
return `${fullName}${
|
|
14
|
-
areasOfPractices && areasOfPractices.length > 0
|
|
15
|
-
? ` | ${areasOfPractices.slice(0, 3).join(', ')}`
|
|
16
|
-
: ''
|
|
17
|
-
} | ABMP Member`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Strips HTML tags and decodes HTML entities from a string
|
|
22
|
-
* @param {string} html - HTML string to clean
|
|
23
|
-
* @returns {string} Cleaned text
|
|
24
|
-
*/
|
|
25
|
-
function stripHtmlTags(html) {
|
|
26
|
-
if (!html) return '';
|
|
27
|
-
// Remove HTML tags and decode HTML entities
|
|
28
|
-
return html
|
|
29
|
-
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
|
30
|
-
.replace(/ /g, ' ') // Replace non-breaking spaces
|
|
31
|
-
.replace(/&/g, '&') // Replace encoded ampersands
|
|
32
|
-
.replace(/</g, '<') // Replace encoded less than
|
|
33
|
-
.replace(/>/g, '>') // Replace encoded greater than
|
|
34
|
-
.replace(/"/g, '"') // Replace encoded quotes
|
|
35
|
-
.replace(/'/g, "'") // Replace encoded apostrophes
|
|
36
|
-
.replace(/\s+/g, ' ') // Replace multiple whitespace with single space
|
|
37
|
-
.trim(); // Remove leading/trailing whitespace
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if member has student membership
|
|
42
|
-
* @param {Object} member - Member object
|
|
43
|
-
* @param {boolean} checkAssociation - Whether to check for specific association
|
|
44
|
-
* @returns {boolean} True if member has student membership
|
|
45
|
-
*/
|
|
46
|
-
function hasStudentMembership(member, checkAssociation) {
|
|
47
|
-
const memberships = member?.memberships;
|
|
48
|
-
if (!Array.isArray(memberships)) return false;
|
|
49
|
-
|
|
50
|
-
return memberships.some(membership => {
|
|
51
|
-
const isStudent = membership.membertype === MEMBERSHIPS_TYPES.STUDENT;
|
|
52
|
-
const hasCorrectAssociation = !checkAssociation || membership.association === SITE_ASSOCIATION;
|
|
53
|
-
return isStudent && hasCorrectAssociation;
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check if member should have student badge
|
|
59
|
-
* @param {Object} member - Member object
|
|
60
|
-
* @returns {boolean} True if should have badge
|
|
61
|
-
*/
|
|
62
|
-
function shouldHaveStudentBadge(member) {
|
|
63
|
-
return hasStudentMembership(member, true);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get addresses by status, excluding main address
|
|
68
|
-
* @param {Array} addresses - All addresses
|
|
69
|
-
* @param {Array} addressDisplayOption - Display options
|
|
70
|
-
* @returns {Array} Processed addresses
|
|
71
|
-
*/
|
|
72
|
-
function getAddressesByStatus(addresses = [], addressDisplayOption = []) {
|
|
73
|
-
const visible = addresses.filter(addr => addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW);
|
|
74
|
-
if (visible.length < 2) {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
const opts = Array.isArray(addressDisplayOption) ? addressDisplayOption : [];
|
|
78
|
-
const mainOpt = opts.find(o => o.isMain);
|
|
79
|
-
const mainKey = mainOpt ? mainOpt.key : visible[0].key;
|
|
80
|
-
return visible
|
|
81
|
-
.filter(addr => addr?.key !== mainKey)
|
|
82
|
-
.map(addr => {
|
|
83
|
-
const addressString = formatAddress(addr);
|
|
84
|
-
return addressString ? { _id: generateId(), address: addressString } : null;
|
|
85
|
-
})
|
|
86
|
-
.filter(Boolean);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get member profile data formatted for display
|
|
91
|
-
* @param {Object} member - Member object
|
|
92
|
-
* @returns {Object} Formatted profile data
|
|
93
|
-
*/
|
|
94
|
-
function getMemberProfileData(member) {
|
|
95
|
-
if (!member) {
|
|
96
|
-
throw new Error('member is required');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const addresses = member.addresses || [];
|
|
100
|
-
const licenceNo = member.licenses
|
|
101
|
-
?.map(val => val.license)
|
|
102
|
-
.filter(Boolean)
|
|
103
|
-
.join(', ');
|
|
104
|
-
const processedAddresses = getAddressesByStatus(member.addresses, member.addressDisplayOption);
|
|
105
|
-
|
|
106
|
-
const memberships = member.memberships || [];
|
|
107
|
-
const abmp = memberships.find(m => m.association === SITE_ASSOCIATION);
|
|
108
|
-
|
|
109
|
-
const areasOfPractices =
|
|
110
|
-
member.areasOfPractices
|
|
111
|
-
?.filter(item => typeof item === 'string' && item.trim().length > 0)
|
|
112
|
-
.map(item => item.trim())
|
|
113
|
-
.sort((a, b) =>
|
|
114
|
-
a.localeCompare(b, undefined, {
|
|
115
|
-
sensitivity: 'base',
|
|
116
|
-
numeric: true,
|
|
117
|
-
})
|
|
118
|
-
) || [];
|
|
119
|
-
|
|
120
|
-
const mainAddress = getMainAddress(member.addressDisplayOption, addresses);
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
mainAddress: mainAddress,
|
|
124
|
-
testimonials: member.testimonial || [],
|
|
125
|
-
licenceNo,
|
|
126
|
-
processedAddresses,
|
|
127
|
-
memberSince: (member.showABMP && abmp && formatDateToMonthYear(abmp?.membersince)) || '',
|
|
128
|
-
shouldHaveStudentBadge: shouldHaveStudentBadge(member),
|
|
129
|
-
logoImage: member.logoImage,
|
|
130
|
-
fullName: member.fullName,
|
|
131
|
-
profileImage: member.profileImage,
|
|
132
|
-
showContactForm: member.showContactForm,
|
|
133
|
-
bookingUrl: member.bookingUrl,
|
|
134
|
-
aboutService: member.aboutService,
|
|
135
|
-
businessName: (member.showBusinessName && member.businessName) || '',
|
|
136
|
-
phone: member.toShowPhone || '',
|
|
137
|
-
areasOfPractices,
|
|
138
|
-
gallery: member.gallery,
|
|
139
|
-
bannerImages: member.bannerImages,
|
|
140
|
-
showWixUrl: member.showWixUrl,
|
|
141
|
-
_id: member._id,
|
|
142
|
-
url: member.url,
|
|
143
|
-
city: mainAddress?.city || '',
|
|
144
|
-
state: mainAddress?.state || '',
|
|
145
|
-
isPrivateMember: member.memberships.some(
|
|
146
|
-
membership => membership.membertype === MEMBERSHIPS_TYPES.PAC_STAFF
|
|
147
|
-
),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
module.exports = {
|
|
152
|
-
generateSEOTitle,
|
|
153
|
-
stripHtmlTags,
|
|
154
|
-
hasStudentMembership,
|
|
155
|
-
shouldHaveStudentBadge,
|
|
156
|
-
getAddressesByStatus,
|
|
157
|
-
getMemberProfileData,
|
|
158
|
-
};
|
package/backend/tasks.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const { TASKS_NAMES } = require('./consts');
|
|
2
|
-
const { MEMBER_ACTIONS, synchronizeSinglePage, syncMembersDataPerAction } = require('./daily-pull');
|
|
3
|
-
|
|
4
|
-
const getDailyMembersDataSyncChildTasks = () => {
|
|
5
|
-
// we don't want to sync none action as it means this members data hasn't changed and we don't need to sync it
|
|
6
|
-
const MEMBER_ACTIONS_EXCEPT_NONE = Object.values(MEMBER_ACTIONS).filter(
|
|
7
|
-
action => action !== MEMBER_ACTIONS.NONE
|
|
8
|
-
);
|
|
9
|
-
return MEMBER_ACTIONS_EXCEPT_NONE.map(action => ({
|
|
10
|
-
name: TASKS_NAMES.ScheduleMembersDataPerAction,
|
|
11
|
-
data: { action },
|
|
12
|
-
}));
|
|
13
|
-
};
|
|
14
|
-
const TASKS = {
|
|
15
|
-
[TASKS_NAMES.ScheduleDailyMembersDataSync]: {
|
|
16
|
-
name: TASKS_NAMES.ScheduleDailyMembersDataSync,
|
|
17
|
-
scheduleChildrenSequentially: false,
|
|
18
|
-
estimatedDurationSec: 60,
|
|
19
|
-
childTasks: getDailyMembersDataSyncChildTasks(),
|
|
20
|
-
},
|
|
21
|
-
[TASKS_NAMES.ScheduleMembersDataPerAction]: {
|
|
22
|
-
name: TASKS_NAMES.ScheduleMembersDataPerAction,
|
|
23
|
-
getIdentifier: task => task.data.action,
|
|
24
|
-
process: syncMembersDataPerAction,
|
|
25
|
-
shouldSkipCheck: () => false,
|
|
26
|
-
estimatedDurationSec: 6,
|
|
27
|
-
},
|
|
28
|
-
[TASKS_NAMES.SyncMembers]: {
|
|
29
|
-
name: TASKS_NAMES.SyncMembers,
|
|
30
|
-
getIdentifier: task => task,
|
|
31
|
-
process: synchronizeSinglePage,
|
|
32
|
-
shouldSkipCheck: () => false,
|
|
33
|
-
estimatedDurationSec: 6,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
module.exports = { TASKS };
|