1jQuery(document).ready(function ($) { 2 3 4 function updateButtonState(button) { 5 var postID = button.data('post-id'); 6 var userID = button.data('user-id'); 7 var wishlistStatus; 8 9 if (userID) { 10 $.ajax({ 11 type: 'POST', 12 url: ajaxurl, 13 data: { 14 action: 'get_wishlist_status', 15 post_id: postID, 16 user_id: userID, 17 }, 18 success: function (response) { 19 wishlistStatus = response; 20 21 if (wishlistStatus === 'in_wishlist') { 22 button.html('<i class="fas fa-regular fa-heart"></i>'); 23 button.removeClass('heart-icon-empty').addClass('heart-icon'); 24 25 } else if (wishlistStatus === 'not_in_wishlist') { 26 button.html('<i class="fal fa-regular fa-heart"></i>'); 27 button.removeClass('heart-icon').addClass('heart-icon-empty'); 28 29 if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) { 30 removeWishlistDataFromMainPage(postID); 31 } 32 33 } 34 fetchWishlistData(); 35 }, 36 }); 37 } 38 else 39 { 40 // Guest user - add the item to a cookie 41 $.ajax({ 42 type: 'POST', 43 url: ajaxurl, 44 data: { 45 action: 'get_wishlist_status', 46 post_id: postID, 47 }, 48 success: function (response) { 49 wishlistStatus = response; 50 51 if (wishlistStatus === 'added') { 52 button.html('<i class="fas fa-regular fa-heart"></i>'); 53 button.removeClass('heart-icon-empty').addClass('heart-icon'); 54 55 } else if (wishlistStatus === 'removed') { 56 button.html('<i class="fal fa-regular fa-heart"></i>'); 57 button.removeClass('heart-icon').addClass('heart-icon-empty'); 58 if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) { 59 removeWishlistDataFromMainPage(postID); 60 } 61 62 } 63 fetchWishlistData(); 64 }, 65 }); 66 } 67 68 } 69 70 jQuery(document).on('click', '.wishlist-button', function(e) { 71 e.preventDefault(); 72 73 var button = jQuery(this); 74 var postID = button.data('post-id'); 75 var userID = button.data('user-id'); 76 77 // Prevent double-clicks while AJAX is running 78 if (button.hasClass('loading')) { 79 return; 80 } 81 button.addClass('loading'); 82 button.prop('disabled', true); // disable button 83 button.css('opacity', '0.5'); // optional visual feedback 84 85 jQuery.ajax({ 86 url: ajaxurl, // make sure ajax_object is localized in PHP 87 type: 'POST', 88 data: { 89 action: userID > 0 ? 'update_wishlist' : 'update_guest_wishlist', 90 post_id: postID, 91 user_id: userID 92 }, 93 success: function(response) { 94 var wishlistStatus = response.trim(); // remove any extra spaces 95 96 if (wishlistStatus === 'added') { 97 button.html('<i class="fas fa-regular fa-heart"></i>'); 98 button.removeClass('heart-icon-empty').addClass('heart-icon'); 99 fetchWishlistData(); 100 } else if (wishlistStatus === 'removed') { 101 button.html('<i class="fal fa-regular fa-heart"></i>'); 102 button.removeClass('heart-icon').addClass('heart-icon-empty'); 103 fetchWishlistData(); 104 105 if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) { 106 removeWishlistDataFromMainPage(postID); 107 } 108 } else { 109 console.log('Unexpected response:', wishlistStatus); 110 } 111 console.log('AJAX Response:', response); 112 }, 113 complete: function() { 114 // Re-enable button after AJAX finishes 115 button.removeClass('loading'); 116 button.prop('disabled', false); 117 button.css('opacity', '1'); 118 } 119 }); 120 }); 121 122 123 // Call the function to set the initial button state on page load 124 updateButtonState($('.wishlist-button')); 125 126 var wishlistCount = parseInt($('.custom_wishlist_count').text()); 127 128 // Append the count after the heart icon 129 var heartIcon = $('.wp-bottom-menu .fa-heart'); 130 var countElement = $('<span class="wishlist-count-footer custom_wishlist_count">' + wishlistCount + '</span>'); 131 heartIcon.after(countElement); 132 133 function fetchWishlistData() { 134 $.ajax({ 135 type: 'POST', 136 url: ajaxurl, 137 data: { 138 action: 'get_wishlist_data_count', 139 }, 140 success: function (data) { 141 // Update the wishlist section with the new data 142 // parse the JSON string to an array 143 let parsedArray = JSON.parse(data); 144 // count the number of elements 145 let count = parsedArray.length; 146 147 $('.custom_wishlist_count').html(count); 148 $('.custom_wishlist_count1').html(count); 149 localStorage.setItem('guest_wishlist_full_name', JSON.stringify(parsedArray)); 150 }, 151 error: function (error) { 152 console.log("Error fetching wishlist data:", error); 153 } 154 }); 155 } 156 function removeWishlistDataFromMainPage(postID) { 157 158 const article1 = document.querySelector(`article[data-id="${postID}"]`); 159 if (article1) { 160 article1.remove(); 161 } 162 const article2 = document.querySelector(`.custom_tour_carousel_mobile article[data-id="${postID}"]`); 163 if (article2) { 164 article2.remove(); 165 } 166 } 167 168 169 $('.gallery-slider').slick({ 170 slidesToShow: 1, 171 slidesToScroll: 1,
172 arrows: false, 173 dots: false, 174 infinite: true, 175 autoplay:true, 176 speed: 300, 177 adaptiveHeight: true 178 }); 179}); 180jQuery(document).ready(function($) { 181 $('.month-grid-container').hover(function() { 182 $(this).find('.month-grid-tooltip').fadeIn(); 183 }, function() { 184 $(this).find('.month-grid-tooltip').fadeOut(); 185 }); 186 187 188 189 190 $("#hb-slider").owlCarousel({ 191 navigation : false, // Show next and prev buttons 192 dots:false, 193 slideSpeed : 300, 194 paginationSpeed : 400, 195 autoplay:true, 196 autoplayTimeout:7500, 197 smartSpeed:1000, 198 loop:true, 199 items : 1, 200 itemsDesktop : false, 201 itemsDesktopSmall : false, 202 itemsTablet: false, 203 itemsMobile : false 204 205 }); 206 207 $("#close-header-banner").click(function(){ 208 $(".header-banner-slider").hide(); 209 }) 210}); 211 212 213 214 215/*Tanya Ticket Changes*/ 216jQuery(document).ready(function($) { 217 218 $('#tabbing-products .w-grid-item').each(function() { 219 var $item = $(this); 220 221 // Find closest parent with a type-* class 222 var $parent = $item.closest('.type-post, .type-route, .type-camino-tours'); 223 224 if ($parent.hasClass('type-post')) { 225 $item.append('<div class="custom-type-label">Blog Post</div>'); 226 } else if ($parent.hasClass('type-route')) { 227 $item.append('<div class="custom-type-label">Collection</div>'); 228 } else if ($parent.hasClass('type-camino-tours')) { 229 $item.append('<div class="custom-type-label">Product</div>'); 230 } 231 232 }); 233 234 $('.btn-view-more').on('click', function () { 235 const $btn = $(this); 236 const isExpanded = $btn.hasClass('btn-view-less'); 237 238 if (isExpanded) { 239 $('.hide-service-mobile').css('display', 'none'); 240 $btn.removeClass('btn-view-less').text('View More'); 241 } else { 242 $('.hide-service-mobile').css('display', 'flex'); 243 $btn.addClass('btn-view-less').text('View Less'); 244 } 245 }); 246 247 // Toggle route grid items 248 $('.btn-view-more-route').on('click', function () { 249 const $btn = $(this); 250 const isExpanded = $btn.hasClass('btn-view-less-route'); 251 252 if (isExpanded) { 253 // Hide items from 4th onward 254 $('.custom_route_vertical_grid .w-grid-item:nth-child(n+4)').hide(); 255 $btn.removeClass('btn-view-less-route').text('View More'); 256 } else { 257 // Show items using block or flex 258 $('.custom_route_vertical_grid .w-grid-item:nth-child(n+4)').css('display', 'block'); 259 $btn.addClass('btn-view-less-route').text('View Less'); 260 } 261 }); 262 263 264 $('.mb-condense-text').each(function () { 265 const $container = $(this); 266 const $paragraphs = $container.find('p'); 267 268 if ($paragraphs.length === 0) return; 269 270 const firstP = $paragraphs.eq(0); 271 const fullText = firstP.text().trim(); 272 const fullHTML = firstP.html(); 273 const dotIndex = fullText.indexOf('.'); 274 275 if (dotIndex === -1) return; // no full stop 276 277 // Split content at the first dot 278 const visiblePart = fullText.slice(0, dotIndex + 1); // with dot 279 const hiddenPart = fullText.slice(dotIndex + 1).trim(); 280 281 // HTML 282 const visibleHTML = `<p>${visiblePart}</p>`; 283 let hiddenHTML = ''; 284 285 if (hiddenPart.length > 0) { 286 hiddenHTML += `<p>${hiddenPart}</p>`; 287 } 288 289 // Add remaining paragraphs to hidden 290 $paragraphs.slice(1).each(function () { 291 hiddenHTML += $(this).prop('outerHTML'); 292 }); 293 294 // Final structure 295 const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`; 296 const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`; 297 298 $container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`); 299 }); 300 301 // Expand 302 $(document).on('click', '.read-more-btn', function () { 303 const $container = $(this).closest('.mb-condense-text'); 304 $container.find('.hidden-paragraphs').slideDown(); 305 $(this).hide(); 306 }); 307 308 // Collapse 309 $(document).on('click', '.read-less-btn', function () { 310 const $container = $(this).closest('.mb-condense-text');
311 $container.find('.hidden-paragraphs').slideUp(); 312 $container.find('.read-more-btn').show(); 313 }); 314 315 316 //Highlight Section Content 317 $('.custom_highlight_section.custom_highlight_section_mb .wpb_text_column.us_custom_679d515e .wpb_wrapper').each(function () { 318 const $container = $(this); 319 const $paragraphs = $container.find('p'); 320 321 if ($paragraphs.length === 0) return; 322 323 const firstP = $paragraphs.eq(0); 324 const fullText = firstP.text().trim(); 325 const fullHTML = firstP.html(); 326 const dotIndex = fullText.indexOf('.'); 327 328 if (dotIndex === -1) return; // no full stop found 329 330 const visiblePart = fullText.slice(0, dotIndex + 1); // up to and including the first period 331 const hiddenPart = fullText.slice(dotIndex + 1).trim(); 332 333 let visibleHTML = `<p>${visiblePart}</p>`; 334 let hiddenHTML = ''; 335 336 if (hiddenPart.length > 0) { 337 hiddenHTML += `<p>${hiddenPart}</p>`; 338 } 339 340 $paragraphs.slice(1).each(function () { 341 hiddenHTML += $(this).prop('outerHTML'); 342 }); 343 344 const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`; 345 const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`; 346 347 // Set final HTML 348 $container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`); 349 }); 350 351 // Show hidden content 352 $(document).on('click', '.read-more-btn', function () { 353 const $container = $(this).closest('.wpb_wrapper'); 354 $container.find('.hidden-paragraphs').slideDown(); 355 $(this).hide(); 356 }); 357 358 // Hide hidden content 359 $(document).on('click', '.read-less-btn', function () { 360 const $container = $(this).closest('.wpb_wrapper'); 361 $container.find('.hidden-paragraphs').slideUp(); 362 $container.find('.read-more-btn').show(); 363 }); 364 365 366 //Itinerary Section Content 367 368 369 $('.custom_itinerary_layout .mb-itinerary .custom_iti_days_de
369sc .wpb_text_column .wpb_wrapper').each(function () { 370 const $container = $(this); 371 const $paragraphs = $container.find('p'); 372 373 if ($paragraphs.length === 0) return; 374 375 const firstP = $paragraphs.eq(0); 376 const fullText = firstP.text().trim(); 377 const fullHTML = firstP.html(); 378 const dotIndex = fullText.indexOf('.'); 379 380 if (dotIndex === -1) return; // no full stop, don't change 381 382 const visiblePart = fullText.slice(0, dotIndex + 1); // includes the dot 383 const hiddenPart = fullText.slice(dotIndex + 1).trim(); 384 385 let visibleHTML = `<p>${visiblePart}</p>`; 386 let hiddenHTML = ''; 387 388 if (hiddenPart.length > 0) { 389 hiddenHTML += `<p>${hiddenPart}</p>`; 390 } 391 392 // Add the rest of the <p> tags to hidden content 393 $paragraphs.slice(1).each(function () { 394 hiddenHTML += $(this).prop('outerHTML'); 395 }); 396 397 const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`; 398 const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`; 399 400 $container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`); 401 }); 402 403 // Expand hidden content 404 $(document).on('click', '.read-more-btn', function () { 405 const $container = $(this).closest('.wpb_wrapper'); 406 $container.find('.hidden-paragraphs').slideDown(); 407 $(this).hide(); 408 }); 409 410 // Collapse hidden content 411 $(document).on('click', '.read-less-btn', function () { 412 const $container = $(this).closest('.wpb_wrapper'); 413 $container.find('.hidden-paragraphs').slideUp(); 414 $container.find('.read-more-btn').show(); 415 }); 416 417 418 $('.review-quote-slider').on('init', function () { 419 $(this).removeClass('slider-loading'); 420 }); 421 $('.review-quote-slider').slick({ 422 slidesToShow: 1, 423 dots:false, 424 arrows: true, 425 infinite: false, 426 autoplay: true, 427 autoplaySpeed: 8000, 428 prevArrow:"<button type='button' class='slick-prev pull-left'><i class='fa fa-angle-left' aria-hidden='true'></i></button>", 429 nextArrow:"<button type='button' class='slick-next pull-right'><i class='fa fa-angle-right' aria-hidden='true'></i></button>" 430 }); 431 432 433 $('.rating span.w-post-elm-value').each(function () { 434 var text = $(this).text().toLowerCase().trim(); 435 var stars = ''; 436 437 if (text === 'comfort') { 438 stars = '3-Star'; 439 } else if (text === 'budget') { 440 stars = '2-Star'; 441 } else if (text === 'deluxe') { 442 stars = '4-Star'; 443 } 444 445 if (stars !== '') { 446 $(this).text(stars); 447 } 448 }); 449 450 451 452}); 453 454/*Route Template FAQ Accordion*/ 455 456jQuery(document).ready(function($) { 457 458 // Detect language from URL 459 var currentUrl = window.location.href; 460 var isSpanish = currentUrl.indexOf('/es/') !== -1; 461 462 var readMoreText = isSpanish ? "Leer más" : "Read More"; 463 var readLessText = isSpanish ? "Leer menos" : "Read Less"; 464 465 const $faqRows = $('.route_faq .repeater-row'); 466 467 // Append icon to each question 468 $('.route_faq .question').each(function() { 469 $(this).append('<i class="faq-icon far fa-plus"></i>'); 470 }); 471 472 // Open first row 473 //const $first = $faqRows.first(); 474 //$first.addClass('active').find('.answer').slideDown(); 475 //$first.find('.faq-icon').removeClass('fa-plus').addClass('fa-minus'); 476 477 // On click 478 $('.route_faq .question').on('click', function() { 479 const $clickedRow = $(this).closest('.repeater-row'); 480 const $icon = $(this).find('.faq-icon'); 481 482 if (!$clickedRow.hasClass('active')) { 483 // Close all 484 $faqRows.removeClass('active').find('.answer').slideUp(); 485 $('.faq-icon').removeClass('fa-minus').addClass('fa-plus'); 486 487 // Open clicked 488 $clickedRow.addClass('active').find('.answer').slideDown(); 489 $icon.removeClass('fa-plus').addClass('fa-minus'); 490 } else { 491 // Toggle off if already open 492 $clickedRow.removeClass('active').find('.answer').slideUp(); 493 $icon.removeClass('fa-minus').addClass('fa-plus'); 494 } 495 }); 496 497 $("#faq-section-main .faq_content").each(function() { 498 499 500 var $this = $(this); 501 var lineHeight = parseInt($this.css("line-height")) || 24; // fallback 502 var maxLines = 3; 503 var maxHeight = lineHeight * maxLines; 504 505 // Only add Read More if content is taller than 3 lines 506 if ($this[0].scrollHeight > maxHeight) { 507 $this.css({ 508 "max-height": maxHeight, 509 "overflow": "hidden", 510 "transition": "max-height 0.4s ease" 511 }); 512 513 // Add toggle link 514 $this.after('<span class="read-more faq_btn" style="cursor:pointer;color:#0073aa;">'+ readMoreText +'</span>'); 515 516 $(document).on("click", ".read-more", function() { 517 var $btn = $(this); 518 519 if ($this.hasClass("expanded")) { 520 // collapse 521 $this.removeClass("expanded").css("max-height", maxHeight); 522 $btn.text(readMoreText); 523 } else { 524 // expand 525 $this.addClass("expanded").css("max-height", $this[0].scrollHeight); 526 $btn.text(readLessText); 527 } 528 }); 529 } 530 }); 531 532 533 534}); 535/*Route Template FAQ Accordion*/ 536 537/*Route Rating Mobile Slider*/ 538jQuery(document).ready(function($) { 539 function initMobileSlider() { 540 if ($(window).width() <= 991 && !$('.camino-top-head-right .stacking_default').hasClass('slick-initialized')) { 541 $('.camino-top-head-right .stacking_default').slick({ 542 dots: false,
543 arrows: false, 544 infinite: true, 545 autoplay:true, 546 speed: 300, 547 slidesToShow: 2, 548 adaptiveHeight: true 549 }); 550 } else if ($(window).width() > 600 && $('.camino-top-head-right .stacking_default').hasClass('slick-initialized')) { 551 $('.camino-top-head-right .stacking_default').slick('unslick'); 552 } 553 } 554 555 // Initialize on load 556 initMobileSlider(); 557 558 // Recheck on resize 559 $(window).on('resize', function() { 560 initMobileSlider(); 561 }); 562 563 564 565}); 566 567 568jQuery(document).ready(function($) { 569 570$('#map-info-view-tbn').click(function () { 571 $(".map_info_main").css("display", function (i, currentDisplay) { 572 if (currentDisplay === "none") { 573 return "inline-block"; 574 } else { 575 return "none"; 576 } 577 }); 578 579 // Toggle button text 580 let label = $(this).find('.w-btn-label'); 581 let currentText = label.text().trim(); 582 label.text(currentText === 'Read Less' ? 'Read More' : 'Read Less'); 583}); 584 585/*Wrap anchor tag before image gallery*/ 586 587$('#route-gallery-section .w-post-elm.post_image img').each(function() { 588 var img = $(this); 589 var originalSrc = img.attr('src'); 590 591 // Remove size suffix like -225x225 (but keep extension) 592 var fullImgSrc = originalSrc.replace(/-\d+x\d+(?=\.(jpg|jpeg|png|gif|webp))/i, ''); 593 594 // Wrap with anchor tag if not already wrapped 595 if (!img.parent('a.fancybox').length) { 596 img.wrap('<a href="' + fullImgSrc + '" data-fancybox="gallery" class="fancybox"></a>'); 597 } 598 }); 599}); 600 601 602 603 604/*Guided Groups Book Now Script*/ 605jQuery(document).ready(function($) { 606 $('.custom_booking_btn a:not(.enq_now_link)').on('click', function(e) { 607 e.preventDefault(); 608 const $row = $(this).closest('.custom_booking_row'); 609 const tourID = $row.find('.GuidedTourID').val(); 610 const RoutePageID = $('.RoutePageID').val(); 611 612 const date = $row.find('.custom_booking_date').text().trim(); 613 const availability = $row.find('.booking_slot').text().trim(); 614 615 const baseUrl = $(this).attr('href'); 616 const newUrl = baseUrl + 617 '?tour_id=' + encodeURIComponent(tourID) + 618 '&date=' + encodeURIComponent(date) + 619 '&RoutePageID=' + encodeURIComponent(RoutePageID) + 620 '&availability=' + encodeURIComponent(availability); 621 622 // Redirect 623 window.location.href = newUrl; 624 }); 625 626 $('.tour-list-guided').each(function() { 627 const $slider = $(this); 628 629 $slider.on('init', function(event, slick){ 630 // Add margin-right to each slide after Slick initializes 631 $slider.find('.slick-slide').css('margin-right', '20px'); // â Change spacing here 632 }); 633 634 $slider.slick({ 635 slidesToShow: 3, 636 slidesToScroll: 1, 637 infinite: false, 638 arrows: false, 639 dots: false, 640 autoplay: true, 641 responsive: [ 642 { 643 breakpoint: 768, 644 settings: { 645 slidesToShow: 1 646 } 647 } 648 ] 649 }); 650 }); 651 652 /*Travel Agent Testimonial Slider*/ 653 $('.travel_agent_testimonial_list .repeater').slick({ 654 slidesToShow: 1, 655 dots:false, 656 arrows: true, 657 infinite: false, 658 autoplay: false, 659 adaptiveHeight: true, 660 prevArrow:"<button type='button' class='slick-prev pull-left'><i class='fa fa-angle-left' aria-hidden='true'></i></button>", 661 nextArrow:"<button type='button' class='slick-next pull-right'><i class='fa fa-angle-right' aria-hidden='true'></i></button>" 662 }); 663}); 664 665 666 667/*Tour Pages FAQ Script*/ 668jQuery(document).ready(function($) { 669 const $faqWrapper = $('.tour-faq'); 670 const $faqRows = $('.tour-faq-list .repeater-row'); 671 672 // Hide section if no FAQ rows 673 if ($faqRows.length === 0) { 674 $faqWrapper.hide(); 675 return; // Stop script 676 } 677 678 $('.tour-faq-list .question').each(function() { 679 $(this).append('<i class="faq-icon far fa-plus"></i>'); 680 }); 681 682 $('.tour-faq-list .question').on('click', function() { 683 const $clickedRow = $(this).closest('.repeater-row'); 684 const $icon = $(this).find('.faq-icon'); 685 686 if (!$clickedRow.hasClass('active')) { 687 $faqRows.removeClass('active').find('.answer').slideUp(); 688 $('.faq-icon').removeClass('fa-minus').addClass('fa-plus'); 689 690 $clickedRow.addClass('active').find('.answer').slideDown(); 691 $icon.removeClass('fa-plus').addClass('fa-minus'); 692 } else { 693 $clickedRow.removeClass('active').find('.answer').slideUp(); 694 $icon.removeClass('fa-minus').addClass('fa-plus'); 695 } 696 }); 697}); 698/*End Tour Pages FAQ Script*/ 699 700 701 702/*******Customer Area Login Script******/ 703jQuery(document).ready(function($) { 704 const apiLoginUrl = "https://api.followthecamino.com/api/login_check"; 705 const apiUserUrl = "https://api.followthecamino.com/api/user_info"; 706 const tokenKey = "jwtToken"; 707 const tokenUserKey = "jwtUser"; 708 const customerLandingUrl = "https://customer.followthecamino.com/auth/auth-token?token="; 709
710 // Check for existing token â if valid, donât redirect automatically 711 const existingToken = localStorage.getItem(tokenKey); 712 if (existingToken) { 713 validateToken(existingToken); // Only validate, no redirect 714 } 715 716 // Handle login form submit 717 $("#ftcLoginForm").on("submit", function(e) { 718 e.preventDefault(); 719 $("#ftc_message").empty(); 720 $("#ftc_emailError, #ftc_passwordError").empty(); 721 $("#ftc_username, #ftc_password").css("border-color", "#ddd"); 722 723 const username = $("#ftc_username").val().trim(); 724 const password = $("#ftc_password").val().trim(); 725 let isValid = true; 726 727 // Inline validation 728 if (username === "") { 729 $("#ftc_emailError").text("Email is required."); 730 $("#ftc_username").css("border-color", "#e74c3c"); 731 isValid = false; 732 } else if (!validateEmail(username)) { 733 $("#ftc_emailError").text("Please enter a valid email."); 734 $("#ftc_username").css("border-color", "#e74c3c"); 735 isValid = false; 736 } 737 738 if (password === "") { 739 $("#ftc_passwordError").text("Password is required."); 740 $("#ftc_password").css("border-color", "#e74c3c"); 741 isValid = false; 742 } 743 744 if (!isValid) return; 745 746 // AJAX login request 747 $.ajax({ 748 url: apiLoginUrl, 749 type: "POST", 750 contentType: "application/json", 751 data: JSON.stringify({ 752 username: username, 753 password: password 754 }), 755 beforeSend: function() { 756 $("#ftc_message").html("<div style='color:#3498db;padding:8px;'>Logging in...</div>"); 757 }, 758 success: function(response) { 759 // Save token to localStorage 760 localStorage.setItem(tokenKey, response.token); 761 762 $("#ftc_message").html("<div style='color:#2ecc71;padding:8px;border-radius:5px;'>Login successful! Redirecting...</div>"); 763 764 // Redirect once to customer area 765 setTimeout(() => { 766 window.location.href = customerLandingUrl + encodeURIComponent(response.token); 767 }, 1000); 768 }, 769 error: function(xhr) { 770 localStorage.removeItem(tokenKey); 771 localStorage.removeItem(tokenUserKey); 772 $("#ftc_message").html("<div style='color:#e74c3c;padding:8px;border-radius:5px;'>Invalid email or password. Please try again.</div>"); 773 } 774 }); 775 }); 776 777 // Token validation 778 function validateToken(token) { 779 $.ajax({ 780 url: apiUserUrl, 781 type: "GET", 782 headers: { 783 "Authorization": "Bearer " + token, 784 "Content-Type": "application/json" 785 }, 786 success: function(userData) { 787 console.log("User verified:", userData.username); 788 localStorage.setItem(tokenUserKey, userData.firstName); 789 // User token is valid, do nothing (stay on current page) 790 }, 791 error: function() { 792 // Token invalid â clear it 793 localStorage.removeItem(tokenKey); 794 localStorage.removeItem(tokenUserKey); 795 console.log("Token expired or invalid, cleared from storage."); 796 } 797 }); 798 } 799 800 function validateEmail(email) { 801 const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 802 return re.test(String(email).toLowerCase()); 803 } 804 805 //localStorage.removeItem(tokenKey); 806 807}); 808 809 810 811/*Check User Info*/ 812jQuery(document).ready(function($) { 813 const $loginBtn = $('#LoginCustomer'); 814 const $loginBtnMB = $('#MBLoginCustomer'); 815 const token = localStorage.getItem('jwtToken'); 816 817 if (token) { 818 $.ajax({ 819 url: 'https://api.followthecamino.com/api/user_info', 820 type: 'GET', 821 headers: { 822 'Authorization': 'Bearer ' + token 823 }, 824 success: function(response) { 825 /*if (response) { 826 console.log('â Valid token â user info:', response); 827 }*/ 828 if (response && response.username) { 829 // â Valid token 830 $loginBtn 831 .removeClass('customer-login icon_atleft pum-trigger') 832 .addClass('logged-in-user') 833 .data('url', 'https://customer.followthecamino.com/landing'); 834 $loginBtn.find('.w-btn-label').text(response.firstName); 835 836 $loginBtnMB 837 .removeClass('customer-login icon_atleft pum-trigger') 838 .addClass('logged-in-user') 839 .data('url', 'https://customer.followthecamino.com/landing'); 840 $loginBtnMB.find('.w-btn-label').text(response.firstName); 841 } else { 842 // â Invalid response 843 resetLoginButton(); 844 } 845 }, 846 error: function() { 847 // â Token invalid or expired 848 resetLoginButton(); 849 } 850 }); 851 } else { 852 // â No token stored 853 resetLoginButton(); 854 } 855 856 // Helper: revert button back to login state 857 function resetLoginButton() { 858 $loginBtn 859 .addClass('customer-login icon_atleft pum-trigger') 860 .removeClass('logged-in-user') 861 .removeData('url'); 862 $loginBtn.find('.w-btn-label').text('Client Login'); 863 864 $loginBtnMB 865 .addClass('customer-login icon_atleft pum-trigger') 866 .removeClass('logged-in-user') 867 .removeData('url'); 868 $loginBtnMB.find('.w-btn-label').text('Client Login'); 869 } 870 871 $(document).on('click', '.logged-in-user', function () { 872 const url = $(this).data('url'); 873 if (url) { 874 window.location.href = url; 875 } 876 }); 877}); 878/*******Customer Area Login Script END ******/
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.