humanware

js/scripts.js

Feb 27th, 2026
17
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.11 KB | None | 0 0
  1. document.addEventListener("DOMContentLoaded", function () {
  2. const nav = document.getElementById("main-navbar");
  3. if (!nav) return;
  4.  
  5. const threshold = 40;
  6. const hideOffset = 120;
  7. const delta = 6;
  8.  
  9. let lastY = window.scrollY;
  10. let ticking = false;
  11.  
  12. const isMenuOpen = () => {
  13. const collapse = nav.querySelector(".navbar-collapse");
  14. return collapse && collapse.classList.contains("show");
  15. };
  16.  
  17. const onScroll = () => {
  18. const y = window.scrollY;
  19.  
  20. if (y > threshold) nav.classList.add("scrolled");
  21. else nav.classList.remove("scrolled");
  22.  
  23. if (y <= 5) nav.classList.add("at-top");
  24. else nav.classList.remove("at-top");
  25.  
  26. if (isMenuOpen()) {
  27. nav.classList.add("menu-open");
  28. nav.classList.remove("nav-hidden");
  29. lastY = y;
  30. return;
  31. } else {
  32. nav.classList.remove("menu-open");
  33. }
  34.  
  35. const diff = y - lastY;
  36. if (Math.abs(diff) > delta) {
  37. if (diff > 0 && y > hideOffset) nav.classList.add("nav-hidden");
  38. else nav.classList.remove("nav-hidden");
  39. lastY = y;
  40. }
  41. };
  42.  
  43. const onScrollRaf = () => {
  44. if (ticking) return;
  45. ticking = true;
  46. requestAnimationFrame(() => {
  47. onScroll();
  48. ticking = false;
  49. });
  50. };
  51.  
  52. onScroll();
  53. window.addEventListener("scroll", onScrollRaf, { passive: true });
  54.  
  55. nav.addEventListener("shown.bs.collapse", () => {
  56. nav.classList.add("menu-open");
  57. nav.classList.remove("nav-hidden");
  58. });
  59.  
  60. nav.addEventListener("hidden.bs.collapse", () => {
  61. nav.classList.remove("menu-open");
  62. });
  63.  
  64. if (typeof bootstrap === "undefined") return;
  65.  
  66. const DESKTOP = window.matchMedia("(min-width: 992px)");
  67.  
  68. function bindHoverDropdowns() {
  69. document.querySelectorAll(".navbar .dropdown").forEach(function (dd) {
  70. const toggle = dd.querySelector(":scope > a.dropdown-toggle");
  71. const menu = dd.querySelector(":scope > .dropdown-menu");
  72. if (!toggle || !menu) return;
  73.  
  74. const instance = bootstrap.Dropdown.getOrCreateInstance(toggle, { autoClose: "outside" });
  75.  
  76. let hideTimer = null;
  77.  
  78. const showNow = () => {
  79. if (!DESKTOP.matches) return;
  80. if (hideTimer) clearTimeout(hideTimer);
  81. instance.show();
  82. };
  83.  
  84. const hideLater = () => {
  85. if (!DESKTOP.matches) return;
  86. if (hideTimer) clearTimeout(hideTimer);
  87. hideTimer = setTimeout(() => instance.hide(), 180);
  88. };
  89.  
  90. dd.addEventListener("mouseenter", showNow);
  91. dd.addEventListener("mouseleave", hideLater);
  92. menu.addEventListener("mouseenter", showNow);
  93. menu.addEventListener("mouseleave", hideLater);
  94.  
  95. toggle.addEventListener("click", function (e) {
  96. if (!DESKTOP.matches) return;
  97.  
  98. const href = toggle.getAttribute("href");
  99. if (href && href !== "#") {
  100. e.preventDefault();
  101. window.location.href = href;
  102. } else {
  103. e.preventDefault();
  104. instance.toggle();
  105. }
  106. });
  107.  
  108. dd.addEventListener("hide.bs.dropdown", function () {
  109. dd.querySelectorAll(".dropdown-menu.show").forEach(function (m) {
  110. m.classList.remove("show");
  111. });
  112. dd.querySelectorAll(".nht-submenu-toggle[aria-expanded='true']").forEach(function (b) {
  113. b.setAttribute("aria-expanded", "false");
  114. });
  115. });
  116. });
  117. }
  118.  
  119. bindHoverDropdowns();
  120.  
  121. document.querySelectorAll(".navbar .dropdown-submenu").forEach(function (li) {
  122. if (li.classList.contains("no-submenu-toggle")) return;
  123. const link = li.querySelector(":scope > a.dropdown-toggle");
  124. const sub = li.querySelector(":scope > .dropdown-menu");
  125. if (!link || !sub) return;
  126. let btn = li.querySelector(":scope > button.nht-submenu-toggle");
  127. if (!btn) {
  128. btn = document.createElement("button");
  129. btn.type = "button";
  130. btn.className = "nht-submenu-toggle";
  131. btn.setAttribute("aria-label", "Open submenu");
  132. btn.setAttribute("aria-expanded", "false");
  133. li.insertBefore(btn, sub);
  134. }
  135.  
  136. const closeSiblings = () => {
  137. const parentMenu = li.closest(".dropdown-menu");
  138. if (!parentMenu) return;
  139.  
  140. parentMenu.querySelectorAll(":scope > li > .dropdown-menu.show").forEach(function (openMenu) {
  141. if (openMenu !== sub) {
  142. openMenu.classList.remove("show");
  143. const ownerBtn = openMenu.parentElement.querySelector(":scope > button.nht-submenu-toggle");
  144. if (ownerBtn) ownerBtn.setAttribute("aria-expanded", "false");
  145. }
  146. });
  147. };
  148. const openSub = () => {
  149. closeSiblings();
  150. sub.classList.add("show");
  151. btn.setAttribute("aria-expanded", "true");
  152. };
  153.  
  154. const closeSub = () => {
  155. sub.classList.remove("show");
  156. btn.setAttribute("aria-expanded", "false");
  157. };
  158.  
  159. const toggleSub = () => {
  160. if (sub.classList.contains("show")) closeSub();
  161. else openSub();
  162. };
  163.  
  164. btn.addEventListener("click", function (e) {
  165. e.preventDefault();
  166. e.stopPropagation();
  167. toggleSub();
  168. });
  169.  
  170. li.addEventListener("mouseenter", function () {
  171. if (!DESKTOP.matches) return;
  172. openSub();
  173. });
  174.  
  175. li.addEventListener("mouseleave", function () {
  176. if (!DESKTOP.matches) return;
  177. closeSub();
  178. });
  179. });
  180.  
  181. document.querySelectorAll(".dropdown-menu.nht-mega").forEach(function (mega) {
  182. const tabs = mega.querySelectorAll(".nht-mega__tab");
  183. const panels = mega.querySelectorAll(".nht-mega__panel");
  184. if (!tabs.length || !panels.length) return;
  185.  
  186. const activate = (key) => {
  187. tabs.forEach((t) => t.classList.remove("is-active"));
  188. panels.forEach((p) => p.classList.remove("is-active"));
  189.  
  190. const tab = mega.querySelector('.nht-mega__tab[data-mega-tab="' + key + '"]');
  191. const panel = mega.querySelector('.nht-mega__panel[data-mega-panel="' + key + '"]');
  192.  
  193. if (tab) tab.classList.add("is-active");
  194. if (panel) panel.classList.add("is-active");
  195. };
  196.  
  197. tabs.forEach(function (tab) {
  198. const key = tab.getAttribute("data-mega-tab");
  199. tab.addEventListener("mouseenter", function () {
  200. if (!DESKTOP.matches) return;
  201. if (mega.querySelector('.nht-mega__panel[data-mega-panel="' + key + '"]')) activate(key);
  202. });
  203.  
  204. tab.addEventListener("click", function (e) {
  205. e.preventDefault();
  206. e.stopPropagation();
  207. if (mega.querySelector('.nht-mega__panel[data-mega-panel="' + key + '"]')) activate(key);
  208. });
  209. });
  210. });
  211.  
  212. const root = document.querySelector(".pkg-page");
  213. if (!root) return;
  214.  
  215. const postId = root.getAttribute("data-post-id");
  216. const msg = document.getElementById("pkgActionMsg");
  217.  
  218. const showMsg = (t) => {
  219. if (!msg) return;
  220. msg.textContent = t;
  221. window.clearTimeout(showMsg._t);
  222. showMsg._t = window.setTimeout(() => (msg.textContent = ""), 2400);
  223. };
  224.  
  225. const shareBtn = document.getElementById("pkgShareBtn");
  226. const url = window.location.href;
  227.  
  228. if (shareBtn) {
  229. shareBtn.addEventListener("click", async () => {
  230. try {
  231. if (navigator.share) {
  232. await navigator.share({ title: document.title, url });
  233. showMsg("Shared successfully.");
  234. return;
  235. }
  236. await navigator.clipboard.writeText(url);
  237. showMsg("Link copied to clipboard.");
  238. } catch (e) {
  239. try {
  240. await navigator.clipboard.writeText(url);
  241. showMsg("Link copied to clipboard.");
  242. } catch (err) {
  243. showMsg("Unable to share. Please copy the URL manually.");
  244. }
  245. }
  246. });
  247. }
  248.  
  249. const wishBtn = document.getElementById("pkgWishlistBtn");
  250. const storageKey = "pkg_wishlist_ids";
  251.  
  252. const getList = () => {
  253. try {
  254. return JSON.parse(localStorage.getItem(storageKey) || "[]");
  255. } catch (e) {
  256. return [];
  257. }
  258. };
  259.  
  260. const setList = (arr) => localStorage.setItem(storageKey, JSON.stringify(arr));
  261.  
  262. const setWishUI = (active) => {
  263. if (!wishBtn) return;
  264. wishBtn.classList.toggle("is-active", active);
  265. wishBtn.setAttribute("aria-pressed", active ? "true" : "false");
  266.  
  267. const icon = wishBtn.querySelector("i");
  268. if (icon) {
  269. icon.classList.toggle("fa-regular", !active);
  270. icon.classList.toggle("fa-solid", active);
  271. icon.classList.toggle("fa-heart", true);
  272. }
  273. showMsg(active ? "Added to wishlist." : "Removed from wishlist.");
  274. };
  275.  
  276. if (wishBtn && postId) {
  277. const list = getList();
  278. setWishUI(list.includes(postId));
  279.  
  280. wishBtn.addEventListener("click", () => {
  281. const listNow = getList();
  282. const idx = listNow.indexOf(postId);
  283. if (idx >= 0) listNow.splice(idx, 1);
  284. else listNow.push(postId);
  285. setList(listNow);
  286. setWishUI(listNow.includes(postId));
  287. });
  288. }
  289.  
  290. const items = Array.from(document.querySelectorAll(".pkg-gallery-item"));
  291. const modalEl = document.getElementById("pkgGalleryModal");
  292. const imgEl = document.getElementById("pkgGalleryImg");
  293. const prevBtn = document.getElementById("pkgPrevImg");
  294. const nextBtn = document.getElementById("pkgNextImg");
  295.  
  296. let index = 0;
  297. let modal = null;
  298.  
  299. const openAt = (i) => {
  300. if (!items.length || !modalEl || !imgEl) return;
  301. index = i;
  302.  
  303. const full = items[index].dataset.full || "";
  304. const alt = items[index].dataset.alt || "";
  305. imgEl.src = full;
  306. imgEl.alt = alt;
  307.  
  308. if (!modal) modal = new bootstrap.Modal(modalEl);
  309. modal.show();
  310. };
  311.  
  312. items.forEach((a, i) => {
  313. a.addEventListener("click", (e) => {
  314. e.preventDefault();
  315. openAt(i);
  316. });
  317. });
  318.  
  319. if (prevBtn) prevBtn.addEventListener("click", () => openAt((index - 1 + items.length) % items.length));
  320. if (nextBtn) nextBtn.addEventListener("click", () => openAt((index + 1) % items.length));
  321.  
  322. document.querySelectorAll(".price-counter").forEach((counter) => {
  323. const updateCount = () => {
  324. const target = Number(counter.getAttribute("data-target")) || 0;
  325. const count = Number(counter.innerText) || 0;
  326. const increment = target / 100;
  327.  
  328. if (count < target) {
  329. counter.innerText = String(Math.ceil(count + increment));
  330. setTimeout(updateCount, 20);
  331. } else {
  332. counter.innerText = String(target);
  333. }
  334. };
  335. updateCount();
  336. });
  337.  
  338. document.querySelectorAll(".counter-number").forEach((counter) => {
  339. const updateCounter = () => {
  340. const target = Number(counter.getAttribute("data-target")) || 0;
  341. const count = Number(counter.innerText) || 0;
  342. const increment = target / 200;
  343.  
  344. if (count < target) {
  345. counter.innerText = String(Math.ceil(count + increment));
  346. setTimeout(updateCounter, 15);
  347. } else {
  348. counter.innerText = String(target);
  349. }
  350. };
  351. updateCounter();
  352. });
  353.  
  354. const featureButtons = document.querySelectorAll("#activities .btn");
  355. const featureCards = document.querySelectorAll("#activities .activity-card");
  356.  
  357. if (featureButtons.length && featureCards.length) {
  358. featureButtons.forEach((btn) => {
  359. btn.addEventListener("click", () => {
  360. featureButtons.forEach((b) => b.classList.remove("active"));
  361. btn.classList.add("active");
  362. const target = btn.getAttribute("data-target");
  363.  
  364. featureCards.forEach((card) => {
  365. if (target === "all") card.style.display = "block";
  366. else card.style.display = card.classList.contains(target) ? "block" : "none";
  367. });
  368. });
  369. });
  370. }
  371.  
  372. const featureFeatureButtons = document.querySelectorAll("#activities-feature .btn");
  373. const featureFeatureCards = document.querySelectorAll("#activities-feature .activity-card");
  374. const btnRow = document.querySelector("#activities-feature .filter-row");
  375.  
  376. if (featureFeatureButtons.length && featureFeatureCards.length && btnRow) {
  377. featureFeatureButtons.forEach((btn) => {
  378. btn.addEventListener("click", () => {
  379. if (btn.dataset.dragged === "true") {
  380. btn.dataset.dragged = "false";
  381. return;
  382. }
  383.  
  384. featureFeatureButtons.forEach((b) => b.classList.remove("active"));
  385. btn.classList.add("active");
  386. const target = btn.getAttribute("data-target");
  387.  
  388. featureFeatureCards.forEach((card) => {
  389. if (target === "all") card.style.display = "block";
  390. else card.style.display = card.classList.contains(target) ? "block" : "none";
  391. });
  392. });
  393. });
  394.  
  395. let isDraggingBtn = false;
  396. let startXBtn = 0;
  397. let scrollStartBtn = 0;
  398.  
  399. btnRow.addEventListener("pointerdown", (e) => {
  400. isDraggingBtn = true;
  401. startXBtn = e.clientX;
  402. scrollStartBtn = btnRow.scrollLeft;
  403. btnRow.setPointerCapture?.(e.pointerId);
  404. });
  405.  
  406. btnRow.addEventListener("pointermove", (e) => {
  407. if (!isDraggingBtn) return;
  408.  
  409. const walk = e.clientX - startXBtn;
  410. if (Math.abs(walk) > 5) {
  411. btnRow.scrollLeft = scrollStartBtn - walk * 2;
  412. featureFeatureButtons.forEach((b) => (b.dataset.dragged = "true"));
  413. }
  414. });
  415.  
  416. const stopBtnDrag = () => (isDraggingBtn = false);
  417. btnRow.addEventListener("pointerup", stopBtnDrag);
  418. btnRow.addEventListener("pointercancel", stopBtnDrag);
  419. btnRow.addEventListener("pointerleave", stopBtnDrag);
  420. }
  421.  
  422. const slider = document.getElementById("activities-slider");
  423. if (slider) {
  424. let isDown = false;
  425. let startX = 0;
  426. let scrollLeft = 0;
  427.  
  428. slider.addEventListener("mousedown", (e) => {
  429. isDown = true;
  430. slider.classList.add("active");
  431. startX = e.pageX - slider.offsetLeft;
  432. scrollLeft = slider.scrollLeft;
  433. });
  434.  
  435. ["mouseleave", "mouseup"].forEach((evt) => {
  436. slider.addEventListener(evt, () => {
  437. isDown = false;
  438. slider.classList.remove("active");
  439. });
  440. });
  441.  
  442. slider.addEventListener("mousemove", (e) => {
  443. if (!isDown) return;
  444. e.preventDefault();
  445. const x = e.pageX - slider.offsetLeft;
  446. const walk = (x - startX) * 2;
  447. slider.scrollLeft = scrollLeft - walk;
  448. });
  449. }
  450.  
  451. const scrollElements = document.querySelectorAll(
  452. "small, h1, h2, h3, h4, h5, h6, p, a, .search-form, .gallery-img-other, .gallery-img-center, .service-card, .feature-card, .right-content img, #activities-images img, .blog-card, #testimonial-section img"
  453. );
  454.  
  455. scrollElements.forEach((el) => {
  456. el.classList.add("animate-on-scroll");
  457.  
  458. if (
  459. el.classList.contains("search-form") ||
  460. el.classList.contains("gallery-img-other") ||
  461. el.classList.contains("gallery-img-center") ||
  462. el.classList.contains("service-card") ||
  463. el.classList.contains("feature-card") ||
  464. el.classList.contains("blog-card") ||
  465. el.closest(".right-content") ||
  466. el.closest("#activities-images") ||
  467. el.closest("#testimonial-section")
  468. ) {
  469. el.classList.add("zoom-on-scroll");
  470. }
  471.  
  472. if (el.tagName === "SMALL") el.classList.add("fade-in");
  473. if (/^H[1-6]$/.test(el.tagName)) el.classList.add("slide-up");
  474. if (el.tagName === "P") el.classList.add("fade-in-up");
  475. if (el.tagName === "A") el.classList.add("fade-in-link");
  476. });
  477.  
  478. if (scrollElements.length) {
  479. const observer = new IntersectionObserver(
  480. (entries) => {
  481. entries.forEach((entry) => {
  482. if (entry.isIntersecting) entry.target.classList.add("in-view");
  483. else entry.target.classList.remove("in-view");
  484. });
  485. },
  486. { threshold: 0.1 }
  487. );
  488.  
  489. scrollElements.forEach((el) => observer.observe(el));
  490. }
  491.  
  492. const carousel = document.getElementById("testimonial-carousel");
  493. if (carousel) {
  494. let offset = 0;
  495. const firstCard = carousel.querySelector(".col-md-4");
  496. if (firstCard) {
  497. const cardWidth = firstCard.offsetWidth + 16;
  498. let interval;
  499. let isPaused = false;
  500.  
  501. function slideCarousel() {
  502. offset -= cardWidth;
  503. carousel.style.transform = `translateX(${offset}px)`;
  504.  
  505. setTimeout(() => {
  506. carousel.appendChild(carousel.firstElementChild);
  507. offset += cardWidth;
  508. carousel.style.transition = "none";
  509. carousel.style.transform = `translateX(${offset}px)`;
  510. carousel.offsetHeight;
  511. carousel.style.transition = "transform 0.6s ease-in-out";
  512. }, 600);
  513. }
  514.  
  515. function startCarousel() {
  516. interval = setInterval(slideCarousel, 3000);
  517. }
  518. function stopCarousel() {
  519. clearInterval(interval);
  520. }
  521.  
  522. startCarousel();
  523.  
  524. carousel.addEventListener("click", () => {
  525. if (isPaused) return;
  526. stopCarousel();
  527. isPaused = true;
  528. setTimeout(() => {
  529. startCarousel();
  530. isPaused = false;
  531. }, 5000);
  532. });
  533. }
  534. }
  535.  
  536. document.querySelectorAll(".faq-item").forEach((item) => {
  537. const toggle = item.querySelector(".faq-toggle");
  538. const title = item.querySelector(".faq-title") || item.querySelector(".faq-question");
  539. if (!title || !toggle) return;
  540.  
  541. title.addEventListener("click", () => {
  542. item.classList.toggle("active");
  543. toggle.textContent = item.classList.contains("active") ? "–" : "+";
  544. });
  545. });
  546.  
  547. const cardsWrapper = document.getElementById("left-cards-wrapper");
  548. if (cardsWrapper) {
  549. const allCards = Array.from(cardsWrapper.getElementsByClassName("blog-card"));
  550. const cardsPerPage = 5;
  551. const totalPages = Math.ceil(allCards.length / cardsPerPage);
  552. const paginationWrapper = document.getElementById("blog-pagination");
  553.  
  554. if (paginationWrapper && totalPages > 1) {
  555. paginationWrapper.innerHTML = "";
  556.  
  557. for (let i = 1; i <= totalPages; i++) {
  558. const li = document.createElement("li");
  559. li.className = "page-item";
  560. li.innerHTML = `<a class="page-link" href="#" data-page="${i}">${i}</a>`;
  561. paginationWrapper.appendChild(li);
  562. }
  563.  
  564. const showPage = (page) => {
  565. allCards.forEach((card, idx) => {
  566. const start = (page - 1) * cardsPerPage;
  567. const end = page * cardsPerPage;
  568. if (idx >= start && idx < end) card.classList.add("active");
  569. else card.classList.remove("active");
  570. });
  571. };
  572.  
  573. showPage(1);
  574.  
  575. paginationWrapper.querySelectorAll(".page-link").forEach((link) => {
  576. link.addEventListener("click", (e) => {
  577. e.preventDefault();
  578. showPage(parseInt(link.dataset.page, 10));
  579. });
  580. });
  581. } else {
  582. allCards.forEach((card) => card.classList.add("active"));
  583. }
  584. }
  585. });
  586.  
  587. /* ====================================================================
  588. TOP CONTACT BAR
  589. ==================================================================== */
  590. document.addEventListener("DOMContentLoaded", function() {
  591. const dropdown = document.getElementById("nhtCountryDropdown");
  592. const phoneLink = document.getElementById("phoneLink");
  593.  
  594. if (!dropdown) return;
  595. dropdown.addEventListener("click", function(e) {
  596. e.stopPropagation();
  597. this.classList.toggle("is-active");
  598. });
  599.  
  600. document.addEventListener("click", function() {
  601. dropdown.classList.remove("is-active");
  602. });
  603.  
  604. const options = dropdown.querySelectorAll(".nht-option");
  605. options.forEach(option => {
  606. option.addEventListener("click", function() {
  607. const country = this.getAttribute("data-country");
  608. const phone = this.getAttribute("data-phone");
  609. const flag = this.querySelector("img").src;
  610.  
  611. dropdown.querySelector(".nht-selected-name").textContent = country;
  612. dropdown.querySelector(".nht-current-flag").src = flag;
  613.  
  614. if (phoneLink) {
  615. phoneLink.textContent = phone;
  616. phoneLink.href = "tel:" + phone.replace(/\s+/g, "");
  617. }
  618. });
  619. });
  620. });
  621. jQuery(document).ready(function($) {
  622. $('.pkg-enquiry-form').on('submit', function(e) {
  623. e.preventDefault();
  624.  
  625. var $form = $(this);
  626. var $btn = $form.find('button[type="submit"]');
  627. var $alert = $form.find('.alert');
  628.  
  629. $alert?.remove();
  630.  
  631. $btn.prop('disabled', true).text('Sending...');
  632.  
  633. // Prepare AJAX data
  634. var data = {
  635. action: 'pkg_enquiry',
  636. pkg_enquiry_nonce: nht_ajax_obj.enquiry_nonce,
  637. name: $form.find('input[name="name"]').val(),
  638. email: $form.find('input[name="email"]').val(),
  639. phone: $form.find('input[name="phone"]').val(),
  640. country: $form.find('input[name="country"]').val(),
  641. message: $form.find('textarea[name="message"]').val(),
  642. package_id: $form.find('input[name="package_id"]').val(),
  643. package_title: $form.find('input[name="package_title"]').val()
  644. };
  645.  
  646. // Send AJAX request
  647. $.ajax({
  648. url: nht_ajax_obj.ajax_url,
  649. method: 'POST',
  650. data: data,
  651. dataType: 'json',
  652. success: function(response) {
  653. console.log('AJAX response:', response);
  654.  
  655. if (response.success) {
  656. $form.prepend('<div class="alert alert-success small">'+response.data.message+'</div>');
  657. $form[0].reset();
  658. } else {
  659. $form.prepend('<div class="alert alert-danger small">'+response.data.message+'</div>');
  660. }
  661. },
  662. error: function(xhr, status, error) {
  663. console.error('AJAX error:', status, error, xhr.responseText);
  664. $form.prepend('<div class="alert alert-danger small">Something went wrong. Try again.</div>');
  665. },
  666. complete: function() {
  667. $btn.prop('disabled', false).text('Send enquiry');
  668. }
  669. });
  670. });
  671. });
  672.  
  673. jQuery(document).ready(function ($) {
  674.  
  675. /* =====================================================
  676. * FRONTEND BOOKING FORM SUBMISSION
  677. ====================================================== */
  678. $('#nhtBookingForm').on('submit', function (e) {
  679. e.preventDefault();
  680.  
  681. var form = $(this);
  682. var submitBtn = form.find('button[type="submit"]');
  683. var messageDiv = $('#booking-message');
  684.  
  685. // --- FRONTEND VALIDATION ---
  686. var travelDate = form.find('input[name="travel_date"]').val();
  687. var pax = parseInt(form.find('input[name="pax"]').val(), 10);
  688. var name = form.find('input[name="name"]').val().trim();
  689. var email = form.find('input[name="email"]').val().trim();
  690. var phone = form.find('input[name="phone"]').val().trim();
  691.  
  692. if (!name || !email || !phone || !travelDate || pax < 1) {
  693. messageDiv.html('<div class="alert alert-danger">Please fill all required fields correctly.</div>');
  694. return;
  695. }
  696.  
  697. var today = new Date();
  698. today.setHours(0,0,0,0);
  699. var selectedDate = new Date(travelDate);
  700. if (selectedDate < today) {
  701. messageDiv.html('<div class="alert alert-danger">Travel date cannot be in the past.</div>');
  702. return;
  703. }
  704. // --- END FRONTEND VALIDATION ---
  705.  
  706. var formData = form.serialize(); // keep the original ajax logic
  707. submitBtn.prop('disabled', true).text('Processing...');
  708.  
  709. $.ajax({
  710. url: nht_ajax_obj.ajax_url,
  711. type: 'POST',
  712. data: formData,
  713. dataType: 'json',
  714. success: function (response) {
  715. if (response.success) {
  716. messageDiv.html('<div class="alert alert-success">' + response.data.message + '</div>');
  717. form[0].reset();
  718.  
  719. // Optional: Close modal after 2 seconds
  720. setTimeout(function() {
  721. $('#pkgBookTripModal').modal('hide');
  722. messageDiv.html('');
  723. }, 2000);
  724. } else {
  725. messageDiv.html('<div class="alert alert-danger">' + response.data + '</div>');
  726. }
  727. },
  728. error: function () {
  729. messageDiv.html('<div class="alert alert-danger">Something went wrong. Please try again.</div>');
  730. },
  731. complete: function () {
  732. submitBtn.prop('disabled', false).text('Confirm Booking');
  733. }
  734. });
  735. });
  736.  
  737. /* =====================================================
  738. * ADMIN BOOKING STATUS UPDATE
  739. ====================================================== */
  740. $('.nht-booking-action').on('click', function () {
  741. var btn = $(this);
  742. var post_id = btn.data('id');
  743. var status = btn.data('status');
  744.  
  745. btn.prop('disabled', true).text('Updating...');
  746.  
  747. $.ajax({
  748. url: nht_ajax_obj.ajax_url,
  749. type: 'POST',
  750. data: {
  751. action: 'nht_update_booking_status',
  752. nonce: nht_ajax_obj.nonce,
  753. post_id: post_id,
  754. status: status
  755. },
  756. dataType: 'json',
  757. success: function (response) {
  758. if (response.success) {
  759. var statusSpan = btn.closest('tr').find('.nht-status-pill');
  760. statusSpan.text(response.data.status);
  761.  
  762. var colors = {
  763. 'Pending': '#e67e22',
  764. 'Approved': '#27ae60',
  765. 'Rejected': '#c0392b'
  766. };
  767. statusSpan.css('background', colors[response.data.status] || '#7f8c8d');
  768.  
  769. btn.remove();
  770. } else {
  771. alert(response.data || 'Failed to update status.');
  772. btn.prop('disabled', false).text(btn.data('status').charAt(0).toUpperCase() + btn.data('status').slice(1));
  773. }
  774. },
  775. error: function () {
  776. alert('Something went wrong. Please try again.');
  777. btn.prop('disabled', false).text(btn.data('status').charAt(0).toUpperCase() + btn.data('status').slice(1));
  778. }
  779. });
  780. });
  781.  
  782. });
  783.  
Advertisement
Add Comment
Please, Sign In to add comment