{"version":3,"file":"product-details-B4a2ll0k.js","sources":["../../../scripts/components/products/details/cruise-benefit.vue","../../../scripts/services/helpers/truncation.ts","../../../scripts/components/products/details/product-itinerary.vue","../../../scripts/components/products/details/track-sailing.vue","../../../scripts/components/advisor/recommended-advisors.vue","../../../scripts/components/products/details/cruise-details.vue","../../../scripts/components/products/details/hotel-features.vue","../../../scripts/components/products/details/hotel-guest-rooms.vue","../../../scripts/components/products/details/hotel-details.vue","../../../scripts/components/products/details/tour-experiences.vue","../../../scripts/components/products/details/tour-details.vue","../../../scripts/services/layout/sticky-subhead.ts","../../../scripts/components/advisor/advisor-details.vue","../../../scripts/components/products/details/product-details.vue","../../../scripts/entry-points/product-details.ts"],"sourcesContent":["<template>\r\n <div :id=\"'benefit-' + benefit.id\" ref=\"benefit\" :class=\"['-item', (isCarousel) ? 'slide slide-flex' : '']\">\r\n <div v-if=\"isHostedBenefit\" class=\"-benefit-number-label d-md-none\" v-html=\"hostedBenefitNumberLabel\"></div>\r\n <div class=\"-overview\">\r\n <img v-if=\"benefit.thumbnailImageUrl\" :src=\"benefit.thumbnailImageUrl\" width=\"230\" class=\"d-none d-md-block\" :title=\"benefit.name\" :alt=\"benefit.name\" />\r\n <div v-if=\"benefit.isEvent\" :class=\"['mt-1 text--small -details', (collapseMobileFields) ? 'mobile-benefit-collapsed' : '']\">\r\n <div v-if=\"benefit.meals\">Meals: <b v-html=\"benefit.meals\"></b></div>\r\n <div v-if=\"benefit.ageAppropriateness\">Recommended Ages: <b v-html=\"benefit.ageAppropriateness\"></b></div>\r\n <div v-if=\"benefit.activityLevel\">Activity Level: <b v-html=\"benefit.activityLevel\"></b></div>\r\n <div v-if=\"benefit.experiences\">Experience(s): <b v-html=\"benefit.experiences\"></b></div>\r\n </div>\r\n </div>\r\n <div class=\"-description\">\r\n <h3 v-html=\"benefit.name\"></h3>\r\n <div v-if=\"benefit.location || benefit.duration\" class=\"mt-1\">\r\n <span v-if=\"benefit.location\" v-html=\"benefit.location\"></span>\r\n <span v-if=\"benefit.duration\" v-html=\"benefit.duration\"></span>\r\n </div>\r\n <div v-if=\"benefit.notice\" class=\"-notice mt-1\" v-html=\"benefit.notice\"></div>\r\n <div v-if=\"collapseMobileFields\" class=\"d-md-none mobile-benefit-expand-button mt-1 text-center\"><button class=\"btn btn-xs btn-primary-emphasis\" @click=\"expandHostedBenefits\">Show all details</button></div>\r\n <div :class=\"['mt-1', (collapseMobileFields) ? 'mobile-benefit-collapsed' : '']\" v-html=\"benefit.description\"></div>\r\n <div v-if=\"benefit.isEvent\" :class=\"['mt-1', (collapseMobileFields) ? 'mobile-benefit-collapsed' : '']\">\r\n <div><b v-html=\"benefit.availability\"></b> of {{ benefit.capacity }} spaces remaining {{ benefit.waitlist }}</div>\r\n </div>\r\n </div>\r\n <div v-if=\"isHostedBenefit\" class=\"-benefit-number-label d-none d-md-block\" v-html=\"hostedBenefitNumberLabel\"></div>\r\n </div>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { CruiseBenefit } from \"interfaces/cruise\";\r\n import { getPlural } from \"virtuoso-shared-web-ui\";\r\n import { PropType, ref, useTemplateRef } from \"vue\";\r\n\r\n const props = defineProps({\r\n benefit: {\r\n type: Object as PropType<CruiseBenefit>,\r\n default: undefined\r\n },\r\n hostedBenefitIndex: {\r\n type: Number,\r\n default: undefined\r\n },\r\n isCarousel: {\r\n type: Boolean\r\n },\r\n isHostedBenefit: {\r\n type: Boolean\r\n },\r\n totalHostedBenefits: {\r\n type: Number,\r\n default: undefined\r\n }\r\n });\r\n\r\n const collapseMobileFields = ref((props.isHostedBenefit && !props.benefit.isForAllGuests));\r\n const hostedBenefitNumberLabel = ref(`${props.hostedBenefitIndex + 1} of ${props.totalHostedBenefits} possible benefit${getPlural(props.totalHostedBenefits)}`);\r\n const benefitRef = useTemplateRef(\"benefit\");\r\n\r\n function expandHostedBenefits(): void {\r\n collapseMobileFields.value = false;\r\n\r\n // Need the carousel to recalculate the height \r\n const splideElement = benefitRef.value.closest(`.splide__track`);\r\n if (splideElement) {\r\n const evt = new CustomEvent(\"resize\", { bubbles: true, cancelable: false, composed: true });\r\n splideElement.dispatchEvent(evt);\r\n }\r\n } \r\n</script>\r\n","/**\r\n * applyVerticalTruncation - Sets truncation with a gradient and \"View more\" link on the bottom\r\n * The whole element is clickable to expand.\r\n * Height of the truncated content is based off of the max-height of .truncate-vertical.-initialized,\r\n which defaults to the values in .truncate-5\r\n * Use the @truncate-lines mixin to set different heights.\r\n * Optionally add a data-view-more-text element to specify link text other than \"View more\".\r\n * @param containerElement - The block element(s) to be vertically truncated, defaults to document.querySelectorAll(\".truncate-vertical\")\r\n */\r\nexport function applyVerticalTruncation(truncateElements: NodeList = document.querySelectorAll<HTMLElement>(\".truncate-vertical\")): void {\r\n truncateElements.forEach((element: HTMLElement) => {\r\n\r\n if (!element.classList.contains(\"-initialized\")) {\r\n\r\n const viewMoreText: string = element.dataset.viewMoreText || \"View more\";\r\n const viewLessText: string = element.dataset.viewLessText || \"View less\";\r\n\r\n element.classList.add(\"-initialized\");\r\n\r\n const initialHeight: number = element.offsetHeight;\r\n const copyHeight = element.scrollHeight;\r\n\r\n // Check to see if the text is long enough to even worry with this mess\r\n if (copyHeight <= initialHeight) {\r\n element.classList.add(\"-not-needed\");\r\n } else {\r\n\r\n // Add the inner div\r\n const innerWrap = document.createElement(\"div\");\r\n innerWrap.classList.add(\"-inner\");\r\n innerWrap.innerHTML = element.innerHTML;\r\n\r\n // Add the 'View less\" button now so it takes up space when expanding\r\n innerWrap.insertAdjacentHTML(\"beforeend\", `<div><button class=\"btn-link -toggle -view-less\">${viewLessText} <i class=\"icon-angle-up\"></i></button></div>`);\r\n element.innerHTML = innerWrap.outerHTML;\r\n const maxHeight = element.scrollHeight;\r\n\r\n // Add the gradient overlay\r\n element.insertAdjacentHTML(\"beforeend\", `<div class=\"-gradient\"><button class=\"btn-link -toggle\">${viewMoreText} <i class=\"icon-angle-down\"></i></button></div>`);\r\n\r\n // // Allow re-collapsing\r\n element.querySelector(\".-view-less\").addEventListener(\"click\", () => {\r\n element.style.maxHeight = maxHeight + \"px\"; // Explicitly reset the current full height from 'none'\r\n setTimeout(() => { // Let the max-height percolate so the transition transitions\r\n element.style.maxHeight = initialHeight + \"px\"; // Then back down to collapsed\r\n }, 1);\r\n\r\n // Re-enable \"View more\"\r\n setTimeout(() => { // Prevent immediate re-execution\r\n applyTruncationHandlers(element, maxHeight);\r\n element.classList.remove(\"-reveal\");\r\n }, 100);\r\n });\r\n\r\n applyTruncationHandlers(element, maxHeight);\r\n }\r\n }\r\n });\r\n\r\n}\r\n\r\nfunction applyTruncationHandlers(el: HTMLElement, maxHeight: number): void {\r\n el.addEventListener(\"click\", () => {\r\n el.style.maxHeight = maxHeight + \"px\";\r\n\r\n function handleTransitionEnd(e: Event) {\r\n if (e.target === e.currentTarget) {\r\n // To keep multiple events from being fired (child elements with transitions)\r\n el.style.maxHeight = \"none\"; // Set the max-height to none after the transition so page resizing works\r\n el.removeEventListener(\"transitionend\", handleTransitionEnd);\r\n }\r\n }\r\n\r\n el.addEventListener(\"transitionend\", handleTransitionEnd);\r\n\r\n el.classList.add(\"-reveal\");\r\n }, { once: true });\r\n}\r\n","<template>\r\n <ol class=\"list-unstyled itinerary-container\">\r\n <li v-for=\"(day, dayIndex) in itinerary\" :key=\"dayIndex\" :class=\"'itinerary-row' + ' -' + productType\">\r\n <div class=\"-day context-dark\">\r\n <div>Day {{ day.itineraryDay }}</div>\r\n <div v-if=\"day.dateFormatted\" class=\"-date\" v-html=\"day.dateFormatted\"></div>\r\n </div>\r\n <div class=\"-content\">\r\n <div class=\"-overview\">\r\n <div class=\"-stops\">\r\n <div class=\"-label\">Location(s)</div>\r\n <ol class=\"list-unstyled\">\r\n <li v-for=\"(stop, stopIndex) in day.stops\" :key=\"stopIndex\" class=\"text-emphasis\">\r\n <i v-if=\"isCruiseTour\" :class=\"['me-1', (stop.isOnLand) ? 'icon-shuttle' : 'icon-Cruises']\"></i>\r\n {{ stop.location }}\r\n <span v-if=\"isCruise && (stop.timeArrive || stop.timeDepart)\" class=\"-times\">\r\n <span v-if=\"stop.timeArrive\">Arrive: {{ stop.timeArrive }}</span>\r\n <span v-if=\"stop.timeDepart\">Depart: {{ stop.timeDepart }}</span>\r\n </span>\r\n </li>\r\n </ol>\r\n </div>\r\n <div v-if=\"isTour\" class=\"-lodging\">\r\n <div class=\"-label\">Hotel</div>\r\n <div v-if=\"day.hotelName\" class=\"text-emphasis\" v-html=\"day.hotelName\"></div>\r\n <div v-else class=\"text-emphasis\">None</div>\r\n </div>\r\n <div v-if=\"isTour\" class=\"-meals\">\r\n <div class=\"-label\">Meals</div>\r\n <div v-if=\"day.meals\" class=\"text-emphasis\" v-html=\"day.meals\"></div>\r\n <div v-else class=\"text-emphasis\">None</div>\r\n </div>\r\n </div>\r\n <div v-if=\"day.description\" class=\"-description truncate-vertical truncate-3\" data-view-more-text=\"More details\" data-view-less-text=\"Fewer details\" v-html=\"day.description\"></div>\r\n </div>\r\n </li>\r\n </ol>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { ItineraryDay } from \"interfaces/date\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { applyVerticalTruncation } from \"services/helpers/truncation\";\r\n import { nextTick, PropType } from \"vue\";\r\n\r\n const props = defineProps({\r\n productType: {\r\n type: String as PropType<ProductType>,\r\n default: undefined\r\n },\r\n isCruiseTour: {\r\n type: Boolean\r\n },\r\n itinerary: {\r\n type: Array as PropType<ItineraryDay[]>,\r\n default: undefined\r\n }\r\n });\r\n\r\n const isCruise = (props.productType === ProductType.CRUISES);\r\n const isTour = (props.productType === ProductType.TOURS);\r\n\r\n nextTick(() => {\r\n applyVerticalTruncation(document.querySelectorAll(\".itinerary-container .truncate-vertical\"));\r\n });\r\n \r\n</script>\r\n","<template>\r\n <div v-if=\"canViewComponent\" id=\"track-sailing-component\" class=\"mt-2\">\r\n <button v-show=\"!showTrackCruiseForm\" class=\"btn btn-primary-regular btn-sm w-100\" @click=\"toggleTrackSailingForm()\">Track Sailing</button>\r\n <div v-show=\"showTrackCruiseForm\" class=\"-wrapper\">\r\n <h3>Track this Sailing</h3>\r\n <div class=\"mt-1 text--small\">Client Name(s)</div>\r\n <div class=\"mt-half input-group\">\r\n <input id=\"track-sailing-client\" v-model=\"clientNamesValue\" type=\"text\" maxlength=\"50\" class=\"form-control\" :disabled=\"isLoading\" @keyup.enter=\"doTrackCruise\" />\r\n </div>\r\n <div v-if=\"validationText\" class=\"text-danger text--small mt-1\">{{ validationText }}</div>\r\n <div class=\"mt-1\">\r\n <button :class=\"['btn btn-primary-emphasis btn-sm', isLoading ? 'disabled' : '']\" :disabled=\"isLoading\" @click=\"doTrackCruise\">Save</button>\r\n <button :class=\"['btn btn-tertiary btn-sm ms-2', isLoading ? 'disabled' : '']\" :disabled=\"isLoading\" @click=\"toggleTrackSailingForm(true)\">Cancel</button>\r\n </div>\r\n </div>\r\n <div class=\"text--small mt-2 mx-auto d-none d-lg-block\"><a :href=\"cobrandLink('/cruisealerts')\">Learn more about Virtuoso Cruise Alerts</a></div>\r\n </div>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { AddCruiseAlertResponse } from \"interfaces/responses/product-detail-responses\";\r\n import { saveTrackSailing } from \"services/api/user\";\r\n import { isAdvisor, isVStaff } from \"services/auth/user-info\";\r\n import { toastError, toastSuccess } from \"services/helpers/toasts\";\r\n import { isEmbeddedMode } from \"services/layout/environment\";\r\n import { cobrandLink } from \"virtuoso-shared-web-ui\";\r\n import { ref } from \"vue\";\r\n\r\n const props = defineProps({\r\n sailingId: {\r\n type: Number,\r\n default: undefined\r\n }\r\n });\r\n\r\n const clientNamesValue = defineModel<string>();\r\n const canViewComponent = (isAdvisor() || isVStaff()) && !isEmbeddedMode();\r\n const showTrackCruiseForm = ref(false);\r\n const validationText = ref(\"\");\r\n const isLoading = ref(false);\r\n\r\n \r\n function doTrackCruise(): void {\r\n if (!clientNamesValue.value || clientNamesValue.value.length < 3 || clientNamesValue.value.length > 50) {\r\n validationText.value = \"Client name(s) must be between 3 and 50 characters.\";\r\n return;\r\n }\r\n\r\n isLoading.value = true;\r\n validationText.value = \"\";\r\n\r\n saveTrackSailing(props.sailingId, clientNamesValue.value).then((resultJSON: AddCruiseAlertResponse) => {\r\n if (resultJSON && resultJSON.success) {\r\n toastSuccess(\"Sailing tracked successfully!\");\r\n toggleTrackSailingForm();\r\n clientNamesValue.value = \"\";\r\n isLoading.value = false;\r\n } else {\r\n toastError(\"Error tracking this sailing, please try again.\");\r\n isLoading.value = false;\r\n }\r\n }, () => {\r\n toastError(\"Error tracking this sailing, please try again.\");\r\n isLoading.value = false;\r\n });\r\n }\r\n\r\n function toggleTrackSailingForm(clearValue = false): void {\r\n validationText.value = \"\";\r\n if (clearValue) {\r\n clientNamesValue.value = \"\";\r\n }\r\n\r\n showTrackCruiseForm.value = !showTrackCruiseForm.value;\r\n }\r\n</script>\r\n","<template>\r\n <div v-if=\"canViewComponent && !isLoading\" class=\"recommended-advisors mt-3\">\r\n <div v-if=\"hasAdvisors && !isCobranded()\" class=\"slab p-2\">\r\n <h4>Connect with a Virtuoso Travel Advisor</h4>\r\n <ul class=\"list-unstyled\">\r\n <li v-for=\"(advisor, index) in recommendedAdvisors\" :key=\"index\" class=\"mt-2 d-flex d-md-block d-lg-flex\">\r\n <div v-if=\"advisor.advisorImageUrl\" class=\"-headshot mb-2\"><a :href=\"advisor.advisorUrl\" @click=\"trackRecommendedAdvisorClick(advisor, index)\"><img :src=\"advisor.advisorImageUrl\" :alt=\"advisor.advisorName\" class=\"rounded-circle d-block mx-auto\" width=\"120\" loading=\"lazy\" /></a></div>\r\n <div v-track-element-view=\"getRecommendedAdvisorTrackingData(advisor, index)\" class=\"-contact-info\">\r\n <div><a :href=\"advisor.advisorUrl\" class=\"-name\" @click=\"trackRecommendedAdvisorClick(advisor, index)\" v-html=\"advisor.advisorName\"></a></div>\r\n <div v-if=\"advisor.recommendedLabel\" class=\"text--xsmall\" v-html=\"advisor.recommendedLabel\"></div>\r\n <div class=\"mt-1\">\r\n <div v-if=\"advisor.advisorSpecialtyLabel\" class=\"fw-bold text--small\" v-html=\"advisor.advisorSpecialtyLabel\"></div>\r\n <div class=\"text--xsmall\" v-html=\"advisor.agencyLocation\"></div>\r\n <div class=\"text--xsmall\" v-html=\"advisor.agencyName\"></div>\r\n <advisor-contact-button-component :advisor=\"advisorContactButtonInfo(advisor)\" :event-name=\"gaCategory\" button-style=\"down-arrow\" class=\"mt-1-half\"></advisor-contact-button-component>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n <div class=\"mt-2\"><a :href=\"advisorCatalogLinkFiltered\">Or, find a different advisor</a></div>\r\n </div>\r\n <a v-else-if=\"!isNetworkUser() && !isCobranded()\" class=\"btn btn-primary-emphasis btn-sm d-block\" :href=\"cobrandLink('/travel/advisors/search#')\">Find a Travel Advisor</a>\r\n <a v-else-if=\"!isNetworkUser() && isCobranded() && !hideCobrandedButton\" class=\"btn btn-primary-emphasis btn-sm d-block\" :href=\"cobrandLink('/travel/advisors/search#')\">Contact Advisor to Book</a>\r\n </div>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import AdvisorContactButtonComponent from \"components/advisor/advisor-contact-button.vue\";\r\n import { AdvisorContactButton, RecommendedAdvisor, RecommendedAdvisorQuery } from \"interfaces/advisor\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { getRecommendedAdvisors } from \"services/api/advisors\";\r\n import { isNetworkUser } from \"services/auth/user-info\";\r\n import { extractCountryFromLocationString } from \"services/helpers/destinations\";\r\n import { generateMediaServerImageUrl } from \"services/helpers/images\";\r\n import { isEmbeddedMode } from \"services/layout/environment\";\r\n import { translateB2BFacetToConsumer } from \"services/search/product-search\";\r\n import { parseAdvisorMeidFromUrlString, translateToConsumerUrl } from \"services/transformers/products\";\r\n import { getRecommendedAdvisorTrackingData, trackRecommendedAdvisorClick } from \"services/analytics\";\r\n import { cobrandLink, getPlural, isCobranded } from \"virtuoso-shared-web-ui\";\r\n import { computed, PropType, provide, ref } from \"vue\";\r\n \r\n const props = defineProps({\r\n advisorQuery: {\r\n type: Object as PropType<RecommendedAdvisorQuery>,\r\n default: () => ({})\r\n },\r\n hideCobrandedButton: {\r\n type: Boolean,\r\n default: false\r\n },\r\n setRecommendedAdvisorIds: {\r\n type: Function,\r\n default: (): void => { /* if setRecommendedAdvisorsIds() is passed as undefined */ }\r\n }\r\n });\r\n\r\n // Data provided to advisor contact form\r\n provide(\"searchFilters\", computed(() => ({\r\n destinationSpecialties: advisorSearchquery.value.ProductLocationCountry,\r\n specialties: advisorSearchquery.value.InterestType}))\r\n );\r\n\r\n const advisorCatalogLinkFiltered = ref(cobrandLink(\"/travel/advisors/search#\"));\r\n const canViewComponent = ref((!isEmbeddedMode() && !isNetworkUser()));\r\n const gaCategory = ref((props.advisorQuery.ProductTypeName === \"Cruise\") ? \"Sailing Detail\" : `${props.advisorQuery.ProductTypeName} Detail`);\r\n const hasAdvisors = ref(false);\r\n const isLoading = ref(true);\r\n const recommendedAdvisors = ref([] as RecommendedAdvisor[]);\r\n const advisorSearchquery = ref(props.advisorQuery);\r\n\r\n if (canViewComponent.value) {\r\n // Special handling for US destination specialties, no \"magic sub region\" mapping (for now?)\r\n if (advisorSearchquery.value.ProductLocationCountry === \"United States\") {\r\n advisorSearchquery.value.ProductLocationCountry = \"United States - All\";\r\n }\r\n\r\n // Translation from ship cruise type to advisor interest type\r\n if (advisorSearchquery.value.ProductTypeName === \"Cruise\" && advisorSearchquery.value.InterestType) {\r\n advisorSearchquery.value.InterestType = `${advisorSearchquery.value.InterestType} Cruising`;\r\n }\r\n\r\n if (advisorSearchquery.value.ProductLocationCountry) {\r\n advisorCatalogLinkFiltered.value += `${translateB2BFacetToConsumer(\"advisor_specilty_destination_for_facet\")}=${encodeURIComponent(advisorSearchquery.value.ProductLocationCountry)}`;\r\n } else if (advisorSearchquery.value.InterestType) {\r\n advisorCatalogLinkFiltered.value += `${translateB2BFacetToConsumer(\"advisor_interest_types\")}=${encodeURIComponent(advisorSearchquery.value.InterestType)}`;\r\n }\r\n\r\n getAdvisors();\r\n }\r\n\r\n function advisorContactButtonInfo(advisor: RecommendedAdvisor): AdvisorContactButton {\r\n return {\r\n company: advisor.agencyName,\r\n companyId: advisor.companyId,\r\n country: advisor.country,\r\n email: advisor.advisorEmail,\r\n id: parseAdvisorMeidFromUrlString(advisor.advisorUrl),\r\n name: advisor.advisorName,\r\n phone: advisor.advisorPhone,\r\n productType: ProductType.ADVISORS\r\n };\r\n }\r\n\r\n function getAdvisors(): void {\r\n\r\n let firstRecommendedAdvisor: number = null;\r\n let lastRecommendedAdvisor: number = null;\r\n\r\n if (!isCobranded()) { // If it's cobranded, we only ever show either a static button or nothing\r\n\r\n getRecommendedAdvisors(advisorSearchquery.value)\r\n .then((advisorsJSON) => {\r\n\r\n // Test data for local testing, comment out the getRecommendedAdvisors().then() lines & the reject below\r\n // const advisorsJSON = advisorsTestData;\r\n\r\n if (advisorsJSON && advisorsJSON.length > 0) {\r\n const validAdvisors: RecommendedAdvisor[] = [];\r\n\r\n advisorsJSON.forEach((advisor) => {\r\n if (advisor.AdvisorName && advisor.ProfilePageUrl && advisor.EmailAddress && advisor.PhoneNumber && advisor.AgencyLocation && advisor.AgencyName) {\r\n validAdvisors.push({\r\n advisorEmail: advisor.EmailAddress,\r\n advisorImageUrl: (advisor.ImageUrl) ? generateMediaServerImageUrl(advisor.ImageUrl, { height: 140, width: 140 }) : \"\",\r\n advisorName: advisor.AdvisorName,\r\n advisorPhone: advisor.PhoneNumber,\r\n advisorSpecialtyLabel: advisor.AdvisorSpecialityLabel,\r\n advisorUrl: translateToConsumerUrl(advisor.ProfilePageUrl),\r\n agencyLocation: advisor.AgencyLocation,\r\n agencyName: advisor.AgencyName,\r\n companyId: advisor.CompanyId,\r\n country: extractCountryFromLocationString(advisor.AgencyLocation),\r\n recommendedLabel: (advisor.TotalReviews && advisor.TotalReviews > 0) ? `${advisor.TotalRecommendedPercent}% Recommended (${advisor.TotalReviews} Review${getPlural(advisor.TotalReviews)})` : \"\",\r\n totalReviews: advisor.TotalReviews\r\n });\r\n }\r\n });\r\n\r\n if (validAdvisors.length) {\r\n if (validAdvisors.length > 2) {\r\n validAdvisors.length = 2; // If, for some reason, it returns more than 2 advisors, trim it down to 2\r\n }\r\n recommendedAdvisors.value = validAdvisors;\r\n hasAdvisors.value = true;\r\n // there are at max 2 advisors\r\n if (recommendedAdvisors.value[0]) {\r\n firstRecommendedAdvisor = parseAdvisorMeidFromUrlString(recommendedAdvisors.value[0].advisorUrl);\r\n }\r\n if (recommendedAdvisors.value[1]) {\r\n lastRecommendedAdvisor = parseAdvisorMeidFromUrlString(recommendedAdvisors.value[1].advisorUrl);\r\n }\r\n }\r\n\r\n } else {\r\n console.log(\"No recommended advisors available.\");\r\n }\r\n isLoading.value = false;\r\n },\r\n (err: Error) => {\r\n console.log(\"Error retrieving recommended advisors\", err);\r\n isLoading.value = false;\r\n })\r\n .finally(() => {\r\n props.setRecommendedAdvisorIds(firstRecommendedAdvisor, lastRecommendedAdvisor);\r\n });\r\n } else {\r\n if (props.hideCobrandedButton) {\r\n canViewComponent.value = false;\r\n }\r\n isLoading.value = false;\r\n }\r\n }\r\n</script>\r\n","<template>\r\n <div v-if=\"isOverviewReady\" ref=\"product-detail\" class=\"product-detail\">\r\n <top-splash-component :top-splash-data=\"topSplashData\" @promo-click=\"showTab('promotions', false, true)\"></top-splash-component>\r\n <div v-if=\"isReady\">\r\n <div class=\"container mt-4\">\r\n <div class=\"product-detail-overview mb-6\">\r\n <div>\r\n <h1 v-html=\"sailing.cruiseName\"></h1>\r\n <div v-if=\"sailing.isCruiseTour\" class=\"mt-1\"><b>CRUISETOUR</b></div>\r\n <div v-if=\"sailing.sailings.length > 1\" class=\"mt-2\">\r\n Travel Dates<br />\r\n <template v-if=\"isEmbeddedMode()\">\r\n Ask your advisor about available travel dates\r\n </template>\r\n <label v-else class=\"select--styled\">\r\n <select id=\"itinerary-nav\" class=\"w-100\" aria-label=\"Select Travel Dates\" @change=\"goToSailing()\">\r\n <option v-for=\"(item, index) in sailing.sailings\" :key=\"index\" :value=\"item.id\" :selected=\"item.id === sailing.id\" :data-url=\"item.url\" v-html=\"item.travelDates\"></option>\r\n </select>\r\n </label>\r\n </div>\r\n <div class=\"mt-3 -description\">\r\n <b v-if=\"sailing.sailings.length <= 1\">{{ sailing.travelDates }}<br /></b>\r\n <b v-html=\"sailing.cruisePorts\"></b><br />\r\n <b v-if=\"isEmbeddedMode()\" class=\"weglot-exclude\" v-html=\"sailing.companyName\"></b>\r\n <a v-else class=\"weglot-exclude\" :href=\"sailing.brandUrl\"><b v-html=\"sailing.companyName\"></b></a>\r\n <br />\r\n Ship: <b v-if=\"isEmbeddedMode()\" class=\"weglot-exclude\" v-html=\"sailing.shipName\"></b>\r\n <b v-else class=\"weglot-exclude\"><a :href=\"sailing.shipUrl\" v-html=\"sailing.shipName\"></a></b>\r\n </div>\r\n\r\n <div v-if=\"isNetworkUser() && sailing.cruiseDepartureCode\" class=\"mt-2\">Sailing ID: {{ sailing.cruiseDepartureCode }}</div>\r\n\r\n <h2 class=\"text--serif mt-3\">At a Glance</h2>\r\n\r\n <img :src=\"sailing.cruiseMapUrl\" width=\"500\" :alt=\"'Cruise Itinerary Map for ' + sailing.cruiseName\" :title=\"'Cruise Itinerary Map for ' + sailing.cruiseName\" class=\"mt-2\" loading=\"lazy\" />\r\n\r\n <div v-if=\"sailing.whatIsIncluded\" class=\"mt-2\">\r\n <div class=\"mb-1\"><b>Included</b></div>\r\n <div v-html=\"sailing.whatIsIncluded\"></div>\r\n </div>\r\n\r\n <div v-if=\"sailing.whatIsNotIncluded\" class=\"mt-2\">\r\n <div class=\"mb-1\"><b>Not Included</b></div>\r\n <div v-html=\"sailing.whatIsNotIncluded\"></div>\r\n </div>\r\n </div>\r\n <div class=\"-gallery mt-5 mt-md-0 mb-6\">\r\n <image-gallery-component v-if=\"sailing.galleryImages && sailing.galleryImages.length\"\r\n :gallery-data=\"sailing.galleryImages\"\r\n :product-id=\"sailing.id.toString()\"\r\n :product-name=\"sailing.cruiseName\"\r\n :product-type=\"ProductType.CRUISES\"></image-gallery-component>\r\n <recommended-advisors-component v-if=\"!suppressRecommendedAdvisors\" :advisor-query=\"recommendedAdvisorQuery\" :product-type=\"ProductType.CRUISES\" :set-recommended-advisor-ids=\"setRecommendedAdvisorIds\"></recommended-advisors-component>\r\n <a v-if=\"isNetworkUser() && !isEmbeddedMode()\" id=\"b2blink-legacy-link-label\" class=\"btn btn-primary-emphasis btn-sm d-block mt-3\" :href=\"legacyLink\" @click=\"handleLegacyLinkClick\">{{ legacyLinkLabel }}</a>\r\n <track-sailing-component :sailing-id=\"productId\"></track-sailing-component>\r\n <button class=\"wl-heartable -save-this mt-3\" data-wl-type=\"cruise\" :data-wl-id=\"heartableUrl\" :data-wl-title=\"cruiseTitle\"></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div id=\"detail-tabs\"></div>\r\n <div class=\"container d-none d-md-block\">\r\n <ul ref=\"tab-nav-container\" class=\"tab-nav-container\">\r\n <li id=\"tab-itinerary\"><button @click=\"showTab('itinerary')\">Itinerary</button></li>\r\n <li v-if=\"hasBenefits\" id=\"tab-benefits\"><button @click=\"showTab('benefits')\">Virtuoso Benefits</button></li>\r\n <li v-if=\"sailing.promotions.length\" id=\"tab-promotions\"><button @click=\"showTab('promotions')\">Promotions</button></li>\r\n <li v-if=\"sailing.addOnDayTours.length || sailing.postPackages.length || sailing.prePackages.length\" id=\"tab-add-ons\"><button @click=\"showTab('add-ons')\">Add-ons</button></li>\r\n </ul>\r\n </div>\r\n <div class=\"slab -tab-slab\">\r\n <div class=\"container\">\r\n <ul class=\"tab-content\">\r\n <li id=\"tc-itinerary\">\r\n <button class=\"tab-nav\" @click=\"showTab('itinerary')\">Itinerary</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Itinerary</h4>\r\n <div v-if=\"sailing.isCruiseTour\" class=\"my-1 cruisetour-key\"><span class=\"text-nowrap me-3\"><i class=\"icon-shuttle\"></i> Tour Segment</span> <span class=\"text-nowrap\"><i class=\"icon-Cruises\"></i> Cruise Segment</span></div>\r\n <product-itinerary-component :itinerary=\"sailing.itinerary\" :product-type=\"ProductType.CRUISES\" :is-cruise-tour=\"sailing.isCruiseTour\"></product-itinerary-component>\r\n </div>\r\n </li>\r\n <li v-if=\"hasBenefits\" id=\"tc-benefits\">\r\n <button class=\"tab-nav\" @click=\"showTab('benefits')\">Virtuoso Benefits</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Virtuoso Benefits</h4>\r\n <div v-if=\"sailing.benefitsHostedGroups.length > 0\" class=\"mb-4 mt-2\">\r\n <h2 class=\"text--serif mt-2\">Hosted Benefits</h2>\r\n <div v-for=\"(hbg, hbgIndex) in sailing.benefitsHostedGroups\" :key=\"hbgIndex\" class=\"benefits-box mt-2\">\r\n <div class=\"-title-row\">\r\n <div class=\"-dates\" v-html=\"hbg.dates\"></div>\r\n <div class=\"-hosts\" v-html=\"hbg.hosts\"></div>\r\n </div>\r\n <div v-if=\"hbg.hostedBenefits.length > 0\" class=\"benefits-container\">\r\n <div class=\"text--small\">Guests may select one of the following benefits. Please consult your Virtuoso travel advisor for restrictions and fees that apply to 3rd and 4th passengers in the cabin.</div>\r\n <CarouselSliderComponent v-if=\"hbg.hostedBenefits.length > 1 && isBenefitsCarouselInitialized\" slide-class=\"-slide\" :slides=\"hbg.hostedBenefits\" :carousel-config=\"carouselConfig\">\r\n <template #slides=\"{slide, index}\">\r\n <cruise-benefit-component :benefit=\"slide\"\r\n :is-hosted-benefit=\"true\"\r\n :is-carousel=\"true\"\r\n :hosted-benefit-index=\"index\"\r\n :total-hosted-benefits=\"hbg.hostedBenefits.length\"></cruise-benefit-component>\r\n </template>\r\n </CarouselSliderComponent>\r\n <cruise-benefit-component v-else-if=\"hbg.hostedBenefits.length === 1\"\r\n :benefit=\"hbg.hostedBenefits[0]\"\r\n :is-hosted-benefit=\"true\"\r\n :is-carousel=\"false\"\r\n :hosted-benefit-index=\"0\"\r\n :total-hosted-benefits=\"1\"></cruise-benefit-component>\r\n </div>\r\n <div v-if=\"hbg.forAllBenefits.length > 0\" class=\"container mt-2\">\r\n <div><b>Guests may also enjoy these benefits.</b> Restrictions may apply.</div>\r\n <cruise-benefit-component v-for=\"(ben, benIndex) in hbg.forAllBenefits\" :key=\"benIndex\" :benefit=\"ben\" :is-hosted-benefit=\"false\"></cruise-benefit-component>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"sailing.benefitsExclusive.length > 0\" class=\"mt-2 mb-4\">\r\n <h2 class=\"text--serif\">Exclusive Benefits</h2>\r\n <div class=\"benefits-box mt-2\">\r\n <div class=\"benefits-container\">\r\n <div>Guests may also be entitled to the following complimentary benefits. Please consult your Virtuoso travel advisor for restrictions that may apply.</div>\r\n <cruise-benefit-component v-for=\"(ben, benIndex) in sailing.benefitsExclusive\" :key=\"benIndex\" :benefit=\"ben\" :is-hosted-benefit=\"false\"></cruise-benefit-component>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"sailing.benefitsHQGroups.length > 0\" class=\"mt-2\">\r\n <h2 class=\"text--serif\">HQ Group Benefits</h2>\r\n <div class=\"benefits-box mt-2\">\r\n <div class=\"benefits-container\">\r\n <div>Benefits may be recalled by the cruise line at any time. Please contact the cruise line to verify availability before offering them to your clients. Combinability restrictions may also apply, please see the Booking Instructions for information.</div>\r\n <div v-for=\"(hqGroup, index) in sailing.benefitsHQGroups\" :key=\"index\">\r\n <template v-if=\"hqGroup.benefits.length > 0\">\r\n <div v-if=\"hqGroup.visibilityCountries && isVStaff()\" class=\"text-danger mt-2\">Visible Only to {{ hqGroup.visibilityCountries }}</div>\r\n <cruise-benefit-component v-for=\"(ben, benIndex) in hqGroup.benefits\" :key=\"benIndex\" :benefit=\"ben\" :is-hosted-benefit=\"false\"></cruise-benefit-component>\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </li>\r\n <li v-if=\"sailing.promotions.length\" id=\"tc-promotions\">\r\n <button class=\"tab-nav\" @click=\"showTab('promotions')\">Promotions</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Promotions</h4>\r\n <promotions-component :product-type=\"ProductType.CRUISES\" :promotions=\"sailing.promotions\"></promotions-component>\r\n </div>\r\n </li>\r\n <li v-if=\"sailing.addOnDayTours.length || sailing.postPackages.length || sailing.prePackages.length\" id=\"tc-add-ons\">\r\n <button class=\"tab-nav\" @click=\"showTab('add-ons')\">Add-ons</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Add-ons</h4>\r\n\r\n <div v-if=\"sailing.prePackages.length > 0\" class=\"mb-4\">\r\n <h2 class=\"text--serif\">Pre-Packages</h2>\r\n <div class=\"add-ons-box mt-2\">\r\n <div class=\"-title-row\">\r\n <div class=\"-location\" v-html=\"sailing.prePackages[0].location\"></div>\r\n <div class=\"-toggle\"><button aria-label=\"Toggle Pre-Packages\" @click.prevent.stop=\"toggleAddOn('prepackages')\"><i id=\"add-on-toggle-prepackages\" class=\"icon-plus-circle-ut\"></i></button></div>\r\n </div>\r\n <div id=\"add-on-container-prepackages\" class=\"add-on-container\">\r\n <add-on-tours-component v-for=\"(pkg, pkgIndex) in sailing.prePackages\" :key=\"pkgIndex\" :add-on=\"pkg\"></add-on-tours-component>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"sailing.addOnDayTours.length > 0\" class=\"mb-4\">\r\n <h2 class=\"text--serif\">Shore Excursions</h2>\r\n <div v-for=\"(segment, segmentIndex) in sailing.addOnDayTours\" :key=\"segmentIndex\" class=\"add-ons-box mt-2\">\r\n <div class=\"-title-row\">\r\n <div class=\"-location\">{{ segment.addOnDate }} <span class=\"d-none d-md-inline\">—</span><span class=\"d-md-none\"><br /></span> {{ segment.addOnLocation }}</div>\r\n <div class=\"-toggle\"><button aria-label=\"Toggle Shore Excursions\" @click.prevent.stop=\"toggleAddOn('shore-excursion-' + segmentIndex)\"><i :id=\"'add-on-toggle-shore-excursion-' + segmentIndex\" class=\"icon-plus-circle-ut\"></i></button></div>\r\n </div>\r\n <div :id=\"'add-on-container-shore-excursion-' + segmentIndex\" class=\"add-on-container\">\r\n <add-on-tours-component v-for=\"(pkg, pkgIndex) in segment.addOns\" :key=\"pkgIndex\" :add-on=\"pkg\"></add-on-tours-component>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"sailing.postPackages.length > 0\">\r\n <h2 class=\"text--serif\">Post-Packages</h2>\r\n <div class=\"add-ons-box mt-2\">\r\n <div class=\"-title-row\">\r\n <div class=\"-location\" v-html=\"sailing.postPackages[0].location\"></div>\r\n <div class=\"-toggle\"><button aria-label=\"Toggle Post-Packages\" @click.prevent.stop=\"toggleAddOn('postpackages')\"><i id=\"add-on-toggle-postpackages\" class=\"icon-plus-circle-ut\"></i></button></div>\r\n </div>\r\n <div id=\"add-on-container-postpackages\" class=\"add-on-container\">\r\n <add-on-tours-component v-for=\"(pkg, pkgIndex) in sailing.postPackages\" :key=\"pkgIndex\" :add-on=\"pkg\"></add-on-tours-component>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-else class=\"v-loading -no-overlay my-3\"></div>\r\n </div>\r\n <LogoSplash v-else />\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { Options } from \"@splidejs/vue-splide\";\r\n import AddOnToursComponent from \"components/products/details/add-on-tours.vue\";\r\n import CruiseBenefitComponent from \"components/products/details/cruise-benefit.vue\";\r\n import ProductItineraryComponent from \"components/products/details/product-itinerary.vue\";\r\n import PromotionsComponent from \"components/products/details/product-promotions.vue\";\r\n import TrackSailingComponent from \"components/products/details/track-sailing.vue\";\r\n import RecommendedAdvisorsComponent from \"components/advisor/recommended-advisors.vue\";\r\n import CarouselSliderComponent from \"components/shared/carousel-slider.vue\";\r\n import ImageGalleryComponent from \"components/shared/image-gallery.vue\";\r\n import LogoSplash from \"components/shared/logo-splash.vue\";\r\n import TopSplashComponent from \"components/shared/top-splash.vue\";\r\n import { AddOnPackage, ProductDetailsCruiseResponse } from \"interfaces/responses/product-detail-responses\";\r\n import { RecommendedAdvisorQuery } from \"interfaces/advisor\";\r\n import { CruiseBenefit } from \"interfaces/cruise\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { GalleryItem } from \"interfaces/image\";\r\n import { ProductDetailsSailing, ProductTopSplash, Promotion } from \"interfaces/product\";\r\n import { AddOnDayTours, SortedBenefits, TourEvent } from \"interfaces/tour\";\r\n import { getProductDetails } from \"services/api/products\";\r\n import { isNetworkUser, isSupplier, isVStaff } from \"services/auth/user-info\";\r\n import { sanitizeUserGeneratedContent, toggleSlideWithFade } from \"services/helpers/html\";\r\n import { generateMediaServerImageUrl, hydrateImageGallery } from \"services/helpers/images\";\r\n import { toastError } from \"services/helpers/toasts\";\r\n import { isEmbeddedMode, isMobileScreenWidth } from \"services/layout/environment\";\r\n import { setB2BDesktopCookie } from \"services/layout/metadata\";\r\n import { translateToConsumerUrl } from \"services/transformers/products\";\r\n import { trackEvent } from \"services/analytics\";\r\n import { enableHearts } from \"services/wanderlist\";\r\n import { cobrandLink, getPlural, parseURLParameters, slugify } from \"virtuoso-shared-web-ui\";\r\n import { capitalizeFirst } from \"virtuoso-shared-web-ui\";\r\n import { nextTick, ref, useTemplateRef } from \"vue\";\r\n\r\n let hasMultipleHostedBenefits = false;\r\n let topPromoName = \"\"; // Referenced by GA\r\n\r\n const props = defineProps({\r\n productId: {\r\n type: Number,\r\n default: undefined\r\n },\r\n suppressRecommendedAdvisors: {\r\n type: Boolean,\r\n default: false\r\n }\r\n });\r\n const carouselConfig: Options = {\r\n adjustableHeight: true,\r\n start: 0\r\n }; \r\n const cruiseTitle = ref(\"\");\r\n const heartableUrl = ref(\"\");\r\n const hasBenefits = ref(false);\r\n const isBenefitsCarouselInitialized = ref(false);\r\n const isOverviewReady = ref(false);\r\n const isReady = ref(false);\r\n const legacyLink = cobrandLink(`/cruises/sailings/${props.productId}`);\r\n const legacyLinkLabel = (isSupplier()) ? \"For Partners: Update Your Profile\" : \"For Advisors: Advanced Results\";\r\n const productDetailRef = useTemplateRef(\"product-detail\");\r\n const qsParams = parseURLParameters();\r\n const recommendedAdvisorQuery = ref<RecommendedAdvisorQuery>({} as RecommendedAdvisorQuery);\r\n const sailing = ref<ProductDetailsSailing>({} as ProductDetailsSailing);\r\n const tabNavContainerRef = useTemplateRef(\"tab-nav-container\");\r\n const topSplashData = ref<ProductTopSplash>({} as ProductTopSplash); \r\n\r\n function autoExpandAndToggle(item: string): void {\r\n toggleAddOn(item);\r\n }\r\n\r\n function goToSailing(): void {\r\n const elSelect = document.getElementById(\"itinerary-nav\") as HTMLSelectElement;\r\n const newId = parseInt(elSelect.value, 10);\r\n if (newId && newId !== props.productId) {\r\n window.location.href = elSelect.options[elSelect.selectedIndex].dataset.url;\r\n }\r\n }\r\n\r\n function handleLegacyLinkClick(): void {\r\n setB2BDesktopCookie();\r\n trackEvent(\"legacy_page_click\", {\r\n affiliation: `${sailing.value.companyId}`,\r\n item_id: `${props.productId}`,\r\n item_name: sailing.value.companyName,\r\n item_category: capitalizeFirst(ProductType.CRUISES.slice(0,-1))\r\n });\r\n }\r\n\r\n function hasOnlyOnePostPackage(): boolean {\r\n return (sailing.value.prePackages.length === 0 && sailing.value.addOnDayTours.length === 0 && sailing.value.postPackages.length === 1);\r\n }\r\n\r\n function hasOnlyOnePrePackage(): boolean {\r\n return (sailing.value.prePackages.length === 1 && sailing.value.addOnDayTours.length === 0 && sailing.value.postPackages.length === 0);\r\n }\r\n\r\n function hasOnlyOneShoreExcursion(): boolean {\r\n return (sailing.value.prePackages.length === 0 && sailing.value.addOnDayTours.length === 1 && sailing.value.postPackages.length === 0);\r\n }\r\n\r\n function hydratePrePostPackages(packages: AddOnPackage[]): TourEvent[] {\r\n const hydratedPackages: TourEvent[] = [];\r\n packages.forEach((pkg: AddOnPackage) => {\r\n const tourEventId = parseInt(pkg.packageMasterEntityID, 10);\r\n hydratedPackages.push({\r\n companyName: pkg.supplierName,\r\n description: sanitizeUserGeneratedContent(pkg.packageDescription),\r\n eventDate: pkg.packageDateFormatted,\r\n eventLength: pkg.packageLength,\r\n id: tourEventId,\r\n location: pkg.locationFormatted,\r\n name: pkg.packageName,\r\n thumbnailImageUrl: (pkg.imageLibraryItems && pkg.imageLibraryItems[0].url) ? generateMediaServerImageUrl(pkg.imageLibraryItems[0].url, { width: 230 }) : \"https://virtuoso-prod.dotcms.cloud/images/photo-coming-soon-148x110.png\",\r\n url: cobrandLink(`/packages/${pkg.packageMasterEntityID}/${slugify(pkg.packageName)}`),\r\n virtuosoHotel: (pkg.supportingPropertyName) ? pkg.supportingPropertyName : \"\"\r\n });\r\n });\r\n return hydratedPackages;\r\n }\r\n\r\n function loadOverview(): void {\r\n const sailingOverview = window.VIRTUOSO.sailingOverviewData;\r\n\r\n if (sailingOverview && sailingOverview.CruiseName) {\r\n loadSailing(); // Start the full load ASAP\r\n\r\n const sailingCruiseLength = (sailingOverview.CruiseLength).replace(\"days\", \"Days\");\r\n const thisSailing: ProductDetailsSailing = {\r\n addOnDayTours: [],\r\n benefitsHostedGroups: [],\r\n benefitsHQGroups: [],\r\n benefitsExclusive: [],\r\n brandUrl: \"\",\r\n companyId: (sailingOverview.SupplierId) ? parseInt(sailingOverview.SupplierId, 10) : 0,\r\n companyName: sailingOverview.SupplierName || \"\",\r\n cruiseDepartureCode: \"\",\r\n cruiseId: sailingOverview.MasterCruiseId,\r\n cruiseMapUrl: \"\",\r\n cruiseName: `${sailingOverview.CruiseName} (${sailingCruiseLength.replace(\" \", \" \")})`,\r\n cruisePorts: `${sailingOverview.DeparturePort} to ${sailingOverview.ArrivalPort}`,\r\n cruiseTypeByShip: \"\",\r\n featuredImageUrl: sailingOverview.FeaturedImage || \"https://virtuoso-prod.dotcms.cloud/images/image-not-available-results-266x200.png\",\r\n featuredVideoUrl: sailingOverview.FeaturedVideoUrl,\r\n id: props.productId,\r\n isCruiseTour: false,\r\n itinerary: [],\r\n postPackages: [],\r\n prePackages: [],\r\n promotions: [],\r\n sailings: [],\r\n shipId: 0,\r\n shipName: sailingOverview.ShipName,\r\n shipUrl: \"\",\r\n travelDates: `${sailingOverview.DepartureDate} to ${sailingOverview.ReturnDate}`,\r\n travelLength: sailingCruiseLength,\r\n whatIsIncluded: \"\",\r\n whatIsNotIncluded: \"\"\r\n };\r\n\r\n heartableUrl.value = `https://www.virtuoso.com/travel/luxury-cruises/cruises/${sailingOverview.MasterCruiseId}/${slugify(sailingOverview.CruiseName)}`;\r\n\r\n sailing.value = thisSailing;\r\n cruiseTitle.value = `${sailingOverview.SupplierName} - ${sailingOverview.CruiseName} (${(sailingOverview.CruiseLength)})`;\r\n\r\n topSplashData.value = {\r\n companyName: thisSailing.companyName,\r\n featuredImageCaption: thisSailing.featuredImageCaption,\r\n featuredImageUrl: thisSailing.featuredImageUrl,\r\n featuredVideoUrl: thisSailing.featuredVideoUrl,\r\n productName: thisSailing.cruiseName,\r\n productType: ProductType.CRUISES,\r\n wanderlistId: heartableUrl.value,\r\n wanderlistTitle: cruiseTitle.value\r\n };\r\n\r\n isOverviewReady.value = true;\r\n\r\n nextTick(() => {\r\n enableHearts(productDetailRef.value);\r\n });\r\n } else {\r\n redirectOnError();\r\n }\r\n }\r\n\r\n function loadSailing(): void {\r\n getProductDetails(ProductType.CRUISES, props.productId).then((resultSailing: ProductDetailsCruiseResponse) => {\r\n\r\n if (resultSailing && resultSailing.companyName && resultSailing.companyName) {\r\n\r\n const cruiseLength = (resultSailing.cruiseLength).replace(\"days\", \"Days\");\r\n\r\n const thisSailing: ProductDetailsSailing = {\r\n addOnDayTours: [],\r\n benefitsHostedGroups: [],\r\n benefitsHQGroups: [],\r\n benefitsExclusive: [],\r\n brandUrl: cobrandLink(`/travel/luxury-cruises/cruise-lines/${resultSailing.companyId}/${slugify(resultSailing.companyName)}`),\r\n companyId: resultSailing.companyId || 0,\r\n companyName: resultSailing.companyName || \"\",\r\n cruiseDepartureCode: resultSailing.cruiseDepartureCode || \"\",\r\n cruiseId: `${resultSailing.cruiseId}`,\r\n cruiseMapUrl: resultSailing.cruiseLargeImagePath || \"\",\r\n cruiseName: `${resultSailing.cruiseName} (${cruiseLength.replace(\" \", \" \")})`,\r\n cruisePorts: resultSailing.cruisePorts,\r\n cruiseTypeByShip: resultSailing.cruiseTypeByShip,\r\n featuredImageUrl: (sailing.value.featuredImageUrl) ? sailing.value.featuredImageUrl : \"https://virtuoso-prod.dotcms.cloud/images/image-not-available-results-266x200.png\", // fallback, replaced later\r\n featuredVideoUrl: (sailing.value.featuredVideoUrl) ? sailing.value.featuredVideoUrl : \"\",\r\n id: props.productId,\r\n isCruiseTour: (resultSailing.cruiseType && resultSailing.cruiseType.toLowerCase() === \"cruisetour\") ? true : false,\r\n itinerary: [],\r\n postPackages: [],\r\n prePackages: [],\r\n promotions: [],\r\n sailings: [],\r\n shipId: resultSailing.shipId,\r\n shipName: resultSailing.shipName || \"\",\r\n shipUrl: cobrandLink(`/travel/luxury-cruises/ships/${resultSailing.shipId}/${slugify(resultSailing.shipName)}`),\r\n travelDates: resultSailing.travelDates,\r\n travelLength: cruiseLength,\r\n whatIsIncluded: \"\",\r\n whatIsNotIncluded: \"\"\r\n };\r\n\r\n // What's (Not) Included\r\n if (resultSailing.whatIsIncludedItems) {\r\n if (resultSailing.whatIsIncludedItems.length > 1) {\r\n let theItems = \"\";\r\n resultSailing.whatIsIncludedItems.forEach((item: string) => {\r\n theItems += `<li>${item}</li>`;\r\n });\r\n thisSailing.whatIsIncluded = `<ul>${theItems}</ul>`;\r\n } else {\r\n thisSailing.whatIsIncluded = sanitizeUserGeneratedContent(resultSailing.whatIsIncludedItems[0]);\r\n }\r\n }\r\n\r\n if (resultSailing.whatIsNotIncludedItems) {\r\n if (resultSailing.whatIsNotIncludedItems.length > 1) {\r\n let theItems = \"\";\r\n resultSailing.whatIsNotIncludedItems.forEach((item: string) => {\r\n theItems += `<li>${item}</li>`;\r\n });\r\n thisSailing.whatIsNotIncluded = `<ul>${theItems}</ul>`;\r\n } else {\r\n thisSailing.whatIsNotIncluded = sanitizeUserGeneratedContent(resultSailing.whatIsNotIncludedItems[0]);\r\n }\r\n }\r\n\r\n // Images -- first image is the featured image\r\n let galleryImages: GalleryItem[] = [];\r\n if (resultSailing.imageLibraryItems && resultSailing.imageLibraryItems.length) {\r\n galleryImages = hydrateImageGallery(resultSailing.imageLibraryItems);\r\n thisSailing.featuredImageUrl = galleryImages[0].image;\r\n thisSailing.featuredImageCaption = galleryImages[0].description;\r\n }\r\n thisSailing.galleryImages = galleryImages;\r\n\r\n\r\n // Featured Video\r\n if (resultSailing.supplierVideos && resultSailing.supplierVideos.length) {\r\n const featuredVideo = resultSailing.supplierVideos.find((video) => video.isFeaturedVideo);\r\n if (featuredVideo) {\r\n thisSailing.featuredVideoCaption = featuredVideo.title;\r\n if (featuredVideo.webContentURL !== sailing.value.featuredVideoUrl) {\r\n thisSailing.featuredVideoUrl = featuredVideo.webContentURL; // Don't \"overwrite\" the property if it is the same from the overview, worried about the video resetting\r\n }\r\n }\r\n }\r\n\r\n\r\n // Other sailings, if populated\r\n if (resultSailing.sailings && resultSailing.sailings.length && resultSailing.sailings.length > 1) {\r\n resultSailing.sailings.forEach((sailing) => {\r\n if (sailing.masterEntityId && sailing.travelDates && sailing.detailUrl) {\r\n thisSailing.sailings.push({\r\n id: sailing.masterEntityId,\r\n travelDates: sailing.travelDates,\r\n url: cobrandLink(translateToConsumerUrl(sailing.detailUrl))\r\n });\r\n }\r\n });\r\n }\r\n\r\n\r\n // Itinerary\r\n if (resultSailing.itineraryPoints && resultSailing.itineraryPoints.length) {\r\n let previousDay = 0;\r\n let currentDay = 0;\r\n resultSailing.itineraryPoints.forEach((it) => {\r\n currentDay = it.dayOfCruise;\r\n if (it.dayOfCruise !== previousDay) {\r\n thisSailing.itinerary.push({\r\n dateFormatted: it.segmentDate,\r\n description: \"\",\r\n itineraryDay: currentDay,\r\n stops: []\r\n });\r\n previousDay = currentDay;\r\n }\r\n if (thisSailing.itinerary[currentDay - 1]) { // Sanity check, just in case\r\n thisSailing.itinerary[currentDay - 1].stops.push({\r\n isOnLand: it.isOnLand,\r\n location: it.portName,\r\n timeArrive: (it.timeStart && it.timeStart !== \"00:00:00\") ? it.timeStart : \"\",\r\n timeDepart: (it.timeEnd && it.timeEnd !== \"00:00:00\") ? it.timeEnd : \"\"\r\n });\r\n if (it.description && it.description !== \"\") {\r\n thisSailing.itinerary[currentDay - 1].description = sanitizeUserGeneratedContent(it.description);\r\n }\r\n }\r\n });\r\n }\r\n\r\n\r\n // Benefits\r\n if (resultSailing.cruiseBenefits && resultSailing.cruiseBenefits.benefitPrograms && resultSailing.cruiseBenefits.benefitPrograms.length) {\r\n resultSailing.cruiseBenefits.benefitPrograms.forEach((benefitGroup) => {\r\n if (benefitGroup.name === \"Exclusive Benefits\") {\r\n if (benefitGroup.benefitGroups && benefitGroup.benefitGroups.length) {\r\n benefitGroup.benefitGroups.forEach((ben) => {\r\n if (ben.sortedBenefits && ben.sortedBenefits.length) {\r\n thisSailing.benefitsExclusive = populateBenefits(ben.sortedBenefits);\r\n }\r\n });\r\n }\r\n\r\n } else if (benefitGroup.name === \"HQ Group Benefits\" && isNetworkUser()) {\r\n if (benefitGroup.benefitGroups && benefitGroup.benefitGroups.length) {\r\n benefitGroup.benefitGroups.forEach((ben) => {\r\n if (ben.sortedBenefits && ben.sortedBenefits.length) {\r\n thisSailing.benefitsHQGroups.push({\r\n benefits: populateBenefits(ben.sortedBenefits),\r\n visibilityCountries: (ben.visibilityCountries || \"\").replace(\",\", \", \")\r\n });\r\n }\r\n });\r\n }\r\n\r\n } else if (benefitGroup.name === \"Virtuoso Voyages Hosted Benefits\") {\r\n if (benefitGroup.benefitGroups && benefitGroup.benefitGroups.length) {\r\n benefitGroup.benefitGroups.forEach((hbg) => {\r\n\r\n const hostedBenefits: CruiseBenefit[] = [];\r\n const forAllBenefits: CruiseBenefit[] = [];\r\n const hostList: string[] = [];\r\n const hostTitle = (hbg.hosts && hbg.hosts.length && hbg.hosts[0].hostedAssignedName !== \"To Be Assigned\") ? `Your Onboard Host${getPlural(hbg.hosts.length)}: ` : \"Host \";\r\n\r\n if (hbg.sortedBenefits && hbg.sortedBenefits.length) {\r\n hbg.sortedBenefits.forEach((hb) => {\r\n const tempBenefit: CruiseBenefit =\r\n {\r\n activityLevel: hb.activityLevel || \"\",\r\n ageAppropriateness: hb.ageAppropriateness || \"\",\r\n availability: hb.displayedAvailability,\r\n benefitDate: hb.benefitDate,\r\n duration: hb.benefitDuration || \"\",\r\n capacity: hb.capacity,\r\n description: sanitizeUserGeneratedContent(hb.benefitDescription),\r\n experiences: (hb.activityType) ? hb.activityType.slice(0, -1).replaceAll(\"|\", \" • \") : \"\",\r\n id: hb.benefitId,\r\n isCarAndDriver: hb.isCarAndDriver,\r\n isEvent: hb.isEvent,\r\n isForAllGuests: hb.isForAllGuests,\r\n isOption: hb.isOption,\r\n location: hb.benefitLocation,\r\n meals: hb.meals || \"\",\r\n notice: (hb.notice && !hb.isForAllGuests) ? hb.notice : \"\", // Only display the notice for hosted chosen benefits\r\n name: hb.benefitName,\r\n thumbnailImageUrl: hb.thumbnailImageUrl,\r\n waitlist: hb.displayedWaitlist || \"\"\r\n };\r\n\r\n if (hb.isForAllGuests) {\r\n forAllBenefits.push(tempBenefit);\r\n } else {\r\n hostedBenefits.push(tempBenefit);\r\n }\r\n\r\n hasBenefits.value = true;\r\n });\r\n }\r\n\r\n if (hbg.hosts && hbg.hosts.length) {\r\n hbg.hosts.forEach((host) => {\r\n if (host.hostedAssignedName) {\r\n const memberName = (host.memberName) ? `, ${host.memberName}` : \"\";\r\n hostList.push(host.hostedAssignedName + memberName);\r\n }\r\n });\r\n }\r\n\r\n thisSailing.benefitsHostedGroups.push({\r\n dates: (hbg.benefitGroupStartDate !== hbg.benefitGroupEndDate) ? `${hbg.benefitGroupStartDate} - ${hbg.benefitGroupEndDate}` : hbg.benefitGroupStartDate,\r\n forAllBenefits: forAllBenefits,\r\n hostedBenefits: hostedBenefits,\r\n hosts: hostTitle + hostList.join(\", \")\r\n });\r\n\r\n if (hostedBenefits.length > 1) { // This controls whether we initialize the carousel or not\r\n hasMultipleHostedBenefits = true;\r\n }\r\n });\r\n }\r\n }\r\n });\r\n }\r\n\r\n\r\n // Add Ons -- Day tours and pre/post packages\r\n if (resultSailing.addOns && resultSailing.addOns.hasAddOns) {\r\n // Pre Packages\r\n if (resultSailing.addOns.prePackages && resultSailing.addOns.prePackages.length) {\r\n thisSailing.prePackages = hydratePrePostPackages(resultSailing.addOns.prePackages);\r\n }\r\n\r\n // Post Packages\r\n if (resultSailing.addOns.postPackages && resultSailing.addOns.postPackages.length) {\r\n thisSailing.postPackages = hydratePrePostPackages(resultSailing.addOns.postPackages);\r\n }\r\n\r\n // Day Tours\r\n if (resultSailing.addOns.addOnDayTours && resultSailing.addOns.addOnDayTours.length) {\r\n const dayTours: AddOnDayTours[] = [];\r\n const validDayTourSegments = resultSailing.addOns.addOnDayTours.filter((dayTour) => {\r\n return dayTour.addOnDate && dayTour.addOnLocation && dayTour.dayTours;\r\n });\r\n\r\n validDayTourSegments.forEach((segment, segmentIndex: number) => {\r\n if (segment.dayTours && segment.dayTours.length) {\r\n dayTours.push({ // First add the date+location segment\r\n addOnDate: segment.addOnDate,\r\n addOnLocation: segment.addOnLocation,\r\n addOns: []\r\n });\r\n\r\n segment.dayTours.forEach((pkg) => { // Then loop through the tours for that segment and add them\r\n dayTours[segmentIndex].addOns.push({\r\n activityLevel: pkg.activityLevelList || \"\",\r\n ageAppropriateness: pkg.ageAppropriatenessList || \"\",\r\n companyName: pkg.supplierName,\r\n description: sanitizeUserGeneratedContent(pkg.dayTourDescription),\r\n eventDate: pkg.dayTourDateFormatted,\r\n eventLength: pkg.dayTourLength,\r\n experiences: (pkg.bulletedActivityTypes) ? pkg.bulletedActivityTypes : \"\",\r\n id: parseInt(pkg.dayTourMasterEntityID, 10), // cast to MEID string to number id\r\n location: pkg.locationFormatted,\r\n meals: pkg.mealsFormatted || \"\",\r\n name: pkg.dayTourName,\r\n thumbnailImageUrl: pkg.thumbnailImageUrl || \"\",\r\n url: cobrandLink(`/travel/luxury-tours/${pkg.dayTourMasterEntityID}/${slugify(pkg.dayTourName)}`),\r\n virtuosoHotel: \"\"\r\n });\r\n });\r\n }\r\n });\r\n thisSailing.addOnDayTours = dayTours;\r\n }\r\n }\r\n\r\n\r\n // Promotions -- Virtuoso Exclusive Promotions added first\r\n if (resultSailing.promotions && resultSailing.promotions.length) {\r\n const visiblePromotions: Promotion[] = [];\r\n resultSailing.promotions.forEach((promo) => {\r\n if (!promo.isAdvisorIncentive && !promo.incentiveTypeCode) {\r\n visiblePromotions.push({\r\n description: promo.description || \"\",\r\n endDateMS: (promo.travelEndDate) ? new Date(promo.travelEndDate).getTime() : 0,\r\n formattedTravelDates: promo.formattedTravelDates || \"\",\r\n isExclusive: false,\r\n name: promo.promotionName || \"\",\r\n promotionId: promo.masterEntityId,\r\n startDateMS: (promo.travelStartDate) ? new Date(promo.travelStartDate).getTime() : 0,\r\n url: (promo.masterEntityId) ? cobrandLink(`/promotions/${promo.masterEntityId}/${slugify(promo.promotionName)}`) : \"\"\r\n });\r\n }\r\n });\r\n\r\n thisSailing.promotions = visiblePromotions;\r\n topPromoName = (thisSailing.promotions.length) ? thisSailing.promotions[0].name : \"\";\r\n }\r\n\r\n\r\n sailing.value = thisSailing;\r\n topSplashData.value.promotion = thisSailing.promotions[0] ?? topSplashData.value.promotion; // Update for promos\r\n\r\n // Recommended Advisors\r\n recommendedAdvisorQuery.value = {\r\n Id: thisSailing.id,\r\n InterestType: thisSailing.cruiseTypeByShip,\r\n ProductLocationCountry: \"\",\r\n ProductPois: \"\",\r\n ProductTypeName: \"Cruise\"\r\n } as RecommendedAdvisorQuery;\r\n\r\n\r\n isOverviewReady.value = true;\r\n isReady.value = true;\r\n \r\n nextTick(() => {\r\n // Onload Tab support\r\n showThenJumpToTab();\r\n enableHearts(productDetailRef.value);\r\n\r\n\r\n if (hasOnlyOnePostPackage()) {\r\n autoExpandAndToggle(\"postpackages\");\r\n }\r\n\r\n if (hasOnlyOnePrePackage()) {\r\n autoExpandAndToggle(\"prepackages\");\r\n }\r\n\r\n if (hasOnlyOneShoreExcursion()) {\r\n autoExpandAndToggle(\"shore-excursion-0\");\r\n }\r\n });\r\n\r\n } else {\r\n redirectOnError();\r\n }\r\n }, () => redirectOnError());\r\n }\r\n \r\n function populateBenefits(rawBenefits: SortedBenefits[]): CruiseBenefit[] {\r\n\r\n let benefitGroup: CruiseBenefit[] = [];\r\n rawBenefits.forEach((eb) => {\r\n benefitGroup.push({\r\n description: sanitizeUserGeneratedContent(eb.benefitDescription),\r\n id: eb.benefitId,\r\n isCarAndDriver: eb.isCarAndDriver,\r\n isEvent: eb.isEvent,\r\n isForAllGuests: eb.isForAllGuests,\r\n isOption: eb.isOption,\r\n name: eb.benefitName,\r\n notice: \"\", // Never display the notice for Exclusive benefits\r\n thumbnailImageUrl: eb.thumbnailImageUrl\r\n });\r\n hasBenefits.value = true;\r\n });\r\n return benefitGroup;\r\n }\r\n\r\n function redirectOnError(): void {\r\n toastError(\"Error retrieving data\");\r\n setTimeout(() => {\r\n location.href = cobrandLink(`/travel/luxury-cruises`);\r\n }, 3000);\r\n }\r\n\r\n function setRecommendedAdvisorIds(advisor1MEID?: number, advisor2MEID?: number): void {\r\n // get cruise index key in sessionStorage\r\n const getVirtuosoCruiseSearchIndexValue = sessionStorage.getItem(`VirtuosoCruiseSearchIndex_${props.productId}`);\r\n\r\n if (getVirtuosoCruiseSearchIndexValue) {\r\n // then delete cruise index key in sessionStorage\r\n sessionStorage.removeItem(`VirtuosoCruiseSearchIndex_${props.productId}`);\r\n }\r\n const cruise: ProductDetailsSailing = sailing.value;\r\n\r\n trackEvent(\"view_item\", {\r\n ...((getVirtuosoCruiseSearchIndexValue) && { index: parseInt(getVirtuosoCruiseSearchIndexValue, 10) }), // should add index with value if session key existed\r\n item_id: `${props.productId}`,\r\n item_name: cruise.companyName,\r\n coupon: (cruise.promotions.length >= 1) ? \"Promotion Available\" : \"\",\r\n item_category: \"Cruise\",\r\n item_category2: `${cruise.shipName}`,\r\n item_category3: cruise.travelLength,\r\n item_category4: (advisor1MEID) ? `${advisor1MEID}` : \"\",\r\n item_category5: (advisor2MEID) ? `${advisor2MEID}` : \"\"\r\n });\r\n }\r\n\r\n function scrollToTabResponsive(tabName = \"\"): void {\r\n if (isMobileScreenWidth()) {\r\n return (tabName) ? document.getElementById(`tc-${tabName}`)?.scrollIntoView()\r\n : document.querySelector(\".tab-content .-active\")?.scrollIntoView({ block: \"start\" });\r\n }\r\n tabNavContainerRef.value.scrollIntoView();\r\n }\r\n\r\n function showTab(tabName: string, preventJump = false, fromTopLink = false): void {\r\n document.querySelectorAll(\".tab-nav-container .-active, .tab-content .-active\").forEach((el) => el.classList.remove(\"-active\"));\r\n document.querySelectorAll(`#tab-${tabName}, #tc-${tabName}`).forEach((el) => el.classList.add(\"-active\"));\r\n\r\n if (!preventJump) {\r\n scrollToTabResponsive(tabName);\r\n }\r\n\r\n if (tabName === \"benefits\") {\r\n if (hasMultipleHostedBenefits && !isBenefitsCarouselInitialized.value) { \r\n isBenefitsCarouselInitialized.value = true;\r\n }\r\n }\r\n\r\n if (fromTopLink) {\r\n\r\n trackEvent(\"view_promotion\", {\r\n item_id: `${sailing.value.promotions[0].promotionId}`,\r\n item_name: topPromoName,\r\n item_category: \"Promotion\",\r\n item_variant: \"Cruise\",\r\n item_category2: \"Promo Visibility: All\",\r\n affiliation: `${props.productId}`\r\n });\r\n }\r\n\r\n }\r\n\r\n function showThenJumpToTab(): void {\r\n\r\n const qsTabLabel = qsParams[\"tab\"];\r\n\r\n nextTick(() => { // Tab Show then Scroll\r\n // Show \r\n showTab(\"itinerary\", true); // default first tab\r\n\r\n if (qsTabLabel && document.getElementById(`tc-${qsTabLabel}`)) { // check qsParam tab exists\r\n showTab(qsTabLabel, true);\r\n }\r\n // Show Promo Manual Tab\r\n if (qsParams.promotions === \"1\") { // If the manually added ?promotions=1 query string variable is present, scroll to the promotions section on load\r\n const qsPromoTabName = (sailing.value.promotions.length) ? \"promotions\" : \"itinerary\";\r\n showTab(qsPromoTabName, true);\r\n }\r\n\r\n // Default Scroll\r\n if ((\"tab\" in qsParams) || (\"promotions\" in qsParams)) {\r\n scrollToTabResponsive();\r\n }\r\n });\r\n }\r\n\r\n function toggleAddOn(id: string): void {\r\n const btn = document.getElementById(`add-on-toggle-${id}`);\r\n const container = document.getElementById(`add-on-container-${id}`);\r\n\r\n\r\n btn.classList.toggle(\"icon-plus-circle-ut\");\r\n btn.classList.toggle(\"icon-minus-circle-ut\");\r\n toggleSlideWithFade(container, 500);\r\n }\r\n\r\n loadOverview(); // Immediately load the summary\r\n</script>\r\n","<template>\r\n <div class=\"slab product-features mt-6\">\r\n <div class=\"container\">\r\n <h2 class=\"text--serif\">At the Hotel</h2>\r\n <template v-for=\"hotelFeatureKey in Object.keys(hotelFeatures)\">\r\n <div v-if=\"hotelFeatures[hotelFeatureKey as keyof ProductDetailHotelFeatures].length\" :key=\"hotelFeatureKey\" class=\"-category\">\r\n <h4>{{ hotelFeatureCategoryMap.get(hotelFeatureKey) }}</h4>\r\n <ul>\r\n <li v-for=\"feature in hotelFeatures[hotelFeatureKey as keyof ProductDetailHotelFeatures]\" :key=\"feature\">\r\n <i :class=\"hotelFeatureIconMap.get(`${hotelFeatureKey}-${feature}`) || 'icon-check-ut'\"></i>\r\n {{ feature }}\r\n </li>\r\n </ul>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { ProductDetailHotelFeatures } from \"interfaces/responses/product-detail-responses\";\r\n import { PropType } from \"vue\";\r\n\r\n defineProps({\r\n hotelFeatures: {\r\n type: Object as PropType<ProductDetailHotelFeatures>,\r\n default: () => ({})\r\n }\r\n });\r\n\r\n const hotelFeatureCategoryMap = new Map<string, string>([\r\n [\"features\" , \"Features\" ],\r\n [\"inYourRoom\" , \"In Your Room\"],\r\n [\"recreation\" , \"Recreation\" ],\r\n [\"services\" , \"Services\" ]\r\n ]);\r\n\r\n const hotelFeatureIconMap = new Map<string, string>([\r\n [\"features-24-hour Security\" , \"icon-lock\" ],\r\n [\"features-Adults Only\" , \"icon-adult\" ],\r\n [\"features-Air Conditioning\" , \"icon-air-conditioner\" ],\r\n [\"features-All-inclusive\" , \"icon-all-inclusive\" ],\r\n [\"features-Banquet Facilities\" , \"icon-banquet\" ],\r\n [\"features-Business Center\" , \"icon-printer\" ],\r\n [\"features-Casino\" , \"icon-coins\" ],\r\n [\"features-Children's Programs\" , \"icon-children\" ],\r\n [\"features-Club Floor\" , \"icon-disco\" ],\r\n [\"features-Complimentary Parking\" , \"icon-parking\" ],\r\n [\"features-Conference Facilities\" , \"icon-networking\" ],\r\n [\"features-Connecting Rooms\" , \"icon-connected-rooms\" ],\r\n [\"features-Culinary Program\" , \"icon-culinary\" ],\r\n [\"features-Internet Access\" , \"icon-connection\" ],\r\n [\"features-Library\" , \"icon-library\" ],\r\n [\"features-Lounge/Bar\" , \"icon-lounge\" ],\r\n [\"features-Meal Plans Available\" , \"icon-meal-plans\" ],\r\n [\"features-Michelin Star Restaurant\" , \"icon-michelin\" ],\r\n [\"features-Multi-lingual Staff\" , \"icon-multi-lingual\" ],\r\n [\"features-Nightclub\" , \"icon-disco\" ],\r\n [\"features-Parking\" , \"icon-parking\" ],\r\n [\"features-Pet Friendly\" , \"icon-paw\" ],\r\n [\"features-Restaurant\" , \"icon-restaurant\" ],\r\n [\"features-Shopping\" , \"icon-shopping\" ],\r\n [\"features-Villas\" , \"icon-villa\" ],\r\n [\"features-Wheelchair Accessible\" , \"icon-wheelchair\" ],\r\n [\"inYourRoom-Bathrobes\" , \"icon-bathrobe\" ],\r\n [\"inYourRoom-Butler Service\" , \"icon-butler\" ],\r\n [\"inYourRoom-Complimentary Coffee/Tea\" , \"icon-tea\" ],\r\n [\"inYourRoom-Complimentary Newspaper\" , \"icon-newspaper\" ],\r\n [\"inYourRoom-Hair Dryer\" , \"icon-hair-dryer\" ],\r\n [\"inYourRoom-Iron/Ironing Board\" , \"icon-iron\" ],\r\n [\"inYourRoom-Mini Bar\" , \"icon-mini-bar\" ],\r\n [\"inYourRoom-Safe\" , \"icon-safe\" ],\r\n [\"inYourRoom-Slippers\" , \"icon-slippers\" ],\r\n [\"recreation-Bicycle Rental\" , \"icon-bicycle\" ],\r\n [\"recreation-Fitness Center\" , \"icon-fitness\" ],\r\n [\"recreation-Golf\" , \"icon-golf\" ],\r\n [\"recreation-Horseback Riding\" , \"icon-horseback-riding\"],\r\n [\"recreation-Jacuzzi/Whirlpool\" , \"icon-jacuzzi1\" ],\r\n [\"recreation-Pool\" , \"icon-pool\" ],\r\n [\"recreation-Sauna\" , \"icon-sauna\" ],\r\n [\"recreation-Skiing\" , \"icon-skiing\" ],\r\n [\"recreation-Snow Sports\" , \"icon-snow-sports\" ],\r\n [\"recreation-Spa\" , \"icon-spa\" ],\r\n [\"recreation-Tennis Courts\" , \"icon-tennis\" ],\r\n [\"recreation-Water Sports\" , \"icon-water-sports\" ],\r\n [\"services-Babysitting Services\" , \"icon-babysitting\" ],\r\n [\"services-Car Rental Desk\" , \"icon-car\" ],\r\n [\"services-Complimentary Airport Transfers\", \"icon-shuttle\" ],\r\n [\"services-Concierge Desk\" , \"icon-concierge\" ],\r\n [\"services-Currency Exchange\" , \"icon-exchange\" ],\r\n [\"services-Hearing Impaired Services\" , \"icon-hearing-impaired\"],\r\n [\"services-House Safe\" , \"icon-house-safe\" ],\r\n [\"services-Housekeeping -- Twice Daily\" , \"icon-housekeeping\" ],\r\n [\"services-Laundry/Dry Cleaning/Pressing\" , \"icon-laundry\" ],\r\n [\"services-Limousine Service\" , \"icon-limo\" ],\r\n [\"services-Room Service\" , \"icon-room-service\" ],\r\n [\"services-Room Service 24-hours\" , \"icon-room-service\" ],\r\n [\"services-Salon\" , \"icon-salon\" ],\r\n [\"services-Shoeshine Service\" , \"icon-shoe\" ],\r\n [\"services-Turndown Service\" , \"icon-turndown\" ],\r\n [\"services-Valet Parking\" , \"icon-valet\" ],\r\n [\"services-Wakeup Calls\" , \"icon-wakeup-call\" ],\r\n [\"services-Wedding Services\" , \"icon-wedding\" ]\r\n ]);\r\n</script>\r\n","<template>\r\n <ul v-if=\"rooms.length\" class=\"hotel-guest-rooms generic-cards -twocols -mobile-horizontal-scroll\" aria-label=\"room list\">\r\n <li v-for=\"(room, index) in rooms\" :key=\"index\" class=\"guest-room-card\">\r\n <div class=\"bg-white p-2 shadow-sm h-100\">\r\n <h4 class=\"fw-bold\">{{ room.roomTypeName }}</h4>\r\n <div class=\"d-flex gap-3 text-16 flex-column flex-md-row\">\r\n <div class=\"-room-description\" v-html=\"room.descriptionHtml\"></div>\r\n <div class=\"-room-features\">\r\n <ul aria-label=\"room feature list\">\r\n <li v-for=\"(feature, feature_idx) in getRoomFeatures(room.featureGroups)\" :key=\"feature_idx\" class=\"m-0 mw-100\">\r\n {{ feature }}\r\n </li>\r\n </ul>\r\n <button class=\"btn btn-sm btn-tertiary mt-2\" @click=\"(e) => handleViewMoreClick(e, index)\">\r\n View More\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n\r\n <ModalShell id=\"hotel-room-sidebar\" :open=\"showRoomSidebar\" modal-style=\"sidebar\" @close=\"showRoomSidebar=false\">\r\n <div class=\"d-flex flex-column h-100\">\r\n <div ref=\"sidebar-room-details\" class=\"p-5 text-16 overflow-auto\">\r\n <h4 class=\"text-16 text-emphasis m-0\">{{ hotelName }}</h4>\r\n <h4>{{ activeRoom.roomTypeName }}</h4>\r\n\r\n <div class=\"py-3\" v-html=\"activeRoom.descriptionHtml\"></div>\r\n\r\n <div>\r\n <template v-for=\"(featureGroup, fgIndex) in activeRoom.featureGroups\" :key=\"fgIndex\">\r\n <template v-if=\"featureGroup.features.length\">\r\n <div class=\"fw-bold mt-3\">{{ featureGroup.groupName }}</div>\r\n <ul class=\"room-feature-list d-flex flex-wrap\">\r\n <li v-for=\"(feature, fIndex) in featureGroup.features\" :key=\"fIndex\" class=\"mt-1\">{{ feature }}</li>\r\n </ul>\r\n </template>\r\n </template>\r\n </div>\r\n </div>\r\n <div v-if=\"rooms.length > 1\" class=\"room-nav-buttons p-2 d-flex align-items-center w-100 mt-auto justify-content-between shadow-sm\">\r\n <button class=\"btn btn-sm btn-tertiary d-flex align-items-center gap-1 flex-fill justify-content-center position-relative h-100\" @click=\"(e) => handleViewMoreClick(e, prevRoom.index)\">\r\n <i class=\"icon-angle-left text-16 position-absolute start-0 ps-2\"></i>\r\n <span class=\"ps-1\">{{ prevRoom.label }}</span>\r\n </button>\r\n <button class=\"btn btn-sm btn-tertiary d-flex align-items-center gap-1 flex-fill justify-content-center position-relative h-100\" @click=\"(e) => handleViewMoreClick(e, nextRoom.index)\">\r\n <span class=\"pe-1\">{{ nextRoom.label }}</span>\r\n <i class=\"icon-angle-right text-16 position-absolute end-0 pe-2\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </ModalShell>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import ModalShell from \"components/shared/modal-shell.vue\";\r\n import { GuestRoom, GuestRoomFeatureGroup } from \"interfaces/responses/product-detail-responses\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { trackEvent } from \"services/analytics\";\r\n import { capitalizeFirst } from \"virtuoso-shared-web-ui\";\r\n import { computed, PropType, ref, useTemplateRef } from \"vue\";\r\n\r\n\r\n const props = defineProps({\r\n hotelId: {\r\n type: Number,\r\n required: true\r\n },\r\n hotelName: {\r\n type: String,\r\n required: true\r\n },\r\n rooms: {\r\n type: Array as PropType<GuestRoom[]>,\r\n default: () => [] as GuestRoom[]\r\n }\r\n });\r\n\r\n const showRoomSidebar = ref(false);\r\n const activeItemIndex = ref(null);\r\n const sideRoomDetailsRef = useTemplateRef(\"sidebar-room-details\");\r\n const totalRooms = props.rooms.length;\r\n \r\n const activeRoom = computed(() => props.rooms[activeItemIndex.value]);\r\n\r\n const nextRoom = computed(() => {\r\n const targetIndex = (activeItemIndex.value < totalRooms -1) ? activeItemIndex.value + 1 : 0;\r\n\r\n return {\r\n label: props.rooms[targetIndex].roomTypeName,\r\n index: targetIndex\r\n };\r\n });\r\n\r\n const prevRoom = computed(() => {\r\n const targetIndex = (activeItemIndex.value > 0) ? activeItemIndex.value - 1 : totalRooms - 1;\r\n\r\n return {\r\n label: props.rooms[targetIndex].roomTypeName,\r\n index: targetIndex\r\n };\r\n });\r\n\r\n\r\n function getRoomFeatures(groups: GuestRoomFeatureGroup[], max = 6) {\r\n return groups.map((featureGroup) => featureGroup.features).flat().slice(0, max);\r\n }\r\n\r\n function handleViewMoreClick(event: Event, roomArrayIndex: number) {\r\n (event.currentTarget as HTMLElement).blur();\r\n activeItemIndex.value = roomArrayIndex;\r\n showRoomSidebar.value = true;\r\n\r\n sideRoomDetailsRef.value?.scrollTo({top: 0, behavior: \"smooth\"});\r\n\r\n trackEvent(\"view_room_details\", {\r\n item_id: props.hotelId.toString(),\r\n item_name: props.hotelName,\r\n item_category: capitalizeFirst(ProductType.HOTELS.slice(0,-1)),\r\n item_variant: activeRoom.value.roomTypeName\r\n });\r\n }\r\n\r\n</script>\r\n","<template>\r\n <div v-if=\"isReady\" ref=\"product-detail\" class=\"product-detail\">\r\n <top-splash-component :top-splash-data=\"topSplashData\" @promo-click=\"showTab('promotions', false, true)\"></top-splash-component>\r\n <div class=\"container mt-4\">\r\n <div class=\"product-detail-overview\">\r\n <div class=\"-info\">\r\n <h1 class=\"mb-3 weglot-exclude\" v-html=\"hotel.companyName\"></h1>\r\n <div v-if=\"hotel.location\" class=\"-location\" v-html=\"hotel.location\"></div>\r\n <div v-if=\"hotel.neighborhood\"><b>NEIGHBORHOOD:</b> {{ hotel.neighborhood }}</div>\r\n <div v-if=\"hotel.nearestAirport\"><b>NEAREST AIRPORT:</b> {{ hotel.nearestAirport }}</div>\r\n <div class=\"d-md-flex mt-0\">\r\n <div v-if=\"hotel.numberOfRooms\" class=\"me-md-3\"><b>SIZE:</b> {{ hotel.numberOfRooms }}</div>\r\n <div v-if=\"hotel.roomStyle\" class=\"me-md-3\"><b>ROOM STYLE:</b> {{ hotel.roomStyle }}</div>\r\n <div v-if=\"hotel.vibe\"><b>VIBE:</b> {{ hotel.vibe }}</div>\r\n </div>\r\n <hotel-experiences-component class=\"mt-3\" :experiences=\"hotel.experiences\"></hotel-experiences-component>\r\n <div class=\"-description mt-3\" v-html=\"hotel.bobDescription\"></div>\r\n </div>\r\n <div class=\"-gallery mt-5 mt-md-0 mb-6\">\r\n <image-gallery-component v-if=\"hotel.galleryImages && hotel.galleryImages.length\"\r\n :gallery-data=\"hotel.galleryImages\"\r\n :product-id=\"hotel.id.toString()\"\r\n :product-name=\"hotel.companyName\"\r\n :product-type=\"ProductType.HOTELS\"></image-gallery-component>\r\n <div v-if=\"hotel.tip\" class=\"advisor-tip mt-3 mb-0\">\r\n <h3>Insider Tip</h3>\r\n <div class=\"mt-2\" v-html=\"hotel.tip\"></div>\r\n </div>\r\n <recommended-advisors-component v-if=\"!suppressRecommendedAdvisors\" :advisor-query=\"recommendedAdvisorQuery\" :hide-cobranded-button=\"true\" :set-recommended-advisor-ids=\"setRecommendedAdvisorIds\" :product-type=\"ProductType.HOTELS\"></recommended-advisors-component>\r\n <a v-if=\"!isAdvisorOrVStaff && !isSupplier() && !isEmbeddedMode()\" id=\"b2blink-check-rates-availability\" class=\"btn btn-primary-emphasis btn-sm mt-3 d-block\" :href=\"bookingLink\" @click=\"handleLegacyLinkClick\">Check Rates and Availability</a>\r\n <a v-if=\"isSupplier() && !isEmbeddedMode()\" id=\"b2blink-update-your-profile\" class=\"btn btn-primary-emphasis btn-sm mt-3 d-none d-md-block\" :href=\"bookingLinkForAdvisors\" @click=\"handleLegacyLinkClick\">For Partners: Update Your Profile</a>\r\n <a v-if=\"isAdvisorOrVStaff && !isEmbeddedMode()\" id=\"b2blink-info-booking\" class=\"btn btn-primary-emphasis btn-sm mt-3 d-block\" :href=\"bookingLinkForAdvisors\" @click=\"handleLegacyLinkClick\">For Advisors: Info & Booking</a>\r\n <button class=\"wl-heartable -save-this mt-3\" data-wl-type=\"hotel\" :data-wl-id=\"productId\" :data-wl-title=\"hotel.companyName\" :data-wl-list-name=\"hotel.country\" aria-label=\"Save to Wanderlist\"></button>\r\n </div>\r\n <div v-if=\"hotel.amenities\" class=\"-amenities mt-4\">\r\n <h2 class=\"text--serif\">Virtuoso travelers receive:</h2>\r\n <div class=\"mt-2\" v-html=\"hotel.amenities\"></div>\r\n <div class=\"mt-3\">Many of these perks and amenities are only available when you book your stay through a Virtuoso travel advisor.</div>\r\n </div>\r\n </div>\r\n </div>\r\n <hotel-features-component v-if=\"hotel.hotelFeatures\" :hotel-features=\"hotel.hotelFeatures\"></hotel-features-component>\r\n <div class=\"container my-6\">\r\n <MapView type=\"product\" :product-map-config=\"productMapConfig\" />\r\n </div>\r\n <div v-if=\"showTabBlock\" id=\"detail-tabs\"></div>\r\n <div v-if=\"showTabBlock\" class=\"container d-none d-md-block\">\r\n <ul ref=\"tab-nav-container\" class=\"tab-nav-container\">\r\n <li v-if=\"hotel.guestRooms.length\" id=\"tab-rooms\"><button @click=\"showTab('rooms')\">Guest Rooms & Suites</button></li>\r\n <li v-if=\"hotel.promotions.length\" id=\"tab-promotions\"><button @click=\"showTab('promotions')\">Promotions & Packages</button></li>\r\n <li v-if=\"hotel.healthAndSafety\" id=\"tab-health\"><button @click=\"showTab('health')\">Health & Safety Protocols</button></li>\r\n <li v-if=\"hotel.reviewsCount > 0\" id=\"tab-reviews\"><button @click=\"showTab('reviews')\">Reviews & Recommendations</button></li>\r\n <li v-if=\"hasSustainabilityContent\" id=\"tab-sustainability\"><button @click=\"showTab('sustainability')\">Sustainability</button></li>\r\n </ul>\r\n </div>\r\n <div v-if=\"showTabBlock\" class=\"slab -tab-slab\">\r\n <div class=\"container\">\r\n <ul class=\"tab-content\">\r\n <li v-if=\"hotel.guestRooms.length\" id=\"tc-rooms\">\r\n <button class=\"tab-nav\" @click=\"showTab('rooms')\">Guest Rooms & Suites</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Guest Rooms & Suites</h4>\r\n <hotel-guest-rooms-component :hotel-name=\"hotel.companyName\" :hotel-id=\"hotel.id\" :rooms=\"hotel.guestRooms\"></hotel-guest-rooms-component>\r\n </div>\r\n </li>\r\n <li v-if=\"hotel.promotions.length\" id=\"tc-promotions\">\r\n <button class=\"tab-nav\" @click=\"showTab('promotions')\">Promotions & Packages</button>\r\n <div class=\"-container\">\r\n <h4 class=\"-title\">Promotions & Packages</h4>\r\n <promotions-component :product-type=\"ProductType.HOTELS\" :promotions=\"hotel.promotions\"></promotions-component>\r\n </div>\r\n </li>\r\n <li v-if=\"hotel.healthAndSafety\" id=\"tc-health\">\r\n <button class=\"tab-nav\" @click=\"showTab('health')\">Health & Safety Protocols</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Health & Safety Protocols</h4>\r\n <div v-html=\"hotel.healthAndSafety\"></div>\r\n </div>\r\n </li>\r\n <li v-if=\"hotel.reviewsCount > 0\" id=\"tc-reviews\">\r\n <button class=\"tab-nav\" @click=\"showTab('reviews')\">Reviews & Recommendations</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Reviews & Recommendations</h4>\r\n <product-reviews-component :company-id=\"hotel.companyId\" :product-type=\"ProductType.HOTELS\" :reviews-data=\"reviewsData\"></product-reviews-component>\r\n </div>\r\n </li>\r\n <li v-if=\"hasSustainabilityContent\" id=\"tc-sustainability\">\r\n <button class=\"tab-nav\" @click=\"showTab('sustainability')\">Sustainability</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Sustainability</h4>\r\n <template v-if=\"hotel.sustainability\">\r\n <h4 class=\"fw-bold\">Our Commitment</h4>\r\n <div class=\"mb-3\" v-html=\"hotel.sustainability\"></div>\r\n </template>\r\n <template v-if=\"hotel.sustainabilityCertifications.length > 0\">\r\n <h4 class=\"fw-bold\">Our Credentials</h4>\r\n <ul class=\"centered-list mb-3\">\r\n <li v-for=\"(cert, index) in hotel.sustainabilityCertifications\" :key=\"index\"><a :href=\"cert.url\" target=\"_blank\" v-html=\"cert.text\"></a></li>\r\n </ul>\r\n </template>\r\n <template v-if=\"hotel.sustainabilityVideoUrl\">\r\n <video controls playsinline :src=\"hotel.sustainabilityVideoUrl\" width=\"100%\"></video>\r\n </template>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n </div>\r\n <LogoSplash v-else />\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import HotelExperiencesComponent from \"components/products/details/hotel-experiences.vue\";\r\n import HotelFeaturesComponent from \"components/products/details/hotel-features.vue\";\r\n import HotelGuestRoomsComponent from \"components/products/details/hotel-guest-rooms.vue\";\r\n import PromotionsComponent from \"components/products/details/product-promotions.vue\";\r\n import ProductReviewsComponent from \"components/products/details/product-reviews.vue\";\r\n import RecommendedAdvisorsComponent from \"components/advisor/recommended-advisors.vue\";\r\n import ImageGalleryComponent from \"components/shared/image-gallery.vue\";\r\n import LogoSplash from \"components/shared/logo-splash.vue\";\r\n import MapView from \"components/shared/map-view.vue\";\r\n import TopSplashComponent from \"components/shared/top-splash.vue\";\r\n import { DotCMSProductDetailPageResponse } from \"interfaces/responses/dotcms-responses\";\r\n import { ProductDetailsHotelResponse } from \"interfaces/responses/product-detail-responses\";\r\n import { RecommendedAdvisorQuery } from \"interfaces/advisor\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { ProductDetailsHotel } from \"interfaces/hotel\";\r\n import { GalleryItem } from \"interfaces/image\";\r\n import { ProductTopSplash, Promotion } from \"interfaces/product\";\r\n import { getCmsContent } from \"services/api/cms\";\r\n import { getProductDetails } from \"services/api/products\";\r\n import { isAdvisor, isSupplier, isVStaff } from \"services/auth/user-info\";\r\n import { formatLocation } from \"services/helpers/destinations\";\r\n import { hydrateImageGallery } from \"services/helpers/images\";\r\n import { toastError } from \"services/helpers/toasts\";\r\n import { isEmbeddedMode, isMobileScreenWidth } from \"services/layout/environment\";\r\n import { renderMetadata, setB2BDesktopCookie } from \"services/layout/metadata\";\r\n import { getSustainabilityCerts } from \"services/transformers/products\";\r\n import { trackEvent } from \"services/analytics\";\r\n import { enableHearts } from \"services/wanderlist\";\r\n import { cobrandLink, getPlural, parseURLParameters } from \"virtuoso-shared-web-ui\";\r\n import { capitalizeFirst } from \"virtuoso-shared-web-ui\";\r\n import { nextTick, ref, useTemplateRef } from \"vue\";\r\n\r\n let topPromoName = \"\"; // Referenced by GA\r\n\r\n const qsParams = parseURLParameters();\r\n\r\n const props = defineProps({\r\n productId: {\r\n type: Number,\r\n default: undefined\r\n },\r\n suppressRecommendedAdvisors: {\r\n type: Boolean,\r\n default: false\r\n }\r\n });\r\n\r\n const bookingLink = ref(\"\");\r\n const bookingLinkForAdvisors = ref(\"\");\r\n const hotel = ref<ProductDetailsHotel>({} as ProductDetailsHotel);\r\n const hasSustainabilityContent = ref(false);\r\n const isAdvisorOrVStaff = (isAdvisor() || isVStaff());\r\n const isReady = ref(false);\r\n const productDetailRef = useTemplateRef(\"product-detail\");\r\n const productMapConfig = ref({ \r\n popupHtml: \"\",\r\n longitude: 0,\r\n latitude: 0\r\n });\r\n const recommendedAdvisorQuery = ref<RecommendedAdvisorQuery>({} as RecommendedAdvisorQuery);\r\n const reviewsData = ref({});\r\n const showTabBlock = ref(false);\r\n const tabNavContainerRef = useTemplateRef(\"tab-nav-container\");\r\n const topSplashData = ref<ProductTopSplash>({} as ProductTopSplash);\r\n\r\n function handleLegacyLinkClick(): void {\r\n setB2BDesktopCookie();\r\n trackEvent(\"legacy_page_click\", {\r\n affiliation: `${hotel.value.companyId}`,\r\n item_id: `${props.productId}`,\r\n item_name: hotel.value.companyName,\r\n item_category: capitalizeFirst(ProductType.HOTELS.slice(0,-1))\r\n });\r\n }\r\n\r\n function loadHotel(): void {\r\n let resultHotel: ProductDetailsHotelResponse;\r\n let cmsJSON = {} as DotCMSProductDetailPageResponse;\r\n\r\n const hotelContentPromise = getProductDetails(ProductType.HOTELS, props.productId);\r\n hotelContentPromise.then((hotelResponse: ProductDetailsHotelResponse) => {\r\n resultHotel = hotelResponse;\r\n }); // Reject is caught in the Promise.all below\r\n\r\n const cmsContentPromise = getCmsContent<DotCMSProductDetailPageResponse[]>({\r\n contentTypes: [\"ProductDetailPage\"],\r\n depth: 1,\r\n limit: 1,\r\n queryClauses: [\r\n `+(ProductDetailPage.productId:${props.productId})`\r\n ]\r\n });\r\n cmsContentPromise.then((productJSON) => { // Most products won't have content in dotCMS\r\n if (productJSON?.length) {\r\n cmsJSON = productJSON[0];\r\n }\r\n }); // Reject is caught in the Promise.all below\r\n\r\n Promise.all([hotelContentPromise, cmsContentPromise]).then(() => {\r\n\r\n if (resultHotel && resultHotel.companyName) {\r\n\r\n const hasAddress = (resultHotel.companyInfo && resultHotel.companyInfo.addresses && resultHotel.companyInfo.addresses.length);\r\n\r\n const thisHotel: ProductDetailsHotel = {\r\n address1: (hasAddress && resultHotel.companyInfo.addresses[0].addressLine1) ? resultHotel.companyInfo.addresses[0].addressLine1 : \"\",\r\n address2: (hasAddress && resultHotel.companyInfo.addresses[0].addressLine2) ? resultHotel.companyInfo.addresses[0].addressLine2 : \"\",\r\n address3: (hasAddress && resultHotel.companyInfo.addresses[0].addressLine3) ? resultHotel.companyInfo.addresses[0].addressLine3 : \"\",\r\n amenities: resultHotel.virtuosoAmenitiesHtml,\r\n bobDescription: resultHotel.asSeenInTravelFolioDescription,\r\n companyId: resultHotel.companyId || 0, // Only used for retrieving reviews\r\n country: resultHotel.companyInfo.addresses[0].country,\r\n experiences: (resultHotel.hotelExperiences && resultHotel.hotelExperiences.length) ? resultHotel.hotelExperiences : [],\r\n featuredImageUrl: \"https://virtuoso-prod.dotcms.cloud/images/image-not-available-results-266x200.png\", // fallback, replaced later\r\n guestRooms: (resultHotel.guestRooms) ? resultHotel.guestRooms : [],\r\n healthAndSafety: (resultHotel.supplierHealthAndSafetyHtml) ? resultHotel.supplierHealthAndSafetyHtml : \"\",\r\n hotelFeatures: {\r\n features: (resultHotel.hotelFeatures.features && resultHotel.hotelFeatures.features.length) ? resultHotel.hotelFeatures.features : [],\r\n inYourRoom: (resultHotel.hotelFeatures.inYourRoom && resultHotel.hotelFeatures.inYourRoom.length) ? resultHotel.hotelFeatures.inYourRoom : [],\r\n recreation: (resultHotel.hotelFeatures.recreation && resultHotel.hotelFeatures.recreation.length) ? resultHotel.hotelFeatures.recreation : [],\r\n services: (resultHotel.hotelFeatures.services && resultHotel.hotelFeatures.services.length) ? resultHotel.hotelFeatures.services : []\r\n },\r\n id: props.productId,\r\n latitude: (resultHotel.latitude) ? resultHotel.latitude : 0,\r\n longitude: (resultHotel.longitude) ? resultHotel.longitude : 0,\r\n companyName: cmsJSON.title || resultHotel.companyName,\r\n neighborhood: (resultHotel.neighborhood) ? resultHotel.neighborhood : \"\",\r\n numberOfRooms: (resultHotel.numberOfRooms) ? `${resultHotel.numberOfRooms} room${getPlural(resultHotel.numberOfRooms)}` : \"\",\r\n promotions: [],\r\n roomStyle: (resultHotel.roomStyle) ? resultHotel.roomStyle : \"\",\r\n sustainability: (resultHotel.supplierSustainability) ? resultHotel.supplierSustainability : \"\",\r\n sustainabilityCertifications: getSustainabilityCerts(resultHotel.sustainabilityCertifications),\r\n sustainabilityVideoUrl: resultHotel.sustainabilityVideoUrl,\r\n tip: (resultHotel.asSeenInTravelFolioInTheKnow) ? resultHotel.asSeenInTravelFolioInTheKnow : \"\",\r\n vibe: (resultHotel.hotelVibes) ? resultHotel.hotelVibes : \"\"\r\n };\r\n\r\n // Location\r\n if (hasAddress) {\r\n thisHotel.location = formatLocation(resultHotel.companyInfo.addresses[0].city, resultHotel.companyInfo.addresses[0].state, resultHotel.companyInfo.addresses[0].country);\r\n }\r\n\r\n // Images -- first image is the featured image\r\n let galleryImages: GalleryItem[] = [];\r\n if (resultHotel.imageLibraryItems && resultHotel.imageLibraryItems.length) {\r\n galleryImages = hydrateImageGallery(resultHotel.imageLibraryItems);\r\n thisHotel.featuredImageUrl = galleryImages[0].image;\r\n thisHotel.featuredImageCaption = galleryImages[0].description;\r\n }\r\n thisHotel.galleryImages = galleryImages;\r\n\r\n // Featured Video\r\n if (resultHotel.supplierVideos && resultHotel.supplierVideos.length) {\r\n const featuredVideo = resultHotel.supplierVideos.find((video) => video.isFeaturedVideo);\r\n if (featuredVideo) {\r\n thisHotel.featuredVideoCaption = featuredVideo.title;\r\n thisHotel.featuredVideoUrl = featuredVideo.webContentURL;\r\n }\r\n }\r\n\r\n // Nearest airport\r\n if (resultHotel.nearestAirportDescription) {\r\n thisHotel.nearestAirport = resultHotel.nearestAirportDescription;\r\n if (resultHotel.nearestAirportDistanceInMiles) {\r\n thisHotel.nearestAirport += ` - ${resultHotel.nearestAirportDistanceInMiles} mi/${Math.round(resultHotel.nearestAirportDistanceInMiles * 1.609)} km`;\r\n }\r\n }\r\n\r\n // Promotions -- Virtuoso Exclusive Promotions added first\r\n const combinedPromotions: Promotion[] = [];\r\n const sourcePromotionsExclusives = (resultHotel.virtuosoExclusivePromotions && resultHotel.virtuosoExclusivePromotions.length) ? resultHotel.virtuosoExclusivePromotions : [];\r\n const sourcePromotionsNormal = (resultHotel.promotions && resultHotel.promotions.length) ? resultHotel.promotions : [];\r\n [...sourcePromotionsExclusives, ...sourcePromotionsNormal].forEach((promo) => {\r\n if (!promo.isAdvisorIncentive && !promo.incentiveTypeCode) {\r\n combinedPromotions.push({\r\n description: promo.description || \"\",\r\n endDateMS: (promo.travelEndDate) ? new Date(promo.travelEndDate).getTime() : 0,\r\n formattedTravelDates: promo.formattedTravelDates || \"\",\r\n isExclusive: (promo.promotionType === \"Virtuoso Exclusive Hotel Offer\"),\r\n name: promo.promotionName || \"\",\r\n promotionId: promo.masterEntityId,\r\n startDateMS: (promo.travelStartDate) ? new Date(promo.travelStartDate).getTime() : 0,\r\n url: (promo.masterEntityId) ? cobrandLink(`/${(promo.promotionType === \"Virtuoso Exclusive Hotel Offer\") ? \"virtuosoexclusivepromotions\" : \"promotions\"}/${promo.masterEntityId}`) : \"\"\r\n });\r\n }\r\n });\r\n\r\n // Check for featured promotion and reorder the array if necessary\r\n if (cmsJSON.featuredPromotionId && combinedPromotions.length > 1) {\r\n const featuredPromoIndex = combinedPromotions.findIndex((promo) => parseInt(cmsJSON.featuredPromotionId, 10) === promo.promotionId);\r\n if (featuredPromoIndex > 0) {\r\n const featuredItem = combinedPromotions.splice(featuredPromoIndex, 1);\r\n combinedPromotions.unshift(featuredItem[0]);\r\n }\r\n }\r\n\r\n thisHotel.promotions = combinedPromotions;\r\n topPromoName = (thisHotel.promotions.length) ? thisHotel.promotions[0].name : \"\";\r\n hasSustainabilityContent.value = !!(thisHotel.sustainability || thisHotel.sustainabilityCertifications.length || thisHotel.sustainabilityVideoUrl);\r\n\r\n\r\n // Reviews metadata -- actual reviews are pulled in separate component\r\n const reviewsObj = (resultHotel.reviewsInfoJson) ? JSON.parse(resultHotel.reviewsInfoJson) : {};\r\n thisHotel.reviewsCount = (reviewsObj && reviewsObj.TotalActiveReviews) ? reviewsObj.TotalActiveReviews : 0;\r\n thisHotel.reviewsPercent = (reviewsObj && reviewsObj.TotalRecommendedPercent) ? Math.round(reviewsObj.TotalRecommendedPercent) : 100;\r\n if (thisHotel.reviewsCount > 0) {\r\n // passed to reviews component\r\n reviewsData.value = {\r\n percent: `<b>${thisHotel.reviewsPercent}% Recommended</b>`,\r\n count: `<b>${thisHotel.reviewsCount} Review${getPlural(thisHotel.reviewsCount)}</b>`\r\n };\r\n }\r\n\r\n // Build map popup HTML\r\n let mapPopupHtml = `<h3>${thisHotel.companyName}</h3>`;\r\n if (hasAddress) {\r\n mapPopupHtml += (thisHotel.address1) ? `${thisHotel.address1}<br>` : \"\";\r\n mapPopupHtml += (thisHotel.address2) ? `${thisHotel.address2}<br>` : \"\";\r\n mapPopupHtml += (thisHotel.address3) ? `${thisHotel.address3}<br>` : \"\";\r\n mapPopupHtml += (thisHotel.location) ? thisHotel.location : \"\";\r\n }\r\n productMapConfig.value = {\r\n popupHtml: mapPopupHtml,\r\n latitude: thisHotel.latitude,\r\n longitude: thisHotel.longitude\r\n };\r\n\r\n bookingLink.value = cobrandLink(`/hotels/${thisHotel.id}?flow=1`);\r\n bookingLinkForAdvisors.value = cobrandLink(`/hotels/${thisHotel.id}`);\r\n hotel.value = thisHotel;\r\n\r\n topSplashData.value = {\r\n ...(thisHotel.promotions.length && { promotion: thisHotel.promotions[0] }), // set Promotion if it exists\r\n companyName: thisHotel.companyName,\r\n featuredImageCaption: thisHotel.featuredImageCaption,\r\n featuredImageUrl: thisHotel.featuredImageUrl,\r\n featuredVideoUrl: thisHotel.featuredVideoUrl,\r\n productName: thisHotel.companyName,\r\n productType: ProductType.HOTELS,\r\n wanderlistId: props.productId.toString(),\r\n wanderlistName: thisHotel.country\r\n } as ProductTopSplash;\r\n\r\n renderMetadata({\r\n description: thisHotel.bobDescription,\r\n title: thisHotel.companyName\r\n });\r\n\r\n\r\n // Recommended Advisors\r\n recommendedAdvisorQuery.value = {\r\n Id: thisHotel.id,\r\n InterestType: \"\",\r\n ProductLocationCountry: (hasAddress) ? resultHotel.companyInfo.addresses[0].country : \"\",\r\n ProductPois: \"\",\r\n ProductTypeName: \"Hotel\"\r\n } as RecommendedAdvisorQuery;\r\n\r\n isReady.value = true;\r\n \r\n\r\n\r\n nextTick(() => {\r\n // Onload tab support for this hotel\r\n showThenJumpToTab();\r\n enableHearts(productDetailRef.value);\r\n });\r\n\r\n } else {\r\n redirectOnError();\r\n }\r\n }, () => redirectOnError());\r\n }\r\n\r\n function redirectOnError(): void {\r\n toastError(\"Error retrieving data\");\r\n setTimeout(() => { location.href = cobrandLink(`/travel/luxury-hotels`); }, 3000);\r\n }\r\n\r\n function setRecommendedAdvisorIds(advisor1MEID?: number, advisor2MEID?: number): void {\r\n // get hotel index key in sessionStorage\r\n const getVirtuosoHotelSearchIndexValue = sessionStorage.getItem(`VirtuosoHotelSearchIndex_${props.productId}`);\r\n\r\n if (getVirtuosoHotelSearchIndexValue) {\r\n // then delete hotel index key in sessionStorage\r\n sessionStorage.removeItem(`VirtuosoHotelSearchIndex_${props.productId}`);\r\n }\r\n\r\n trackEvent(\"view_item\", {\r\n ...((getVirtuosoHotelSearchIndexValue) && { index: parseInt(getVirtuosoHotelSearchIndexValue, 10) }), // should add index with value if session key existed\r\n item_id: `${props.productId}`,\r\n item_name: hotel.value.companyName,\r\n coupon: (hotel.value.promotions.length >= 1) ? \"Promotion Available\" : \"\",\r\n item_category: \"Hotel\",\r\n item_category2: `${hotel.value.reviewsCount}`,\r\n item_category3: hotel.value.vibe,\r\n item_category4: (advisor1MEID) ? `${advisor1MEID}` : \"\",\r\n item_category5: (advisor2MEID) ? `${advisor2MEID}` : \"\",\r\n item_variant: hotel.value.roomStyle\r\n });\r\n }\r\n\r\n function scrollToTabResponsive(tabName = \"\"): void {\r\n if (isMobileScreenWidth()) {\r\n return (tabName) ? document.getElementById(`tc-${tabName}`)?.scrollIntoView()\r\n : document.querySelector(\".tab-content .-active\")?.scrollIntoView({ block: \"start\" });\r\n }\r\n tabNavContainerRef.value.scrollIntoView();\r\n }\r\n\r\n function showTab(tabName: string, preventJump = false, fromTopLink = false): void {\r\n document.querySelectorAll(\".tab-nav-container .-active, .tab-content .-active\").forEach((el) => el.classList.remove(\"-active\"));\r\n document.querySelectorAll(`#tab-${tabName}, #tc-${tabName}`).forEach((el) => el.classList.add(\"-active\"));\r\n\r\n if (!preventJump) {\r\n scrollToTabResponsive(tabName);\r\n }\r\n\r\n if (fromTopLink) {\r\n trackEvent(\"view_promotion\", {\r\n item_id: `${hotel.value.promotions[0].promotionId}`,\r\n item_name: topPromoName,\r\n item_category: \"Promotion\",\r\n item_variant: (hotel.value.promotions[0].isExclusive) ? \"Virtuoso Exclusive Hotel Offer\" : \"Hotel/Resort\",\r\n affiliation: `${props.productId}`\r\n });\r\n }\r\n }\r\n\r\n function showThenJumpToTab(): void {\r\n const defaultTabName =\r\n (hotel.value.guestRooms.length) ? \"rooms\" :\r\n (hotel.value.promotions.length) ? \"promotions\" :\r\n (hotel.value.healthAndSafety) ? \"health\" :\r\n (hotel.value.reviewsCount > 0) ? \"reviews\" :\r\n (hasSustainabilityContent.value) ? \"sustainability\" : \"\";\r\n\r\n const qsTabLabel = qsParams[\"tab\"];\r\n\r\n showTabBlock.value = (defaultTabName.length > 0);\r\n\r\n nextTick(() => { // Tab Show then Scroll\r\n // Show\r\n if (defaultTabName) { // first tab\r\n showTab(defaultTabName, true);\r\n }\r\n if (qsTabLabel && document.getElementById(`tc-${qsTabLabel}`)) { // check qsParam tab exists\r\n showTab(qsTabLabel, true);\r\n }\r\n // Show Promo Manual Tab\r\n if (qsParams.promotions === \"1\") { // If the manually added ?promotions=1 query string variable is present, scroll to the promotions section on load\r\n const qsPromoTabName = (hotel.value.promotions.length) ? \"promotions\" : defaultTabName;\r\n showTab(qsPromoTabName, true);\r\n }\r\n // Default Scroll\r\n if ((\"tab\" in qsParams) || (\"promotions\" in qsParams)) {\r\n scrollToTabResponsive();\r\n }\r\n });\r\n }\r\n\r\n loadHotel();\r\n \r\n</script>\r\n","<template>\r\n <div class=\"product-features -narrow mt-2\">\r\n <ul>\r\n <li v-for=\"experience in tourExperiences\" :key=\"experience\">\r\n <i :class=\"tourExperienceIconMap.get(`tour-${experience}`) || 'icon-check-ut'\"></i>\r\n {{ experience }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import { PropType } from \"vue\";\r\n\r\n const tourExperienceIconMap = new Map([\r\n [\"tour-Adventure\" , \"icon-h20-adventure\" ],\r\n [\"tour-Air & Helicopter Tours\" , \"icon-Air\" ],\r\n [\"tour-Cruises & Water Tours\" , \"icon-Cruises\" ],\r\n [\"tour-Culture, History & Arts\" , \"icon-h20-local-immersion\"],\r\n [\"tour-Family Vacations\" , \"icon-Family\" ],\r\n [\"tour-Festivals & Events\" , \"icon-festivals\" ],\r\n [\"tour-Food & Wine\" , \"icon-Food\" ],\r\n [\"tour-Islands & Beaches\" , \"icon-h20-beach\" ],\r\n [\"tour-LGBTQ Travel\" , \"icon-lgbtq\" ],\r\n [\"tour-Rail Journeys\" , \"icon-Rail\" ],\r\n [\"tour-Romance, Weddings & Honeymoons\", \"icon-Romance\" ],\r\n [\"tour-Sightseeing\" , \"icon-Sightseeing\" ],\r\n [\"tour-Sports\" , \"icon-h20-golf\" ],\r\n [\"tour-Walking & Biking\" , \"icon-Walking\" ],\r\n [\"tour-Wellness\" , \"icon-h20-wellness-ut\" ],\r\n [\"tour-Wheelchair Accessible\" , \"icon-wheelchair\" ],\r\n [\"tour-Wildlife & Nature\" , \"icon-Wildlife\" ]\r\n ]);\r\n\r\n\r\n defineProps({\r\n tourExperiences: {\r\n type: Array as PropType<string[]>,\r\n default: undefined\r\n }\r\n });\r\n</script>\r\n","<template>\r\n <div v-if=\"isReady\" ref=\"product-detail\" class=\"product-detail\">\r\n <top-splash-component :top-splash-data=\"topSplashData\" @promo-click=\"showTab('promotions', false, true)\"></top-splash-component>\r\n <div class=\"container mt-4\">\r\n <div class=\"product-detail-overview mb-6\">\r\n <div>\r\n <h1 v-html=\"tour.tourName\"></h1>\r\n <div class=\"mt-1\">\r\n {{ tour.tourType }}\r\n <template v-if=\"tour.subType\">\r\n | {{ tour.subType }}\r\n </template>\r\n </div>\r\n\r\n <div class=\"mt-2 -description\">\r\n <div v-if=\"tour.childTours.length > 1\" class=\"mt-2\">\r\n Travel Dates<br />\r\n <template v-if=\"isEmbeddedMode()\">\r\n Ask your advisor about available travel dates\r\n </template>\r\n <label v-else class=\"select--styled\">\r\n <select id=\"itinerary-nav\" class=\"w-100\" aria-label=\"Select Travel Dates\" @change=\"goToTour()\">\r\n <option v-for=\"(item, index) in tour.childTours\"\r\n :key=\"index\"\r\n :value=\"item.id\"\r\n :selected=\"item.id === tour.id\"\r\n :data-url=\"item.url\"\r\n v-html=\"item.travelDates\"></option>\r\n </select>\r\n </label>\r\n </div>\r\n <template v-else>\r\n <b>{{ tour.travelDates }}</b>\r\n <br />\r\n </template>\r\n <b v-html=\"formattedLocation\"></b><br />\r\n <a v-if=\"tour.brandUrl && !isEmbeddedMode()\" class=\"weglot-exclude\" :href=\"tour.brandUrl\">\r\n <b v-html=\"tour.companyName\"></b>\r\n </a>\r\n <b v-else class=\"weglot-exclude\" v-html=\"tour.companyName\"></b>\r\n </div>\r\n\r\n <div v-if=\"isDayTour\" class=\"mt-2\">\r\n <div v-if=\"tour.meals\">Meals: <b v-html=\"tour.meals\"></b></div>\r\n <div v-if=\"tour.ageAppropriateness\" class=\"mt-1\">\r\n Recommended Ages: <b v-html=\"tour.ageAppropriateness\"></b>\r\n </div>\r\n <div v-if=\"tour.activityLevel\" class=\"mt-1\">\r\n Activity Level: <b v-html=\"tour.activityLevel\"></b>\r\n <b v-if=\"isWheelchairAccessible\">• Wheelchair Accessible</b>\r\n </div>\r\n </div>\r\n\r\n <tour-experiences-component v-if=\"tour.experiences.length\"\r\n :tour-experiences=\"tour.experiences\"></tour-experiences-component>\r\n\r\n <div v-if=\"tour.tourDescription\"\r\n class=\"mt-3 truncate-vertical truncate-15\"\r\n data-view-more-text=\"More details\"\r\n data-view-less-text=\"Fewer details\"\r\n v-html=\"tour.tourDescription\"></div>\r\n\r\n <h2 class=\"text--serif mt-3\">At a Glance</h2>\r\n\r\n <MapView v-if=\"routeMapConfig.routeMarkers.length\" type=\"route\" :route-map-config=\"routeMapConfig\" />\r\n\r\n <div v-if=\"tour.whatIsIncluded\" class=\"mt-2\">\r\n <div class=\"mb-1\"><b>Included</b></div>\r\n <div v-html=\"tour.whatIsIncluded\"></div>\r\n </div>\r\n\r\n <div v-if=\"tour.whatIsNotIncluded\" class=\"mt-2\">\r\n <div class=\"mb-1\"><b>Not Included</b></div>\r\n <div v-html=\"tour.whatIsNotIncluded\"></div>\r\n </div>\r\n </div>\r\n <div class=\"-gallery mt-5 mt-md-0 mb-6\">\r\n <image-gallery-component v-if=\"tour.galleryImages && tour.galleryImages.length\"\r\n :gallery-data=\"tour.galleryImages\"\r\n :product-id=\"tour.id.toString()\"\r\n :product-name=\"tour.tourName\"\r\n :product-type=\"catalogType\"></image-gallery-component>\r\n <recommended-advisors-component v-if=\"!suppressRecommendedAdvisors\"\r\n :advisor-query=\"recommendedAdvisorQuery\"\r\n :product-type=\"catalogType\"\r\n :set-recommended-advisor-ids=\"setRecommendedAdvisorIds\"></recommended-advisors-component>\r\n <a v-if=\"isNetworkUser() && !isEmbeddedMode()\"\r\n id=\"b2blink-legacy-link-label\"\r\n class=\"btn btn-primary-emphasis btn-sm d-block mt-3\"\r\n :href=\"legacyLink\"\r\n @click=\"handleLegacyLinkClick\">{{ legacyLinkLabel }}</a>\r\n <button class=\"wl-heartable -save-this mt-3\"\r\n data-wl-type=\"tour\"\r\n :data-wl-id=\"heartableUrl\"\r\n :data-wl-title=\"tour.tourName\"\r\n :data-wl-list-name=\"wanderlistTourName\"\r\n aria-label=\"Save to Wanderlist\"></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"showTabBlock\" id=\"detail-tabs\"></div>\r\n <div v-if=\"showTabBlock\" class=\"container d-none d-md-block\">\r\n <ul ref=\"tabNavContainer\" class=\"tab-nav-container\">\r\n <li v-if=\"tour.itinerary.length > 1\" id=\"tab-itinerary\">\r\n <button @click=\"showTab('itinerary')\">Itinerary</button>\r\n </li>\r\n <li v-if=\"tour.virtuosoBenefits\" id=\"tab-benefits\">\r\n <button @click=\"showTab('benefits')\">Virtuoso Benefits</button>\r\n </li>\r\n <li v-if=\"tour.promotions.length\" id=\"tab-promotions\">\r\n <button @click=\"showTab('promotions')\">Promotions</button>\r\n </li>\r\n <li v-if=\"tour.addOnDayTours.length\" id=\"tab-add-ons\">\r\n <button @click=\"showTab('add-ons')\">Add-ons</button>\r\n </li>\r\n </ul>\r\n </div>\r\n <div v-if=\"showTabBlock\" class=\"slab -tab-slab\">\r\n <div class=\"container\">\r\n <ul class=\"tab-content\">\r\n <li v-if=\"tour.itinerary.length > 1\" id=\"tc-itinerary\">\r\n <button class=\"tab-nav\" @click=\"showTab('itinerary')\">\r\n Itinerary\r\n </button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Itinerary</h4>\r\n <product-itinerary-component :itinerary=\"tour.itinerary\"\r\n :product-type=\"catalogType\"\r\n :is-cruise-tour=\"false\"></product-itinerary-component>\r\n </div>\r\n </li>\r\n <li v-if=\"tour.virtuosoBenefits\" id=\"tc-benefits\">\r\n <button class=\"tab-nav\" @click=\"showTab('benefits')\">\r\n Virtuoso Benefits\r\n </button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Virtuoso Benefits</h4>\r\n <h2 class=\"text--serif\">Exclusively for Virtuoso Guests</h2>\r\n <div class=\"mt-2\" v-html=\"tour.virtuosoBenefits\"></div>\r\n </div>\r\n </li>\r\n <li v-if=\"tour.promotions.length\" id=\"tc-promotions\">\r\n <button class=\"tab-nav\" @click=\"showTab('promotions')\">\r\n Promotions\r\n </button>\r\n <div class=\"-container\">\r\n <h4 class=\"-title\">Promotions</h4>\r\n <promotions-component :product-type=\"catalogType\" :promotions=\"tour.promotions\"></promotions-component>\r\n </div>\r\n </li>\r\n <li v-if=\"tour.addOnDayTours.length\" id=\"tc-add-ons\">\r\n <button class=\"tab-nav\" @click=\"showTab('add-ons')\">Add-ons</button>\r\n <div class=\"-container\">\r\n <h4 class=\"tab-title\">Add-ons</h4>\r\n <div class=\"mb-4\">\r\n <h2 class=\"text--serif\">Excursions</h2>\r\n <div v-for=\"(segment, segmentIndex) in tour.addOnDayTours\" :key=\"segmentIndex\" class=\"add-ons-box mt-2\">\r\n <div class=\"-title-row\">\r\n <div class=\"-location\">\r\n {{ segment.addOnDate }}\r\n <span class=\"d-none d-md-inline\">—</span><span class=\"d-md-none\"><br /></span>\r\n {{ segment.addOnLocation }}\r\n </div>\r\n <div class=\"-toggle\">\r\n <button @click.prevent.stop=\"toggleAddOn(segmentIndex)\">\r\n <i :id=\"'add-on-toggle-shore-excursion-' + segmentIndex\" class=\"icon-plus-circle-ut\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n <div :id=\"'add-on-container-shore-excursion-' + segmentIndex\" class=\"add-on-container\">\r\n <add-on-tours-component v-for=\"(pkg, pkgIndex) in segment.addOns\" :key=\"pkgIndex\" :add-on=\"pkg\"></add-on-tours-component>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n </div>\r\n <LogoSplash v-else />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n import AddOnToursComponent from \"components/products/details/add-on-tours.vue\";\r\n import ProductItineraryComponent from \"components/products/details/product-itinerary.vue\";\r\n import PromotionsComponent from \"components/products/details/product-promotions.vue\";\r\n import TourExperiencesComponent from \"components/products/details/tour-experiences.vue\";\r\n import RecommendedAdvisorsComponent from \"components/advisor/recommended-advisors.vue\";\r\n import ImageGalleryComponent from \"components/shared/image-gallery.vue\";\r\n import LogoSplash from \"components/shared/logo-splash.vue\";\r\n import MapView from \"components/shared/map-view.vue\";\r\n import TopSplashComponent from \"components/shared/top-splash.vue\";\r\n import { tourBrandSupplierTypesRestricted } from \"config/collections\";\r\n import { ProductDetailsTourResponse } from \"interfaces/responses/product-detail-responses\";\r\n import { RecommendedAdvisorQuery } from \"interfaces/advisor\";\r\n import { ProductType, SupplierType } from \"interfaces/enums\";\r\n import { GalleryItem } from \"interfaces/image\";\r\n import { ProductDetailsTour, ProductTopSplash, Promotion } from \"interfaces/product\";\r\n import { AddOnDayTours, DayTourPackage } from \"interfaces/tour\";\r\n import { getProductDetails } from \"services/api/products\";\r\n import { isNetworkUser, isSupplier } from \"services/auth/user-info\";\r\n import { sanitizeUserGeneratedContent, toggleSlideWithFade } from \"services/helpers/html\";\r\n import { hydrateImageGallery } from \"services/helpers/images\";\r\n import { toastError } from \"services/helpers/toasts\";\r\n import { applyVerticalTruncation } from \"services/helpers/truncation\";\r\n import { isEmbeddedMode, isMobileScreenWidth } from \"services/layout/environment\";\r\n import { setB2BDesktopCookie } from \"services/layout/metadata\";\r\n import { trackEvent } from \"services/analytics\";\r\n import { enableHearts } from \"services/wanderlist\";\r\n import { capitalizeFirst, cobrandLink, parseURLParameters, slugify } from \"virtuoso-shared-web-ui\";\r\n import { nextTick, ref, useTemplateRef } from \"vue\";\r\n\r\n let topPromoName = \"\"; // Referenced by GA\r\n\r\n const qsParams = parseURLParameters();\r\n const props = defineProps({\r\n productId: {\r\n type: Number,\r\n default: undefined\r\n },\r\n suppressRecommendedAdvisors: {\r\n type: Boolean,\r\n default: false\r\n }\r\n });\r\n\r\n const catalogType = ProductType.TOURS;\r\n const heartableUrl = document.querySelector(\"link[rel='canonical']\")?.getAttribute(\"href\");\r\n const isDayTour = ref(false);\r\n const isReady = ref(false);\r\n const isWheelchairAccessible = ref(false);\r\n const formattedLocation = ref(\"\");\r\n const legacyLink = cobrandLink(`/tours/${props.productId}`);\r\n const legacyLinkLabel = (isSupplier()) ? \"For Partners: Update Your Profile\" : \"For Advisors: Advanced Results\";\r\n const productDetailRef = useTemplateRef(\"product-detail\");\r\n const recommendedAdvisorQuery = ref<RecommendedAdvisorQuery>({} as RecommendedAdvisorQuery);\r\n const routeMapConfig = ref({ routeMarkers: [] });\r\n const showTabBlock = ref(false);\r\n const topSplashData = ref<ProductTopSplash>({} as ProductTopSplash);\r\n const tour = ref<ProductDetailsTour>({} as ProductDetailsTour);\r\n const wanderlistTourName = ref(\"\");\r\n const tabNavContainerRef = useTemplateRef(\"tabNavContainer\");\r\n\r\n function goToTour(): void {\r\n const elSelect = document.getElementById(\"itinerary-nav\") as HTMLSelectElement;\r\n const newId = parseInt(elSelect.value, 10);\r\n if (newId && newId !== props.productId) {\r\n window.location.href = elSelect.options[elSelect.selectedIndex].dataset.url;\r\n }\r\n }\r\n\r\n function handleLegacyLinkClick(): void {\r\n setB2BDesktopCookie();\r\n trackEvent(\"legacy_page_click\", {\r\n affiliation: `${tour.value.companyId}`,\r\n item_id: `${props.productId}`,\r\n item_name: tour.value.companyName,\r\n item_category: capitalizeFirst(ProductType.TOURS.slice(0,-1))\r\n });\r\n }\r\n\r\n function loadTour(): void {\r\n getProductDetails(catalogType, props.productId).then((resultTour: ProductDetailsTourResponse) => {\r\n if (resultTour && resultTour.companyName) {\r\n const brandUrl = (tourBrandSupplierTypesRestricted.includes(resultTour.companyType as SupplierType)) ? cobrandLink(`/travel/luxury-tours/tour-operators/${resultTour.companyId}/${slugify(resultTour.companyName)}`) : \"\";\r\n\r\n const thisTour: ProductDetailsTour = {\r\n addOnDayTours: [],\r\n companyId: resultTour.companyId || 0, // Only used for retrieving reviews\r\n brandUrl: brandUrl,\r\n childTours: [],\r\n experiences: resultTour.tourExperiences || [],\r\n featuredImageUrl: \"https://virtuoso-prod.dotcms.cloud/images/image-not-available-results-266x200.png\", // fallback, replaced later\r\n id: props.productId,\r\n itinerary: [],\r\n companyName: resultTour.companyName,\r\n promotions: [],\r\n activityLevel: resultTour.tourActivityLevels, // Day tours only?\r\n ageAppropriateness: resultTour.tourAgeAppropriateness,\r\n meals: resultTour.meals ? resultTour.meals.join(\", \") : \"\", // Day tours only?\r\n supplierType: resultTour.companyType as SupplierType,\r\n tourCategory: resultTour.tourCategory,\r\n tourDescription: (resultTour.tourDescription) ? sanitizeUserGeneratedContent(resultTour.tourDescription) : \"\",\r\n tourLength: resultTour.tourLength,\r\n tourName: `${resultTour.tourName} (${(resultTour.tourLength.trim()).replace(\" \", \" \").replace(\"days\", \"Days\").replace(\"hours\", \"Hours\")})`,\r\n tourNameWithoutLength: resultTour.tourName,\r\n tourType: resultTour.tourType,\r\n travelDates: resultTour.travelDates,\r\n virtuosoBenefits: resultTour.virtuosoBenefitsDesc || \"\",\r\n whatIsIncluded: \"\",\r\n whatIsNotIncluded: \"\"\r\n };\r\n\r\n isDayTour.value = (thisTour.tourType === \"Day Tour\");\r\n\r\n isWheelchairAccessible.value = thisTour.experiences.includes(\"Wheelchair Accessible\");\r\n formattedLocation.value = resultTour.embarkation;\r\n\r\n if (resultTour.disembarkation && (!isDayTour.value || resultTour.embarkation !== resultTour.disembarkation)) {\r\n formattedLocation.value += ` to ${resultTour.disembarkation}`;\r\n }\r\n\r\n\r\n // What's (Not) Included\r\n if (resultTour.whatIsIncludedItems && resultTour.whatIsIncludedItems.length) {\r\n thisTour.whatIsIncluded = (resultTour.whatIsIncludedItems.length > 1)\r\n ? `<ul>${resultTour.whatIsIncludedItems.map((item) => `<li>${item}</li>`).join(\"\")}</ul>`\r\n : sanitizeUserGeneratedContent(resultTour.whatIsIncludedItems[0]);\r\n }\r\n\r\n if (resultTour.whatIsNotIncludedItems && resultTour.whatIsNotIncludedItems.length) {\r\n thisTour.whatIsNotIncluded = (resultTour.whatIsNotIncludedItems.length > 1)\r\n ? `<ul>${resultTour.whatIsNotIncludedItems.map((item) => `<li>${item}</li>`).join(\"\")}</ul>`\r\n : sanitizeUserGeneratedContent(resultTour.whatIsNotIncludedItems[0]);\r\n }\r\n\r\n // Images -- first image is the featured image\r\n let galleryImages: GalleryItem[] = [];\r\n if (resultTour.imageLibraryItems && resultTour.imageLibraryItems.length) {\r\n galleryImages = hydrateImageGallery(resultTour.imageLibraryItems);\r\n thisTour.featuredImageUrl = galleryImages[0].image;\r\n thisTour.featuredImageCaption = galleryImages[0].description;\r\n }\r\n thisTour.galleryImages = galleryImages;\r\n\r\n // Featured Video\r\n if (resultTour.supplierVideos && resultTour.supplierVideos.length) {\r\n const featuredVideo = resultTour.supplierVideos.find((video) => video.isFeaturedVideo);\r\n if (featuredVideo) {\r\n thisTour.featuredVideoCaption = featuredVideo.title;\r\n thisTour.featuredVideoUrl = featuredVideo.webContentURL;\r\n }\r\n }\r\n\r\n\r\n // Child tours, if populated\r\n if (resultTour.childProducts && resultTour.childProducts.length && resultTour.childProducts.length > 1) {\r\n resultTour.childProducts.forEach((childTour) => {\r\n if (childTour.masterEntityId) { // && childTour.travelDates\r\n thisTour.childTours.push({\r\n id: childTour.masterEntityId,\r\n travelDates: `${childTour.travelStartDate} - ${childTour.travelEndDate}`, // childTour.travelDates,\r\n url: cobrandLink(`/travel/luxury-tours/${childTour.masterEntityId}/${slugify(resultTour.tourName)}`)\r\n });\r\n }\r\n });\r\n }\r\n\r\n // Add \"Select Dates\" option at the beginning if this is a master tour\r\n if (thisTour.childTours.length && !thisTour.childTours.some((childTour) => childTour.id === thisTour.id )) {\r\n thisTour.childTours.unshift({\r\n id: null,\r\n travelDates: \"Select Dates\",\r\n url: \"\"\r\n });\r\n }\r\n\r\n\r\n // Itinerary\r\n if (resultTour.itineraryPoints && resultTour.itineraryPoints.length) {\r\n let countryFound = 0;\r\n let previousDay = 0;\r\n let currentDay = 0;\r\n let previousDataDay = 0; // The day number from the data -- tours sometimes have insane, inaccurate day numbers (e.g. 138, 139, 1)\r\n resultTour.itineraryPoints.forEach((it) => {\r\n if (it.dayOfTour > (previousDay + 1) || it.dayOfTour < previousDay) { // Bad data -- \"this\" day is more than one day ahead or behind of the last day\r\n currentDay = (it.dayOfTour === previousDataDay) ? currentDay : currentDay + 1;\r\n } else {\r\n currentDay = it.dayOfTour;\r\n }\r\n\r\n if (currentDay !== previousDay) {\r\n thisTour.itinerary.push({\r\n country: it.country,\r\n dateFormatted: (thisTour.tourType === \"Multi-Day Tour\" && thisTour.tourCategory === \"NoSpecificDeparture\") ? \"\" : it.segmentDate,\r\n description: \"\",\r\n hotelName: it.hotelName,\r\n itineraryDay: currentDay,\r\n meals: it.mealsFormatted || \"None\",\r\n stops: []\r\n });\r\n previousDay = currentDay;\r\n }\r\n\r\n if (thisTour.itinerary[currentDay - 1]) { // Sanity check, just in case\r\n thisTour.itinerary[currentDay - 1].stops.push({\r\n latitude: it.portLatitude || 0,\r\n location: it.portName || \"N/A\",\r\n longitude: it.portLongitude || 0\r\n });\r\n }\r\n\r\n if (it.portLatitude && it.portLongitude && it.portLatitude !== 0 && it.portLongitude !== 0) {\r\n routeMapConfig.value.routeMarkers.push({\r\n masterEntityId: 0,\r\n pinCoordinate: {\r\n latitude: it.portLatitude,\r\n longitude: it.portLongitude\r\n },\r\n pinType: 0,\r\n locationPath: \"\",\r\n title: it.portName\r\n });\r\n }\r\n\r\n if (it.stopDescription && it.stopDescription !== \"\") {\r\n if (thisTour.itinerary[currentDay - 1]) { // Sanity check, just in case\r\n thisTour.itinerary[currentDay - 1].description = sanitizeUserGeneratedContent(it.stopDescription);\r\n }\r\n }\r\n previousDataDay = it.dayOfTour;\r\n\r\n // Wanderlist, Default List Name\r\n if (it.country && countryFound === 0) {\r\n wanderlistTourName.value = it.country;\r\n countryFound++;\r\n } else if (countryFound === 1) { // -- Multi-Day Tour\r\n if (it.country && it.country !== wanderlistTourName.value) {\r\n wanderlistTourName.value += ` and ${it.country}`;\r\n countryFound++;\r\n }\r\n }\r\n });\r\n }\r\n\r\n // Add Ons -- Day tours\r\n if (resultTour.addOns && resultTour.addOns.addOnDayTours && resultTour.addOns.addOnDayTours.length) {\r\n const dayTours: AddOnDayTours[] = [];\r\n resultTour.addOns.addOnDayTours.forEach((segment, segmentIndex: number) => {\r\n if (segment.dayTours && segment.dayTours.length) {\r\n dayTours.push({ // First add the date+location segment\r\n addOnDate: segment.addOnDate,\r\n addOnLocation: segment.addOnLocation,\r\n addOns: []\r\n });\r\n\r\n segment.dayTours.forEach((pkg: DayTourPackage) => { // Then loop through the tours for that segment and add them\r\n dayTours[segmentIndex].addOns.push({\r\n activityLevel: pkg.activityLevelList || \"\",\r\n ageAppropriateness: pkg.ageAppropriatenessList || \"\",\r\n companyName: pkg.supplierName,\r\n description: sanitizeUserGeneratedContent(pkg.dayTourDescription),\r\n eventDate: pkg.dayTourDateFormatted,\r\n eventLength: pkg.dayTourLength,\r\n experiences: (pkg.bulletedActivityTypes) ? pkg.bulletedActivityTypes : \"\",\r\n id: parseInt(pkg.dayTourMasterEntityID, 10),\r\n location: pkg.locationFormatted,\r\n meals: pkg.mealsFormatted || \"\",\r\n name: pkg.dayTourName,\r\n thumbnailImageUrl: pkg.thumbnailImageUrl || \"\",\r\n url: cobrandLink(`/packages/${pkg.dayTourMasterEntityID}/${slugify(pkg.dayTourName)}`),\r\n virtuosoHotel: \"\"\r\n });\r\n });\r\n }\r\n });\r\n thisTour.addOnDayTours = dayTours;\r\n }\r\n\r\n\r\n // Promotions -- Virtuoso Exclusive Promotions added first\r\n if (resultTour.promotions && resultTour.promotions.length) {\r\n const visiblePromotions: Promotion[] = [];\r\n resultTour.promotions.forEach((promo) => {\r\n if (!promo.isAdvisorIncentive && !promo.incentiveTypeCode) {\r\n visiblePromotions.push({\r\n description: promo.description || \"\",\r\n endDateMS: (promo.travelEndDate) ? new Date(promo.travelEndDate).getTime() : 0,\r\n formattedTravelDates: promo.formattedTravelDates || \"\",\r\n isExclusive: false,\r\n name: promo.promotionName || \"\",\r\n promotionId: promo.masterEntityId,\r\n startDateMS: (promo.travelStartDate) ? new Date(promo.travelStartDate).getTime() : 0,\r\n url: (promo.masterEntityId) ? cobrandLink(`/promotions/${promo.masterEntityId}`) : \"\"\r\n });\r\n }\r\n });\r\n\r\n thisTour.promotions = visiblePromotions;\r\n topPromoName = (thisTour.promotions.length) ? thisTour.promotions[0].name : \"\";\r\n }\r\n\r\n tour.value = thisTour;\r\n\r\n topSplashData.value = {\r\n companyName: thisTour.companyName,\r\n featuredImageCaption: thisTour.featuredImageCaption,\r\n featuredImageUrl: thisTour.featuredImageUrl,\r\n featuredVideoUrl: thisTour.featuredVideoUrl,\r\n productName: thisTour.tourName,\r\n productType: ProductType.TOURS,\r\n ...(thisTour.promotions[0] && { promotion: thisTour.promotions[0] }),\r\n wanderlistId: heartableUrl,\r\n wanderlistName: wanderlistTourName.value\r\n } as ProductTopSplash;\r\n\r\n\r\n\r\n // Recommended Advisors -- country of second day for multi-day tours, first day otherwise\r\n let recommendedAdvisorCountry = \"\";\r\n if (thisTour.itinerary.length) {\r\n if (isDayTour.value) {\r\n recommendedAdvisorCountry = thisTour.itinerary[0].country;\r\n } else {\r\n recommendedAdvisorCountry = (thisTour.itinerary.length > 1) ? thisTour.itinerary[1].country : thisTour.itinerary[0].country;\r\n }\r\n }\r\n\r\n recommendedAdvisorQuery.value = {\r\n Id: thisTour.id,\r\n InterestType: \"\",\r\n ProductLocationCountry: recommendedAdvisorCountry,\r\n ProductPois: \"\",\r\n ProductTypeName: \"Tour\"\r\n } as RecommendedAdvisorQuery;\r\n\r\n isReady.value = true; \r\n\r\n nextTick(() => {\r\n // Onload scroll to tab support\r\n showThenJumpToTab();\r\n if (thisTour.tourDescription) {\r\n applyVerticalTruncation(document.querySelectorAll(\".product-detail-overview .truncate-vertical\"));\r\n }\r\n\r\n enableHearts(productDetailRef.value);\r\n });\r\n\r\n } else {\r\n redirectOnError();\r\n }\r\n }, () => redirectOnError());\r\n }\r\n\r\n function redirectOnError(): void {\r\n toastError(\"Error retrieving data\");\r\n setTimeout(() => {\r\n location.href = cobrandLink(`/travel/luxury-tours`);\r\n }, 3000);\r\n }\r\n\r\n function scrollToTabResponsive(tabName = \"\"): void {\r\n if (isMobileScreenWidth()) {\r\n return (tabName) ? document.getElementById(`tc-${tabName}`)?.scrollIntoView()\r\n : document.querySelector(\".tab-content .-active\")?.scrollIntoView({ block: \"start\" });\r\n }\r\n tabNavContainerRef.value.scrollIntoView();\r\n }\r\n\r\n function setRecommendedAdvisorIds(advisor1MEID?: number, advisor2MEID?: number): void {\r\n\r\n const getVirtuosoTourSearchIndexValue = sessionStorage.getItem(`VirtuosoTourSearchIndex_${props.productId}`);\r\n\r\n if (getVirtuosoTourSearchIndexValue) {\r\n sessionStorage.removeItem(`VirtuosoTourSearchIndex_${props.productId}`);\r\n }\r\n\r\n trackEvent(\"view_item\", {\r\n ...((getVirtuosoTourSearchIndexValue) && { index: parseInt(getVirtuosoTourSearchIndexValue, 10) }), // should add index with value if session key existed\r\n item_id: `${props.productId}`,\r\n item_name: tour.value.tourNameWithoutLength,\r\n coupon: (tour.value.promotions.length >= 1) ? \"Promotion Available\" : \"\",\r\n item_category: \"Tour\",\r\n item_category2: (tour.value.activityLevel) ? `${tour.value.activityLevel}` : \"\",\r\n item_category3: tour.value.tourLength,\r\n item_category4: (advisor1MEID) ? `${advisor1MEID}` : \"\",\r\n item_category5: (advisor2MEID) ? `${advisor2MEID}` : \"\",\r\n item_variant: `${tour.value.galleryImages.length} photos`\r\n });\r\n };\r\n\r\n function showTab(tabName: string, preventJump = false, fromTopLink = false): void {\r\n document.querySelectorAll(\".tab-nav-container .-active, .tab-content .-active\").forEach((el) => el.classList.remove(\"-active\"));\r\n document.querySelectorAll(`#tab-${tabName}, #tc-${tabName}`).forEach((el) => el.classList.add(\"-active\"));\r\n\r\n if (!preventJump) {\r\n scrollToTabResponsive(tabName);\r\n }\r\n\r\n if (fromTopLink) {\r\n\r\n trackEvent(\"view_promotion\", {\r\n item_id: `${tour.value.promotions[0].promotionId}`,\r\n item_name: topPromoName,\r\n item_category: \"Promotion\",\r\n item_variant: \"Tour\",\r\n item_category2: \"Promo Visibility: All\",\r\n affiliation: `${props.productId}`\r\n });\r\n }\r\n }\r\n\r\n function showThenJumpToTab(): void {\r\n // Which bottom tab should be selected by default\r\n const defaultTabName =\r\n (tour.value.itinerary.length > 1) ? \"itinerary\" :\r\n (tour.value.virtuosoBenefits) ? \"benefits\" :\r\n (tour.value.promotions.length) ? \"promotions\" :\r\n (tour.value.addOnDayTours.length) ? \"add-ons\" : \"\";\r\n\r\n const qsTabLabel = qsParams[\"tab\"];\r\n\r\n showTabBlock.value = (defaultTabName.length > 0);\r\n\r\n nextTick(() => { // Tab Show then Scroll\r\n // Show\r\n if (defaultTabName) { // first tab\r\n showTab(defaultTabName, true);\r\n }\r\n if (qsTabLabel && document.getElementById(`tc-${qsTabLabel}`)) { // check qsParam tab exists\r\n showTab(qsTabLabel, true);\r\n }\r\n // Show Promo Manual Tab\r\n if (qsParams.promotions === \"1\") { // If the manually added ?promotions=1 query string variable is present, scroll to the promotions section on load\r\n const qsPromoTabName = (tour.value.promotions.length) ? \"promotions\" : defaultTabName;\r\n showTab(qsPromoTabName, true);\r\n }\r\n // Default Scroll\r\n if ((\"tab\" in qsParams) || (\"promotions\" in qsParams)) {\r\n scrollToTabResponsive();\r\n }\r\n });\r\n\r\n }\r\n\r\n function toggleAddOn(id: number): void {\r\n const btn = document.getElementById(`add-on-toggle-shore-excursion-${id}`);\r\n const container = document.getElementById(`add-on-container-shore-excursion-${id}`);\r\n\r\n\r\n btn.classList.toggle(\"icon-plus-circle-ut\");\r\n btn.classList.toggle(\"icon-minus-circle-ut\");\r\n toggleSlideWithFade(container, 500); \r\n }\r\n\r\n loadTour();\r\n\r\n</script>\r\n","const desktopRootMargin = \"-214px\"; // -152 header height - 60 .-info height\r\nconst desktopRootMarginNearStickySubhead = \"-294px 0px 0px 0px\";\r\nconst mobileTopRootMargin = \"-122px\"; // -56 header height - 66 .-info height\r\n\r\n/**\r\n *\r\n * @param {HTMLElement} advisorHeader - selector for '.advisor-header'\r\n * @param {HTMLElement} stickyHeader - selector for '.sticky-advisor-header'\r\n * */\r\nfunction advisorHeaderNameObserver(advisorHeader: HTMLElement, stickyHeader: HTMLElement): void {\r\n const config = {\r\n rootMargin: `${(window.innerWidth >= 1200 ? desktopRootMargin : mobileTopRootMargin)} 0px 0px 0px`, // include nav bar height desktop & mobile\r\n threshold: 0\r\n };\r\n\r\n const stickyObserver = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {\r\n entries.forEach((entry: IntersectionObserverEntry) => {\r\n if (!entry.isIntersecting) { // not visible in viewport\r\n advisorHeader.classList.add(\"-sticky\");\r\n stickyHeader.classList.add(\"-sticky\");\r\n } else {\r\n stickyHeader.classList.remove(\"-sticky\");\r\n advisorHeader.classList.remove(\"-sticky\");\r\n }\r\n });\r\n }, config);\r\n\r\n stickyObserver.observe(advisorHeader);\r\n}\r\n\r\n\r\n\r\nfunction setActiveTab(tab: HTMLElement): void {\r\n if (!tab.classList.contains(\"-active\")) {\r\n Array.from(tab.parentElement.children).forEach((tab) => tab.classList.remove(\"-active\"));\r\n tab.classList.add(\"-active\");\r\n }\r\n}\r\n\r\n\r\n\r\n/**\r\n * Sets up observers with different thresholds for each section\r\n */\r\nfunction observeSections(): void {\r\n const aboutTab = document.getElementById(\"tab-about\");\r\n const placesTab = document.getElementById(\"tab-places\");\r\n const reviewsTab = document.getElementById(\"tab-reviews\");\r\n\r\n // About section\r\n const aboutObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n if (entry.intersectionRatio > 0.5) {\r\n setActiveTab(aboutTab);\r\n } else { // This catches scrolling up when the threshold wasn't offscreen\r\n if (placesTab && placesTab.classList.contains(\"-active\")) {\r\n setActiveTab(aboutTab);\r\n }\r\n }\r\n }\r\n });\r\n }, { threshold: [0.5, 0.9], rootMargin: desktopRootMarginNearStickySubhead });\r\n aboutObserver.observe(document.getElementById(\"about\"));\r\n\r\n // Places Visited section\r\n if (placesTab) {\r\n const placesObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n setActiveTab(placesTab);\r\n }\r\n });\r\n }, { threshold: 0.5, rootMargin: desktopRootMarginNearStickySubhead });\r\n placesObserver.observe(document.getElementById(\"places\"));\r\n }\r\n\r\n // Reviews & Recommendations section\r\n if (reviewsTab) {\r\n const reviewsObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n if (entry.intersectionRatio > 0.25) {\r\n setActiveTab(reviewsTab);\r\n } else { // This catches scrolling down when the threshold wasn't off screen\r\n if (placesTab && placesTab.classList.contains(\"-active\")) {\r\n setActiveTab(reviewsTab);\r\n }\r\n }\r\n }\r\n });\r\n }, { threshold: [0.25, 0.5], rootMargin: desktopRootMarginNearStickySubhead });\r\n reviewsObserver.observe(document.getElementById(\"reviews\"));\r\n }\r\n}\r\n\r\n\r\n/**\r\n * Runs Observers for the Advisor Detail Page\r\n * to implement sticky subhead and subnav highlighting\r\n */\r\nexport function initAdvisorSubhead(): void {\r\n const advisorHeader = document.querySelector<HTMLElement>(\".advisor-header\");\r\n const stickyHeader = document.querySelector<HTMLElement>(\".sticky-advisor-header\");\r\n const sections = document.querySelectorAll(\"section\");\r\n\r\n if (advisorHeader && stickyHeader && sections.length) {\r\n // Sticky subhead\r\n advisorHeaderNameObserver(advisorHeader, stickyHeader);\r\n\r\n // Subnav highlighting\r\n observeSections();\r\n } else {\r\n console.warn(\"Page sections not found, not applying observers\");\r\n }\r\n}\r\n","<template>\r\n <div v-if=\"isReady\" class=\"product-detail -advisor\">\r\n <div class=\"container px-lg-0\">\r\n <div class=\"advisor-header context-dark\">\r\n <div class=\"-photo\">\r\n <img :src=\"advisor.featuredImageUrl\" :title=\"'Photo of ' + advisor.advisorName\" :alt=\"'Photo of ' + advisor.advisorName\">\r\n </div>\r\n <div class=\"-info\">\r\n <h1 class=\"mb-2\">{{ advisor.advisorFirstName }}<br />{{ advisor.advisorLastName }}</h1>\r\n <div class=\"-agency\">{{ advisor.icPrefix }}<a :href=\"advisor.agencyUrl\" v-html=\"advisor.companyName\"></a></div>\r\n <div class=\"-location\" v-html=\"advisor.location\"></div>\r\n </div>\r\n <div class=\"-links pt-2 pt-md-1\">\r\n <ul v-if=\"advisor.socialMedia.length\" class=\"list-unstyled list-inline mb-0\">\r\n <li v-for=\"(website, index) in advisor.socialMedia\" :key=\"index\"><a :href=\"website.siteUrl\" class=\"text-decoration-none\" target=\"_blank\" rel=\"external\"><i :class=\"socialMediaIconMap.get(website.siteName)\"></i></a></li>\r\n </ul>\r\n </div>\r\n </div>\r\n <div class=\"sticky-advisor-header bg--white justify-content-sm-center\">\r\n <div class=\"-info context-dark\">\r\n <div class=\"h2\" v-html=\"advisor.advisorName\"></div>\r\n </div>\r\n <div class=\"d-flex flex-row justify-content-lg-between align-items-center my-2\">\r\n <div class=\"d-none d-xl-block\">\r\n <ul class=\"tab-nav-container\">\r\n <li id=\"tab-about\" class=\"-active\"><button @click=\"jumpToSection('about')\">About Me</button></li>\r\n <li v-if=\"placesVisitedMapConfig.mapMarkers.length\" id=\"tab-places\"><button @click=\"jumpToSection('places')\">Places Visited</button></li>\r\n <li v-if=\"advisor.reviewsCount > 0\" id=\"tab-reviews\"><button @click=\"jumpToSection('reviews')\">Reviews & Recommendations</button></li>\r\n </ul>\r\n </div>\r\n <div v-if=\"advisor.isAcceptingNewClients\" class=\"-contact-button\">\r\n <advisor-contact-button-component class=\"-inline\" :advisor=\"advisorContactButtonInfo(advisor)\" event-name=\"Advisor Detail\" button-style=\"sub-head\"></advisor-contact-button-component>\r\n </div>\r\n </div>\r\n </div>\r\n <section id=\"about\">\r\n <div class=\"product-detail-overview\">\r\n <div class=\"-info\">\r\n <div v-if=\"advisor.companyLogoUrl\" class=\"d-block d-md-none my-3 text-center -logo\">\r\n <a :href=\"advisor.agencyUrl\"><img :src=\"advisor.companyLogoUrl\" :title=\"advisor.icPrefix + advisor.companyName\" :alt=\"advisor.icPrefix + advisor.companyName\" /></a>\r\n </div>\r\n <h2 class=\"text--serif fw-normal\">About Me</h2>\r\n <div class=\"-description mt-3\" v-html=\"advisor.description\"></div>\r\n </div>\r\n <div class=\"-gallery mt-5 mt-md-0 mb-6\">\r\n <div v-if=\"advisor.companyLogoUrl\" class=\"d-none d-md-block mb-4 text-center -logo\">\r\n <a :href=\"advisor.agencyUrl\"><img :src=\"advisor.companyLogoUrl\" :title=\"advisor.icPrefix + advisor.companyName\" :alt=\"advisor.icPrefix + advisor.companyName\" /></a>\r\n </div>\r\n <div v-if=\"advisor.interests.length || advisor.destinations.length\">\r\n <h4 class=\"mb-2\">Travel Specialties</h4>\r\n <div class=\"row mb-3\">\r\n <div v-if=\"advisor.interests.length\" class=\"col-6\">\r\n <b>Interests</b><br />\r\n <ul class=\"list-unstyled mt-half\">\r\n <li v-for=\"(interest, index) in advisor.interests\" :key=\"index\" v-html=\"interest\"></li>\r\n </ul>\r\n </div>\r\n <div v-if=\"advisor.destinations.length\" class=\"col-6\">\r\n <b>Destinations</b><br />\r\n <ul class=\"list-unstyled mt-half\">\r\n <li v-for=\"(destination, destIndex) in advisor.destinations\" :key=\"destination\" :class=\"[(destIndex > 4 && !showExtraDestinations) ? 'd-none' : '']\"\r\n v-html=\"destination\"></li>\r\n <li v-if=\"advisor.destinations.length > 5 && !showExtraDestinations\"><button class=\"btn btn-link btn-sm p-0\" @click=\"showExtraDestinations = true\">View All</button></li>\r\n </ul>\r\n </div>\r\n </div>\r\n </div>\r\n <template v-if=\"languagesCSV\">\r\n <h4>Languages</h4>\r\n <div class=\"mb-3\" v-html=\"languagesCSV\"></div>\r\n </template>\r\n <template v-if=\"advisor.sellingTravelSince\">\r\n <h4>Planning Travel Since:</h4>\r\n <div class=\"mb-3\" v-html=\"advisor.sellingTravelSince\"></div>\r\n </template>\r\n <div v-if=\"advisor.isVVCertifiedHost\" class=\"text-center\">\r\n <img src=\"https://virtuoso-prod.dotcms.cloud/images/products/vv-certified-host.png\" title=\"Virtuoso Voyages Certified Host\" alt=\"Virtuoso Voyages Certified Host\" width=\"200\" height=\"118\" />\r\n </div>\r\n <a v-if=\"isNetworkUser\" class=\"btn btn-primary-emphasis btn-sm d-block mt-3\" :href=\"cobrandLink(`/advisors/${props.productId}`)\" @click=\"handleLegacyLinkClick\">{{ (isAdvisor()) ? \"For Advisors: Update Your Profile\" : \"For Partners: Advanced Results\" }}</a>\r\n </div>\r\n </div>\r\n </section>\r\n <section v-if=\"placesVisitedMapConfig.mapMarkers.length\" id=\"places\">\r\n <h2 class=\"text--serif fw-normal mb-3 mb-xl-0\">Places Visited</h2>\r\n <table class=\"largeMapTableContainer mx-auto\">\r\n <tbody>\r\n <tr>\r\n <td class=\"largeMapCell\">\r\n <MapView type=\"placesVisited\" :places-visited-map-config=\"placesVisitedMapConfig\" />\r\n </td>\r\n <td class=\"largeMapListCell\">\r\n <div class=\"largeMapListDiv\">\r\n <table class=\"largeMapListTable f16\"></table>\r\n </div>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n </section>\r\n <section v-if=\"advisor.reviewsCount > 0\" id=\"reviews\">\r\n <h2 class=\"text--serif fw-normal mb-3\">Reviews & Recommendations</h2>\r\n <product-reviews-component v-if=\"!suppressReviews\" :company-id=\"advisor.id\" :product-type=\"ProductType.ADVISORS\" :reviews-data=\"reviewsData\"></product-reviews-component>\r\n </section>\r\n </div>\r\n </div>\r\n <LogoSplash v-else />\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import ProductReviewsComponent from \"components/products/details/product-reviews.vue\";\r\n import AdvisorContactButtonComponent from \"components/advisor/advisor-contact-button.vue\";\r\n import LogoSplash from \"components/shared/logo-splash.vue\";\r\n import MapView from \"components/shared/map-view.vue\";\r\n import { AdvisorVisitedPlace, ProductDetailsAdvisorResponse } from \"interfaces/responses/product-detail-responses\";\r\n import { AdvisorContactButton } from \"interfaces/advisor\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { MapMarker } from \"interfaces/map\";\r\n import { ProductDetailsAdvisor } from \"interfaces/product\";\r\n import { getProductDetails } from \"services/api/products\";\r\n import { isAdvisor, isNetworkUser, virtuosoUser } from \"services/auth/user-info\";\r\n import { formatLocation } from \"services/helpers/destinations\";\r\n import { generateMediaServerImageUrl } from \"services/helpers/images\";\r\n import { toastError } from \"services/helpers/toasts\";\r\n import { renderMetadata } from \"services/layout/metadata\";\r\n import { initAdvisorSubhead } from \"services/layout/sticky-subhead\";\r\n import { formatAdvisorAgencyName } from \"services/transformers/products\";\r\n import { trackEvent } from \"services/analytics\";\r\n import { capitalizeFirst, cobrandLink, getCobrandType, getPlural, slugify } from \"virtuoso-shared-web-ui\";\r\n import { nextTick, ref } from \"vue\";\r\n\r\n\r\n const props = defineProps({\r\n productId: {\r\n type: Number,\r\n default: undefined\r\n },\r\n suppressReviews: {\r\n type: Boolean,\r\n default: false\r\n }\r\n });\r\n\r\n const advisor = ref<ProductDetailsAdvisor>({} as ProductDetailsAdvisor);\r\n const isReady = ref(false);\r\n const languagesCSV = ref(\"\");\r\n const reviewsData = ref({});\r\n const showExtraDestinations = ref(false);\r\n const placesVisitedMapConfig = ref({ mapMarkers: [] as MapMarker[]});\r\n\r\n const socialMediaIconMap = new Map([\r\n [\"Facebook\", \"icon-facebook-circle\"],\r\n [\"Instagram\", \"icon-instagram-circle\"],\r\n [\"LinkedIn\", \"icon-linkedin-circle\"],\r\n [\"Pinterest\", \"icon-pinterest-circle\"],\r\n [\"Twitter\", \"icon-twitter-circle\"],\r\n [\"YouTube\", \"icon-youtube-circle\"],\r\n [\"Facebook\", \"icon-facebook-circle\"],\r\n [\"Facebook\", \"icon-facebook-circle\"],\r\n [\"Facebook\", \"icon-facebook-circle\"]\r\n ]);\r\n\r\n if (getCobrandType() === \"advisor\" && virtuosoUser.cobrandModel.advisorMeid !== props.productId) {\r\n location.href = cobrandLink(`/travel`);\r\n } else {\r\n loadAdvisor();\r\n }\r\n\r\n function advisorContactButtonInfo(advisor: ProductDetailsAdvisor): AdvisorContactButton {\r\n return {\r\n company: advisor.companyName,\r\n companyId: advisor.companyId,\r\n country: advisor.agencyCountryName,\r\n email: advisor.advisorEmail,\r\n id: advisor.id,\r\n name: advisor.advisorName,\r\n phone: advisor.advisorPhone,\r\n productType: ProductType.ADVISORS\r\n };\r\n }\r\n\r\n function handleLegacyLinkClick(): void {\r\n trackEvent(\"legacy_page_click\", {\r\n affiliation: `${advisor.value.companyId}`,\r\n item_id: `${props.productId}`,\r\n item_name: advisor.value.advisorName,\r\n item_category: capitalizeFirst(ProductType.ADVISORS.slice(0,-1))\r\n });\r\n }\r\n\r\n function jumpToSection(sectionName: string): void {\r\n document.querySelector(\".tab-nav-container .-active\").classList.remove(\"-active\");\r\n document.getElementById(`tab-${sectionName}`).classList.add(\"-active\");\r\n\r\n document.getElementById(sectionName)?.scrollIntoView();\r\n setTimeout(function () {\r\n document.getElementById(sectionName)?.scrollIntoView(); // In case the sticky header appearing/disappearing caused a shift\r\n }, 1);\r\n }\r\n\r\n function loadAdvisor(): void {\r\n getProductDetails(ProductType.ADVISORS, props.productId).then((resultAdvisor: ProductDetailsAdvisorResponse) => {\r\n\r\n if (resultAdvisor && resultAdvisor.advisorName) {\r\n const thisAdvisor: ProductDetailsAdvisor = {\r\n advisorEmail: resultAdvisor.advisorEmail,\r\n advisorFirstName: resultAdvisor.advisorFirstName,\r\n advisorLastName: resultAdvisor.advisorLastName,\r\n advisorName: resultAdvisor.advisorName,\r\n advisorPhone: resultAdvisor.officePhone,\r\n agencyCountryName: resultAdvisor.agencyCountryName,\r\n agencyUrl: cobrandLink(`/agencies/${resultAdvisor.companyId}/${slugify(resultAdvisor.companyName)}`),\r\n companyId: resultAdvisor.companyId,\r\n companyLogoUrl: resultAdvisor.companyLogo,\r\n companyName: resultAdvisor.companyName,\r\n dbaName: resultAdvisor.advisorDoingBusinessAs,\r\n description: resultAdvisor.advisorAboutMe,\r\n destinations: resultAdvisor.advisorSpecialtyCountries || [],\r\n featuredImageUrl: generateMediaServerImageUrl(resultAdvisor.advisorProfileImageUrl, { width: 200 }),\r\n icPrefix: formatAdvisorAgencyName(resultAdvisor.companyName, resultAdvisor.advisorIsIndependentContractor, resultAdvisor.advisorDoingBusinessAs, true),\r\n id: resultAdvisor.advisorMasterEntityId,\r\n interests: resultAdvisor.advisorInterestTypes || [],\r\n isAcceptingNewClients: resultAdvisor.isAdvisorAcceptingNewClients,\r\n isIC: resultAdvisor.advisorIsIndependentContractor,\r\n isVVCertifiedHost: resultAdvisor.isVVCertifiedHost,\r\n languages: resultAdvisor.advisorLanguages || [],\r\n location: \"\", // Derived below\r\n reviewsCount: resultAdvisor.totalActiveReviews || 0,\r\n reviewsPercent: resultAdvisor.totalRecommendedPercent || 0,\r\n sellingTravelSince: Number(resultAdvisor.advisorSellingTravelSinceYear) || null,\r\n socialMedia: [] // Derived below\r\n };\r\n\r\n // Advisor Address\r\n if (resultAdvisor.profileAddresses && resultAdvisor.profileAddresses.length) {\r\n const primaryAddress = resultAdvisor.profileAddresses.find((item) => item.typeName === \"Primary\");\r\n if (primaryAddress) {\r\n thisAdvisor.location = formatLocation(\r\n primaryAddress.city,\r\n primaryAddress.state,\r\n primaryAddress.country\r\n );\r\n }\r\n }\r\n\r\n languagesCSV.value = thisAdvisor.languages.join(\", \");\r\n\r\n // Social Media\r\n if (resultAdvisor.profileWebSites && resultAdvisor.profileWebSites.length) {\r\n (resultAdvisor.profileWebSites).forEach((website) => {\r\n if (website.typeName && website.typeName !== \"Primary\" && website.link) {\r\n thisAdvisor.socialMedia.push({\r\n siteName: website.typeName,\r\n siteUrl: website.link\r\n });\r\n }\r\n });\r\n }\r\n\r\n // Reviews\r\n if (thisAdvisor.reviewsCount > 0) {\r\n reviewsData.value = {\r\n percent: `<b>${thisAdvisor.reviewsPercent}% Recommended</b>`,\r\n count: `<b>${thisAdvisor.reviewsCount} Review${getPlural(thisAdvisor.reviewsCount)}</b>`\r\n };\r\n }\r\n\r\n\r\n // Places Visited map data\r\n const placesVisited: AdvisorVisitedPlace[] = resultAdvisor.advisorVisitedPlaces || [];\r\n\r\n if (placesVisited.length) {\r\n placesVisited.forEach((place) => {\r\n placesVisitedMapConfig.value.mapMarkers.push({\r\n Id: place.pointOfInterestId,\r\n PushpinLabel: \"\", // Always blank?\r\n SequenceLabel: `${place.pointOfInterestId}`,\r\n Longitude: place.longitude,\r\n Latitude: place.latitude,\r\n InfoWindowHtml: `<h3>${place.placeName}, ${place.countryName}</h3><div>Last Trip: ${place.lastYearVisited}<br>Number of Trips: ${place.numOfVisits}`,\r\n Title: `${place.placeName}, ${place.countryName}`,\r\n CountryCodeISO2: place.countryCodeISO2,\r\n CountryId: place.countryId,\r\n Date: `${place.lastYearVisited}`,\r\n NumOfVisits: place.numOfVisits\r\n });\r\n });\r\n }\r\n\r\n advisor.value = thisAdvisor;\r\n\r\n renderMetadata({\r\n title: thisAdvisor.advisorName\r\n });\r\n\r\n isReady.value = true;\r\n\r\n // Apply sticky subhead and highlighted subnav observers\r\n nextTick(function () {\r\n initAdvisorSubhead();\r\n });\r\n\r\n // GA4 tracking\r\n const getVirtuosoAdvisorSearchIndexValue = sessionStorage.getItem(`VirtuosoAdvisorSearchIndex_${props.productId}`);\r\n\r\n if (getVirtuosoAdvisorSearchIndexValue) {\r\n sessionStorage.removeItem(`VirtuosoAdvisorSearchIndex_${props.productId}`);\r\n }\r\n\r\n trackEvent(\"view_item\",\r\n {\r\n ...((getVirtuosoAdvisorSearchIndexValue) && { index: parseInt(getVirtuosoAdvisorSearchIndexValue, 10) }), // should add index with value if session key existed\r\n item_id: `${props.productId}`,\r\n item_name: advisor.value.advisorName,\r\n item_brand: (advisor.value.isIC) ? advisor.value.dbaName : advisor.value.companyName,\r\n item_category: \"Advisor\",\r\n item_category2: advisor.value.location,\r\n item_category3: `${advisor.value.reviewsCount}`,\r\n item_category4: (advisor.value.isVVCertifiedHost) ? \"virtuoso voyages\" : \"\",\r\n item_variant: (advisor.value.isIC) ? \"independent contractor\" : \"advisor\",\r\n affiliation: (advisor.value.isIC) ? advisor.value.companyName : \"\"\r\n });\r\n } else {\r\n redirectOnError();\r\n }\r\n }, () => redirectOnError());\r\n };\r\n\r\n function redirectOnError(): void {\r\n toastError(\"Record not found or you don't have access to view it\");\r\n setTimeout(function () {\r\n location.href = cobrandLink(`/travel/advisors`);\r\n }, 3000);\r\n }\r\n</script>\r\n","<template>\r\n <component :is=\"detailsComponentMap[detailsComponentKey]\" :product-id=\"productId\"></component>\r\n</template>\r\n\r\n\r\n<script setup lang=\"ts\">\r\n import CruiseDetailsComponent from \"components/products/details/cruise-details.vue\";\r\n import HotelDetailsComponent from \"components/products/details/hotel-details.vue\";\r\n import TourDetailsComponent from \"components/products/details/tour-details.vue\";\r\n import AdvisorDetailsComponent from \"components/advisor/advisor-details.vue\";\r\n import { ProductType } from \"interfaces/enums\";\r\n import { PropType } from \"vue\";\r\n import type { Component } from \"vue\"; \r\n\r\n const props = defineProps({\r\n catalogType: {\r\n type: String as PropType<ProductType>,\r\n default: undefined\r\n },\r\n productId: {\r\n type: Number,\r\n default: undefined\r\n }\r\n }); \r\n // Map of component names to actual components\r\n const detailsComponentMap: Record<string, Component> = {\r\n \"cruise-details-component\": CruiseDetailsComponent,\r\n \"hotel-details-component\": HotelDetailsComponent,\r\n \"tour-details-component\": TourDetailsComponent,\r\n \"advisor-details-component\": AdvisorDetailsComponent\r\n };\r\n\r\n const detailsComponentKey = props.catalogType.toLowerCase().slice(0, -1) + \"-details-component\";\r\n</script>\r\n","import ProductDetailsComponent from \"components/products/details/product-details.vue\";\r\nimport { ProductType } from \"interfaces/enums\";\r\nimport { captureRedirectData } from \"services/analytics\";\r\nimport { cobrandLink } from \"virtuoso-shared-web-ui\";\r\nimport { createApp } from \"vue\";\r\nimport { mountApp } from \"vue-app\";\r\n\r\n\r\nif (window.VIRTUOSO.productType && window.VIRTUOSO.productId) {\r\n const app = createApp(ProductDetailsComponent,\r\n {\r\n catalogType: window.VIRTUOSO.productType,\r\n productId: window.VIRTUOSO.productId\r\n }\r\n );\r\n mountApp(app, \"page-app\");\r\n} else {\r\n captureRedirectData();\r\n if (window.VIRTUOSO.productType) {\r\n const redirUrl = (window.VIRTUOSO.productType === ProductType.ADVISORS) ? \"/travel/advisors\" : `/travel/luxury-${window.VIRTUOSO.productType}`;\r\n console.error(\"Product ID not provided\");\r\n location.href = cobrandLink(redirUrl);\r\n } else {\r\n console.error(\"Product Type not provided\");\r\n location.href = cobrandLink(\"/travel\");\r\n }\r\n}\r\n\r\n"],"names":["props","__props","collapseMobileFields","ref","hostedBenefitNumberLabel","getPlural","benefitRef","useTemplateRef","expandHostedBenefits","splideElement","evt","applyVerticalTruncation","truncateElements","element","viewMoreText","viewLessText","initialHeight","innerWrap","maxHeight","applyTruncationHandlers","el","handleTransitionEnd","e","isCruise","ProductType","isTour","nextTick","clientNamesValue","_useModel","canViewComponent","isAdvisor","isVStaff","isEmbeddedMode","showTrackCruiseForm","validationText","isLoading","doTrackCruise","saveTrackSailing","resultJSON","toastSuccess","toggleTrackSailingForm","toastError","clearValue","provide","computed","advisorSearchquery","advisorCatalogLinkFiltered","cobrandLink","isNetworkUser","gaCategory","hasAdvisors","recommendedAdvisors","translateB2BFacetToConsumer","getAdvisors","advisorContactButtonInfo","advisor","parseAdvisorMeidFromUrlString","firstRecommendedAdvisor","lastRecommendedAdvisor","isCobranded","getRecommendedAdvisors","advisorsJSON","validAdvisors","generateMediaServerImageUrl","translateToConsumerUrl","extractCountryFromLocationString","err","hasMultipleHostedBenefits","topPromoName","carouselConfig","cruiseTitle","heartableUrl","hasBenefits","isBenefitsCarouselInitialized","isOverviewReady","isReady","legacyLink","legacyLinkLabel","isSupplier","productDetailRef","qsParams","parseURLParameters","recommendedAdvisorQuery","sailing","tabNavContainerRef","topSplashData","autoExpandAndToggle","item","toggleAddOn","goToSailing","elSelect","newId","handleLegacyLinkClick","setB2BDesktopCookie","trackEvent","capitalizeFirst","hasOnlyOnePostPackage","hasOnlyOnePrePackage","hasOnlyOneShoreExcursion","hydratePrePostPackages","packages","hydratedPackages","pkg","tourEventId","sanitizeUserGeneratedContent","slugify","loadOverview","sailingOverview","loadSailing","sailingCruiseLength","thisSailing","enableHearts","redirectOnError","getProductDetails","resultSailing","cruiseLength","theItems","galleryImages","hydrateImageGallery","featuredVideo","video","previousDay","currentDay","it","benefitGroup","ben","populateBenefits","hbg","hostedBenefits","forAllBenefits","hostList","hostTitle","hb","tempBenefit","host","memberName","dayTours","dayTour","segment","segmentIndex","visiblePromotions","promo","showThenJumpToTab","rawBenefits","eb","setRecommendedAdvisorIds","advisor1MEID","advisor2MEID","getVirtuosoCruiseSearchIndexValue","cruise","scrollToTabResponsive","tabName","isMobileScreenWidth","_a","_b","showTab","preventJump","fromTopLink","qsTabLabel","qsPromoTabName","id","btn","container","toggleSlideWithFade","hotelFeatureCategoryMap","hotelFeatureIconMap","showRoomSidebar","activeItemIndex","sideRoomDetailsRef","totalRooms","activeRoom","nextRoom","targetIndex","prevRoom","getRoomFeatures","groups","max","featureGroup","handleViewMoreClick","event","roomArrayIndex","bookingLink","bookingLinkForAdvisors","hotel","hasSustainabilityContent","isAdvisorOrVStaff","productMapConfig","reviewsData","showTabBlock","loadHotel","resultHotel","cmsJSON","hotelContentPromise","hotelResponse","cmsContentPromise","getCmsContent","productJSON","hasAddress","thisHotel","getSustainabilityCerts","formatLocation","combinedPromotions","sourcePromotionsExclusives","sourcePromotionsNormal","featuredPromoIndex","featuredItem","reviewsObj","mapPopupHtml","renderMetadata","getVirtuosoHotelSearchIndexValue","defaultTabName","tourExperienceIconMap","catalogType","isDayTour","isWheelchairAccessible","formattedLocation","routeMapConfig","tour","wanderlistTourName","goToTour","loadTour","resultTour","brandUrl","tourBrandSupplierTypesRestricted","thisTour","childTour","countryFound","previousDataDay","recommendedAdvisorCountry","getVirtuosoTourSearchIndexValue","desktopRootMargin","desktopRootMarginNearStickySubhead","mobileTopRootMargin","advisorHeaderNameObserver","advisorHeader","stickyHeader","config","entries","entry","setActiveTab","tab","observeSections","aboutTab","placesTab","reviewsTab","initAdvisorSubhead","sections","languagesCSV","showExtraDestinations","placesVisitedMapConfig","socialMediaIconMap","getCobrandType","virtuosoUser","loadAdvisor","jumpToSection","sectionName","resultAdvisor","thisAdvisor","formatAdvisorAgencyName","primaryAddress","website","placesVisited","place","getVirtuosoAdvisorSearchIndexValue","detailsComponentMap","CruiseDetailsComponent","HotelDetailsComponent","TourDetailsComponent","AdvisorDetailsComponent","detailsComponentKey","app","createApp","ProductDetailsComponent","mountApp","captureRedirectData","redirUrl"],"mappings":"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"}