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