abmp-npm 2.0.73 → 2.0.75

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 (86) hide show
  1. package/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +19 -0
  2. package/.claude/worktrees/fix-save-button-disable/.prettierignore +7 -0
  3. package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +16 -0
  4. package/.claude/worktrees/fix-save-button-disable/README.md +1 -0
  5. package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +136 -0
  6. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +124 -0
  7. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +129 -0
  8. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +49 -0
  9. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +129 -0
  10. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +194 -0
  11. package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +13 -0
  12. package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +251 -0
  13. package/.claude/worktrees/fix-save-button-disable/backend/consts.js +58 -0
  14. package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +67 -0
  15. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +222 -0
  16. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +39 -0
  17. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +5 -0
  18. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +213 -0
  19. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +41 -0
  20. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +161 -0
  21. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +151 -0
  22. package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +29 -0
  23. package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +58 -0
  24. package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +18 -0
  25. package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +52 -0
  26. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +86 -0
  27. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +3 -0
  28. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +35 -0
  29. package/.claude/worktrees/fix-save-button-disable/backend/index.js +19 -0
  30. package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +158 -0
  31. package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +27 -0
  32. package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +8 -0
  33. package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +54 -0
  34. package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +159 -0
  35. package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +111 -0
  36. package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +88 -0
  37. package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +789 -0
  38. package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +54 -0
  39. package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +3 -0
  40. package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +191 -0
  41. package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +128 -0
  42. package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +124 -0
  43. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +256 -0
  44. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +160 -0
  45. package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +34 -0
  46. package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +78 -0
  47. package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +134 -0
  48. package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +10 -0
  49. package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +46 -0
  50. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +251 -0
  51. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +433 -0
  52. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +606 -0
  53. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +378 -0
  54. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +174 -0
  55. package/.claude/worktrees/fix-save-button-disable/backend/utils.js +277 -0
  56. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +201 -0
  57. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +159 -0
  58. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +201 -0
  59. package/.claude/worktrees/fix-save-button-disable/eslint.config.js +120 -0
  60. package/.claude/worktrees/fix-save-button-disable/index.js +5 -0
  61. package/.claude/worktrees/fix-save-button-disable/package-lock.json +12160 -0
  62. package/.claude/worktrees/fix-save-button-disable/package.json +60 -0
  63. package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +129 -0
  64. package/.claude/worktrees/fix-save-button-disable/pages/Home.js +779 -0
  65. package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +27 -0
  66. package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +29 -0
  67. package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +354 -0
  68. package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +39 -0
  69. package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +13 -0
  70. package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +46 -0
  71. package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +19 -0
  72. package/.claude/worktrees/fix-save-button-disable/pages/index.js +12 -0
  73. package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +2486 -0
  74. package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +798 -0
  75. package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +25 -0
  76. package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +235 -0
  77. package/.claude/worktrees/fix-save-button-disable/public/consts.js +113 -0
  78. package/.claude/worktrees/fix-save-button-disable/public/index.js +5 -0
  79. package/.claude/worktrees/fix-save-button-disable/public/messages.js +16 -0
  80. package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +43 -0
  81. package/package.json +1 -1
  82. package/pages/Home.js +50 -16
  83. package/pages/contactForLocation.js +28 -0
  84. package/pages/index.js +1 -0
  85. package/pages/personalDetails.js +8 -3
  86. package/public/consts.js +1 -0
