@meetelise/chat 1.35.0 → 1.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/dist/src/MyPubnub.d.ts +116 -0
  2. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-item-matrix-qty-selector/addon-item-matrix-qty-selector-styles.d.ts +2 -0
  3. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-item-matrix-qty-selector/addon-item-matrix-qty-selector.d.ts +28 -0
  4. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-item-qty-selector/addon-item-qty-selector-styles.d.ts +2 -0
  5. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-item-qty-selector/addon-item-qty-selector.d.ts +18 -0
  6. package/dist/src/WebComponent/FeeCalculator/components/addons/common-addon-styles.d.ts +2 -0
  7. package/dist/src/WebComponent/FeeCalculator/components/addons/rentable-item-qty-selector/rentable-item-qty-selector-styles.d.ts +2 -0
  8. package/dist/src/WebComponent/FeeCalculator/components/addons/rentable-item-qty-selector/rentable-item-qty-selector.d.ts +22 -0
  9. package/dist/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout-styles.d.ts +2 -0
  10. package/dist/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.d.ts +46 -0
  11. package/dist/src/WebComponent/FeeCalculator/components/fee-card/fee-card-styles.d.ts +2 -0
  12. package/dist/src/WebComponent/FeeCalculator/components/fee-card/fee-card.d.ts +21 -0
  13. package/dist/src/WebComponent/FeeCalculator/components/fee-item/fee-item-styles.d.ts +2 -0
  14. package/dist/src/WebComponent/FeeCalculator/components/fee-item/fee-item.d.ts +13 -0
  15. package/dist/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector-styles.d.ts +2 -0
  16. package/dist/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.d.ts +34 -0
  17. package/dist/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card-styles.d.ts +2 -0
  18. package/dist/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card.d.ts +18 -0
  19. package/dist/src/WebComponent/FeeCalculator/components/incentive-banner/incentive-banner-styles.d.ts +1 -0
  20. package/dist/src/WebComponent/FeeCalculator/components/incentive-banner/incentive-banner.d.ts +8 -0
  21. package/dist/src/WebComponent/FeeCalculator/components/incentive-banner/index.d.ts +1 -0
  22. package/dist/src/WebComponent/FeeCalculator/components/index.d.ts +5 -0
  23. package/dist/src/WebComponent/FeeCalculator/components/promo-card/promo-card-styles.d.ts +2 -0
  24. package/dist/src/WebComponent/FeeCalculator/components/promo-card/promo-card.d.ts +13 -0
  25. package/dist/src/WebComponent/FeeCalculator/constants.d.ts +3 -0
  26. package/dist/src/WebComponent/FeeCalculator/fee-calculator-styles.d.ts +1 -0
  27. package/dist/src/WebComponent/FeeCalculator/fee-calculator.d.ts +55 -0
  28. package/dist/src/WebComponent/FeeCalculator/index.d.ts +2 -0
  29. package/dist/src/WebComponent/FeeCalculator/model/building-fee-view.d.ts +41 -0
  30. package/dist/src/WebComponent/FeeCalculator/model/building-fee.d.ts +82 -0
  31. package/dist/src/WebComponent/FeeCalculator/model/desired-addon.d.ts +5 -0
  32. package/dist/src/WebComponent/FeeCalculator/model/desired-rentable-item.d.ts +4 -0
  33. package/dist/src/WebComponent/FeeCalculator/model/index.d.ts +13 -0
  34. package/dist/src/WebComponent/FeeCalculator/model/item-combination.d.ts +6 -0
  35. package/dist/src/WebComponent/FeeCalculator/model/item-quantity.d.ts +4 -0
  36. package/dist/src/WebComponent/FeeCalculator/model/pricing-matrix.d.ts +18 -0
  37. package/dist/src/WebComponent/FeeCalculator/model/pricing-rule.d.ts +8 -0
  38. package/dist/src/WebComponent/FeeCalculator/model/rent-frequency.d.ts +1 -0
  39. package/dist/src/WebComponent/FeeCalculator/model/rentable-item-summary.d.ts +13 -0
  40. package/dist/src/WebComponent/FeeCalculator/model/rentable-item.d.ts +10 -0
  41. package/dist/src/WebComponent/FeeCalculator/model/transaction-category.d.ts +23 -0
  42. package/dist/src/WebComponent/FeeCalculator/model/unit-fee-bundle.d.ts +31 -0
  43. package/dist/src/WebComponent/LeadSourceClient.d.ts +46 -0
  44. package/dist/src/WebComponent/OfficeHours.d.ts +21 -0
  45. package/dist/src/WebComponent/Scheduler/date-picker.d.ts +28 -0
  46. package/dist/src/WebComponent/Scheduler/time-picker.d.ts +16 -0
  47. package/dist/src/WebComponent/Scheduler/tour-scheduler.d.ts +102 -0
  48. package/dist/src/WebComponent/Scheduler/tour-type-option.d.ts +13 -0
  49. package/dist/src/WebComponent/Scheduler/tourSchedulerStyles.d.ts +1 -0
  50. package/dist/src/WebComponent/actions/InputStyles.d.ts +1 -0
  51. package/dist/src/WebComponent/actions/action-confirm-button.d.ts +13 -0
  52. package/dist/src/WebComponent/actions/call-us-window.d.ts +37 -0
  53. package/dist/src/WebComponent/actions/collapse-expand-button.d.ts +8 -0
  54. package/dist/src/WebComponent/actions/details-window.d.ts +14 -0
  55. package/dist/src/WebComponent/actions/email-us-window.d.ts +46 -0
  56. package/dist/src/WebComponent/actions/formatPhoneNumber.d.ts +17 -0
  57. package/dist/src/WebComponent/actions/minimize-expand-button.d.ts +8 -0
  58. package/dist/src/WebComponent/chat-additional-actions.d.ts +28 -0
  59. package/dist/src/WebComponent/health-chat.d.ts +47 -0
  60. package/dist/src/WebComponent/healthchat-styles.d.ts +1 -0
  61. package/dist/src/WebComponent/icons/ApplyOutlineIcon.d.ts +2 -0
  62. package/dist/src/WebComponent/icons/BookTourOutlineIcon.d.ts +2 -0
  63. package/dist/src/WebComponent/icons/CalculatorOutlineIcon.d.ts +2 -0
  64. package/dist/src/WebComponent/icons/ChatOutlineIcon.d.ts +2 -0
  65. package/dist/src/WebComponent/icons/ChevronLeftIcon.d.ts +2 -0
  66. package/dist/src/WebComponent/icons/ChevronRightIcon.d.ts +2 -0
  67. package/dist/src/WebComponent/icons/ContactResidentIcon.d.ts +2 -0
  68. package/dist/src/WebComponent/icons/DollarOutlineIcon.d.ts +7 -0
  69. package/dist/src/WebComponent/icons/EmailOutlineIcon.d.ts +2 -0
  70. package/dist/src/WebComponent/icons/HeyThereEmojiIcon.d.ts +2 -0
  71. package/dist/src/WebComponent/icons/PhoneOutlineIcon.d.ts +2 -0
  72. package/dist/src/WebComponent/icons/SendMessageIcon.d.ts +3 -0
  73. package/dist/src/WebComponent/icons/TourSelfGuidedIcon.d.ts +2 -0
  74. package/dist/src/WebComponent/icons/TourVirtuallyIcon.d.ts +2 -0
  75. package/dist/src/WebComponent/icons/TourWithAgentIcon.d.ts +2 -0
  76. package/dist/src/WebComponent/icons/XOutlineIcon.d.ts +2 -0
  77. package/dist/src/WebComponent/index.d.ts +2 -0
  78. package/dist/src/WebComponent/launcher/Launcher.d.ts +99 -0
  79. package/dist/src/WebComponent/launcher/launcherStyles.d.ts +1 -0
  80. package/dist/src/WebComponent/launcher/mobile-launcher.d.ts +27 -0
  81. package/dist/src/WebComponent/launcher/typeEmojiStyles.d.ts +1 -0
  82. package/dist/src/WebComponent/launcher/typeMiniStyles.d.ts +1 -0
  83. package/dist/src/WebComponent/launcher/typeMobileStyles.d.ts +1 -0
  84. package/dist/src/WebComponent/leasing-chat-styles.d.ts +1 -0
  85. package/dist/src/WebComponent/loaders/index.d.ts +4 -0
  86. package/dist/src/WebComponent/loaders/mega-loader.d.ts +7 -0
  87. package/dist/src/WebComponent/loaders/skeleton-card.d.ts +12 -0
  88. package/dist/src/WebComponent/loaders/skeleton-loader-styles.d.ts +3 -0
  89. package/dist/src/WebComponent/loaders/skeleton-loader.d.ts +13 -0
  90. package/dist/src/WebComponent/me-chat.d.ts +91 -0
  91. package/dist/src/WebComponent/me-select.d.ts +24 -0
  92. package/dist/src/WebComponent/mini-loader.d.ts +5 -0
  93. package/dist/src/WebComponent/pubnub-chat-styles.d.ts +1 -0
  94. package/dist/src/WebComponent/pubnub-chat.d.ts +48 -0
  95. package/dist/src/WebComponent/pubnub-media.d.ts +14 -0
  96. package/dist/src/WebComponent/pubnub-message-styles.d.ts +1 -0
  97. package/dist/src/WebComponent/pubnub-message.d.ts +39 -0
  98. package/dist/src/WebComponent/simple-launcher/simple-launcher-styles.d.ts +1 -0
  99. package/dist/src/WebComponent/simple-launcher/simple-launcher.d.ts +12 -0
  100. package/dist/src/WebComponent/utilities-chat.d.ts +47 -0
  101. package/dist/src/WebComponent/utilities-styles.d.ts +1 -0
  102. package/dist/src/WebComponent/utils.d.ts +31 -0
  103. package/dist/src/analytics.d.ts +64 -0
  104. package/dist/src/disclaimers.d.ts +16 -0
  105. package/dist/src/fetchBuildingABTestType.d.ts +8 -0
  106. package/dist/src/fetchBuildingInfo.d.ts +58 -0
  107. package/dist/src/fetchBuildingWebchatView.d.ts +124 -0
  108. package/dist/src/fetchFeatureFlag.d.ts +14 -0
  109. package/dist/src/fetchLeadSources.d.ts +4 -0
  110. package/dist/src/fetchPhoneNumberFromSource.d.ts +6 -0
  111. package/dist/src/getAvailabilities.d.ts +46 -0
  112. package/dist/src/getBuildingPhoneNumber.d.ts +1 -0
  113. package/dist/src/getShouldAllowScheduling.d.ts +1 -0
  114. package/dist/src/getShouldShowWebchat.d.ts +3 -0
  115. package/dist/src/getTimezoneString.d.ts +1 -0
  116. package/dist/src/globals.d.ts +2 -0
  117. package/dist/src/gtm.d.ts +6 -0
  118. package/dist/src/handleChatId.d.ts +11 -0
  119. package/dist/src/insertDNIIntoWebsite.d.ts +5 -0
  120. package/dist/src/insertLeadSourceIntoSchedulerLinks.d.ts +4 -0
  121. package/dist/src/main/MEChat.d.ts +74 -0
  122. package/dist/src/main/utils.d.ts +2 -0
  123. package/dist/src/postLeadSources.d.ts +3 -0
  124. package/dist/src/rentgrata.d.ts +4 -0
  125. package/dist/src/replaceSelectButtonsWithNewLink.d.ts +5 -0
  126. package/dist/src/services/fees/calculateQuote.d.ts +14 -0
  127. package/dist/src/services/fees/fetchBuildingFeesV2.d.ts +10 -0
  128. package/dist/src/services/fees/fetchBuildingUnits.d.ts +29 -0
  129. package/dist/src/services/fees/utils.d.ts +1 -0
  130. package/dist/src/svgIcons.d.ts +5 -0
  131. package/dist/src/themes.d.ts +5 -0
  132. package/dist/src/types/incentive-v2.d.ts +23 -0
  133. package/dist/src/types/rest-sdk.types.d.ts +11 -0
  134. package/dist/src/types/webchat-no-show-reason.d.ts +1 -0
  135. package/dist/src/utils/getWidgetOverrides.d.ts +39 -0
  136. package/dist/src/utils/queryParamBuilder.d.ts +8 -0
  137. package/dist/src/utils.d.ts +13 -0
  138. package/package.json +1 -1
  139. package/public/dist/index.js +250 -164
  140. package/src/MyPubnub.ts +792 -0
  141. package/src/WebComponent/FeeCalculator/components/addons/addon-item-matrix-qty-selector/addon-item-matrix-qty-selector-styles.ts +39 -0
  142. package/src/WebComponent/FeeCalculator/components/addons/addon-item-matrix-qty-selector/addon-item-matrix-qty-selector.ts +206 -0
  143. package/src/WebComponent/FeeCalculator/components/addons/addon-item-qty-selector/addon-item-qty-selector-styles.ts +6 -0
  144. package/src/WebComponent/FeeCalculator/components/addons/addon-item-qty-selector/addon-item-qty-selector.ts +101 -0
  145. package/src/WebComponent/FeeCalculator/components/addons/common-addon-styles.ts +98 -0
  146. package/src/WebComponent/FeeCalculator/components/addons/rentable-item-qty-selector/rentable-item-qty-selector-styles.ts +9 -0
  147. package/src/WebComponent/FeeCalculator/components/addons/rentable-item-qty-selector/rentable-item-qty-selector.ts +136 -0
  148. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout-styles.ts +151 -0
  149. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.ts +261 -0
  150. package/src/WebComponent/FeeCalculator/components/fee-card/fee-card-styles.ts +65 -0
  151. package/src/WebComponent/FeeCalculator/components/fee-card/fee-card.ts +91 -0
  152. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item-styles.ts +44 -0
  153. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item.ts +38 -0
  154. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector-styles.ts +149 -0
  155. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts +247 -0
  156. package/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card-styles.ts +82 -0
  157. package/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card.ts +76 -0
  158. package/src/WebComponent/FeeCalculator/components/incentive-banner/incentive-banner-styles.ts +40 -0
  159. package/src/WebComponent/FeeCalculator/components/incentive-banner/incentive-banner.ts +43 -0
  160. package/src/WebComponent/FeeCalculator/components/incentive-banner/index.ts +1 -0
  161. package/src/WebComponent/FeeCalculator/components/index.ts +5 -0
  162. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card-styles.ts +39 -0
  163. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card.ts +39 -0
  164. package/src/WebComponent/FeeCalculator/constants.ts +5 -0
  165. package/src/WebComponent/FeeCalculator/fee-calculator-styles.ts +310 -0
  166. package/src/WebComponent/FeeCalculator/fee-calculator.ts +341 -0
  167. package/src/WebComponent/FeeCalculator/index.ts +4 -0
  168. package/src/WebComponent/FeeCalculator/model/building-fee-view.ts +84 -0
  169. package/src/WebComponent/FeeCalculator/model/building-fee.ts +126 -0
  170. package/src/WebComponent/FeeCalculator/model/desired-addon.ts +6 -0
  171. package/src/WebComponent/FeeCalculator/model/desired-rentable-item.ts +4 -0
  172. package/src/WebComponent/FeeCalculator/model/index.ts +13 -0
  173. package/src/WebComponent/FeeCalculator/model/item-combination.ts +16 -0
  174. package/src/WebComponent/FeeCalculator/model/item-quantity.ts +4 -0
  175. package/src/WebComponent/FeeCalculator/model/pricing-matrix.ts +45 -0
  176. package/src/WebComponent/FeeCalculator/model/pricing-rule.ts +9 -0
  177. package/src/WebComponent/FeeCalculator/model/rent-frequency.ts +1 -0
  178. package/src/WebComponent/FeeCalculator/model/rentable-item-summary.ts +34 -0
  179. package/src/WebComponent/FeeCalculator/model/rentable-item.ts +21 -0
  180. package/src/WebComponent/FeeCalculator/model/transaction-category.ts +23 -0
  181. package/src/WebComponent/FeeCalculator/model/unit-fee-bundle.ts +54 -0
  182. package/src/WebComponent/LeadSourceClient.ts +332 -0
  183. package/src/WebComponent/MEChat.css +5 -0
  184. package/src/WebComponent/OfficeHours.ts +73 -0
  185. package/src/WebComponent/Scheduler/date-picker.ts +405 -0
  186. package/src/WebComponent/Scheduler/time-picker.ts +195 -0
  187. package/src/WebComponent/Scheduler/tour-scheduler.ts +1430 -0
  188. package/src/WebComponent/Scheduler/tour-type-option.ts +112 -0
  189. package/src/WebComponent/Scheduler/tourSchedulerStyles.ts +418 -0
  190. package/src/WebComponent/actions/InputStyles.ts +57 -0
  191. package/src/WebComponent/actions/action-confirm-button.ts +125 -0
  192. package/src/WebComponent/actions/call-us-window.ts +467 -0
  193. package/src/WebComponent/actions/collapse-expand-button.ts +65 -0
  194. package/src/WebComponent/actions/details-window.ts +150 -0
  195. package/src/WebComponent/actions/email-us-window.ts +556 -0
  196. package/src/WebComponent/actions/formatPhoneNumber.ts +72 -0
  197. package/src/WebComponent/actions/minimize-expand-button.ts +93 -0
  198. package/src/WebComponent/chat-additional-actions.ts +135 -0
  199. package/src/WebComponent/health-chat.ts +270 -0
  200. package/src/WebComponent/healthchat-styles.ts +119 -0
  201. package/src/WebComponent/icons/ApplyOutlineIcon.ts +22 -0
  202. package/src/WebComponent/icons/BookTourOutlineIcon.ts +13 -0
  203. package/src/WebComponent/icons/CalculatorOutlineIcon.ts +80 -0
  204. package/src/WebComponent/icons/ChatOutlineIcon.ts +10 -0
  205. package/src/WebComponent/icons/ChevronLeftIcon.ts +7 -0
  206. package/src/WebComponent/icons/ChevronRightIcon.ts +7 -0
  207. package/src/WebComponent/icons/ContactResidentIcon.ts +9 -0
  208. package/src/WebComponent/icons/DollarOutlineIcon.ts +26 -0
  209. package/src/WebComponent/icons/EmailOutlineIcon.ts +7 -0
  210. package/src/WebComponent/icons/HeyThereEmojiIcon.ts +12 -0
  211. package/src/WebComponent/icons/PhoneOutlineIcon.ts +7 -0
  212. package/src/WebComponent/icons/SendMessageIcon.ts +17 -0
  213. package/src/WebComponent/icons/TourSelfGuidedIcon.ts +17 -0
  214. package/src/WebComponent/icons/TourVirtuallyIcon.ts +17 -0
  215. package/src/WebComponent/icons/TourWithAgentIcon.ts +17 -0
  216. package/src/WebComponent/icons/XOutlineIcon.ts +8 -0
  217. package/src/WebComponent/index.ts +2 -0
  218. package/src/WebComponent/launcher/Launcher.ts +1284 -0
  219. package/src/WebComponent/launcher/launcherStyles.ts +500 -0
  220. package/src/WebComponent/launcher/mobile-launcher.ts +162 -0
  221. package/src/WebComponent/launcher/typeEmojiStyles.ts +161 -0
  222. package/src/WebComponent/launcher/typeMiniStyles.ts +60 -0
  223. package/src/WebComponent/launcher/typeMobileStyles.ts +50 -0
  224. package/src/WebComponent/leasing-chat-styles.ts +114 -0
  225. package/src/WebComponent/loaders/index.ts +7 -0
  226. package/src/WebComponent/loaders/mega-loader.ts +36 -0
  227. package/src/WebComponent/loaders/skeleton-card.ts +31 -0
  228. package/src/WebComponent/loaders/skeleton-loader-styles.ts +112 -0
  229. package/src/WebComponent/loaders/skeleton-loader.ts +34 -0
  230. package/src/WebComponent/me-chat.ts +1264 -0
  231. package/src/WebComponent/me-select.ts +322 -0
  232. package/src/WebComponent/mini-loader.ts +28 -0
  233. package/src/WebComponent/pubnub-chat-styles.ts +204 -0
  234. package/src/WebComponent/pubnub-chat.ts +771 -0
  235. package/src/WebComponent/pubnub-media.ts +208 -0
  236. package/src/WebComponent/pubnub-message-styles.ts +54 -0
  237. package/src/WebComponent/pubnub-message.ts +431 -0
  238. package/src/WebComponent/simple-launcher/simple-launcher-styles.ts +34 -0
  239. package/src/WebComponent/simple-launcher/simple-launcher.ts +100 -0
  240. package/src/WebComponent/utilities-chat.ts +270 -0
  241. package/src/WebComponent/utilities-styles.ts +110 -0
  242. package/src/WebComponent/utils.ts +82 -0
  243. package/src/analytics.ts +217 -0
  244. package/src/assetUrls.ts +6 -0
  245. package/src/disclaimers.ts +301 -0
  246. package/src/fetchBuildingABTestType.ts +21 -0
  247. package/src/fetchBuildingInfo.ts +88 -0
  248. package/src/fetchBuildingWebchatView.ts +176 -0
  249. package/src/fetchFeatureFlag.ts +250 -0
  250. package/src/fetchLeadSources.ts +98 -0
  251. package/src/fetchPhoneNumberFromSource.ts +31 -0
  252. package/src/fetchWebchatPreferences.ts +54 -0
  253. package/src/getAvailabilities.ts +189 -0
  254. package/src/getBuildingPhoneNumber.ts +26 -0
  255. package/src/getShouldAllowScheduling.ts +16 -0
  256. package/src/getShouldShowWebchat.ts +114 -0
  257. package/src/getTimezoneString.ts +40 -0
  258. package/src/globals.ts +3 -0
  259. package/src/gtm.ts +17 -0
  260. package/src/handleChatId.ts +101 -0
  261. package/src/insertDNIIntoWebsite.ts +146 -0
  262. package/src/insertLeadSourceIntoSchedulerLinks.ts +71 -0
  263. package/src/main/MEChat.test.ts +110 -0
  264. package/src/main/MEChat.ts +404 -0
  265. package/src/main/utils.ts +70 -0
  266. package/src/postLeadSources.ts +44 -0
  267. package/src/rentgrata.ts +74 -0
  268. package/src/replaceSelectButtonsWithNewLink.ts +91 -0
  269. package/src/services/fees/calculateQuote.ts +74 -0
  270. package/src/services/fees/fetchBuildingFees.ts +50 -0
  271. package/src/services/fees/fetchBuildingFeesV2.ts +59 -0
  272. package/src/services/fees/fetchBuildingFloorplans.ts +74 -0
  273. package/src/services/fees/fetchBuildingUnits.ts +86 -0
  274. package/src/services/fees/utils.ts +4 -0
  275. package/src/svgIcons.ts +14 -0
  276. package/src/themes.ts +65 -0
  277. package/src/types/incentive-v2.ts +24 -0
  278. package/src/types/rest-sdk.types.ts +13 -0
  279. package/src/types/webchat-no-show-reason.ts +6 -0
  280. package/src/utils/getWidgetOverrides.ts +91 -0
  281. package/src/utils/queryParamBuilder.ts +28 -0
  282. package/src/utils.ts +121 -0
