'use strict';
import {
global_unavailableDates,
getDayOfWeek,
getPostId,
insertRating,
getNonce,
getNameLanguage,
filterResponse,
updateStorage,
fixedDate,
fixedMonth,
useGeocode
} from './functions.js';
const {__, _x, _n, _nx} = wp.i18n;
const $ = jQuery;
// We'll define these globally just in case your code references them:
let $dateLoad = $('.aside-date-loaded');
let $asideDate = $('.aside-date');
/**
* 1) FETCH AVAILABILITY
*/
const getAvailability = async (id) => {
console.log('[DEBUG] getAvailability() called with ID:', id);
try {
let availabilityResponse = await axios({
method: 'POST',
data: new URLSearchParams({
action: 'availability_product',
id: id,
targetCurrency: localStorage.getItem('viatorCurrency') ?? viatorParameters?.default_currency,
}),
url: '/wp-admin/admin-ajax.php',
});
let availability = filterResponse(availabilityResponse);
console.log('[DEBUG] Availability raw data:', availability);
// Hide the date loader, show the calendar date
$dateLoad.addClass('d-none');
$asideDate.removeClass('d-none');
return {
items: availability.bookableItems,
originalCurrency: availability.currency,
rates: availability.rates
};
} catch (err) {
console.error('[ERROR] getAvailability failed:', err);
return {
items: [],
originalCurrency: null,
rates: []
};
}
};
/**
* 2) INSERT PRODUCT INTO DOM
*/
const insertProduct = ($wrapper, product) => {
console.log('[DEBUG] insertProduct() called. Product code =', product?.productCode, product);
// 2.1) Find the .excursion__content inside $wrapper
const $cntBlock = $wrapper.querySelector('.excursion__content');
if (!$cntBlock) {
console.warn('[DEBUG] No .excursion__content found inside the wrapper, cannot insert product details.');
return;
}
// For debugging, let’s empty the .excursion__content first
$cntBlock.innerHTML = '';
/********************
* PRODUCT IMAGES
********************/
const productSlider = () => {
if (!product?.images?.length) {
console.log('[DEBUG] No images found in product');
return;
}
console.log('[DEBUG] Found images, constructing slider...');
// Just a small example:
// You’d normally have wp.template('productSlider') or something.
const $sliderBox = document.createElement('div');
$sliderBox.classList.add('excursion__slider');
let html = `
`;
for (const imgObj of product.images) {
// We pick the first variant or largest
if (!imgObj.variants?.length) continue;
let bestVariant = imgObj.variants[0];
html += `
`;
}
html += `
`;
// Insert
$sliderBox.innerHTML = html;
$cntBlock.appendChild($sliderBox);
// If you have existing slick() init calls:
// jQuery('.excursion__slider-main').slick({ ... });
};
/********************
* PRODUCT TAGS
********************/
const tags = () => {
// If product.tags is an array of IDs or strings, we can just do:
if (!product?.tags?.length) {
console.log('[DEBUG] No tags found in product');
return;
}
console.log('[DEBUG] Found tags:', product.tags);
const $tagsWrap = document.createElement('div');
$tagsWrap.classList.add('d-flex', 'tags');
product.tags.forEach((tg) => {
let $tgItem = document.createElement('div');
$tgItem.classList.add('tags__item');
$tgItem.textContent = tg; // or do your localized name
$tagsWrap.appendChild($tgItem);
});
$cntBlock.appendChild($tagsWrap);
};
/********************
* PRODUCT TITLE
********************/
const title = () => {
const $h2 = document.createElement('h2');
$h2.classList.add('excursion__title');
$h2.textContent = product?.title || '';
$cntBlock.appendChild($h2);
// Also update
document.title = product?.title || document.title;
};
/********************
* INFO BOX:
* - Reviews
* - Duration
* - Free cancellation
* - Pickup offered
* - Ticket type
********************/
const buildInfoBox = () => {
let $infoBox = document.createElement('div');
$infoBox.classList.add('row','align-items-center','excursion__info');
// 1) REVIEWS
if (product?.reviews) {
let $colLeft = document.createElement('div');
$colLeft.classList.add('col-xl-5','col-sm-6','excursion__info-wrap','reviews-block');
let $rev = document.createElement('div');
$rev.classList.add('review');
// Star rating
insertRating($rev, Math.round(product.reviews.combinedAverageRating));
// Number of reviews
let $txt = document.createElement('span');
$txt.classList.add('review__text');
$txt.textContent = product.reviews.totalReviews + ' ' + __('reviews','viator');
$rev.appendChild($txt);
$colLeft.appendChild($rev);
$infoBox.appendChild($colLeft);
}
// 2) DURATION
// we check both fixedDurationInMinutes or variableDurationFrom/toMinutes
const getDurationInHours = () => {
if (!product?.itinerary?.duration) return null;
if (product.itinerary.duration.fixedDurationInMinutes) {
return (product.itinerary.duration.fixedDurationInMinutes / 60).toFixed(2);
} else if (product.itinerary.duration.variableDurationToMinutes) {
return (product.itinerary.duration.variableDurationToMinutes / 60).toFixed(2);
}
return null;
};
let durationHours = getDurationInHours();
if (durationHours) {
let $colDur = document.createElement('div');
$colDur.classList.add('col-xl-5','col-sm-6','excursion__info-wrap');
let $durBlock = document.createElement('div');
$durBlock.classList.add('d-flex','align-items-center','excursion__info-item','excursion-grey');
let $span = document.createElement('span');
$span.classList.add('excursion__duration-icon');
let $img = document.createElement('img');
$img.src = `${window.location.origin}/wp-content/plugins/viator/assets/img/icons/icon-clock.svg`;
$img.alt = 'clock';
$span.appendChild($img);
$durBlock.appendChild($span);
$durBlock.appendChild(document.createTextNode(` ${durationHours} ${__('hours','viator')}`));
$colDur.appendChild($durBlock);
$infoBox.appendChild($colDur);
}
// 3) FREE CANCELLATION
if (product?.cancellationPolicy?.type !== 'ALL_SALES_FINAL') {
let $colCancel = document.createElement('div');
$colCancel.classList.add('col-xl-5','col-sm-6','excursion__info-wrap');
let $cancelBlock = document.createElement('div');
$cancelBlock.classList.add('d-flex','align-items-center','excursion__info-item','excursion-green');
$cancelBlock.setAttribute('title', __('Up to 24 hours in advance.', 'viator'));
let $spanC = document.createElement('span');
$spanC.classList.add('excursion__cancellation-icon');
let $imgC = document.createElement('img');
$imgC.src = `${window.location.origin}/wp-content/plugins/viator/assets/img/icons/icon-check.svg`;
$imgC.alt = 'check';
$spanC.appendChild($imgC);
$cancelBlock.appendChild($spanC);
$cancelBlock.appendChild(document.createTextNode(__('Free cancellation','viator')));
$colCancel.appendChild($cancelBlock);
$infoBox.appendChild($colCancel);
}
// 4) PICKUP
if (product?.logistics?.travelerPickup?.pickupOptionType === 'PICKUP_EVERYONE' ||
product?.logistics?.travelerPickup?.pickupOptionType === 'PICKUP_AND_MEET_AT_START_POINT') {
let $colPick = document.createElement('div');
$colPick.classList.add('col-xl-5','col-sm-6','excursion__info-wrap');
let $pickup = document.createElement('div');
$pickup.classList.add('d-flex','align-items-center','excursion__info-item','excursion-green');
let $spanP = document.createElement('span');
$spanP.classList.add('excursion__cancellation-icon');
let $imgP = document.createElement('img');
$imgP.src = `${window.location.origin}/wp-content/plugins/viator/assets/img/icons/icon-check.svg`;
$imgP.alt = 'check';
$spanP.appendChild($imgP);
$pickup.appendChild($spanP);
$pickup.appendChild(document.createTextNode(__('Pickup offered','viator')));
$colPick.appendChild($pickup);
$infoBox.appendChild($colPick);
}
// 5) TICKET TYPE
if (product?.ticketInfo?.ticketTypes?.length) {
let $typeArr = product.ticketInfo.ticketTypes;
let ticketTxt = __('Mobile ticket','viator');
if ($typeArr.length > 1) {
ticketTxt = __('Mobile and paper ticket','viator');
} else if ($typeArr[0] === 'PAPER') {
ticketTxt = __('Paper ticket','viator');
}
let $colTick = document.createElement('div');
$colTick.classList.add('col-xl-5','col-sm-6','excursion__info-wrap');
let $tickBlock = document.createElement('div');
$tickBlock.classList.add('d-flex','align-items-center','excursion__info-item','excursion-green');
let $spanT = document.createElement('span');
$spanT.classList.add('excursion__cancellation-icon');
let $imgT = document.createElement('img');
$imgT.src = `${window.location.origin}/wp-content/plugins/viator/assets/img/icons/icon-check.svg`;
$imgT.alt = 'check';
$spanT.appendChild($imgT);
$tickBlock.appendChild($spanT);
$tickBlock.appendChild(document.createTextNode(ticketTxt));
$colTick.appendChild($tickBlock);
$infoBox.appendChild($colTick);
}
$cntBlock.appendChild($infoBox);
};
/********************
* DESCRIPTION, DETAILS, CANCELLATIONS, ETC.
********************/
const buildFaqContent = () => {
let $descContainer = document.createElement('div');
$descContainer.classList.add('excursion__description');
// 1) Description
if (product?.description) {
let $descItem = document.createElement('div');
$descItem.classList.add('faq__item','active','description');
let $descHeader = document.createElement('div');
$descHeader.classList.add('faq__item-header');
$descHeader.textContent = __('Description','viator');
let $descBody = document.createElement('div');
$descBody.classList.add('faq__item-body','test');
let $p = document.createElement('p');
$p.innerHTML = product.description.replace(/\n/g, '
');
$descBody.appendChild($p);
$descItem.appendChild($descHeader);
$descItem.appendChild($descBody);
$descContainer.appendChild($descItem);
}
// 2) Details: inclusions, exclusions, additionalInfo
if (product?.inclusions?.length || product?.exclusions?.length || product?.additionalInfo?.length) {
let $detailItem = document.createElement('div');
$detailItem.classList.add('faq__item');
let $detailHeader = document.createElement('div');
$detailHeader.classList.add('faq__item-header');
$detailHeader.textContent = __('Details','viator');
let $detailBody = document.createElement('div');
$detailBody.classList.add('faq__item-body');
// Inclusions
if (product?.inclusions?.length) {
let $incTitle = document.createElement('h5');
$incTitle.textContent = __('What\'s Included','viator');
let $ulInc = document.createElement('ul');
$ulInc.classList.add('faq__item-list','faq__details');
product.inclusions.forEach((inc) => {
let $li = document.createElement('li');
$li.classList.add('faq__detail','faq__detail-pass');
$li.textContent = inc?.otherDescription || inc?.description || '';
$ulInc.appendChild($li);
});
$detailBody.appendChild($incTitle);
$detailBody.appendChild($ulInc);
}
// Exclusions
if (product?.exclusions?.length) {
product.exclusions.forEach((exc) => {
let $li = document.createElement('li');
$li.classList.add('faq__detail','faq__detail-off');
$li.textContent = exc?.otherDescription || exc?.description || '';
// We can append to the same UL or create a new
});
}
// Additional info
if (product?.additionalInfo?.length) {
let $infoTitle = document.createElement('h5');
$infoTitle.textContent = __('Additional Info','viator');
let $ulInfo = document.createElement('ul');
$ulInfo.classList.add('faq__item-list');
product.additionalInfo.forEach((ad) => {
let $li = document.createElement('li');
$li.textContent = ad.description;
$ulInfo.appendChild($li);
});
$detailBody.appendChild($infoTitle);
$detailBody.appendChild($ulInfo);
// Operated by
if (product?.supplier?.name) {
let $pOp = document.createElement('p');
$pOp.innerHTML = __('Operated by','viator') + ` ${product.supplier.name}`;
$detailBody.appendChild($pOp);
}
}
$detailItem.appendChild($detailHeader);
$detailItem.appendChild($detailBody);
$descContainer.appendChild($detailItem);
}
// 3) Cancellations
if (product?.cancellationPolicy?.description || product?.cancellationPolicy?.refundEligibility) {
let $cancelItem = document.createElement('div');
$cancelItem.classList.add('faq__item');
let $cancelHeader = document.createElement('div');
$cancelHeader.classList.add('faq__item-header');
$cancelHeader.textContent = __('Cancellations','viator');
let $cancelBody = document.createElement('div');
$cancelBody.classList.add('faq__item-body');
let $cancelUl = document.createElement('ul');
$cancelUl.classList.add('faq__item-list','faq__cancel-list');
if (product.cancellationPolicy.description) {
// it might have
or not
let descLines = product.cancellationPolicy.description.split('
');
descLines.forEach(dl => {
let $li = document.createElement('li');
$li.textContent = dl.trim();
$cancelUl.appendChild($li);
});
}
if (product.cancellationPolicy.refundEligibility) {
// Add lines for partial refunds etc
}
// etc. your existing logic
$cancelBody.appendChild($cancelUl);
$cancelItem.appendChild($cancelHeader);
$cancelItem.appendChild($cancelBody);
$descContainer.appendChild($cancelItem);
}
// 4) Meeting point, schedule, etc.
// Similar expansions…
// Finally, append to $cntBlock
$cntBlock.appendChild($descContainer);
};
// ACTUAL CALLS
productSlider();
tags();
title();
buildInfoBox();
buildFaqContent();
};
/**
* 3) MAIN getProduct()
*/
const getProduct = async () => {
console.log('[DEBUG] getProduct() started');
const $contentWrap = $('.container .excursion__wrap');
if (!$contentWrap.length) {
console.error('[DEBUG] No .excursion__wrap found inside .container. Abort.');
return;
}
// Build request
let requestOptions = {
action: 'get_product',
type: 'viator',
destName: viatorSingleObj?.dest_name ?? '',
targetCurrency: localStorage.getItem('viatorCurrency') ?? viatorParameters?.default_currency,
id: viatorSingleObj?.post_id ?? getPostId(),
};
if (!requestOptions.id) {
console.warn('[DEBUG] No product ID found, maybe getPostId() is missing?');
$contentWrap.html(__('Error', 'viator'));
return;
}
// Possibly handle get_tags / get_destinations
if (!localStorage.getItem('viatorTags') || Date.now() > localStorage.getItem('viatorTagsLastUp')) {
requestOptions['get_tags'] = true;
}
if (!localStorage.getItem('viatorDest') || Date.now() > localStorage.getItem('viatorDestLastUp')) {
requestOptions['get_destinations'] = true;
}
let viatorLang = sessionStorage.getItem('viatorLang');
if (viatorLang) {
requestOptions['language'] = viatorLang;
}
console.log('[DEBUG] Sending get_product request:', requestOptions);
// 1) get the main product info
let response;
try {
response = await axios({
method: 'POST',
data: new URLSearchParams(requestOptions),
url: '/wp-admin/admin-ajax.php',
});
} catch (err) {
console.error('[ERROR] get_product request failed:', err);
return;
}
console.log('[DEBUG] Raw product response:', response);
// filterResponse
let productData = filterResponse(response);
console.log('[DEBUG] productData after filterResponse:', productData);
// 2) get availability
let availabilityData = await getAvailability(requestOptions.id);
// 3) Merge them
let finalResult = { ...productData, ...availabilityData };
console.log('[DEBUG] finalResult combined:', finalResult);
// 4) Insert into DOM
insertProduct($contentWrap[0], finalResult);
};
// Export
export { getProduct, insertProduct };