abmp-npm 10.0.72 → 10.0.74

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/index.js CHANGED
@@ -16,5 +16,4 @@ module.exports = {
16
16
  ...require('./dev-only-methods'),
17
17
  ...require('./tasks/migration-methods'),
18
18
  ...require('./tasks/url-migration-methods'),
19
- ...require('./public-profile-methods'),
20
19
  };
package/backend/jobs.js CHANGED
@@ -2,6 +2,7 @@ const { taskManager } = require('psdev-task-manager');
2
2
 
3
3
  const { MEMBER_ACTIONS } = require('./daily-pull/consts');
4
4
  const { TASKS_NAMES } = require('./tasks/consts');
5
+ const { dailyPullExecutionCheck } = require('./tasks/daily-pull-check-methods');
5
6
  const { TASKS } = require('./tasks/tasks-configs');
6
7
 
7
8
  async function runScheduledTasks() {
@@ -96,6 +97,16 @@ async function scheduleFixUrlsWithSpacesTask() {
96
97
  }
97
98
  }
98
99
 
100
+ async function runDailyPullExecutionCheck() {
101
+ try {
102
+ console.log('runDailyPullExecutionCheck started!');
103
+ return await dailyPullExecutionCheck({});
104
+ } catch (error) {
105
+ console.error(`Failed to runDailyPullExecutionCheck: ${error.message}`);
106
+ throw new Error(`Failed to runDailyPullExecutionCheck: ${error.message}`);
107
+ }
108
+ }
109
+
99
110
  async function updateSiteMapS3() {
100
111
  try {
101
112
  return await taskManager().schedule({
@@ -115,4 +126,5 @@ module.exports = {
115
126
  scheduleCreateContactsFromMembersTask,
116
127
  scheduleFixPrimaryAddressForMembersTask,
117
128
  scheduleFixUrlsWithSpacesTask,
129
+ runDailyPullExecutionCheck,
118
130
  };
@@ -125,5 +125,4 @@ module.exports = {
125
125
  generateSEOTitle,
126
126
  stripHtmlTags,
127
127
  getMemberProfileData,
128
- transformMemberToProfileData,
129
128
  };
@@ -22,6 +22,7 @@ const TASKS_NAMES = {
22
22
  fixPrimaryAddressChunk: 'fixPrimaryAddressChunk',
23
23
  scheduleFixUrlsWithSpaces: 'scheduleFixUrlsWithSpaces',
24
24
  fixUrlsWithSpacesChunk: 'fixUrlsWithSpacesChunk',
25
+ dailyPullExecutionCheck: 'dailyPullExecutionCheck',
25
26
  };
26
27
 
27
28
  module.exports = {
@@ -0,0 +1,58 @@
1
+ const { taskManager } = require('psdev-task-manager');
2
+ const { COLLECTIONS } = require('psdev-task-manager/public/consts');
3
+
4
+ const { wixData } = require('../elevated-modules');
5
+ const { queryAllItems } = require('../utils');
6
+
7
+ const { TASKS_NAMES } = require('./consts');
8
+
9
+ const DEFAULT_HOURS_BACK = 4;
10
+
11
+ /**
12
+ * Detects whether the daily pull was scheduled (cron / root task).
13
+ * If no `ScheduleDailyMembersDataSync` task exists in the lookback window, schedules it.
14
+ */
15
+ async function dailyPullExecutionCheck(taskData) {
16
+ const hoursBack =
17
+ taskData?.hoursBack && Number.isFinite(taskData.hoursBack)
18
+ ? taskData.hoursBack
19
+ : DEFAULT_HOURS_BACK;
20
+ const sinceDate = new Date(Date.now() - hoursBack * 60 * 60 * 1000);
21
+
22
+ console.log('dailyPullExecutionCheck started', { hoursBack, sinceDate });
23
+
24
+ const rootTasksQuery = wixData
25
+ .query(COLLECTIONS.TASKS)
26
+ .eq('name', TASKS_NAMES.ScheduleDailyMembersDataSync)
27
+ .ge('_createdDate', sinceDate);
28
+
29
+ const rootTasks = await queryAllItems(rootTasksQuery);
30
+ const rootTaskScheduled = rootTasks.length > 0;
31
+
32
+ const result = {
33
+ success: rootTaskScheduled,
34
+ sinceDate: sinceDate.toISOString(),
35
+ rootTaskName: TASKS_NAMES.ScheduleDailyMembersDataSync,
36
+ rootTasksFound: rootTasks.length,
37
+ };
38
+
39
+ if (!rootTaskScheduled) {
40
+ console.log('ScheduleDailyMembersDataSync missing in window; scheduling root daily pull', {
41
+ hoursBack,
42
+ });
43
+ await taskManager().schedule({
44
+ name: TASKS_NAMES.ScheduleDailyMembersDataSync,
45
+ data: {},
46
+ type: 'scheduled',
47
+ });
48
+ result.fallbackScheduled = true;
49
+ }
50
+
51
+ console.log('dailyPullExecutionCheck result', JSON.stringify(result, null, 2));
52
+
53
+ return result;
54
+ }
55
+
56
+ module.exports = {
57
+ dailyPullExecutionCheck,
58
+ };
@@ -6,4 +6,5 @@ module.exports = {
6
6
  ...require('./url-migration-methods'),
7
7
  ...require('./address-primary-methods'),
8
8
  ...require('./url-space-fix-methods'),
9
+ ...require('./daily-pull-check-methods'),
9
10
  };
@@ -9,6 +9,7 @@ const {
9
9
  fixPrimaryAddressChunk,
10
10
  } = require('./address-primary-methods');
11
11
  const { TASKS_NAMES } = require('./consts');
12
+ const { dailyPullExecutionCheck } = require('./daily-pull-check-methods');
12
13
  const {
13
14
  scheduleTaskForEmptyAboutYouMembers,
14
15
  convertAboutYouHtmlToRichContent,
@@ -202,6 +203,13 @@ const TASKS = {
202
203
  shouldSkipCheck: () => false,
203
204
  estimatedDurationSec: 80,
204
205
  },
206
+ [TASKS_NAMES.dailyPullExecutionCheck]: {
207
+ name: TASKS_NAMES.dailyPullExecutionCheck,
208
+ getIdentifier: task => task.data,
209
+ process: dailyPullExecutionCheck,
210
+ shouldSkipCheck: () => false,
211
+ estimatedDurationSec: 30,
212
+ },
205
213
  };
206
214
 
207
215
  module.exports = { TASKS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "10.0.72",
3
+ "version": "10.0.74",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "check-cycles": "madge --circular .",
@@ -42,7 +42,6 @@
42
42
  "@wix/secrets": "^1.0.62",
43
43
  "@wix/site-location": "^1.31.0",
44
44
  "@wix/site-members": "^1.32.0",
45
- "@wix/site-seo": "^1.22.0",
46
45
  "@wix/site-storage": "^1.22.0",
47
46
  "@wix/site-window": "^1.44.0",
48
47
  "@wix/urls": "^1.0.57",
package/pages/Home.js CHANGED
@@ -5,6 +5,7 @@ const { withWarmUpData } = require('psdev-utils/frontend');
5
5
 
6
6
  const { ADDRESS_STATUS_TYPES, DEFAULT_FILTER, DROPDOWN_OPTIONS } = require('../public/consts.js');
7
7
  const { createHomepageUtils } = require('../public/Utils/homePage.js');
8
+ const { logHomePageLoadPhase } = require('../public/Utils/homePageLoadTrace.js');
8
9
  const {
9
10
  getMainAddress,
10
11
  formatPracticeAreasForDisplay,
@@ -13,6 +14,8 @@ const {
13
14
  normalizeExternalUrl,
14
15
  } = require('../public/Utils/sharedUtils.js');
15
16
 
17
+ logHomePageLoadPhase('home_js_module_evaluated');
18
+
16
19
  let filter = JSON.parse(JSON.stringify(DEFAULT_FILTER));
17
20
  let dropDownOptions = JSON.parse(JSON.stringify(DROPDOWN_OPTIONS));
18
21
  let stateCityMap;
@@ -36,6 +39,7 @@ const homePageOnReady = async ({
36
39
  getNonCompiledFiltersOptions,
37
40
  filterProfiles,
38
41
  }) => {
42
+ logHomePageLoadPhase('wix_onready_handler_entered');
39
43
  const {
40
44
  getParamsMapping,
41
45
  handlePagination,
@@ -58,7 +62,9 @@ const homePageOnReady = async ({
58
62
  detectMobile();
59
63
  initPageUI();
60
64
  attachEventListeners();
65
+ logHomePageLoadPhase('before_handleUrlParams');
61
66
  await handleUrlParams();
67
+ logHomePageLoadPhase('after_handleUrlParams');
62
68
 
63
69
  async function detectMobile() {
64
70
  try {
@@ -279,6 +285,7 @@ const homePageOnReady = async ({
279
285
  }
280
286
 
281
287
  async function handleUrlParams() {
288
+ logHomePageLoadPhase('handleUrlParams_start');
282
289
  const { isDefaultStateParams, filter: newFilter } = await parseAndValidateQueryParams(
283
290
  filter,
284
291
  pagination
@@ -288,7 +295,9 @@ const homePageOnReady = async ({
288
295
  }
289
296
 
290
297
  async function applyFilterToUI(isDefaultStateParams) {
298
+ logHomePageLoadPhase('applyFilterToUI_start', { isDefaultStateParams });
291
299
  const renderingEnv = await rendering.env();
300
+ logHomePageLoadPhase('applyFilterToUI_rendering_env', { env: renderingEnv });
292
301
  const setFilterFromParams = async (isInitializeValue = true) => {
293
302
  const params = await wixLocation.query();
294
303
  console.log('params inside setFilterFromParams ', params);
@@ -322,7 +331,9 @@ const homePageOnReady = async ({
322
331
  await setFilterFromParams(true);
323
332
  if (isDefaultStateParams) {
324
333
  console.log('default state set for nearby');
334
+ logHomePageLoadPhase('applyFilterToUI_default_path_fetch_and_nearby_start');
325
335
  await Promise.all([fetchFilterData(), nearByHandler(true)]);
336
+ logHomePageLoadPhase('applyFilterToUI_default_path_complete');
326
337
  return;
327
338
  }
328
339
  console.log('not default state');
@@ -341,12 +352,15 @@ const homePageOnReady = async ({
341
352
  : () => updateResults('filterTimeout', true);
342
353
  console.log('filter ..', filter);
343
354
  try {
355
+ logHomePageLoadPhase('applyFilterToUI_non_default_path_start');
344
356
  await Promise.all([
345
357
  fetchFilterData().then(() => setFilterFromParams(false)),
346
358
  //TODO: remove this workaround to fix issue with SSR showing invalid results
347
359
  renderingEnv === 'backend' ? Promise.resolve() : searchPromise(),
348
360
  ]);
361
+ logHomePageLoadPhase('applyFilterToUI_non_default_path_complete');
349
362
  } catch (error) {
363
+ logHomePageLoadPhase('applyFilterToUI_error', { message: String(error && error.message) });
350
364
  console.error('[applyFilterToUI] failed with error:', error);
351
365
  multiStateBoxSelector.changeState('errorState');
352
366
  }
@@ -425,6 +439,7 @@ const homePageOnReady = async ({
425
439
  }
426
440
  // NEAR BY FILTER
427
441
  async function nearByHandler(preservePagination = false) {
442
+ logHomePageLoadPhase('nearByHandler_start', { preservePagination });
428
443
  const isSearchingNearby = _$w('#nearBy').checked;
429
444
  const renderingEnv = await rendering.env();
430
445
  // 1. Disable nearby input while processing
@@ -438,6 +453,7 @@ const homePageOnReady = async ({
438
453
  filter = newFilter;
439
454
  console.log('filter inside nearByHandler', filter);
440
455
  if (!success) {
456
+ logHomePageLoadPhase('nearByHandler_geolocation_failed');
441
457
  if (renderingEnv !== 'backend') {
442
458
  //on Backend environment, geolocation API don't work, so makes no sense to change state for near by
443
459
  multiStateBoxSelector.changeState('nearByState');
@@ -452,6 +468,7 @@ const homePageOnReady = async ({
452
468
  // If location is not selected, change state to "resultsState"
453
469
  if (!isSearchingNearby) {
454
470
  if (await noSearchCriteria()) {
471
+ logHomePageLoadPhase('nearByHandler_no_search_criteria');
455
472
  multiStateBoxSelector.changeState('noSearchCriteria');
456
473
  // 4. Re-enable nearby input
457
474
  _$w('#nearBy').enable();
@@ -463,6 +480,7 @@ const homePageOnReady = async ({
463
480
 
464
481
  // 4. Re-enable nearby input when done
465
482
  _$w('#nearBy').enable();
483
+ logHomePageLoadPhase('nearByHandler_complete', { success: true });
466
484
  return true;
467
485
  }
468
486
 
@@ -470,6 +488,7 @@ const homePageOnReady = async ({
470
488
  // FETCH STATE/CITY/AREAS OF PRACTICE FROM BACKEND ONCE AND STORE IT
471
489
 
472
490
  async function fetchFilterData() {
491
+ logHomePageLoadPhase('fetchFilterData_start');
473
492
  let completeStateList, areasOfPracticesList, stateCityMapList;
474
493
  try {
475
494
  const { COMPILED_STATE_LIST, COMPILED_AREAS_OF_PRACTICES, COMPILED_STATE_CITY_MAP } =
@@ -510,6 +529,7 @@ const homePageOnReady = async ({
510
529
 
511
530
  // Update filter states after data is loaded
512
531
  updateFiltersState();
532
+ logHomePageLoadPhase('fetchFilterData_complete');
513
533
  }
514
534
 
515
535
  // CONSTRUCT DROPDOWN OPTIONS FOR STATE, CITY, AREA OF PRACTICES
package/pages/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  module.exports = {
2
2
  ...require('./ContactUs.js'),
3
3
  ...require('./Profile.js'),
4
- ...require('./publicProfile.js'),
5
4
  ...require('./Home.js'),
6
5
  ...require('./personalDetails.js'),
7
6
  ...require('./QAPage.js'),
@@ -3,6 +3,7 @@ const { window: wixWindow, rendering } = require('@wix/site-window');
3
3
 
4
4
  const { DEFAULT_FILTER } = require('../consts.js');
5
5
 
6
+ const { logHomePageLoadPhase } = require('./homePageLoadTrace.js');
6
7
  const { debouncedFunction } = require('./sharedUtils.js');
7
8
 
8
9
  const createHomepageUtils = (_$w, filterProfiles) => {
@@ -362,6 +363,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
362
363
  });
363
364
  }
364
365
  async function getAndSetUserLocation(isSearchingNearby, filter) {
366
+ logHomePageLoadPhase('getAndSetUserLocation_start', { isSearchingNearby });
365
367
  try {
366
368
  let location = {
367
369
  coords: {
@@ -369,6 +371,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
369
371
  longitude: 0,
370
372
  },
371
373
  };
374
+ logHomePageLoadPhase('getAndSetUserLocation_before_getCurrentGeolocation');
372
375
  location = await wixWindow.getCurrentGeolocation();
373
376
 
374
377
  console.log('location inside getAndSetUserLocation', location);
@@ -384,8 +387,15 @@ const createHomepageUtils = (_$w, filterProfiles) => {
384
387
  latitude: userLat,
385
388
  longitude: userLong,
386
389
  };
390
+ logHomePageLoadPhase('getAndSetUserLocation_success', {
391
+ lat: userLat,
392
+ lng: userLong,
393
+ });
387
394
  return { success: true, filter };
388
395
  } catch (error) {
396
+ logHomePageLoadPhase('getAndSetUserLocation_error', {
397
+ message: String(error && error.message),
398
+ });
389
399
  console.warn('Failed to get user location in getAndSetUserLocation', error);
390
400
  return { success: false, filter };
391
401
  }
@@ -504,6 +514,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
504
514
  );
505
515
  }
506
516
  async function parseAndValidateQueryParams(filter, pagination) {
517
+ logHomePageLoadPhase('parseAndValidateQueryParams_start');
507
518
  const params = await wixLocation.query();
508
519
  const paramsMapping = getParamsMapping(filter, pagination);
509
520
  const {
@@ -515,6 +526,11 @@ const createHomepageUtils = (_$w, filterProfiles) => {
515
526
  const isSearchingNearby = params.nearby === 'true';
516
527
  const isNoParams = !withoutPreviewParams || Object.keys(withoutPreviewParams).length === 0;
517
528
  const { success, filter: newFilter } = await getAndSetUserLocation(isSearchingNearby, filter);
529
+ logHomePageLoadPhase('parseAndValidateQueryParams_after_geolocation', {
530
+ isNoParams,
531
+ isSearchingNearby,
532
+ success,
533
+ });
518
534
 
519
535
  // Auto-enable nearby if GPS permission granted on fresh page load
520
536
  if (
@@ -525,6 +541,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
525
541
  !isSearchingNearby
526
542
  ) {
527
543
  await wixQueryParams.add({ nearby: 'true', page: '1' });
544
+ logHomePageLoadPhase('parseAndValidateQueryParams_return', { branch: 'auto_nearby_url' });
528
545
  return { isDefaultStateParams: true, filter: newFilter };
529
546
  }
530
547
 
@@ -538,6 +555,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
538
555
  // });
539
556
  // Don't search yet - let the caller decide what to do
540
557
  // The search will be handled in applyFilterToUI
558
+ logHomePageLoadPhase('parseAndValidateQueryParams_return', { branch: 'default_no_params' });
541
559
  return { isDefaultStateParams: true, filter: newFilter };
542
560
  }
543
561
  let autoAdjustFilters = false;
@@ -577,6 +595,10 @@ const createHomepageUtils = (_$w, filterProfiles) => {
577
595
  withoutPreviewParams.page) ||
578
596
  (Object.keys(withoutPreviewParams).length === 1 && withoutPreviewParams.nearby);
579
597
  const isDefaultStateParams = isNoParams || isNearbyFilter;
598
+ logHomePageLoadPhase('parseAndValidateQueryParams_return', {
599
+ branch: 'with_query_params',
600
+ isDefaultStateParams,
601
+ });
580
602
  return { isDefaultStateParams, filter: newFilter };
581
603
  }
582
604
 
@@ -660,6 +682,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
660
682
  isSearchingNearby,
661
683
  preservePagination = false,
662
684
  }) {
685
+ logHomePageLoadPhase('search_start', { timeoutType, isSearchingNearby });
663
686
  const multiStateBoxSelector = _$w('#resultsStateBox');
664
687
  const renderingEnv = await rendering.env();
665
688
  const initSearchResultsUI = () => {
@@ -681,6 +704,7 @@ const createHomepageUtils = (_$w, filterProfiles) => {
681
704
  longitude: 0,
682
705
  }) === JSON.stringify(DEFAULT_FILTER)
683
706
  ) {
707
+ logHomePageLoadPhase('search_short_circuit_no_criteria');
684
708
  multiStateBoxSelector.changeState('noSearchCriteria');
685
709
  return [];
686
710
  }
@@ -704,14 +728,19 @@ const createHomepageUtils = (_$w, filterProfiles) => {
704
728
  timeoutType,
705
729
  args: { filter, isSearchingNearby },
706
730
  });
731
+ logHomePageLoadPhase('search_before_filterProfiles', { renderingEnv });
707
732
  const { success, response, error } = await funcPromise();
708
733
  if (!success) {
709
734
  _$w('#numberOfResults').text = '';
710
735
  console.error('[search] failed with error:', error);
736
+ logHomePageLoadPhase('search_filterProfiles_failed', {
737
+ message: String(error && error.message),
738
+ });
711
739
  multiStateBoxSelector.changeState('errorState');
712
740
  return [];
713
741
  }
714
742
  const totalCount = response.items.length;
743
+ logHomePageLoadPhase('search_filterProfiles_success', { totalCount });
715
744
  if (!totalCount) {
716
745
  _$w('#numberOfResults').text = 'Showing 0 results';
717
746
  _$w('#noResultsMessage').text = `${
@@ -0,0 +1,58 @@
1
+ /**
2
+ * One session per full page load. First log call creates `loadId` (send this to support for GCL search).
3
+ * Logs a plain object so DevTools shows an expandable tree; `loadId` is still easy to copy for GCL.
4
+ */
5
+
6
+ function randomSegment() {
7
+ return Math.random().toString(36).slice(2, 10);
8
+ }
9
+
10
+ function nowMs() {
11
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
12
+ return performance.now();
13
+ }
14
+ return Date.now();
15
+ }
16
+
17
+ let session = null;
18
+
19
+ function ensureSession() {
20
+ if (session) {
21
+ return session;
22
+ }
23
+ const t0 = nowMs();
24
+ session = {
25
+ loadId: `hpl_${Date.now()}_${randomSegment()}_${randomSegment()}`,
26
+ t0,
27
+ };
28
+ return session;
29
+ }
30
+
31
+ /**
32
+ * @param {string} phase
33
+ * @param {Record<string, unknown>} [detail]
34
+ */
35
+ function logHomePageLoadPhase(phase, detail) {
36
+ const s = ensureSession();
37
+ const elapsed = Math.round(nowMs() - s.t0);
38
+ const payload = {
39
+ type: 'HomePageLoad',
40
+ loadId: s.loadId,
41
+ phase,
42
+ elapsedSinceStartMs: elapsed,
43
+ wallTimeIso: new Date().toISOString(),
44
+ };
45
+ if (detail && typeof detail === 'object') {
46
+ payload.detail = detail;
47
+ }
48
+ console.log('[HomePageLoad]', payload);
49
+ }
50
+
51
+ function getHomePageLoadId() {
52
+ return ensureSession().loadId;
53
+ }
54
+
55
+ module.exports = {
56
+ logHomePageLoadPhase,
57
+ getHomePageLoadId,
58
+ };
package/public/consts.js CHANGED
@@ -5,7 +5,6 @@ const REGEX = {
5
5
 
6
6
  const COLLECTIONS = {
7
7
  MEMBERS_DATA: 'MembersDataLatest',
8
- MEMBERS_DATA_PUBLIC: 'MembersDataPublic',
9
8
  CONTACT_US_SUBMISSIONS: 'contactUsSubmissions',
10
9
  SITE_CONFIGS: 'SiteConfigs',
11
10
  COMPILED_STATE_CITY_MAP: 'CompiledStateCityMap',
@@ -1,54 +0,0 @@
1
- const { COLLECTIONS } = require('../public/consts');
2
-
3
- const { CONFIG_KEYS } = require('./consts');
4
- const { MEMBER_ACTIONS } = require('./daily-pull/consts');
5
- const { wixData } = require('./elevated-modules');
6
- const { findMemberByWixDataId } = require('./members-data-methods');
7
- const { transformMemberToProfileData } = require('./routers/utils');
8
- const { getSiteConfigs } = require('./utils');
9
-
10
- const getPublicMemberRecord = memberDataId =>
11
- wixData
12
- .query(COLLECTIONS.MEMBERS_DATA_PUBLIC)
13
- .eq('memberData', memberDataId)
14
- .limit(1)
15
- .find()
16
- .then(res => res.items?.[0] || null);
17
-
18
- async function getPublicMemberProfileData({ memberDataId }) {
19
- if (!memberDataId) {
20
- return null;
21
- }
22
-
23
- const publicMember = await getPublicMemberRecord(memberDataId);
24
- if (!publicMember || publicMember.showWixUrl !== true) {
25
- return null;
26
- }
27
-
28
- const member = await findMemberByWixDataId(memberDataId);
29
- if (!member || member.action === MEMBER_ACTIONS.DROP || member.showWixUrl !== true) {
30
- return null;
31
- }
32
-
33
- const siteConfigs = await getSiteConfigs();
34
- const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
35
- const defaultProfileImage = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_IMAGE];
36
-
37
- const profileData = transformMemberToProfileData(member, siteAssociation);
38
- return {
39
- profileData: { ...profileData, defaultProfileImage },
40
- };
41
- }
42
-
43
- module.exports = {
44
- getPublicMemberProfileData,
45
- async getPublicProfileSeoConfig() {
46
- const siteConfigs = await getSiteConfigs();
47
- return {
48
- siteAssociation: siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION],
49
- defaultSEODescription: siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_SEO_DESCRIPTION],
50
- siteLogoUrl: siteConfigs[CONFIG_KEYS.SITE_LOGO_URL],
51
- defaultProfileImage: siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_IMAGE],
52
- };
53
- },
54
- };
@@ -1,522 +0,0 @@
1
- const { location: wixLocation } = require('@wix/site-location');
2
- const { seo } = require('@wix/site-seo');
3
- const { window: wixWindow } = require('@wix/site-window');
4
-
5
- const { LIGHTBOX_NAMES } = require('../public/consts');
6
- const {
7
- generateId,
8
- formatPracticeAreasForDisplay,
9
- isWixHostedImage,
10
- } = require('../public/Utils/sharedUtils');
11
-
12
- const TESTIMONIALS_PER_PAGE_CONFIG = {
13
- DESKTOP: 4,
14
- TABLET: 2,
15
- MOBILE: 1,
16
- };
17
-
18
- const BREAKPOINTS = {
19
- DESKTOP: 1301,
20
- TABLET: 750,
21
- };
22
-
23
- const resolveMemberDataId = memberData => {
24
- if (!memberData) {
25
- return null;
26
- }
27
- if (typeof memberData === 'string') {
28
- return memberData;
29
- }
30
- return memberData?._id || memberData?._ref || null;
31
- };
32
-
33
- async function publicProfileOnReady({
34
- $w: _$w,
35
- getPublicMemberProfileData,
36
- getPublicProfileSeoConfig,
37
- }) {
38
- const dataset = _$w('#dynamicDataset');
39
- let profileData = null;
40
-
41
- let testimonialsPerPage = TESTIMONIALS_PER_PAGE_CONFIG.TABLET;
42
- let currentTestimonialPage = 0;
43
-
44
- if (typeof getPublicMemberProfileData !== 'function') {
45
- console.error('[publicProfileOnReady] getPublicMemberProfileData is required');
46
- wixLocation.to(`${wixLocation.baseUrl}/404`);
47
- return;
48
- }
49
-
50
- await dataset.onReadyAsync();
51
- const item = dataset.getCurrentItem();
52
-
53
- if (!item || item.showWixUrl !== true) {
54
- wixLocation.to(`${wixLocation.baseUrl}/404`);
55
- return;
56
- }
57
-
58
- const memberDataId = resolveMemberDataId(item.memberData);
59
- if (!memberDataId) {
60
- wixLocation.to(`${wixLocation.baseUrl}/404`);
61
- return;
62
- }
63
-
64
- const result = await getPublicMemberProfileData({ memberDataId });
65
- profileData = result?.profileData || null;
66
-
67
- if (!profileData) {
68
- wixLocation.to(`${wixLocation.baseUrl}/404`);
69
- return;
70
- }
71
-
72
- if (typeof getPublicProfileSeoConfig === 'function') {
73
- try {
74
- const seoConfig = await getPublicProfileSeoConfig();
75
- applySeo(profileData, seoConfig);
76
- } catch (error) {
77
- console.error('[publicProfileOnReady] Failed to set SEO', error);
78
- }
79
- }
80
-
81
- initializePage();
82
-
83
- function initializePage() {
84
- bindProfileData();
85
- setupAddressToggle();
86
- setupResponsiveTestimonials();
87
- }
88
-
89
- // Profile data binding
90
- function bindProfileData() {
91
- bindAddressData();
92
- bindMemberInfo();
93
- bindContactInfo();
94
- bindBusinessInfo();
95
- bindGalleryData();
96
- bindTestimonialsData();
97
- }
98
-
99
- function bindAddressData() {
100
- if (profileData.mainAddress) {
101
- setTextForElements(
102
- ['#LocationText', '#LocationText2', '#LocationText3'],
103
- profileData.mainAddress
104
- );
105
- } else {
106
- deleteElements(['#locationContainer', '#location1Container', '#locationContainer2']);
107
- }
108
-
109
- setupAdditionalAddresses();
110
- }
111
-
112
- function setupAdditionalAddresses() {
113
- _$w('#moreAdressesRepeater').data = profileData.moreAddressesToDisplay;
114
-
115
- if (profileData.moreAddressesToDisplay.length > 0) {
116
- _$w('#moreLocationButton').expand();
117
- _$w('#addressTitle').collapse();
118
- }
119
-
120
- _$w('#moreAdressesRepeater').onItemReady(($item, itemData) => {
121
- console.log('Item Data:', itemData);
122
- $item('#adressText').text = itemData.address || '';
123
- });
124
- }
125
-
126
- function setupAddressToggle() {
127
- toggleContainer('#moreLocationButton', '#addressContainer');
128
- }
129
-
130
- function toggleContainer(buttonId, containerId) {
131
- const $button = _$w(buttonId);
132
- const $container = _$w(containerId);
133
-
134
- $button.onClick(() => {
135
- const isCollapsed = $container.collapsed;
136
- $container[isCollapsed ? 'expand' : 'collapse']();
137
- $button.label = isCollapsed ? 'Less Locations -' : 'More Locations +';
138
- });
139
- }
140
-
141
- function bindMemberInfo() {
142
- bindMemberSince();
143
- bindStudentBadge();
144
- bindProfileImages();
145
- bindFullName();
146
- }
147
-
148
- function bindMemberSince() {
149
- if (profileData.memberSince) {
150
- _$w('#sinceYearText').text = profileData.memberSince;
151
- } else {
152
- _$w('#memberSinceBox').delete();
153
- }
154
- }
155
-
156
- function bindStudentBadge() {
157
- if (profileData.shouldHaveStudentBadge) {
158
- _$w('#studentContainer, #studentContainerMobile').expand();
159
- } else {
160
- _$w('#studentContainer, #studentContainerMobile').delete();
161
- }
162
- }
163
-
164
- function bindProfileImages() {
165
- if (profileData.logoImage) {
166
- _$w('#logoImage').src = profileData.logoImage;
167
- } else {
168
- _$w('#logoImage').delete();
169
- }
170
-
171
- if (profileData.profileImage && isWixHostedImage(profileData.profileImage)) {
172
- _$w('#profileImage').src = profileData.profileImage;
173
- } else {
174
- _$w('#profileImage').src = profileData.defaultProfileImage;
175
- }
176
- }
177
-
178
- function bindFullName() {
179
- if (profileData.fullName) {
180
- setTextForElements(
181
- ['#fullNameText', '#fullNameText2', '#fullNameTextFoter'],
182
- profileData.fullName
183
- );
184
- } else {
185
- deleteElements(['#fullNameText', '#fullNameText2', '#fullNameTextFoter']);
186
- }
187
- }
188
-
189
- // Contact information binding
190
- function bindContactInfo() {
191
- bindContactForm();
192
- bindBookingUrl();
193
- bindPhoneNumber();
194
- bindLicenseNumber();
195
- }
196
-
197
- function bindContactForm() {
198
- if (profileData.showContactForm) {
199
- _$w('#contactButton').onClick(() =>
200
- wixWindow.openLightbox(LIGHTBOX_NAMES.CONTACT_US, profileData)
201
- );
202
- } else {
203
- _$w('#contactButton').delete();
204
- }
205
- }
206
-
207
- function bindBookingUrl() {
208
- if (profileData.bookingUrl) {
209
- _$w('#bookNowButton').link = profileData.bookingUrl;
210
- } else {
211
- _$w('#bookNowButton').delete();
212
- }
213
- }
214
-
215
- function bindPhoneNumber() {
216
- if (profileData.phone) {
217
- const formattedPhoneNumber = profileData.phone.replace(/[^\d+]/g, '');
218
- const getPhoneHTML = $phoneSelector =>
219
- $phoneSelector.html.replace(
220
- $phoneSelector.text,
221
- `<a href="${`tel:${formattedPhoneNumber}`}">${profileData.phone}</a>`
222
- );
223
- _$w('#phoneText').html = getPhoneHTML(_$w('#phoneText'));
224
- _$w('#phoneText2').html = getPhoneHTML(_$w('#phoneText2'));
225
- } else {
226
- deleteElements(['#phoneContainer', '#phoneContainer2']);
227
- }
228
- }
229
-
230
- function bindLicenseNumber() {
231
- if (profileData.licenceNo) {
232
- _$w('#licenceNoText').text = profileData.licenceNo;
233
- } else {
234
- _$w('#licensesContainer').delete();
235
- }
236
- }
237
-
238
- function bindBusinessInfo() {
239
- bindAboutService();
240
- bindBusinessName();
241
- bindAreasOfPractice();
242
- }
243
-
244
- function bindAboutService() {
245
- if (profileData.aboutService) {
246
- _$w('#aboutYouText').html = profileData.aboutService;
247
- } else {
248
- _$w('#aboutSection').delete();
249
- }
250
- }
251
-
252
- function bindBusinessName() {
253
- if (profileData.businessName) {
254
- _$w('#businessName').text = profileData.businessName;
255
- _$w('#businessName').expand();
256
- } else {
257
- _$w('#businessName').delete();
258
- }
259
- }
260
-
261
- function bindAreasOfPractice() {
262
- const areasText = formatPracticeAreasForDisplay(profileData.areasOfPractices);
263
-
264
- if (areasText) {
265
- _$w('#areaOfPracticesText').text = areasText;
266
- } else {
267
- _$w('#areaOfPracticesText').delete();
268
- }
269
-
270
- if (Array.isArray(profileData.areasOfPractices) && profileData.areasOfPractices.length > 0) {
271
- populateRepeater(profileData.areasOfPractices, '#areaOfPracticesRepeater', '#practiceText');
272
- } else {
273
- _$w('#servicesSection').delete();
274
- }
275
- }
276
-
277
- function bindGalleryData() {
278
- if (profileData.bannerImages && profileData.bannerImages.length > 0) {
279
- _$w('#bannerImage').src = profileData.bannerImages[0];
280
- }
281
-
282
- if (!profileData.gallery?.length) {
283
- _$w('#gallerySection').delete();
284
- } else {
285
- _$w('#gallery').items = profileData.gallery;
286
- _$w('#gallerySection').restore();
287
- }
288
- }
289
-
290
- function bindTestimonialsData() {
291
- if (!profileData.testimonials?.length) {
292
- _$w('#testimonialsSection').delete();
293
- }
294
- }
295
-
296
- // Responsive testimonials setup
297
- async function setupResponsiveTestimonials() {
298
- const { window } = await wixWindow.getBoundingRect();
299
- testimonialsPerPage = getTestimonialsPerPage(window.width);
300
-
301
- // Monitor window resize
302
- setInterval(async () => {
303
- const { window: win } = await wixWindow.getBoundingRect();
304
- const newTestimonialsPerPage = getTestimonialsPerPage(win.width);
305
-
306
- if (newTestimonialsPerPage !== testimonialsPerPage) {
307
- testimonialsPerPage = newTestimonialsPerPage;
308
- currentTestimonialPage = 0;
309
- displayTestimonialsPage(profileData.testimonials);
310
- }
311
- }, 500);
312
-
313
- setupTestimonialsIfAvailable();
314
- }
315
-
316
- function setupTestimonialsIfAvailable() {
317
- if (profileData.testimonials.length > 0) {
318
- setupTestimonialsPagination(profileData.testimonials);
319
- _$w('#testimonialsSection').expand();
320
- } else {
321
- _$w('#testimonialsSection').delete();
322
- }
323
- }
324
-
325
- function getTestimonialsPerPage(width) {
326
- if (width >= BREAKPOINTS.DESKTOP) return TESTIMONIALS_PER_PAGE_CONFIG.DESKTOP;
327
- if (width >= BREAKPOINTS.TABLET) return TESTIMONIALS_PER_PAGE_CONFIG.TABLET;
328
- return TESTIMONIALS_PER_PAGE_CONFIG.MOBILE;
329
- }
330
-
331
- function setTextForElements(elementIds, text) {
332
- elementIds.forEach(id => {
333
- _$w(id).text = text;
334
- });
335
- }
336
-
337
- function deleteElements(elementIds) {
338
- elementIds.forEach(id => {
339
- _$w(id).delete();
340
- });
341
- }
342
-
343
- function populateRepeater(data, repeaterId, textElementId) {
344
- const repeaterData = data.map(item => ({
345
- _id: generateId(),
346
- text: item.trim(),
347
- }));
348
- _$w(repeaterId).data = repeaterData;
349
- _$w(repeaterId).onItemReady(($item, itemData) => {
350
- $item(textElementId).text = itemData.text;
351
- });
352
- }
353
-
354
- // Testimonials pagination
355
- function setupTestimonialsPagination(allTestimonials) {
356
- currentTestimonialPage = 0;
357
-
358
- _$w('#prevTestimonialBtn').onClick(() => {
359
- if (currentTestimonialPage > 0) {
360
- currentTestimonialPage--;
361
- displayTestimonialsPage(allTestimonials);
362
- }
363
- });
364
-
365
- _$w('#nextTestimonialBtn').onClick(() => {
366
- const maxPage = Math.floor((allTestimonials.length - 1) / testimonialsPerPage);
367
- if (currentTestimonialPage < maxPage) {
368
- currentTestimonialPage++;
369
- displayTestimonialsPage(allTestimonials);
370
- }
371
- });
372
-
373
- displayTestimonialsPage(allTestimonials);
374
- }
375
-
376
- function displayTestimonialsPage(allTestimonials) {
377
- const start = currentTestimonialPage * testimonialsPerPage;
378
- const end = start + testimonialsPerPage;
379
- const currentBatch = allTestimonials.slice(start, end);
380
-
381
- populateRepeater(currentBatch, '#testimonialsrepeater', '#testimonialText');
382
- updateTestimonialNavigation(end, allTestimonials.length);
383
- }
384
-
385
- function updateTestimonialNavigation(end, totalLength) {
386
- _$w('#prevTestimonialBtn').hide();
387
- _$w('#nextTestimonialBtn').hide();
388
-
389
- if (currentTestimonialPage > 0) {
390
- _$w('#prevTestimonialBtn').show();
391
- }
392
-
393
- if (end < totalLength) {
394
- _$w('#nextTestimonialBtn').show();
395
- }
396
- }
397
-
398
- function applySeo(data, config) {
399
- if (!data || !config) {
400
- return;
401
- }
402
- const { siteAssociation, defaultSEODescription, siteLogoUrl, defaultProfileImage } = config;
403
-
404
- const profileImage =
405
- data.profileImage?.trim() && isWixHostedImage(data.profileImage)
406
- ? data.profileImage
407
- : defaultProfileImage;
408
- const ogImage = profileImage || data.logoImage || siteLogoUrl;
409
- const seoTitle = generateSEOTitle({
410
- fullName: data.fullName,
411
- areasOfPractices: data.areasOfPractices,
412
- siteAssociation,
413
- });
414
- let description = stripHtmlTags(data.aboutService) || defaultSEODescription || '';
415
- if (description.length > 160) {
416
- description = description.substring(0, 157) + '...';
417
- }
418
- const profileUrl = data.url
419
- ? `${wixLocation.baseUrl}/profile/${data.url}`
420
- : wixLocation.baseUrl;
421
- const shouldNoIndex = data.isPrivateMember || data.shouldHaveStudentBadge;
422
-
423
- seo.setTitle(seoTitle);
424
- seo.setMetaTags(
425
- [
426
- {
427
- name: 'description',
428
- content: description,
429
- },
430
- {
431
- name: 'keywords',
432
- content:
433
- `${data.fullName}, ${data.areasOfPractices ? data.areasOfPractices.slice(0, 3).join(', ') : ''}, ${siteAssociation}, ${data.city || ''}, ${data.state || ''}`
434
- .replace(/,\s*,/g, ',')
435
- .replace(/^,|,$/g, ''),
436
- },
437
- {
438
- name: 'author',
439
- content: data.fullName,
440
- },
441
- {
442
- name: 'robots',
443
- content: shouldNoIndex ? 'noindex, nofollow' : 'index, follow',
444
- },
445
- {
446
- property: 'og:type',
447
- content: 'profile',
448
- },
449
- {
450
- property: 'og:title',
451
- content: seoTitle,
452
- },
453
- {
454
- property: 'og:description',
455
- content: description,
456
- },
457
- {
458
- property: 'og:image',
459
- content: ogImage,
460
- },
461
- {
462
- property: 'og:url',
463
- content: profileUrl,
464
- },
465
- {
466
- property: 'og:site_name',
467
- content: `${siteAssociation} Members`,
468
- },
469
- {
470
- name: 'twitter:card',
471
- content: 'summary_large_image',
472
- },
473
- {
474
- name: 'twitter:title',
475
- content: seoTitle,
476
- },
477
- {
478
- name: 'twitter:description',
479
- content: description,
480
- },
481
- {
482
- name: 'twitter:image',
483
- content: ogImage,
484
- },
485
- {
486
- name: 'geo.region',
487
- content: data.state || '',
488
- },
489
- {
490
- name: 'geo.placename',
491
- content: data.city || '',
492
- },
493
- ].filter(tag => tag.content && tag.content.trim() !== '')
494
- );
495
- }
496
-
497
- function generateSEOTitle({ fullName, areasOfPractices, siteAssociation }) {
498
- return `${fullName}${
499
- areasOfPractices && areasOfPractices.length > 0
500
- ? ` | ${areasOfPractices.slice(0, 3).join(', ')}`
501
- : ''
502
- } | ${siteAssociation} Member`;
503
- }
504
-
505
- function stripHtmlTags(html) {
506
- if (!html) return '';
507
- return html
508
- .replace(/<[^>]*>/g, '')
509
- .replace(/&nbsp;/g, ' ')
510
- .replace(/&amp;/g, '&')
511
- .replace(/&lt;/g, '<')
512
- .replace(/&gt;/g, '>')
513
- .replace(/&quot;/g, '"')
514
- .replace(/&#39;/g, "'")
515
- .replace(/\s+/g, ' ')
516
- .trim();
517
- }
518
- }
519
-
520
- module.exports = {
521
- publicProfileOnReady,
522
- };