All checks were successful
Create Release Package / build-release (push) Successful in 1m20s
- Room search with availability, capacity, room type, amenity, price range, and building filters - AJAX-powered search with pagination and load more - Shortcodes: [bnb_buildings], [bnb_rooms], [bnb_room_search], [bnb_building], [bnb_room] - Widgets: Similar Rooms, Building Rooms, Availability Calendar - Gutenberg blocks: Building, Room, Room Search, Buildings List, Rooms List - Frontend CSS with responsive design and CSS custom properties - Frontend JavaScript with SearchForm, CalendarWidget, AvailabilityForm, PriceCalculator Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
826 lines
22 KiB
JavaScript
826 lines
22 KiB
JavaScript
/**
|
|
* WP BnB Frontend JavaScript
|
|
*
|
|
* Handles search forms, calendar widgets, and interactive elements.
|
|
*
|
|
* @package Magdev\WpBnb
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* WP BnB Frontend namespace.
|
|
*/
|
|
const WpBnb = {
|
|
|
|
/**
|
|
* Configuration from localized script.
|
|
*/
|
|
config: window.wpBnbFrontend || {},
|
|
|
|
/**
|
|
* Initialize all frontend components.
|
|
*/
|
|
init: function() {
|
|
this.initSearchForms();
|
|
this.initCalendarWidgets();
|
|
this.initAvailabilityForms();
|
|
this.initPriceCalculators();
|
|
},
|
|
|
|
/**
|
|
* Initialize room search forms.
|
|
*/
|
|
initSearchForms: function() {
|
|
const forms = document.querySelectorAll('.wp-bnb-search-form');
|
|
forms.forEach(form => {
|
|
new SearchForm(form);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize calendar widgets.
|
|
*/
|
|
initCalendarWidgets: function() {
|
|
const calendars = document.querySelectorAll('.wp-bnb-availability-calendar-widget');
|
|
calendars.forEach(calendar => {
|
|
new CalendarWidget(calendar);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize availability check forms on single room pages.
|
|
*/
|
|
initAvailabilityForms: function() {
|
|
const forms = document.querySelectorAll('.wp-bnb-availability-check');
|
|
forms.forEach(form => {
|
|
new AvailabilityForm(form);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize price calculator forms.
|
|
*/
|
|
initPriceCalculators: function() {
|
|
const calculators = document.querySelectorAll('.wp-bnb-price-calculator');
|
|
calculators.forEach(calculator => {
|
|
new PriceCalculator(calculator);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Make an AJAX request.
|
|
*
|
|
* @param {string} action The AJAX action.
|
|
* @param {Object} data The request data.
|
|
* @return {Promise} Promise resolving to response data.
|
|
*/
|
|
ajax: function(action, data = {}) {
|
|
const formData = new FormData();
|
|
formData.append('action', action);
|
|
formData.append('nonce', this.config.nonce || '');
|
|
|
|
Object.keys(data).forEach(key => {
|
|
if (data[key] !== null && data[key] !== undefined) {
|
|
formData.append(key, data[key]);
|
|
}
|
|
});
|
|
|
|
return fetch(this.config.ajaxUrl || '/wp-admin/admin-ajax.php', {
|
|
method: 'POST',
|
|
body: formData,
|
|
credentials: 'same-origin'
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (!data.success) {
|
|
throw new Error(data.data?.message || 'Request failed');
|
|
}
|
|
return data.data;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Format a date as YYYY-MM-DD.
|
|
*
|
|
* @param {Date} date The date object.
|
|
* @return {string} Formatted date string.
|
|
*/
|
|
formatDate: function(date) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
},
|
|
|
|
/**
|
|
* Parse a date string.
|
|
*
|
|
* @param {string} dateStr Date string in YYYY-MM-DD format.
|
|
* @return {Date|null} Date object or null if invalid.
|
|
*/
|
|
parseDate: function(dateStr) {
|
|
if (!dateStr) return null;
|
|
const parts = dateStr.split('-');
|
|
if (parts.length !== 3) return null;
|
|
return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
|
|
},
|
|
|
|
/**
|
|
* Calculate nights between two dates.
|
|
*
|
|
* @param {Date} checkIn Check-in date.
|
|
* @param {Date} checkOut Check-out date.
|
|
* @return {number} Number of nights.
|
|
*/
|
|
calculateNights: function(checkIn, checkOut) {
|
|
if (!checkIn || !checkOut) return 0;
|
|
const diffTime = checkOut.getTime() - checkIn.getTime();
|
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
},
|
|
|
|
/**
|
|
* Debounce a function.
|
|
*
|
|
* @param {Function} func The function to debounce.
|
|
* @param {number} wait Wait time in milliseconds.
|
|
* @return {Function} Debounced function.
|
|
*/
|
|
debounce: function(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Search Form handler class.
|
|
*/
|
|
class SearchForm {
|
|
constructor(element) {
|
|
this.form = element;
|
|
this.resultsContainer = document.querySelector(
|
|
this.form.dataset.results || '.wp-bnb-search-results'
|
|
);
|
|
this.currentPage = 1;
|
|
this.isLoading = false;
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Form submission.
|
|
this.form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.currentPage = 1;
|
|
this.search();
|
|
});
|
|
|
|
// Date validation.
|
|
const checkIn = this.form.querySelector('[name="check_in"]');
|
|
const checkOut = this.form.querySelector('[name="check_out"]');
|
|
|
|
if (checkIn && checkOut) {
|
|
// Set min date to today.
|
|
const today = WpBnb.formatDate(new Date());
|
|
checkIn.setAttribute('min', today);
|
|
|
|
checkIn.addEventListener('change', () => {
|
|
if (checkIn.value) {
|
|
// Set check-out min to day after check-in.
|
|
const minCheckOut = WpBnb.parseDate(checkIn.value);
|
|
if (minCheckOut) {
|
|
minCheckOut.setDate(minCheckOut.getDate() + 1);
|
|
checkOut.setAttribute('min', WpBnb.formatDate(minCheckOut));
|
|
|
|
// Clear check-out if it's before new minimum.
|
|
if (checkOut.value && checkOut.value <= checkIn.value) {
|
|
checkOut.value = '';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
checkOut.addEventListener('change', () => {
|
|
if (checkOut.value && checkIn.value && checkOut.value <= checkIn.value) {
|
|
alert(WpBnb.config.i18n?.invalidDateRange || 'Check-out must be after check-in');
|
|
checkOut.value = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Reset button.
|
|
const resetBtn = this.form.querySelector('[type="reset"]');
|
|
if (resetBtn) {
|
|
resetBtn.addEventListener('click', () => {
|
|
setTimeout(() => {
|
|
this.clearResults();
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
// Load more button.
|
|
if (this.resultsContainer) {
|
|
this.resultsContainer.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('wp-bnb-load-more')) {
|
|
e.preventDefault();
|
|
this.loadMore();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
getFormData() {
|
|
const formData = new FormData(this.form);
|
|
const data = {};
|
|
|
|
formData.forEach((value, key) => {
|
|
if (value) {
|
|
// Handle array fields (amenities[]).
|
|
if (key.endsWith('[]')) {
|
|
const cleanKey = key.slice(0, -2);
|
|
if (!data[cleanKey]) {
|
|
data[cleanKey] = [];
|
|
}
|
|
data[cleanKey].push(value);
|
|
} else {
|
|
data[key] = value;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Convert arrays to comma-separated strings for AJAX.
|
|
Object.keys(data).forEach(key => {
|
|
if (Array.isArray(data[key])) {
|
|
data[key] = data[key].join(',');
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
search() {
|
|
if (this.isLoading) return;
|
|
|
|
this.isLoading = true;
|
|
this.showLoading();
|
|
|
|
const data = this.getFormData();
|
|
data.page = this.currentPage;
|
|
data.per_page = this.form.dataset.perPage || 12;
|
|
|
|
WpBnb.ajax('wp_bnb_search_rooms', data)
|
|
.then(response => {
|
|
this.renderResults(response, this.currentPage === 1);
|
|
})
|
|
.catch(error => {
|
|
this.showError(error.message);
|
|
})
|
|
.finally(() => {
|
|
this.isLoading = false;
|
|
this.hideLoading();
|
|
});
|
|
}
|
|
|
|
loadMore() {
|
|
this.currentPage++;
|
|
this.search();
|
|
}
|
|
|
|
renderResults(response, replace = true) {
|
|
if (!this.resultsContainer) return;
|
|
|
|
const { rooms, total, page, total_pages } = response;
|
|
|
|
if (replace) {
|
|
this.resultsContainer.innerHTML = '';
|
|
} else {
|
|
// Remove existing load more button.
|
|
const existingLoadMore = this.resultsContainer.querySelector('.wp-bnb-load-more-wrapper');
|
|
if (existingLoadMore) {
|
|
existingLoadMore.remove();
|
|
}
|
|
}
|
|
|
|
if (rooms.length === 0 && replace) {
|
|
this.resultsContainer.innerHTML = `
|
|
<div class="wp-bnb-no-results">
|
|
<p>${WpBnb.config.i18n?.noResults || 'No rooms found matching your criteria.'}</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Create results count.
|
|
if (replace) {
|
|
const countEl = document.createElement('div');
|
|
countEl.className = 'wp-bnb-results-count';
|
|
countEl.innerHTML = `<p>${WpBnb.config.i18n?.resultsFound?.replace('%d', total) || `${total} rooms found`}</p>`;
|
|
this.resultsContainer.appendChild(countEl);
|
|
}
|
|
|
|
// Create grid container.
|
|
let grid = this.resultsContainer.querySelector('.wp-bnb-rooms-grid');
|
|
if (!grid) {
|
|
grid = document.createElement('div');
|
|
grid.className = 'wp-bnb-rooms-grid wp-bnb-grid wp-bnb-grid-3';
|
|
this.resultsContainer.appendChild(grid);
|
|
}
|
|
|
|
// Render room cards.
|
|
rooms.forEach(room => {
|
|
const card = this.createRoomCard(room);
|
|
grid.appendChild(card);
|
|
});
|
|
|
|
// Add load more button if there are more pages.
|
|
if (page < total_pages) {
|
|
const loadMoreWrapper = document.createElement('div');
|
|
loadMoreWrapper.className = 'wp-bnb-load-more-wrapper';
|
|
loadMoreWrapper.innerHTML = `
|
|
<button type="button" class="wp-bnb-load-more wp-bnb-button">
|
|
${WpBnb.config.i18n?.loadMore || 'Load More'}
|
|
</button>
|
|
`;
|
|
this.resultsContainer.appendChild(loadMoreWrapper);
|
|
}
|
|
}
|
|
|
|
createRoomCard(room) {
|
|
const card = document.createElement('article');
|
|
card.className = 'wp-bnb-room-card';
|
|
|
|
let imageHtml = '';
|
|
if (room.thumbnail) {
|
|
imageHtml = `
|
|
<div class="wp-bnb-room-card-image">
|
|
<a href="${this.escapeHtml(room.permalink)}">
|
|
<img src="${this.escapeHtml(room.thumbnail)}" alt="${this.escapeHtml(room.title)}">
|
|
</a>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
let amenitiesHtml = '';
|
|
if (room.amenities && room.amenities.length > 0) {
|
|
const amenityItems = room.amenities.slice(0, 4).map(a =>
|
|
`<span class="wp-bnb-amenity-tag">${this.escapeHtml(a.name)}</span>`
|
|
).join('');
|
|
amenitiesHtml = `<div class="wp-bnb-room-card-amenities">${amenityItems}</div>`;
|
|
}
|
|
|
|
let priceHtml = '';
|
|
if (room.price_display) {
|
|
priceHtml = `
|
|
<div class="wp-bnb-room-card-price">
|
|
<span class="wp-bnb-price">${this.escapeHtml(room.price_display)}</span>
|
|
<span class="wp-bnb-price-unit">/ ${WpBnb.config.i18n?.perNight || 'night'}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
card.innerHTML = `
|
|
${imageHtml}
|
|
<div class="wp-bnb-room-card-content">
|
|
<h3 class="wp-bnb-room-card-title">
|
|
<a href="${this.escapeHtml(room.permalink)}">${this.escapeHtml(room.title)}</a>
|
|
</h3>
|
|
${room.building_name ? `<p class="wp-bnb-room-card-building">${this.escapeHtml(room.building_name)}</p>` : ''}
|
|
<div class="wp-bnb-room-card-meta">
|
|
${room.capacity ? `<span class="wp-bnb-capacity">${room.capacity} ${WpBnb.config.i18n?.guests || 'guests'}</span>` : ''}
|
|
${room.room_type ? `<span class="wp-bnb-room-type">${this.escapeHtml(room.room_type)}</span>` : ''}
|
|
</div>
|
|
${amenitiesHtml}
|
|
${priceHtml}
|
|
<a href="${this.escapeHtml(room.permalink)}" class="wp-bnb-room-card-link wp-bnb-button wp-bnb-button-small">
|
|
${WpBnb.config.i18n?.viewDetails || 'View Details'}
|
|
</a>
|
|
</div>
|
|
`;
|
|
|
|
return card;
|
|
}
|
|
|
|
escapeHtml(str) {
|
|
if (!str) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
showLoading() {
|
|
this.form.classList.add('wp-bnb-loading');
|
|
const submitBtn = this.form.querySelector('[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.dataset.originalText = submitBtn.textContent;
|
|
submitBtn.textContent = WpBnb.config.i18n?.searching || 'Searching...';
|
|
}
|
|
}
|
|
|
|
hideLoading() {
|
|
this.form.classList.remove('wp-bnb-loading');
|
|
const submitBtn = this.form.querySelector('[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
if (submitBtn.dataset.originalText) {
|
|
submitBtn.textContent = submitBtn.dataset.originalText;
|
|
}
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
if (!this.resultsContainer) return;
|
|
this.resultsContainer.innerHTML = `
|
|
<div class="wp-bnb-error">
|
|
<p>${this.escapeHtml(message)}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
clearResults() {
|
|
if (this.resultsContainer) {
|
|
this.resultsContainer.innerHTML = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calendar Widget handler class.
|
|
*/
|
|
class CalendarWidget {
|
|
constructor(element) {
|
|
this.container = element;
|
|
this.roomId = element.dataset.roomId;
|
|
this.currentYear = parseInt(element.querySelector('[data-year]')?.dataset.year) || new Date().getFullYear();
|
|
this.currentMonth = parseInt(element.querySelector('[data-month]')?.dataset.month) || (new Date().getMonth() + 1);
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Navigation buttons.
|
|
this.container.addEventListener('click', (e) => {
|
|
const navBtn = e.target.closest('.wp-bnb-calendar-nav');
|
|
if (navBtn) {
|
|
e.preventDefault();
|
|
const direction = navBtn.dataset.direction;
|
|
if (direction === 'prev') {
|
|
this.navigatePrev();
|
|
} else if (direction === 'next') {
|
|
this.navigateNext();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
navigatePrev() {
|
|
this.currentMonth--;
|
|
if (this.currentMonth < 1) {
|
|
this.currentMonth = 12;
|
|
this.currentYear--;
|
|
}
|
|
this.loadCalendar();
|
|
}
|
|
|
|
navigateNext() {
|
|
this.currentMonth++;
|
|
if (this.currentMonth > 12) {
|
|
this.currentMonth = 1;
|
|
this.currentYear++;
|
|
}
|
|
this.loadCalendar();
|
|
}
|
|
|
|
loadCalendar() {
|
|
this.container.classList.add('wp-bnb-loading');
|
|
|
|
WpBnb.ajax('wp_bnb_get_calendar', {
|
|
room_id: this.roomId,
|
|
year: this.currentYear,
|
|
month: this.currentMonth
|
|
})
|
|
.then(response => {
|
|
this.renderCalendar(response);
|
|
})
|
|
.catch(error => {
|
|
console.error('Calendar load error:', error);
|
|
})
|
|
.finally(() => {
|
|
this.container.classList.remove('wp-bnb-loading');
|
|
});
|
|
}
|
|
|
|
renderCalendar(data) {
|
|
const monthContainer = this.container.querySelector('.wp-bnb-calendar-month');
|
|
if (!monthContainer) return;
|
|
|
|
// Update month/year attributes.
|
|
monthContainer.dataset.year = this.currentYear;
|
|
monthContainer.dataset.month = this.currentMonth;
|
|
|
|
// Update month name.
|
|
const monthNameEl = monthContainer.querySelector('.wp-bnb-calendar-month-name');
|
|
if (monthNameEl) {
|
|
monthNameEl.textContent = `${data.month_name} ${this.currentYear}`;
|
|
}
|
|
|
|
// Rebuild calendar grid.
|
|
const tbody = monthContainer.querySelector('.wp-bnb-calendar-grid tbody');
|
|
if (!tbody) return;
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
let day = 1;
|
|
const totalDays = data.days_in_month;
|
|
const firstDay = data.first_day_of_week;
|
|
const weeks = Math.ceil((firstDay + totalDays) / 7);
|
|
|
|
for (let week = 0; week < weeks; week++) {
|
|
const tr = document.createElement('tr');
|
|
|
|
for (let dow = 0; dow < 7; dow++) {
|
|
const td = document.createElement('td');
|
|
const cellIndex = week * 7 + dow;
|
|
|
|
if (cellIndex < firstDay || day > totalDays) {
|
|
td.className = 'wp-bnb-calendar-empty';
|
|
} else {
|
|
const dayData = data.days[day];
|
|
const classes = ['wp-bnb-calendar-day'];
|
|
|
|
if (dayData) {
|
|
if (dayData.is_booked) {
|
|
classes.push('wp-bnb-booked');
|
|
} else {
|
|
classes.push('wp-bnb-available');
|
|
}
|
|
if (dayData.is_past) {
|
|
classes.push('wp-bnb-past');
|
|
}
|
|
if (dayData.is_today) {
|
|
classes.push('wp-bnb-today');
|
|
}
|
|
td.dataset.date = dayData.date || '';
|
|
}
|
|
|
|
td.className = classes.join(' ');
|
|
td.textContent = day;
|
|
day++;
|
|
}
|
|
|
|
tr.appendChild(td);
|
|
}
|
|
|
|
tbody.appendChild(tr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Availability Form handler class.
|
|
* For checking availability on single room pages.
|
|
*/
|
|
class AvailabilityForm {
|
|
constructor(element) {
|
|
this.form = element;
|
|
this.roomId = element.dataset.roomId;
|
|
this.resultContainer = element.querySelector('.wp-bnb-availability-result');
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
this.form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.checkAvailability();
|
|
});
|
|
|
|
// Date validation.
|
|
const checkIn = this.form.querySelector('[name="check_in"]');
|
|
const checkOut = this.form.querySelector('[name="check_out"]');
|
|
|
|
if (checkIn && checkOut) {
|
|
const today = WpBnb.formatDate(new Date());
|
|
checkIn.setAttribute('min', today);
|
|
|
|
checkIn.addEventListener('change', () => {
|
|
if (checkIn.value) {
|
|
const minCheckOut = WpBnb.parseDate(checkIn.value);
|
|
if (minCheckOut) {
|
|
minCheckOut.setDate(minCheckOut.getDate() + 1);
|
|
checkOut.setAttribute('min', WpBnb.formatDate(minCheckOut));
|
|
}
|
|
}
|
|
this.clearResult();
|
|
});
|
|
|
|
checkOut.addEventListener('change', () => {
|
|
this.clearResult();
|
|
});
|
|
}
|
|
}
|
|
|
|
checkAvailability() {
|
|
const checkIn = this.form.querySelector('[name="check_in"]')?.value;
|
|
const checkOut = this.form.querySelector('[name="check_out"]')?.value;
|
|
|
|
if (!checkIn || !checkOut) {
|
|
this.showResult('error', WpBnb.config.i18n?.selectDates || 'Please select check-in and check-out dates.');
|
|
return;
|
|
}
|
|
|
|
if (checkOut <= checkIn) {
|
|
this.showResult('error', WpBnb.config.i18n?.invalidDateRange || 'Check-out must be after check-in.');
|
|
return;
|
|
}
|
|
|
|
this.form.classList.add('wp-bnb-loading');
|
|
|
|
WpBnb.ajax('wp_bnb_get_availability', {
|
|
room_id: this.roomId,
|
|
check_in: checkIn,
|
|
check_out: checkOut
|
|
})
|
|
.then(response => {
|
|
if (response.available) {
|
|
let message = WpBnb.config.i18n?.available || 'Room is available!';
|
|
if (response.price_display) {
|
|
message += ` ${WpBnb.config.i18n?.totalPrice || 'Total'}: ${response.price_display}`;
|
|
}
|
|
this.showResult('success', message, response);
|
|
} else {
|
|
this.showResult('error', WpBnb.config.i18n?.notAvailable || 'Sorry, the room is not available for these dates.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
this.showResult('error', error.message);
|
|
})
|
|
.finally(() => {
|
|
this.form.classList.remove('wp-bnb-loading');
|
|
});
|
|
}
|
|
|
|
showResult(type, message, data = null) {
|
|
if (!this.resultContainer) return;
|
|
|
|
let html = `<div class="wp-bnb-availability-${type}">${this.escapeHtml(message)}</div>`;
|
|
|
|
if (type === 'success' && data && data.booking_url) {
|
|
html += `
|
|
<a href="${this.escapeHtml(data.booking_url)}" class="wp-bnb-button wp-bnb-book-now">
|
|
${WpBnb.config.i18n?.bookNow || 'Book Now'}
|
|
</a>
|
|
`;
|
|
}
|
|
|
|
this.resultContainer.innerHTML = html;
|
|
this.resultContainer.style.display = 'block';
|
|
}
|
|
|
|
clearResult() {
|
|
if (this.resultContainer) {
|
|
this.resultContainer.innerHTML = '';
|
|
this.resultContainer.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
escapeHtml(str) {
|
|
if (!str) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Price Calculator handler class.
|
|
*/
|
|
class PriceCalculator {
|
|
constructor(element) {
|
|
this.container = element;
|
|
this.roomId = element.dataset.roomId;
|
|
this.priceDisplay = element.querySelector('.wp-bnb-calculated-price');
|
|
this.breakdownDisplay = element.querySelector('.wp-bnb-price-breakdown');
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
const checkIn = this.container.querySelector('[name="check_in"]');
|
|
const checkOut = this.container.querySelector('[name="check_out"]');
|
|
|
|
if (checkIn && checkOut) {
|
|
const debouncedCalculate = WpBnb.debounce(() => this.calculate(), 300);
|
|
|
|
checkIn.addEventListener('change', debouncedCalculate);
|
|
checkOut.addEventListener('change', debouncedCalculate);
|
|
}
|
|
}
|
|
|
|
calculate() {
|
|
const checkIn = this.container.querySelector('[name="check_in"]')?.value;
|
|
const checkOut = this.container.querySelector('[name="check_out"]')?.value;
|
|
|
|
if (!checkIn || !checkOut || checkOut <= checkIn) {
|
|
this.clearDisplay();
|
|
return;
|
|
}
|
|
|
|
this.container.classList.add('wp-bnb-loading');
|
|
|
|
WpBnb.ajax('wp_bnb_calculate_price', {
|
|
room_id: this.roomId,
|
|
check_in: checkIn,
|
|
check_out: checkOut
|
|
})
|
|
.then(response => {
|
|
this.displayPrice(response);
|
|
})
|
|
.catch(error => {
|
|
console.error('Price calculation error:', error);
|
|
this.clearDisplay();
|
|
})
|
|
.finally(() => {
|
|
this.container.classList.remove('wp-bnb-loading');
|
|
});
|
|
}
|
|
|
|
displayPrice(data) {
|
|
if (this.priceDisplay) {
|
|
this.priceDisplay.innerHTML = `
|
|
<span class="wp-bnb-price-label">${WpBnb.config.i18n?.total || 'Total'}:</span>
|
|
<span class="wp-bnb-price-amount">${this.escapeHtml(data.formatted_total)}</span>
|
|
`;
|
|
this.priceDisplay.style.display = 'block';
|
|
}
|
|
|
|
if (this.breakdownDisplay && data.breakdown) {
|
|
let breakdownHtml = '<ul class="wp-bnb-breakdown-list">';
|
|
|
|
if (data.breakdown.nights) {
|
|
breakdownHtml += `<li>${data.breakdown.nights} ${WpBnb.config.i18n?.nights || 'nights'}</li>`;
|
|
}
|
|
if (data.breakdown.tier) {
|
|
breakdownHtml += `<li>${this.escapeHtml(data.breakdown.tier)}</li>`;
|
|
}
|
|
if (data.breakdown.base_total) {
|
|
breakdownHtml += `<li>${WpBnb.config.i18n?.basePrice || 'Base'}: ${this.escapeHtml(data.breakdown.base_total)}</li>`;
|
|
}
|
|
if (data.breakdown.weekend_total && parseFloat(data.breakdown.weekend_total) > 0) {
|
|
breakdownHtml += `<li>${WpBnb.config.i18n?.weekendSurcharge || 'Weekend surcharge'}: ${this.escapeHtml(data.breakdown.weekend_total)}</li>`;
|
|
}
|
|
if (data.breakdown.season_name) {
|
|
breakdownHtml += `<li>${WpBnb.config.i18n?.season || 'Season'}: ${this.escapeHtml(data.breakdown.season_name)}</li>`;
|
|
}
|
|
|
|
breakdownHtml += '</ul>';
|
|
|
|
this.breakdownDisplay.innerHTML = breakdownHtml;
|
|
this.breakdownDisplay.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
clearDisplay() {
|
|
if (this.priceDisplay) {
|
|
this.priceDisplay.innerHTML = '';
|
|
this.priceDisplay.style.display = 'none';
|
|
}
|
|
if (this.breakdownDisplay) {
|
|
this.breakdownDisplay.innerHTML = '';
|
|
this.breakdownDisplay.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
escapeHtml(str) {
|
|
if (!str) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
// Initialize on DOM ready.
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => WpBnb.init());
|
|
} else {
|
|
WpBnb.init();
|
|
}
|
|
|
|
// Expose to global scope for potential external use.
|
|
window.WpBnb = WpBnb;
|
|
|
|
})();
|