1import { decorateIcons } from '../../scripts/aem.js'; 2import { 3 getConfigValue, 4 getMaxLengthByCountryCode, 5 getCountryIso, 6} from '../../scripts/configs.js'; 7import { 8 sanitizeDOMSync, 9 createModalFromContent, 10 openModal, 11 closeModal, 12 showToastNotification, 13} from '../../scripts/utils.js'; 14import { 15 createAddress, updateAddress, makePrimaryAddress, deleteAddress, 16 getDeliveryMatrixAddressStructure, 17 getAddressCitySegments, 18 getAddressAreas, 19 getAddressRegionSegments, 20} from '../../scripts/address/api.js'; 21import fetchExternalDataShortcode from '../../scripts/address/shortcode-api.js'; 22import { getCustomer } from '../../scripts/customer/api.js'; 23import { 24 setErrorMessageForField, 25 validateForm, 26 validateInput, 27 validatePhone, 28 showErrorMessage, 29 customSelectbox, 30 validateCustomerEmail, 31 resetMessage, 32} from '../../scripts/forms.js'; 33import { restrictSpecialCharacterText, getUserEmail } from '../../scripts/helpers/user-utils.js'; 34import { dataLayerCustomerExistCheckoutErrors } from '../../scripts/analytics/google-data-layer.js'; 35import '../../scripts/third-party/dompurify.js'; 36import debounce from '../../scripts/helpers/debounce.js'; 37import { 38 createShortCodeField, 39 createBuildingNumberField, 40 loadShortCodeMapModal, 41 allowSaveWhenApiDown, 42} from './shortCode.js'; 43 44export const isShortCodeEnabled = await getConfigValue('shortcode-enabled') === 'true'; 45export const isShortCodeMandatory = await getConfigValue('short-code-mandatory') === 'true'; 46 47async function validateAndStoreShortcode(form, placeholders) { 48 const shortcodeInput = form.querySelector('#short_code'); 49 if (!shortcodeInput) return true; 50 const entered = shortcodeInput.value?.trim()?.toUpperCase(); 51 52 let result; 53 try { 54 result = await fetchExternalDataShortcode(entered); 55 } catch (e) { 56 // Network / JS failure â allow save 57 return allowSaveWhenApiDown(form, placeholders, e?.message); 58 } 59 60 const status = String(result?.status || '').toUpperCase(); 61 const message = String(result?.message || ''); 62 const details = result?.addressDetails || null; 63 // API AUTH / SERVER FAILURE â validation not possible 64 if ( 65 status === 'FAILED' 66 && ( 67 message.includes('401') || message.includes('403') || message.includes('404') || message.includes('500') || message.includes('503') 68 ) 69 ) { 70 return allowSaveWhenApiDown(form, placeholders, message); 71 } 72 if (status !== 'SUCCESS' || !details) { 73 const errorBox = form.querySelector('.shortcode-error'); 74 if (errorBox) { 75 const msg = placeholders?.invalidShortcodeData 76 || 'Invalid Short Address, please try again.'; 77 errorBox.textContent = msg; 78 errorBox.classList.remove('hide'); 79 } 80 shortcodeInput.classList.add('invalid'); 81 return false; 82 } 83 84 const errorBox = form.querySelector('.shortcode-error'); 85 if (errorBox) { 86 errorBox.textContent = ''; 87 errorBox.classList.add('hide'); 88 } 89 shortcodeInput.classList.remove('invalid'); 90 91 const { 92 latitude, longitude, postalCode, additionalNumber, 93 } = details; 94 if (latitude) shortcodeInput.dataset.latitude = latitude; 95 if (longitude) shortcodeInput.dataset.longitude = longitude; 96 if (postalCode) shortcodeInput.dataset.postalCode = postalCode; 97 if (additionalNumber) shortcodeInput.dataset.additionalNumber = additionalNumber; 98 99 return true; 100} 101 102const countryRules = { 103 QA: { 104 required: ['street', 'area', 'address_building_segment', 'address_city_segment'], 105 notRequired: ['address'], 106 }, 107 AE: { 108 required: ['address_building_segment', 'address_city_segment', 'area', 'street'], 109 notRequired: ['address_apartment_segment'], 110 }, 111 KW: { 112 required: ['address_building_segment', 'address_block_segment', 'area', 'street', 'governate'], 113 notRequired: ['address_apartment_segment'], 114 }, 115 SA: { 116 required: ['address_building_segment', 'area', 'street', 'address_city_segment'], 117 notRequired: ['address_apartment_segment', 'address_block_segment', 'secondary_number', 'longitude', 'latitude', 'postcode'], 118 }, 119 EG: {
120 required: ['address_building_segment', 'address_region_segment', 'street', 'address_city_segment'], 121 notRequired: ['address_landmark_segment', 'postcode', 'address_floor_segment'], 122 }, 123 JO: { 124 required: ['address_district_segment', 'address_city_segment', 'address_building_segment', 'street'], 125 notRequired: ['address_landmark_segment'], 126 }, 127 BH: { 128 required: ['address_road_segment', 'address_building_segment', 'address_block_segment', 'address_district_segment', 'address_city_segment'], 129 notRequired: ['address_apartment_segment'], 130 }, 131}; 132if (isShortCodeEnabled) { 133 if (isShortCodeMandatory) { 134 countryRules.SA.required.push('short_code'); 135 } else { 136 countryRules.SA.notRequired.push('short_code'); 137 } 138} 139const countriesWithRegions = ['EG', 'JO', 'BH']; 140 141// adding toast notification 142export function addStatusMessage(message) { 143 showToastNotification(message); 144 window.scrollTo({ 145 top: 0, 146 behavior: 'smooth', 147 }); 148} 149 150export async function updateOptionData(form, options, type) { 151 const selectEle = form?.querySelector(type); 152 const selectItemsDiv = selectEle?.querySelector('.select-items'); 153 if (selectItemsDiv) { 154 selectItemsDiv.innerHTML = ''; 155 // Rebuild dropdown from options 156 Array.from(options?.items).forEach((option) => { 157 if (option?.location_id) { 158 const cityDiv = document.createElement('div'); 159 cityDiv.setAttribute('data-value', option?.location_id); 160 cityDiv.textContent = option?.label; 161 selectItemsDiv.appendChild(cityDiv); 162 } 163 }); 164 } 165} 166 167export const accountNoAddress = await getConfigValue('account-no-adddress-enabled'); 168export const isAddAddressModalEnabled = await getConfigValue('add-address-modal-enabled'); 169const dropdownLabelEnabled = await getConfigValue('dropdown-label-enabled'); 170const disableInlineValidation = await getConfigValue('disable-inline-validation'); 171function selectItemsTriggerModal(formElement) { 172 formElement.querySelectorAll('.select-items div').forEach((optionitem) => { 173 optionitem.addEventListener('click', function (e) { 174 e.stopPropagation(); 175 const currentDropdown = this.closest('.custom-select'); 176 const selected = currentDropdown.querySelector('.select-selected'); 177 selected.textContent = this.textContent; 178 selected.dataset.value = this.dataset.value; 179 this.parentNode.classList.add('select-hide'); 180 const parentInput = this.closest('div.input-field-wrapper.dropdown'); 181 parentInput.querySelector('.error-message-container')?.classList.add('hide'); 182 currentDropdown.querySelector('.select-search')?.classList.add('hide'); 183 }); 184 }); 185} 186 187async function showDropdownModal(blockConfigs, modalType, placeholders, formElement, formDropdown) { 188 const modalId = `select-${modalType}-modal`; 189 let modalTitle; 190 let elementId; 191 let values; 192 const countryCode = await getConfigValue('country-code'); 193 const hasDistrict = formElement.querySelector('#address_district_segment'); 194 const hasRegion = formElement.querySelector('#address_region_segment'); 195 const hasCity = formElement.querySelector('#address_city_segment'); 196 if (modalType === 'city' && !hasDistrict && !hasRegion) { 197 modalTitle = placeholders.selectCity || 'Select City'; 198 elementId = '#address_city_segment .select-selected'; 199 values = await getAddressCitySegments(countryCode); 200 } else if (modalType === 'city' && hasDistrict) { 201 modalTitle = placeholders.selectCity || 'Select City'; 202 elementId = '#address_city_segment .select-selected'; 203 const districtValue = formElement.querySelector('.address_district_segment .select-selected')?.getAttribute('data-value') || formElement.querySelector('.address_district_segment .select-selected')?.getAttribute('data-initial-value'); 204 values = await getAddressCitySegments(countryCode, districtValue); 205 } else if (modalType === 'city' && hasRegion) { 206 modalTitle = placeholders.selectCity || 'Select City'; 207 elementId = '#address_city_segment .select-selected'; 208 const regionValue = formElement.querySelector('.address_region_segment .select-selected')?.getAttribute('data-value') || formElement.querySelector('.address_regi
208on_segment .select-selected')?.getAttribute('data-initial-value'); 209 values = await getAddressCitySegments(countryCode, regionValue); 210 } else if (modalType === 'area' && hasCity) { 211 modalTitle = placeholders.selectArea || 'Select Area'; 212 elementId = '#area .select-selected'; 213 const cityValue = formElement.querySelector('.address_city_segment .select-selected')?.getAttribute('data-value') || formElement.querySelector('.address_city_segment .select-selected')?.getAttribute('data-initial-value'); 214 if (!cityValue) { 215 return; 216 } 217 values = await getAddressAreas(countryCode, cityValue); 218 } else if (modalType === 'area') { 219 modalTitle = placeholders.selectArea || 'Select Area'; 220 elementId = '#area .select-selected'; 221 const cityValue = formElement.querySelector('.address_city_segment .select-selected')?.getAttribute('data-value') || formElement.querySelector('.address_city_segment .select-selected')?.getAttribute('data-initial-value'); 222 values = await getAddressAreas(countryCode, cityValue); 223 } else if (modalType === 'district') { 224 modalTitle = placeholders.selectDistrict || 'Select District'; 225 elementId = '#address_district_segment .select-selected'; 226 values = await getAddressRegionSegments(countryCode); 227 } else if (modalType === 'region') { 228 modalTitle = placeholders.selectRegion || 'Select Region'; 229 elementId = '#address_region_segment .select-selected'; 230 values = await getAddressRegionSegments(countryCode); 231 } 232 const modalContent = document.createElement('div'); 233 modalContent.classList.add(`select-${modalType}-content`); 234 const itemList = document.createElement('ul'); 235 itemList.innerHTML = `<li class="no-results hide">${placeholders.dropdownNoResultsFound || 'No Results Found'}</li>`; 236 values?.items.forEach((val) => { 237 const listItem = document.createElement('li'); 238 listItem.id = val.location_id; 239 listItem.textContent = val.label; 240 itemList.appendChild(listItem); 241 }); 242 243 /* searchbox created */ 244 const searchContainerDiv = document.createElement('div'); 245 const searchBarDiv = document.createElement('div'); 246 searchBarDiv.setAttribute('data-filter-attr-name', 'searchBox'); 247 searchBarDiv.setAttribute('data-filter-attr-value', 'searchBox'); 248 searchBarDiv.classList.add('search-field'); 249 const searchIcon = document.createElement('span'); 250 searchIcon.classList.add('icon', 'search-icon'); 251 const searchInput = document.createElement('input'); 252 searchInput.type = 'text'; 253 searchInput.classList.add('searchBox', 'search-input'); 254 searchInput.placeholder = placeholders.addressDropdownSearchPlaceholder || 'Search'; 255 searchBarDiv.appendChild(searchIcon); 256 searchBarDiv.appendChild(searchInput); 257 searchContainerDiv.appendChild(searchBarDiv); 258 searchContainerDiv.appendChild(itemList); 259 260 modalContent.innerHTML = searchContainerDiv.innerHTML; 261 262 const removeDropdownErrors = () => { 263 const inputWrapper = formDropdown?.parentElement?.parentElement; 264 inputWrapper?.classList?.remove('invalid'); 265 inputWrapper?.querySelector('.error-message-container')?.classList.add('hide'); 266 }; 267 268 const toggleAreaField = () => { 269 const getCityElement = formElement.querySelector('#address_city_segment .select-selected'); 270 const getAreaElement = formElement.querySelector('#area .select-selected'); 271 const getPlaceholderSpan = getAreaElement.querySelector('span'); 272 if (getPlaceholderSpan) { 273 getPlaceholderSpan.textContent = placeholders.selectArea || 'Select Area'; 274 } else { 275 getAreaElement.textContent = blockConfigs['checkout-area-dropdown-placeholder']; 276 } 277 getAreaElement.setAttribute('data-value', ''); 278 getAreaElement.setAttribute('data-initial-value', ''); 279 if (getCityElement) { 280 getAreaElement.setAttribute('aria-disabled', 'false'); 281 } else { 282 getAreaElement.setAttribute('aria-disabled', 'true'); 283 } 284 }; 285 286 const toggleCityField = () => { 287 const getDistrictElement = formElement.querySelector('#address_district_segment .select-selected'); 288 const getRegionElement = formElement.querySelector('#address_region_segment .select-selected'); 289 const getCityElement = formElement.querySelector('#address_city_segment .select-selected'); 290 const getPlaceholderSpan = getCityElement.querySelector('span'); 291 if (getPlaceholderSpan && !getDistrictElement && !getRegionElement) { 292 getPlaceholderSpan.textContent = placeholders.selectArea || 'Select Area'; 293 } else if (getDistrictElement || getRegionElement) { 294 if (getPlaceholderSpan) { 295 getPlaceholderSpan.textContent = placeholders.selectCity || 'Select City'; 296 } else { 297 getCityElement.textContent = placeholders.selectCity || 'Select City'; 298 } 299 } else { 300 getCityElement.textContent = blockConfigs['checkout-city-dropdown-placeholder']; 301 } 302 getCityElement.setAttribute('data-value', ''); 303 getCityElement.setAttribute('data-initial-value', ''); 304 if (getDistrictElement || getRegionElement) { 305 getCityElement.setAttribute('aria-disabled', 'false'); 306 } else { 307 getCityElement.setAttribute('aria-disabled', 'true'); 308 } 309 }; 310 311 modalContent.addEventListener('click', (e) => { 312 if (e.target.tagName === 'LI') { 313 const getElem = formElement.querySelector(elementId); 314 getElem.setAttribute('data-value', `${e.target.id}`); 315 if (dropdownLabelEnabled && getElem.querySelector('span')) { 316 getElem.querySelector('span').innerHTML = e.target.textContent; 317 } else { 318 getElem.innerHTML = e.target.textContent; 319 } 320 closeModal(modalId); 321 removeDropdownErrors(); 322 if (modalType === 'city' && !hasDistrict && !hasRegion) { 323 toggleAreaField(); 324 }
324 else if (modalType === 'district' || modalType === 'region') { 325 toggleCityField(); 326 } 327 } 328 }); 329 const listItems = modalContent.querySelectorAll('li'); 330 const searchInputElem = modalContent.querySelector('.searchBox'); 331 const noResultsOption = modalContent?.querySelector('.no-results'); 332 searchInputElem.addEventListener('keyup', (e) => { 333 const filterText = e.target.value.toLowerCase(); // Get input and convert to lowercase 334 let anyVisible = false; 335 const filteredItems = Array.from(listItems)?.filter((item) => !item.classList.contains('no-results')); 336 filteredItems.forEach((item) => { 337 const itemText = item.textContent.toLowerCase(); // Convert list item text to lowercase 338 if (itemText.includes(filterText)) { 339 item.style.display = ''; // Show matching item 340 anyVisible = true; 341 } else { 342 item.style.display = 'none'; // Hide non-matching item 343 } 344 }); 345 346 if (!anyVisible) { 347 noResultsOption.classList.remove('hide'); 348 } else { 349 noResultsOption.classList.add('hide'); 350 } 351 }); 352 353 const arrowLeftIcon = blockConfigs?.flags?.arrowLeftIcon ? blockConfigs.flags.arrowLeftIcon : 'arrow-left-blue'; 354 const closeIcon = blockConfigs?.flags?.closeIcon ? blockConfigs.flags.closeIcon : 'icon-close-blue'; 355 await createModalFromContent( 356 modalId, 357 modalTitle, 358 modalContent, 359 [`${modalId}`], 360 arrowLeftIcon, 361 true, 362 closeIcon, 363 ); 364 openModal(modalId); 365 const backButton = document.querySelector(`dialog.${modalId} .modal-header span`); 366 backButton.addEventListener('click', () => { 367 closeModal(modalId); 368 }); 369} 370 371export async function wrapTwoDivsinWrapper(firstDiv, secondDiv, className) { 372 if (firstDiv.closest(`.${className}`)) { 373 return; 374 } 375 const WrapperDiv = document.createElement('div'); 376 WrapperDiv.className = className; // Add a class to the wrapper div if needed 377 378 // Insert the wrapper div before the first div 379 firstDiv.parentNode.insertBefore(WrapperDiv, firstDiv); 380 // Move the two divs into the wrapper div 381 WrapperDiv.appendChild(firstDiv); 382 WrapperDiv.appendChild(secondDiv); 383} 384 385function customSelectboxModal(blockConfigs, placeholders, formElement) { 386 const searchInput = formElement.querySelector('.select-search'); 387 const options = formElement?.querySelectorAll('.select-items div:not([disabled])'); 388 const noResultsOption = formElement?.querySelector('.no-results'); 389 searchInput?.addEventListener('keyup', function () { 390 const filter = this.value.toLowerCase(); 391 let anyVisible = false; 392 393 options.forEach((option) => { 394 const text = option.textContent.toLowerCase(); 395 if (text.includes(filter)) { 396 noResultsOption.classList.add('hide'); 397 option.classList.remove('hide'); 398 anyVisible = true; 399 } else { 400 option.classList.add('hide'); 401 } 402 }); 403 404 if (!anyVisible) { 405 noResultsOption.classList.remove('hide'); 406 } else { 407 noResultsOption.classList.add('hide'); 408 } 409 }); 410 411 if (!formElement) return; 412 const hasDistrict = formElement.querySelector('#address_district_segment'); 413 const hasRegion = formElement.querySelector('#address_region_segment'); 414 formElement.querySelectorAll('.custom-select').forEach((item) => { 415 item.addEventListener('click', function () { 416 let modalOpened = false; 417 if (item.getAttribute('name') === 'address_city_segment' && !hasDistrict && !hasRegion) { 418 showDropdownModal(blockConfigs, 'city', placeholders, formElement, item); 419 modalOpened = true; 420 } else if (item.getAttribute('name') === 'address_city_segment' && hasDistrict && (item.querySelector('.select-selected').getAttribute('aria-disabled') === 'false' || item.querySelector('.select-selected').getAttribute('data-initial-value'))) { 421 item.querySelector('.select-items').innerHTML = ''; 422 showDropdownModal(blockConfigs, 'city', placeholders, formElement, item); 423 modalOpened = true; 424 } else if (item.getAttribute('name') === 'address_city_segment' && hasRegion && (item.querySelector('.select-selected').getAttribute('aria-disabled') === 'false' || item.querySelector('.select-selected').getAttribute('data-initial-value'))) { 425 item.querySelector('.select-items').innerHTML = ''; 426 showDropdownModal(blockConfigs, 'city', placeholders, formElement, item); 427 modalOpened = true; 428 } else if (item.getAttribute('name') === 'area') { 429 item.querySelector('.select-items').innerHTML = ''; 430 showDropdownModal(blockConfigs, 'area', placeholders, formElement, item); 431 modalOpened = true; 432 } else if (item.getAttribute('name') === 'address_district_segment') { 433 showDropdownModal(blockConfigs, 'district', placeholders, formElement, item); 434 modalOpened = true; 435 }
435 else if (item.getAttribute('name') === 'address_region_segment') { 436 showDropdownModal(blockConfigs, 'region', placeholders, formElement, item); 437 modalOpened = true; 438 } 439 if (modalOpened) return; 440 441 formElement.querySelector('.select-search').classList.remove('hide'); 442 // closeModal(addAddressModalId); 443 const currentItems = this.parentNode.querySelector('.select-items'); 444 // Hide all other dropdowns 445 document.querySelectorAll('.select-items').forEach((selectItem) => { 446 if (selectItem !== currentItems) { 447 selectItem.classList.add('select-hide'); 448 } 449 selectItem.addEventListener('click', () => { 450 formElement.querySelector('.select-search').classList.add('hide'); 451 }); 452 }); 453 // Toggle the clicked dropdown 454 currentItems.classList.remove('select-hide'); 455 currentItems.parentNode.querySelector('.select-search')?.classList.toggle('hide'); 456 }); 457 }); 458 459 selectItemsTriggerModal(formElement); 460 461 document.addEventListener('click', (e) => { 462 if (!e.target.matches('.custom-select, .custom-select *')) { 463 formElement.querySelectorAll('.select-items').forEach((item) => { 464 item.classList.add('select-hide'); 465 }); 466 formElement.querySelector('.select-search').classList.add('hide'); 467 } 468 }); 469} 470 471// handleNoAddress 472export function handleNoAddress() { 473 const noAddress = document.querySelector('.account-no-address'); 474 noAddress?.classList.add('hide'); 475} 476 477// Handling address hovering effect 478export function handleAddressHover(addressWrapper) { 479 const cta = addressWrapper.querySelector('.make-primary-cta'); 480 cta?.addEventListener('mouseover', () => { 481 const addressFirstRow = cta.closest('.address').querySelector('.address-data'); 482 addressFirstRow?.classList.add('primary-address-data'); 483 }); 484 cta?.addEventListener('mouseout', () => { 485 const addressFirstRow = cta.closest('.address').querySelector('.address-data'); 486 addressFirstRow?.classList.remove('primary-address-data'); 487 }); 488 489 const data = addressWrapper.querySelector('.address-data'); 490 data?.addEventListener('mouseover', () => { 491 data.closest('.address').querySelector('.make-primary-cta')?.classList.add('hovered'); 492 }); 493 data?.addEventListener('mouseout', () => { 494 data.closest('.address').querySelector('.make-primary-cta')?.classList.remove('hovered'); 495 }); 496} 497 498// live search feature to a custom dropdown menu. As the user types in searchInput, 499// the function filters the dropdown options, displaying only those that match the search term 500// and showing a "No results" message if no matches are found 501function enableDropdownSearch(dropdown) { 502 if (!dropdown) return; 503 const searchInput = dropdown?.querySelector('.select-search'); 504 const options = Array.from(dropdown?.querySelectorAll('.select-items div:not([disabled])')); 505 const noResultsOption = dropdown?.querySelector('.no-results'); 506 const selectItemsContainer = dropdown.querySelector('.select-items'); 507 508 options.sort((a, b) => a.textContent.localeCompare(b.textContent)); 509 selectItemsContainer.innerHTML = ''; 510 options.forEach((option) => { option.setAttribute('tabindex', 0); selectItemsContainer.appendChild(option); }); 511 512 searchInput.addEventListener('keyup', function () { 513 const filter = this.value.toLowerCase(); 514 let anyVisible = false; 515 516 options.forEach((option) => { 517 const text = option.textContent.toLowerCase(); 518 if (text.includes(filter)) { 519 option.classList.remove('hide'); 520 anyVisible = true; 521 } else { 522 option.classList.add('hide'); 523 } 524 }); 525 526 if (!anyVisible) { 527 noResultsOption.classList.remove('hide'); 528 } else { 529 noResultsOption.classList.add('hide'); 530 } 531 }); 532} 533 534async function updateArea(countryCode, cityValue, form, placeholders) { 535 const areas = await getAddressAreas(countryCode, cityValue); 536 const areaField = form.querySelector('#area'); 537 const areaSelect = areaField.querySelector('.select-selected'); 538 const areaItems = areaField.querySelector('.select-items'); 539 const initialAreaValue = areaSelect?.getAttribute('data-initial-value'); 540 areaItems.innerHTML = `<div data-value="" class="no-results hide">${placeholders.dropdownNoResultsFound || 'No Results Found'}</div>`; 541 let selectedAreaLabel; 542 543 areas.items?.forEach((area) => { 544 if (area.location_id === Number(initialAreaValue)) { 545 selectedAreaLabel = area.label; 546 } 547 const option = document.createElement('div'); 548 option.setAttribute('data-value', area.location_id); 549 option.textContent = area.label; 550 areaItems.appendChild(option); 551 }); 552 areaField.querySelector('.select-items > div[disabled]')?.remove(); 553 enableDropdownSearch(areaField); 554 // Reset the dropdown state 555 if (dropdownLabelEnabled && areaSelect.querySelector('span')) { 556 areaSelect.querySelector('span').textContent = selectedAreaLabel || placeholders.selectArea || 'Select Area'; 557 } else { 558 areaSelect.innerHTML = selectedAreaLabel || placeholders.selectArea || 'Select Area'; 559 } 560 areaSelect.dataset.value = initialAreaValue; 561 562 if (!selectedAreaLabel) { 563 areaSelect.setAttribute('data-initial-value', ''); 564 areaSelect.dataset.value = ''; 565 } 566 567 // Clean up any existing event listeners 568 const clonedAreaSelect = areaSelect.cloneNode(true); 569 areaField.replaceChild(clonedAreaSelect, areaSelect); 570 571 // Reinitialize the custom select box 572 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.area')); 573} 574 575function updateCity(regionValue, form, placeholders, cities, resetDefaultValue) { 576 const cityField = form.querySelector('#address_city_segment'); 577 const citySelect = cityField.querySelector('.select-selected'); 578 if (resetDefaultValue) citySelect.setAttribute('data-initial-value', ''); 579 const cityItems = cityField.querySelector('.select-items'); 580 const initialCityValue = citySelect?.getAttribute('data-initial-value'); 581 cityItems.innerHTML = ''; 582 let selectedCityLabel; 583 584 const regionSpecificCities = cities?.items?.filter((city) => city.parent_id === +regionValue); 585 if (regionSpecificCities?.length) {
586 regionSpecificCities.forEach((city) => { 587 if (city.location_id === Number(initialCityValue)) { 588 selectedCityLabel = city.label; 589 } 590 const option = document.createElement('div'); 591 option.setAttribute('data-value', city.location_id); 592 option.textContent = city.label; 593 cityItems.appendChild(option); 594 }); 595 } else { 596 cityItems.innerHTML = `<div data-value="" class="no-results">${placeholders.dropdownNoResultsFound || 'No Results Found'}</div>`; 597 } 598 599 cityField.querySelector('.select-items > div[disabled]')?.remove(); 600 enableDropdownSearch(cityField); 601 602 // Reset the dropdown state 603 citySelect.textContent = selectedCityLabel || placeholders.selectCity || 'Select City'; 604 citySelect.dataset.value = initialCityValue; 605 606 // Clean up any existing event listeners 607 const clonedCitySelect = citySelect.cloneNode(true); 608 cityField.replaceChild(clonedCitySelect, citySelect); 609 610 // Reinitialize the custom select box 611 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.address_city_segment')); 612} 613 614// Hide the 'new-address-container' element by adding the 'hide' class 615export function hideNewAddressCta() { 616 const newAddressContainer = document.querySelector('.new-address-container'); 617 newAddressContainer?.classList.add('hide'); 618} 619 620// populating city in address form 621async function populateCity(form, citySegments, countryCode, placeholders) { 622 const cityField = form.querySelector('#address_city_segment'); 623 const initialCityValue = cityField?.querySelector('.select-selected')?.getAttribute('data-initial-value'); 624 let selectedCityLabel; 625 if (citySegments && citySegments?.items?.length > 0) { 626 await updateOptionData(form, citySegments, '#address_city_segment'); 627 } 628 if (cityField && citySegments) { 629 citySegments.items?.forEach((segment) => { 630 if (segment.location_id === Number(initialCityValue)) { 631 selectedCityLabel = segment.label; 632 } 633 }); 634 cityField.querySelector('.select-items > div[disabled]')?.remove(); 635 } 636 if (selectedCityLabel) { 637 const getElem = cityField.querySelector('.select-selected'); 638 if (dropdownLabelEnabled && getElem.querySelector('span')) { 639 getElem.querySelector('span').textContent = selectedCityLabel; 640 } else { 641 getElem.innerHTML = selectedCityLabel; 642 } 643 } 644 645 if (cityField) { 646 enableDropdownSearch(cityField); 647 cityField.value = initialCityValue; 648 cityField.querySelectorAll('.select-items div').forEach((item) => { 649 ['click', 'keydown'].map((e) => item.addEventListener(e, async (event) => { 650 await updateArea(countryCode, event.target.getAttribute('data-value'), form, placeholders); 651 })); 652 }); 653 } 654 if (initialCityValue) { 655 await updateArea(countryCode, cityField.value, form, placeholders); 656 } else { 657 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.area')); 658 } 659} 660 661// populating city in address form 662async function populateRegions(form, regionSegments, countryCode, placeholders) { 663 const cities = await getAddressCitySegments(countryCode); 664 const regionIdSelector = (countryCode === 'JO' || countryCode === 'BH') ? '#address_district_segment' : '#address_region_segment'; 665 const regionClassSelector = (countryCode === 'JO' || countryCode === 'BH') ? '.input-field-wrapper.dropdown.address_district_segment' : '.input-field-wrapper.dropdown.address_region_segment'; 666 if (regionSegments && regionSegments?.items?.length > 0) { 667 await updateOptionData(form, regionSegments, regionIdSelector); 668 } 669 const regionField = form.querySelector(regionIdSelector); 670 const initialRegionValue = regionField?.querySelector('.select-selected')?.getAttribute('data-initial-value'); 671 672 const regionItems = regionField.querySelector('.select-items'); 673 regionItems.innerHTML = `<div data-value="" class="no-results hide">${placeholders.dropdownNoResultsFound || 'No Results Found'}</div>`; 674 675 let selectedCityLabel; 676 if (regionField && regionSegments) { 677 regionSegments.items?.forEach((segment) => { 678 if (segment.location_id === Number(initialRegionValue)) { 679 selectedCityLabel = segment.label; 680 } 681 682 const option = document.createElement('div'); 683 option.setAttribute('data-value', segment.location_id); 684 option.textContent = segment.label; 685 regionItems.appendChild(option); 686 }); 687 regionField.querySelector('.select-items > div[disabled]')?.remove(); 688 } 689 if (selectedCityLabel) { 690 regionField.querySelector('.select-selected').textContent = selectedCityLabel; 691 } 692 if (regionField) { 693 enableDropdownSearch(regionField); 694 regionField.value = initialRegionValue; 695 regionField.querySelectorAll('.select-items div').forEach((item) => { 696 item.addEventListener('click', async (event) => { 697 await updateCity(event.target.getAttribute('data-value'), form, placeholders, cities, true); 698 }); 699 }); 700 } 701 setTimeout(() => { 702 updateCity(regionField.value, form, placeholders, cities, false); 703 }); 704 customSelectbox(form.querySelector(regionClassSelector)); 705} 706 707// to set country code 708function setCountryCode(form, countryCode) { 709 const countryId = form.querySelector('#country_id'); 710 if (countryId) { 711 countryId.value = countryCode; 712 countryId.disabled = true; 713 } 714} 715 716// Function to handle country code visibility based on phone number input 717function updateCountryCodeVisibility(inputElement, force = true) { 718 const addressModalClosest = inputElement?.closest('.add-address-modal, .edit-ad
718dress-modal, .checkout-form'); 719 const countryCodeLabel = addressModalClosest?.querySelector('.countrycode'); 720 if (countryCodeLabel) { 721 const isNumeric = /^\d*$/.test(inputElement.value.trim()) && inputElement.value.trim(); 722 if (force) { 723 countryCodeLabel.classList.toggle('hidden', !isNumeric); 724 } else { 725 countryCodeLabel.classList.toggle('hidden', false); 726 } 727 } 728} 729 730// Handle initial visibility when the modal or form is opened 731function handleInitialCheck() { 732 const telephone = document.querySelector('#telephone'); 733 if (telephone) { 734 updateCountryCodeVisibility(telephone); 735 } 736} 737 738const hasTransition = await getConfigValue('phone-input-has-transition') === 'true'; 739 740/** 741 * Validates and cleans telephone number before sending to API 742 * - Strips country prefix if present 743 * - Enforces maxlength based on country code 744 * - Returns cleaned number (without prefix) ready for API submission 745 * 746 * @param {string} telephoneValue - The telephone value from input 747 * @param {string} countryPrefix - Country prefix (e.g., '+965') 748 * @param {number} maxLength - Maximum allowed length for phone number 749 * @returns {string} - Cleaned telephone number without prefix, trimmed to maxLength 750 */ 751function validateAndCleanTelephone(telephoneValue, countryPrefix, maxLength) { 752 if (!telephoneValue) return ''; 753 754 let cleaned = telephoneValue.toString().trim(); 755 756 // Remove all occurrences of country prefix from the beginning 757 if (countryPrefix) { 758 cleaned = cleaned.replace(new RegExp(`^\\${countryPrefix}+`), ''); 759 } 760 761 // Remove any leading + sign that might remain 762 cleaned = cleaned.replace(/^\+/, ''); 763 764 // Enforce maxlength - take only the last maxLength digits 765 // This handles cases where user bypassed maxlength attribute 766 if (maxLength && cleaned.length > maxLength) { 767 cleaned = cleaned.slice(-maxLength); 768 } 769 770 return cleaned; 771} 772 773// to set telephone input 774function createTelephoneInput( 775 countryCode, 776 maxLength, 777 countryPrefix, 778 fieldDiv, 779 name, 780 label, 781 isRequired, 782 value, 783) { 784 const pattern = '[0-9]*'; 785 fieldDiv.classList.add('phonevalidation'); 786 if (hasTransition) { 787 fieldDiv.classList.add('notransistion'); 788 } 789 fieldDiv.innerHTML = ` 790 <div class="input-wrapper input-field input-phone-prefix"> 791 <label for="countrycode" class="countrycode ${hasTransition ? 'hidden' : ''}">${countryPrefix}</label> 792 <input type="tel" id="${name}" name="${name}" data-phone-prefix="${countryPrefix}" aria-label="${label}" placeholder=" " value="${value}" ${isRequired ? 'required' : ''} pattern='${pattern}' maxlength='${maxLength}' /> 793 <label for="${name}" class="input-label">${label}</label> 794 </div>`; 795 796 // Handle initial visibility on focus (when editing existing numbers) 797 document.addEventListener('focus', (event) => { 798 if (event.target && event.target.id === 'telephone') { 799 updateCountryCodeVisibility(event.target, false); 800 } 801 }, true); 802 803 // Handle initial visibility on focus (when editing existing numbers) 804 document.addEventListener('focusout', (event) => { 805 if (event.target && event.target.id === 'telephone') { 806 updateCountryCodeVisibility(event.target); 807 } 808 }, true); 809 810 handleInitialCheck(); 811} 812 813function getSelectedLabel(countryCode, field) { 814 return field.attribute.options?.find( 815 (option) => option.value === countryCode, 816 )?.label || countryCode; 817} 818 819function getSelectedValue(countryCode, field) { 820 return field.attribute.options?.find( 821 (option) => option.value === countryCode, 822 )?.value || countryCode; 823} 824 825// eslint-disable-next-line max-len 826function handleCheckoutFormStructure(formStructure, isLoggedIn, countryCode, placeholders, showAddressFormEmailForLoggedInUsers) { 827 // Helper function to add a field after a specific attribute code 828 const addFieldAfterAttributeCode = (attributeCode, newField, offset) => { 829 const fieldIndex = formStructure.findIndex( 830 (field) => field.attribute.attribute_code === attributeCode, 831 ); 832 if (fieldIndex !== -1) { 833 formStructure.splice(fieldIndex + offset, 0, newField); 834 } 835 }; 836 837 // Find the telephone field and add the email field after it 838 const telephoneFieldIndex = formStructure.findIndex((field) => field.attribute.attribute_code === 'telephone'); 839 // eslint-disable-next-line max-len 840 if (telephoneFieldIndex !== -1 && (!isLoggedIn || (isLoggedIn && showAddressFormEmailForLoggedInUsers))) { 841 const emailField = JSON.parse(JSON.stringify(formStructure[telephoneFieldIndex])); 842 emailField.attribute.attribute_code = 'email'; 843 emailField.attribute.store_label = placeholders.email || 'Email'; 844 emailField.attribute.frontend_input = 'text'; 845 emailField.attribute.required = true; 846
847 addFieldAfterAttributeCode('telephone', emailField, countryCode === 'KW' ? -1 : 0); 848 } 849} 850 851function restructureForm(form, config) { 852 if (!config?.checkoutV1) { 853 form.querySelector('.input-field-wrapper label[for="email"]')?.closest('.input-field-wrapper')?.classList?.add('emailValidation'); 854 const phoneDiv = form.querySelector('.input-field-wrapper.phonevalidation'); 855 const emailDiv = form.querySelector('.input-field-wrapper.emailValidation'); 856 const city = form.querySelector('.input-field-wrapper.dropdown.address_city_segment'); 857 const area = form.querySelector('.input-field-wrapper.dropdown.area'); 858 const district = form.querySelector('.input-field-wrapper.dropdown.address_district_segment'); 859 const region = form.querySelector('.input-field-wrapper.dropdown.address_region_segment'); 860 wrapTwoDivsinWrapper(phoneDiv, emailDiv, 'form__phone-email-wrapper'); 861 if (city && area) { 862 wrapTwoDivsinWrapper(city, area, 'form__city-area-wrapper'); 863 } else if (district && city) { 864 wrapTwoDivsinWrapper(district, city, 'form__district-city-wrapper'); 865 } else if (region && city) { 866 wrapTwoDivsinWrapper(region, city, 'form__region-city-wrapper'); 867 } 868 } 869} 870 871// to show address form 872export async function showAddressForm( 873 blockConfigs, 874 $parent, 875 placeholders, 876 addressObj, 877 newCustomer = false, 878 isCheckoutPage = false, 879 isLoggedIn = false, 880 config = {}, 881 updateOnlyTelephone = false, 882 infoMessage = '', 883 isTopup = false, 884 showAddressFormEmailForLoggedInUsers = false, 885) { 886 if (isCheckoutPage) { 887 window.dispatchEvent(new CustomEvent('react:addressFormLoading')); 888 } 889 const countryCode = await getConfigValue('country-code'); 890 const addressesContainer = document.querySelector('.addresses-container'); 891 892 /* 893 Filtering landmark and address_apartment_segment for EG for now from UI. 894 Once it's handled on backend, this code needs to be removed. 895 OLD CODE : const formStructure = await getDeliveryMatrixAddressStructure(countryCode); 896 */ 897 const formStructureAll = await getDeliveryMatrixAddressStructure(countryCode); 898 const excludedAttributes = ['landmark', 'address_apartment_segment']; 899 const filteredFormStructure = formStructureAll.filter( 900 (item) => !(item?.country_id === 'EG' && excludedAttributes.includes(item?.attribute?.attribute_code)), 901 ); 902 const formStructure = [...filteredFormStructure]; 903 904 const isUpdate = Object.keys(addressObj).length > 0; 905 const block = $parent.closest('.account-address-book'); 906 const maxLength = await getMaxLengthByCountryCode(countryCode); 907 const countryPrefix = `+${await getCountryIso(countryCode)}`; 908 const showArea = countryCode === 'KW'; 909 910 const form = document.createElement('form'); 911 let cityDefaultLabel = placeholders.selectCity || 'Select City'; 912 let areaDefaultLabel = placeholders.selectArea || 'Select Area'; 913 914 const firstnameField = formStructure?.find((d) => d?.attribute?.attribute_code === 'firstname'); 915 if (firstnameField) { 916 firstnameField.sort_order = '0'; 917 } 918 formStructure.sort((a, b) => { 919 if (Number(a.sort_order) < Number(b.sort_order)) return -1; 920 if (Number(a.sort_order) > Number(b.sort_order)) return 1; 921 return 0; 922 }); 923 924 if (isCheckoutPage) { 925 // eslint-disable-next-line max-len 926 handleCheckoutFormStructure(formStructure, isLoggedIn, countryCode, placeholders, showAddressFormEmailForLoggedInUsers); 927 } 928 929 const checkoutRequiredFields = ['address_building_segment', 'address_block_segment', 'address_district_segment', 'address_city_segment']; 930 const customerData = await getCustomer(); 931 const savedEmail = await getUserEmail('guest-checkout-email'); 932 formStructure 933 .filter((field) => field.visible === '1' && field.attribute.attribute_code !== 'lastname') 934 .forEach((field) => { 935 if (['firstname', 'lastname', 'telephone'].includes(field.attribute.attribute_code)) { 936 field.attribute.required = true; 937 } 938 const rules = countryRules[field.country_id]; 939 if (rules) { 940 if (rules.required.includes(field.attribute.attribute_code)) { 941 field.attribute.required = true; 942 } else if (rules.notRequired.includes(field.attribute.attribute_code)) { 943 field.attribute.required = false;
944 } 945 } 946 const fieldDiv = document.createElement('div'); 947 fieldDiv.classList.add('input-field-wrapper'); 948 let isDisabled = false; 949 if (isCheckoutPage && updateOnlyTelephone && field.attribute.attribute_code !== 'telephone') { 950 fieldDiv.classList.add('address-form-field-disabled'); 951 isDisabled = true; 952 } 953 const checkoutGuestEmail = !isTopup ? (savedEmail ?? '') : ''; 954 const guestUserEmail = addressObj?.[field.attribute.attribute_code] || checkoutGuestEmail; 955 if ((isLoggedIn || (guestUserEmail && !isTopup)) && field.attribute.attribute_code === 'email') isDisabled = true; 956 const validations = ['tabindex="0"']; 957 const isRequiedInCheckout = isCheckoutPage 958 ? checkoutRequiredFields.includes(field.attribute.attribute_code) : false; 959 if (field.attribute.required || isRequiedInCheckout) { 960 validations.push('required'); 961 } 962 963 field.attribute.validation_rules?.forEach((rule) => { 964 if (rule.name === 'min_text_length') { 965 validations.push(`minlength="${rule.value}"`); 966 } else if (rule.name === 'max_text_length') { 967 validations.push(`maxlength="${rule.value}"`); 968 } 969 }); 970 971 if (field.attribute.attribute_code === 'telephone') { 972 createTelephoneInput(countryCode, maxLength, countryPrefix, fieldDiv, field.attribute.attribute_code, field.attribute.store_label, field.attribute.required, addressObj?.[field.attribute.attribute_code] ? addressObj[field.attribute.attribute_code].replace(countryPrefix, '') : '', isCheckoutPage, updateOnlyTelephone, infoMessage); 973 } else if (field.attribute.attribute_code === 'short_code') { 974 // Show ONLY if config enabled AND market is SA 975 if (!isShortCodeEnabled || countryCode !== 'SA') { 976 return; 977 } 978 const value = addressObj?.custom_attributes?.find( 979 (a) => a.attribute_code === 'short_code', 980 )?.value || addressObj?.short_code || ''; 981 982 if (field.attribute.required) { 983 validations.push('required'); 984 } 985 986 createShortCodeField({ 987 fieldDiv, 988 storeLabel: placeholders.shortCodeLabel || 'Short Address (Eg. RRRD2929)', 989 value, 990 validations, 991 placeholders, 992 blockConfigs, 993 }); 994 995 form.appendChild(fieldDiv); 996 return; 997 } else if (field.attribute.attribute_code === 'address_building_segment' && isShortCodeEnabled && countryCode === 'SA') { 998 // Show ONLY if config enabled AND market is SA 999 const value = addressObj?.custom_attributes?.find( 1000 (a) => a.attribute_code === 'address_building_segment', 1001 )?.value || addressObj?.address_building_segment || ''; 1002 1003 if (field.attribute.required) { 1004 validations.push('required'); 1005 } 1006 createBuildingNumberField({ 1007 fieldDiv, 1008 storeLabel: field.attribute.store_label || 'Building Number (Eg. 1234)', 1009 value, 1010 validations, 1011 placeholders, 1012 blockConfigs, 1013 }); 1014 1015 form.appendChild(fieldDiv); 1016 return; 1017 } else if (field.attribute.frontend_input === 'text') { 1018 let textInputValue; 1019 if (field.attribute.attribute_code === 'email') { 1020 const email = customerData?.email || ''; 1021 textInputValue = isLoggedIn ? email : guestUserEmail; 1022 } else { 1023 textInputValue = addressObj?.[field.attribute.attribute_code]; 1024 } 1025 let label = field.attribute.store_label; 1026 let attributeCode = field.attribute.attribute_code; 1027 if (field.attribute.attribute_code === 'firstname') { 1028 textInputValue = textInputValue ? `${textInputValue} ${addressObj?.lastname ?? ''}` : ''; 1029 label = placeholders.fullname || 'Full Name'; 1030 attributeCode = 'fullName'; 1031 } 1032 fieldDiv.innerHTML = ` 1033 <div class="input-wrapper input-field"> 1034 <input type="text" id="${attributeCode}" name="${attributeCode}" aria-label="${label}" 1035 placeholder=" " 1036 ${isDisabled ? 'disabled' : ''} 1037 value="${sanitizeDOMSync(textInputValue || '')}" 1038 ${field.attribute.attribute_code === 'firstname' ? 'pattern="^\\p{L}+(?:\\s\\p{L}+)+\\s?$"' : ''} 1039 ${field.attribute.attribute_code === 'email' ? 'pattern="^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"' : ''} 1040 ${validations.join(' ')}> 1041 <label for="${attributeCode}">${label}</label> 1042 </div> 1043 `; 1044 } else if (field.attribute.frontend_input === 'select') { 1045 const defaultLabelContent = field.attribute?.attribute_code === 'country_id' 1046 ? getSelectedLabel(countryCode, field) 1047 : `${field.attribute.store_label}`; 1048 const defaultLabelContentValue = field.attribute?.attribute_code === 'country_id' 1049 ? getSelectedValue(countryCode, field) 1050 : `${field.attribute.store_label}`; 1051 if (field.attribute?.attribute_code === 'city') { 1052 cityDefaultLabel = defaultLabelContent; 1053 } else if (field.attribute?.attribute_code === 'area') { 1054 areaDefaultLabel = defaultLabelContent; 1055 } 1056 const classesToAdd = ['dropdown', 'notransistion', field.attribute.attribute_code]; 1057 1058 if (isCheckoutPage && field.attribute.attribute_code === 'country_id') { 1059 classesToAdd.push('hidden'); 1060 } 1061 fieldDiv.classList.add(...classesToAdd); 1062 fieldDiv.innerHTML = ` 1063 <div class="input-wrapper input-field"> 1064 <div class="custom-select" id="${field.attribute.attribute_code}" name="${field.attribute.attribute_code}" aria-label="${field.attribute.store_label}" ${validations.join(' ')}> 1065 <div class="select-selected ${field.attribute.attribute_code === 'country_id' ? ' disabled' : ''} ${isDisabled ? ' disable' : ''}" data-value="${field.attribute.attribute_code === 'country_id' ? defaultLabelContentValue : ''}">${defaultLabelContent}</div> 1066 <input type="text" class="select-search hide ${isCheckoutPage && 'hideSearch'}" placeholder="${isCheckoutPage && field.attribute.attribute_code !== 'country_id' ? defaultLabelContent : ''}"> 1067 <div class="select-items select-hide"> 1068 <div data-value="" class="no-results ${field.attribute.attribute_code === 'area' && !showArea ? '' : 'hide'}">${placeholders.dropdownNoResultsFound || 'No Results Found'}</div> 1069 ${field.attribute.attribute_code === 'area' && !showArea ? '' : field.attribute.options?.filter((option) => option.label !== 'Select an option') 1070 .map((option) => `<div data-value="${option.value}" ${addressObj?.[field.attribute.attribute_code] === option.value ? 'selected' : ''}>${option.label}</div>`) 1071 .join('')} 1072 </div> 1073 </div> 1074 </div> 1075 `; 1076 1077 field.attribute.options?.map((option) => `<div data-value="${option.value}">${option.label}</div>`); 1078 } else if (field.attribute.frontend_input === 'multiline') { 1079 const rows = field.attribute.multiline_count ? `rows="${field.attribute.multiline_count}"` : ''; 1080 fieldDiv.innerHTML = ` 1081 <div class="input-field-wrapper"> 1082 <div class="input-wrapper input-field"> 1083 <textarea id="${field.attribute.attribute_code}" name="${field.attribute.attribute_code}" aria-label="${field.attribute.store_label}" placeholder=" " ${validations.join(' ')} ${rows} ${isDisabled ? 'disabled' : ''}>${sanitizeDOMSync(addressObj?.[field.attribute.attribute_code] || '')}</textarea> 1084 <label for="${field.attribute.attribute_code}">${field.attribute.store_label}</label> 1085 </div> 1086 </div> 1087 `; 1088 } 1089
1090 const customAttributeFields = ['area', 'address_region_segment', 'address_building_segment', 1091 'address_apartment_segment', 'address_city_segment', 'address_block_segment', 1092 'address', 'address_floor_segment', 'address_landmark_segment', 'address_district_segment', 'address_road_segment', 'short_code', 'secondary_number', 'latitude', 'longitude', 1093 ]; 1094 if (customAttributeFields.indexOf(field.attribute.attribute_code) !== -1) { 1095 let value = addressObj.custom_attributes?.find((attr) => attr.attribute_code === field.attribute.attribute_code)?.value || ''; 1096 if (field?.attribute?.required === true && field?.attribute?.frontend_input === 'select') { 1097 value = (field?.attribute?.options.some((option) => String(option.value) === value) === true) ? value : ''; 1098 } 1099 1100 const inputField = fieldDiv.querySelector('input, .select-selected, textarea'); 1101 inputField.value = value; 1102 inputField.setAttribute('data-initial-value', value); 1103 } 1104 form.appendChild(fieldDiv); 1105 }); 1106 let submitButtonLabel = placeholders.addAddress || 'Add Address'; 1107 if (isUpdate || isCheckoutPage) { 1108 submitButtonLabel = placeholders.save || 'Save'; 1109 } 1110 1111 const addressFormButtons = isCheckoutPage 1112 ? `<div class="address-form-button-wrapper"> 1113 <button type="submit" class="address-submit-button address-form-button" aria-label="${submitButtonLabel}"><span>${submitButtonLabel}</span></button> 1114 </div>` 1115 : `<div class="address-form-button-wrapper"> 1116 <button type="submit" class="address-submit-button address-form-button" aria-label="${submitButtonLabel}"><span>${submitButtonLabel}</span></button> 1117 <button type="button" class="address-cancel-button secondary address-form-button" aria-label="${placeholders.cancel || 'Cancel'}">${placeholders.cancel || 'Cancel'}</button> 1118 </div>`; 1119 1120 form.insertAdjacentHTML('beforeend', addressFormButtons); 1121 1122 form.noValidate = true; 1123 setCountryCode(form, countryCode); 1124 1125 if (countriesWithRegions.includes(countryCode)) { 1126 const regionSegments = await getAddressRegionSegments(countryCode); 1127 await populateRegions(form, regionSegments, countryCode, placeholders); 1128 } else { 1129 const citySegments = await getAddressCitySegments(countryCode); 1130 await populateCity(form, citySegments, countryCode, placeholders); 1131 } 1132 1133 if (showArea) { 1134 await updateArea(countryCode, '', form, placeholders); 1135 } 1136 1137 // TODO - Use Block-Config(Developer authorable) instead of config(Author authorable). 1138 if (disableInlineValidation === 'false') { 1139 form.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 1140 setErrorMessageForField(input, placeholders); 1141 input.addEventListener('input', () => { 1142 validateInput(input); 1143 }); 1144 }); 1145 } else { 1146 form.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 1147 setErrorMessageForField(input, placeholders); 1148 input.addEventListener('input', () => { 1149 debounce(() => { 1150 resetMessage(input); 1151 const isFormValid = validateForm(form, placeholders, isCheckoutPage); 1152 form.querySelector('button').disabled = !isFormValid; 1153 }, 300)(); 1154 }); 1155 1156 input.addEventListener('blur', async () => { 1157 if (input.type === 'tel') { 1158 const phoneValue = input.value; 1159 const isValidPhoneNumber = await validatePhone(phoneValue, countryCode); 1160 if (!isValidPhoneNumber) { 1161 let phoneNumberErrorMessage = placeholders.invalidMobileNumber || `The number provided ${input.value} is not a valid mobile number.`; 1162 phoneNumberErrorMessage = phoneNumberErrorMessage.replace('{{mobile}}', input.value); 1163 showErrorMessage(input, phoneNumberErrorMessage); 1164 form.querySelector('button').disabled = true; 1165 return; 1166 } 1167 } 1168 if (!validateInput(input)) { 1169 form.querySelector('button').disabled = true; 1170 } 1171 }); 1172 }); 1173 } 1174 1175 form.querySelectorAll('.custom-select').forEach((dropdown) => { 1176 setErrorMessageForField(dropdown, placeholders); 1177 dropdown.addEventListener('keydown', (event) => { 1178 if (event.key === 'Enter') { 1179 const selectBox = dropdown.querySelector('.select-selected'); 1180 if (selectBox) { 1181 selectBox.click(); 1182 } 1183 } 1184 }); 1185 dropdown.addEventListener('blur', (event) => { 1186 event.stopPropagation(); 1187 if (dropdown.contains(event.relatedTarget)) { 1188 return; 1189 } 1190 const mainBox = event.target.closest('.custom-select'); 1191 const selectionBox = mainBox.querySelector('.select-selected'); 1192 if (!selectionBox.getAttribute('data-initial-value') && !selectionBox.getAttribute('data-value')) { 1193 const dropdownLabel = dropdown.getAttribute('aria-label'); 1194 let errorMessage = placeholders.addAddressSelectError || `Please select ${dropdownLabel}`; 1195 errorMessage = errorMessage.replace('{{label}}', dropdownLabel); 1196 showErrorMessage(mainBox, errorMessage); 1197 } else { 1198 resetMessage(mainBox); 1199 } 1200 }); 1201 const dropDownOptions = dropdown.querySelectorAll('.select-items'); 1202 dropDownOptions.forEach((option) => { 1203 option.addEventListener('click', () => { 1204 option.closest('.input-field-wrapper')?.classList.remove('invalid'); 1205 const isFormValid = validateForm(form, placeholders, isCheckoutPage); 1206 form.querySelector('button').disabled = !isFormValid; 1207 }); 1208 }); 1209 }); 1210 1211 const phoneInput = form.querySelector('#telephone'); 1212 phoneInput.addEventListener('input', async (event) => { 1213 event.target.value = event.target.value.replace(/[^0-9]/g, ''); 1214 if (event.target.value.length > phoneInput.maxLength) { 1215 const isValidPhoneNumber = await validatePhone( 1216 phoneInput.value, 1217 countryCode, 1218 ); 1219 let phoneErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 1220 phoneErrorMessage = phoneErrorMessage.replace('{{mobile}}', phoneInput.value); 1221 1222 if (!isValidPhoneNumber) { 1223 showErrorMessage(phoneInput, phoneErrorMessage); 1224 } 1225 } 1226 }); 1227 1228 const addressCancelButton = form.querySelector('.address-cancel-button'); 1229 addressCancelButton?.addEventListener('click', () => { 1230 if (newCustomer) { 1231 $parent?.firstChild?.querySelectorAll('input, textarea').forEach((input) => { 1232 input.value = ''; 1233 }); 1234 document.querySelector('.dropdown.address_city_segment .select-selected').textContent = cityDefaultLabel; 1235 document.querySelector('.dropdown.area .select-selected').textContent = areaDefaultLabel; 1236 } else { 1237 $parent?.classList.add('hide'); 1238 } 1239 window.scrollTo(0, 0); 1240 }); 1241 1242 const addressSubmitButton = form.querySelector('.address-submit-button'); 1243 form.addEventListener('submit', async (event) => { 1244 event.preventDefault(); 1245 const phoneValue = phoneInput.value; 1246 let isValidPhoneNumber = false;
1247 let phoneNumLengthError = false; 1248 const phoneValidationPromise = validatePhone(phoneValue, countryCode) 1249 .then((result) => { 1250 isValidPhoneNumber = result; 1251 }) 1252 .catch(() => { 1253 phoneNumLengthError = true; 1254 }); 1255 1256 const isFormValid = validateForm(form, placeholders, isCheckoutPage); 1257 form.querySelector('button').disabled = !isFormValid; 1258 await phoneValidationPromise; 1259 if (!isValidPhoneNumber || phoneNumLengthError) { 1260 let phoneNumberErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 1261 phoneNumberErrorMessage = phoneNumberErrorMessage.replace('{{mobile}}', phoneInput.value); 1262 showErrorMessage(phoneInput, phoneNumberErrorMessage); 1263 } 1264 if (!isValidPhoneNumber || phoneNumLengthError || !isFormValid) { 1265 return; 1266 } 1267 1268 addressSubmitButton.classList.add('loader'); 1269 1270 if (isShortCodeMandatory) { 1271 const isShortcodeValid = await validateAndStoreShortcode(form, placeholders); 1272 if (!isShortcodeValid) { 1273 addressSubmitButton.classList.remove('loader'); 1274 return; 1275 } 1276 } 1277 1278 if (isCheckoutPage && !isLoggedIn) { 1279 const emailInput = form.querySelector('#email'); 1280 const responseData = await validateCustomerEmail(emailInput.value); 1281 if (responseData?.customerExists) { 1282 const emailErrorMessage = placeholders.emailExist || 'You already have an account. Please log in.'; 1283 showErrorMessage(emailInput, emailErrorMessage, true); 1284 dataLayerCustomerExistCheckoutErrors(); 1285 addressSubmitButton.classList.remove('loader'); 1286 return; 1287 } 1288 emailInput.classList.remove('error'); 1289 } 1290 1291 const address = {}; 1292 const baseCustomAttributeNames = [ 1293 'address_city_segment', 1294 ...(countryCode === 'EG' ? ['address_region_segment'] : ['area']), 1295 'address_building_segment', 1296 'address_apartment_segment', 1297 ...(countryCode === 'EG' || countryCode === 'JO' ? ['address_floor_segment'] : []), 1298 ...(countryCode === 'EG' || countryCode === 'JO' ? ['address_landmark_segment'] : []), 1299 ...(countryCode === 'JO' || countryCode === 'BH' ? ['address_district_segment'] : []), 1300 ...(countryCode === 'BH' ? ['address_road_segment', 'address_block_segment'] : []), 1301 'address', 1302 'short_code', 1303 'longitude', 1304 'latitude', 1305 'secondary_number', 1306 ]; 1307 1308 if (showArea) { 1309 baseCustomAttributeNames.push('address_block_segment'); 1310 } 1311 1312 // ---- ADD LAT/LNG ONLY FOR SA + SHORTCODE ENABLED ---- 1313 if (countryCode === 'SA' && isShortCodeEnabled === true) { 1314 const scInput = form.querySelector('#short_code'); 1315 const hasValidShortcodeData = scInput && scInput.dataset?.latitude 1316 && scInput.dataset?.longitude; 1317 1318 if (hasValidShortcodeData) { 1319 address.custom_attributes = address.custom_attributes || []; 1320 address.custom_attributes.push( 1321 { 1322 attribute_code: 'latitude', 1323 value: scInput.dataset.latitude, 1324 }, 1325 { 1326 attribute_code: 'longitude', 1327 value: scInput.dataset.longitude, 1328 }, 1329 ); 1330 if (scInput.dataset?.additionalNumber) { 1331 address.custom_attributes.push({ 1332 attribute_code: 'secondary_number', 1333 value: scInput.dataset.additionalNumber, 1334 }); 1335 } 1336 // SA-only postcode override 1337 if (scInput.dataset?.postalCode) { 1338 address.postcode = scInput.dataset.postalCode; 1339 } 1340 } 1341 } 1342 1343 const customAttributeNames = new Set(baseCustomAttributeNames); 1344 const inputs = form.querySelectorAll('input, .custom-select, textarea'); 1345 1346 inputs.forEach((input) => { 1347 const name = input?.getAttribute('name'); 1348 if (!name) return; 1349 1350 let key = name; 1351 let value = sanitizeDOMSync(input.value?.trim() || ''); 1352 1353 switch (name) { 1354 case 'country_id': 1355 key = 'country_code'; 1356 break; 1357 1358 case 'address_city_segment': 1359 case 'address_district_segment':
1360 case 'address_region_segment': 1361 case 'area': { 1362 const selected = input.querySelector('.select-selected'); 1363 const dataValue = selected?.getAttribute('data-value') || ''; 1364 const dataInitialValue = selected?.getAttribute('data-initial-value') || ''; 1365 value = input.classList.contains('custom-select') 1366 ? dataValue || dataInitialValue 1367 : value; 1368 1369 if (name === 'address_city_segment') { 1370 address.city = isCheckoutPage ? (dataValue || dataInitialValue || '-') : (dataValue || '-'); 1371 } else if (name === 'address_region_segment') { 1372 address.region = isCheckoutPage ? (dataValue || dataInitialValue || '-') : (dataValue || '-'); 1373 } 1374 break; 1375 } 1376 1377 case 'fullName': { 1378 const [firstName = '', ...rest] = value.split(/\s+/); 1379 address.firstname = firstName; 1380 address.lastname = rest.join(' '); 1381 return; 1382 } 1383 1384 case 'telephone': { 1385 // Validate and clean telephone: strip prefix, enforce maxlength 1386 // This handles cases where user bypassed maxlength via dev tools or browser issues 1387 const cleanedTelephone = validateAndCleanTelephone(value, countryPrefix, maxLength); 1388 // Add prefix back for API (API expects full number with prefix) 1389 address.telephone = cleanedTelephone ? `${countryPrefix}${cleanedTelephone}` : ''; 1390 return; 1391 } 1392 1393 default: 1394 break; 1395 } 1396 1397 if (customAttributeNames.has(name)) { 1398 address.custom_attributes = address.custom_attributes || []; 1399 address.custom_attributes.push({ 1400 attribute_code: key, 1401 value, 1402 }); 1403 } else if (!(isCheckoutPage && isLoggedIn && key === 'email')) { 1404 address[key] = value; 1405 } 1406 }); 1407 1408 // Due to out of the box validations for commerce we have to 1409 // send City attribute explicitly, It is not required at alshaya 1410 if (countryCode === 'KW') address.city = 'city'; 1411 1412 // Sending null at region due to magento mapping 1413 if (countryCode === 'EG') address.region = null; 1414 1415 if (countryCode === 'BH') address.street = [null]; 1416 1417 let res = null; 1418 let message; 1419 1420 if (isUpdate && !isCheckoutPage) { 1421 res = await updateAddress(addressObj.id, address); 1422 if (res?.success) { 1423 message = placeholders.addressUpdated || 'Address is updated successfully.'; 1424 } 1425 } else if (isCheckoutPage) { 1426 if (isLoggedIn && !isTopup) { 1427 delete address.address; 1428 if (isUpdate) { 1429 const response = await updateAddress(addressObj.id 1430 || addressObj?.customer_address_id, address, config); 1431 address.id = response?.data?.commerce_updateCustomerAddress?.id; 1432 address.street = response?.data?.commerce_updateCustomerAddress?.street 1433 ?? address?.street; 1434 } else { 1435 const response = await createAddress(address, config); 1436 address.id = response?.data?.commerce_createCustomerAddress?.id; 1437 address.street = response?.data?.commerce_createCustomerAddress?.street 1438 ?? address?.street; 1439 } 1440 } 1441 window.dispatchEvent(new CustomEvent('react:addressFormSubmitted', { detail: { address, isUpdate } })); 1442 } else { 1443 res = await createAddress(address); 1444 if (res?.success) { 1445 message = placeholders.addressAdded || 'Address is added successfully.'; 1446 } 1447 } 1448 if (res?.success) { 1449 block.dispatchEvent(new CustomEvent('address-updated')); 1450 addressesContainer.classList.remove('hide'); 1451 $parent.classList.add('hide'); 1452 document.querySelector('.new-address-container').classList.remove('hide'); 1453 addStatusMessage(message); 1454 } 1455 }); 1456 1457 decorateIcons(form); 1458 if (!isCheckoutPage) { 1459 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.country_id')); 1460 } 1461 if (countryCode !== 'EG') customSelectbox(form.querySelector('.input-field-wrapper.dropdown.address_city_segment')); 1462 $parent.innerHTML = ''; 1463 if (isCheckoutPage && !config?.checkoutV1) { 1464 const formDiv = document.createElement('div'); 1465 formDiv.append(form); 1466 formDiv.classList.add('address-form', 'checkout-form'); 1467 $parent.appendChild(formDiv); 1468 } else { 1469 $parent.appendChild(form); 1470 } 1471 if (isCheckoutPage) { 1472 window.dispatchEvent(new CustomEvent('react:addressFormLoaded')); 1473 if (isUpdate) validateForm(form, placeholders, isCheckoutPage); 1474 } 1475 $parent.classList.remove('hide'); 1476 1477 const showAreaModal = await getConfigValue('addressbook-show-area-modal') === 'true'; 1478 if (isCheckoutPage && showAreaModal) { 1479 const formElement = document.querySelector('.dialog__overlay form'); 1480 if (formElement) { 1481 blockConfigs['checkout-area-dropdown-placeholder'] = areaDefaultLabel; 1482 blockConfigs['checkout-city-dropdown-placeholder'] = cityDefaultLabel; 1483 customSelectboxModal(blockConfigs, placeholders, formElement); 1484 } 1485 } 1486 1487 handleInitialCheck(); 1488 1489 if (isCheckoutPage) { 1490 restructureForm(form, config); 1491 } 1492 // Load Short Code Map modal if enabled 1493 if (blockConfigs?.flags?.enableShortCodeMap === true) {
1494 loadShortCodeMapModal(placeholders, 'checkout-home-delivery'); 1495 } 1496} 1497 1498// to shoe address form for new customer 1499function handleNewCustomer(blockConfigs, placeholders, addressesContainer) { 1500 const addressForm = document.querySelector('.address-form'); 1501 showAddressForm(blockConfigs, addressForm, placeholders, {}, true); 1502 addressesContainer.classList.add('hide'); 1503 hideNewAddressCta(); 1504} 1505 1506async function showDeleteAddressModal(addressId, placeholders, addressesContainer) { 1507 const deleteTitle = placeholders.deleteAddressModalTitle || 'Delete Address'; 1508 const deleteContent = document.createElement('div'); 1509 deleteContent.classList.add('delete-content'); 1510 1511 deleteContent.innerHTML = `<span class="delete-title">${placeholders.deleteAddressModalTitle || 'Delete Address'}</span> 1512 <span class="delete-message">${placeholders.deleteAddressModalMessage || 'You have selected to delete this address, are you sure?'}</span> 1513 <div class="delete-buttons"> 1514 <button class="delete-cancel secondary" aria-label="${placeholders.deleteAddressCancel || 'No'}">${placeholders.deleteAddressCancel || 'No'}</button> 1515 <button class="delete-confirm" aria-label="${placeholders.deleteAddressConfirm || 'Yes'}">${placeholders.deleteAddressConfirm || 'Yes'}</button> 1516 </div>`; 1517 await createModalFromContent('delete-address-modal', deleteTitle, deleteContent.outerHTML, ['delete-address-modal'], 'trash', false, 'icon-close-black'); 1518 1519 document.querySelector('.delete-address-modal .delete-cancel').addEventListener('click', () => { 1520 closeModal('delete-address-modal'); 1521 }); 1522 1523 document.querySelector('.delete-address-modal .delete-confirm').addEventListener('click', async () => { 1524 closeModal('delete-address-modal'); 1525 const res = await deleteAddress(addressId); 1526 if (res?.success) { 1527 const message = placeholders.addressDeleted || 'Address is deleted successfully.'; 1528 addStatusMessage(message); 1529 const block = addressesContainer.closest('.account-address-book'); 1530 block?.dispatchEvent(new CustomEvent('address-updated')); 1531 } 1532 }); 1533 openModal('delete-address-modal'); 1534} 1535 1536export const displayAddressList = async function ( 1537 blockConfigs, 1538 addressForm, 1539 addressesContainer, 1540 placeholders, 1541) { 1542 addressesContainer.innerHTML = ''; 1543 const countryCode = await getConfigValue('country-code'); 1544 const customerPromise = getCustomer(false); 1545 const citySegmentsPromise = getAddressCitySegments(countryCode); 1546 const regionSegmentsPromise = getAddressRegionSegments(countryCode); 1547 const areasPromise = getAddressAreas(countryCode); 1548 const formStructurePromise = getDeliveryMatrixAddressStructure(countryCode); 1549 1550 const [customer, citySegments, regionSegments, areas, 1551 formStructure] = await Promise.all([customerPromise, 1552 citySegmentsPromise, 1553 regionSegmentsPromise, 1554 areasPromise, 1555 formStructurePromise, 1556 ]); 1557 1558 const country = formStructure 1559 .find((field) => field.visible === '1' && field?.attribute?.attribute_code === 'country_id') 1560 ?.attribute?.options?.find((option) => option.value === countryCode)?.label; 1561 if (customer?.addresses?.length < 1) { 1562 handleNewCustomer(blockConfigs, placeholders, addressesContainer); 1563 return; 1564 } 1565 customer?.addresses.sort((a, b) => (b.default_billing === true) - (a.default_billing === true)); 1566 customer?.addresses?.forEach((address) => { 1567 const addressWrapper = document.createElement('div'); 1568 addressWrapper.classList.add('address-wrapper'); 1569 1570 const addressDiv = document.createElement('div'); 1571 addressDiv.classList.add('address'); 1572 1573 const editAddressHtml = `<div class="edit-address-cta-wrapper"> 1574 <a href="#" class="edit-address-cta" aria-label="${placeholders.edit || 'Edit'}"><span>${placeholders.edit || 'Edit'}</span></a> 1575 </div>`; 1576 1577 const city = citySegments.items?.find( 1578 (item) => item.location_id 1579 === Number( 1580 address.custom_attributes?.find( 1581 (attr) => attr.attribute_code === 'address_city_segment', 1582 )?.value, 1583 ), 1584 )?.label; 1585 1586 const area = areas.items?.find( 1587 (item) => item.location_id 1588 === Number( 1589 address.custom_attributes?.find( 1590 (attr) => attr.attribute_code === 'area', 1591 )?.value, 1592 ), 1593 )?.label; 1594 1595 const region = regionSegments.items?.find( 1596 (item) => item.location_id 1597 === Number( 1598 address.custom_attributes?.find( 1599 (attr) =>
1599 attr.attribute_code === 'address_region_segment', 1600 )?.value, 1601 ), 1602 )?.label; 1603 1604 const buildingName = sanitizeDOMSync( 1605 address.custom_attributes?.find( 1606 (attr) => attr.attribute_code === 'address_building_segment', 1607 )?.value, 1608 ); 1609 const apartment = sanitizeDOMSync( 1610 address.custom_attributes?.find( 1611 (attr) => attr.attribute_code === 'address_apartment_segment', 1612 )?.value, 1613 ); 1614 1615 const shortcode = sanitizeDOMSync( 1616 address.custom_attributes?.find( 1617 (attr) => attr.attribute_code === 'short_code', 1618 )?.value, 1619 ); 1620 1621 const shortcodeSpan = country ? `<span>${shortcode}</span>` : ''; 1622 const countrySpan = country ? `<span>${country}</span>` : ''; 1623 const citySpan = city ? `<span>${city}</span>` : ''; 1624 const areaSpan = area ? `<span>${area}</span>` : ''; 1625 const regionSpan = region ? `<span>${region}</span>` : ''; 1626 const buildingSpan = buildingName ? `<span>${buildingName}</span>` : ''; 1627 const apartmentSpan = apartment ? `<span>${apartment}</span>` : ''; 1628 1629 const addressContent = ` 1630 <div class="address-data ${address.default_billing || customer.addresses.length === 1 ? 'primary-address-data' : ''}"> 1631 <div class="address-userinfo"> 1632 <div class="address-section col-1 deliveryTo"> 1633 <p class="label"><span>${placeholders.deliveryTo || 'Delivery To'}</span></p> 1634 <p class="address-value"><span>${address.firstname} ${address.lastname}</span></p> 1635 </div> 1636 <div class="address-section col-1 contact-number"> 1637 <p class="label"><span>${placeholders.contactNumber || 'Contact Number'}</span></p> 1638 <p class="address-value"><span>${address.telephone}</span></p> 1639 </div> 1640 </div> 1641 <div class="address-section address-line"> 1642 <p class="label"><span>${placeholders.deliveryAddress || 'Delivery Address'}</span></p> 1643 <p class="address-value"> 1644 ${shortcodeSpan} 1645 ${countrySpan} 1646 ${citySpan}${areaSpan}${regionSpan} 1647 <span>${sanitizeDOMSync(address.street)}</span>${sanitizeDOMSync(buildingSpan)}${sanitizeDOMSync(apartmentSpan)} 1648 </p> 1649 </div> 1650 </div> 1651 ${ 1652 address.default_billing 1653 ? ` 1654 <div class="address-section cta-container primary-address-cta-container"> 1655 <div class="primary-address-cta-wrapper"> 1656 <span class="icon icon-radio-active"></span> 1657 <span>${placeholders.primaryAddress || 'Primary Address'}</span> 1658 </div> 1659 ${editAddressHtml} 1660 </div>` 1661 : ` 1662 <div class="address-section cta-container address-cta-container"> 1663 <div class="make-primary-cta-wrapper"> 1664 <span class="icon icon-radio-active hovericon"></span> 1665 <span class="icon icon-radio-icon"></span> 1666 <a class="make-primary-cta" aria-label="${placeholders.makePrimary || 'Make Primary'}"><span>${placeholders.makePrimary || 'Make Primary'}</span></a> 1667 </div> 1668 ${editAddressHtml} 1669 <div class="delete-address-cta-wrapper"> 1670 <a href="#" class="delete-address-cta" aria-label="${placeholders.delete || 'Delete'}"><span>${placeholders.delete || 'Delete'}</span></a> 1671 </div>` 1672}`; 1673 1674 addressDiv.innerHTML = addressContent; 1675 addressWrapper.appendChild(addressDiv); 1676 decorateIcons(addressWrapper); 1677 1678 const editButton = addressWrapper.querySelector('.edit-address-cta'); 1679 if (editButton) { 1680 editButton.addEventListener('click', async () => { 1681 editButton.classList.add('loader'); 1682 await showAddressForm(blockConfigs, addressForm, placeholders, address); 1683 editButton.classList.remove('loader'); 1684 }); 1685 } 1686 1687 const deleteButton = addressWrapper.querySelector('.delete-address-cta'); 1688 deleteButton?.addEventListener('click', async () => { 1689 await showDeleteAddressModal(address.id, placeholders, addressesContainer); 1690 }); 1691 1692 const makePrimaryButton = addressWrapper.querySelector( 1693 '.make-primary-cta-wrapper', 1694 ); 1695 makePrimaryButton?.addEventListener('click', async () => { 1696 const res = await makePrimaryAddress(address.id); 1697 if (res?.success) { 1698 const message = placeholders.primaryAddressUpdated 1699 || 'Primary address is updated successfully.'; 1700 addStatusMessage(message, placeholders); 1701 const block = addressWrapper.closest('.account-address-book'); 1702 block?.dispatchEvent(new CustomEvent('address-updated')); 1703 } 1704 }); 1705 handleAddressHover(addressWrapper); 1706 addressesContainer.appendChild(addressWrapper); 1707 const addressSubmitButton = document.querySelector( 1708 '.address-submit-button', 1709 ); 1710 addressSubmitButton?.classList.remove('hide'); 1711 }); 1712}; 1713 1714function phoneValidation(phoneInput, countryCode, formElement, placeholders) { 1715 phoneInput.addEventListener('blur', async (event) => { 1716 event.target.value = event.target.value.replace(/[^0-9]/g, ''); 1717 const isValidPhoneNumber = await validatePhone( 1718 phoneInput.value, 1719 countryCode, 1720 ); 1721 let phoneErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 1722 phoneErrorMessage = phoneErrorMessage.replace('{{mobile}}', phoneInput.value); 1723 if (!isValidPhoneNumber) { 1724 showErrorMessage(phoneInput, phoneErrorMessage); 1725 formElement.querySelector('button').disabled = true; 1726 } 1727 }); 1728 1729 phoneInput.addEventListener('input', debounce(() => { 1730 resetMessage(phoneInput); 1731 formElement.querySelector('button').disabled = false;
1732 }), 300); 1733 1734 phoneInput.addEventListener('input', (event) => { 1735 event.target.value = event.target.value.replace(/[^0-9]/g, ''); 1736 }); 1737} 1738 1739// to show address form 1740export async function showAddressFormModal( 1741 blockConfigs, 1742 formId, 1743 $parent, 1744 placeholders, 1745 addressObj, 1746 newCustomer = false, 1747 isCheckoutPage = false, 1748 isLoggedIn = false, 1749 config = {}, 1750 updateOnlyTelephone = false, 1751 infoMessage = '', 1752 isTopup = false, 1753 showAddressFormEmailForLoggedInUsers = false, 1754) { 1755 if (isCheckoutPage) { 1756 window.dispatchEvent(new CustomEvent('react:addressFormLoading')); 1757 } 1758 const countryCode = await getConfigValue('country-code'); 1759 const addressesContainer = document.querySelector('.addresses-container'); 1760 1761 // Update form structure for EG market. 1762 const disableLandmark = blockConfigs?.flags?.disableLandmark; 1763 let formStructure = await getDeliveryMatrixAddressStructure(countryCode); 1764 if (disableLandmark === true) { 1765 const excludedAttributes = ['landmark', 'address_apartment_segment']; 1766 const filteredFormStructure = formStructure.filter( 1767 (item) => !(item?.country_id === 'EG' && excludedAttributes.includes(item?.attribute?.attribute_code)), 1768 ); 1769 formStructure = [...filteredFormStructure]; 1770 } 1771 1772 const citySegments = await getAddressCitySegments(countryCode); 1773 const isUpdate = Object.keys(addressObj).length > 0; 1774 const block = $parent.closest('.account-address-book'); 1775 const maxLength = await getMaxLengthByCountryCode(countryCode); 1776 const countryPrefix = `+${await getCountryIso(countryCode)}`; 1777 const showArea = countryCode === 'KW'; 1778 const form = document.createElement('form'); 1779 let cityDefaultLabel = placeholders.selectCity || 'Select City'; 1780 let areaDefaultLabel = placeholders.selectArea || 'Select Area'; 1781 // Retrieve the email from session storage 1782 const customerData = await getCustomer(); 1783 const email = customerData?.email || ''; 1784 // Variable to track if the email field has been added 1785 let emailFieldAdded = false; 1786 1787 if (isCheckoutPage) { 1788 // eslint-disable-next-line max-len 1789 handleCheckoutFormStructure(formStructure, isLoggedIn, countryCode, placeholders, showAddressFormEmailForLoggedInUsers); 1790 } 1791 1792 const checkoutRequiredFields = ['address', 'address_building_segment']; 1793 const firstnameField = formStructure?.find((d) => d?.attribute?.attribute_code === 'firstname'); 1794 if (firstnameField) { 1795 firstnameField.sort_order = '0'; 1796 } 1797 formStructure.sort((a, b) => { 1798 if (Number(a.sort_order) < Number(b.sort_order)) return -1; 1799 if (Number(a.sort_order) > Number(b.sort_order)) return 1; 1800 return 0; 1801 }); 1802 1803 formStructure 1804 .filter((field) => field.visible === '1' && field.attribute.attribute_code !== 'lastname') 1805 .forEach(async (field) => { 1806 if (['firstname', 'lastname', 'telephone'].includes(field.attribute.attribute_code)) { 1807 field.attribute.required = true; 1808 } 1809 const rules = countryRules[field.country_id]; 1810 if (rules) { 1811 if (rules.required.includes(field.attribute.attribute_code)) { 1812 field.attribute.required = true; 1813 } else if (rules.notRequired.includes(field.attribute.attribute_code)) { 1814 field.attribute.required = false; 1815 } 1816 } 1817 const fieldDiv = document.createElement('div'); 1818 fieldDiv.classList.add('input-field-wrapper'); 1819 let isDisabled = false; 1820 if (isCheckoutPage && updateOnlyTelephone && field.attribute.attribute_code !== 'telephone') { 1821 fieldDiv.classList.add('address-form-field-disabled'); 1822 isDisabled = true; 1823 } 1824 const validations = ['tabindex="0"']; 1825 1826 const isRequiedInCheckout = isCheckoutPage 1827 ? checkoutRequiredFields.includes(field.attribute.attribute_code) : false; 1828 if (field.attribute.required || isRequiedInCheckout) { 1829 validations.push('required'); 1830 } 1831 1832 field.attribute.validation_rules?.forEach((rule) => { 1833 if (rule.name === 'min_text_length') { 1834 validations.push(`minlength="${rule.value}"`); 1835 } else if (rule.name === 'max_text_length') { 1836 validations.push(`maxlength="${rule.value}"`); 1837 } 1838 }); 1839 1840 if (field.attribute.attribute_code === 'telephone') { 1841 createTelephoneInput(countryCode, maxLength, countryPrefix, fieldDiv, field.attribute.attribute_code, field.attribute.store_label, field.attribute.required, addressObj?.[field.attribute.attribute_code] ? addressObj[field.attribute.attribute_code].replace(countryPrefix, '') : '', isCheckoutPage, updateOnlyTelep
1841hone, infoMessage); 1842 } else if (field.attribute.attribute_code === 'short_code') { 1843 // Show ONLY if config enabled AND market is SA 1844 if (!isShortCodeEnabled || countryCode !== 'SA') { 1845 return; 1846 } 1847 1848 const value = addressObj?.custom_attributes?.find( 1849 (a) => a.attribute_code === 'short_code', 1850 )?.value || addressObj?.short_code || ''; 1851 1852 if (field.attribute.required) { 1853 validations.push('required'); 1854 } 1855 1856 createShortCodeField({ 1857 fieldDiv, 1858 storeLabel: placeholders.shortCodeLabel || 'Short Address (Eg. RRRD2929)', 1859 value, 1860 validations, 1861 placeholders, 1862 blockConfigs, 1863 }); 1864 1865 form.appendChild(fieldDiv); 1866 return; 1867 } else if (field.attribute.attribute_code === 'address_building_segment' && isShortCodeEnabled && countryCode === 'SA') { 1868 // Show ONLY if config enabled AND market is SA 1869 const value = addressObj?.custom_attributes?.find( 1870 (a) => a.attribute_code === 'address_building_segment', 1871 )?.value || addressObj?.address_building_segment || ''; 1872 1873 if (field.attribute.required) { 1874 validations.push('required'); 1875 } 1876 createBuildingNumberField({ 1877 fieldDiv, 1878 storeLabel: field.attribute.store_label || 'Building Number (Eg. 1234)', 1879 value, 1880 validations, 1881 placeholders, 1882 blockConfigs, 1883 }); 1884 1885 form.appendChild(fieldDiv); 1886 return; 1887 } else if (field.attribute.frontend_input === 'text') { 1888 let textInputValue = addressObj?.[field.attribute.attribute_code]; 1889 let label = field.attribute.store_label; 1890 let attributeCode = field.attribute.attribute_code; 1891 if (field.attribute.attribute_code === 'firstname') { 1892 textInputValue = textInputValue ? `${textInputValue} ${addressObj?.lastname ?? ''}` : ''; 1893 label = placeholders.fullname || 'Full Name'; 1894 attributeCode = 'fullName'; 1895 } 1896 fieldDiv.innerHTML = ` 1897 <div class="input-field-wrapper"> 1898 <div class="input-wrapper input-field"> 1899 <input type="text" id="${attributeCode}" name="${attributeCode}" aria-label="${label}" 1900 placeholder=" " 1901 ${isDisabled ? 'disabled' : ''} 1902 value="${textInputValue || ''}" 1903 ${field.attribute.attribute_code === 'firstname' ? 'pattern="^\\p{L}+(?:\\s\\p{L}+)+\\s?$"' : ''} 1904 ${field.attribute.attribute_code === 'email' ? 'pattern="^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"' : ''} 1905 ${validations.join(' ')}> 1906 <label for="${attributeCode}">${label}</label> 1907 </div> 1908 </div> 1909 `; 1910 } else if (field.attribute.frontend_input === 'select') { 1911 const defaultLabelContent = field.attribute?.attribute_code === 'country_id' 1912 ? getSelectedLabel(countryCode, field) 1913 : `${field.attribute.store_label}`; 1914 const defaultLabelContentValue = field.attribute?.attribute_code === 'country_id' 1915 ? getSelectedValue(countryCode, field) 1916 : `${field.attribute.store_label}`; 1917 1918 if (field.attribute?.attribute_code === 'city') { 1919 cityDefaultLabel = defaultLabelContent; 1920 } else if (field.attribute?.attribute_code === 'area') { 1921 areaDefaultLabel = defaultLabelContent; 1922 } 1923 const classesToAdd = ['dropdown', 'notransistion', field.attribute.attribute_code]; 1924 1925 if (isCheckoutPage && field.attribute.attribute_code === 'country_id') { 1926 classesToAdd.push('hidden'); 1927 } 1928 fieldDiv.classList.add(...classesToAdd); 1929 fieldDiv.innerHTML = ` 1930 <div class="input-wrapper input-field"> 1931 <div class="custom-select" id="${field.attribute.attribute_code}" name="${field.attribute.attribute_code}" aria-label="${field.attribute.store_label}" ${validations.join(' ')} required> 1932 <div class="select-selected${field.attribute.attribute_code === 'country_id' || isDisabled ? ' disabled' : ''}" data-value="${field.attribute.attribute_code === 'country_id' ? defaultLabelContentValue : ''}"> 1933 <label for="${field.attribute.store_label}">${field.attribute.store_label}</label>
1934 <span for="${field.attribute.store_label}">${placeholders.select || 'Select'} ${field.attribute.store_label}</span> 1935 </div> 1936 <input type="text" class="select-search hide" placeholder="${isCheckoutPage && field.attribute.attribute_code !== 'country_id' ? defaultLabelContent : ''}" /> 1937 <div class="select-items select-hide"> 1938 <div data-value="" class="no-results ${field.attribute.attribute_code === 'area' && showArea ? '' : 'hide'}">${placeholders.dropdownNoResultsFound || 'No Results Found'}</div> 1939 ${field.attribute.attribute_code === 'area' && showArea ? '' : field.attribute.options?.filter((option) => option.label !== 'Select an option') 1940 .map((option) => `<div data-value="${option.value}" ${addressObj?.[field.attribute.attribute_code] === option.value ? 'selected' : ''}>${option.label}</div>`) 1941 .join('')} 1942 </div> 1943 </div> 1944 </div> 1945 `; 1946 1947 field.attribute.options?.map((option) => `<div data-value="${option.value}">${option.label}</div>`); 1948 } else if (field.attribute.frontend_input === 'multiline') { 1949 const rows = field.attribute.multiline_count ? `rows="${field.attribute.multiline_count}"` : ''; 1950 fieldDiv.innerHTML = ` 1951 <div class="input-field-wrapper"> 1952 <div class="input-wrapper input-field"> 1953 <textarea id="${field.attribute.attribute_code}" name="${field.attribute.attribute_code}" aria-label="${field.attribute.store_label}" placeholder=" " ${validations.join(' ')} ${rows} ${isDisabled ? 'disabled' : ''}>${addressObj?.[field.attribute.attribute_code] || ''}</textarea> 1954 <label for="${field.attribute.attribute_code}">${field.attribute.store_label}</label> 1955 </div> 1956 </div> 1957 `; 1958 } 1959 1960 // Check if the current field is the telephone field 1961 if (field.attribute.attribute_code === 'country_id' && !emailFieldAdded) { 1962 // Create email field to add after the telephone field 1963 const emailFieldDiv = document.createElement('div'); 1964 emailFieldDiv.classList.add('input-field-wrapper', 'emailValidation', 'notransistion'); 1965 emailFieldDiv.innerHTML = ` 1966 <div class="input-wrapper input-field email-field"> 1967 <label for="email" class="input-label">${placeholders.emailAddressLabel || 'Email Address'}</label> 1968 <input type="email" class="email" id="email" name="email" aria-label="${placeholders.emailAddressLabel || 'Email Address'}" 1969 placeholder=" " 1970 value="${email}" disabled> 1971 </div> 1972 `; 1973 1974 // Append the email field right after the telephone field 1975 form.appendChild(emailFieldDiv); 1976 1977 // Set the flag to true so the email field is only added once 1978 emailFieldAdded = true; 1979 } 1980 1981 const validAttributeCodes = [ 1982 'area', 1983 'address_building_segment', 1984 'address_apartment_segment', 1985 'address_city_segment', 1986 'address', 1987 'address_district_segment', 1988 'address_landmark_segment', 1989 'address_region_segment', 1990 'address_floor_segment', 1991 'address_block_segment', 1992 'address_road_segment', 1993 'short_code', 1994 'latitude', 1995 'longitude', 1996 'secondary_number', 1997 ]; 1998 if (validAttributeCodes.includes(field.attribute.attribute_code)) { 1999 const value = addressObj.custom_attributes?.find((attr) => attr.attribute_code === field.attribute.attribute_code)?.value || ''; 2000 const inputField = fieldDiv.querySelector('input, .select-selected, textarea'); 2001 inputField.setAttribute('value', value); 2002 inputField.setAttribute('data-initial-value', value); 2003 inputField.setAttribute('data-value', value); 2004 } 2005 2006 if (blockConfigs?.decorations?.addressFormElement) { 2007 if (formId === 'edit-address-modal') { 2008 const attributeCode = field.attribute.attribute_code; 2009 const label = field.attribute.store_label; 2010 blockConfigs.decorations.addressFormElement(fieldDiv, attributeCode, label); 2011 } 2012 } 2013 form.appendChild(fieldDiv); 2014 }); 2015 2016 const addressFormButtons = `<div class="address-form-button-wrapper"> 2017 <button type="submit" class="address-submit-button address-form-button" aria-label="${placeholders.save || 'Save'}"><span>${placeholders.save || 'Save'}</span></button> 2018 </div>`; 2019 2020 form.insertAdjacentHTML('beforeend', addressFormButtons); 2021 2022 form.noValidate = true; 2023 setCountryCode(form, countryCode); 2024 if (countriesWithRegions.includes(countryCode)) { 2025 const regionSegments = await getAddressRegionSegments(countryCode); 2026 await populateRegions(form, regionSegments, countryCode, placeholders); 2027 } else { 2028 await populateCity(form, citySegments, countryCode, placeholders); 2029 } 2030 2031 if (showArea) { 2032 await updateArea(countryCode, '', form, placeholders); 2033 } 2034 2035 form.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 2036 setErrorMessageForField(input, placeholders); 2037 input.addEventListener('input', () => { 2038 validateInput(input); 2039 }); 2040 }); 2041 2042 const phoneInput = form.querySelector('#telephone'); 2043 phoneInput.addEventListener('input', async (event) => { 2044 event.target.value = event.target.value.replace(/[^0-9]/g, ''); 2045 if (event.target.value.length > phoneInput.maxLength) { 2046 const isValidPhoneNumber = await validatePhone( 2047 phoneInput.value, 2048 countryCode, 2049 ); 2050 let phoneErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 2051 phoneErrorMessage = phoneErrorMessage.replace('{{mobile}}', phoneInput.value); 2052 2053 if (!isValidPhoneNumber) { 2054 showErrorMessage(phoneInput, phoneErrorMessage); 2055 } 2056 } 2057 }); 2058 2059 const addressCancelButton = form.querySelector('.address-cancel-button'); 2060 addressCancelButton?.addEventListener('click', () => { 2061 if (newCustomer) { 2062 $parent?.firstChild?.querySelectorAll('input, textarea').forEach((input) => { 2063 input.value = ''; 2064 }); 2065 document.querySelector('.dropdown.address_city_segment .select-selected').textContent = cityDefaultLabel; 2066 document.querySelector('.dropdown.area .select-selected').textContent = areaDefaultLabel; 2067 } else { 2068 $parent?.classList.add('hide'); 2069 } 2070 window.scrollTo(0, 0); 2071 }); 2072 2073 const addressSubmitButton = form.querySelector('.address-submit-button'); 2074 form.addEventListener('submit', async (event) => { 2075 event.preventDefault(); 2076 if (!validateForm(form, placeholders, isCheckoutPage)) { 2077 return; 2078 } 2079 const phoneValue = phoneInput.value; 2080 const isValidPhoneNumber = await validatePhone(phoneValue, countryCode); 2081 if (!isValidPhoneNumber) { 2082 let phoneNumberErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 2083 phoneNumberErrorMessage = phoneNumberErrorMessage.replace('{{mobile}}', phoneInput.value); 2084 showErrorMessage(phoneInput, phoneNumberErrorMessage); 2085 return; 2086 } 2087 2088 addressSubmitButton.classList.add('loader'); 2089 2090 if (isCheckoutPage && !isLoggedIn) { 2091 const emailInput = form.querySelector('#email'); 2092 const responseData = await validateCustomerEmail(emailInput.value); 2093 if (responseData?.customerExists) { 2094 const emailErrorMessage = placeholders.emailExist || 'You already have an account. Please log in.'; 2095 showErrorMessage(emailInput, emailErrorMessage, true); 2096 dataLayerCustomerExistCheckoutErrors(); 2097 addressSubmitButton.classList.remove('loader'); 2098 return; 2099 } 2100 emailInput.classList.remove('error'); 2101 } 2102 2103 const address = {}; 2104 const customAttributeNames = new Set([ 2105 'address_city_segment', 2106 'area', 2107 'address_building_segment', 2108 'address_apartment_segment', 2109 'address', 2110 'short_code', 2111 'secondary_number', 2112 'longitude', 2113 'latitude', 2114 ]); 2115 const inputs = form.querySelectorAll('input, .custom-select, textarea'); 2116
2117 inputs.forEach((input) => { 2118 const name = input?.getAttribute('name'); 2119 if (!name) return; 2120 2121 let key = name; 2122 let value = input.value?.trim() || ''; 2123 2124 switch (name) { 2125 case 'country_id': 2126 key = 'country_code'; 2127 break; 2128 2129 case 'address_city_segment': 2130 case 'area': { 2131 const selected = input.querySelector('.select-selected'); 2132 const dataValue = selected?.getAttribute('data-value') || ''; 2133 const dataInitialValue = selected?.getAttribute('data-initial-value') || ''; 2134 value = input.classList.contains('custom-select') 2135 ? dataValue || dataInitialValue 2136 : value; 2137 2138 if (name === 'address_city_segment') { 2139 address.city = isCheckoutPage ? (dataValue || dataInitialValue || '-') : (dataValue || '-'); 2140 } 2141 break; 2142 } 2143 2144 case 'fullName': { 2145 const [firstName = '', ...rest] = value.split(/\s+/); 2146 address.firstname = firstName; 2147 address.lastname = rest.join(' '); 2148 return; 2149 } 2150 2151 case 'telephone': { 2152 // Validate and clean telephone: strip prefix, enforce maxlength 2153 // This handles cases where user bypassed maxlength via dev tools or browser issues 2154 const cleanedTelephoneModal = validateAndCleanTelephone(value, countryPrefix, maxLength); 2155 // Add prefix back for API (API expects full number with prefix) 2156 address.telephone = cleanedTelephoneModal ? `${countryPrefix}${cleanedTelephoneModal}` : ''; 2157 return; 2158 } 2159 default: 2160 break; 2161 } 2162 2163 if (customAttributeNames.has(name)) { 2164 address.custom_attributes = address.custom_attributes || []; 2165 address.custom_attributes.push({ 2166 attribute_code: key, 2167 value, 2168 }); 2169 } else { 2170 address[key] = value; 2171 } 2172 }); 2173 2174 let res = null; 2175 let message; 2176 if (isUpdate && !isCheckoutPage) { 2177 res = await updateAddress(addressObj.id, address); 2178 if (res?.success) { 2179 message = placeholders.addressUpdated || 'Address is updated successfully.'; 2180 } 2181 } else if (isCheckoutPage) { 2182 if (isLoggedIn && !isTopup) { 2183 delete address.address; 2184 if (isUpdate) { 2185 const response = await updateAddress(addressObj.id, address, config); 2186 address.id = response?.data?.commerce_updateCustomerAddress?.id; 2187 address.street = response?.data?.commerce_updateCustomerAddress?.street; 2188 } else { 2189 const response = await createAddress(address, config); 2190 address.id = response?.data?.commerce_createCustomerAddress?.id; 2191 address.street = response?.data?.commerce_createCustomerAddress?.street; 2192 } 2193 } 2194 window.dispatchEvent(new CustomEvent('react:addressFormSubmitted', { detail: { address, isUpdate } })); 2195 } else { 2196 res = await createAddress(address); 2197 if (res?.success) { 2198 message = placeholders.addressAdded || 'Address is added successfully.'; 2199 } 2200 } 2201 if (res?.success) { 2202 block.dispatchEvent(new CustomEvent('address-updated')); 2203 addressesContainer.classList.remove('hide'); 2204 $parent.classList.add('hide'); 2205 document.querySelector('.new-address-container').classList.remove('hide'); 2206 addStatusMessage(message); 2207 } 2208 }); 2209 2210 decorateIcons(form); 2211 if (!isCheckoutPage) { 2212 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.country_id')); 2213 } 2214 customSelectbox(form.querySelector('.input-field-wrapper.dropdown.address_city_segment')); 2215 $parent.innerHTML = ''; 2216 $parent.appendChild(form); 2217 if (isCheckoutPage) { 2218 window.dispatchEvent(new CustomEvent('react:addressFormLoaded')); 2219 } 2220 if (countryCode === 'EG') { 2221 form.querySelector('#address_floor_segment').closest('form > .input-field-wrapper')?.remove(); 2222 } 2223 // Load Short Code Map modal if enabled 2224 if (blockConfigs?.flags?.enableShortCodeMap === true) { 2225 loadShortCodeMapModal(placeholders, formId); 2226 } 2227 // $parent.classList.remove('hide'); 2228 return $parent; 2229} 2230 2231// EDIT ADDRESS form in modal 2232export async function showEditAddressModal( 2233 blockConfigs, 2234 addressForm, 2235 placeholders, 2236 addressObj, 2237 citySegments, 2238 isCheckoutPage = false, 2239 isLoggedIn = false, 2240 config = {}, 2241 isTopup = false, 2242 // newCustomer = false,
2243 // updateOnlyTelephone = false, 2244 // infoMessage = '' 2245) { 2246 const editAddressModalId = 'edit-address-modal'; 2247 const editAddressTitle = placeholders.editAddressModalTitle || 'Edit Address'; 2248 const editAddressContent = document.createElement('div'); 2249 editAddressContent.classList.add('edit-address-content'); 2250 try { 2251 const formContent = await showAddressFormModal( 2252 blockConfigs, 2253 editAddressModalId, 2254 addressForm, 2255 placeholders, 2256 addressObj, 2257 ); 2258 editAddressContent.appendChild(formContent); 2259 } catch (error) { 2260 console.error('Error rendering address form:', error); 2261 } 2262 const closeIcon = blockConfigs?.flags?.closeIcon ? blockConfigs.flags.closeIcon : 'icon-close-blue'; 2263 await createModalFromContent( 2264 editAddressModalId, 2265 editAddressTitle, 2266 editAddressContent.outerHTML, 2267 ['edit-address-modal'], 2268 null, 2269 false, 2270 closeIcon, 2271 ); 2272 openModal(editAddressModalId); 2273 // Attach the submit event listener after modal is open and content is loaded 2274 2275 const formElement = document.querySelector('.edit-address-content .address-form form'); 2276 const phoneInput = formElement.querySelector('#telephone'); 2277 const countryCode = await getConfigValue('country-code'); 2278 const countryPrefix = `+${await getCountryIso(countryCode)}`; 2279 const maxLength = await getMaxLengthByCountryCode(countryCode); 2280 let isUpdate = ''; 2281 if (addressObj) { 2282 isUpdate = Object.keys(addressObj)?.length > 0; 2283 } 2284 2285 if (phoneInput) { 2286 const addressModalClosest = phoneInput?.closest('.add-address-modal, .edit-address-modal, .checkout-form'); 2287 const countryCodeLabel = addressModalClosest?.querySelector('.countrycode'); 2288 if (countryCodeLabel) { 2289 const isNumeric = /^\d*$/.test(phoneInput.value.trim()) && phoneInput.value.trim(); 2290 countryCodeLabel.classList.toggle('hidden', !isNumeric); 2291 } 2292 } 2293 2294 const addressSubmitButton = formElement.querySelector('.address-submit-button'); 2295 const addressesContainer = document.querySelector('.addresses-container'); 2296 const updateSubmitButtonState = () => { 2297 const isFormValid = formElement.checkValidity(); // Check overall form validity 2298 const isPhoneValid = phoneInput.value.length >= phoneInput.minLength 2299 && phoneInput.value.length <= phoneInput.maxLength; 2300 // Check if City and Area dropdowns have valid selections 2301 const cityDropdown = formElement.querySelector('#address_city_segment .select-selected'); 2302 const isCityValid = cityDropdown && cityDropdown.getAttribute('data-value') !== ''; 2303 const areaDropdown = formElement.querySelector('.area .select-selected'); 2304 const isAreaValid = areaDropdown && areaDropdown.getAttribute('data-value') !== ''; 2305 const districtDropdown = formElement.querySelector('#address_district_segment .select-selected'); 2306 const isDistrictValid = districtDropdown && districtDropdown.getAttribute('data-value') !== ''; 2307 const regionDropdown = formElement.querySelector('#address_region_segment .select-selected'); 2308 const isRegionValid = regionDropdown && regionDropdown.getAttribute('data-value') !== ''; 2309 // Disable submit button if form is invalid or if city/area are not selected 2310 addressSubmitButton.disabled = !( 2311 isFormValid && isPhoneValid && isCityValid 2312 && (isAreaValid || isDistrictValid || isRegionValid)); 2313 }; 2314 phoneValidation(phoneInput, countryCode, formElement, placeholders); 2315 2316 if (formElement) { 2317 // samriddhi submit 2318 formElement.addEventListener('submit', async (event) => { 2319 event.preventDefault(); 2320 if (!validateForm(formElement, placeholders, isCheckoutPage)) { 2321 return; 2322 } 2323 const phoneValue = phoneInput.value; 2324 const isValidPhoneNumber = await validatePhone(phoneValue, countryCode); 2325 if (!isValidPhoneNumber) { 2326 let phoneNumberErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 2327 phoneNumberErrorMessage = phoneNumberErrorMessage.replace('{{mobile}}', phoneInput.value); 2328 showErrorMessage(phoneInput, phoneNumberErrorMessage); 2329 return; 2330 } 2331 2332 addressSubmitButton.classList.add('loader'); 2333 2334 const isShortcodeValid = await validateAndStoreShortcode(formElement, placeholders); 2335 if (!isShortcodeValid) { 2336 addressSubmitButton.classList.remove('loader'); 2337 return; 2338 } 2339 if (isCheckoutPage && !isLoggedIn) { 2340 const emailInput = formElement.querySelector('#email'); 2341 const responseData = await validateCustomerEmail(emailInput.value); 2342 if (responseData?.customerExists) { 2343 const emailErrorMessage = placeholders.emailExist || 'You already have an account. Please log in.'; 2344 showErrorMessage(emailInput, emailErrorMessage, true); 2345 dataLayerCustomerExistCheckoutErrors(); 2346 addressSubmitButton.classList.remove('loader'); 2347 return; 2348 } 2349 emailInput.classList.remove('error'); 2350 } 2351 2352 const address = {}; 2353 const customAttributeNames = new Set([ 2354 'address_city_segment', 2355 'area', 2356 'address_building_segment', 2357 'address_apartment_segment', 2358 'address', 2359 'address_district_segment', 2360 'address_landmark_segment',
2361 'address_region_segment', 2362 'address_floor_segment', 2363 'address_block_segment', 2364 'address_road_segment', 2365 'short_code', 2366 'secondary_number', 2367 'longitude', 2368 'latitude', 2369 ]); 2370 const inputs = formElement.querySelectorAll('input, .custom-select, textarea'); 2371 2372 inputs.forEach((input) => { 2373 const name = input?.getAttribute('name'); 2374 if (!name) return; 2375 2376 let key = name; 2377 let value = input.value?.trim() || ''; 2378 switch (name) { 2379 case 'country_id': { 2380 key = 'country_code'; 2381 break; 2382 } 2383 2384 case 'address_city_segment': 2385 case 'address_district_segment': 2386 case 'address_region_segment': 2387 case 'area': { 2388 const selected = input.querySelector('.select-selected'); 2389 const dataValue = selected?.getAttribute('data-value') || ''; 2390 const dataInitialValue = selected?.getAttribute('data-initial-value') || ''; 2391 value = input.classList.contains('custom-select') 2392 ? dataValue || dataInitialValue 2393 : value; 2394 2395 if (name === 'address_city_segment' || name === 'address_district_segment' || name === 'address_region_segment') { 2396 address.city = isCheckoutPage ? (dataValue || dataInitialValue || '-') : (dataValue || '-'); 2397 } 2398 break; 2399 } 2400 2401 case 'fullName': { 2402 const [firstName = '', ...rest] = value.split(/\s+/); 2403 address.firstname = firstName; 2404 address.lastname = rest.join(' '); 2405 return; 2406 } 2407 2408 case 'telephone': { 2409 // Validate and clean telephone: strip prefix, enforce maxlength 2410 // This handles cases where user bypassed maxlength via dev tools or browser issues 2411 const cleanedTelephoneEdit = validateAndCleanTelephone(value, countryPrefix, maxLength); 2412 // Add prefix back for API (API expects full number with prefix) 2413 address.telephone = cleanedTelephoneEdit ? `${countryPrefix}${cleanedTelephoneEdit}` : ''; 2414 return; 2415 } 2416 default: 2417 break; 2418 } 2419 2420 if (customAttributeNames.has(name)) { 2421 address.custom_attributes = address.custom_attributes || []; 2422 address.custom_attributes.push({ 2423 attribute_code: key, 2424 value, 2425 }); 2426 } else if (key !== 'email') { 2427 address[key] = value; 2428 } 2429 }); 2430 2431 let res = null; 2432 let message; 2433 if (isUpdate && !isCheckoutPage) { 2434 res = await updateAddress(addressObj.id, address); 2435 if (res?.success) { 2436 message = placeholders.addressUpdated || 'Address is updated successfully.'; 2437 closeModal('edit-address-modal'); 2438 } 2439 } else if (isCheckoutPage) { 2440 if (isLoggedIn && !isTopup) { 2441 delete address.address; 2442 if (isUpdate) { 2443 const response = await updateAddress(addressObj.id, address, config); 2444 address.id = response?.data?.commerce_updateCustomerAddress?.id; 2445 address.street = response?.data?.commerce_updateCustomerAddress?.street; 2446 } else { 2447 const response = await createAddress(address, config); 2448 address.id = response?.data?.commerce_createCustomerAddress?.id; 2449 address.street = response?.data?.commerce_createCustomerAddress?.street; 2450 } 2451 } 2452 window.dispatchEvent(new CustomEvent('react:addressFormSubmitted', { detail: { address, isUpdate } })); 2453 } else { 2454 res = await createAddress(address); 2455 if (res?.success) { 2456 message = placeholders.addressAdded || 'Address is added successfully.'; 2457 closeModal('edit-address-modal'); 2458 } 2459 } 2460 if (res?.success) { 2461 window.dispatchEvent(new CustomEvent('address-updated')); 2462 closeModal('edit-address-modal'); 2463 addressesContainer.classList.remove('hide'); 2464 // $parent.classList.add('hide'); 2465 document.querySelector('.new-address-container').classList.remove('hide'); 2466 addStatusMessage(message); 2467 } 2468 }); 2469 } 2470 2471 // TODO - Use Block-Config(Developer authorable) instead of config(Author authorable). 2472 if (disableInlineValidation === 'false') { 2473 formElement.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 2474 input.addEventListener('input', () => { 2475 validateInput(input); 2476 updateSubmitButtonState(); 2477 }); 2478 input.addEventListener('blur', () => { 2479 updateSubmitButtonState(); 2480 }); 2481 }); 2482 } else { 2483 formElement.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 2484 if (input.type === 'tel') { 2485 return; 2486 } 2487 input.addEventListener('input', () => { 2488 debounce(() => { 2489 resetMessage(input); 2490 formElement.querySelector('button').disabled = false;
2491 }, 300)(); 2492 }); 2493 2494 input.addEventListener('blur', () => { 2495 if (!validateInput(input)) { 2496 formElement.querySelector('button').disabled = true; 2497 } 2498 }); 2499 }); 2500 } 2501 // Enable/disable Area field based on City selection 2502 const cityDropdown = formElement.querySelector('#address_city_segment .select-selected'); 2503 const areaDropdown = formElement.querySelector('#area .select-selected'); 2504 const districtDropdown = formElement.querySelector('#address_district_segment .select-selected'); 2505 const regionDropdown = formElement.querySelector('#address_region_segment .select-selected'); 2506 const inputFieldWrapper = formElement.querySelector('.input-field-wrapper.dropdown'); 2507 const areaValid = () => { 2508 const areaOptionsContainer = formElement.querySelector('#area .select-items'); 2509 areaOptionsContainer.addEventListener('click', (e) => { 2510 e.stopPropagation(); 2511 areaDropdown.textContent = e.target.textContent; 2512 areaDropdown.setAttribute('data-value', e.target.getAttribute('data-value')); 2513 if (areaDropdown.getAttribute('data-value') !== '') { 2514 const inputFieldWrapperArea = formElement.querySelector('.input-field-wrapper.area'); 2515 inputFieldWrapperArea.classList.remove('invalid'); 2516 areaOptionsContainer.classList.add('select-hide'); 2517 } 2518 }); 2519 }; 2520 2521 const cityValid = () => { 2522 const cityOptionsContainer = formElement.querySelector('#address_city_segment .select-items'); 2523 cityOptionsContainer.addEventListener('click', (e) => { 2524 e.stopPropagation(); 2525 cityDropdown.textContent = e.target.textContent; 2526 cityDropdown.setAttribute('data-value', e.target.getAttribute('data-value')); 2527 if (cityDropdown.getAttribute('data-value') !== '') { 2528 const inputFieldWrapperCity = formElement.querySelector('.input-field-wrapper.address_city_segment'); 2529 inputFieldWrapperCity.classList.remove('invalid'); 2530 } 2531 }); 2532 }; 2533 2534 const toggleAreaField = () => { 2535 if (districtDropdown) { 2536 const isDistrictSelected = districtDropdown.getAttribute('data-value') !== ''; 2537 if (isDistrictSelected) { 2538 cityDropdown.setAttribute('aria-disabled', 'false'); 2539 } else { 2540 cityDropdown.setAttribute('aria-disabled', 'true'); 2541 } 2542 } else if (regionDropdown) { 2543 const isRegionSelected = regionDropdown.getAttribute('data-value') !== ''; 2544 if (isRegionSelected) { 2545 cityDropdown.setAttribute('aria-disabled', 'false'); 2546 } else { 2547 cityDropdown.setAttribute('aria-disabled', 'true'); 2548 } 2549 } else if (cityDropdown) { 2550 const isCitySelected = cityDropdown.getAttribute('data-value') !== ''; 2551 if (isCitySelected) { 2552 areaDropdown.setAttribute('aria-disabled', 'false'); 2553 } else { 2554 areaDropdown.setAttribute('aria-disabled', 'true'); 2555 } 2556 } 2557 }; 2558 2559 if (districtDropdown) { 2560 // Function to update District selection and toggle City field state 2561 formElement.querySelectorAll('#address_district_segment .select-items div').forEach((districtOption) => { 2562 districtOption.addEventListener('click', () => { 2563 // Update district selection text and data-value 2564 districtDropdown.textContent = districtOption.textContent; 2565 districtDropdown.setAttribute('data-value', districtOption.getAttribute('data-value')); 2566 // Check if both city and area have values to remove the invalid class 2567 if (districtDropdown.getAttribute('data-value') !== '') { 2568 inputFieldWrapper.classList.remove('invalid'); 2569 } 2570 toggleAreaField(); 2571 }); 2572 }); 2573 } else if (regionDropdown) { 2574 // Function to update District selection and toggle City field state 2575 formElement.querySelectorAll('#address_region_segment .select-items div').forEach((regionOption) => { 2576 regionOption.addEventListener('click', () => { 2577 // Update region selection text and data-value 2578 regionDropdown.textContent = regionOption.textContent; 2579 regionDropdown.setAttribute('data-value', regionOption.getAttribute('data-value')); 2580 // Check if both region and city have values to remove the invalid class 2581 if (regionDropdown.getAttribute('data-value') !== '') { 2582 inputFieldWrapper.classList.remove('invalid'); 2583 } 2584 toggleAreaField(); 2585 }); 2586 }); 2587 } else { 2588 // Function to update city selection and toggle Area field state 2589 formElement.querySelectorAll('#address_city_segment .select-items div').forEach((cityOption) => { 2590 cityOption.addEventListener('click', () => { 2591 // Update city selection text and data-value 2592 cityDropdown.textContent = cityOption.textContent; 2593 cityDropdown.setAttribute('data-value', cityOption.getAttribute('data-value')); 2594 // Check if both city and area have values to remove the invalid class
2595 if (cityDropdown.getAttribute('data-value') !== '') { 2596 inputFieldWrapper.classList.remove('invalid'); 2597 } 2598 toggleAreaField(); 2599 }); 2600 }); 2601 } 2602 2603 // Trigger function on city selection 2604 // cityDropdown.addEventListener('click', toggleAreaField); 2605 if (districtDropdown || regionDropdown) { 2606 cityDropdown.addEventListener('click', cityValid); 2607 toggleAreaField(); 2608 cityValid(); 2609 } else { 2610 areaDropdown.addEventListener('click', areaValid); 2611 toggleAreaField(); 2612 areaValid(); 2613 } 2614 formElement.noValidate = true; 2615 setCountryCode(formElement, countryCode); 2616 if (countriesWithRegions.includes(countryCode)) { 2617 const regionSegments = await getAddressRegionSegments(countryCode); 2618 await populateRegions(formElement, regionSegments, countryCode, placeholders); 2619 } else { 2620 await populateCity(formElement, citySegments, countryCode, placeholders); 2621 } 2622 customSelectboxModal(blockConfigs, placeholders, formElement); 2623 const phoneDiv = document.querySelector('.edit-address-content .address-form form .input-field-wrapper.phonevalidation'); 2624 const emailDiv = document.querySelector('.edit-address-content .address-form form .input-field-wrapper.emailValidation'); 2625 const city = document.querySelector('.edit-address-content .address-form form .input-field-wrapper.dropdown.address_city_segment'); 2626 const area = document.querySelector('.edit-address-content .address-form form .input-field-wrapper.dropdown.area'); 2627 const district = document.querySelector('.edit-address-modal .address-form form .input-field-wrapper.dropdown.address_district_segment'); 2628 const region = document.querySelector('.edit-address-modal .address-form form .input-field-wrapper.dropdown.address_region_segment'); 2629 wrapTwoDivsinWrapper(phoneDiv, emailDiv, 'form__phone-email-wrapper'); 2630 if (districtDropdown) { 2631 wrapTwoDivsinWrapper(district, city, 'form__district-city-wrapper'); 2632 } else if (regionDropdown) { 2633 wrapTwoDivsinWrapper(region, city, 'form__region-city-wrapper'); 2634 } else if (cityDropdown) { 2635 wrapTwoDivsinWrapper(city, area, 'form__city-area-wrapper'); 2636 } 2637} 2638 2639// ADD ADDRESS form in modal 2640export async function showAddAddressModal( 2641 blockConfigs, 2642 placeholders, 2643 citySegments, 2644 addressObj = {}, 2645 config = {}, 2646 isCheckoutPage = false, 2647 isLoggedIn = false, 2648 isTopup = false, 2649 // newCustomer = false, 2650 // updateOnlyTelephone = false, 2651 // infoMessage = '' 2652) { 2653 const addAddressModalId = 'add-address-modal'; 2654 const addAddressTitle = placeholders.addAddress || 'Add Address'; 2655 const addAddressContent = document.createElement('div'); 2656 addAddressContent.classList.add('add-address-content'); 2657 const addressForm = document.createElement('div'); 2658 addressForm.classList.add('address-form'); 2659 try { 2660 const formContent = await showAddressFormModal( 2661 blockConfigs, 2662 addAddressModalId, 2663 addressForm, 2664 placeholders, 2665 {}, 2666 ); 2667 addAddressContent.innerHTML = formContent; 2668 } catch (error) { 2669 console.error('Error rendering address form:', error); 2670 } 2671 const closeIcon = blockConfigs?.flags?.closeIcon ? blockConfigs.flags.closeIcon : 'icon-close-blue'; 2672 await createModalFromContent( 2673 addAddressModalId, 2674 addAddressTitle, 2675 addressForm.outerHTML, 2676 ['add-address-modal'], 2677 null, 2678 false, 2679 closeIcon, 2680 ); 2681 openModal(addAddressModalId); 2682 // Attach the submit event listener after modal is open and content is loaded 2683 const formElement = document.querySelector(`#${addAddressModalId} .address-form form`); 2684 document.addEventListener('input', ({ target }) => { 2685 restrictSpecialCharacterText(target); 2686 }); 2687 2688 const phoneInput = formElement.querySelector('#telephone'); 2689 const countryCode = await getConfigValue('country-code'); 2690 const countryPrefix = `+${await getCountryIso(countryCode)}`; 2691 const maxLength = await getMaxLengthByCountryCode(countryCode); 2692 const isUpdate = Object.keys(addressObj).length > 0; 2693 2694 const addressSubmitButton = formElement.querySelector('.address-submit-button'); 2695 const addressesContainer = document.querySelector('.addresses-container'); 2696 const updateSubmitButtonState = () => { 2697 const isFormValid = formElement.checkValidity(); // Check overall form validity 2698 const isPhoneValid = phoneInput.value.length >= phoneInput.minLength 2699 && phoneInput.value.length <= phoneInput.maxLength; 2700 // Check if City and Area dropdowns have valid selections 2701 const cityDropdown = formElement.querySelector('#address_city_segment .select-selected'); 2702 const isCityValid = cityDropdown && cityDropdown.getAttribute('data-value') !== ''; 2703 const areaDropdown = formElement.querySelector('.area .select-selected'); 2704 const isAreaValid = areaDropdown && areaDropdown.getAttribute('data-value') !== ''; 2705 const districtDropdown = formElement.querySelector('#address_district_segment .select-selected'); 2706 const isDistrictValid = districtDropdown && districtDropdown.getAttribute('data-value') !== ''; 2707 const regionDropdown = formElement.querySelector('#address_regi
2707on_segment .select-selected'); 2708 const isRegionValid = regionDropdown && regionDropdown.getAttribute('data-value') !== ''; 2709 // Disable submit button if form is invalid or if city/area are not selected 2710 addressSubmitButton.disabled = !( 2711 isFormValid && isPhoneValid && isCityValid 2712 && (isAreaValid || isDistrictValid || isRegionValid)); 2713 }; 2714 phoneValidation(phoneInput, countryCode, formElement, placeholders); 2715 2716 if (formElement) { 2717 // samriddhi submit 2718 formElement.addEventListener('submit', async (event) => { 2719 event.preventDefault(); 2720 updateSubmitButtonState(); 2721 if (!validateForm(formElement, placeholders, isCheckoutPage)) { 2722 return; 2723 } 2724 const phoneValue = phoneInput.value; 2725 const isValidPhoneNumber = await validatePhone(phoneValue, countryCode); 2726 if (!isValidPhoneNumber) { 2727 let phoneNumberErrorMessage = placeholders.invalidMobileNumber || `The number provided ${phoneInput.value} is not a valid mobile number.`; 2728 phoneNumberErrorMessage = phoneNumberErrorMessage.replace('{{mobile}}', phoneInput.value); 2729 showErrorMessage(phoneInput, phoneNumberErrorMessage); 2730 return; 2731 } 2732 2733 addressSubmitButton.classList.add('loader'); 2734 2735 if (isShortCodeMandatory) { 2736 const isShortcodeValid = await validateAndStoreShortcode(formElement, placeholders); 2737 if (!isShortcodeValid) { 2738 addressSubmitButton.classList.remove('loader'); 2739 return; 2740 } 2741 } 2742 2743 if (isCheckoutPage && !isLoggedIn) { 2744 const emailInput = formElement.querySelector('#email'); 2745 const responseData = await validateCustomerEmail(emailInput.value); 2746 if (responseData?.customerExists) { 2747 const emailErrorMessage = placeholders.emailExist || 'You already have an account. Please log in.'; 2748 showErrorMessage(emailInput, emailErrorMessage, true); 2749 dataLayerCustomerExistCheckoutErrors(); 2750 addressSubmitButton.classList.remove('loader'); 2751 return; 2752 } 2753 emailInput.classList.remove('error'); 2754 } 2755 2756 const address = {}; 2757 const customAttributeNames = new Set([ 2758 'address_city_segment', 2759 'area', 2760 'address_building_segment', 2761 'address_apartment_segment', 2762 'address', 2763 'address_district_segment', 2764 'address_floor_segment', 2765 'address_landmark_segment', 2766 'address_region_segment', 2767 'address_block_segment', 2768 'address_road_segment', 2769 'short_code', 2770 'secondary_number', 2771 'longitude', 2772 'latitude', 2773 ]); 2774 const inputs = formElement.querySelectorAll('input, .custom-select, textarea'); 2775 2776 inputs.forEach((input) => { 2777 const name = input?.getAttribute('name'); 2778 if (!name) return; 2779 2780 let key = name; 2781 let value = input.value?.trim() || ''; 2782 switch (name) { 2783 case 'country_id': { 2784 key = 'country_code'; 2785 break; 2786 } 2787 2788 case 'address_city_segment': 2789 case 'address_district_segment': 2790 case 'address_region_segment': 2791 case 'area': { 2792 const selected = input.querySelector('.select-selected'); 2793 const dataValue = selected?.getAttribute('data-value') || ''; 2794 const dataInitialValue = selected?.getAttribute('data-initial-value') || ''; 2795 value = input.classList.contains('custom-select') 2796 ? dataValue || dataInitialValue 2797 : value; 2798 2799 if (name === 'address_city_segment' || name === 'address_district_segment' || name === 'address_region_segment') { 2800 address.city = isCheckoutPage ? (dataValue || dataInitialValue || '-') : (dataValue || '-'); 2801 } 2802 break; 2803 } 2804 2805 case 'fullName': { 2806 const [firstName = '', ...rest] = value.split(/\s+/); 2807 address.firstname = firstName; 2808 address.lastname = rest.join(' '); 2809 return; 2810 } 2811 2812 case 'telephone': { 2813 // Validate and clean telephone: strip prefix, enforce maxlength 2814 // This handles cases where user bypassed maxlength via dev tools or browser issues 2815 const cleanedTelephoneAdd = validateAndCleanTelephone(value, countryPrefix, maxLength); 2816 // Add prefix back for API (API expects full number with prefix)
2817 address.telephone = cleanedTelephoneAdd ? `${countryPrefix}${cleanedTelephoneAdd}` : ''; 2818 return; 2819 } 2820 default: 2821 break; 2822 } 2823 2824 if (customAttributeNames.has(name)) { 2825 address.custom_attributes = address.custom_attributes || []; 2826 address.custom_attributes.push({ 2827 attribute_code: key, 2828 value, 2829 }); 2830 } else if (key !== 'email') { 2831 address[key] = value; 2832 } 2833 const noAddress = document.querySelector('.account-no-address'); 2834 if (!noAddress.classList.contains('hide')) { 2835 noAddress.classList.add('hide'); 2836 } 2837 }); 2838 2839 // Due to out of the box validations for commerce we have to 2840 // send City attribute explicitly, It is not required at alshaya 2841 if (countryCode === 'KW') address.city = 'city'; 2842 2843 // Sending null at region due to magento mapping 2844 if (countryCode === 'EG') address.region = null; 2845 2846 if (countryCode === 'BH') address.street = [null]; 2847 2848 let res = null; 2849 let message; 2850 if (isUpdate && !isCheckoutPage) { 2851 res = await updateAddress(addressObj.id, address); 2852 if (res?.success) { 2853 message = placeholders.addressUpdated || 'Address is updated successfully.'; 2854 closeModal('add-address-modal'); 2855 } 2856 } else if (isCheckoutPage) { 2857 if (isLoggedIn && !isTopup) { 2858 delete address.address; 2859 if (isUpdate) { 2860 const response = await updateAddress(addressObj.id, address, config); 2861 address.id = response?.data?.commerce_updateCustomerAddress?.id; 2862 address.street = response?.data?.commerce_updateCustomerAddress?.street; 2863 } else { 2864 const response = await createAddress(address, config); 2865 address.id = response?.data?.commerce_createCustomerAddress?.id; 2866 address.street = response?.data?.commerce_createCustomerAddress?.street; 2867 } 2868 } 2869 window.dispatchEvent(new CustomEvent('react:addressFormSubmitted', { detail: { address, isUpdate } })); 2870 } else { 2871 const customer = await getCustomer(false); 2872 if (customer.addresses.length === 0) { 2873 address.default_billing = true; 2874 address.default_shipping = true; 2875 } 2876 res = await createAddress(address); 2877 if (res?.success) { 2878 message = placeholders.addressAdded || 'Address is added successfully.'; 2879 closeModal('add-address-modal'); 2880 } 2881 } 2882 if (res?.success) { 2883 window.dispatchEvent(new CustomEvent('address-updated')); 2884 closeModal('add-address-modal'); 2885 addressesContainer.classList.remove('hide'); 2886 // $parent.classList.add('hide'); 2887 document.querySelector('.new-address-container').classList.remove('hide'); 2888 addStatusMessage(message); 2889 } 2890 }); 2891 } 2892 2893 // TODO - Use Block-Config(Developer authorable) instead of config(Author authorable). 2894 if (disableInlineValidation === 'false') { 2895 formElement.querySelectorAll('input, .custom-select, textarea').forEach((input) => { 2896 input.addEventListener('input', () => { 2897 validateInput(input); 2898 updateSubmitButtonState(); 2899 }); 2900 input.addEventListener('blur', () => { 2901 updateSubmitButtonState(); 2902 }); 2903 }); 2904 } else { 2905 formElement.querySelectorAll('input, textarea').forEach((input) => { 2906 if (input.type === 'tel') { 2907 return; 2908 } 2909 input.addEventListener('input', () => { 2910 debounce(() => { 2911 resetMessage(input); 2912 formElement.querySelector('button').disabled = false; 2913 }, 300)(); 2914 }); 2915 2916 input.addEventListener('blur', () => { 2917 if (!validateInput(input)) { 2918 formElement.querySelector('button').disabled = true; 2919 } 2920 }); 2921 2922 formElement.querySelectorAll('.custom-select').forEach((dropdown) => { 2923 dropdown.addEventListener('click', (event) => { 2924 event.stopPropagation(); 2925 if (dropdown.contains(event.relatedTarget)) { 2926 return; 2927 } 2928 formElement.querySelector('button').disabled = false;
2929 const mainBox = event.target.closest('.custom-select'); 2930 const selectionBox = mainBox.querySelector('.select-selected'); 2931 if (!selectionBox.getAttribute('data-initial-value') && !selectionBox.getAttribute('data-value')) { 2932 const dropdownLabel = dropdown.getAttribute('aria-label'); 2933 let errorMessage = placeholders.addAddressSelectError || `Please select ${dropdownLabel}`; 2934 errorMessage = errorMessage.replace('{{label}}', dropdownLabel); 2935 showErrorMessage(mainBox, errorMessage); 2936 } 2937 }); 2938 }); 2939 }); 2940 } 2941 // Enable/disable Area field based on City selection 2942 const cityDropdown = formElement.querySelector('#address_city_segment .select-selected'); 2943 const areaDropdown = formElement.querySelector('#area .select-selected'); 2944 const districtDropdown = formElement.querySelector('#address_district_segment .select-selected'); 2945 const regionDropdown = formElement.querySelector('#address_region_segment .select-selected'); 2946 const inputFieldWrapper = formElement.querySelector('.input-field-wrapper.dropdown'); 2947 const areaValid = () => { 2948 const areaOptionsContainer = formElement.querySelector('#area .select-items'); 2949 areaOptionsContainer.addEventListener('click', (e) => { 2950 e.stopPropagation(); 2951 areaDropdown.textContent = e.target.textContent; 2952 areaDropdown.setAttribute('data-value', e.target.getAttribute('data-value')); 2953 if (areaDropdown.getAttribute('data-value') !== '') { 2954 const inputFieldWrapperArea = formElement.querySelector('.input-field-wrapper.area'); 2955 inputFieldWrapperArea.classList.remove('invalid'); 2956 } 2957 }); 2958 }; 2959 2960 const cityValid = () => { 2961 const cityOptionsContainer = formElement.querySelector('#address_city_segment .select-items'); 2962 cityOptionsContainer.addEventListener('click', (e) => { 2963 e.stopPropagation(); 2964 cityDropdown.textContent = e.target.textContent; 2965 cityDropdown.setAttribute('data-value', e.target.getAttribute('data-value')); 2966 if (cityDropdown.getAttribute('data-value') !== '') { 2967 const inputFieldWrapperCity = formElement.querySelector('.input-field-wrapper.address_city_segment'); 2968 inputFieldWrapperCity.classList.remove('invalid'); 2969 } 2970 }); 2971 }; 2972 2973 const toggleAreaField = () => { 2974 if (districtDropdown) { 2975 const isDistrictSelected = districtDropdown.getAttribute('data-value') !== ''; 2976 if (isDistrictSelected) { 2977 cityDropdown.setAttribute('aria-disabled', 'false'); 2978 } else { 2979 cityDropdown.setAttribute('aria-disabled', 'true'); 2980 } 2981 } else if (regionDropdown) { 2982 const isRegionSelected = regionDropdown.getAttribute('data-value') !== ''; 2983 if (isRegionSelected) { 2984 cityDropdown.setAttribute('aria-disabled', 'false'); 2985 } else { 2986 cityDropdown.setAttribute('aria-disabled', 'true'); 2987 } 2988 } else if (cityDropdown) { 2989 const isCitySelected = cityDropdown.getAttribute('data-value') !== ''; 2990 if (isCitySelected) { 2991 areaDropdown.setAttribute('aria-disabled', 'false'); 2992 } else { 2993 areaDropdown.setAttribute('aria-disabled', 'true'); 2994 } 2995 } 2996 }; 2997 2998 if (districtDropdown) { 2999 // Function to update District selection and toggle City field state 3000 formElement.querySelectorAll('#address_district_segment .select-items div').forEach((districtOption) => { 3001 districtOption.addEventListener('click', () => { 3002 // Update district selection text and data-value 3003 districtDropdown.textContent = districtOption.textContent; 3004 districtDropdown.setAttribute('data-value', districtOption.getAttribute('data-value')); 3005 // Check if both city and area have values to remove the invalid class 3006 if (districtDropdown.getAttribute('data-value') !== '') { 3007 inputFieldWrapper.classList.remove('invalid'); 3008 } 3009 toggleAreaField(); 3010 }); 3011 }); 3012 } else if (regionDropdown) { 3013 // Function to update Region selection and toggle City field state 3014 formElement.querySelectorAll('#address_region_segment .select-items div').forEach((regionOption) => { 3015 regionOption.addEventListener('click', () => { 3016 // Update region selection text and data-value 3017 regionDropdown.textContent = regionOption.textContent; 3018 regionDropdown.setAttribute('data-value', regionOption.getAttribute('data-value')); 3019 // Check if both region and city have values to remove the invalid class 3020 if (regionDropdown.getAttribute('data-value') !== '') { 3021 inputFieldWrapper.classList.remove('invalid'); 3022 } 3023 toggleAreaField(); 3024 }); 3025 }); 3026 } else { 3027 // Function to update city selection and toggle Area field state 3028 formElement.querySelectorAll('#address_city_segment .select-items div').forEach((cityOption) => { 3029 cityOption.addEventListener('click', () => { 3030 // Update city selection text and data-value 3031 cityDropdown.textContent = cityOption.textContent; 3032 cityDropdown.setAttribute('data-value', cityOption.getAttribute('data-value')); 3033 // Check if both city and area have values to remove the invalid class
3034 if (cityDropdown.getAttribute('data-value') !== '') { 3035 inputFieldWrapper.classList.remove('invalid'); 3036 } 3037 toggleAreaField(); 3038 }); 3039 }); 3040 } 3041 3042 // Trigger function on city selection 3043 // cityDropdown.addEventListener('click', toggleAreaField); 3044 if (districtDropdown || regionDropdown) { 3045 cityDropdown.addEventListener('click', cityValid); 3046 toggleAreaField(); 3047 cityValid(); 3048 } else { 3049 areaDropdown.addEventListener('click', areaValid); 3050 toggleAreaField(); 3051 areaValid(); 3052 } 3053 formElement.noValidate = true; 3054 setCountryCode(formElement, countryCode); 3055 await populateCity(formElement, citySegments, countryCode, placeholders); 3056 customSelectboxModal(blockConfigs, placeholders, formElement); 3057 const phoneDiv = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.phonevalidation'); 3058 const emailDiv = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.emailValidation'); 3059 const city = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.dropdown.address_city_segment'); 3060 const area = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.dropdown.area'); 3061 const district = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.dropdown.address_district_segment'); 3062 const region = document.querySelector('.add-address-modal .address-form form .input-field-wrapper.dropdown.address_region_segment'); 3063 3064 wrapTwoDivsinWrapper(phoneDiv, emailDiv, 'form__phone-email-wrapper'); 3065 if (districtDropdown) { 3066 wrapTwoDivsinWrapper(district, city, 'form__district-city-wrapper'); 3067 } else if (regionDropdown) { 3068 wrapTwoDivsinWrapper(region, city, 'form__region-city-wrapper'); 3069 } else if (cityDropdown) { 3070 wrapTwoDivsinWrapper(city, area, 'form__city-area-wrapper'); 3071 } 3072 3073 const addAddressModal = document.querySelector('.add-address-modal'); 3074 if (addAddressModal) { 3075 const countryCodeLabel = addAddressModal.querySelector('.countrycode'); 3076 if (countryCodeLabel) { 3077 countryCodeLabel.classList.add('hidden'); 3078 } 3079 } 3080}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.