abmp-npm 1.8.4 → 1.8.6

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.
@@ -0,0 +1,2008 @@
1
+ const { location: wixLocation } = require('@wix/site-location');
2
+ const { storage: wixStorage } = require('@wix/site-storage');
3
+ const { window: wixWindow } = require('@wix/site-window');
4
+ const _ = require('lodash');
5
+
6
+ const {
7
+ ADDRESS_STATUS_TYPES,
8
+ DEFAULT_BUSINESS_NAME_TEXT,
9
+ FREE_WEBSITE_TEXT_STATES,
10
+ LIGHTBOX_NAMES,
11
+ ABMP_MEMBERS_HOME_URL,
12
+ } = require('../public/consts');
13
+ const { handleOnCustomValidation, isNotValidUrl } = require('../public/Utils/formValidation');
14
+ const { generateId } = require('../public/Utils/sharedUtils');
15
+
16
+ const MAX_PHONES_COUNT = 10;
17
+ const MAX_ADDRESSES_COUNT = 10;
18
+
19
+ const ADDRESS_STATES = {
20
+ VIEW: 'addressViewState',
21
+ EDIT: 'addressEditState',
22
+ };
23
+
24
+ const TESTIMONIAL_STATES = {
25
+ VIEW: 'testimonialState',
26
+ ADD: 'addTestimonialState',
27
+ };
28
+
29
+ const GALLERY_STATES = {
30
+ VIEW: 'imageState',
31
+ ADD: 'addImageState',
32
+ };
33
+
34
+ const MAIN_STATE_BOX_STATES = {
35
+ FORM_STATE: 'formState',
36
+ UNAUTHORIZED_STATE: 'unauthorizedState',
37
+ ERROR_STATE: 'errorState',
38
+ };
39
+
40
+ const FORM_SECTION_HANDLER_MAP = {
41
+ PERSONAL: { section: 'personal', handler: null }, // will be set in init
42
+ BUSINESS_SERVICES: { section: 'businessServices', handler: null },
43
+ CONTACT_BOOKING: { section: 'contactBooking', handler: null },
44
+ DIRECTORY_OPT_OUT: { section: 'directoryOptOut', handler: null },
45
+ WEBSITE_OPT_OUT: { section: 'websiteOptOut', handler: null },
46
+ };
47
+
48
+ const SLUG_FLAGS = {
49
+ VALID: '#validSlugFlag',
50
+ INVALID: '#invalidSlugFlag',
51
+ };
52
+
53
+ const SLUG_MESSAGES = {
54
+ INVALID_FORMAT: 'Enter a valid URL. You can use letters, numbers or dashes.',
55
+ TAKEN: 'Enter a new URL slug. This one is already taken.',
56
+ ERROR: 'There was an error. Please try again.',
57
+ };
58
+
59
+ async function personalDetailsFormOnReady({
60
+ $w,
61
+ getInterestAll,
62
+ saveRegistrationData,
63
+ validateMemberToken,
64
+ checkUrlUniqueness,
65
+ }) {
66
+ let itemMemberObj = {};
67
+ let originalMemberData = {};
68
+ let selectedServices = [];
69
+ const uploadedImages = {
70
+ profileImage: '',
71
+ logoImage: '',
72
+ bannerImage: '',
73
+ };
74
+
75
+ const FALLBACK_ADDRESS_STATUS = ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
76
+
77
+ const formHasUnsavedChanges = {
78
+ [FORM_SECTION_HANDLER_MAP.PERSONAL.section]: false,
79
+ [FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section]: false,
80
+ [FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section]: false,
81
+ [FORM_SECTION_HANDLER_MAP.DIRECTORY_OPT_OUT.section]: false,
82
+ [FORM_SECTION_HANDLER_MAP.WEBSITE_OPT_OUT.section]: false,
83
+ };
84
+
85
+ const slugValidationTimeout = {};
86
+ let isSlugValid = true;
87
+ let currentSlugValidationId = 0;
88
+ let memberData, isValid, isStudent;
89
+
90
+ // Set up handler references after functions are defined
91
+ const setupHandlerReferences = () => {
92
+ FORM_SECTION_HANDLER_MAP.PERSONAL.handler = () => checkPersonalDataChanged();
93
+ FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.handler = () => checkBusinessDataChanged();
94
+ FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.handler = () => checkContactDataChanged();
95
+ FORM_SECTION_HANDLER_MAP.DIRECTORY_OPT_OUT.handler = () => checkDirectoryOptOutDataChanged();
96
+ FORM_SECTION_HANDLER_MAP.WEBSITE_OPT_OUT.handler = () => checkWebsiteOptOutDataChanged();
97
+ };
98
+
99
+ const showUnauthorizedState = () => {
100
+ console.log('❌ Unauthorized');
101
+ $w('#mainMultiStateBox').changeState(MAIN_STATE_BOX_STATES.UNAUTHORIZED_STATE);
102
+ };
103
+
104
+ // Main initialization
105
+ const memberTokenId = await wixLocation.query.token;
106
+ console.log('memberTokenId', memberTokenId);
107
+ if (!memberTokenId) {
108
+ showUnauthorizedState();
109
+ return;
110
+ }
111
+ try {
112
+ const {
113
+ memberData: { isStudent: _isStudent, ...memberDataResponse }, //excluding student from member data so it won't be saved on form save, as this is a runtime calculation flag
114
+ isValid: isValidResponse,
115
+ } = await validateMemberToken(memberTokenId);
116
+ memberData = memberDataResponse;
117
+ isValid = isValidResponse;
118
+ isStudent = _isStudent;
119
+ } catch (error) {
120
+ console.error(`Error in validateMemberToken memberTokenId : ${memberTokenId}`, error);
121
+ $w('#mainMultiStateBox').changeState(MAIN_STATE_BOX_STATES.ERROR_STATE);
122
+ return;
123
+ }
124
+ console.log('memberData frontend', memberData);
125
+ if (!isValid) {
126
+ showUnauthorizedState();
127
+ return;
128
+ }
129
+
130
+ console.log('✅ Authorized 2', { memberTokenId });
131
+ $w('#loginButton2').hide();
132
+ $w('#goBackButton').show();
133
+
134
+ $w('#goBackButton').onClick(() => {
135
+ try {
136
+ const isFormHasUnsavedChanges = Object.values(formHasUnsavedChanges).some(Boolean);
137
+ if (isFormHasUnsavedChanges) {
138
+ wixWindow.openLightbox(LIGHTBOX_NAMES.SAVE_ALERT);
139
+ } else {
140
+ wixLocation.to(ABMP_MEMBERS_HOME_URL);
141
+ }
142
+ } catch (error) {
143
+ console.error('Logout failed:', error);
144
+ }
145
+ });
146
+
147
+ itemMemberObj = memberData;
148
+ originalMemberData = JSON.parse(JSON.stringify(memberData));
149
+ // Initialize selectedServices based on memberData
150
+ selectedServices = Array.isArray(itemMemberObj.areasOfPractices)
151
+ ? itemMemberObj.areasOfPractices.map(label => ({ _id: generateId(), label: String(label) }))
152
+ : [];
153
+
154
+ $w('#mainMultiStateBox').changeState(MAIN_STATE_BOX_STATES.FORM_STATE);
155
+
156
+ setupHandlerReferences();
157
+ init();
158
+ setupStepTrackingWrapper();
159
+ //initially disable save buttons
160
+ $w('#savePersonalButton').disable();
161
+ $w('#saveBusinessButton').disable();
162
+ $w('#saveContactBookingButton').disable();
163
+ onFormDataChanged();
164
+
165
+ function setupStepTrackingWrapper() {
166
+ const stepPairs = [
167
+ {
168
+ mark: '#personalDetailsMark',
169
+ sing: '#personalDetailsSign',
170
+ step: '#personalDetailsStep',
171
+ text: '#personalDetailsText',
172
+ css: 'personal',
173
+ vector: '#personalDetailsVector',
174
+ },
175
+ {
176
+ mark: '#businessServicesMark',
177
+ sing: '#businessServicesSign',
178
+ step: '#businessServicesStep',
179
+ text: '#businessServicesText',
180
+ css: 'business',
181
+ vector: '#businessServicesVector',
182
+ },
183
+ {
184
+ mark: '#contactMark',
185
+ sing: '#contactSign',
186
+ step: '#contactStep',
187
+ text: '#contactText',
188
+ css: 'contact',
189
+ vector: '#contactVector',
190
+ },
191
+ {
192
+ mark: '#galleryMark',
193
+ sing: '#gallerySign',
194
+ step: '#galleryStep',
195
+ text: '#galleryText',
196
+ css: 'gallery',
197
+ vector: '#galleryVector',
198
+ },
199
+ ];
200
+
201
+ setupStepTracking(stepPairs);
202
+ }
203
+
204
+ function setupStepTracking(stepPairs) {
205
+ stepPairs.forEach(({ mark, sing, step, text, css, vector }) => {
206
+ $w(mark).onViewportEnter(() => {
207
+ $w(sing).customClassList.add('current-step');
208
+ $w(step).customClassList.add('highlighted-text');
209
+ $w(text).customClassList.add('highlighted-text');
210
+ $w(vector).customClassList.add('disabeld-step');
211
+ $w('#accordion').customClassList.add(css);
212
+ $w(mark).scrollTo();
213
+ });
214
+
215
+ $w(mark).onViewportLeave(() => {
216
+ $w(sing).customClassList.remove('current-step');
217
+ $w(step).customClassList.remove('highlighted-text');
218
+ $w(text).customClassList.remove('highlighted-text');
219
+ $w(vector).customClassList.remove('disabeld-step');
220
+ $w('#accordion').customClassList.remove(css);
221
+ });
222
+ });
223
+ }
224
+
225
+ function init() {
226
+ const fullProfilePageLink = `${wixLocation.baseUrl}/profile/${itemMemberObj.url}`;
227
+ setPersonalDetails(fullProfilePageLink);
228
+ setBusinessServices();
229
+ setContactBooking(fullProfilePageLink);
230
+ initGallery();
231
+ }
232
+ function initGallery() {
233
+ $w('#galleryRepeater').onItemReady(handleGalleryItem);
234
+ $w('#uploadGalleryImageButton').onChange(async event => {
235
+ const $item = $w.at(event.context);
236
+ const uploadButton = $item('#uploadGalleryImageButton');
237
+ if (uploadButton.value.length === 0) return;
238
+ try {
239
+ const uploadedFiles = await uploadButton.uploadFiles();
240
+ // Initialize gallery array if it doesn't exist
241
+ if (!itemMemberObj.gallery) {
242
+ itemMemberObj.gallery = [];
243
+ }
244
+
245
+ uploadedFiles.forEach(file => {
246
+ itemMemberObj.gallery.unshift({ src: file.fileUrl });
247
+ });
248
+
249
+ await saveGalleryToCMS();
250
+ setGallery();
251
+ } catch (error) {
252
+ $w('#uploadFailedText').expand();
253
+ setTimeout(() => {
254
+ $w('#uploadFailedText').collapse();
255
+ }, 5000);
256
+ console.error('Upload failed:', error);
257
+ }
258
+ });
259
+ $w('#deleteImageButton').onClick(async event => {
260
+ const itemId = event.context.itemId;
261
+ const itemData = $w('#galleryRepeater').data.find(item => item._id === itemId);
262
+ const result = await wixWindow.openLightbox(LIGHTBOX_NAMES.DELETE_CONFIRM);
263
+ if (result && result.toDelete) {
264
+ itemMemberObj.gallery = itemMemberObj.gallery.filter(img => img.src !== itemData.image.src);
265
+ await saveGalleryToCMS();
266
+ setGallery(); // Re-render
267
+ }
268
+ });
269
+ //
270
+ setGallery();
271
+ }
272
+
273
+ function setPersonalDetails(fullProfilePageLink) {
274
+ $w('#firstNameInput').value = itemMemberObj.firstName || '';
275
+ $w('#lastNameInput').value = itemMemberObj.lastName || '';
276
+
277
+ $w('#slugInput').value = itemMemberObj.url || '';
278
+
279
+ $w(SLUG_FLAGS.VALID).collapse();
280
+ $w(SLUG_FLAGS.INVALID).collapse();
281
+
282
+ $w('#profileLink').text = fullProfilePageLink;
283
+ $w('#profileLink').link = fullProfilePageLink;
284
+ $w('#profileLink').target = '_blank';
285
+ $w('#licenceNoText').text = (itemMemberObj.licenses || [])
286
+ .map(val => val.license)
287
+ .filter(Boolean)
288
+ .join(', ');
289
+ const handleIsStudent = () => {
290
+ if (isStudent) {
291
+ $w('#optCheckbox').disable();
292
+ $w('#optCheckbox').checked = false;
293
+ $w('#optCheckbox').customClassList.add('disabled-text');
294
+ $w('#optCheckbox').customClassList.add('disabled-checkbox');
295
+ } else {
296
+ $w('#optCheckbox').enable();
297
+ $w('#optCheckbox').checked = !itemMemberObj.optOut;
298
+ }
299
+ };
300
+ handleIsStudent();
301
+ $w('#optWebsiteCheckbox').checked = itemMemberObj.showWixUrl;
302
+ toggleFreeWebsiteText(itemMemberObj.showWixUrl);
303
+ setupOptOutCheckbox(
304
+ '#optCheckbox',
305
+ '#optConfirmationBox',
306
+ '#yesOptButton',
307
+ '#cancelOptButton',
308
+ confirmed => handleOptConfirmation(confirmed, '#optCheckbox', '#optConfirmationBox', 'optOut')
309
+ );
310
+
311
+ setupOptOutCheckbox(
312
+ '#optWebsiteCheckbox',
313
+ '#optWebsiteConfirmationBox',
314
+ '#yesOptWebsiteButton',
315
+ '#cancelOptwebsiteButton',
316
+ confirmed =>
317
+ handleOptConfirmation(
318
+ confirmed,
319
+ '#optWebsiteCheckbox',
320
+ '#optWebsiteConfirmationBox',
321
+ 'showWixUrl'
322
+ )
323
+ );
324
+ }
325
+
326
+ function setupOptOutCheckbox(
327
+ checkboxId,
328
+ confirmationBoxId,
329
+ confirmBtnId,
330
+ cancelBtnId,
331
+ confirmCallback
332
+ ) {
333
+ const checkbox = $w(checkboxId);
334
+ const box = $w(confirmationBoxId);
335
+
336
+ checkbox.onChange(e => {
337
+ if (!e.target.checked) {
338
+ box.expand();
339
+ checkbox.disable();
340
+ checkbox.customClassList.add('disabled-text');
341
+ } else {
342
+ confirmCallback(true);
343
+ }
344
+ let sectionHandlerType;
345
+ if (checkboxId === '#optCheckbox') {
346
+ sectionHandlerType = FORM_SECTION_HANDLER_MAP.DIRECTORY_OPT_OUT;
347
+ } else if (checkboxId === '#optWebsiteCheckbox') {
348
+ sectionHandlerType = FORM_SECTION_HANDLER_MAP.WEBSITE_OPT_OUT;
349
+ }
350
+ checkFormChanges(sectionHandlerType);
351
+ });
352
+
353
+ $w(confirmBtnId).onClick(() => confirmCallback(true));
354
+ $w(cancelBtnId).onClick(() => confirmCallback(false));
355
+ }
356
+
357
+ function toggleFreeWebsiteText(isFreeWebsiteEnabled) {
358
+ if (isFreeWebsiteEnabled) {
359
+ $w('#freeWebsiteText').text = FREE_WEBSITE_TEXT_STATES.ENABLED;
360
+ } else {
361
+ $w('#freeWebsiteText').text = FREE_WEBSITE_TEXT_STATES.DISABLED;
362
+ }
363
+ }
364
+ async function handleOptConfirmation(confirmed, optCheckbox, optConfirmationBox, field) {
365
+ const checkbox = $w(optCheckbox);
366
+ const box = $w(optConfirmationBox);
367
+ if (confirmed) {
368
+ const toSaveOptValue = optCheckbox === '#optCheckbox' ? !checkbox.checked : checkbox.checked;
369
+ const formData = {
370
+ ...itemMemberObj,
371
+ [field]: toSaveOptValue,
372
+ };
373
+
374
+ await saveData(formData);
375
+ if (field === 'showWixUrl') {
376
+ const showExistingUrl = $w('#showExsistingUrlCheckbox').checked;
377
+ if (toSaveOptValue) {
378
+ if (!showExistingUrl) {
379
+ $w('#showUrlWixCheckbox').checked = true;
380
+ }
381
+ $w('#showUrlWixCheckbox').enable();
382
+ } else {
383
+ $w('#showUrlWixCheckbox').checked = false;
384
+ $w('#showUrlWixCheckbox').disable();
385
+ }
386
+ toggleFreeWebsiteText(toSaveOptValue);
387
+ }
388
+ }
389
+
390
+ box.collapse();
391
+ checkbox.enable();
392
+ checkbox.customClassList.remove('disabled-text');
393
+
394
+ if (!confirmed) {
395
+ checkbox.checked = !checkbox.checked;
396
+ }
397
+ const section =
398
+ field === 'showWixUrl'
399
+ ? FORM_SECTION_HANDLER_MAP.WEBSITE_OPT_OUT.section
400
+ : FORM_SECTION_HANDLER_MAP.DIRECTORY_OPT_OUT.section;
401
+ formHasUnsavedChanges[section] = false;
402
+ }
403
+
404
+ function onFormDataChanged() {
405
+ const CHANGE_EVENTS = {
406
+ ON_CHANGE: 'onChange',
407
+ ON_INPUT: 'onInput',
408
+ };
409
+ const elements = {
410
+ $firstNameInput: $w('#firstNameInput'),
411
+ $lastNameInput: $w('#lastNameInput'),
412
+ $businessNameCheckbox: $w('#businessNameCheckbox'),
413
+ $yearJoinedcheckbox: $w('#yearJoinedcheckbox'),
414
+ $aboutInput: $w('#aboutInput'),
415
+ $businessNameInput: $w('#businessNameInput'),
416
+ $uploadProfileButton: $w('#uploadProfileButton'),
417
+ $uploadLogoButton: $w('#uploadLogoButton'),
418
+ $uploadBannerButton: $w('#uploadBannerButton'),
419
+ $showContactFormCheckbox: $w('#showCotactFormCheckbox'),
420
+ $contactFormEmailInput: $w('#contactFormEmailInput'),
421
+ $schedulingLinkInput: $w('#schedulingLinkInput'),
422
+ $UrlInput: $w('#UrlInput'),
423
+ $slugInput: $w('#slugInput'),
424
+ };
425
+ const formChangeEventBindings = {
426
+ [FORM_SECTION_HANDLER_MAP.PERSONAL.section]: [
427
+ { $elem: elements.$firstNameInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
428
+ { $elem: elements.$lastNameInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
429
+ { $elem: elements.$slugInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
430
+ ],
431
+ [FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section]: [
432
+ { $elem: elements.$businessNameCheckbox, changeEvent: CHANGE_EVENTS.ON_CHANGE },
433
+ { $elem: elements.$yearJoinedcheckbox, changeEvent: CHANGE_EVENTS.ON_CHANGE },
434
+ { $elem: elements.$aboutInput, changeEvent: CHANGE_EVENTS.ON_CHANGE },
435
+ { $elem: elements.$businessNameInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
436
+ { $elem: elements.$uploadProfileButton, changeEvent: CHANGE_EVENTS.ON_CHANGE },
437
+ { $elem: elements.$uploadLogoButton, changeEvent: CHANGE_EVENTS.ON_CHANGE },
438
+ { $elem: elements.$uploadBannerButton, changeEvent: CHANGE_EVENTS.ON_CHANGE },
439
+ ],
440
+ [FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section]: [
441
+ { $elem: elements.$showContactFormCheckbox, changeEvent: CHANGE_EVENTS.ON_CHANGE },
442
+ { $elem: elements.$contactFormEmailInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
443
+ { $elem: elements.$schedulingLinkInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
444
+ { $elem: elements.$UrlInput, changeEvent: CHANGE_EVENTS.ON_INPUT },
445
+ ],
446
+ };
447
+ Object.keys(formChangeEventBindings).forEach(section => {
448
+ formChangeEventBindings[section].forEach(({ $elem, changeEvent }) => {
449
+ $elem[changeEvent](() => {
450
+ const handlerMap = Object.values(FORM_SECTION_HANDLER_MAP).find(
451
+ handlerMap => handlerMap.section === section
452
+ );
453
+ checkFormChanges(handlerMap);
454
+ });
455
+ });
456
+ });
457
+
458
+ $w('#slugInput').onInput(event => {
459
+ $w('#savePersonalButton').disable();
460
+ const slug = event.target.value;
461
+
462
+ isSlugValid = false;
463
+
464
+ if (slugValidationTimeout.slugValidation) {
465
+ clearTimeout(slugValidationTimeout.slugValidation);
466
+ }
467
+
468
+ const validationId = ++currentSlugValidationId;
469
+
470
+ slugValidationTimeout.slugValidation = setTimeout(async () => {
471
+ try {
472
+ if (validationId !== currentSlugValidationId) {
473
+ return;
474
+ }
475
+
476
+ const result = await validateSlugRealTime(slug);
477
+
478
+ if (validationId === currentSlugValidationId) {
479
+ isSlugValid = result.isValid;
480
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.PERSONAL);
481
+ }
482
+ } catch (error) {
483
+ console.error('Slug validation error:', error);
484
+ if (validationId === currentSlugValidationId) {
485
+ isSlugValid = false;
486
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.PERSONAL);
487
+ }
488
+ }
489
+ }, 800);
490
+ });
491
+ }
492
+
493
+ function checkFormChanges(formSectionHandler) {
494
+ let isFormDataChanged = false;
495
+ const toggleSaveDataButton = (formDataType, isFormDataChanged) => {
496
+ const saveButtonMap = {
497
+ [FORM_SECTION_HANDLER_MAP.PERSONAL.section]: '#savePersonalButton',
498
+ [FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section]: '#saveBusinessButton',
499
+ [FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section]: '#saveContactBookingButton',
500
+ };
501
+
502
+ const buttonSelector = saveButtonMap[formDataType];
503
+ if (!buttonSelector) {
504
+ throw new Error(`No save button defined for form section: ${formDataType}`);
505
+ }
506
+ const $saveDataButton = $w(buttonSelector);
507
+
508
+ let isUrlValid = true;
509
+ let isEmailValid = true;
510
+ let isNameValid = true;
511
+
512
+ if (formDataType === FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section) {
513
+ isEmailValid = $w('#contactFormEmailInput').valid;
514
+ isUrlValid =
515
+ !isNotValidUrl($w('#UrlInput').value) && !isNotValidUrl($w('#schedulingLinkInput').value);
516
+ }
517
+ if (formDataType === FORM_SECTION_HANDLER_MAP.PERSONAL.section) {
518
+ isNameValid = $w('#firstNameInput').valid && $w('#lastNameInput').valid;
519
+ }
520
+
521
+ if (isFormDataChanged && isUrlValid && isEmailValid && isNameValid && isSlugValid) {
522
+ $saveDataButton.enable();
523
+ } else {
524
+ $saveDataButton.disable();
525
+ }
526
+ };
527
+ if (formSectionHandler) {
528
+ const { section, handler } = formSectionHandler;
529
+ isFormDataChanged = handler();
530
+ formHasUnsavedChanges[section] = isFormDataChanged;
531
+ if (
532
+ [
533
+ FORM_SECTION_HANDLER_MAP.PERSONAL.section,
534
+ FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section,
535
+ FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section,
536
+ ].includes(section)
537
+ ) {
538
+ toggleSaveDataButton(section, isFormDataChanged);
539
+ }
540
+ } else {
541
+ Object.values(FORM_SECTION_HANDLER_MAP).forEach(({ section, handler }) => {
542
+ formHasUnsavedChanges[section] = handler();
543
+ });
544
+ }
545
+ }
546
+ function checkWebsiteOptOutDataChanged() {
547
+ const currentWebsiteOptInData = $w('#optWebsiteCheckbox').checked;
548
+ const originalWebsiteOptInData = originalMemberData.showWixUrl;
549
+ return !_.isEqual(currentWebsiteOptInData, originalWebsiteOptInData);
550
+ }
551
+ function checkDirectoryOptOutDataChanged() {
552
+ const currentDirectoryOptOutData = $w('#optCheckbox').checked;
553
+ const originalDirectoryOptOutData = !originalMemberData.optOut;
554
+ return !_.isEqual(currentDirectoryOptOutData, originalDirectoryOptOutData);
555
+ }
556
+ function checkBusinessDataChanged() {
557
+ const currentBusinessData = getBusinessAndServicesData();
558
+ const originalBusinessData = {
559
+ showBusinessName: originalMemberData.showBusinessName,
560
+ businessName: originalMemberData.businessName,
561
+ showABMP: originalMemberData.showABMP,
562
+ aboutService: originalMemberData.aboutService,
563
+ profileImage: originalMemberData.profileImage,
564
+ logoImage: originalMemberData.logoImage,
565
+ bannerImages: originalMemberData.bannerImages || [],
566
+ areasOfPractices: originalMemberData.areasOfPractices || [],
567
+ testimonial: originalMemberData.testimonial || [],
568
+ };
569
+ return !_.isEqual(currentBusinessData, originalBusinessData);
570
+ }
571
+
572
+ function checkContactDataChanged() {
573
+ const currentContactData = getContactAndBookingData();
574
+ const originalContactData = {
575
+ showContactForm: originalMemberData.showContactForm,
576
+ contactFormEmail: originalMemberData.contactFormEmail,
577
+ toShowPhone: originalMemberData.toShowPhone,
578
+ bookingUrl: originalMemberData.bookingUrl,
579
+ website: originalMemberData.website,
580
+ showWebsite: originalMemberData.showWebsite,
581
+ showWixUrl: originalMemberData.showWixUrl,
582
+ addressDisplayOption: originalMemberData.addressDisplayOption,
583
+ addresses: originalMemberData.addresses,
584
+ };
585
+ return !_.isEqual(currentContactData, originalContactData);
586
+ }
587
+
588
+ function setBusinessServices() {
589
+ $w('#businessNameText').text = itemMemberObj.businessName || DEFAULT_BUSINESS_NAME_TEXT;
590
+ $w('#businessNameCheckbox').checked = itemMemberObj.showBusinessName;
591
+ $w('#yearJoinedcheckbox').checked = itemMemberObj.showABMP;
592
+ $w('#aboutInput').value = itemMemberObj.aboutService;
593
+ $w('#businessNameInput').value = itemMemberObj.businessName;
594
+ $w('#clearBusinessNameBtn').onClick(() => {
595
+ $w('#businessNameInput').value = '';
596
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
597
+ });
598
+
599
+ // Get memberships array
600
+ const memberships = Array.isArray(itemMemberObj.memberships) ? itemMemberObj.memberships : [];
601
+ // Find ABMP object
602
+ const abmp = memberships.find(m => m.association === 'ABMP');
603
+ // Set yearJoinedText
604
+ if (abmp && abmp.membersince) {
605
+ $w('#yearJoinedText').text = abmp.membersince;
606
+ } else {
607
+ $w('#yearJoinedText').text = 'Year joined not provided';
608
+ }
609
+
610
+ uploadImageFromLightbox();
611
+ setupImageUploadAndDeleteHandlers();
612
+ setupServiceSelection();
613
+ setInterestData();
614
+ setupTestimonials();
615
+
616
+ // Initialize areasOfPractices array if it doesn't exist
617
+ if (!itemMemberObj.areasOfPractices) {
618
+ itemMemberObj.areasOfPractices = [];
619
+ }
620
+
621
+ if (Array.isArray(itemMemberObj.areasOfPractices)) {
622
+ selectedServices = itemMemberObj.areasOfPractices.map(label => ({
623
+ _id: generateId(),
624
+ label: String(label),
625
+ }));
626
+ renderServices();
627
+ }
628
+
629
+ displayExistingImagesFromCMS();
630
+
631
+ $w('#savePersonalButton').onClick(savePersonalDetails);
632
+ $w('#saveBusinessButton').onClick(saveBusinessServices);
633
+ $w('#servicesRepeater').onItemReady(($item, itemData) => {
634
+ $item('#serviceNameText').text = itemData.label;
635
+ });
636
+ }
637
+
638
+ function uploadImage(uploadButton, imageKey, updateUI) {
639
+ $w(uploadButton).onChange(async () => {
640
+ if ($w(uploadButton).value?.length > 0) {
641
+ try {
642
+ const uploadedFiles = await $w(uploadButton).uploadFiles();
643
+ uploadedFiles.forEach(file => {
644
+ uploadedImages[imageKey] = file.fileUrl;
645
+ updateUI(file);
646
+ });
647
+ } catch (error) {
648
+ console.error(`File upload error: ${error.errorCode}`, error.errorDescription);
649
+ }
650
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
651
+ }
652
+ });
653
+ }
654
+
655
+ function uploadImageFromLightbox() {
656
+ $w('#bannerLightboxButton').onClick(async () => {
657
+ const returnedImage = await wixWindow.openLightbox(LIGHTBOX_NAMES.SELECT_BANNER_IMAGES);
658
+ console.log('uploadedImages', returnedImage);
659
+
660
+ if (returnedImage && returnedImage.image) {
661
+ console.log('Image returned from lightbox:', returnedImage);
662
+
663
+ // Update stored image
664
+ uploadedImages.bannerImage = returnedImage.image;
665
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
666
+ $w('#bannerImage').src = returnedImage.image;
667
+ $w('#bannerImageName').text = extractFileName(returnedImage.image);
668
+ $w('#bannerImageContainer').expand();
669
+ }
670
+ });
671
+ }
672
+
673
+ function extractFileName(fileUrl) {
674
+ try {
675
+ const url = new URL(fileUrl);
676
+ const pathParts = url.pathname.split('/');
677
+ return pathParts[pathParts.length - 1] || '';
678
+ } catch {
679
+ return '';
680
+ }
681
+ }
682
+
683
+ function setupImageUploadAndDeleteHandlers() {
684
+ uploadImage('#uploadProfileButton', 'profileImage', file => {
685
+ $w('#profileImage').src = file.fileUrl;
686
+ $w('#profileImageName').text = formatFileName(file.fileName);
687
+ $w('#profileImageContainer').expand();
688
+ });
689
+
690
+ uploadImage('#uploadLogoButton', 'logoImage', file => {
691
+ $w('#logoImage').src = file.fileUrl;
692
+ $w('#logoImageName').text = file.fileName;
693
+ $w('#logoImageContainer').expand();
694
+ });
695
+
696
+ uploadImage('#uploadBannerButton', 'bannerImage', file => {
697
+ $w('#bannerImage').src = file.fileUrl;
698
+ $w('#bannerImageName').text = file.fileName;
699
+ $w('#bannerImageContainer').expand();
700
+ });
701
+
702
+ setupDeleteHandler(
703
+ '#deleteProfileImage',
704
+ '#profileImage',
705
+ '#profileImageName',
706
+ '#profileImageContainer',
707
+ 'profileImage',
708
+ '#uploadProfileButton'
709
+ );
710
+ setupDeleteHandler(
711
+ '#deleteLogoImage',
712
+ '#logoImage',
713
+ '#logoImageName',
714
+ '#logoImageContainer',
715
+ 'logoImage',
716
+ '#uploadLogoButton'
717
+ );
718
+ setupDeleteHandler(
719
+ '#deleteBannerImage',
720
+ '#bannerImage',
721
+ '#bannerImageName',
722
+ '#bannerImageContainer',
723
+ 'bannerImage',
724
+ '#uploadBannerButton'
725
+ );
726
+ }
727
+
728
+ function setupDeleteHandler(deleteBtn, imgId, nameId, containerId, imageKey, uploadBtnId) {
729
+ $w(deleteBtn).onClick(async () => {
730
+ const result = await wixWindow.openLightbox(LIGHTBOX_NAMES.DELETE_CONFIRM);
731
+
732
+ if (result && result.toDelete) {
733
+ $w(imgId).src = '';
734
+ $w(nameId).text = '';
735
+ $w(containerId).collapse();
736
+ uploadedImages[imageKey] = '';
737
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
738
+ $w(uploadBtnId).reset();
739
+ }
740
+ });
741
+ }
742
+
743
+ function displayExistingImagesFromCMS() {
744
+ const imageMap = [
745
+ {
746
+ key: 'profileImage',
747
+ imageSelector: '#profileImage',
748
+ nameSelector: '#profileImageName',
749
+ containerSelector: '#profileImageContainer',
750
+ },
751
+ {
752
+ key: 'logoImage',
753
+ imageSelector: '#logoImage',
754
+ nameSelector: '#logoImageName',
755
+ containerSelector: '#logoImageContainer',
756
+ },
757
+ {
758
+ key: 'bannerImages',
759
+ imageSelector: '#bannerImage',
760
+ nameSelector: '#bannerImageName',
761
+ containerSelector: '#bannerImageContainer',
762
+ isArray: true,
763
+ },
764
+ ];
765
+
766
+ imageMap.forEach(({ key, imageSelector, nameSelector, containerSelector, isArray }) => {
767
+ const imageValue = isArray
768
+ ? Array.isArray(itemMemberObj[key]) && itemMemberObj[key].length > 0
769
+ ? itemMemberObj[key][0]
770
+ : null
771
+ : itemMemberObj[key];
772
+
773
+ if (imageValue) {
774
+ $w(imageSelector).src = imageValue;
775
+ $w(nameSelector).text = formatFileName(extractFileName(imageValue));
776
+ $w(containerSelector).expand();
777
+ uploadedImages[key === 'bannerImages' ? 'bannerImage' : key] = imageValue;
778
+ }
779
+ });
780
+ }
781
+
782
+ async function handleItemDelete(event, getTextSelector, arrayRef, matchField, renderFn) {
783
+ const result = await wixWindow.openLightbox(LIGHTBOX_NAMES.DELETE_CONFIRM);
784
+
785
+ if (result && result.toDelete) {
786
+ const $clickedItem = $w.at(event.context);
787
+ const textToRemove = $clickedItem(getTextSelector).text;
788
+
789
+ arrayRef.splice(
790
+ 0,
791
+ arrayRef.length,
792
+ ...arrayRef.filter(item =>
793
+ typeof item === 'string' ? item !== textToRemove : item[matchField] !== textToRemove
794
+ )
795
+ );
796
+
797
+ renderFn();
798
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
799
+ }
800
+ }
801
+
802
+ async function setInterestData() {
803
+ const interestsData = await getInterestAll();
804
+
805
+ $w('#removeServiceButton').onClick(event => {
806
+ handleItemDelete(event, '#serviceNameText', selectedServices, 'label', renderServices);
807
+ });
808
+
809
+ if (Array.isArray(interestsData) && interestsData.length > 0) {
810
+ const formattedData = interestsData.map((val, index) => ({
811
+ _id: String(index),
812
+ value: val,
813
+ }));
814
+
815
+ $w('#repeaterInterest').data = formattedData;
816
+ $w('#repeaterInterest').onItemReady(($item, itemData, _index) => {
817
+ $item('#interestText').text = itemData.value;
818
+ });
819
+ }
820
+ }
821
+
822
+ async function filterInterests(searchValue) {
823
+ const container = $w('#containerRepeaterInterest');
824
+ const repeater = $w('#repeaterInterest');
825
+
826
+ const allInterests = await getInterestAll();
827
+ const filtered = allInterests
828
+ .filter(val => val.toLowerCase().includes(searchValue))
829
+ .map(val => ({ _id: generateId(), value: val }));
830
+
831
+ if (filtered.length > 0) {
832
+ repeater.data = filtered;
833
+ container.expand();
834
+ } else {
835
+ repeater.data = [];
836
+ container.collapse();
837
+ }
838
+
839
+ return filtered;
840
+ }
841
+
842
+ const debounce_fun = _.debounce(async () => {
843
+ const searchValue = $w('#intrestInput').value.trim().toLowerCase();
844
+ await filterInterests(searchValue);
845
+ }, 250);
846
+
847
+ function setupServiceSelection() {
848
+ const intrestInput = $w('#intrestInput');
849
+ intrestInput.onClick(() => {
850
+ if (intrestInput.value) {
851
+ intrestInput.onClick(async () => {
852
+ await filterInterests(intrestInput.value);
853
+ });
854
+ } else {
855
+ setInterestData();
856
+ }
857
+ $w('#containerRepeaterInterest').expand();
858
+ });
859
+
860
+ intrestInput.onKeyPress(event => {
861
+ debounce_fun();
862
+
863
+ if (event.key === 'Enter') {
864
+ const typedValue = intrestInput.value.trim();
865
+
866
+ if (
867
+ typedValue &&
868
+ !selectedServices.some(
869
+ service => service.label.toLowerCase() === typedValue.toLowerCase()
870
+ )
871
+ ) {
872
+ selectedServices.unshift({
873
+ _id: generateId(),
874
+ label: typedValue,
875
+ });
876
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
877
+ renderServices();
878
+ intrestInput.value = '';
879
+ $w('#containerRepeaterInterest').collapse();
880
+ }
881
+ }
882
+ });
883
+
884
+ $w('#repeaterInterest').onItemReady(($item, itemData) => {
885
+ $item('#interestText').text = itemData.value;
886
+
887
+ $item('#intrestItem').onClick(() => {
888
+ if (!selectedServices.some(service => service.label === itemData.value)) {
889
+ selectedServices.unshift({
890
+ _id: generateId(),
891
+ label: itemData.value,
892
+ });
893
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
894
+ renderServices();
895
+ $w('#intrestInput').value = '';
896
+ $w('#containerRepeaterInterest').collapse();
897
+ }
898
+ });
899
+ });
900
+ }
901
+
902
+ function renderServices() {
903
+ setupRepeater('#servicesRepeater', selectedServices);
904
+ }
905
+
906
+ function setupRepeater(repeaterId, data) {
907
+ const repeater = $w(repeaterId);
908
+ repeater.data = data;
909
+ }
910
+
911
+ /**
912
+ * Logs user data changes for debugging and investigation purposes
913
+ * @param {string} saveType - Type of save operation (personal, business, contact)
914
+ * @param {Object} beforeData - Data before save
915
+ * @param {Object} afterData - Data after save
916
+ * @param {boolean} success - Whether the save was successful
917
+ */
918
+ function logUserDataChanges(saveType, beforeData, afterData, success) {
919
+ const memberId = wixStorage.local.getItem('memberId');
920
+ const timestamp = new Date().toISOString();
921
+
922
+ console.group(
923
+ `User Data Change Log - ${saveType.toUpperCase()} - ${success ? 'SUCCESS' : 'FAILED'}`
924
+ );
925
+ console.log('Change Details:', {
926
+ memberId,
927
+ timestamp,
928
+ saveType,
929
+ success,
930
+ });
931
+
932
+ console.log('BEFORE Save:', beforeData);
933
+ console.log('AFTER Save:', afterData);
934
+
935
+ // Calculate and log specific changes
936
+ const changes = {};
937
+ Object.keys(afterData).forEach(key => {
938
+ if (JSON.stringify(beforeData[key]) !== JSON.stringify(afterData[key])) {
939
+ changes[key] = {
940
+ before: beforeData[key],
941
+ after: afterData[key],
942
+ };
943
+ }
944
+ });
945
+
946
+ if (Object.keys(changes).length > 0) {
947
+ console.log('Specific Changes:', changes);
948
+ } else {
949
+ console.log('No changes detected in data comparison');
950
+ }
951
+
952
+ console.groupEnd();
953
+ }
954
+
955
+ async function saveData(formData) {
956
+ const memberId = wixStorage.local.getItem('memberId');
957
+
958
+ // Capture data before save for logging
959
+ const beforeSaveData = JSON.parse(JSON.stringify(itemMemberObj));
960
+
961
+ const { type, saveData: saved } = await saveRegistrationData(formData, memberId);
962
+
963
+ if (type === 'success') {
964
+ // Log the successful change
965
+ logUserDataChanges('general', beforeSaveData, saved, true);
966
+
967
+ itemMemberObj = { ...saved };
968
+ originalMemberData = JSON.parse(JSON.stringify(saved));
969
+ return {
970
+ success: true,
971
+ message: 'The information was saved successfully.',
972
+ };
973
+ } else {
974
+ // Log the failed attempt
975
+ logUserDataChanges('general', beforeSaveData, formData, false);
976
+
977
+ return {
978
+ success: false,
979
+ message:
980
+ "It looks like something went wrong — the information wasn't saved. Please try again later.",
981
+ };
982
+ }
983
+ }
984
+
985
+ function isValidSlugFormat(slug) {
986
+ if (!slug || slug.length === 0) return false;
987
+ if (slug.length < 3 || slug.length > 50) return false;
988
+
989
+ const slugRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/;
990
+ return slugRegex.test(slug);
991
+ }
992
+
993
+ async function validateSlugRealTime(slug) {
994
+ $w(SLUG_FLAGS.VALID).collapse();
995
+ $w(SLUG_FLAGS.INVALID).collapse();
996
+
997
+ const trimmedSlug = slug.trim();
998
+
999
+ if (!isValidSlugFormat(trimmedSlug)) {
1000
+ $w('#invalidSlugMessage').text = SLUG_MESSAGES.INVALID_FORMAT;
1001
+ $w(SLUG_FLAGS.INVALID).expand();
1002
+ return { isValid: false };
1003
+ }
1004
+
1005
+ if (trimmedSlug === (originalMemberData.url || '')) {
1006
+ $w(SLUG_FLAGS.VALID).collapse();
1007
+ $w(SLUG_FLAGS.INVALID).collapse();
1008
+ return { isValid: true };
1009
+ }
1010
+
1011
+ try {
1012
+ const result = await checkUrlUniqueness(trimmedSlug, itemMemberObj.memberId);
1013
+ const isUnique = result.isUnique;
1014
+
1015
+ if (isUnique) {
1016
+ $w(SLUG_FLAGS.VALID).expand();
1017
+ $w(SLUG_FLAGS.INVALID).collapse();
1018
+ return { isValid: true };
1019
+ } else {
1020
+ $w('#invalidSlugMessage').text = SLUG_MESSAGES.TAKEN;
1021
+ $w(SLUG_FLAGS.INVALID).expand();
1022
+ $w(SLUG_FLAGS.VALID).collapse();
1023
+ return { isValid: false };
1024
+ }
1025
+ } catch (error) {
1026
+ console.error('Error checking slug uniqueness:', error);
1027
+ $w('#invalidSlugMessage').text = SLUG_MESSAGES.ERROR;
1028
+ $w(SLUG_FLAGS.INVALID).expand();
1029
+ $w(SLUG_FLAGS.VALID).collapse();
1030
+ return { isValid: false };
1031
+ }
1032
+ }
1033
+
1034
+ function getPersonalData() {
1035
+ const firstName = $w('#firstNameInput').value.trim();
1036
+ const lastName = $w('#lastNameInput').value.trim();
1037
+ const fullName = `${firstName} ${lastName}`.trim();
1038
+ const url = $w('#slugInput').value.trim();
1039
+
1040
+ return {
1041
+ firstName,
1042
+ lastName,
1043
+ fullName,
1044
+ url,
1045
+ };
1046
+ }
1047
+
1048
+ function checkPersonalDataChanged() {
1049
+ const currentPersonalData = getPersonalData();
1050
+ const originalPersonalData = {
1051
+ firstName: originalMemberData.firstName || '',
1052
+ lastName: originalMemberData.lastName || '',
1053
+ fullName: originalMemberData.fullName || '',
1054
+ url: originalMemberData.url || '',
1055
+ };
1056
+ return !_.isEqual(currentPersonalData, originalPersonalData);
1057
+ }
1058
+
1059
+ function getBusinessAndServicesData() {
1060
+ const getCurrentTestimonials = () =>
1061
+ $w('#testimonialRepeater')
1062
+ .data.filter(item => item.isAdd === false)
1063
+ .map(item => item.text)
1064
+ .filter(Boolean) || itemMemberObj.testimonial;
1065
+
1066
+ return {
1067
+ showBusinessName: $w('#businessNameCheckbox').checked,
1068
+ businessName: $w('#businessNameInput').value,
1069
+ showABMP: $w('#yearJoinedcheckbox').checked,
1070
+ profileImage: uploadedImages.profileImage,
1071
+ logoImage: uploadedImages.logoImage,
1072
+ bannerImages: uploadedImages.bannerImage ? [uploadedImages.bannerImage] : [],
1073
+ areasOfPractices: selectedServices.map(service => service.label),
1074
+ aboutService: $w('#aboutInput').value,
1075
+ testimonial: getCurrentTestimonials(),
1076
+ };
1077
+ }
1078
+ async function savePersonalDetails() {
1079
+ const beforeData = JSON.parse(JSON.stringify(itemMemberObj));
1080
+ const personalChanges = getPersonalData();
1081
+ const originalUrl = beforeData.url;
1082
+
1083
+ const formData = {
1084
+ ...itemMemberObj,
1085
+ ...personalChanges,
1086
+ };
1087
+
1088
+ // Log the specific personal data changes
1089
+ console.group('Personal Details Save Attempt');
1090
+ console.log('Current Data:', beforeData);
1091
+ console.log('Changes Being Applied:', personalChanges);
1092
+ console.log('Final Form Data:', formData);
1093
+ console.groupEnd();
1094
+
1095
+ const result = await saveData(formData);
1096
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.PERSONAL.section] = false;
1097
+
1098
+ if (result.success) {
1099
+ if (personalChanges.url && personalChanges.url !== originalUrl) {
1100
+ const newProfileLink = `${wixLocation.baseUrl}/profile/${personalChanges.url}`;
1101
+ console.log('🔗 Updating profile link:', {
1102
+ originalUrl,
1103
+ newUrl: personalChanges.url,
1104
+ newProfileLink,
1105
+ });
1106
+ $w('#profileLink').text = newProfileLink;
1107
+ $w('#profileLink').link = newProfileLink;
1108
+
1109
+ $w(SLUG_FLAGS.VALID).collapse();
1110
+ $w(SLUG_FLAGS.INVALID).collapse();
1111
+ }
1112
+
1113
+ $w('#savePersonalButton').disable();
1114
+ }
1115
+
1116
+ handleSaveDataFeedback($w('#personalMessage'), result.message);
1117
+ }
1118
+
1119
+ async function saveBusinessServices() {
1120
+ const beforeData = JSON.parse(JSON.stringify(itemMemberObj));
1121
+ const businessChanges = getBusinessAndServicesData();
1122
+
1123
+ const formData = {
1124
+ ...itemMemberObj,
1125
+ ...businessChanges,
1126
+ };
1127
+
1128
+ // Log the specific business data changes
1129
+ console.group('Business Services Save Attempt');
1130
+ console.log('Current Data:', beforeData);
1131
+ console.log('Changes Being Applied:', businessChanges);
1132
+ console.log('Final Form Data:', formData);
1133
+ console.log('Image Changes:', {
1134
+ profileImage: uploadedImages.profileImage,
1135
+ logoImage: uploadedImages.logoImage,
1136
+ bannerImage: uploadedImages.bannerImage,
1137
+ });
1138
+ console.log('Services Selected:', selectedServices);
1139
+ console.groupEnd();
1140
+
1141
+ const result = await saveData(formData);
1142
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES.section] = false;
1143
+ handleSaveDataFeedback($w('#businessMessage'), result.message);
1144
+ $w('#businessNameText').text = formData.businessName || DEFAULT_BUSINESS_NAME_TEXT;
1145
+ }
1146
+
1147
+ function setupTestimonials() {
1148
+ const addTestimonialButton = $w('#addTestimonialButton');
1149
+
1150
+ addTestimonialButton.onClick(handleAddTestimonial);
1151
+ $w('#deleteTestimonialButton').onClick(event => {
1152
+ handleItemDelete(
1153
+ event,
1154
+ '#testimonialText',
1155
+ itemMemberObj.testimonial,
1156
+ null,
1157
+ renderTestimonials
1158
+ );
1159
+ });
1160
+
1161
+ renderTestimonials();
1162
+ $w('#testimonialRepeater').onItemReady(($item, itemData) => {
1163
+ const msb = $item('#testimonialMSB');
1164
+ if (itemData.isAdd) {
1165
+ msb.changeState(TESTIMONIAL_STATES.ADD);
1166
+ } else {
1167
+ msb.changeState(TESTIMONIAL_STATES.VIEW);
1168
+ $item('#testimonialText').text = itemData.text;
1169
+ }
1170
+ });
1171
+ }
1172
+
1173
+ function renderTestimonials() {
1174
+ const testimonials =
1175
+ itemMemberObj.testimonial === null
1176
+ ? []
1177
+ : Array.isArray(itemMemberObj.testimonial)
1178
+ ? itemMemberObj.testimonial
1179
+ : [];
1180
+ const addItem = { _id: 'add-item', text: '', isAdd: true };
1181
+ const testimonialData = [
1182
+ addItem,
1183
+ ...testimonials.map(text => ({ _id: generateId(), text, isAdd: false })),
1184
+ ];
1185
+
1186
+ setupRepeater('#testimonialRepeater', testimonialData);
1187
+ }
1188
+
1189
+ function handleAddTestimonial(event) {
1190
+ const $clickedItem = $w.at(event.context);
1191
+ const input = $clickedItem('#testimonialsInput');
1192
+ const newText = input.value;
1193
+
1194
+ if (newText?.trim()) {
1195
+ // Initialize testimonial array if it doesn't exist
1196
+ if (!itemMemberObj.testimonial) {
1197
+ itemMemberObj.testimonial = [];
1198
+ }
1199
+ itemMemberObj.testimonial.push(newText.trim());
1200
+ input.value = '';
1201
+ renderTestimonials();
1202
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.BUSINESS_SERVICES);
1203
+ }
1204
+ }
1205
+
1206
+ function setContactBooking(fullProfilePageLink) {
1207
+ // derive booleans only once
1208
+ const showWixUrlCheckbox = !itemMemberObj.showWebsite && itemMemberObj.showWixUrl;
1209
+ const showExistingUrlCheckbox = itemMemberObj.showWebsite;
1210
+
1211
+ // basic fields
1212
+ $w('#showCotactFormCheckbox').checked = itemMemberObj.showContactForm;
1213
+ $w('#contactFormEmailInput').value = itemMemberObj.contactFormEmail;
1214
+ $w('#schedulingLinkInput').value = itemMemberObj.bookingUrl;
1215
+
1216
+ // URL part
1217
+ $w('#UrlInput').value = itemMemberObj.website || '';
1218
+ $w('#showUrlWixCheckbox').checked = showWixUrlCheckbox;
1219
+ $w('#showExsistingUrlCheckbox').checked = showExistingUrlCheckbox;
1220
+ $w('#urlWebsiteText').text = fullProfilePageLink;
1221
+
1222
+ // custom validation for url inputs and email
1223
+ handleOnCustomValidation($w('#UrlInput'));
1224
+ handleOnCustomValidation($w('#schedulingLinkInput'));
1225
+
1226
+ // enable/disable & styling in one pass
1227
+ if (showWixUrlCheckbox) {
1228
+ $w('#UrlInput').disable();
1229
+ $w('#urlWebsiteText').customClassList.add('highlighted-text');
1230
+ } else if (showExistingUrlCheckbox) {
1231
+ $w('#UrlInput').enable();
1232
+ $w('#urlWebsiteText').customClassList.remove('highlighted-text');
1233
+ } else {
1234
+ // neither checked: disable input & remove highlight
1235
+ $w('#UrlInput').disable();
1236
+ $w('#urlWebsiteText').customClassList.remove('highlighted-text');
1237
+ }
1238
+
1239
+ // clear buttons
1240
+ $w('#clearSchedulingLinkInput').onClick(() => {
1241
+ $w('#schedulingLinkInput').value = '';
1242
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1243
+ });
1244
+ $w('#clearExistingUrlLinkInput').onClick(() => {
1245
+ $w('#UrlInput').value = '';
1246
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1247
+ });
1248
+
1249
+ // toggle handlers
1250
+ $w('#showUrlWixCheckbox').onChange(e => {
1251
+ if (e.target.checked) {
1252
+ $w('#showExsistingUrlCheckbox').checked = false;
1253
+ $w('#UrlInput').disable();
1254
+ $w('#urlWebsiteText').customClassList.add('highlighted-text');
1255
+ }
1256
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1257
+ });
1258
+ $w('#showExsistingUrlCheckbox').onChange(e => {
1259
+ if (e.target.checked) {
1260
+ $w('#showUrlWixCheckbox').checked = false;
1261
+ $w('#UrlInput').enable();
1262
+ $w('#urlWebsiteText').customClassList.remove('highlighted-text');
1263
+ }
1264
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1265
+ });
1266
+
1267
+ setupAddressRepeater();
1268
+ setupPhoneRepeater();
1269
+ $w('#saveContactBookingButton').onClick(saveContactBooking);
1270
+ }
1271
+
1272
+ /**
1273
+ * Converts our internal address format to AddressInput component format
1274
+ * @param {Object} address - Our internal address object with line1, city, state, postalcode, latitude, longitude
1275
+ * @returns {Object} AddressInput compatible object
1276
+ */
1277
+ function convertToAddressInputFormat(address) {
1278
+ if (!address) return null;
1279
+
1280
+ if (!address.line1 && !address.city && !address.state && !address.postalcode) {
1281
+ return null;
1282
+ }
1283
+
1284
+ const formatted = formatFullAddress(address);
1285
+
1286
+ const hasValidCoordinates = Boolean(address.latitude && address.longitude);
1287
+ const location = hasValidCoordinates
1288
+ ? { latitude: address.latitude, longitude: address.longitude }
1289
+ : null;
1290
+
1291
+ return {
1292
+ formatted,
1293
+ location,
1294
+ streetAddress: {
1295
+ name: extractStreetName(address.line1),
1296
+ number: extractStreetNumber(address.line1),
1297
+ },
1298
+ city: address.city || '',
1299
+ subdivision: address.state || '',
1300
+ country: address.country || 'US',
1301
+ postalCode: address.postalcode || '',
1302
+ };
1303
+ }
1304
+
1305
+ /**
1306
+ * Converts AddressInput format back to our internal address format
1307
+ * @param {Object} addressInputValue - AddressInput value object
1308
+ * @param {Object} existingAddress - Existing address data to preserve key and other fields
1309
+ * @returns {Object} Our internal address format
1310
+ */
1311
+ function parseAddressInput(addressInputValue, existingAddress = null) {
1312
+ if (!addressInputValue) return null;
1313
+
1314
+ let line1 = '';
1315
+ if (addressInputValue.streetAddress) {
1316
+ const number = addressInputValue.streetAddress.number || '';
1317
+ const name = addressInputValue.streetAddress.name || '';
1318
+ line1 = `${number} ${name}`.trim();
1319
+ }
1320
+
1321
+ if (!line1 && addressInputValue.formatted) {
1322
+ line1 = addressInputValue.formatted.split(',')[0]?.trim() || '';
1323
+ }
1324
+
1325
+ return {
1326
+ key: existingAddress?.key || generateId(),
1327
+ line1,
1328
+ line2: existingAddress?.line2 || '',
1329
+ city: addressInputValue.city || '',
1330
+ state: addressInputValue.subdivision || '',
1331
+ postalcode: addressInputValue.postalCode || '',
1332
+ country: addressInputValue.country || 'US',
1333
+ latitude: addressInputValue.location?.latitude || existingAddress?.latitude || 0,
1334
+ longitude: addressInputValue.location?.longitude || existingAddress?.longitude || 0,
1335
+ addressStatus: existingAddress?.addressStatus || FALLBACK_ADDRESS_STATUS,
1336
+ };
1337
+ }
1338
+
1339
+ function setupAddressRepeater() {
1340
+ $w('#addressesList').onItemReady(($item, itemData, _index) =>
1341
+ handleAddressItem($item, itemData, _index)
1342
+ );
1343
+ renderAddressesList();
1344
+
1345
+ $w('#newAddressButton').onClick(addNewAddress);
1346
+
1347
+ setupAddressRepeaterEventListeners();
1348
+ }
1349
+
1350
+ function setupAddressRepeaterEventListeners() {
1351
+ $w('#mainAddressCheckbox').onChange(event => {
1352
+ const data = $w('#addressesList').data;
1353
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1354
+ const $item = $w.at(event.context);
1355
+
1356
+ $w('#mainAddressCheckbox').checked = false;
1357
+ $item('#mainAddressCheckbox').checked = true;
1358
+
1359
+ if (clickedItemData.address.addressStatus === ADDRESS_STATUS_TYPES.DONT_SHOW) {
1360
+ updateAddressStatus(clickedItemData._id, ADDRESS_STATUS_TYPES.STATE_CITY_ZIP);
1361
+ $item('#addressStatusOptions').value = ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
1362
+ }
1363
+
1364
+ updateMainAddressSelection(clickedItemData._id);
1365
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1366
+ });
1367
+
1368
+ $w('#addressStatusOptions').onChange(event => {
1369
+ const data = $w('#addressesList').data;
1370
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1371
+ const newStatus = event.target.value;
1372
+ const $item = $w.at(event.context);
1373
+ const isMain = $item('#mainAddressCheckbox').checked;
1374
+
1375
+ if (isMain && newStatus === ADDRESS_STATUS_TYPES.DONT_SHOW) {
1376
+ $item('#addressStatusOptions').value = ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
1377
+ return;
1378
+ }
1379
+
1380
+ updateAddressStatus(clickedItemData._id, newStatus);
1381
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1382
+ });
1383
+
1384
+ $w('#addressItemEditBtn').onClick(event => {
1385
+ const data = $w('#addressesList').data;
1386
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1387
+ const $item = $w.at(event.context);
1388
+ $item('#addressItemStates').changeState(ADDRESS_STATES.EDIT);
1389
+ const addressInputValue = convertToAddressInputFormat(clickedItemData.address);
1390
+ $item('#addressEditInput').value = addressInputValue;
1391
+ });
1392
+
1393
+ $w('#addressItemRemoveBtn').onClick(async event => {
1394
+ const data = $w('#addressesList').data;
1395
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1396
+ const result = await wixWindow.openLightbox(LIGHTBOX_NAMES.DELETE_CONFIRM);
1397
+ if (result && result.toDelete) {
1398
+ removeAddress(clickedItemData._id);
1399
+ }
1400
+ });
1401
+
1402
+ $w('#addressEditInput').onChange(event => {
1403
+ const $item = $w.at(event.context);
1404
+ validateAddressCompleteness($item);
1405
+ });
1406
+
1407
+ $w('#addressEditCancelBtn').onClick(event => {
1408
+ const data = $w('#addressesList').data;
1409
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1410
+ const $item = $w.at(event.context);
1411
+ if (clickedItemData.isNewAddress) {
1412
+ removeNewAddressFromRepeater(clickedItemData._id);
1413
+ } else {
1414
+ $item('#addressItemStates').changeState(ADDRESS_STATES.VIEW);
1415
+ $item('#addressValidationMessage').hide();
1416
+ }
1417
+ });
1418
+
1419
+ $w('#addressEditSaveBtn').onClick(event => {
1420
+ const data = $w('#addressesList').data;
1421
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1422
+ const $item = $w.at(event.context);
1423
+ saveAddressFromSingleInput($item, clickedItemData);
1424
+ });
1425
+ }
1426
+
1427
+ function addNewAddress() {
1428
+ const currentData = $w('#addressesList').data || [];
1429
+
1430
+ if (currentData.length >= MAX_ADDRESSES_COUNT) {
1431
+ return;
1432
+ }
1433
+
1434
+ const newAddressId = generateId();
1435
+
1436
+ const newAddress = {
1437
+ key: newAddressId,
1438
+ line1: '',
1439
+ line2: '',
1440
+ city: '',
1441
+ state: '',
1442
+ postalcode: '',
1443
+ country: 'US',
1444
+ latitude: 0,
1445
+ longitude: 0,
1446
+ addressStatus: ADDRESS_STATUS_TYPES.STATE_CITY_ZIP,
1447
+ };
1448
+
1449
+ const newAddressItem = {
1450
+ _id: newAddressId,
1451
+ address: newAddress,
1452
+ isMain: false,
1453
+ addressStatus: ADDRESS_STATUS_TYPES.STATE_CITY_ZIP,
1454
+ isNewAddress: true,
1455
+ };
1456
+
1457
+ renderAddressesList([...currentData, newAddressItem]);
1458
+ }
1459
+
1460
+ function handleAddressItem($item, itemData, index) {
1461
+ const multiStateBox = $item('#addressItemStates');
1462
+
1463
+ setupAddressViewState($item, itemData, index);
1464
+ setupAddressEditState($item, itemData, index);
1465
+
1466
+ if (itemData.isNewAddress) {
1467
+ multiStateBox.changeState(ADDRESS_STATES.EDIT);
1468
+ } else {
1469
+ multiStateBox.changeState(ADDRESS_STATES.VIEW);
1470
+ }
1471
+ }
1472
+
1473
+ function setupAddressViewState($item, itemData, index) {
1474
+ const formattedAddress = formatFullAddress(itemData.address);
1475
+
1476
+ $item('#addressItemtext').text = formattedAddress;
1477
+ $item('#addressItemNumber').text = `Location ${index + 1}`;
1478
+ $item('#mainAddressCheckbox').checked = itemData.isMain || false;
1479
+
1480
+ // TO DO: Ask client what should be the default address status
1481
+ const addressStatus = itemData.address.addressStatus || ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
1482
+ $item('#addressStatusOptions').value = addressStatus;
1483
+ }
1484
+
1485
+ function setupAddressEditState($item, itemData, _index) {
1486
+ $item('#addressEditInput').enable();
1487
+
1488
+ const addressInputValue = convertToAddressInputFormat(itemData.address);
1489
+ $item('#addressEditInput').value = addressInputValue;
1490
+
1491
+ $item('#addressValidationMessage').hide();
1492
+ }
1493
+
1494
+ function validateAddressCompleteness($item) {
1495
+ const addressInput = $item('#addressEditInput');
1496
+ const saveBtn = $item('#addressEditSaveBtn');
1497
+ const validationMessage = $item('#addressValidationMessage');
1498
+
1499
+ const addressValue = addressInput.value;
1500
+
1501
+ if (!addressValue) {
1502
+ showAddressValidationError(validationMessage, saveBtn, 'Please provide a complete address');
1503
+ return false;
1504
+ }
1505
+
1506
+ const missingFields = [];
1507
+
1508
+ if (!addressValue.streetAddress?.name) {
1509
+ missingFields.push('street name');
1510
+ }
1511
+
1512
+ if (!addressValue.streetAddress?.number) {
1513
+ missingFields.push('street number');
1514
+ }
1515
+
1516
+ if (!addressValue.city) {
1517
+ missingFields.push('city');
1518
+ }
1519
+
1520
+ if (!addressValue.subdivision) {
1521
+ missingFields.push('state');
1522
+ }
1523
+
1524
+ if (!addressValue.postalCode) {
1525
+ missingFields.push('postal code');
1526
+ }
1527
+
1528
+ if (!addressValue.location?.latitude || !addressValue.location?.longitude) {
1529
+ missingFields.push('valid location details');
1530
+ }
1531
+
1532
+ if (missingFields.length > 0) {
1533
+ const message = `Please provide: ${missingFields.join(', ')}`;
1534
+ showAddressValidationError(validationMessage, saveBtn, message);
1535
+ return false;
1536
+ }
1537
+
1538
+ hideAddressValidationError(validationMessage, saveBtn);
1539
+ return true;
1540
+ }
1541
+
1542
+ function showAddressValidationError(validationMessage, saveBtn, message) {
1543
+ validationMessage.text = message;
1544
+ validationMessage.show();
1545
+ saveBtn.disable();
1546
+ }
1547
+
1548
+ function hideAddressValidationError(validationMessage, saveBtn) {
1549
+ validationMessage.hide();
1550
+ saveBtn.enable();
1551
+ }
1552
+
1553
+ function saveAddressFromSingleInput($item, itemData) {
1554
+ if (!validateAddressCompleteness($item)) {
1555
+ return;
1556
+ }
1557
+
1558
+ const addressInput = $item('#addressEditInput');
1559
+ const addressValue = addressInput.value;
1560
+
1561
+ const convertedAddress = parseAddressInput(addressValue, itemData.address);
1562
+
1563
+ const formattedAddress = formatFullAddress(convertedAddress);
1564
+ $item('#addressItemtext').text = formattedAddress;
1565
+
1566
+ if (itemData.isNewAddress) {
1567
+ setNewAddress(itemData._id, convertedAddress);
1568
+ } else {
1569
+ updateAddress(itemData._id, convertedAddress);
1570
+ }
1571
+
1572
+ $item('#addressItemStates').changeState(ADDRESS_STATES.VIEW);
1573
+ $item('#addressValidationMessage').hide();
1574
+ }
1575
+
1576
+ function removeNewAddressFromRepeater(addressId) {
1577
+ const currentData = $w('#addressesList').data || [];
1578
+ const updatedData = currentData.filter(item => item._id !== addressId);
1579
+ $w('#addressesList').data = updatedData;
1580
+ }
1581
+
1582
+ function setNewAddress(addressId, addressData) {
1583
+ if (!itemMemberObj.addresses) {
1584
+ itemMemberObj.addresses = [];
1585
+ }
1586
+ itemMemberObj.addresses.push(addressData);
1587
+
1588
+ if (!itemMemberObj.addressDisplayOption) {
1589
+ itemMemberObj.addressDisplayOption = [];
1590
+ }
1591
+ itemMemberObj.addressDisplayOption.push({
1592
+ key: addressData.key,
1593
+ isMain: false,
1594
+ });
1595
+
1596
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1597
+ }
1598
+
1599
+ function extractStreetNumber(line1) {
1600
+ if (!line1) return '';
1601
+ const match = line1.match(/^\d+/);
1602
+ return match ? match[0] : '';
1603
+ }
1604
+
1605
+ function extractStreetName(line1) {
1606
+ if (!line1) return '';
1607
+ return line1.replace(/^\d+\s*/, '').trim();
1608
+ }
1609
+
1610
+ function renderAddressesList(updatedAddresses) {
1611
+ let addressData = updatedAddresses || [];
1612
+
1613
+ if (!addressData || addressData?.length === 0) {
1614
+ const addresses = Array.isArray(itemMemberObj.addresses) ? itemMemberObj.addresses : [];
1615
+ const displayOptions = Array.isArray(itemMemberObj.addressDisplayOption)
1616
+ ? itemMemberObj.addressDisplayOption
1617
+ : [];
1618
+
1619
+ addressData = addresses.map((address, index) => {
1620
+ const displayOption = displayOptions.find(opt => opt.key === address.key);
1621
+ return {
1622
+ _id: address.key || `address_${index}`,
1623
+ address,
1624
+ isMain: displayOption?.isMain || false,
1625
+ addressStatus: address.addressStatus || FALLBACK_ADDRESS_STATUS,
1626
+ isNewAddress: false,
1627
+ };
1628
+ });
1629
+ }
1630
+
1631
+ const repeater = $w('#addressesList');
1632
+ repeater.data = addressData;
1633
+
1634
+ updateAddressAddButtonState();
1635
+ }
1636
+
1637
+ function updateAddressAddButtonState() {
1638
+ const currentData = $w('#addressesList').data || [];
1639
+ const newAddressButton = $w('#newAddressButton');
1640
+
1641
+ if (currentData.length >= MAX_ADDRESSES_COUNT) {
1642
+ newAddressButton.disable();
1643
+ } else {
1644
+ newAddressButton.enable();
1645
+ }
1646
+ }
1647
+
1648
+ function updateMainAddressSelection(selectedId) {
1649
+ if (!itemMemberObj.addressDisplayOption) {
1650
+ itemMemberObj.addressDisplayOption = [];
1651
+ }
1652
+
1653
+ itemMemberObj.addressDisplayOption.forEach(option => {
1654
+ option.isMain = false;
1655
+ });
1656
+
1657
+ const selectedOption = itemMemberObj.addressDisplayOption.find(opt => opt.key === selectedId);
1658
+
1659
+ selectedOption.isMain = true;
1660
+ }
1661
+
1662
+ function updateAddressStatus(addressId, newStatus) {
1663
+ const addresses = Array.isArray(itemMemberObj.addresses) ? itemMemberObj.addresses : [];
1664
+ const addressIndex = addresses.findIndex(
1665
+ addr => (addr.key || `address_${addresses.indexOf(addr)}`) === addressId
1666
+ );
1667
+
1668
+ if (addressIndex !== -1) {
1669
+ itemMemberObj.addresses[addressIndex].addressStatus = newStatus;
1670
+ }
1671
+ }
1672
+
1673
+ function updateAddress(addressId, newAddressValue) {
1674
+ const addresses = Array.isArray(itemMemberObj.addresses) ? itemMemberObj.addresses : [];
1675
+ const addressIndex = addresses.findIndex(
1676
+ addr => (addr.key || `address_${addresses.indexOf(addr)}`) === addressId
1677
+ );
1678
+
1679
+ if (addressIndex !== -1) {
1680
+ itemMemberObj.addresses[addressIndex] = {
1681
+ ...itemMemberObj.addresses[addressIndex],
1682
+ ...newAddressValue,
1683
+ };
1684
+
1685
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1686
+ }
1687
+ }
1688
+
1689
+ function removeAddress(addressId) {
1690
+ if (itemMemberObj.addresses) {
1691
+ itemMemberObj.addresses = itemMemberObj.addresses.filter(
1692
+ addr => (addr.key || `address_${itemMemberObj.addresses.indexOf(addr)}`) !== addressId
1693
+ );
1694
+ }
1695
+
1696
+ if (itemMemberObj.addressDisplayOption) {
1697
+ itemMemberObj.addressDisplayOption = itemMemberObj.addressDisplayOption.filter(
1698
+ opt => opt.key !== addressId
1699
+ );
1700
+ }
1701
+
1702
+ renderAddressesList();
1703
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1704
+ }
1705
+
1706
+ function formatFullAddress(addr) {
1707
+ if (!addr) return '';
1708
+
1709
+ const parts = [];
1710
+
1711
+ if (addr.line1) parts.push(addr.line1);
1712
+ if (addr.city) parts.push(addr.city);
1713
+ if (addr.state && addr.postalcode) {
1714
+ parts.push(`${addr.state} ${addr.postalcode}`);
1715
+ } else if (addr.state) {
1716
+ parts.push(addr.state);
1717
+ } else if (addr.postalcode) {
1718
+ parts.push(addr.postalcode);
1719
+ }
1720
+
1721
+ return parts.join(', ') || 'No address entered';
1722
+ }
1723
+
1724
+ function setupPhoneRepeater() {
1725
+ $w('#phoneNumbersList').onItemReady(handlePhoneItem);
1726
+ renderPhonesList();
1727
+
1728
+ $w('#addPhoneButton').onClick(addNewPhone);
1729
+
1730
+ setupPhoneRepeaterEventListeners();
1731
+ }
1732
+
1733
+ function setupPhoneRepeaterEventListeners() {
1734
+ $w('#phoneInput').onInput(event => {
1735
+ const data = $w('#phoneNumbersList').data;
1736
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1737
+ const phoneValue = event.target.value;
1738
+
1739
+ updatePhoneNumber(clickedItemData._id, phoneValue);
1740
+
1741
+ if (clickedItemData.isNewPhone && phoneValue.trim()) {
1742
+ addNewPhoneToData(clickedItemData._id, phoneValue.trim());
1743
+ clickedItemData.isNewPhone = false;
1744
+ }
1745
+
1746
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1747
+ });
1748
+
1749
+ $w('#showPhoneCheckbox').onChange(event => {
1750
+ const data = $w('#phoneNumbersList').data;
1751
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1752
+ const $item = $w.at(event.context);
1753
+
1754
+ $w('#showPhoneCheckbox').checked = false;
1755
+ $item('#showPhoneCheckbox').checked = true;
1756
+
1757
+ updateShowPhoneSelection(clickedItemData._id, event.target.checked);
1758
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1759
+ });
1760
+
1761
+ $w('#removePhoneBtn').onClick(async event => {
1762
+ const data = $w('#phoneNumbersList').data;
1763
+ const clickedItemData = data.find(item => item._id === event.context.itemId);
1764
+ const result = await wixWindow.openLightbox(LIGHTBOX_NAMES.DELETE_CONFIRM);
1765
+ if (result && result.toDelete) {
1766
+ removePhone(clickedItemData._id);
1767
+ }
1768
+ });
1769
+ }
1770
+
1771
+ function addNewPhone() {
1772
+ const currentData = $w('#phoneNumbersList').data || [];
1773
+
1774
+ if (currentData.length >= MAX_PHONES_COUNT) {
1775
+ return;
1776
+ }
1777
+
1778
+ const newPhoneId = generateId();
1779
+ const newPhoneItem = {
1780
+ _id: newPhoneId,
1781
+ phoneNumber: '',
1782
+ showPhone: false,
1783
+ isNewPhone: true,
1784
+ phoneIndex: currentData.length + 1,
1785
+ };
1786
+
1787
+ renderPhonesList([...currentData, newPhoneItem]);
1788
+ }
1789
+
1790
+ function handlePhoneItem($item, itemData) {
1791
+ $item('#phoneInput').value = itemData.phoneNumber || '';
1792
+ $item('#showPhoneCheckbox').checked = itemData.showPhone || false;
1793
+ $item('#phoneNumberLabel').text = `Phone ${itemData.phoneIndex}`;
1794
+ }
1795
+
1796
+ function renderPhonesList(updatedPhones) {
1797
+ let phoneData = updatedPhones || [];
1798
+
1799
+ if (!phoneData || phoneData?.length === 0) {
1800
+ const phones = Array.isArray(itemMemberObj.phones) ? itemMemberObj.phones : [];
1801
+
1802
+ phoneData = phones.map((phone, index) => ({
1803
+ _id: `phone_${index}`,
1804
+ phoneNumber: phone,
1805
+ showPhone: phone === itemMemberObj.toShowPhone,
1806
+ isNewPhone: false,
1807
+ phoneIndex: index + 1,
1808
+ }));
1809
+ }
1810
+
1811
+ const repeater = $w('#phoneNumbersList');
1812
+
1813
+ repeater.data = phoneData;
1814
+ updatePhoneAddButtonState();
1815
+ }
1816
+
1817
+ function updatePhoneAddButtonState() {
1818
+ const currentData = $w('#phoneNumbersList').data || [];
1819
+ const addPhoneButton = $w('#addPhoneButton');
1820
+
1821
+ if (currentData.length >= MAX_PHONES_COUNT) {
1822
+ addPhoneButton.disable();
1823
+ } else {
1824
+ addPhoneButton.enable();
1825
+ }
1826
+ }
1827
+
1828
+ function updatePhoneNumber(phoneId, newPhoneNumber) {
1829
+ const currentData = $w('#phoneNumbersList').data || [];
1830
+ const itemIndex = currentData.findIndex(item => item._id === phoneId);
1831
+
1832
+ if (itemIndex !== -1) {
1833
+ currentData[itemIndex].phoneNumber = newPhoneNumber;
1834
+ renderPhonesList(currentData);
1835
+ syncPhonesFromRepeater();
1836
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1837
+ }
1838
+ }
1839
+
1840
+ function syncPhonesFromRepeater() {
1841
+ const phoneData = $w('#phoneNumbersList').data || [];
1842
+ itemMemberObj.phones = phoneData
1843
+ .filter(item => !item.isNewPhone && item.phoneNumber.trim())
1844
+ .map(item => item.phoneNumber);
1845
+ }
1846
+
1847
+ function addNewPhoneToData(phoneId, phoneNumber) {
1848
+ if (!itemMemberObj.phones) {
1849
+ itemMemberObj.phones = [];
1850
+ }
1851
+
1852
+ itemMemberObj.phones.push(phoneNumber);
1853
+ renderPhonesList();
1854
+ checkFormChanges(FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING);
1855
+ }
1856
+
1857
+ function removePhone(phoneId) {
1858
+ const currentData = $w('#phoneNumbersList').data || [];
1859
+ const phoneToRemove = currentData.find(item => item._id === phoneId);
1860
+
1861
+ if (phoneToRemove) {
1862
+ if (itemMemberObj.toShowPhone === phoneToRemove.phoneNumber) {
1863
+ itemMemberObj.toShowPhone = null;
1864
+ }
1865
+
1866
+ const updatedData = currentData.filter(item => item._id !== phoneId);
1867
+ renderPhonesList(updatedData);
1868
+ }
1869
+ }
1870
+
1871
+ function updateShowPhoneSelection(phoneId, isVisible) {
1872
+ const currentData = $w('#phoneNumbersList').data || [];
1873
+ const selectedItem = currentData.find(item => item._id === phoneId);
1874
+
1875
+ if (selectedItem && selectedItem.phoneNumber) {
1876
+ if (isVisible) {
1877
+ itemMemberObj.toShowPhone = selectedItem.phoneNumber;
1878
+ } else {
1879
+ itemMemberObj.toShowPhone = null;
1880
+ }
1881
+ }
1882
+ }
1883
+
1884
+ function getToShowPhone() {
1885
+ return itemMemberObj.toShowPhone || null;
1886
+ }
1887
+
1888
+ function getContactAndBookingData() {
1889
+ const showWixUrl = $w('#showUrlWixCheckbox').checked;
1890
+ const showExistingUrl = $w('#showExsistingUrlCheckbox').checked;
1891
+
1892
+ const addresses = Array.isArray(itemMemberObj.addresses) ? itemMemberObj.addresses : [];
1893
+ const phones = Array.isArray(itemMemberObj.phones) ? itemMemberObj.phones : [];
1894
+
1895
+ return {
1896
+ showContactForm: $w('#showCotactFormCheckbox').checked,
1897
+ contactFormEmail: $w('#contactFormEmailInput').value,
1898
+ toShowPhone: getToShowPhone(),
1899
+ bookingUrl: $w('#schedulingLinkInput').value,
1900
+ website: $w('#UrlInput').value,
1901
+ showWebsite: showExistingUrl,
1902
+ showWixUrl,
1903
+ addresses,
1904
+ addressDisplayOption: itemMemberObj.addressDisplayOption || [],
1905
+ phones,
1906
+ };
1907
+ }
1908
+ async function saveContactBooking() {
1909
+ // if showWixUrl value changes then update optWebsiteCheckbox value
1910
+ $w('#optWebsiteCheckbox').checked = itemMemberObj.showWixUrl;
1911
+
1912
+ const beforeData = JSON.parse(JSON.stringify(itemMemberObj));
1913
+ const contactChanges = getContactAndBookingData();
1914
+
1915
+ const formData = {
1916
+ ...itemMemberObj,
1917
+ ...contactChanges,
1918
+ };
1919
+
1920
+ // Log the specific contact & booking data changes
1921
+ console.group('Contact & Booking Save Attempt');
1922
+ console.log('Current Data:', beforeData);
1923
+ console.log('Changes Being Applied:', contactChanges);
1924
+ console.log('Final Form Data:', formData);
1925
+ console.log('Address Changes:', {
1926
+ addressCount: contactChanges.addresses?.length || 0,
1927
+ addressDisplayOptions: contactChanges.addressDisplayOption,
1928
+ });
1929
+ console.log('Phone Selection:', contactChanges.toShowPhone);
1930
+ console.groupEnd();
1931
+
1932
+ const result = await saveData(formData);
1933
+ formHasUnsavedChanges[FORM_SECTION_HANDLER_MAP.CONTACT_BOOKING.section] = false;
1934
+ handleSaveDataFeedback($w('#contactMessage'), result.message);
1935
+ }
1936
+
1937
+ function handleSaveDataFeedback($messageElement, message) {
1938
+ $messageElement.text = message;
1939
+ $messageElement.expand();
1940
+ setTimeout(() => {
1941
+ $messageElement.collapse();
1942
+ }, 5000);
1943
+ }
1944
+
1945
+ function setGallery() {
1946
+ // Initialize gallery array if it doesn't exist
1947
+ if (!itemMemberObj.gallery) {
1948
+ itemMemberObj.gallery = [];
1949
+ }
1950
+ const gallery = itemMemberObj.gallery;
1951
+
1952
+ const galleryData = buildGalleryData(gallery);
1953
+
1954
+ $w('#galleryRepeater').data = galleryData;
1955
+ }
1956
+
1957
+ function buildGalleryData(gallery) {
1958
+ return [
1959
+ { _id: 'add-item', isAdd: true },
1960
+ ...gallery.map(image => ({
1961
+ _id: generateId(),
1962
+ image,
1963
+ isAdd: false,
1964
+ })),
1965
+ ];
1966
+ }
1967
+
1968
+ function handleGalleryItem($item, itemData) {
1969
+ const multiStateBox = $item('#galleryMSB');
1970
+
1971
+ if (itemData.isAdd) {
1972
+ setupAddImageState($item, multiStateBox);
1973
+ } else {
1974
+ setupImageState($item, itemData, multiStateBox);
1975
+ }
1976
+ }
1977
+
1978
+ function setupAddImageState($item, multiStateBox) {
1979
+ multiStateBox.changeState(GALLERY_STATES.ADD);
1980
+ }
1981
+
1982
+ function setupImageState($item, itemData, multiStateBox) {
1983
+ multiStateBox.changeState(GALLERY_STATES.VIEW);
1984
+ $item('#galleryImage').src = itemData.image.src;
1985
+ }
1986
+
1987
+ async function saveGalleryToCMS() {
1988
+ const formData = {
1989
+ ...itemMemberObj,
1990
+ gallery: itemMemberObj.gallery,
1991
+ };
1992
+
1993
+ await saveData(formData);
1994
+ }
1995
+
1996
+ function formatFileName(fullName, maxBaseLength = 23) {
1997
+ const dotIndex = fullName.lastIndexOf('.');
1998
+ if (dotIndex === -1 || fullName.length <= maxBaseLength + 4) return fullName;
1999
+
2000
+ const name = fullName.slice(0, dotIndex);
2001
+ const ext = fullName.slice(dotIndex);
2002
+ return `${name.slice(0, maxBaseLength)}...${ext}`;
2003
+ }
2004
+ }
2005
+
2006
+ module.exports = {
2007
+ personalDetailsFormOnReady,
2008
+ };