PageSourceSearch

https://wherryhotel.com/wp-content/themes/redcat/assets/js/booking-system.js?ver=1.3.8

js wherryhotel.com collected 2026-09-27 05:22:42 UTC 14,141 bytes, 452 lines download raw bytes

1/**
2 * Redcat Booking System JavaScript
3 * Simple implementation - just the essentials to make it work
4 */
5
6(function () {
7  "use strict";
8
9  // Main booking object
10  const RedcatBooking = {
11    panel: null,
12    overlay: null,
13    currentTab: "table",
14    bookingUrl: "https://booking.eu.guestline.app/RCI/availability", // Default URL
15    hotelCode: "RCISTANF", // Default hotel code
16    tableWidget: "",
17    partyShortcode: "",
18
19    init: function () {
20      this.panel = document.getElementById("redcat-booking-panel");
21
22      if (!this.panel) {
23        console.warn("Booking panel not found");
24        return;
25      }
26
27      this.overlay = this.panel.querySelector(".booking-panel-overlay");
28      this.setupEventListeners();
29      this.loadDynamicContent();
30    },
31
32    loadDynamicContent: function () {
33      // Look for booking buttons container to get dynamic settings
34      const bookingContainer = document.querySelector(
35        ".redcat-booking-buttons"
36      );
37
38      if (bookingContainer) {
39        console.log("Found booking container:", bookingContainer); // Debug
40
41        // Get dynamic hotel code
42        const customHotelCode =
43          bookingContainer.getAttribute("data-hotel-code");
44        if (customHotelCode) {
45          this.hotelCode = customHotelCode;
46          console.log("Set hotel code:", this.hotelCode); // Debug
47        }
48
49        // Get dynamic booking URL
50        const customUrl = bookingContainer.getAttribute("data-booking-url");
51        if (customUrl) {
52          this.bookingUrl = customUrl;
53          console.log("Set booking URL:", this.bookingUrl); // Debug
54        }
55
56        // Get table widget content (updated from iframe to widget code)
57        const widgetContent =
58          bookingContainer.getAttribute("data-table-widget");
59        if (widgetContent) {
60          this.tableWidget = widgetContent;
61          console.log("Found table widget content"); // Debug
62          this.setupTableWidget();
63        }
64
65        // The party shortcode is now handled server-side, so we don't need to load it here
66        // Just check if it exists for status reporting
67        const shortcodeInput = document.getElementById("party-shortcode-data");
68        if (shortcodeInput && shortcodeInput.value) {
69          console.log("Party shortcode will be processed server-side"); // Debug
70        }
71      } else {
72        console.log("No booking container found"); // Debug
73      }
74    },
75
76    setupTableWidget: function () {
77      // Check if widget is already loaded server-side
78      const tableContent = this.panel.querySelector("#table-booking-content");
79      const placeholder = this.panel.querySelector("#table-placeholder");
80      const existingWidget = tableContent
81        ? tableContent.querySelector(".table-widget-container")
82        : null;
83
84      // If server-side rendering worked, don't override it
85      if (existingWidget) {
86        console.log("Table widget already loaded server-side");
87        return;
88      }
89
90      // Client-side fallback for dynamic loading
91      if (!this.tableWidget || !tableContent || !placeholder) {
92        console.log(
93          "Table widget client-side setup failed: missing data or elements"
94        );
95        return;
96      }
97
98      try {
99        // Hide the placeholder
100        placeholder.style.display = "none";
101
102        // Create widget container
103        const widgetContainer = document.createElement("div");
104        widgetContainer.className = "table-widget-container";
105
106        // Basic security check on client side (server-side is more thorough)
107        if (this.isWidgetCodeSafe(this.tableWidget)) {
108          widgetContainer.innerHTML = this.tableWidget;
109
110          // Add basic styling
111          const style = document.createElement("style");
112          style.textContent = `
113            .table-widget-container {
114              width: 100%;
115              min-height: 400px;
116            }
117            .table-widget-container iframe {
118              width: 100%;
119              min-height: 400px;
120              border: none;
121            }
122            .table-widget-container > div {
123              width: 100%;
124              min-height: 400px;
125            }
126          `;
127          document.head.appendChild(style);
128
129          // Add widget to content
130          tableContent.appendChild(widgetContainer);
131
132          console.log("Table widget loaded successfully via client-side");
133        } else {
134          // Show error for unsafe content
135          widgetContainer.className = "widget-error";
136          widgetContainer.innerHTML = `
137            <p>Table booking widget could not be loaded due to security restrictions.</p>
138            <small>Please ensure the widget code is from a trusted provider.</small>
139          `;
140          tableContent.appendChild(widgetContainer);
141          console.error("Table widget failed security check");
142        }
143      } catch (error) {
144        console.error("Error loading table widget:", error);
145
146        // Show error message
147        const errorContainer = document.createElement("div");
148        errorContainer.className = "widget-error";
149        errorContainer.innerHTML = `
150          <p>Error loading table booking widget.</p>
151          <small>Please check the widget code format.</small>
152        `;
153        tableContent.appendChild(errorContainer);
154      }
155    },
156
157    isWidgetCodeSafe: function (code) {
158      if (!code) return false;
159
160      // Basic client-side security checks
161      const dangerousPatterns = [
162        /javascript\s*:/i,
163        /on\w+\s*=/i, // onclick, onload, etc.
164        /<\s*\/?\s*(object|embed|applet)\s*>/i,
165        /expression\s*\(/i,
166        /vbscript\s*:/i,
167      ];
168
169      for (let pattern of dangerousPatterns) {
170        if (pattern.test(code)) {
171          return false;
172        }
173      }
174
175      // Check for trusted domains (basic check)
176      const trustedDomains = [
177        "designmynight.com",
178        "onsass.designmynight.com",
179        "widgets.designmynight.com",
180        "opentable.com",
181        "resy.com",
182        "bookatable.com",
183        "guestline.app",
184      ];
185
186      // If it contains external references, ensure they're from trusted domains
187      const urlMatches = code.match(/(src|href)=["']([^"']+)["']/g);
188      if (urlMatches) {
189        for (let match of urlMatches) {
190          const url = match.match(/(src|href)=["']([^"']+)["']/)[2];
191          try {
192            const urlObj = new URL(url, window.location.origin);
193            const hostname = urlObj.hostname;
194
195            const isTrusted = trustedDomains.some(
196              (domain) => hostname === domain || hostname.endsWith("." + domain)
197            );
198
199            if (!isTrusted) {
200              console.warn(
201                "Table widget references untrusted domain:",
202                hostname
203              );
204              // Allow but warn - server-side validation is more strict
205            }
206          } catch (e) {
207            // Relative URLs or invalid URLs are generally safer
208            continue;
209          }
210        }
211      }
212
213      return true;
214    },
215
216    setupEventListeners: function () {
217      // Close button
218      const closeBtn = this.panel.querySelector(".booking-panel-close");
219      if (closeBtn) {
220        closeBtn.addEventListener("click", () => this.closePanel());
221      }
222
223      // Overlay click to close
224      if (this.overlay) {
225        this.overlay.addEventListener("click", () => this.closePanel());
226      }
227
228      // Escape key to close
229      document.addEventListener("keydown", (e) => {
230        if (e.key === "Escape" && this.panel.classList.contains("active")) {
231          this.closePanel();
232        }
233      });
234
235      // Tab switching
236      const tabs = this.panel.querySelectorAll(".booking-tab");
237      tabs.forEach((tab) => {
238        tab.addEventListener("click", () => {
239          const tabType = tab.getAttribute("data-tab");
240          this.switchTab(tabType);
241        });
242      });
243
244      // Room booking form
245      const roomForm = this.panel.querySelector("#room-booking-form");
246      if (roomForm) {
247        roomForm.addEventListener("submit", (e) => this.handleRoomBooking(e));
248      }
249    },
250
251    openPanel: function (type) {
252      if (!this.panel) return;
253
254      // Show the panel
255      this.panel.classList.add("active");
256      this.panel.setAttribute("aria-hidden", "false");
257
258      // Switch to the requested tab
259      if (type) {
260        this.switchTab(type);
261      }
262
263      // Prevent body scroll
264      document.body.style.overflow = "hidden";
265    },
266
267    closePanel: function () {
268      if (!this.panel) return;
269
270      this.panel.classList.remove("active");
271      this.panel.setAttribute("aria-hidden", "true");
272
273      // Restore body scroll
274      document.body.style.overflow = "";
275    },
276
277    switchTab: function (tabType) {
278      if (!this.panel) return;
279
280      // Update tabs
281      const tabs = this.panel.querySelectorAll(".booking-tab");
282      tabs.forEach((tab) => {
283        if (tab.getAttribute("data-tab") === tabType) {
284          tab.classList.add("active");
285          tab.setAttribute("aria-selected", "true");
286        } else {
287          tab.classList.remove("active");
288          tab.setAttribute("aria-selected", "false");
289        }
290      });
291
292      // Update content
293      const contents = this.panel.querySelectorAll(".booking-content");
294      contents.forEach((content) => {
295        if (content.id === `booking-content-${tabType}`) {
296          content.classList.add("active");
297        } else {
298          content.classList.remove("active");
299        }
300      });
301
302      this.currentTab = tabType;
303    },
304
305    handleRoomBooking: function (e) {
306      e.preventDefault();
307
308      const form = e.target;
309      const formData = new FormData(form);
310
311      // Get form values
312      const arrival = formData.get("arrival");
313      const departure = formData.get("departure");
314      const adults = formData.get("adults");
315      const children = formData.get("children");
316      const promo = formData.get("promo");
317
318      // Simple validation
319      if (!arrival || !departure) {
320        alert("Please select arrival and departure dates");
321        return;
322      }
323
324      if (new Date(departure) <= new Date(arrival)) {
325        alert("Departure date must be after arrival date");
326        return;
327      }
328
329      // Build the booking URL using the dynamic URL and hotel code
330      const bookingUrl = this.buildBookingUrl(
331        arrival,
332        departure,
333        adults,
334        children,
335        promo
336      );
337
338      // Open in new tab
339      window.open(bookingUrl, "_blank");
340
341      // Close the panel
342      this.closePanel();
343    },
344
345    buildBookingUrl: function (arrival, departure, adults, children, promo) {
346      // Use the dynamic booking URL and hotel code from settings
347      const baseUrl = this.bookingUrl;
348      const params = new URLSearchParams({
349        hotel: this.hotelCode, // Now uses dynamic hotel code
350        arrival: arrival,
351        departure: departure,
352        adults: adults,
353        children: children,
354      });
355
356      // Add promo code if provided
357      if (promo && promo.trim()) {
358        params.append("codes", promo.trim());
359      }
360
361      return `${baseUrl}?${params.toString()}`;
362    },
363  };
364
365  // Initialize when DOM is ready
366  document.addEventListener("DOMContentLoaded", function () {
367    RedcatBooking.init();
368
369    // Set up global event listeners for booking buttons
370    document.addEventListener("click", function (e) {
371      const button = e.target.closest(".booking-button");
372      // Only handle table and room buttons here (party is handled separately with query params)
373      if (button && button.id !== "book-party-btn") {
374        const bookingType = button.getAttribute("data-booking-type");
375        if (bookingType && RedcatBooking.panel) {
376          RedcatBooking.openPanel(bookingType);
377        }
378      }
379    });
380
381    // Handle direct links to party booking section
382    if (window.location.hash === "#party-booking-content") {
383      // Open party tab
384      RedcatBooking.openPanel("party");
385    }
386  });
387
388  // Make it globally available
389  window.RedcatBooking = RedcatBooking;
390
391// Initialize Airship form height adjustment
392function adjustAirshipIframes() {
393  const airshipIframes = document.querySelectorAll(
394    'iframe[src*="airship.co.uk"]'
395  );
396  airshipIframes.forEach(function (iframe) {
397    if (!iframe.hasAttribute("data-height-set")) {
398      iframe.setAttribute("data-height-set", "true");
399      iframe.style.width = "100%";
400      iframe.style.border = "none";
401      
402      // Different heights for different screen sizes
403      let minHeight;
404      if (window.innerWidth <= 480) {
405        // Small mobile: extra height for full form visibility
406        minHeight = "1300px";
407      } else if (window.innerWidth <= 768) {
408        // Large mobile/tablet portrait: good height for forms
409        minHeight = "1800px";
410      } else if (window.innerWidth <= 1024) {
411        // Tablet landscape/small desktop: moderate height
412        minHeight = "900px";
413      } else {
414        // Large desktop: minimal height to avoid whitespace
415        minHeight = "850px";
416      }
417      iframe.style.minHeight = minHeight;
418      
419      try {
420        const contentHeight = iframe.contentWindow.document.body.scrollHeight;
421        if (contentHeight && contentHeight > 100) {
422          iframe.style.height = contentHeight + "px";
423        } else {
424          // Responsive fallback instead of fixed 100vh
425          iframe.style.height = minHeight;
426        }
427      } catch (e) {
428        // Cross-origin fallback - responsive height
429        iframe.style.height = minHeight;
430      }
431    }
432  });
433}
434
435// Add resize listener for responsiveness
436window.addEventListener('resize', function() {
437  // Remove height-set attribute to allow re-adjustment
438  const airshipIframes = document.querySelectorAll(
439    'iframe[src*="airship.co.uk"]'
440  );
441  airshipIframes.forEach(function (iframe) {
442    iframe.removeAttribute("data-height-set");
443  });
444  adjustAirshipIframes();
445});
446
447// Run adjustments with delays to catch dynamically loaded iframes
448setTimeout(adjustAirshipIframes, 1000);
449setTimeout(adjustAirshipIframes, 3000);
450setTimeout(adjustAirshipIframes, 5000);
451// End Airship form height adjustment
452})();

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.