1import Cookies from '../../services/Cookies.js' 2import Urls from '../../services/Urls.js' 3import Fetcher from '../../services/Fetcher.js' 4import Helpers from '../../services/Helpers.js' 5import Cart from '../../services/Cart.js' 6import Utils from '../../services/Utils.js' 7import Router from '../../services/Router.js' 8import loadTempusDominus from '../../services/TempusDominusLoader.js' 9import RetreatBookingTime from '../../services/RetreatBookingTime.js' 10import Env from '../../services/Env.js' 11 12let injectScript = async (token) => { 13 return new Promise((resolve, reject) => { 14 const script = document.createElement('script'); 15 script.src = `https://www.paypal.com/sdk/js?client-id=${encodeURIComponent(Env.quiltworxPaypalClientId())}&components=buttons&debug=true&commit=true`; 16 script['data-client-token'] = token; 17 script.addEventListener('load', resolve); 18 script.addEventListener('error', e => reject(e.error)); 19 document.head.appendChild(script); 20 }); 21} 22 23let injectLuxonScript = async () => { 24 return new Promise((resolve, reject) => { 25 const script = document.createElement('script'); 26 script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js'; 27 script.addEventListener('load', resolve); 28 script.addEventListener('error', e => reject(e.error)); 29 document.head.appendChild(script); 30 }); 31} 32 33function placeOrderButton(cart, holderId, override = false){ 34 document.getElementById(holderId).disabled = false; 35 // document.getElementById(holderId).innerHTML = `<button class="btn btn-block btn-primary" id="">Place Order</button>`; 36 37} 38 39let generateClientToken = async () => { 40 const response = await fetch(Urls.quiltworxPaypalToken, { 41 method: "post" 42 }); 43 const data = await response.json(); 44 return data; 45 } 46 47let generatePaypalClient = async (cartResponse) => {
48 let clientToken = await generateClientToken(); 49 clientToken['priceTotal'] = cartResponse['priceTotal']; 50 paypalClientRenderForm(clientToken); 51} 52 53function paypalClientRenderForm(json){ 54 55 let errorElement = document.getElementById('card-errors'); 56 let cardInput = document.getElementById('card-element'); 57 58 if(json['clientToken']){ 59 injectScript(json['clientToken']) 60 .then(() => { 61 paypal 62 .Buttons({ 63 // Sets up the transaction when a payment button is clicked 64 createOrder: function (data, actions) { 65 // let isValid = validateForm(); 66 // createCheckout(booking); 67 let subtotalContainer = document.getElementById('cart-subtotal'); 68 let rightNow = new Date().getTime(); 69 let bookingUniqueId = subtotalContainer.dataset.uniqueId; 70 71 return fetch(Urls.quiltworxPaypalOrders, { 72 method: "post", 73 headers: { 74 "Content-Type": "application/json", 75 }, 76 body: JSON.stringify({ 77 value: json['priceTotal'], 78 token: json['clientToken'], 79 orderId: bookingUniqueId + '-' + rightNow.toString(), 80 capture: true, 81 }) 82 }) 83 .then((response) => response.json()) 84 .then((order) => order.id); 85 }, 86 // Finalize the transaction after payer approval 87 onApprove: function (data, actions) { 88 89 let subtotalContainer = document.getElementById('cart-subtotal'); 90 let booking = {}; 91 booking.id = subtotalContainer.dataset.booking; 92 let postBody = { 93 value: json['priceTotal'], 94 token: Cookies.getCookie("token"), 95 booking: booking, 96 } 97 if (subtotalContainer.dataset.payment) { 98 postBody.paymentId = subtotalContainer.dataset.payment; 99 } else { 100 postBody.type = subtotalContainer.dataset.type; 101 postBody.qty = subtotalContainer.dataset.qty; 102 } 103 const postOptions = { 104 method: "POST", 105 headers: { 106 'Content-Type': 'application/json', 107 }, 108 body: JSON.stringify(postBody) 109 }; 110 if (Cookies.getCookie("token")) { 111 postOptions.headers['Authorization'] = `Token ${Cookies.getCookie("token")}`; 112 } 113 return fetch(`${Urls.apiRetreatPaypalOrders}${data.orderID}/`, postOptions) 114 .then(orderData => orderData.json()) 115 .then((orderData) => { 116 // Successful capture! For dev/demo purposes: 117 // console.log( "Capture result", orderData, JSON.stringify(orderData, null, 2)); 118 // let transaction = orderData.purchase_units[0].payments.captures[0]; 119 // alert(`Transaction ${transaction.status}: ${transaction.id} 120 121 // See console for all available details 122 // `); 123 // When ready to go live, remove the alert and show a success message within this page. For example: 124 // let element = document.getElementById('paypal-button-container'); 125 // element.innerHTML = '<h3>Thank you for your payment!</h3>'; 126 // Or go to another URL: actions.redirect('thank_you.html'); 127 if (orderData.success) { 128 actions.redirect(window.location.origin + '/retreat-confirmation/') 129 } else { 130 if (orderData.message) { 131 Utils.grr(orderData.message, "danger"); 132 return actions.restart(); 133 } else { 134 Utils.grr("There was an error; please try again.", "danger"); 135 } 136 } 137 }); 138 139 140 }, 141 // fundingSource: paypal.FUNDING.PAYPAL, 142 143 }) 144 .render("#paypal-button-container"); 145 146 }).catch(error => { 147 console.error(error); 148 }); 149 150 }else{ 151 loader.classList.remove('loader-show', 'full-screen'); 152 loader.classList.add('loader-hide'); 153 // placeOrderButton(cartResponse, "placeOrder"); 154 // May do other errors 155 if(json["error"]){ 156 Utils.grr(json["error"], "danger-stay"); 157 }else{ 158 Utils.grr("There was an error, please try again", "danger"); 159 } 160 161 } 162} 163 164 165function apiCallback(json) { 166 // Router.router() 167 return json 168} 169 170 171let createCheckout = async (data) => { 172 try { 173 const response = await Fetcher.post(Urls.apiRetreatBookings, JSON.stringify(data), apiCallback); 174 return response; 175 } catch (err) { 176 177 } 178} 179 180const phoneValidation = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im; 181 182const getPhoneNumber = (phone) => { 183 const test = phoneValidation.exec(phone); 184 if(test) return phone; 185 return false;
186} 187 188const validateForm = () => { 189 190 let contactAddress = document.getElementById('contact-address'); 191 let contactEmail = document.getElementById('contact-email'); 192 let contactFirstName = document.getElementById('first-name'); 193 let contactLastName = document.getElementById('last-name'); 194 let contactPhone = document.getElementById('contact-phone'); 195 let arrival = document.getElementById("arrival-timepicker"); 196 let departure = document.getElementById("departure-timepicker"); 197 198 let contactFields = [ 199 contactAddress, 200 contactEmail, 201 contactFirstName, 202 contactLastName, 203 contactPhone, 204 arrival, 205 departure, 206 ] 207 208 let errorString = ""; 209 let isValid = true; 210 211 for (const field of contactFields) { 212 if (!field.value) { 213 field.classList.add('is-invalid'); 214 isValid = false; 215 errorString += `${field.previousElementSibling.innerHTML} is missing. `; 216 } else { 217 field.classList.remove('is-invalid'); 218 } 219 } 220 221 if (!isValid) { 222 Utils.grr(errorString, "danger"); 223 } 224 225 return isValid; 226} 227 228const createPaypalWidget = async (cartTotal) => { 229 // Paypal Setup 230 const cartResponse = {priceTotal: cartTotal}; 231 let paypalToken = await generatePaypalClient(cartResponse); 232 // placeOrderButton(cartResponse, "paypal-button-container"); 233} 234 235let cancelBooking = async () => { 236 const loader = null || document.getElementById('loader'); 237 loader.classList.add('loader-show'); 238 loader.classList.remove('loader-hide'); 239 240 let booking = {}; 241 booking.id = document.getElementById("save-contact-info").dataset.booking; 242 booking.status = 'cancelled' 243 await createCheckout(booking) 244 245 loader.classList.add('loader-hide'); 246 loader.classList.remove('loader-show'); 247 248 Utils.grr("Booking has been cancelled.", "success"); 249 document.getElementById("close-cancel-modal").click(); 250 Router.router(); 251} 252 253let saveContactInfo = async () => { 254 255 document.getElementById("save-contact-info").disabled = true; 256 const loader = null || document.getElementById('loader'); 257 loader.classList.add('loader-show'); 258 loader.classList.remove('loader-hide'); 259 260 let booking = {}; 261 262 booking.firstName = document.getElementById("first-name").value 263 booking.lastName = document.getElementById("last-name").value 264 booking.phone = document.getElementById("contact-phone").value 265 booking.email = document.getElementById("contact-email").value 266 booking.address = document.getElementById("contact-address").value 267 booking.arrival = document.getElementById("arrival-timepicker").value; 268 booking.departure = document.getElementById("departure-timepicker").value; 269 booking.specialNeeds = document.getElementById("special-needs").value; 270 booking.mealPreference = document.getElementById("meal-preference-select").value; 271 booking.id = document.getElementById("save-contact-info").dataset.booking; 272 273 274 await createCheckout(booking); 275 276 document.getElementById("save-contact-info").disabled = false; 277 loader.classList.add('loader-hide'); 278 loader.classList.remove('loader-show'); 279 280 Utils.grr("Contact Info updated successfully.", "success"); 281 document.getElementById("close-contact-modal").click(); 282 Router.router(); 283 284} 285 286let successfulLogin = (json) => { 287 Utils.setLoginCookies(json); 288 Cart.cartLoginAction(true); 289 Cart.miniCartUpdate(); 290 $('#guestLogin').modal('hide');
291 Utils.historyPushAndRoute(Urls.appMarketplaceCheckout, true); 292 293}; 294 295let setCookiesAndForward = (json) => { 296 if (json['token']) { 297 successfulLogin(json); 298 // setTimeout(function () { 299 // successfulLogin(json); 300 // }, 500); 301 } else { 302 document.getElementById("login_submit_btn").disabled = false; 303 loader.classList.add('loader-hide'); 304 loader.classList.remove('loader-show'); 305 Cookies.eraseCookie('token'); 306 Cookies.eraseCookie('gsqSession'); 307 Cookies.eraseCookie('seller_id'); 308 Cookies.eraseCookie('seller_title'); 309 document.getElementById("login_submit_btn").classList.remove('action'); 310 Utils.grr('Your login and/or password is incorrect. Please try again or <a href="https://www.quiltster.com" target="_blank">reset your password via Quiltster</a>.', "danger"); 311 } 312}; 313 314let postCredentials = async (username, password) => { 315 let body = { 316 username: username, 317 password: password 318 } 319 Fetcher.postWithoutAuth(Urls.apiLogin, Fetcher.prepareFormData(body), setCookiesAndForward); 320} 321 322let updateItem = async (id, data) => { 323 if (Cookies.getCookie('token')) { 324 try { 325 const response = await Fetcher.post(Urls.apiGetCartItem + id + "/", JSON.stringify(data), apiCallback); 326 return response; 327 } catch (err) { 328 329 } 330 }else if (Cookies.getCookie('guest_cart')) { 331 data.guest_cart = Cookies.getCookie('guest_cart'); 332 try { 333 const response = await Fetcher.postWithoutAuth(Urls.apiGetCartItem + id + "/?guest_cart="+Cookies.getCookie('guest_cart'), Utils.jsObjectToParamString(data).substr(1), apiCallback); 334 return response; 335 } catch (err) { 336 // console.log(err); 337 } 338 } 339} 340 341let QuiltworxAccountRetreatBookingSingle = { 342 343 render: async (request) => { 344 345 injectLuxonScript(); 346 await loadTempusDominus(); 347 let cart = {}; 348 349 function getJsonCallback(json){ 350 return json.items[0] 351 } 352 353 let getJson = async (request) => { 354 try { 355 const response = await Fetcher.get(Urls.apiRetreatBookings + request.slug + '/', getJsonCallback); 356 return response; 357 } catch (err) { 358 // console.log(err) 359 } 360 } 361 const bookingJson = await getJson(request); 362 const booking = bookingJson['items'][0]; 363 364 const startDate = luxon.DateTime.fromISO(booking.retreat.startDate); 365 const endDate = luxon.DateTime.fromISO(booking.retreat.endDate); 366 const startDateString = startDate.toLocaleString(luxon.DateTime.DATE_FULL); 367 const endDateString = endDate.toLocaleString(luxon.DateTime.DATE_FULL); 368 369 let optionsBeforeHtml = ''; 370 let optionsAfterHtml = ''; 371 if (booking.retreat.additionalDays) { 372 let purchasedBefore = booking.additionalDaysBefore ? parseInt(booking.additionalDaysBefore) : 0; 373 for (const dayItem of booking.retreat.additionalDays) { 374 if (dayItem.type === 'day_before') { 375 for (const x of Array(dayItem.maxQuantity + 1 - purchasedBefore).keys()) { 376 optionsBeforeHtml += `<option value="${x * parseFloat(dayItem.price)}">${x}</option>` 377 } 378 } 379 if (dayItem.type === 'day_after') { 380 let purchasedAfter = booking.additionalDaysAfter ? parseInt(booking.additionalDaysAfter) : 0; 381 for (const x of Array(dayItem.maxQuantity + 1 - purchasedAfter).keys()) { 382 optionsAfterHtml += `<option value="${x * parseFloat(dayItem.price)}">${x}</option>` 383 } 384 } 385 } 386 } 387 388 Utils.setDocumentTitle(request, booking.retreat.title); 389 390 let view = /*html*/ ` 391 <div class="modal fade hide" id="editContactInfoModal" tabindex="-1" role="dialog" aria-modal="true"> 392 <div class="modal-dialog modal-dialog-centered modal-lg" role="document"> 393 <div class="modal-content"> 394 <!-- Close --> 395 <button type="button" class="close" id="close-contact-modal" data-dismiss="modal" aria-label="Close"> 396 <i class="fe fe-x" aria-hidden="true"></i> 397 </button> 398 <!-- Content --> 399 <div class="container-fluid"> 400 <div class="row "> 401 <div class="col mt-5 p-3 text-center"> 402 <!-- Heading --> 403 <h4 class="mb-3">Edit Contact Info</h4> 404 <div class="col-12"> 405 <form class="form text-left"> 406 <div class="form-row mt-3"> 407 <div class="col-6 pr-3"> 408 <label for="first-name">First Name</label> 409 <input 410 id="first-name" 411 type="text" 412 class="form-control pl-2 pr-0 contact-field" 413 value="${booking.firstName}" 414 /> 415 </div> 416 <div class="col-6 pl-3"> 417 <label for="last-name">Last Name</label> 418 <input 419 id="last-name" 420 type="text" 421 class="form-control pl-2 pr-0 contact-field" 422 value="${booking.lastName}" 423 /> 424 </div> 425 </div> 426 427 <div class="form-row mt-3"> 428 <div class="col-6 pr-3"> 429 <label for="contact-email">Email</label> 430 <input 431 id="contact-email" 432 type="email" 433 class="form-control pl-2 pr-0 contact-field" 434 value="${booking.email}" 435 /> 436 </div> 437 <div class="col-6 pl-3"> 438 <label for="contact-phone">Phone</label> 439 <input 440 id="contact-phone" 441 type="text" 442 class="form-control pl-2 pr-0 contact-field" 443 value="${booking.phone}" 444 /> 445 </div> 446 </div> 447 448 <div class="form-row mt-3"> 449 <div class="col-12"> 450 <label for="address">Mailing Address</label> 451 <textarea 452 id="contact-address" 453 class="form-control contact-field" 454 >${booking.address ? booking.address : ''}</textarea> 455 </div> 456 </div> 457 458 <h4 class="mt-6">Arrival & Departure</h4> 459 <div class="form-row mt-3"> 460 <div id="additional-day-note" class="alert alert-warning d-none"> 461 <i class="fe fe-alert-circle"></i> 462 Be sure to include your additional day(s) in your arrival & departure time. 463 </div> 464 <div class="col-6 pr-3"> 465 <label for="arrival-timepicker">Arrival Time</label> 466 <input 467 id="arrival-timepicker" 468 type="text" 469 class="form-control pl-2 pr-0" 470 data-td-target="#arrival-timepicker" 471 value="${RetreatBookingTime.formatPickerValue(booking.arrival)}" 472 /> 473 </div> 474 <div class="col-6 pl-3"> 475 <label for="departure-datepicker">Departure Time</label> 476 <input 477 id="departure-timepicker" 478 type="text" 479 class="form-control pl-2 pr-0" 480 data-td-target="#departure-date" 481 value="${RetreatBookingTime.formatPickerValue(booking.departure)}" 482 /> 483 </div> 484 </div> 485 <div class="form-row mt-3"> 486 <div class="col-12"> 487 <label for="special-needs">Any special needs or disabilities?</label> 488 <textarea id="special-needs" name="special-needs" class="form-control">${booking.specialNeeds}</textarea> 489 </div> 490 </div> 491 492 <h4 class="mt-6">Meal Preference</h4> 493 494 <div class="col-6 form-row align-items-center"> 495 <select id="meal-preference-select" class="custom-select custom-select-sm"> 496 <option value="standard"${booking.mealPreference === 'standard' ? ' selected' : ''}>Standard</option> 497 <option value="vegetarian"${booking.mealPreference === 'vegetarian' ? ' selected' : ''}>Vegetarian</option> 498 <option value="vegan"${booking.mealPreference === 'vegan' ? ' selected' : ''}>Vegan</option> 499 <option value="gluten_free"${booking.mealPreference === 'gluten_free' ? ' selected' : ''}>Gluten free</option> 500 <option value="nut_free"${booking.mealPreference === 'nut_free' ? ' selected' : ''}>Nut free</option> 501 <option value="dairy_free"${booking.mealPreference === 'dairy_free' ? ' selected' : ''}>Dairy Free</option> 502 <option value="no_pork"${booking.mealPreference === 'no_pork' ? ' selected' : ''}>No Pork</option> 503 <option value="no_seafood"${booking.mealPreference === 'no_seafood' ? ' selected' : ''}>No Seafood</option> 504 </select> 505 </div> 506 507 <button type="button" id="save-contact-info" class="btn btn-primary float-end mt-6 mb-4" style="float:right;" data-booking="${booking.id}">Save Changes</button> 508 509 </form> 510 </div> 511 </div> 512 </div> 513 </div> 514 </div> 515 </div> 516 </div> 517 518 <div class="modal fade hide" id="confirmCancelModal" tabindex="-1" role="dialog" aria-modal="true"> 519 <div class="modal-dialog modal-dialog-centered modal
519-lg" role="document"> 520 <div class="modal-content"> 521 <!-- Close --> 522 <button type="button" class="close" id="close-cancel-modal" data-dismiss="modal" aria-label="Close"> 523 <i class="fe fe-x" aria-hidden="true"></i> 524 </button> 525 <!-- Content --> 526 <div class="container-fluid"> 527 <div class="row modal-body"> 528 529 <div class="col mt-5 p-3"> 530 <!-- Heading --> 531 <h4 class="mb-6">Are you sure you want to cancel?</h4> 532 <div class="col-12"> 533 534 <p>Please confirm to cancel your reservation. All payments made to date are non-refundable.</p> 535 <p>If you would like more info, please contact <a href="mailto:[email protected]">[email protected]</a>.</p> 536 <p><a href="http://www.quiltworx.com/retreats/cancellation-policy/" target="_blank">Please see our cancellation policy here.</a></p> 537 538 </div> 539 </div> 540 </div> 541 </div> 542 <div class="modal-footer mt-0 pt-0" style="justify-content: flex-end;"> 543 <!-- Button --> 544 <button class="btn btn-sm btn-link" data-dismiss="modal"> 545 Never mind 546 </button> 547 <button class="btn btn-sm btn-danger" id="cancel-booking"> 548 Yes, Cancel Booking 549 </button> 550 </div> 551 </div> 552 </div> 553 </div> 554 555 <section class="pb-12"> 556 <div class="container"> 557 <div class="row" id="booking-row"> 558 <div class="col-12 pt-3" id="retreatDescription"> 559 <!-- Heading --> 560 <h3 class="mb-5">${booking.retreat.title}</h3> 561 <p><strong>Event Dates: ${startDateString}${booking.retreat.endDate ? ' — ' + endDateString : ''}</strong></p> 562 <p class="mb-8">${booking.retreat.description}</p> 563 564 <div class="card card-lg mb-7 border"> 565 <div class="card-body pb-0 p-3"> 566 <!-- Info --> 567 <div class="card card-sm"> 568 <div class="card-header align-middle"> 569 <div class="row"> 570 <div class="col-10"> 571 <h4 class="mb-0 mt-2 align-middle">Booking ID: ${booking.uniqueId} ${booking.status === 'cancelled' ? ' (CANCELLED)' : ``}</h4> 572 </div> 573 <div class="col-2"> 574 ${booking.status === 'cancelled' ? '' : `<button class="btn btn-sm btn-primary btn-inline" style="float:right" data-toggle="modal" data-target="#editContactInfoModal">Edit</button>`} 575 </div> 576 </div> 577 </div> 578 <div class="card-body"> 579 <div class="row"> 580 <div class="col-6"> 581 <p><strong>Retreat</strong><br />${booking.retreat.title}</p> 582 <p><strong>Arrival</strong><br />${booking.arrival ? `${RetreatBookingTime.formatRetreatDate(booking.arrival)} ${RetreatBookingTime.formatRetreatTime(booking.arrival)}` : '-'}</p> 583 <p><strong>Name</strong><br />${booking.firstName} ${booking.lastName}</p> 584 <p><strong>Address</strong><br />${booking.address}</p> 585 <p><strong>Meal Request</strong><br />${Utils.capitalize(booking.mealRequest)}</p> 586 <p><strong>Special Needs</strong><br />${booking.specialNeeds ? booking.specialNeeds : 'None'}</p> 587 </div> 588 <div class="col-6"> 589 <p><strong>Ticket & Room</strong><br />${booking.room.ticket.title} - ${booking.room.title} ${booking.isPrivate ? ' (Private)' : ''}</p> 590 <p><strong>Departure</strong><br />${booking.departure ? `${RetreatBookingTime.formatRetreatDate(booking.departure)} ${RetreatBookingTime.formatRetreatTime(booking.departure)}` : '-'}</p> 591 <p><strong>Phone</strong><br /><a href="tel:${booking.phone}">${booking.phone}</a></p> 592 <p><strong>Email</strong><br /><a href="mailto:${booking.email}">${booking.email}</a></p> 593 </div> 594 </div> 595 <hr /> 596 <div class="row"> 597 <div class="col-6${booking.retreat.additionalDays ? '' : ' d-none'}"> 598 <p><strong>Additional Days Before Event</strong><br />${booking.additionalDaysBefore ? booking.additionalDaysBefore : 'None'}</p> 599 </div> 600 <div class="col-6${booking.retreat.additionalDays ? '' : ' d-none'}"> 601 <p><strong>Additional Days Before Event</strong><br />${booking.additionalDaysAfter ? booking.additionalDaysAfter : 'None'}</p> 602 </div> 603 <div class="col-6${booking.retreat.equipmentRental ? '' : ' d-none'}"> 604 <p><strong>${booking.retreat.equipmentRental ? booking.retreat.equipmentRental.title : 'Equipment'} Rental?</strong><br />${booking.equipmentRental ? 'Yes' : 'No'}</p> 605 </div> 606 </div> 607 </div> 608 </div> 609 <div class="card card-sm"> 610 <div class="card-header align-middle"> 611 <div class="row"> 612 <div class="col-10"> 613 <h4 class="mb-0 mt-0 align-middle">Payment Schedule</h4> 614 </div> 615 </div> 616 </div> 617 <div class="card-body"> 618 <div class="row"> 619 <div class="table-responsive"> 620 <table class="table"> 621 <thead class="thead-light"> 622 <tr> 623 <th scope="col">Type</th> 624 <th scope="col" class="text-center">Paid</th> 625 <th scope="col">Amount</th> 626 <th scope="col">Date</th> 627 </tr> 628 </thead> 629 <tbody> 630 ${booking.payments ? booking.payments.map((payment, index) => 631 /*html*/` 632 <tr> 633 <th scope="row" class="align-middle"> 634 ${Utils.capitalize(payment.type)} 635 </th> 636 <td class="text-center${payment.isPaid ? '' : ' py-3'}"> 637 ${payment.isPaid ? 638 '<div class="rounded-circle bg-success text-white mt-6 mt-sm-0" style="width:26px;height:26px;margin:0 auto;"><span class="fe fe-check"></span></div>' 639 : 640 `<button class="btn btn-primary btn-inline btn-sm paynow-button mt-6 mt-sm-0" data-booking="${booking.id}" data-unique-id="${booking.uniqueId}" data-payment="${payment.id}" data-amount="${payment.amount}" data-type="${payment.type}">Pay Now</button>`} 641 </td> 642 <td class="align-middle"> 643 $${payment.amount} 644 </td> 645 <td class="align-middle"> 646 ${payment.paidDate ? 'Paid: ' + Utils.formatDate(payment.paidDate, false) : payment.dueDate ? 'Due: ' + Utils.formatDate(payment.dueDate, false) : ''} 647 </td> 648 </tr>`).join('\n') 649 : ''} 650 </tbody> 651 </table> 652 </div> 653 </div> 654 <div class="mb-6"> 655 656 657 ${booking.retreat.additionalDays ? booking.retreat.additionalDays.map((dayItem) => ` 658 659 ${ (dayItem.type === 'day_before') && (!booking.additionalDaysBefore || (booking.additionalDaysBefore < dayItem.maxQuantity)) ? ` 660 <h5 class="mt-5">Additional Days Before Event</h5> 661 <p>Additional days before the event can be purchased for $${dayItem.price}/day.</p> 662 <form class="form-inline"> 663 <div class="w-25 form-row align-items-center"> 664 <div class="" style="white-space:nowrap;"> 665 <select id="additional-days-before-select" class="custom-select custom-select-sm mr-1"> 666 ${optionsBeforeHtml} 667 </select> 668 days 669 </div> 670 </div> 671 <div class="w-75 pr-lg-2 text-right d-none" id="additional-days-before-subtotal-wrap">
672 <span id="additional-days-before-subtotal">$0</span> 673 <button class="btn btn-primary btn-inline ml-2 ml-md-6 mr-md-6 paynow-button" id="buy-day-before-button" data-unique-id="${booking.uniqueId}" data-booking="${booking.id}" data-type="day_before">Buy Now</button> 674 </div> 675 </form> 676 ` : ''} 677 678 ${ (dayItem.type === 'day_after') && (!booking.additionalDaysAfter || (booking.additionalDaysAfter < dayItem.maxQuantity)) ? ` 679 <h5 class="mt-5">Additional Days After Event</h5> 680 <p>Additional days after the event can be purchased for $${dayItem.price}/day.</p> 681 <form class="form-inline"> 682 <div class="w-25 form-row align-items-center"> 683 <div class="" style="white-space:nowrap;"> 684 <select id="additional-days-after-select" class="custom-select custom-select-sm mr-1"> 685 ${optionsAfterHtml} 686 </select> 687 days 688 </div> 689 </div> 690 <div class="w-75 pr-lg-2 text-right d-none" id="additional-days-after-subtotal-wrap"> 691 <span id="additional-days-after-subtotal">$0</span> 692 <button class="btn btn-primary btn-inline ml-2 ml-md-6 mr-md-6 paynow-button" id="buy-day-after-button" data-unique-id="${booking.uniqueId}" data-booking="${booking.id}" data-type="day_after">Buy Now</button> 693 </div> 694 </form> 695 ` : ''} 696 697 `).join("\n") : ''} 698 699 700 ${ !booking.equipmentRental && booking.retreat.equipmentRental ? ` 701 <h5 class="mt-8">Equipment Rental</h5> 702 <p>Would you like to rent a ${booking.retreat.equipmentRental.title} for $${booking.retreat.equipmentRental.price}?</p> 703 <form class="form-inline"> 704 <div class="w-50 form-row align-items-center"> 705 <select id="equipment-rental-select" class="custom-select custom-select-sm" data-name="${booking.retreat.equipmentRental.title}"> 706 <option value="0">No</option> 707 <option value="${booking.retreat.equipmentRental.price}">Yes</option> 708 </select> 709 </div> 710 <div class="w-50 pr-lg-2 text-right d-none" id="equipment-rental-subtotal-wrap"> 711 <span id="equipment-rental-subtotal">$0</span> 712 <button class="btn btn-primary btn-inline ml-2 ml-md-6 mr-md-6 paynow-button" id="buy-equipment-button" data-unique-id="${booking.uniqueId}" data-booking="${booking.id}" data-type="equipment">Buy Now</button> 713 </div> 714 </form> 715 ` : ''} 716 </div> 717 </div> 718 </div> 719 720 ${booking.status === 'cancelled' ? '' : `<button class="btn btn-danger my-5 ml-2" data-toggle="modal" data-target="#confirmCancelModal">Cancel Booking</button>`} 721 722 </div> 723 </div> 724 </div> 725 </div> 726 <div class="row justify-content-md-center d-none" id="cart-row"> 727 <div class="col-12 col-md-7"> 728 <style> 729 .list-group-item:first-child { 730 border:none; 731 } 732 </style> 733 <div id="ticket-list" class="row break tab-content" style="flex-wrap:nowrap;"> 734 735 <div class="col-12 mx-auto prefs-list fade active show tab-pane" id="setPrefs" role="tabpanel" aria-labelledby="setPrefs-tab"> 736 <div class="mb-2 font-weight-bold"> 737 <h3>Shopping Cart</h3> 738 </div> 739 <!-- List group --> 740 <ul id="cart-list" class="list-group list-group-lg list-group-flush-x mb-6 item-list"> 741 </ul> 742 <button class="btn btn-outline-secondary btn-inline btn-sm mt-6" id="cancel-cart">Cancel</button> 743 </div> 744 745 </div> 746 747 </div> 748 <div id="checkout-column" class="col-12 col-md-5 col-lg-4 offset-lg-1"> 749 750 <!-- Total --> 751 <div class="card mb-7 bg-light"> 752 <div class="card-body"> 753 <ul class="list-group list-group-sm list-group-flush-y list-group-flush-x" id="list-cart-details"> 754 <li class="list-group-item d-flex">
755 <span>Total Charge</span> <span class="ml-auto font-size-sm" id="cart-subtotal"> - </span> 756 </li> 757 <li class="list-group-item d-none"> 758 <span>Tax</span> <span class="ml-auto font-size-sm"></span> 759 </li> 760 <li class="list-group-item border-0 d-none font-size-lg font-weight-bold"> 761 <span>Total</span> <span class="ml-auto font-size-sm"></span> 762 </li> 763 <li class="list-group-item border-0 d-none font-size-xs text-muted"> 764 <span>Shipping and taxes will be calculated at checkout.</span> 765 </li> 766 </ul> 767 </div> 768 769 </div> 770 771 <!-- Button --> 772 <div id="paypal-button-container" class="paypal-button-container mb-2"></div> 773 774 <!-- Disclaimer --> 775 <p class="mb-7 font-size-xs text-gray-500"> 776 Your personal data will be used to process your order, support 777 your experience throughout this website, and for other purposes 778 described in our privacy policy. 779 </p> 780 781 </div> 782 </div> 783 </div> 784 </section> 785 ` 786 return view 787 }, 788 after_render: async () => { 789 790 let payButtons = document.getElementsByClassName('paynow-button'); 791 for (const payButton of payButtons) { 792 payButton.addEventListener('click', function(e){ 793 e.preventDefault(); 794 let subtotalContainer = document.getElementById('cart-subtotal'); 795 796 // set data attributes 797 subtotalContainer.dataset.type = e.target.dataset.type; 798 subtotalContainer.dataset.amount = e.target.dataset.amount; 799 subtotalContainer.dataset.booking = e.target.dataset.booking; 800 subtotalContainer.dataset.uniqueId = e.target.dataset.uniqueId; 801 let typeString = ''; 802 if (e.target.dataset.type === 'day_before') { 803 delete subtotalContainer.dataset.payment; 804 subtotalContainer.dataset.qty = e.target.dataset.qty; 805 typeString = `${e.target.dataset.qty} Additional Day${e.target.dataset.qty > 1 ? 's' : ''} Before Event`; 806 } else if (e.target.dataset.type === 'day_after') { 807 delete subtotalContainer.dataset.payment; 808 subtotalContainer.dataset.qty = e.target.dataset.qty; 809 typeString = `${e.target.dataset.qty} Additional Day${e.target.dataset.qty > 1 ? 's' : ''} After Event`; 810 } else if (e.target.dataset.type === 'equipment') { 811 delete subtotalContainer.dataset.payment; 812 subtotalContainer.dataset.qty = 1; 813 typeString = `${e.target.dataset.name} Rental`; 814 } else { // second and final payments 815 subtotalContainer.dataset.payment = e.target.dataset.payment; 816 subtotalContainer.dataset.qty = 1; 817 typeString = `${Utils.capitalize(e.target.dataset.type)} Payment`; 818 } 819 820 const cartHtml = `<li class="list-group-item"><div class="row align-items-center"><div class="col-10">${typeString}</div><div class="col-2">$${e.target.dataset.amount}</div></div></li>` 821 document.getElementById('cart-list').innerHTML = cartHtml; 822 subtotalContainer.innerHTML = `$${e.target.dataset.amount}`; 823 createPaypalWidget(e.target.dataset.amount); 824 825 document.getElementById('booking-row').classList.add('d-none'); 826 document.getElementById('cart-row').classList.remove('d-none'); 827 window.scroll(0,0); 828 829 }); 830 } 831 832 document.getElementById("additional-days-before-select") && document.getElementById("additional-days-before-select").addEventListener('change', function(e){ 833 e.preventDefault(); 834 let price = parseFloat(e.target.value); 835 let qty = parseFloat(e.target.selectedOptions[0].firstChild.data); 836 document.getElementById("buy-day-before-button").dataset.amount = price.toFixed(2); 837 document.getElementById("buy-day-before-button").dataset.qty = qty; 838 document.getElementById("additional-days-before-subtotal").innerHTML = '$' + price.toFixed(2); 839 if (qty > 0) { 840 document.getElementById("additional-days-before-subtotal-wrap").classList.remove("d-none"); 841 } else { 842 document.getElementById("additional-days-before-subtotal-wrap").classList.add("d-none"); 843 } 844 }); 845 846 document.getElementById("additional-days-after-select") && document.getElementById("additional-days-after-select").addEventListener('change', function(e){ 847 e.preventDefault(); 848 let price = parseFloat(e.target.value); 849 let qty = parseFloat(e.target.selectedOptions[0].firstChild.data); 850 document.getElementById("buy-day-after-button").dataset.amount = price.toFixed(2); 851 document.getElementById("buy-day-after-button").dataset.qty = qty; 852 document.getElementById("additional-days-after-subtotal").innerHTML = '$' + price.toFixed(2); 853 if (qty > 0) { 854 document.getElementById("additional-days-after-subtotal-wrap").classList.remove("d-none"); 855 } else { 856 document.getElementById("additional-days-after-subtotal-wrap").classList.add("d-none"); 857 } 858 }); 859 860 document.getElementById("equipment-rental-select") && document.getElementById("equipment-rental-select").addEventListener('change', function(e){ 861 e.preventDefault(); 862 let price = parseFloat(e.target.value); 863 document.getElementById("buy-equipment-button").dataset.amount = price.toFixed(2); 864 document.getElementById("buy-equipment-button").dataset.name = e.target.dataset.name; 865 document.getElementById("equipment-rental-subtotal").innerHTML = '$' + price.toFixed(2); 866 if (price > 0) { 867 document.getElementById("equipment-rental-subtotal-wrap").classList.remove("d-none"); 868 } else { 869 document.getElementById("equipment-rental-subtotal-wrap").classList.add("d-none"); 870 } 871 }); 872 873 document.getElementById("arrival-timepicker").addEventListener('focus', function(e) { 874 e.target.classList.remove('is-invalid'); 875 }) 876 877 document.getElementById("departure-timepicker").addEventListener('focus', function(e) { 878 e.target.classList.remove('is-invalid'); 879 }) 880 881 document.getElementById("cancel-cart").addEventListener('click', function(e) { 882 document.getElementById("cart-row").classList.add('d-none'); 883 document.getElementById("booking-row").classList.remove('d-none'); 884 }) 885 886 let pickerOptions = { 887 display: { 888 icons: { 889 time: 'fe fe-clock', 890 date: 'fe fe-calendar', 891 up: 'fe fe-arrow-up', 892 down: 'fe fe-arrow-down', 893 previous: 'fe fe-chevron-left', 894 next: 'fe fe-chevron-right', 895 today: 'fe fe-calendar-check', 896 clear: 'fe fe-trash', 897 close: 'fe fe-x', 898 }, 899 buttons: { 900 today: true, 901 clear: true, 902 close: true, 903 }, 904 theme: 'light', 905 }, 906 localization: { 907 locale: 'en-US', 908 // State the format rather than leaning on the locale default: 909 // the picker parses the input's value positionally against 910 // this string and re-emits it after every pick, and the same 911 // string is posted back to the api. See RetreatBookingTime.js. 912 format: RetreatBookingTime.PICKER_FORMAT, 913 } 914 } 915 916 new tempusDominus.TempusDominus(document.getElementById('arrival-timepicker'), pickerOptions); 917 new tempusDominus.TempusDominus(document.getElementById('departure-timepicker'), pickerOptions); 918 919 document.getElementById("save-contact-info").addEventListener('click', function(e) { 920 let isValid = validateForm(); 921 if (isValid) { 922 saveContactInfo(); 923 } 924 }); 925 926 document.getElementById("cancel-booking").addEventListener('click', function(e) { 927 928 cancelBooking(); 929 930 }); 931 932 } 933} 934export default QuiltworxAccountRetreatBookingSingle;
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.