@@ -0,0 +1,25 @@
1
+ // handle custom validation for url inputs
2
+ function handleOnCustomValidation(element) {
3
+ element.onCustomValidation((value, reject) => {
4
+ if (isNotValidUrl(value)) {
5
+ reject('Please enter a valid URL');
6
+ }
7
+ });
8
+ }
9
+
10
+ // check if url is not valid using regex
11
+ function isNotValidUrl(url) {
12
+ // Empty URLs are considered valid (optional field)
13
+ if (!url) return false;
14
+
15
+ // URL must start with protocol OR www - handles all TLDs including multi-level and query params
16
+ const urlRegex =
17
+ /^(https?:\/\/|www\.)([da-z.-]+)\.([a-z.]{2,})([/\w .-]*)*(\?[&\w=.-]*)?(#[&\w=.-]*)?\/?$/;
18
+
19
+ return !urlRegex.test(url);
20
+ }
21
+
22
+ module.exports = {
23
+ handleOnCustomValidation,
24
+ isNotValidUrl,
25
+ };
@@ -0,0 +1,235 @@
1
+ const { ADDRESS_STATUS_TYPES, DEBOUNCE_DELAY } = require('../consts.js');
2
+
3
+ const checkAddressIsVisible = (addresses = []) => {
4
+ // ensure we always get an array
5
+ if (!Array.isArray(addresses)) return [];
6
+
7
+ return addresses.filter(
8
+ val => val.addressStatus === undefined || val.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW
9
+ );
10
+ };
11
+
12
+ //Formats an array of practice areas, showing as many as fit within 70 characters
13
+ function formatPracticeAreasForDisplay(areaOfPractices = []) {
14
+ // always return a string
15
+ if (!Array.isArray(areaOfPractices) || areaOfPractices.length === 0) {
16
+ return '';
17
+ }
18
+
19
+ // Filter out null/undefined/empty
20
+ const validAreas = areaOfPractices.filter(
21
+ area => area !== null && area !== undefined && area !== ''
22
+ );
23
+
24
+ if (validAreas.length === 0) {
25
+ return '';
26
+ }
27
+
28
+ if (validAreas.length === 1) {
29
+ return validAreas[0].length > 70 ? validAreas[0].substring(0, 67) + '...' : validAreas[0];
30
+ }
31
+
32
+ // build up to 70-char string
33
+ let current = '';
34
+ const visible = [];
35
+ for (const item of validAreas) {
36
+ const sep = visible.length ? ', ' : '';
37
+ const next = current + sep + item;
38
+ if (next.length > 70) break;
39
+ visible.push(item);
40
+ current = next;
41
+ }
42
+
43
+ // if nothing fit, at least show the first (truncated)
44
+ if (visible.length === 0) {
45
+ const first = validAreas[0];
46
+ return first.length > 67 ? first.substring(0, 67) + '...' : first;
47
+ }
48
+
49
+ const remaining = validAreas.length - visible.length;
50
+ return remaining > 0 ? `${visible.join(', ')}, +${remaining} Techniques` : visible.join(', ');
51
+ }
52
+
53
+ function debouncedFunction({ func, debounceTimeout, timeoutType, args }) {
54
+ return new Promise(resolve => {
55
+ debounceTimeout[timeoutType] = setTimeout(async () => {
56
+ try {
57
+ const response = await func(args);
58
+ resolve({ success: true, response });
59
+ } catch (error) {
60
+ console.error('Error updating results:', error);
61
+ resolve({ success: false, error });
62
+ }
63
+ }, DEBOUNCE_DELAY[timeoutType]);
64
+ });
65
+ }
66
+
67
+ const isValidLocation = location => location.latitude && location.longitude;
68
+
69
+ /**
70
+ * @param {Array} addressDisplayOption
71
+ * @param {Array} addresses
72
+ * @param {Object|boolean} [options] - Optional. Pass { requireValidCoordinates: true } for home search/distance; omit or false for profile display.
73
+ */
74
+ function findMainAddress(addressDisplayOption = [], addresses = [], options = {}) {
75
+ const requireValidCoordinates =
76
+ typeof options === 'boolean' ? options : Boolean(options?.requireValidCoordinates);
77
+ const optionsArr = Array.isArray(addressDisplayOption) ? addressDisplayOption : [];
78
+ const mainOpt = optionsArr.find(opt => opt.isMain);
79
+ if (mainOpt) {
80
+ const mainAddr = addresses.find(
81
+ addr =>
82
+ addr.key === mainOpt.key &&
83
+ addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW &&
84
+ (!requireValidCoordinates || isValidLocation(addr))
85
+ );
86
+ if (mainAddr) {
87
+ return mainAddr;
88
+ }
89
+ }
90
+ const visibleAddresses = addresses.filter(
91
+ addr =>
92
+ addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW &&
93
+ (!requireValidCoordinates || isValidLocation(addr))
94
+ );
95
+ if (visibleAddresses.length) {
96
+ return visibleAddresses[0];
97
+ }
98
+ return '';
99
+ }
100
+ function formatAddress(item) {
101
+ if (!item) return '';
102
+ let addressParts = [];
103
+ const limitedPostalCode = (item.postalcode && String(item.postalcode).slice(0, 5)) || ''; //show only 5 digits to not show full user address
104
+ const status = item.addressStatus;
105
+ switch (status) {
106
+ case ADDRESS_STATUS_TYPES.FULL_ADDRESS:
107
+ addressParts = [item.line1, item.line2, item.city, item.state, limitedPostalCode];
108
+ break;
109
+ case ADDRESS_STATUS_TYPES.STATE_CITY_ZIP:
110
+ addressParts = [item.city, item.state, limitedPostalCode];
111
+ break;
112
+ default:
113
+ if (status === ADDRESS_STATUS_TYPES.DONT_SHOW) return '';
114
+ // Legacy addresses may have no addressStatus; show city/state/zip by default
115
+ addressParts = [item.city, item.state, limitedPostalCode];
116
+ break;
117
+ }
118
+ return addressParts.filter(Boolean).join(', ');
119
+ }
120
+
121
+ /**
122
+ * @param {Array} addressDisplayOption
123
+ * @param {Array} addresses
124
+ * @param {Object|boolean} [options] - Optional. Pass { requireValidCoordinates: true } for home search/distance; omit or false for profile display.
125
+ */
126
+ function getMainAddress(addressDisplayOption = [], addresses = [], options = {}) {
127
+ const mainAddr = findMainAddress(addressDisplayOption, addresses, options);
128
+ if (mainAddr) {
129
+ return formatAddress(mainAddr);
130
+ }
131
+ return '';
132
+ }
133
+ function shuffleArray(array) {
134
+ const shuffled = [...array];
135
+ for (let i = shuffled.length - 1; i > 0; i--) {
136
+ const j = Math.floor(Math.random() * (i + 1));
137
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
138
+ }
139
+ return shuffled;
140
+ }
141
+
142
+ function toRadians(degrees) {
143
+ return degrees * (Math.PI / 180);
144
+ }
145
+
146
+ function calculateDistance(location1, location2) {
147
+ if (!isValidLocation(location1) || !isValidLocation(location2)) {
148
+ return null;
149
+ }
150
+
151
+ // Earth's radius in miles
152
+ const earthRadius = 3958.8;
153
+
154
+ // Convert latitude and longitude from degrees to radians
155
+ const latRad1 = toRadians(location1.latitude);
156
+ const lonRad1 = toRadians(location1.longitude);
157
+ const latRad2 = toRadians(location2.latitude);
158
+ const lonRad2 = toRadians(location2.longitude);
159
+
160
+ // Differences in coordinates
161
+ const latDiff = latRad2 - latRad1;
162
+ const lonDiff = lonRad2 - lonRad1;
163
+
164
+ // Haversine formula
165
+ const a =
166
+ Math.sin(latDiff / 2) * Math.sin(latDiff / 2) +
167
+ Math.cos(latRad1) * Math.cos(latRad2) * Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
168
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
169
+ const distance = earthRadius * c;
170
+
171
+ return distance;
172
+ }
173
+
174
+ /**
175
+ * Generate a unique ID
176
+ * @returns {string} Unique identifier
177
+ */
178
+ function generateId() {
179
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
180
+ }
181
+
182
+ function isWixHostedImage(imageUrl) {
183
+ return (
184
+ imageUrl?.trim() &&
185
+ (imageUrl?.startsWith('wix:') || imageUrl?.startsWith('https://static.wixstatic.com'))
186
+ );
187
+ }
188
+
189
+ /** Web URLs only: bare hostnames get https:// so they are not treated as site-relative paths. */
190
+ function normalizeExternalUrl(url) {
191
+ if (!url || typeof url !== 'string') return url;
192
+ const trimmed = url.trim();
193
+ if (!trimmed) return trimmed;
194
+ if (/^https?:\/\//i.test(trimmed)) return trimmed;
195
+ return `https://${trimmed}`;
196
+ }
197
+
198
+ /**
199
+ * Normalizes an email for comparison (lowercased + trimmed).
200
+ * Wix CRM treats emails as case-insensitive, so comparisons must too.
201
+ * @param {string} email
202
+ * @returns {string}
203
+ */
204
+ function normalizeEmail(email) {
205
+ return typeof email === 'string' ? email.trim().toLowerCase() : '';
206
+ }
207
+
208
+ /**
209
+ * Case-insensitive email equality. Two empty/missing emails are not considered a match.
210
+ * @param {string} a
211
+ * @param {string} b
212
+ * @returns {boolean}
213
+ */
214
+ function emailsMatch(a, b) {
215
+ const normalizedA = normalizeEmail(a);
216
+ const normalizedB = normalizeEmail(b);
217
+ return Boolean(normalizedA) && normalizedA === normalizedB;
218
+ }
219
+
220
+ module.exports = {
221
+ checkAddressIsVisible,
222
+ formatPracticeAreasForDisplay,
223
+ debouncedFunction,
224
+ findMainAddress,
225
+ getMainAddress,
226
+ shuffleArray,
227
+ calculateDistance,
228
+ toRadians,
229
+ generateId,
230
+ formatAddress,
231
+ isWixHostedImage,
232
+ normalizeExternalUrl,
233
+ normalizeEmail,
234
+ emailsMatch,
235
+ };
@@ -0,0 +1,113 @@
1
+ const REGEX = {
2
+ NAME: /^[a-zA-Z\s'-]{2,}$/,
3
+ MESSAGE: /^[A-Za-z0-9\s.,!?'"-]{2,}$/,
4
+ };
5
+
6
+ const COLLECTIONS = {
7
+ MEMBERS_DATA: 'MembersDataLatest',
8
+ CONTACT_US_SUBMISSIONS: 'contactUsSubmissions',
9
+ SITE_CONFIGS: 'SiteConfigs',
10
+ COMPILED_STATE_CITY_MAP: 'CompiledStateCityMap',
11
+ STATE: 'State',
12
+ INTERESTS: 'interests',
13
+ STATE_CITY_MAP: 'City',
14
+ UPDATED_LOGIN_EMAILS: 'updatedLoginEmails',
15
+ QA_USERS: 'QA_Users', //Make QA users configurable per site
16
+ BUTTON_CLICKS: 'ButtonClicks',
17
+ };
18
+
19
+ /**
20
+ * Address display status types
21
+ */
22
+ const ADDRESS_STATUS_TYPES = {
23
+ FULL_ADDRESS: 'full_address',
24
+ STATE_CITY_ZIP: 'state_city_zip',
25
+ DONT_SHOW: 'dont_show',
26
+ };
27
+
28
+ const DEFAULT_FILTER = {
29
+ skip: 0,
30
+ limit: 12,
31
+ practiceAreas: [],
32
+ practiceAreasSearch: '',
33
+ latitude: 0,
34
+ longitude: 0,
35
+ postalcode: null,
36
+ state: [],
37
+ stateSearch: '',
38
+ citySearch: '',
39
+ city: [],
40
+ searchText: null,
41
+ };
42
+ const DROPDOWN_OPTIONS = {
43
+ state: {
44
+ selectedOptions: [],
45
+ displayText: '',
46
+ },
47
+ city: {
48
+ selectedOptions: [],
49
+ displayText: '',
50
+ },
51
+ practiceAreas: {
52
+ selectedOptions: [],
53
+ displayText: '',
54
+ },
55
+ };
56
+ const DEBOUNCE_DELAY = {
57
+ searchTimeout: 1000,
58
+ filterTimeout: 300,
59
+ zeroTimeout: 0,
60
+ };
61
+ const MEMBERS_FIELDS = {
62
+ _id: '_id',
63
+ profileImage: 'profileImage',
64
+ fullName: 'fullName',
65
+ addresses: 'addresses',
66
+ aboutService: 'aboutService',
67
+ showBookingUrl: 'showBookingUrl',
68
+ bookingUrl: 'bookingUrl',
69
+ areasOfPractices: 'areasOfPractices',
70
+ url: 'url',
71
+ phones: 'phones',
72
+ toShowPhone: 'toShowPhone',
73
+ showContactForm: 'showContactForm',
74
+ website: 'website',
75
+ showWixUrl: 'showWixUrl',
76
+ memberships: 'memberships',
77
+ showWebsite: 'showWebsite',
78
+ addressDisplayOption: 'addressDisplayOption',
79
+ };
80
+
81
+ const LIGHTBOX_NAMES = {
82
+ SAVE_ALERT: 'Save Alert',
83
+ LOGIN_ERROR_ALERT: 'loginError',
84
+ DELETE_CONFIRM: 'deleteConfirm',
85
+ SELECT_BANNER_IMAGES: 'Select Banner Images',
86
+ CONTACT_US: 'Contact Us',
87
+ MAIN_ADDRESS_ERROR: 'mainAddressError',
88
+ };
89
+
90
+ const FREE_WEBSITE_TEXT_STATES = {
91
+ ENABLED: 'This is the default and will auto-populate with the information entered on this page.',
92
+ DISABLED: 'To deactivate, please opt in via Personal Details.',
93
+ };
94
+
95
+ const DEFAULT_BUSINESS_NAME_TEXT = 'Business name not provided';
96
+ const PAGES_PATHS = {
97
+ PROFILE: 'profile',
98
+ MEMBERS_FORM: 'directory-website-update',
99
+ };
100
+
101
+ module.exports = {
102
+ REGEX,
103
+ COLLECTIONS,
104
+ ADDRESS_STATUS_TYPES,
105
+ DEFAULT_FILTER,
106
+ DROPDOWN_OPTIONS,
107
+ DEBOUNCE_DELAY,
108
+ MEMBERS_FIELDS,
109
+ LIGHTBOX_NAMES,
110
+ FREE_WEBSITE_TEXT_STATES,
111
+ DEFAULT_BUSINESS_NAME_TEXT,
112
+ PAGES_PATHS,
113
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ ...require('./consts'),
3
+ ...require('./messages'),
4
+ ...require('./Utils/sharedUtils'),
5
+ };
@@ -0,0 +1,16 @@
1
+ const VALIDATION_MESSAGES = {
2
+ CONTACT_US: {
3
+ FIRST_NAME: 'Please enter a valid first name.',
4
+ LAST_NAME: 'Please enter a valid last name.',
5
+ MESSAGE: 'Please enter a valid message.',
6
+ PHONE: 'Please enter a valid US phone number.',
7
+ CAPTCHA: 'Please fix the highlighted fields before captcha verification.',
8
+ INVALID_FIELDS: 'Please fix the highlighted fields before submitting.',
9
+ SUBMISSION_FAILED: 'An error occurred. Please try again.',
10
+ SUBMISSION_SUCCESS: 'Contact submitted successfully',
11
+ },
12
+ };
13
+
14
+ module.exports = {
15
+ VALIDATION_MESSAGES,
16
+ };
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "2.0.73",
3
+ "version": "2.0.75",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "check-cycles": "madge --circular .",
package/pages/Home.js CHANGED
@@ -3,12 +3,17 @@ const { location: wixLocation } = require('@wix/site-location');
3
3
  const { window: wixWindow, rendering } = require('@wix/site-window');
4
4
  const { withWarmUpData } = require('psdev-utils/frontend');
5
5
 
6
- const { ADDRESS_STATUS_TYPES, DEFAULT_FILTER, DROPDOWN_OPTIONS } = require('../public/consts.js');
6
+ const {
7
+ ADDRESS_STATUS_TYPES,
8
+ DEFAULT_FILTER,
9
+ DROPDOWN_OPTIONS,
10
+ LIGHTBOX_NAMES,
11
+ } = require('../public/consts.js');
7
12
  const { createHomepageUtils } = require('../public/Utils/homePage.js');
8
13
  const {
9
14
  getMainAddress,
15
+ findMainAddress,
10
16
  formatPracticeAreasForDisplay,
11
- checkAddressIsVisible,
12
17
  isWixHostedImage,
13
18
  normalizeExternalUrl,
14
19
  } = require('../public/Utils/sharedUtils.js');
@@ -30,6 +35,24 @@ const pagination = {
30
35
  let searchResults = [];
31
36
  let isMobile = false;
32
37
 
38
+ const getDirectionsTarget = itemData => {
39
+ const addresses = Array.isArray(itemData?.addresses) ? itemData.addresses : [];
40
+ const mainAddr = findMainAddress(itemData?.addressDisplayOption, addresses);
41
+ if (!mainAddr) {
42
+ return { mainAddr: null, mapsLink: null };
43
+ }
44
+ const canShowDirections =
45
+ mainAddr.addressStatus === ADDRESS_STATUS_TYPES.FULL_ADDRESS &&
46
+ mainAddr.latitude &&
47
+ mainAddr.longitude;
48
+ return {
49
+ mainAddr,
50
+ mapsLink: canShowDirections
51
+ ? `https://maps.google.com/?q=${mainAddr.latitude},${mainAddr.longitude}`
52
+ : null,
53
+ };
54
+ };
55
+
33
56
  const homePageOnReady = async ({
34
57
  _$w,
35
58
  getCompiledFiltersOptions,
@@ -181,6 +204,16 @@ const homePageOnReady = async ({
181
204
  await updateResults('zeroTimeout');
182
205
  });
183
206
  const baseUrl = await wixLocation.baseUrl();
207
+ _$w('#showMaps').onClick(event => {
208
+ const member = searchResults.find(result => result._id === event.context.itemId);
209
+ if (!member) {
210
+ return;
211
+ }
212
+ const { mapsLink } = getDirectionsTarget(member);
213
+ if (!mapsLink) {
214
+ wixWindow.openLightbox(LIGHTBOX_NAMES.CONTACT_FOR_LOCATION, member);
215
+ }
216
+ });
184
217
  _$w('#profileRepeater').onItemReady(($item, itemData) => {
185
218
  // 1) safely default to arrays
186
219
  const addresses = Array.isArray(itemData.addresses) ? itemData.addresses : [];
@@ -225,23 +258,24 @@ const homePageOnReady = async ({
225
258
  $item('#milesAwayText').text = '';
226
259
  }
227
260
 
228
- // 7) "Show maps" button enabled only if there's a full address with valid coordinates
229
- const visible = checkAddressIsVisible(addresses);
230
- const fullAddressWithValidCoords = visible.find(
231
- addr =>
232
- addr.addressStatus === ADDRESS_STATUS_TYPES.FULL_ADDRESS &&
233
- addr.latitude &&
234
- addr.longitude
235
- );
261
+ // 7) "Show maps" button. Links to the primary address when it is a full address;
262
+ // otherwise it stays visible and the page-level onClick opens a lightbox.
263
+ const { mainAddr, mapsLink } = getDirectionsTarget(itemData);
236
264
 
237
- if (fullAddressWithValidCoords) {
265
+ if (!mainAddr) {
266
+ $item('#showMaps').hide();
267
+ } else {
238
268
  $item('#showMaps').enable();
239
269
  $item('#showMaps').show();
240
- const { latitude, longitude } = fullAddressWithValidCoords;
241
- $item('#showMaps').link = `https://maps.google.com/?q=${latitude},${longitude}`;
242
- $item('#showMaps').target = '_blank';
243
- } else {
244
- $item('#showMaps').hide();
270
+
271
+ if (mapsLink) {
272
+ $item('#showMaps').link = mapsLink;
273
+ $item('#showMaps').target = '_blank';
274
+ } else {
275
+ // Clear any link left over from a recycled repeater item, otherwise the
276
+ // button would still navigate to the previous member's address.
277
+ $item('#showMaps').link = undefined;
278
+ }
245
279
  }
246
280
 
247
281
  // 8) Phone / contact form
@@ -0,0 +1,28 @@
1
+ const { location: wixLocation } = require('@wix/site-location');
2
+ const { lightbox } = require('@wix/site-window');
3
+
4
+ const { PAGES_PATHS } = require('../public/consts.js');
5
+
6
+ /**
7
+ * Shown from the directory when a member's primary address is not a full address,
8
+ * so "Directions" cannot be offered. Sends the visitor to the member's profile
9
+ * page rather than opening the contact form.
10
+ */
11
+ async function contactForLocationOnReady({ $w: _$w }) {
12
+ const member = await lightbox.getContext();
13
+ const profilePath = member?.url;
14
+
15
+ if (!profilePath) {
16
+ _$w('#contact').hide();
17
+ return;
18
+ }
19
+
20
+ _$w('#contact').onClick(async () => {
21
+ await lightbox.close();
22
+ wixLocation.to(`/${PAGES_PATHS.PROFILE}/${profilePath}`);
23
+ });
24
+ }
25
+
26
+ module.exports = {
27
+ contactForLocationOnReady,
28
+ };
package/pages/index.js CHANGED
@@ -9,4 +9,5 @@ module.exports = {
9
9
  ...require('./deleteConfirm.js'),
10
10
  ...require('./SaveAlerts.js'),
11
11
  ...require('./LearnMore.js'),
12
+ ...require('./contactForLocation.js'),
12
13
  };
@@ -1262,9 +1262,10 @@ async function personalDetailsOnReady({
1262
1262
  console.groupEnd();
1263
1263
 
1264
1264
  const result = await saveData(formData);
1265
- formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.PERSONAL.section] = false;
1266
1265
 
1267
1266
  if (result.success) {
1267
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.PERSONAL.section] = false;
1268
+
1268
1269
  if (personalChanges.url && personalChanges.url !== originalUrl) {
1269
1270
  const newProfileLink = `${baseUrl}/profile/${personalChanges.url}`;
1270
1271
  console.log('🔗 Updating profile link:', {
@@ -1317,7 +1318,10 @@ async function personalDetailsOnReady({
1317
1318
  console.groupEnd();
1318
1319
 
1319
1320
  const result = await saveData(formData);
1320
- formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section] = false;
1321
+ if (result.success) {
1322
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section] = false;
1323
+ _$w('#saveBusinessButton').disable();
1324
+ }
1321
1325
  handleSaveDataFeedback(_$w('#businessMessage'), result.message);
1322
1326
  _$w('#businessNameText').text = formData.businessName || DEFAULT_BUSINESS_NAME_TEXT;
1323
1327
  }
@@ -2380,8 +2384,9 @@ async function personalDetailsOnReady({
2380
2384
  // Sync Personal Details opt-in from saved member.
2381
2385
  _$w('#optWebsiteCheckbox').checked = itemMemberObj.showWixUrl;
2382
2386
  toggleFreeWebsiteText(itemMemberObj.showWixUrl);
2387
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section] = false;
2388
+ _$w('#saveContactBookingButton').disable();
2383
2389
  }
2384
- formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section] = false;
2385
2390
  handleSaveDataFeedback(_$w('#contactMessage'), result.message);
2386
2391
  }
2387
2392
 
package/public/consts.js CHANGED
@@ -85,6 +85,7 @@ const LIGHTBOX_NAMES = {
85
85
  SELECT_BANNER_IMAGES: 'Select Banner Images',
86
86
  CONTACT_US: 'Contact Us',
87
87
  MAIN_ADDRESS_ERROR: 'mainAddressError',
88
+ CONTACT_FOR_LOCATION: 'contactForLocation',
88
89
  };
89
90
 
90
91
  const FREE_WEBSITE_TEXT_STATES = {