@@ -0,0 +1,1430 @@
1
+ import { html, LitElement, PropertyValueMap, TemplateResult } from "lit";
2
+ import { customElement, property, query, state } from "lit/decorators.js";
3
+ import {
4
+ shortcutKeyIsPressed,
5
+ formatToPhoneInput,
6
+ isPrintableCharacter,
7
+ } from "../actions/formatPhoneNumber";
8
+ import "./tour-type-option.ts";
9
+ import "./date-picker.ts";
10
+ import "./time-picker.ts";
11
+ import "../me-select.ts";
12
+ import {
13
+ DateWithTimeZoneOffset,
14
+ getAvailabilitiesGroupedByDay,
15
+ getExistenceOfAvailabilitiesByTourType,
16
+ getTimezoneForBuilding,
17
+ resetAvailabilitiesCache,
18
+ } from "../../getAvailabilities";
19
+ import { TourAvailabilityResponseRankOrderedSupportedTourTypesEnum } from "../../types/rest-sdk.types";
20
+ import { format } from "date-fns";
21
+ import { DatePicker } from "./date-picker";
22
+ import { MESelect } from "../me-select";
23
+ import { TimePicker } from "./time-picker";
24
+ import { LabeledOption } from "../../fetchBuildingInfo";
25
+ import { isMobile, isValidPhoneNumber } from "../../utils";
26
+ import axios, { AxiosError } from "axios";
27
+ import mapValues from "lodash/mapValues";
28
+ import classnames from "classnames";
29
+ import parseISO from "date-fns/parseISO";
30
+ import compareAsc from "date-fns/compareAsc";
31
+ import { InputStyles } from "../actions/InputStyles";
32
+ import { classMap } from "lit/directives/class-map.js";
33
+ import { FeatureFlagsShowDropdown } from "../../fetchFeatureFlag";
34
+ import { pushGtmEvent } from "../../gtm";
35
+ import formDisclaimer from "../../disclaimers";
36
+ import { tourSchedulerStyles } from "./tourSchedulerStyles";
37
+ import { defaultPrimaryColor, defaultBackgroundColor } from "../../themes";
38
+ import { LogType, sendLoggingEvent } from "../../analytics";
39
+ import LeadSourceClient, {
40
+ getDefaultLeadSourceAttribution,
41
+ } from "../LeadSourceClient";
42
+ import { getShouldAllowScheduling } from "../../getShouldAllowScheduling";
43
+ import { TourVirtuallyIcon } from "../icons/TourVirtuallyIcon";
44
+ import { TourSelfGuidedIcon } from "../icons/TourSelfGuidedIcon";
45
+ import { TourWithAgentIcon } from "../icons/TourWithAgentIcon";
46
+ import { shouldOpenTourLink } from "../../fetchBuildingWebchatView";
47
+ import formatInTimeZone from "date-fns-tz/formatInTimeZone";
48
+ import { getTimezoneAbbreviation } from "../../getTimezoneString";
49
+
50
+ @customElement("tour-scheduler")
51
+ export class TourScheduler extends LitElement {
52
+ @property({ attribute: false })
53
+ tourTypeOptions: LabeledOption[] = [];
54
+ @property({ attribute: true })
55
+ chatId = "";
56
+ @property({ type: Number })
57
+ buildingId = 0;
58
+ @property({ attribute: true })
59
+ buildingSlug = "";
60
+ @property({ attribute: true })
61
+ sgtUrl = "";
62
+ @property({ attribute: true })
63
+ selfGuidedToursTypeOffered = "";
64
+ @property({ attribute: true })
65
+ selfGuidedTourEnabled = false;
66
+ @property({ attribute: true })
67
+ escortedToursLink = "";
68
+ @property({ attribute: true })
69
+ virtualToursLink = "";
70
+ @property({ attribute: true })
71
+ virtualToursTypeOffered = "";
72
+ @property({ attribute: true })
73
+ orgSlug = "";
74
+ @property({ attribute: true })
75
+ hasDynamicSchedulingEnabled = false;
76
+
77
+ @property({ attribute: true })
78
+ private leadSourceClient: LeadSourceClient | null = null;
79
+
80
+ @property({ attribute: true })
81
+ buildingName = "";
82
+
83
+ onCloseClicked?: (e: MouseEvent) => void;
84
+
85
+ @state()
86
+ private tourType: TourType | null = null;
87
+ @state()
88
+ private shouldShowTourType = {
89
+ [TourType.Guided]: true,
90
+ [TourType.Self]: true,
91
+ [TourType.Virtual]: true,
92
+ };
93
+ @state()
94
+ private email = "";
95
+ @state()
96
+ private phoneNumber = "";
97
+ @state()
98
+ private availabilitiesGroupedByDay: {
99
+ [day: string]: DateWithTimeZoneOffset[];
100
+ } = {};
101
+ @state()
102
+ private calendarTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
103
+ @state()
104
+ private waitingForAvailabilities = true;
105
+ @state()
106
+ private selectedDate?: Date;
107
+ @state()
108
+ private selectedTime?: DateWithTimeZoneOffset | null;
109
+ @state()
110
+ private mobilePageIndex = 0;
111
+ @state()
112
+ private isSubmitting = false;
113
+ @state()
114
+ private tourIsBooked = false;
115
+ @state()
116
+ private shouldAllowScheduling = false;
117
+ @state()
118
+ private shouldAllowScheduleLoading = true;
119
+ @state()
120
+ private promptForReschedule = false;
121
+ @state()
122
+ private canceledReschedule = false;
123
+
124
+ @property({ attribute: false })
125
+ leadSources: string[] = [];
126
+
127
+ @property({ attribute: true })
128
+ orgLegalName = "";
129
+
130
+ @property({ attribute: true })
131
+ currentLeadSource = ""; // the default lead source based on referrer and query params.
132
+
133
+ @property({ attribute: true })
134
+ featureFlagShowDropdown = "";
135
+
136
+ @property({ attribute: true })
137
+ compactDesign = false;
138
+
139
+ @property({ attribute: true })
140
+ primaryColor: string = defaultPrimaryColor;
141
+
142
+ @property({ attribute: true })
143
+ backgroundColor: string = defaultBackgroundColor;
144
+
145
+ @property({ attribute: true })
146
+ foregroundColorOnPrimaryBackgroundColor = "white";
147
+
148
+ @property({ attribute: true })
149
+ foregroundColorOnSecondaryBackgroundColor = "black";
150
+
151
+ @query(".nameContainer#firstName input")
152
+ firstNameInput!: HTMLInputElement;
153
+ @query(".nameContainer#lastName input")
154
+ lastNameInput!: HTMLInputElement;
155
+ @query(".inputContainer#email input")
156
+ emailInput!: HTMLInputElement;
157
+ @query(".inputContainer#phone input")
158
+ phoneInput!: HTMLInputElement;
159
+ @query("me-select#leadSource")
160
+ selectedLeadSource!: MESelect;
161
+
162
+ @state()
163
+ firstNameInputValue = "";
164
+ @state()
165
+ lastNameInputValue = "";
166
+ @state()
167
+ leadSourceInputValue = "";
168
+
169
+ @state()
170
+ errorGettingAvailabilities = false;
171
+
172
+ _setAvailabilities = async (): Promise<void> => {
173
+ try {
174
+ const [allowScheduling, availabilitiesExistForTourType] =
175
+ await Promise.all([
176
+ getShouldAllowScheduling(
177
+ this.buildingId,
178
+ this.hasDynamicSchedulingEnabled
179
+ ),
180
+ getExistenceOfAvailabilitiesByTourType(),
181
+ ]);
182
+
183
+ this.shouldAllowScheduling = allowScheduling;
184
+ this.shouldAllowScheduleLoading = false;
185
+ this.calendarTimeZone = await getTimezoneForBuilding(this.buildingId);
186
+
187
+ sendLoggingEvent({
188
+ logTitle: "AVAILABILITIES_EXIST_FOR_TOUR_TYPE",
189
+ logData: availabilitiesExistForTourType,
190
+ logType: LogType.info,
191
+ buildingSlug: this.buildingSlug,
192
+ orgSlug: this.orgSlug,
193
+ });
194
+ const allValuesAreFalse = Object.values(
195
+ availabilitiesExistForTourType
196
+ ).every((v) => v === false);
197
+ const hasNoTourLinks =
198
+ !this.sgtUrl && !this.escortedToursLink && !this.virtualToursLink;
199
+ if (allValuesAreFalse && hasNoTourLinks) {
200
+ this.errorGettingAvailabilities = true;
201
+ this.waitingForAvailabilities = false;
202
+ sendLoggingEvent({
203
+ logTitle: "NO_AVAILABILITIES_EXIST",
204
+ logData: null,
205
+ logType: LogType.warn,
206
+ buildingSlug: this.buildingSlug,
207
+ orgSlug: this.orgSlug,
208
+ });
209
+ return;
210
+ }
211
+ this.shouldShowTourType = {
212
+ [TourType.Guided]:
213
+ this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT") &&
214
+ (availabilitiesExistForTourType[TourType.Guided] ||
215
+ !!this.escortedToursLink),
216
+ [TourType.Self]:
217
+ this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED") &&
218
+ (availabilitiesExistForTourType[TourType.Self] || !!this.sgtUrl),
219
+ [TourType.Virtual]:
220
+ this.tourTypeOptions
221
+ .map((o) => o.value)
222
+ .includes("VIRTUAL_SHOWING") &&
223
+ (availabilitiesExistForTourType[TourType.Virtual] ||
224
+ !!this.virtualToursLink),
225
+ };
226
+
227
+ // if there is only a single option for tour type, have that be the default
228
+ const hasOneShowTourType =
229
+ Object.values(this.shouldShowTourType).reduce((acc, cur) => {
230
+ if (cur === true) {
231
+ return acc + 1;
232
+ } else {
233
+ return acc;
234
+ }
235
+ }, 0) === 1;
236
+
237
+ if (hasOneShowTourType) {
238
+ this.tourType = Object.keys(this.shouldShowTourType).find(
239
+ (key) => this.shouldShowTourType[key as TourType]
240
+ ) as TourType;
241
+ } else {
242
+ // if we have multiple tour types, then check if there is a default that the client wants selected
243
+ // HACK: FOR DEMO, FOR https://www.sofioceanhills.com/ -
244
+ // THIS SHOULD BE ADDED TO WEBCHAT CONFIGURATION
245
+ // if (
246
+ // this.buildingSlug === "1ac49f90-6150-11ed-b327-1b3f05e7b9db" &&
247
+ // this.shouldShowTourType[TourType.Self]
248
+ // ) {
249
+ // this.tourType = TourType.Self;
250
+ // }
251
+ }
252
+
253
+ if (this.tourType === null) {
254
+ this.waitingForAvailabilities = false;
255
+ return;
256
+ }
257
+ this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
258
+ tourTypeMap[this.tourType]
259
+ );
260
+ this.waitingForAvailabilities = false;
261
+ } catch (e) {
262
+ this.errorGettingAvailabilities = true;
263
+ this.waitingForAvailabilities = false;
264
+ sendLoggingEvent({
265
+ logTitle: "ERROR_LOADING_AVAILABILITIES",
266
+ logData: { error: e },
267
+ logType: LogType.error,
268
+ buildingSlug: this.buildingSlug,
269
+ orgSlug: this.orgSlug,
270
+ });
271
+ }
272
+ };
273
+
274
+ firstUpdated = async (): Promise<void> => {
275
+ await this._setAvailabilities();
276
+ };
277
+
278
+ protected willUpdate = async (
279
+ _changedProperties:
280
+ | PropertyValueMap<{ tourType: TourType }>
281
+ | Map<PropertyKey, unknown>
282
+ ): Promise<void> => {
283
+ if (_changedProperties.has("tourType") && this.tourType) {
284
+ this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
285
+ tourTypeMap[this.tourType]
286
+ );
287
+ }
288
+ };
289
+
290
+ handlePhoneKeydown = (e: Event): void => {
291
+ // these should always be true, this is just here to mollify TypeScript
292
+ if (
293
+ !(e instanceof KeyboardEvent) ||
294
+ !(e.target instanceof HTMLInputElement) ||
295
+ e.target.selectionStart === null
296
+ // !e.target.selectionStart
297
+ )
298
+ return;
299
+
300
+ const cursorPosition = e.target.selectionStart;
301
+
302
+ if (isPrintableCharacter(e) && !shortcutKeyIsPressed(e)) {
303
+ // If e.key is a character, and no modifier key is pressed, insert it at the cursor, filter out non-numbers, and auto-format
304
+ e.preventDefault();
305
+ e.stopPropagation();
306
+ const updated =
307
+ this.phoneNumber.slice(0, cursorPosition) +
308
+ e.key +
309
+ this.phoneNumber.slice(cursorPosition);
310
+ this.phoneNumber = formatToPhoneInput(updated.replace(/\D/g, ""));
311
+ this.phoneInput.value = this.phoneNumber;
312
+ } else if (e.key === "Backspace") {
313
+ /*
314
+ Handling backspace:
315
+ - A single backspace should delete the last digit before the cursor, not just a punctuation character; the user shouldn't interact directly with the punctuation
316
+ - Let the OS handle backspace combos like `Alt + Backspace`, then re-autoformat if necessary (in keyup)
317
+ - If the user wants to select and backspace a range of text, let them, then auto-format the remainder
318
+ */
319
+
320
+ // backspace combos
321
+ if (shortcutKeyIsPressed(e)) {
322
+ return;
323
+ }
324
+
325
+ // backspace selection
326
+ if (
327
+ this.phoneInput.selectionEnd &&
328
+ this.phoneInput.selectionStart &&
329
+ this.phoneInput.selectionEnd - this.phoneInput.selectionStart > 0
330
+ ) {
331
+ return;
332
+ }
333
+
334
+ // regular backspace
335
+ const originalCharacterCount = this.phoneNumber.length;
336
+ const digitsBeforeCursor = this.phoneNumber
337
+ .slice(0, cursorPosition)
338
+ .replace(/\D/g, "");
339
+ const digitsAfterCursor = this.phoneNumber
340
+ .slice(cursorPosition)
341
+ .replace(/\D/g, "");
342
+ const updatedDigits = `${digitsBeforeCursor.slice(
343
+ 0,
344
+ -1
345
+ )}${digitsAfterCursor}`;
346
+ this.phoneNumber = formatToPhoneInput(updatedDigits);
347
+ this.phoneInput.value = this.phoneNumber;
348
+ const numOfCharactersDeleted =
349
+ originalCharacterCount - this.phoneNumber.length;
350
+ const newCursorPosition = cursorPosition - numOfCharactersDeleted;
351
+ this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
352
+ e.preventDefault();
353
+ e.stopPropagation();
354
+ return;
355
+ } else if (
356
+ ["ArrowLeft", "ArrowRight"].includes(e.key) &&
357
+ !shortcutKeyIsPressed(e) &&
358
+ !e.shiftKey
359
+ ) {
360
+ // when navigating with arrow keys, skip punctuation
361
+ if (e.key === "ArrowLeft") {
362
+ const charactersBeforeCursor = this.phoneNumber.slice(
363
+ 0,
364
+ cursorPosition
365
+ );
366
+ const numberOfNonDigitsBeforeCursor =
367
+ charactersBeforeCursor.split(/\d+/).at(-1)?.length || 0;
368
+ const moveLeftBy = numberOfNonDigitsBeforeCursor + 1;
369
+ const newCursorPosition =
370
+ cursorPosition - moveLeftBy > -1 ? cursorPosition - moveLeftBy : 0;
371
+ this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
372
+ }
373
+ if (e.key === "ArrowRight") {
374
+ const charactersAfterCursor = this.phoneNumber.slice(cursorPosition);
375
+ const numberOfNonDigitsAfterCursor =
376
+ charactersAfterCursor.split(/\d+/)[0].length || 0;
377
+ const moveRightBy = numberOfNonDigitsAfterCursor + 1;
378
+ const newCursorPosition =
379
+ cursorPosition + moveRightBy < this.phoneNumber.length
380
+ ? cursorPosition + moveRightBy
381
+ : this.phoneNumber.length;
382
+ this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
383
+ }
384
+ e.preventDefault();
385
+ e.stopPropagation();
386
+ } else {
387
+ // Let browser/OS handle anything else. We'll handle any changes to the phone input in the `keyup` handler.
388
+ // Could be a keyboard shortcut that modifies the input (like `Cmd/Ctrl + V`, which we'll handle in `keyup`),
389
+ // or a keyboard shortcut that doesn't (like `Cmd + L` to jump to URL bar or `Cmd + R` to reload the page),
390
+ // or Tab, an arrow key, etc.
391
+ return;
392
+ }
393
+ };
394
+
395
+ handlePhoneKeyup = (e: KeyboardEvent): void => {
396
+ if (!e.key) {
397
+ return;
398
+ }
399
+ // After formatting, place the cursor where it was before, defined as "before the digit that followed it before formatting, if any, otherwise at the end".
400
+ // (We never want the cursor to be before a punctuation mark because the next digit typed will appear after the punctuation mark, not before.)
401
+ // If we don't do this, the cursor automatically goes to the end when we set `this.phoneNumber`.
402
+ // This is sometimes undesired: for example, if we've ended up here because a Mac user typed `Alt + Backspace` in the middle.
403
+
404
+ // Arrow keys are intended to change the cursor position, so don't get in their way
405
+ if (
406
+ e.key.includes("Arrow") ||
407
+ ["Meta", "Shift", "Control", "Alt"].includes(e.key)
408
+ ) {
409
+ return;
410
+ }
411
+
412
+ const cursorPosition = this.phoneInput.selectionStart;
413
+ // find the numbers it's before and count backward from end after formatting
414
+ const numbersAfterCursor = cursorPosition
415
+ ? this.phoneInput.value.slice(cursorPosition).replace(/\D/g, "")
416
+ : "";
417
+ this.phoneNumber = formatToPhoneInput(this.phoneInput.value);
418
+
419
+ // EXAMPLES: (123)| 4 numbersAfterCursor will be '4'.
420
+ let cursorNegativeIndex = 0;
421
+ let numbersLeft = numbersAfterCursor.length;
422
+ while (numbersLeft) {
423
+ if (this.phoneNumber.at(cursorNegativeIndex)?.match(/\d/)) {
424
+ numbersLeft--;
425
+ }
426
+ cursorNegativeIndex++;
427
+ }
428
+ const cursorPositiveIndex =
429
+ this.phoneInput.value.length - cursorNegativeIndex + 1;
430
+ this.phoneInput.setSelectionRange(cursorPositiveIndex, cursorPositiveIndex);
431
+ };
432
+
433
+ onChangeEmail = (e: Event): void => {
434
+ if (!e.target) {
435
+ return;
436
+ }
437
+
438
+ this.email = (e.target as HTMLInputElement).value;
439
+ };
440
+
441
+ validators = {
442
+ tourType: (): boolean => this.tourType !== null,
443
+ dateAndTime: (): boolean => !!this.selectedDate && !!this.selectedTime,
444
+ leadInfo: (): boolean => {
445
+ return (
446
+ (!!this.firstNameInput?.value || !!this.lastNameInput?.value) &&
447
+ this.emailInput?.value.includes("@") &&
448
+ // TODO: deleting phone number doesn't cause validation to fail, at least on mobile
449
+ !!this.phoneNumber &&
450
+ this.phoneNumber.length === 14 &&
451
+ isValidPhoneNumber(this.phoneNumber)
452
+ );
453
+ },
454
+ };
455
+
456
+ formIsValidForSubmission = (): boolean => {
457
+ const isValid =
458
+ this.validators.tourType() &&
459
+ this.validators.dateAndTime() &&
460
+ this.validators.leadInfo();
461
+ return isValid;
462
+ };
463
+
464
+ /** E.g., `timeStringToHoursAndMinutes("4:15pm")` -> `[16, 15]`
465
+ */
466
+ timeStringToHoursAndMinutes = (
467
+ timeString: string
468
+ ): [hours: number, minutes: number] => {
469
+ const [hoursString, minutesString] = timeString.split(/\D/g);
470
+ const hours =
471
+ parseInt(hoursString) +
472
+ (timeString.toLowerCase().includes("pm") ? 12 : 0);
473
+ const minutes = parseInt(minutesString);
474
+
475
+ return [hours, minutes];
476
+ };
477
+
478
+ submit = async (): Promise<void> => {
479
+ if (!this.selectedDate || !this.selectedTime || this.tourType === null) {
480
+ return;
481
+ }
482
+ const queryParams = new URLSearchParams(window.location.search);
483
+
484
+ let parsedLeadSource = null;
485
+ if (
486
+ this.selectedLeadSource &&
487
+ this.selectedLeadSource.value &&
488
+ this.selectedLeadSource.value.length > 0
489
+ ) {
490
+ parsedLeadSource = this.selectedLeadSource.value;
491
+ } else if (
492
+ this.leadSourceInputValue &&
493
+ this.leadSourceInputValue.length > 0
494
+ ) {
495
+ parsedLeadSource = this.leadSourceInputValue;
496
+ } else {
497
+ parsedLeadSource = this.currentLeadSource;
498
+ }
499
+
500
+ const leadSources = [
501
+ ...new Set(
502
+ parsedLeadSource
503
+ ? [parsedLeadSource, "property-website"]
504
+ : ["property-website"]
505
+ ),
506
+ ];
507
+ if (this.firstNameInput) {
508
+ this.firstNameInputValue = this.firstNameInput.value;
509
+ }
510
+ if (this.lastNameInput) {
511
+ this.lastNameInputValue = this.lastNameInput.value;
512
+ }
513
+ this.leadSourceInputValue = parsedLeadSource;
514
+ pushGtmEvent("scheduleTourSubmitted", {
515
+ email: this.email,
516
+ phone: `+1${this.phoneNumber.match(/\d/g)?.join("")}`,
517
+ firstName: this.firstNameInput?.value ?? this.firstNameInputValue,
518
+ lastName: this.lastNameInput?.value ?? this.lastNameInputValue,
519
+ tourType: tourTypeForSubmission[this.tourType],
520
+ tourTime: `${this.selectedTime.datetime}${this.selectedTime.offset}`,
521
+ originatingSource:
522
+ leadSources.find((i) => i !== "property-website") || null,
523
+ });
524
+ const data = {
525
+ referrer: document.referrer,
526
+ email_address: this.email,
527
+ phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
528
+ building_id: this.buildingId,
529
+ first_name: this.firstNameInput?.value ?? this.firstNameInputValue,
530
+ last_name: this.lastNameInput?.value ?? this.lastNameInputValue,
531
+ tour_type: tourTypeForSubmission[this.tourType],
532
+ tour_time: `${this.selectedTime.datetime}${this.selectedTime.offset}`, // e.g., "2022-06-27T09:00:00-07:00"
533
+ cancel_existing_tours: this.promptForReschedule,
534
+ lead_sources: [
535
+ ...new Set(
536
+ parsedLeadSource
537
+ ? [parsedLeadSource, getDefaultLeadSourceAttribution(this.orgSlug)]
538
+ : [getDefaultLeadSourceAttribution(this.orgSlug)]
539
+ ),
540
+ ],
541
+ query_params: Object.fromEntries(queryParams.entries()),
542
+ conversation_tracking_id: this.leadSourceClient?.chatId,
543
+ };
544
+
545
+ const url = `https://app.meetelise.com/platformApi/state/create/v2/scheduleMe`;
546
+ this.isSubmitting = true;
547
+ try {
548
+ await axios.post(url, data, {
549
+ headers: {
550
+ ["building-slug"]: this.buildingSlug,
551
+ ["X-SecurityKey"]: "JRL8jV4VcSCwOSir5gWkpgNLfKghmhBG",
552
+ ["org-slug"]: this.orgSlug,
553
+ },
554
+ });
555
+
556
+ this.leadSourceClient?.checkAndHandleForLogLeadSource({
557
+ webchatAction: "tour-scheduler",
558
+ stateId: null,
559
+ });
560
+ this.isSubmitting = false;
561
+ this.tourIsBooked = true;
562
+ } catch (e: unknown) {
563
+ const typedError = e as AxiosError<{ detail: string }>;
564
+ const message =
565
+ typedError.response?.data?.["detail"] || "Failed to book tour";
566
+ if (
567
+ typedError.response &&
568
+ typedError.response.headers &&
569
+ typedError.response.headers[
570
+ "funnel-prospect-conflicting-appointment-error"
571
+ ]
572
+ ) {
573
+ if (this.selectedDate && this.selectedTime) {
574
+ const dateAvailabilities =
575
+ this.availabilitiesGroupedByDay[
576
+ format(this.selectedDate, "yyyy-MM-dd")
577
+ ];
578
+ if (dateAvailabilities) {
579
+ this.availabilitiesGroupedByDay[
580
+ format(this.selectedDate, "yyyy-MM-dd")
581
+ ] = dateAvailabilities.filter(
582
+ (availability) => availability !== this.selectedTime
583
+ );
584
+ }
585
+ }
586
+ } else if (
587
+ typedError.response?.status === 409 &&
588
+ !this.promptForReschedule &&
589
+ message === "User already has a tour scheduled for this building"
590
+ ) {
591
+ this.promptForReschedule = true;
592
+ this.isSubmitting = false;
593
+ this.tourIsBooked = false;
594
+ return;
595
+ } else if (
596
+ typedError.response?.status === 409 &&
597
+ !this.promptForReschedule
598
+ ) {
599
+ this.isSubmitting = false;
600
+ this.tourIsBooked = false;
601
+ resetAvailabilitiesCache();
602
+ this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
603
+ tourTypeMap[this.tourType],
604
+ this.buildingId
605
+ );
606
+ alert(
607
+ "This timeslot is no longer available. Please select a different time."
608
+ );
609
+ return;
610
+ }
611
+ alert(message);
612
+ this.isSubmitting = false;
613
+ this.tourIsBooked = false;
614
+ }
615
+ };
616
+
617
+ static styles = [tourSchedulerStyles, InputStyles];
618
+
619
+ tourTypeMenu(): TemplateResult {
620
+ return html`<h2 class="journey-header">Tour Type</h2>
621
+ <div id="tour-type-menu">
622
+ ${this.shouldShowTourType[TourType.Self]
623
+ ? html` <tour-type-option
624
+ heading="Take a tour"
625
+ subtitle="on your own"
626
+ .primaryColor=${this.primaryColor}
627
+ .backgroundColor=${this.backgroundColor}
628
+ @click="${async () => {
629
+ this.tourType = TourType.Self;
630
+ if (
631
+ this.sgtUrl &&
632
+ shouldOpenTourLink(
633
+ TourType.Self,
634
+ this.selfGuidedToursTypeOffered
635
+ )
636
+ ) {
637
+ window.open(this.sgtUrl, "_blank");
638
+ }
639
+ this.availabilitiesGroupedByDay =
640
+ await getAvailabilitiesGroupedByDay(
641
+ tourTypeMap[TourType.Self]
642
+ );
643
+ }}"
644
+ @keydown="${(e: KeyboardEvent) => {
645
+ if ([" ", "Enter"].includes(e.key)) {
646
+ e.preventDefault();
647
+ this.tourType = TourType.Self;
648
+ }
649
+ }}"
650
+ ?selected="${this.tourType === TourType.Self}"
651
+ >
652
+ ${TourSelfGuidedIcon(
653
+ this.tourType === TourType.Self
654
+ ? this.foregroundColorOnPrimaryBackgroundColor
655
+ : this.foregroundColorOnSecondaryBackgroundColor
656
+ )}
657
+ </tour-type-option>`
658
+ : ""}
659
+ ${this.shouldShowTourType[TourType.Guided]
660
+ ? html` <tour-type-option
661
+ heading="Guided tour"
662
+ subtitle="with an agent"
663
+ .primaryColor=${this.primaryColor}
664
+ .backgroundColor=${this.backgroundColor}
665
+ .foregroundColorOnPrimaryBackgroundColor=${this
666
+ .foregroundColorOnPrimaryBackgroundColor}
667
+ .foregroundColorOnSecondaryBackgroundColor=${this
668
+ .foregroundColorOnSecondaryBackgroundColor}
669
+ @click="${async () => {
670
+ this.tourType = TourType.Guided;
671
+
672
+ if (this.escortedToursLink) {
673
+ window.open(this.escortedToursLink, "_blank");
674
+ }
675
+ this.availabilitiesGroupedByDay =
676
+ await getAvailabilitiesGroupedByDay(
677
+ tourTypeMap[TourType.Guided]
678
+ );
679
+ }}"
680
+ @keydown="${(e: KeyboardEvent) => {
681
+ if ([" ", "Enter"].includes(e.key)) {
682
+ e.preventDefault();
683
+ this.tourType = TourType.Guided;
684
+ }
685
+ }}"
686
+ ?selected="${this.tourType === TourType.Guided}"
687
+ >
688
+ ${TourWithAgentIcon(
689
+ this.tourType === TourType.Guided
690
+ ? this.foregroundColorOnPrimaryBackgroundColor
691
+ : this.foregroundColorOnSecondaryBackgroundColor
692
+ )}
693
+ </tour-type-option>`
694
+ : ""}
695
+ ${this.shouldShowTourType[TourType.Virtual]
696
+ ? html` <tour-type-option
697
+ heading="Virtual tour"
698
+ subtitle="over video"
699
+ .primaryColor=${this.primaryColor}
700
+ .backgroundColor=${this.backgroundColor}
701
+ @click="${async () => {
702
+ this.tourType = TourType.Virtual;
703
+ if (
704
+ this.virtualToursLink &&
705
+ shouldOpenTourLink(
706
+ TourType.Virtual,
707
+ this.virtualToursTypeOffered
708
+ )
709
+ ) {
710
+ window.open(this.virtualToursLink, "_blank");
711
+ }
712
+ this.availabilitiesGroupedByDay =
713
+ await getAvailabilitiesGroupedByDay(
714
+ tourTypeMap[TourType.Virtual]
715
+ );
716
+ }}"
717
+ @keydown="${(e: KeyboardEvent) => {
718
+ if ([" ", "Enter"].includes(e.key)) {
719
+ e.preventDefault();
720
+ this.tourType = TourType.Virtual;
721
+ }
722
+ }}"
723
+ ?selected="${this.tourType === TourType.Virtual}"
724
+ >
725
+ ${TourVirtuallyIcon(
726
+ this.tourType === TourType.Virtual
727
+ ? this.foregroundColorOnPrimaryBackgroundColor
728
+ : this.foregroundColorOnSecondaryBackgroundColor
729
+ )}
730
+ </tour-type-option>`
731
+ : ""}
732
+ </div>`;
733
+ }
734
+
735
+ dateAndTimeMenu(): TemplateResult {
736
+ if (
737
+ this.tourType === TourType.Self &&
738
+ this.sgtUrl &&
739
+ shouldOpenTourLink(this.tourType, this.selfGuidedToursTypeOffered)
740
+ ) {
741
+ return html`<h2 class="journey-header">Date and Time</h2>
742
+ <div id="dateAndTimeMenu">
743
+ <button
744
+ id="self-guided-redirect-bttn"
745
+ @click=${() => window.open(this.sgtUrl, "_blank")}
746
+ >
747
+ View Self Guided Tour Times
748
+ </button>
749
+ </div>`;
750
+ }
751
+ return html`<h2 class="journey-header">Date and Time</h2>
752
+ <div id="dateAndTimeMenu">
753
+ <div id="datePicker">
754
+ <date-picker
755
+ .availabilities=${mapValues(
756
+ this.availabilitiesGroupedByDay,
757
+ (dates) => dates.map((date) => new Date(date.offset))
758
+ )}
759
+ @change=${(e: Event) => {
760
+ // if the user clicked a tour type that is suppose to redirect, we redirect that use when they select a date
761
+ // This can happen if the user clicks, is redirect, and then comes back to the webchat
762
+ if (
763
+ this.tourType === TourType.Self &&
764
+ this.sgtUrl &&
765
+ shouldOpenTourLink(
766
+ this.tourType,
767
+ this.selfGuidedToursTypeOffered
768
+ )
769
+ ) {
770
+ window.open(this.sgtUrl, "_blank");
771
+ return;
772
+ }
773
+ if (this.tourType === TourType.Guided && this.escortedToursLink) {
774
+ window.open(this.escortedToursLink, "_blank");
775
+ return;
776
+ }
777
+ if (
778
+ this.tourType === TourType.Virtual &&
779
+ this.virtualToursLink &&
780
+ shouldOpenTourLink(this.tourType, this.virtualToursTypeOffered)
781
+ ) {
782
+ window.open(this.virtualToursLink, "_blank");
783
+ return;
784
+ }
785
+
786
+ if (e.target instanceof DatePicker) {
787
+ this.selectedDate = e.target.selectedDate;
788
+ }
789
+ }}
790
+ ></date-picker>
791
+ <div>
792
+ <time-picker
793
+ ?selecteddateexists=${!!this.selectedDate}
794
+ .timezone=${this.calendarTimeZone}
795
+ .options=${this.selectedDate
796
+ ? this.availabilitiesGroupedByDay[
797
+ format(this.selectedDate, "y-MM-dd")
798
+ ]
799
+ ?.sort((a, b) =>
800
+ compareAsc(parseISO(a.datetime), parseISO(b.datetime))
801
+ )
802
+ .map((date) => {
803
+ return {
804
+ dateWithTimeZoneOffset: date,
805
+ displayTime: format(
806
+ parseISO(`${date.datetime}${date.offset}`),
807
+ "h:mmaaa"
808
+ ),
809
+ displayTimeZoned: formatInTimeZone(
810
+ parseISO(`${date.datetime}${date.offset}`),
811
+ this.calendarTimeZone,
812
+ "h:mmaaa"
813
+ ),
814
+ };
815
+ })
816
+ : []}
817
+ @change=${(e: Event) => {
818
+ if (e.target instanceof TimePicker) {
819
+ if (!this.selectedDate) return;
820
+ const daysAvailabilities =
821
+ this.availabilitiesGroupedByDay[
822
+ format(new Date(this.selectedDate), "y-MM-dd")
823
+ ];
824
+ const index = e.target.selectedTime
825
+ ? daysAvailabilities.indexOf(
826
+ e.target.selectedTime.dateWithTimeZoneOffset
827
+ )
828
+ : null;
829
+ this.selectedTime =
830
+ index !== null ? daysAvailabilities[index] : null;
831
+ }
832
+ }}
833
+ ></time-picker>
834
+ </div>
835
+ </div>
836
+ </div>`;
837
+ }
838
+
839
+ closeButton(): TemplateResult {
840
+ return html` <button
841
+ id="close-button"
842
+ @click=${this.onCloseClicked}
843
+ aria-label="Close"
844
+ aria-describedby="close-button"
845
+ >
846
+ <svg
847
+ width="19"
848
+ height="19"
849
+ viewBox="0 0 19 19"
850
+ fill="none"
851
+ xmlns="http://www.w3.org/2000/svg"
852
+ >
853
+ <line
854
+ x1="0.986863"
855
+ y1="18.2746"
856
+ x2="18.2929"
857
+ y2="0.968593"
858
+ stroke="#202020"
859
+ stroke-width="2"
860
+ />
861
+ <path
862
+ d="M1.01394 0.999997L18.0103 18.0243"
863
+ stroke="#202020"
864
+ stroke-width="2"
865
+ />
866
+ </svg>
867
+ </button>`;
868
+ }
869
+
870
+ mobilePages = [
871
+ {
872
+ validate: this.validators.tourType,
873
+ nextButtonText: "Next",
874
+ nextButtonAction: (): void => {
875
+ this.mobilePageIndex++;
876
+ },
877
+ render: (): TemplateResult => {
878
+ return html`${this.tourTypeMenu()}`;
879
+ },
880
+ },
881
+ {
882
+ validate: this.validators.dateAndTime,
883
+ nextButtonText: "Next",
884
+ nextButtonAction: (): void => {
885
+ this.mobilePageIndex++;
886
+ },
887
+ render: (): TemplateResult => {
888
+ return html`${this.dateAndTimeMenu()}`;
889
+ },
890
+ },
891
+ {
892
+ validate: (): boolean => this.validators.leadInfo(),
893
+ // last page gets <action-confirm-button> instead of the regular button
894
+ nextButtonText: null,
895
+ renderNextButton: (): TemplateResult => html` <action-confirm-button
896
+ id="schedule-bttn"
897
+ .onClick=${this.submit}
898
+ .isLoading=${this.isSubmitting}
899
+ height="50px"
900
+ width="145px"
901
+ text="Schedule tour"
902
+ ?disabled=${!this.formIsValidForSubmission()}
903
+ ></action-confirm-button>`,
904
+ nextButtonAction: null,
905
+ render: (): TemplateResult => {
906
+ return html`${this.userInfoAndLayoutMenu()}`;
907
+ },
908
+ },
909
+ ];
910
+
911
+ userInfoAndLayoutMenu(): TemplateResult {
912
+ return html`<h2 class="journey-header">Your Information</h2>
913
+ <div id="yourInformationMenu">
914
+ <div id="namesWrapper">
915
+ <div class="nameContainer" id="firstName">
916
+ <input
917
+ class=${classMap({
918
+ ["webchat-input"]: true,
919
+ ["nameInput"]: true,
920
+ ["webchat-font__desktop"]: !isMobile(),
921
+ ["webchat-font__mobile"]: isMobile(),
922
+ })}
923
+ type="text"
924
+ placeholder="First name"
925
+ name="firstName"
926
+ autocomplete="given-name"
927
+ @input=${() => this.requestUpdate()}
928
+ />
929
+ </div>
930
+ <div class="nameContainer" id="lastName">
931
+ <input
932
+ class=${classMap({
933
+ ["webchat-input"]: true,
934
+ ["nameInput"]: true,
935
+ ["webchat-font__desktop"]: !isMobile(),
936
+ ["webchat-font__mobile"]: isMobile(),
937
+ })}
938
+ type="text"
939
+ placeholder="Last name"
940
+ name="lastName"
941
+ autocomplete="family-name"
942
+ @input=${() => this.requestUpdate()}
943
+ />
944
+ </div>
945
+ </div>
946
+
947
+ <div class="inputContainer" id="email">
948
+ <input
949
+ class=${classMap({
950
+ ["webchat-input"]: true,
951
+ ["webchat-font__desktop"]: !isMobile(),
952
+ ["webchat-font__mobile"]: isMobile(),
953
+ })}
954
+ type="email"
955
+ inputmode="email"
956
+ placeholder="Email"
957
+ name="email"
958
+ autocomplete="email"
959
+ .value=${this.email}
960
+ @input=${this.onChangeEmail}
961
+ />
962
+ </div>
963
+ <div class="inputContainer" id="phone">
964
+ <input
965
+ class=${classMap({
966
+ ["webchat-input"]: true,
967
+ ["webchat-font__desktop"]: !isMobile(),
968
+ ["webchat-font__mobile"]: isMobile(),
969
+ ["webchat-input__error"]:
970
+ this.phoneNumber.length === 14 &&
971
+ !isValidPhoneNumber(this.phoneNumber),
972
+ })}
973
+ type="tel"
974
+ inputmode="tel"
975
+ placeholder="Phone"
976
+ name="phone"
977
+ autocomplete="tel-national"
978
+ maxlength="14"
979
+ .value=${this.phoneNumber}
980
+ @keydown=${this.handlePhoneKeydown}
981
+ @keyup=${this.handlePhoneKeyup}
982
+ @input=${(e: Event) => {
983
+ if (!e.target) {
984
+ return;
985
+ }
986
+ this.phoneNumber = formatToPhoneInput(
987
+ (e.target as HTMLInputElement).value
988
+ );
989
+ }}
990
+ />
991
+ ${this.phoneNumber.length === 14 &&
992
+ !isValidPhoneNumber(this.phoneNumber)
993
+ ? html`<p class="error-message">Invalid phone number</p>`
994
+ : ""}
995
+ </div>
996
+ ${this.leadSources.length > 0 &&
997
+ (this.featureFlagShowDropdown === FeatureFlagsShowDropdown.always ||
998
+ (this.featureFlagShowDropdown ===
999
+ FeatureFlagsShowDropdown.onAttributionFailure &&
1000
+ this.currentLeadSource.length === 0))
1001
+ ? html` <me-select
1002
+ id="leadSource"
1003
+ value="${this.currentLeadSource}"
1004
+ placeholder="How did you hear about us?"
1005
+ .options="${this.leadSources.map((i) => ({
1006
+ label: i,
1007
+ value: i,
1008
+ }))}"
1009
+ @change=${() => {
1010
+ this.requestUpdate();
1011
+ }}
1012
+ >
1013
+ </me-select>`
1014
+ : ""}
1015
+ </div> `;
1016
+ }
1017
+
1018
+ confirmationMessage(): TemplateResult {
1019
+ if (!this.selectedDate || !this.selectedTime || this.tourType === null)
1020
+ return html``;
1021
+ // format example: "November 9th, 2022 at 11:00pm"
1022
+ const readableDateAndTime = formatInTimeZone(
1023
+ parseISO(`${this.selectedTime.datetime}${this.selectedTime.offset}`),
1024
+ this.calendarTimeZone,
1025
+ "h:mmaaa"
1026
+ );
1027
+
1028
+ if (this.canceledReschedule) {
1029
+ return html`
1030
+ <div id="confirmationMessage">
1031
+ <svg
1032
+ width="20"
1033
+ height="20"
1034
+ viewBox="0 0 20 20"
1035
+ fill="none"
1036
+ xmlns="http://www.w3.org/2000/svg"
1037
+ >
1038
+ <path
1039
+ d="M7 0V2H13V0H15V2H19C19.2652 2 19.5196 2.10536 19.7071 2.29289C19.8946 2.48043 20 2.73478 20 3V19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H1C0.734784 20 0.48043 19.8946 0.292893 19.7071C0.105357 19.5196 0 19.2652 0 19V3C0 2.73478 0.105357 2.48043 0.292893 2.29289C0.48043 2.10536 0.734784 2 1 2H5V0H7ZM18 9H2V18H18V9ZM13.036 10.136L14.45 11.55L9.5 16.5L5.964 12.964L7.38 11.55L9.501 13.672L13.037 10.136H13.036ZM5 4H2V7H18V4H15V5H13V4H7V5H5V4Z"
1040
+ fill="#202020"
1041
+ />
1042
+ </svg>
1043
+ <p>Thank you!</p>
1044
+ <p>We'll see you at your originally schedule tour time.</p>
1045
+ </div>
1046
+ `;
1047
+ }
1048
+
1049
+ return html`
1050
+ <div id="confirmationMessage">
1051
+ <svg
1052
+ width="20"
1053
+ height="20"
1054
+ viewBox="0 0 20 20"
1055
+ fill="none"
1056
+ xmlns="http://www.w3.org/2000/svg"
1057
+ >
1058
+ <path
1059
+ d="M7 0V2H13V0H15V2H19C19.2652 2 19.5196 2.10536 19.7071 2.29289C19.8946 2.48043 20 2.73478 20 3V19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H1C0.734784 20 0.48043 19.8946 0.292893 19.7071C0.105357 19.5196 0 19.2652 0 19V3C0 2.73478 0.105357 2.48043 0.292893 2.29289C0.48043 2.10536 0.734784 2 1 2H5V0H7ZM18 9H2V18H18V9ZM13.036 10.136L14.45 11.55L9.5 16.5L5.964 12.964L7.38 11.55L9.501 13.672L13.037 10.136H13.036ZM5 4H2V7H18V4H15V5H13V4H7V5H5V4Z"
1060
+ fill="#202020"
1061
+ />
1062
+ </svg>
1063
+ <p>Thank you!</p>
1064
+ <p>
1065
+ Your
1066
+ ${{
1067
+ [TourType.Guided]: "guided",
1068
+ [TourType.Self]: "self-guided",
1069
+ [TourType.Virtual]: "virtual",
1070
+ }[this.tourType]}
1071
+ tour is
1072
+ ${this.promptForReschedule ? html`rescheduled` : html`scheduled`} for
1073
+ ${readableDateAndTime}
1074
+ ${getTimezoneAbbreviation(this.calendarTimeZone)}.
1075
+ </p>
1076
+ <p>
1077
+ Look for an email confirmation along with instructions and directions.
1078
+ </p>
1079
+ <p>You can make changes at any time, just reply to the email.</p>
1080
+ </div>
1081
+ `;
1082
+ }
1083
+
1084
+ loadingIcon(size = 21): TemplateResult {
1085
+ return html` <svg
1086
+ id="loadingIcon"
1087
+ width=${size}
1088
+ height=${size}
1089
+ viewBox="0 0 21 21"
1090
+ fill="none"
1091
+ xmlns="http://www.w3.org/2000/svg"
1092
+ >
1093
+ <path
1094
+ d="M17.835 13.1045C18.4839 11.5628 18.6332 9.85647 18.2621 8.22548C17.8909 6.5945 17.018 5.12084 15.7659 4.0117C14.5139 2.90256 12.9457 2.21372 11.2818 2.04201C9.618 1.87031 7.94218 2.22438 6.49 3.05445L5.498 1.31745C7.01563 0.450066 8.73419 -0.00418222 10.4822 2.90165e-05C12.2302 0.00424025 13.9466 0.466764 15.46 1.34145C19.95 3.93345 21.67 9.48345 19.577 14.1115L20.919 14.8855L16.754 17.0995L16.589 12.3855L17.835 13.1045ZM3.085 6.89845C2.43614 8.44015 2.28678 10.1464 2.65792 11.7774C3.02905 13.4084 3.90201 14.8821 5.15407 15.9912C6.40613 17.1003 7.97432 17.7892 9.63816 17.9609C11.302 18.1326 12.9778 17.7785 14.43 16.9485L15.422 18.6855C13.9044 19.5528 12.1858 20.0071 10.4378 20.0029C8.68979 19.9987 6.97344 19.5361 5.46 18.6615C0.97 16.0695 -0.75 10.5195 1.343 5.89145L0 5.11845L4.165 2.90445L4.33 7.61845L3.084 6.89945L3.085 6.89845Z"
1095
+ fill="#1E1E1E"
1096
+ />
1097
+ </svg>`;
1098
+ }
1099
+
1100
+ render(): TemplateResult {
1101
+ const isLoading =
1102
+ this.waitingForAvailabilities || this.shouldAllowScheduleLoading;
1103
+ if (!this.shouldAllowScheduling && !isLoading) {
1104
+ return html` <div
1105
+ id="tour-scheduler-inner-form"
1106
+ class="${classnames({
1107
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1108
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1109
+ "tour-scheduler-mobile": isMobile(),
1110
+ })}"
1111
+ >
1112
+ <div id="top-header">
1113
+ <div></div>
1114
+ ${this.closeButton()}
1115
+ </div>
1116
+ <div class="center-tour-not-avail">
1117
+ <h1>Sorry, there are currently no tour availabilities</h1>
1118
+ <p>Please check back again later</p>
1119
+ </div>
1120
+ </div>`;
1121
+ }
1122
+ if (isLoading) {
1123
+ return html` <div
1124
+ id="tour-scheduler-inner-form"
1125
+ class="${classnames({
1126
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1127
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1128
+ "tour-scheduler-mobile": isMobile(),
1129
+ })}"
1130
+ >
1131
+ <div class="center-tour-not-avail">
1132
+ <h1>Searching for availabilities...</h1>
1133
+ <div class="loading-entire-tour-icon">${this.loadingIcon(48)}</div>
1134
+ </div>
1135
+ </div>`;
1136
+ }
1137
+ if (this.errorGettingAvailabilities) {
1138
+ if (this.buildingId === 123519) {
1139
+ // https://meetelise.zendesk.com/agent/tickets/94824
1140
+ return html` <div
1141
+ class="${classnames({
1142
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1143
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1144
+ "tour-scheduler-mobile": isMobile(),
1145
+ })}"
1146
+ >
1147
+ <div id="top-header">
1148
+ <div></div>
1149
+ ${this.closeButton()}
1150
+ </div>
1151
+ <div class="center-tour-not-avail">
1152
+ <div>
1153
+ <p>
1154
+ Tours are by appointment only. Please call our leasing office at
1155
+ (844) 779-7491 to schedule your tour!"
1156
+ </p>
1157
+ </div>
1158
+ </div>
1159
+ </div>`;
1160
+ }
1161
+ return html` <div
1162
+ class="${classnames({
1163
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1164
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1165
+ "tour-scheduler-mobile": isMobile(),
1166
+ })}"
1167
+ >
1168
+ <div id="top-header">
1169
+ <div></div>
1170
+ ${this.closeButton()}
1171
+ </div>
1172
+ <div class="center-tour-not-avail">
1173
+ <div>
1174
+ <p>Sorry, there are currently no tour availabilities!</p>
1175
+ <p>
1176
+ We apologize, but there are currently no tours available. This
1177
+ could be due to all slots being filled, off-season periods, or
1178
+ maintenance.
1179
+ </p>
1180
+ <p>
1181
+ We understand that this may be disappointing and we apologize for
1182
+ any inconvenience. We recommend checking back soon as our schedule
1183
+ frequently updates.
1184
+ </p>
1185
+ <p>
1186
+ In the meantime, feel free to explore our website for other
1187
+ information and attractions. Thank you for your understanding and
1188
+ patience!
1189
+ </p>
1190
+ </div>
1191
+ </div>
1192
+ </div>`;
1193
+ }
1194
+ if (!isMobile()) {
1195
+ return html`
1196
+ <div
1197
+ class="${classnames({
1198
+ "tour-scheduler-full": !this.compactDesign,
1199
+ "tour-scheduler-compact": this.compactDesign,
1200
+ loading: isLoading,
1201
+ })}"
1202
+ @leadsource="${(e: CustomEvent) =>
1203
+ (this.selectedLeadSource = e.detail.selectedLeadSource)}"
1204
+ >
1205
+ <div id="top-header">
1206
+ <h1 id="tour-header-title">
1207
+ ${isLoading
1208
+ ? html`${this.loadingIcon()} Searching availabilities...`
1209
+ : "Schedule a Tour"}
1210
+ </h1>
1211
+ ${this.closeButton()}
1212
+ </div>
1213
+
1214
+ ${this.tourIsBooked || this.canceledReschedule
1215
+ ? html`
1216
+ <div class="tour-scheduler">${this.confirmationMessage()}</div>
1217
+ `
1218
+ : this.promptForReschedule
1219
+ ? html`
1220
+ <div id="scheduler-container">
1221
+ <div>
1222
+ <h2 class="journey-header">Reschedule Tour</h2>
1223
+ <p class="explanation">
1224
+ You already have a tour scheduled. Would you like to
1225
+ reschedule?
1226
+ </p>
1227
+ </div>
1228
+ </div>
1229
+ <div id="tour-scheduler-footer">
1230
+ <p class="explanation">
1231
+ We'll send a confirmation and any follow-ups to your email
1232
+ address, please reply to the email to make any appointment
1233
+ changes.
1234
+ ${formDisclaimer({
1235
+ buildingName: this.buildingName,
1236
+ phoneNumberInput: this.phoneInput?.value,
1237
+ emailInput: this.emailInput?.value,
1238
+ orgLegalName: this.orgLegalName,
1239
+ orgSlug: this.orgSlug,
1240
+ })}
1241
+ </p>
1242
+
1243
+ <div class="reschedule-buttons-wrapper">
1244
+ <action-confirm-button
1245
+ id="cancel-reschedule-bttn"
1246
+ .onClick=${() => {
1247
+ this.canceledReschedule = true;
1248
+ }}
1249
+ .isLoading=${this.isSubmitting}
1250
+ height="50px"
1251
+ width="145px"
1252
+ text="Cancel"
1253
+ ?disabled=${!this.formIsValidForSubmission()}
1254
+ ></action-confirm-button>
1255
+ <action-confirm-button
1256
+ id="reschedule-button"
1257
+ .onClick=${this.submit}
1258
+ .isLoading=${this.isSubmitting}
1259
+ height="50px"
1260
+ width="145px"
1261
+ text="Reschedule"
1262
+ ?disabled=${!this.formIsValidForSubmission()}
1263
+ ></action-confirm-button>
1264
+ </div>
1265
+ </div>
1266
+ `
1267
+ : html`
1268
+ <div id="scheduler-container">
1269
+ <div id="book-tour-journey-items">
1270
+ <div id="tour-type-menu-outer-container">
1271
+ ${this.tourTypeMenu()}
1272
+ </div>
1273
+ <div id="date-and-time-menu-outer-container">
1274
+ ${this.dateAndTimeMenu()}
1275
+ </div>
1276
+
1277
+ <div id="user-info-and-layout-menu-outer-container">
1278
+ ${this.userInfoAndLayoutMenu()}
1279
+ </div>
1280
+ </div>
1281
+ </div>
1282
+ <div id="tour-scheduler-footer">
1283
+ <p class="explanation">
1284
+ We'll send a confirmation and any follow-ups to your email
1285
+ address, please reply to the email to make any appointment
1286
+ changes.
1287
+ ${formDisclaimer({
1288
+ buildingName: this.buildingName,
1289
+ phoneNumberInput: this.phoneInput?.value,
1290
+ emailInput: this.emailInput?.value,
1291
+ orgLegalName: this.orgLegalName,
1292
+ orgSlug: this.orgSlug,
1293
+ })}
1294
+ </p>
1295
+
1296
+ <action-confirm-button
1297
+ id="schedule-bttn"
1298
+ .onClick=${this.submit}
1299
+ .isLoading=${this.isSubmitting}
1300
+ height="50px"
1301
+ width="145px"
1302
+ text="Schedule tour"
1303
+ ?disabled=${!this.formIsValidForSubmission()}
1304
+ ></action-confirm-button>
1305
+ </div>
1306
+ `}
1307
+ </div>
1308
+ `;
1309
+ } else {
1310
+ const currentPage = this.mobilePages[this.mobilePageIndex];
1311
+ return html`
1312
+ <div
1313
+ class="${classnames("tour-scheduler-mobile", {
1314
+ loading: isLoading,
1315
+ })}"
1316
+ >
1317
+ <div id="top-header">
1318
+ <h1 id="scheduleATour">
1319
+ ${isLoading
1320
+ ? html`${this.loadingIcon()} Searching availabilities...`
1321
+ : "Schedule a tour"}
1322
+ </h1>
1323
+ ${this.closeButton()}
1324
+ </div>
1325
+ <div id="mobile-body-container">
1326
+ ${this.canceledReschedule
1327
+ ? html`
1328
+ <div id="confirmationMessage">
1329
+ <svg
1330
+ width="20"
1331
+ height="20"
1332
+ viewBox="0 0 20 20"
1333
+ fill="none"
1334
+ xmlns="http://www.w3.org/2000/svg"
1335
+ >
1336
+ <path
1337
+ d="M7 0V2H13V0H15V2H19C19.2652 2 19.5196 2.10536 19.7071 2.29289C19.8946 2.48043 20 2.73478 20 3V19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H1C0.734784 20 0.48043 19.8946 0.292893 19.7071C0.105357 19.5196 0 19.2652 0 19V3C0 2.73478 0.105357 2.48043 0.292893 2.29289C0.48043 2.10536 0.734784 2 1 2H5V0H7ZM18 9H2V18H18V9ZM13.036 10.136L14.45 11.55L9.5 16.5L5.964 12.964L7.38 11.55L9.501 13.672L13.037 10.136H13.036ZM5 4H2V7H18V4H15V5H13V4H7V5H5V4Z"
1338
+ fill="#202020"
1339
+ />
1340
+ </svg>
1341
+ <p>Thank you!</p>
1342
+ <p>We'll see you at your originally schedule tour time.</p>
1343
+ </div>
1344
+ `
1345
+ : this.tourIsBooked
1346
+ ? this.confirmationMessage()
1347
+ : this.promptForReschedule
1348
+ ? html` <div id="scheduler-container">
1349
+ <div>
1350
+ <h2 class="journey-header">Reschedule Tour</h2>
1351
+ <p class="explanation">
1352
+ You already have a tour scheduled. Would you like to
1353
+ reschedule?
1354
+ </p>
1355
+ </div>
1356
+ </div>
1357
+ <div id="tour-scheduler-footer">
1358
+ <div class="reschedule-buttons-wrapper">
1359
+ <action-confirm-button
1360
+ id="cancel-reschedule-bttn"
1361
+ .onClick=${() => {
1362
+ this.canceledReschedule = true;
1363
+ }}
1364
+ .isLoading=${this.isSubmitting}
1365
+ height="50px"
1366
+ width="145px"
1367
+ text="Cancel"
1368
+ ?disabled=${!this.formIsValidForSubmission()}
1369
+ ></action-confirm-button>
1370
+ <action-confirm-button
1371
+ id="reschedule-button"
1372
+ .onClick=${this.submit}
1373
+ .isLoading=${this.isSubmitting}
1374
+ height="50px"
1375
+ width="145px"
1376
+ text="Reschedule"
1377
+ ?disabled=${!this.formIsValidForSubmission()}
1378
+ ></action-confirm-button>
1379
+ </div>
1380
+ </div>`
1381
+ : html` ${currentPage.render()}
1382
+ ${!currentPage.renderNextButton
1383
+ ? html` <button
1384
+ id="mobile-next-bttn"
1385
+ @click=${currentPage.nextButtonAction}
1386
+ ?disabled=${(() => {
1387
+ return !currentPage.validate() || isLoading;
1388
+ })()}
1389
+ >
1390
+ ${currentPage.nextButtonText}
1391
+ </button>`
1392
+ : currentPage.renderNextButton()}`}
1393
+ ${this.mobilePageIndex + 1 === this.mobilePages.length
1394
+ ? html`
1395
+ ${formDisclaimer({
1396
+ buildingName: this.buildingName,
1397
+ phoneNumberInput: this.phoneInput?.value,
1398
+ emailInput: this.emailInput?.value,
1399
+ orgLegalName: this.orgLegalName,
1400
+ orgSlug: this.orgSlug,
1401
+ })}
1402
+ `
1403
+ : html``}
1404
+ </div>
1405
+ </div>
1406
+ `;
1407
+ }
1408
+ }
1409
+ }
1410
+
1411
+ export enum TourType {
1412
+ Guided = "guided",
1413
+ Self = "self",
1414
+ Virtual = "virtual",
1415
+ }
1416
+
1417
+ const tourTypeMap = {
1418
+ [TourType.Guided]:
1419
+ TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.WithAgent,
1420
+ [TourType.Self]:
1421
+ TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.SelfGuided,
1422
+ [TourType.Virtual]:
1423
+ TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.VirtualShowing,
1424
+ };
1425
+
1426
+ const tourTypeForSubmission = {
1427
+ [TourType.Guided]: "escorted-tour",
1428
+ [TourType.Self]: "self-guided-tour",
1429
+ [TourType.Virtual]: "live-virtual-tour",
1430
+ };