1// ADDRESS FUNCTIONS 2 3const isUKPostcode = (postcode) => { 4 const ukPostcodeRegex = /^([A-Z]{1,2}[0-9][0-9A-Z]?) ?([0-9][A-Z]{2})$/i; 5 6 return ukPostcodeRegex.test(postcode); 7}; 8 9const showPostcodeMessage = (cartId, message, type = "danger") => { 10 const postcodeInputEl = document.getElementById(`address_postcode_${cartId}`); 11 const postcodeMessageEl = document.getElementById(`postcode_message_${cartId}`); 12 13 if (postcodeInputEl && postcodeMessageEl) { 14 if (type === "danger") { 15 postcodeInputEl.classList.add("invalid"); 16 } 17 postcodeMessageEl.classList.add("alert", "alert-" + type, "active"); 18 postcodeMessageEl.textContent = message; 19 } 20}; 21 22const hidePostcodeMessage = (cartId, type = "danger") => { 23 const postcodeInputEl = document.getElementById(`address_postcode_${cartId}`); 24 const postcodeMessageEl = document.getElementById(`postcode_message_${cartId}`); 25 26 if (postcodeInputEl && postcodeMessageEl) { 27 postcodeInputEl.classList.remove("invalid"); 28 postcodeMessageEl.classList.remove("alert", "alert-" + type, "active"); 29 postcodeMessageEl.textContent = ""; 30 } 31}; 32 33const handleUKPostcodeValidation = (cartId) => { 34 const postcodeInputEl = document.getElementById(`address_postcode_${cartId}`); 35 const countryInputEl = document.getElementById(`address_country_${cartId}`); 36 37 if (countryInputEl && countryInputEl.value === "United Kingdom") { 38 if (postcodeInputEl && postcodeInputEl.value === "") { 39 showPostcodeMessage(cartId, "Please enter your postcode."); 40 } else if (postcodeInputEl && !isUKPostcode(postcodeInputEl.value)) { 41 showPostcodeMessage(cartId, "Please enter a valid UK postcode."); 42 } else { 43 hidePostcodeMessage(cartId); 44 if (postcodeInputEl) postcodeInputEl.classList.remove("invalid"); 45 } 46 } 47}; 48 49const handleAddressSelection = (cartId) => { 50 const addressSelectEl = document.getElementById(`address_lookup_result_${cartId}`); 51 if (!addressSelectEl || addressSelectEl.selectedIndex === 0) return; 52 53 const addressData = addressSelectEl.value; 54 const addressArray = JSON.parse(addressData); 55 56 // Update address fields 57 const addressLine1El = document.getElementById(`address_line1_${cartId}`); 58 const addressLine2El = document.getElementById(`address_line2_${cartId}`); 59 const townEl = document.getElementById(`address_town_${cartId}`); 60 const countyEl = document.getElementById(`address_county_${cartId}`); 61 const countryEl = document.getElementById(`address_country_${cartId}`); 62 63 if (addressLine1El) addressLine1El.value = addressArray.line_1 || ""; 64 if (addressLine2El) addressLine2El.value = addressArray.line_2 || ""; 65 if (townEl) townEl.value = addressArray.town || ""; 66 if (countyEl) countyEl.value = addressArray.county || ""; 67 if (countryEl) countryEl.value = addressArray.country || ""; 68 69 showAllAddressFields(cartId); 70 SaveCart(cartId); 71}; 72 73const movePostcodeInputEl = (cartId) => { 74 const postcodeInputEl = document.getElementById("address_postcode_" + cartId); 75 const townInput = document.getElementById("address_town_" + cartId); 76 const postcodeMessageEl = document.getElementById("postcode_message_" + cartId); 77 const postcodeLookupFieldEl = postcodeInputEl?.closest("#postcode_lookup_field_" + cartId); 78 const addressLookupSelectEl = document.getElementById("address_lookup_result_" + cartId); 79 80 if (postcodeInputEl && townInput) { 81 const existingOnChange = postcodeInputEl.onchange; 82 83 postcodeInputEl.placeholder = "Postcode"; 84 85 postcodeInputEl.onchange = (event) => { 86 if (existingOnChange) existingOnChange.call(postcodeInputEl, event); 87 handleUKPostcodeValidation(cartId); 88 }; 89 90 // Move the entire postcode-lookup-row container if it exists, otherwise just the input 91 if (postcodeLookupFieldEl) { 92 postcodeLookupFieldEl.classList.add("moved"); 93 townInput.parentNode.insertBefore(postcodeLookupFieldEl, townInput.nextSibling); 94 95 if (postcodeMessageEl) { 96 townInput.parentNode.insertBefore(postcodeMessageEl, postcodeLookupFieldEl.nextSibling); 97 } 98 99 if (addressLookupSelectEl) { 100 townInput.parentNode.insertBefore(addressLookupSelectEl, postcodeMessageEl ? postcodeMessageEl.nextSibling : postcodeLookupFieldEl.nextSibling); 101 } 102 } else { 103 // Fallback to old behavior if postcode-lookup-row doesn't exist 104 townInput.parentNode.insertBefore(postcodeInputEl, townInput.nextSibling); 105 106 if (postcodeMessageEl) { 107 townInput.parentNode.insertBefore(postcodeMessageEl, postcodeInputEl.nextSibling); 108 } 109 } 110 } 111}; 112 113const showAllAddressFields = (cartId) => { 114 activeClassHandler("#full_address_fields_" + cartId, "add"); 115 activeClassHandler("#hidden_address_trigger_" + cartId, "remove"); 116 // movePostcodeInputEl(cartId); 117}; 118 119const updateSelectedAddons = (cartId) => { 120 let selectedAddons = []; 121 const currentCartItemEl = document.getElementById(`cart_item_${cartId}`); 122 const addonsLabelEl = currentCartItemEl.querySelector(`.addons-label`); 123 const initialSelectedAddonsEls = currentCartItemEl.querySelectorAll(`.initial-selected-addon`); 124 const noInitialSelectedAddonsEl = currentCartItemEl.querySelector(`.no-initial-selected-addons`); 125 const selectedAddonsEl = currentCartItemEl.querySelector(`.selected-addons`); 126 const addonsSelected = Array.from(currentCartItemEl.querySelectorAll('[id^="attr_addons_"]:checked')); 127 128 selectedAddons = Array.from(selectedAddonsEl.querySelectorAll(".initial-selected-addon span")).map((element) => element.textContent); 129 130 addonsSelected.forEach((addon) => (!selectedAddons.includes(addon.value) ? selectedAddons.push(addon.value) : "")); 131 132 selectedAddons = selectedAddons.filter((addon) => addonsSelected.some((selected) => selected.value === addon)); 133 134 if (noInitialSelectedAddonsEl) noInitialSelectedAddonsEl.style.display = "none";
135 if (initialSelectedAddonsEls) initialSelectedAddonsEls.forEach((el) => (el.style.display = "none")); 136 137 if (selectedAddons.length > 0) { 138 addonsLabelEl.style.display = "block"; 139 selectedAddonsEl.style.display = "block"; 140 selectedAddonsEl.innerHTML = ` 141 ${selectedAddons 142 .map( 143 (addon) => ` 144 <div class="initial-selected-addon"> 145 <span>${addon}</span> 146 ${!addon.includes("(Included)") ? `<i class="fa fa-close" onclick="removeAddonSelected(${cartId}, '${addon}')"></i>` : ""} 147 </div> 148 `, 149 ) 150 .join("")} 151 `; 152 } else { 153 selectedAddonsEl.innerHTML = "No Add-ons Selected"; 154 } 155}; 156 157const removeAddonSelected = (cartId, addonName) => { 158 const currentCartItemEl = document.getElementById(`cart_item_${cartId}`); 159 const addonCheckbox = currentCartItemEl.querySelector(`input[value="${addonName}"]`); 160 161 if (addonCheckbox) { 162 addonCheckbox.checked = false; 163 SaveCart(cartId); 164 updateSelectedAddons(cartId); 165 } 166}; 167 168const showAddressSelectEl = (cartId, address) => { 169 const addressSelectEl = document.getElementById(`address_lookup_result_${cartId}`); 170 if (!addressSelectEl) return; 171 172 // Build address parts array, filtering out empty values 173 const addressParts = [address.line_1, address.line_2, address.town, address.county].filter((part) => part && part.trim() !== ""); 174 175 const nameFormatted = addressParts.join(", "); 176 const valueFormatted = JSON.stringify(address); 177 178 const option = new Option(nameFormatted, valueFormatted); 179 addressSelectEl.add(option); 180 activeClassHandler(`#address_lookup_result_${cartId}`, "add"); 181}; 182 183const isPostcodeValid = (cartId) => { 184 const postcodeEl = document.getElementById(`address_postcode_${cartId}`); 185 const postcode = postcodeEl?.value?.trim(); 186 187 if (!postcode) { 188 showPostcodeMessage(cartId, "Please enter a postcode."); 189 return false; 190 } 191 192 return true; 193}; 194 195const handleAddressLookup = (cartId, event) => { 196 if (event) event.preventDefault(); 197 198 // Get the encryption function from the window object if available 199 const addressLookupAuth = window.addressLookupAuth || ""; 200 201 const postcodeEl = document.getElementById(`address_postcode_${cartId}`); 202 const addressLine1El = document.getElementById(`address_line1_${cartId}`); 203 const countryEl = document.getElementById(`address_country_${cartId}`); 204 205 const datas = { 206 postcode: postcodeEl?.value || "", 207 firstline: addressLine1El?.value || "", 208 country: countryEl?.value || "", 209 auth: addressLookupAuth, 210 }; 211 212 activeClassHandler(`#address_lookup_result_${cartId}`, "remove"); 213 214 hidePostcodeMessage(cartId); 215 216 if (!isPostcodeValid(cartId)) return; 217 218 showPostcodeMessage(cartId, "Looking up addresses...", "info"); 219 220 psLoad("", "postcode_lookup", datas) 221 .then(function (dataReturned) { 222 const parsedArray = JSON.parse(JSON.parse(dataReturned)); 223 224 hidePostcodeMessage(cartId); 225 226 // Remove all previously added options except the first one 227 const addressSelectEl = document.getElementById(`address_lookup_result_${cartId}`); 228 if (addressSelectEl) { 229 addressSelectEl.options.length = 1; 230 231 if (parsedArray !== null && parsedArray["addresses"] && parsedArray["addresses"].length > 0) { 232 parsedArray["addresses"].forEach((address) => showAddressSelectEl(cartId, address)); 233 } else { 234 showPostcodeMessage(cartId, "No addresses found for the provided postcode."); 235 } 236 } 237 }) 238 .catch(function (error) { 239 hidePostcodeMessage(cartId); 240 showPostcodeMessage(cartId, "Error looking up addresses. Please try again."); 241 console.error("Address lookup error:", error); 242 }); 243}; 244 245// DISCOUNT FUNCTIONS 246 247const showDiscountInputs = () => { 248 const discountTriggerEl = document.getElementById("discount_trigger"); 249 250 activeClassHandler("#discount_inputs", "toggle"); 251 discountTriggerEl.classList.toggle("rotate"); 252}; 253 254const applyCouponCode = () => { 255 const couponCode = document.getElementById("coupon_code").value; 256 const submitCodeBtn = document.getElementById("submit_coupon_code"); 257 const couponResultEl = document.getElementById("coupon_submit_result"); 258 259 if (couponCode.trim() === "") { 260 couponResultEl.classList.remove("success"); 261 couponResultEl.classList.add("error"); 262 couponResultEl.innerHTML = ` 263 <i class="fa fa-exclamation-circle fa-fw"></i>
264 <span>Please enter a coupon code.</span> 265 `; 266 return; 267 } 268 269 $.post( 270 `${apiBaseUrl}/cart/apply-discount`, 271 { 272 coupon_code: couponCode, 273 }, 274 (data) => { 275 const errors = data.errors ?? []; 276 277 if (data.errors && data.errors.length > 0) { 278 couponResultEl.classList.remove("success"); 279 couponResultEl.classList.add("error"); 280 couponResultEl.innerHTML = ` 281 <i class="fa fa-exclamation-circle fa-fw"></i> 282 <span>${errors.join("<br>")}</span> 283 `; 284 } else if (data.success) { 285 couponResultEl.classList.remove("error"); 286 couponResultEl.classList.add("success"); 287 couponResultEl.innerHTML = ` 288 <i class="fa fa-check-circle fa-fw"></i> 289 <span>${data.response.discount.message ?? "Coupon successfully applied!"}</span> 290 `; 291 292 submitCodeBtn.classList.remove("dark-blue"); 293 submitCodeBtn.classList.add("cta"); 294 submitCodeBtn.innerHTML = "Discount Applied"; 295 fetchCartTotals(); 296 } else { 297 couponResultEl.classList.remove("success"); 298 couponResultEl.classList.add("error"); 299 couponResultEl.innerHTML = ` 300 <i class="fa fa-exclamation-circle fa-fw"></i> 301 <span>Sorry, something went wrong. Please try again later.</span> 302 `; 303 } 304 }, 305 ).fail(function () { 306 couponResultEl.classList.remove("success"); 307 couponResultEl.classList.add("error"); 308 couponResultEl.innerHTML = ` 309 <i class="fa fa-exclamation-circle fa-fw"></i> 310 <span>Unable to apply coupon at this time. Please try again later.</span> 311 `; 312 }); 313}; 314 315const removeCouponCode = () => { 316 const couponResultEl = document.getElementById("coupon_submit_result"); 317 const submitCodeBtn = document.getElementById("submit_coupon_code"); 318 319 $.post(`${apiBaseUrl}/cart/remove-discount`, {}, (data) => { 320 if (data.success) { 321 couponResultEl.classList.remove("error"); 322 couponResultEl.classList.add("success"); 323 couponResultEl.innerHTML = ` 324 <i class="fa fa-check-circle fa-fw"></i> 325 <span>Coupon successfully removed from your order.</span> 326 `; 327 submitCodeBtn.classList.remove("cta"); 328 submitCodeBtn.classList.add("dark-blue"); 329 submitCodeBtn.innerHTML = "Redeem Discount"; 330 fetchCartTotals(); 331 } 332 }).fail(function () { 333 alert("Unable to remove coupon at this time. Please try again later."); 334 }); 335}; 336 337const applyUserCredit = () => { 338 const creditAmount = document.getElementById("credit_amount").value; 339 const creditResultEl = document.getElementById("credit_amount_result"); 340 const submitCreditBtn = document.getElementById("submit_credit_amount"); 341 342 if (creditAmount.trim() === "") { 343 creditResultEl.classList.remove("success"); 344 creditResultEl.classList.add("error"); 345 creditResultEl.innerHTML = ` 346 <i class="fa fa-exclamation-circle fa-fw"></i> 347 <span>Please enter a credit amount.</span> 348 `; 349 return; 350 } 351 352 $.post( 353 `${apiBaseUrl}/cart/apply-credit`, 354 { 355 credit_amount: creditAmount, 356 }, 357 (data) => { 358 const errors = data.errors ?? []; 359 const creditResponse = data.response.credit ?? {}; 360 361 if (data.errors && data.errors.length > 0) { 362 creditResultEl.classList.remove("success"); 363 creditResultEl.classList.add("error"); 364 creditResultEl.innerHTML = ` 365 <i class="fa fa-exclamation-circle fa-fw"></i> 366 <span>${errors.join("<br>")}</span> 367 `; 368 } else if (creditResponse) { 369 creditResultEl.classList.remove("error"); 370 creditResultEl.classList.add("success"); 371 creditResultEl.innerHTML = ` 372 <i class="fa fa-check-circle fa-fw"></i> 373 <span>Credit successfully applied!</span> 374 `; 375 376 submitCreditBtn.classList.remove("dark-blue"); 377 submitCreditBtn.classList.add("cta"); 378 submitCreditBtn.innerHTML = "Discount Applied"; 379 fetchCartTotals(); 380 } else { 381 creditResultEl.classList.remove("success"); 382 creditResultEl.classList.add("error"); 383 creditResultEl.innerHTML = ` 384 <i class="fa fa-exclamation-circle fa-fw"></i>
385 <span>Sorry, something went wrong. Please try again later.</span> 386 `; 387 } 388 window.location.reload(); 389 }, 390 ).fail(function () { 391 creditResultEl.classList.remove("success"); 392 creditResultEl.classList.add("error"); 393 creditResultEl.innerHTML = ` 394 <i class="fa fa-exclamation-circle fa-fw"></i> 395 <span>Unable to apply coupon at this time. Please check your connection and try again.</span> 396 `; 397 }); 398}; 399 400const removeUserCredit = () => { 401 const creditResultEl = document.getElementById("credit_amount_result"); 402 const submitCreditBtn = document.getElementById("submit_credit_amount"); 403 404 $.post(`${apiBaseUrl}/cart/apply-credit`, { credit_amount: 0 }, (data) => { 405 if (data.success) { 406 creditResultEl.classList.remove("error"); 407 creditResultEl.classList.add("success"); 408 creditResultEl.innerHTML = ` 409 <i class="fa fa-check-circle fa-fw"></i> 410 <span>Credit successfully removed from your order.</span> 411 `; 412 submitCreditBtn.classList.remove("cta"); 413 submitCreditBtn.classList.add("dark-blue"); 414 submitCreditBtn.innerHTML = "Apply Credit Balance"; 415 fetchCartTotals(); 416 } else { 417 creditResultEl.classList.remove("success"); 418 creditResultEl.classList.add("error"); 419 creditResultEl.innerHTML = ` 420 <i class="fa fa-exclamation-circle fa-fw"></i> 421 <span>Sorry, something went wrong. Please try again later.</span> 422 `; 423 } 424 window.location.reload(); 425 }).fail(function () { 426 creditResultEl.classList.remove("success"); 427 creditResultEl.classList.add("error"); 428 creditResultEl.innerHTML = ` 429 <i class="fa fa-exclamation-circle fa-fw"></i> 430 <span>Unable to remove credit at this time. Please check your connection and try again.</span> 431 `; 432 }); 433}; 434 435const fetchCartTotals = () => { 436 const orderTotalsEls = document.querySelectorAll(".order-totals"); 437 438 if (orderTotalsEls) { 439 $.get(`${apiBaseUrl}/cart/totals`, (data) => { 440 const totals = data.response.totals ?? {}; 441 const subtotal = totals.product_total + totals.addon_total + totals.personalisation; 442 443 if (Object.keys(totals).length == 0) { 444 orderTotalsEls[0].innerHTML = "There was an error loading your order totals."; 445 return; 446 } 447 448 orderTotalsEls.forEach((orderTotalEl) => { 449 orderTotalEl.innerHTML = ` 450 <div class="price-container"> 451 <label for="sub_total">Sub Total</label> 452 <span id="sub_total">£${(Number(subtotal) ?? "0.00").toFixed(2)}</span> 453 </div> 454 455 <div class="price-container"> 456 <label for="delivery_total">Delivery</label> 457 <span id="delivery_total">£${(Number(totals.delivery_total) ?? "0.00").toFixed(2)}</span> 458 </div> 459 460 ${ 461 totals.delivery_pass_total && totals.delivery_pass_total > 0 462 ? ` 463 <div class="delivery-pass-total price-container"> 464 <label for="delivery_pass_total">Delivery Pass</label> 465 <span id="delivery_pass_total">£${(Number(totals.delivery_pass_total) ?? "0.00").toFixed(2)}</span> 466 </div> 467 ` 468 : "" 469 } 470 471 ${ 472 totals.discount_total && totals.discount_total > 0 473 ? ` 474 <div class="price-container discount-total"> 475 <label for="discount_total">Discount:</label> 476 477 <span id="discount_total"> 478 -£${(Number(totals.discount_total) ?? "0.00").toFixed(2)} 479 <i class="remove-coupon fa fa-circle-minus" onclick="removeCouponCode();"></i> 480 </span> 481 </div> 482 ` 483 : "" 484 } 485 486 ${ 487 totals.credit_to_use && Number(totals.credit_to_use) > 0 488 ? ` 489 <div class="price-container credit-used"> 490 <label for="credit_used">Credit:</label> 491 <span id="credit_used"> 492 -£${(Number(totals.credit_to_use) ?? "0.00").toFixed(2)} 493 <i class="remove-credit fa fa-circle-minus" onclick="removeUserCredit();"></i> 494 </span> 495 </div> 496 ` 497 : "" 498 } 499 500 <div class="price-container grand-total"> 501 <label for="grand_total">Total:</label> 502 <span id="grand_total">£${(Number(totals.grand_total) ?? "0.00").toFixed(2)}</span> 503 </div> 504 `; 505 }); 506 }); 507 } 508}; 509 510const handleCartFormSubmit = () => { 511 const cartForm = document.getElementById("product_form"); 512 const cartItems = document.querySelectorAll(".cart-item"); 513 514 cartForm.classList.add("form-submitted"); 515
516 cartItems.forEach((cartItem) => { 517 const hiddenFields = cartItem.querySelector(".hidden-fields"); 518 const requiredInputs = hiddenFields.querySelectorAll("input:required"); 519 520 let hasInvalid = false; 521 522 requiredInputs.forEach((input) => { 523 if (!input.checkValidity()) { 524 hasInvalid = true; 525 } 526 }); 527 528 if (hasInvalid) { 529 showAllAddressFields(cartItem.getAttribute("data-cart-id")); 530 hiddenFields.classList.add("active"); 531 } 532 }); 533};
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.