@meetelise/chat 1.21.0 → 1.21.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 (76) hide show
  1. package/.github/pull_request_template.md +61 -0
  2. package/.idea/codeStyles/Project.xml +57 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/.idea/workspace.xml +67 -0
  7. package/README.md +29 -14
  8. package/declarations.d.ts +12 -0
  9. package/package.json +5 -1
  10. package/public/demo/index.html +62 -4
  11. package/public/demo/secret.html +63 -0
  12. package/public/dist/index.js +3184 -1105
  13. package/public/dist/index.js.LICENSE.txt +19 -9
  14. package/public/index.html +6 -4
  15. package/src/MEChat.ts +207 -52
  16. package/src/MyPubnub.ts +657 -0
  17. package/src/WebComponent/LeadSourceClient.ts +300 -0
  18. package/src/WebComponent/Scheduler/date-picker.ts +1 -1
  19. package/src/WebComponent/Scheduler/time-picker.ts +86 -76
  20. package/src/WebComponent/Scheduler/tour-scheduler.ts +694 -764
  21. package/src/WebComponent/Scheduler/tour-type-option.ts +17 -3
  22. package/src/WebComponent/Scheduler/tourSchedulerStyles.ts +418 -0
  23. package/src/WebComponent/actions/InputStyles.ts +32 -10
  24. package/src/WebComponent/actions/action-confirm-button.ts +16 -11
  25. package/src/WebComponent/actions/call-us-window.ts +341 -58
  26. package/src/WebComponent/actions/details-window.ts +30 -16
  27. package/src/WebComponent/actions/email-us-window.ts +89 -58
  28. package/src/WebComponent/actions/formatPhoneNumber.ts +15 -1
  29. package/src/WebComponent/actions/minimize-expand-button.ts +92 -0
  30. package/src/WebComponent/health-chat.ts +267 -0
  31. package/src/WebComponent/healthcare/healthcare-launcher-styles.ts +34 -0
  32. package/src/WebComponent/healthcare/healthcare-launcher.ts +100 -0
  33. package/src/WebComponent/healthchat-styles.ts +119 -0
  34. package/src/WebComponent/index.ts +1 -1
  35. package/src/WebComponent/launcher/Launcher.ts +919 -0
  36. package/src/WebComponent/{launcherStyles.ts → launcher/launcherStyles.ts} +172 -29
  37. package/src/WebComponent/launcher/mobile-launcher.ts +127 -0
  38. package/src/WebComponent/launcher/typeEmojiStyles.ts +161 -0
  39. package/src/WebComponent/launcher/typeMiniStyles.ts +60 -0
  40. package/src/WebComponent/launcher/typeMobileStyles.ts +50 -0
  41. package/src/WebComponent/leasing-chat-styles.ts +114 -0
  42. package/src/WebComponent/me-chat.ts +964 -351
  43. package/src/WebComponent/me-select.ts +48 -21
  44. package/src/WebComponent/mini-loader.ts +28 -0
  45. package/src/WebComponent/pubnub-chat-styles.ts +192 -0
  46. package/src/WebComponent/pubnub-chat.ts +707 -0
  47. package/src/WebComponent/pubnub-media.ts +208 -0
  48. package/src/WebComponent/pubnub-message-styles.ts +54 -0
  49. package/src/WebComponent/pubnub-message.ts +421 -0
  50. package/src/analytics.ts +114 -14
  51. package/src/assetUrls.ts +2 -0
  52. package/src/disclaimers.ts +56 -0
  53. package/src/fetchBuildingABTestType.ts +4 -0
  54. package/src/fetchBuildingInfo.ts +25 -17
  55. package/src/fetchFeatureFlag.ts +147 -0
  56. package/src/fetchLeadSources.ts +67 -1
  57. package/src/fetchPhoneNumberFromSource.ts +31 -0
  58. package/src/fetchWebchatPreferences.ts +55 -0
  59. package/src/getAvailabilities.ts +7 -3
  60. package/src/getBuildingPhoneNumber.ts +26 -0
  61. package/src/getShouldAllowScheduling.ts +16 -0
  62. package/src/getTimezoneString.ts +39 -0
  63. package/src/gtm.ts +17 -0
  64. package/src/handleChatId.ts +101 -0
  65. package/src/insertDNIIntoWebsite.ts +136 -0
  66. package/src/insertLeadSourceIntoSchedulerLinks.ts +50 -0
  67. package/src/postLeadSources.ts +39 -35
  68. package/src/svgIcons.ts +62 -53
  69. package/src/themes.ts +47 -121
  70. package/src/utils.ts +88 -1
  71. package/src/WebComponent/Launcher.ts +0 -559
  72. package/src/WebComponent/actions/text-us-window.ts +0 -279
  73. package/src/chatID.ts +0 -64
  74. package/src/createConversation.ts +0 -57
  75. package/src/fetchCurrentParsedLeadSource.ts +0 -24
  76. package/src/getRegisteredPhoneNumbers.ts +0 -56
@@ -1,8 +1,8 @@
1
- import { css, html, LitElement, PropertyValueMap, TemplateResult } from "lit";
1
+ import { html, LitElement, PropertyValueMap, TemplateResult } from "lit";
2
2
  import { customElement, property, query, state } from "lit/decorators.js";
3
3
  import {
4
4
  shortcutKeyIsPressed,
5
- formatToPhone,
5
+ formatToPhoneInput,
6
6
  isPrintableCharacter,
7
7
  } from "../actions/formatPhoneNumber";
8
8
  import "./tour-type-option.ts";
@@ -19,39 +19,28 @@ import { format } from "date-fns";
19
19
  import { DatePicker } from "./date-picker";
20
20
  import { MESelect } from "../me-select";
21
21
  import { TimePicker } from "./time-picker";
22
- import { LabeledOption, UnitV2 } from "../../fetchBuildingInfo";
23
- import { isMobile } from "../../utils";
24
- import axios from "axios";
25
- import { isNumber, isString, mapValues } from "lodash";
22
+ import { LabeledOption } from "../../fetchBuildingInfo";
23
+ import { isMobile, isValidPhoneNumber } from "../../utils";
24
+ import axios, { AxiosError } from "axios";
25
+ import mapValues from "lodash/mapValues";
26
26
  import classnames from "classnames";
27
27
  import parseISO from "date-fns/parseISO";
28
28
  import compareAsc from "date-fns/compareAsc";
29
29
  import { InputStyles } from "../actions/InputStyles";
30
30
  import { classMap } from "lit/directives/class-map.js";
31
- import postLeadSources from "../../postLeadSources";
32
-
33
- const getHumanReadableLayout = (layout: string) => {
34
- if (layout == "studio") return "Studio";
35
- return {
36
- "1br": "1 bedroom",
37
- "2br": "2 bedroom",
38
- "3br": "3 bedroom",
39
- "4br": "4 bedroom",
40
- "5br": "5 bedroom",
41
- "6br": "6 bedroom",
42
- "7br": "7 bedroom",
43
- "8br": "8 bedroom",
44
- "9br": "9 bedroom",
45
- "10br": "10 bedroom",
46
- }[layout];
47
- };
31
+ import { FeatureFlagsShowDropdown } from "../../fetchFeatureFlag";
32
+ import { pushGtmEvent } from "../../gtm";
33
+ import disclaimer from "../../disclaimers";
34
+ import { tourSchedulerStyles } from "./tourSchedulerStyles";
35
+ import { defaultBrandColor, defaultBackgroundColor } from "../../themes";
36
+ import { LogType, sendLoggingEvent } from "../../analytics";
37
+ import LeadSourceClient, {
38
+ getDefaultLeadSourceAttribution,
39
+ } from "../LeadSourceClient";
40
+ import { getShouldAllowScheduling } from "../../getShouldAllowScheduling";
48
41
 
49
42
  @customElement("tour-scheduler")
50
43
  export class TourScheduler extends LitElement {
51
- @property({ attribute: false })
52
- layoutOptions: string[] = [];
53
- @property({ attribute: false })
54
- unitOptions: UnitV2[] = [];
55
44
  @property({ attribute: false })
56
45
  tourTypeOptions: LabeledOption[] = [];
57
46
  @property({ attribute: true })
@@ -68,6 +57,15 @@ export class TourScheduler extends LitElement {
68
57
  virtualToursLink = "";
69
58
  @property({ attribute: true })
70
59
  orgSlug = "";
60
+ @property({ attribute: true })
61
+ hasDynamicSchedulingEnabled = false;
62
+
63
+ @property({ attribute: true })
64
+ private leadSourceClient: LeadSourceClient | null = null;
65
+
66
+ @property({ attribute: true })
67
+ buildingName = "";
68
+
71
69
  onCloseClicked?: (e: MouseEvent) => void;
72
70
 
73
71
  @state()
@@ -98,12 +96,32 @@ export class TourScheduler extends LitElement {
98
96
  private isSubmitting = false;
99
97
  @state()
100
98
  private tourIsBooked = false;
99
+ @state()
100
+ private shouldAllowScheduling = false;
101
+ @state()
102
+ private shouldAllowScheduleLoading = true;
103
+ @state()
104
+ private promptForReschedule = false;
105
+ @state()
106
+ private canceledReschedule = false;
101
107
 
102
108
  @property({ attribute: false })
103
109
  leadSources: string[] = [];
104
110
 
105
111
  @property({ attribute: true })
106
- currentLeadSource = ""; // the default lead source based on referrer and query params
112
+ currentLeadSource = ""; // the default lead source based on referrer and query params.
113
+
114
+ @property({ attribute: true })
115
+ featureFlagShowDropdown = "";
116
+
117
+ @property({ attribute: true })
118
+ compactDesign = false;
119
+
120
+ @property({ attribute: true })
121
+ brandColor: string = defaultBrandColor;
122
+
123
+ @property({ attribute: true })
124
+ backgroundColor: string = defaultBackgroundColor;
107
125
 
108
126
  @query(".nameContainer#firstName input")
109
127
  firstNameInput!: HTMLInputElement;
@@ -113,35 +131,118 @@ export class TourScheduler extends LitElement {
113
131
  emailInput!: HTMLInputElement;
114
132
  @query(".inputContainer#phone input")
115
133
  phoneInput!: HTMLInputElement;
116
- @query("me-select#unitType")
117
- unitTypeSelect!: MESelect;
118
- @query("me-select#layoutType")
119
- layoutTypeSelect!: MESelect;
120
134
  @query("me-select#leadSource")
121
135
  selectedLeadSource!: MESelect;
122
136
 
137
+ @state()
138
+ firstNameInputValue = "";
139
+ @state()
140
+ lastNameInputValue = "";
141
+ @state()
142
+ leadSourceInputValue = "";
143
+
144
+ @state()
145
+ errorGettingAvailabilities = false;
146
+
123
147
  firstUpdated = async (): Promise<void> => {
124
- const availabilitiesExistForTourType =
125
- await getExistenceOfAvailabilitiesByTourType();
126
- this.shouldShowTourType = {
127
- [TourType.Guided]:
128
- this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT") &&
129
- availabilitiesExistForTourType[TourType.Guided],
130
- [TourType.Self]:
131
- this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED") &&
132
- availabilitiesExistForTourType[TourType.Self],
133
- [TourType.Virtual]:
134
- this.tourTypeOptions.map((o) => o.value).includes("VIRTUAL_SHOWING") &&
135
- availabilitiesExistForTourType[TourType.Virtual],
136
- };
137
- if (this.tourType === null) {
148
+ try {
149
+ const [allowScheduling, availabilitiesExistForTourType] =
150
+ await Promise.all([
151
+ getShouldAllowScheduling(
152
+ this.buildingId,
153
+ this.hasDynamicSchedulingEnabled
154
+ ),
155
+ getExistenceOfAvailabilitiesByTourType(),
156
+ ]);
157
+
158
+ this.shouldAllowScheduling = allowScheduling;
159
+ this.shouldAllowScheduleLoading = false;
160
+
161
+ sendLoggingEvent({
162
+ logTitle: "AVAILABILITIES_EXIST_FOR_TOUR_TYPE",
163
+ logData: availabilitiesExistForTourType,
164
+ logType: LogType.info,
165
+ buildingSlug: this.buildingSlug,
166
+ orgSlug: this.orgSlug,
167
+ });
168
+ const allValuesAreFalse = Object.values(
169
+ availabilitiesExistForTourType
170
+ ).every((v) => v === false);
171
+ const hasNoTourLinks =
172
+ !this.sgtUrl && !this.escortedToursLink && !this.virtualToursLink;
173
+ if (allValuesAreFalse && hasNoTourLinks) {
174
+ this.errorGettingAvailabilities = true;
175
+ this.waitingForAvailabilities = false;
176
+ sendLoggingEvent({
177
+ logTitle: "NO_AVAILABILITIES_EXIST",
178
+ logData: null,
179
+ logType: LogType.warn,
180
+ buildingSlug: this.buildingSlug,
181
+ orgSlug: this.orgSlug,
182
+ });
183
+ return;
184
+ }
185
+ this.shouldShowTourType = {
186
+ [TourType.Guided]:
187
+ this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT") &&
188
+ (availabilitiesExistForTourType[TourType.Guided] ||
189
+ !!this.escortedToursLink),
190
+ [TourType.Self]:
191
+ this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED") &&
192
+ (availabilitiesExistForTourType[TourType.Self] || !!this.sgtUrl),
193
+ [TourType.Virtual]:
194
+ this.tourTypeOptions
195
+ .map((o) => o.value)
196
+ .includes("VIRTUAL_SHOWING") &&
197
+ (availabilitiesExistForTourType[TourType.Virtual] ||
198
+ !!this.virtualToursLink),
199
+ };
200
+
201
+ // if there is only a single option for tour type, have that be the default
202
+ const hasOneShowTourType =
203
+ Object.values(this.shouldShowTourType).reduce((acc, cur) => {
204
+ if (cur === true) {
205
+ return acc + 1;
206
+ } else {
207
+ return acc;
208
+ }
209
+ }, 0) === 1;
210
+
211
+ if (hasOneShowTourType) {
212
+ this.tourType = Object.keys(this.shouldShowTourType).find(
213
+ (key) => this.shouldShowTourType[key as TourType]
214
+ ) as TourType;
215
+ } else {
216
+ // if we have multiple tour types, then check if there is a default that the client wants selected
217
+ // HACK: FOR DEMO, FOR https://www.sofioceanhills.com/ -
218
+ // THIS SHOULD BE ADDED TO WEBCHAT CONFIGURATION
219
+ // if (
220
+ // this.buildingSlug === "1ac49f90-6150-11ed-b327-1b3f05e7b9db" &&
221
+ // this.shouldShowTourType[TourType.Self]
222
+ // ) {
223
+ // this.tourType = TourType.Self;
224
+ // }
225
+ }
226
+
227
+ if (this.tourType === null) {
228
+ this.waitingForAvailabilities = false;
229
+ return;
230
+ }
231
+ this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
232
+ tourTypeMap[this.tourType]
233
+ );
138
234
  this.waitingForAvailabilities = false;
139
- return;
235
+ } catch (e) {
236
+ this.errorGettingAvailabilities = true;
237
+ this.waitingForAvailabilities = false;
238
+ sendLoggingEvent({
239
+ logTitle: "ERROR_LOADING_AVAILABILITIES",
240
+ logData: { error: e },
241
+ logType: LogType.error,
242
+ buildingSlug: this.buildingSlug,
243
+ orgSlug: this.orgSlug,
244
+ });
140
245
  }
141
- this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
142
- tourTypeMap[this.tourType]
143
- );
144
- this.waitingForAvailabilities = false;
145
246
  };
146
247
 
147
248
  protected willUpdate = async (
@@ -176,7 +277,7 @@ export class TourScheduler extends LitElement {
176
277
  this.phoneNumber.slice(0, cursorPosition) +
177
278
  e.key +
178
279
  this.phoneNumber.slice(cursorPosition);
179
- this.phoneNumber = formatToPhone(updated.replace(/\D/g, ""));
280
+ this.phoneNumber = formatToPhoneInput(updated.replace(/\D/g, ""));
180
281
  this.phoneInput.value = this.phoneNumber;
181
282
  } else if (e.key === "Backspace") {
182
283
  /*
@@ -212,7 +313,7 @@ export class TourScheduler extends LitElement {
212
313
  0,
213
314
  -1
214
315
  )}${digitsAfterCursor}`;
215
- this.phoneNumber = formatToPhone(updatedDigits);
316
+ this.phoneNumber = formatToPhoneInput(updatedDigits);
216
317
  this.phoneInput.value = this.phoneNumber;
217
318
  const numOfCharactersDeleted =
218
319
  originalCharacterCount - this.phoneNumber.length;
@@ -283,7 +384,7 @@ export class TourScheduler extends LitElement {
283
384
  const numbersAfterCursor = cursorPosition
284
385
  ? this.phoneInput.value.slice(cursorPosition).replace(/\D/g, "")
285
386
  : "";
286
- this.phoneNumber = formatToPhone(this.phoneInput.value);
387
+ this.phoneNumber = formatToPhoneInput(this.phoneInput.value);
287
388
 
288
389
  // EXAMPLES: (123)| 4 numbersAfterCursor will be '4'.
289
390
  let cursorNegativeIndex = 0;
@@ -316,19 +417,17 @@ export class TourScheduler extends LitElement {
316
417
  this.emailInput?.value.includes("@") &&
317
418
  // TODO: deleting phone number doesn't cause validation to fail, at least on mobile
318
419
  !!this.phoneNumber &&
319
- this.phoneNumber.length === 14
420
+ this.phoneNumber.length === 14 &&
421
+ isValidPhoneNumber(this.phoneNumber)
320
422
  );
321
423
  },
322
- leadSource: (): boolean =>
323
- this.leadSources.length > 0 ? !!this.selectedLeadSource?.value : true,
324
424
  };
325
425
 
326
426
  formIsValidForSubmission = (): boolean => {
327
427
  const isValid =
328
428
  this.validators.tourType() &&
329
429
  this.validators.dateAndTime() &&
330
- this.validators.leadInfo() &&
331
- this.validators.leadSource();
430
+ this.validators.leadInfo();
332
431
  return isValid;
333
432
  };
334
433
 
@@ -347,36 +446,62 @@ export class TourScheduler extends LitElement {
347
446
  };
348
447
 
349
448
  submit = async (): Promise<void> => {
350
- if (!this.selectedDate || !this.selectedTime || this.tourType === null)
449
+ if (!this.selectedDate || !this.selectedTime || this.tourType === null) {
351
450
  return;
451
+ }
352
452
  const queryParams = new URLSearchParams(window.location.search);
453
+
454
+ const parsedLeadSource =
455
+ this.selectedLeadSource && this.selectedLeadSource.value
456
+ ? this.selectedLeadSource.value
457
+ : this.leadSourceInputValue ?? this.currentLeadSource;
458
+
459
+ const leadSources = [
460
+ ...new Set(
461
+ parsedLeadSource
462
+ ? [parsedLeadSource, "property-website"]
463
+ : ["property-website"]
464
+ ),
465
+ ];
466
+ if (this.firstNameInput) {
467
+ this.firstNameInputValue = this.firstNameInput.value;
468
+ }
469
+ if (this.lastNameInput) {
470
+ this.lastNameInputValue = this.lastNameInput.value;
471
+ }
472
+ this.leadSourceInputValue = parsedLeadSource;
473
+ pushGtmEvent("scheduleTourSubmitted", {
474
+ email: this.email,
475
+ phone: `+1${this.phoneNumber.match(/\d/g)?.join("")}`,
476
+ firstName: this.firstNameInput?.value ?? this.firstNameInputValue,
477
+ lastName: this.lastNameInput?.value ?? this.lastNameInputValue,
478
+ tourType: tourTypeForSubmission[this.tourType],
479
+ tourTime: `${this.selectedTime.datetime}${this.selectedTime.offset}`,
480
+ originatingSource:
481
+ leadSources.find((i) => i !== "property-website") || null,
482
+ });
353
483
  const data = {
354
484
  referrer: document.referrer,
355
485
  email_address: this.email,
356
486
  phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
357
487
  building_id: this.buildingId,
358
- first_name: this.firstNameInput.value,
359
- last_name: this.lastNameInput.value,
488
+ first_name: this.firstNameInput?.value ?? this.firstNameInputValue,
489
+ last_name: this.lastNameInput?.value ?? this.lastNameInputValue,
360
490
  tour_type: tourTypeForSubmission[this.tourType],
361
491
  tour_time: `${this.selectedTime.datetime}${this.selectedTime.offset}`, // e.g., "2022-06-27T09:00:00-07:00"
362
- layouts: this.layoutTypeSelect.value
363
- ? [this.layoutTypeSelect.value]
364
- : null,
365
- unit_numbers:
366
- this.unitTypeSelect && this.unitTypeSelect.value
367
- ? [this.unitTypeSelect.value]
368
- : null,
369
- lead_sources:
370
- this.selectedLeadSource && this.selectedLeadSource.value
371
- ? [this.selectedLeadSource.value, "property-website"]
372
- : ["property-website"],
373
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
374
- // @ts-ignore
492
+ cancel_existing_tours: this.promptForReschedule,
493
+ lead_sources: [
494
+ ...new Set(
495
+ parsedLeadSource
496
+ ? [parsedLeadSource, getDefaultLeadSourceAttribution(this.orgSlug)]
497
+ : [getDefaultLeadSourceAttribution(this.orgSlug)]
498
+ ),
499
+ ],
375
500
  query_params: Object.fromEntries(queryParams.entries()),
501
+ conversation_tracking_id: this.leadSourceClient?.chatId,
376
502
  };
377
- const url = `https://app.meetelise.com/platformApi/state/create/scheduleMe`;
503
+ const url = `https://app.meetelise.com/platformApi/state/create/v2/scheduleMe`;
378
504
  this.isSubmitting = true;
379
-
380
505
  try {
381
506
  await axios.post(url, data, {
382
507
  headers: {
@@ -385,496 +510,134 @@ export class TourScheduler extends LitElement {
385
510
  ["org-slug"]: this.orgSlug,
386
511
  },
387
512
  });
388
- await postLeadSources({
389
- chatId: this.chatId,
390
- website: window.location.origin,
391
- referrer: document.referrer,
392
- buildingSlug: this.buildingSlug,
393
- selectedLeadSource:
394
- this.selectedLeadSource && this.selectedLeadSource.value
395
- ? this.selectedLeadSource.value
396
- : "",
513
+
514
+ this.leadSourceClient?.checkAndHandleForLogLeadSource({
515
+ webchatAction: "tour-scheduler",
516
+ stateId: null,
397
517
  });
398
518
  this.isSubmitting = false;
399
519
  this.tourIsBooked = true;
400
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
401
- } catch (e: any) {
402
- const message = e.response.data["detail"] || "Failed to book tour";
520
+ } catch (e: unknown) {
521
+ const typedError = e as AxiosError<{ detail: string }>;
522
+ const message =
523
+ typedError.response?.data?.["detail"] || "Failed to book tour";
524
+ if (
525
+ typedError.response &&
526
+ typedError.response.headers &&
527
+ typedError.response.headers[
528
+ "funnel-prospect-conflicting-appointment-error"
529
+ ]
530
+ ) {
531
+ if (this.selectedDate && this.selectedTime) {
532
+ const dateAvailabilities =
533
+ this.availabilitiesGroupedByDay[
534
+ format(this.selectedDate, "yyyy-MM-dd")
535
+ ];
536
+ if (dateAvailabilities) {
537
+ this.availabilitiesGroupedByDay[
538
+ format(this.selectedDate, "yyyy-MM-dd")
539
+ ] = dateAvailabilities.filter(
540
+ (availability) => availability !== this.selectedTime
541
+ );
542
+ }
543
+ }
544
+ } else if (
545
+ typedError.response?.status === 409 &&
546
+ !this.promptForReschedule
547
+ ) {
548
+ this.promptForReschedule = true;
549
+ this.isSubmitting = false;
550
+ this.tourIsBooked = false;
551
+ return;
552
+ }
403
553
  alert(message);
404
554
  this.isSubmitting = false;
405
555
  this.tourIsBooked = false;
406
556
  }
407
557
  };
408
558
 
409
- static styles = [
410
- css`
411
- * {
412
- box-sizing: border-box;
413
- }
414
-
415
- .tour-scheduler {
416
- position: fixed;
417
- left: 50%;
418
- top: 50%;
419
- transform: translate(-50%, -50%);
420
- width: 1018px;
421
- height: 573px;
422
- background: #ffffff;
423
- box-shadow: 0px 16px 18px 10px rgba(21, 21, 21, 0.4);
424
- border-radius: 10px;
425
- font-family: "Poppins";
426
- color: #202020;
427
- padding: 0 25px 0 27px;
428
-
429
- /* grid stuff */
430
- display: grid;
431
- grid-template-columns: 229px 432px 305px;
432
- grid-template-rows: 44px 54px 32px 245px 117px 1px;
433
- }
434
-
435
- @media screen and (max-width: 1000px) {
436
- .tour-scheduler {
437
- transform: translate(-50%, -50%) scale(0.8, 0.6);
438
- }
439
- }
440
-
441
- h1,
442
- h2 {
443
- margin: 0;
444
- }
445
-
446
- .tour-scheduler > :is(h1, h2) {
447
- align-self: end;
448
- }
449
-
450
- h1 {
451
- font-size: 14px;
452
- font-weight: 700;
453
- }
454
-
455
- h1#scheduleATour {
456
- grid-row: 1 / 2;
457
- grid-column: 1;
458
- align-self: end;
459
- z-index: 1; // idk why, but it's invisible on the confirmation page otherwise
460
- }
461
-
462
- button#closeButton {
463
- grid-row: 1 / 2;
464
- grid-column: 3;
465
- background: none;
466
- border: none;
467
- align-self: end;
468
- justify-self: end;
469
- z-index: 1; // idk why, but it's invisible on the confirmation page otherwise
470
- }
471
-
472
- /*
473
- makes button fit size of SVG:
474
- https://stackoverflow.com/questions/45423874/button-height-is-greater-than-the-nested-contents-height
475
- otherwise there's some empty space at the bottom of the button, which interferes with vertical centering
476
- */
477
- button#closeButton > svg {
478
- vertical-align: middle;
479
- }
480
-
481
- #tourTypeMenu {
482
- grid-row: 4 / 5;
483
- grid-column: 1;
484
- align-self: start;
485
- display: flex;
486
- flex-direction: column;
487
- gap: 15px;
488
- }
489
-
490
- h2 {
491
- font-weight: 600;
492
- font-size: 14px;
493
- grid-row: label-row;
494
- }
495
-
496
- h2#tourType {
497
- grid-column: 1;
498
- grid-row: 2;
499
- }
500
-
501
- h2#dateAndTime {
502
- grid-column: 2;
503
- grid-row: 2;
504
- }
505
-
506
- h2#yourInformation {
507
- grid-column: 3;
508
- grid-row: 2;
509
- }
510
-
511
- #datePicker {
512
- display: flex;
513
- }
514
-
515
- #dateAndTimeMenu {
516
- grid-row: 4 / 5;
517
- grid-column: 2;
518
- align-self: start;
519
- display: flex;
520
- flex-direction: column;
521
- }
522
-
523
- #yourInformationMenu {
524
- grid-row: 4 / 5;
525
- grid-column: 3;
526
- display: flex;
527
- flex-direction: column;
528
- gap: 12px;
529
- }
530
-
531
- #yourInformationMenu input {
532
- width: 100%;
533
- height: 49px;
534
- }
535
-
536
- .unitLayoutChoices {
537
- grid-row: 5 / 6;
538
- grid-column: 3;
539
- align-self: start;
540
- display: flex;
541
- flex-direction: column;
542
- }
543
- .unitLayoutChoicesDropdowns {
544
- display: flex;
545
- justify-content: space-between;
546
- gap: 6px;
547
- }
548
-
549
- .unitLayoutChoice {
550
- margin-bottom: 12px;
551
- width: -webkit-fill-available;
552
- }
553
-
554
- h2.unitLayoutChoice {
555
- margin-bottom: 7px;
556
- }
557
-
558
- .unitLayoutOptions {
559
- display: flex;
560
- flex-direction: column;
561
- gap: 8px;
562
- }
563
-
564
- hr {
565
- grid-row: 6;
566
- grid-column: 1 / 5;
567
- /* remove side margins because of this
568
- * (https://stackoverflow.com/questions/34365271/hr-inside-container-with-display-flex-become-corrupted)
569
- * and top/bottom margins to position the line exactly at the gridline
570
- */
571
- margin: 0;
572
- }
573
-
574
- p {
575
- font-weight: 400;
576
- font-size: 12px;
577
- grid-row: 7;
578
- grid-column: 1 / 3;
579
- align-self: start;
580
- margin: 0;
581
- padding-top: 32px;
582
- }
583
-
584
- #schedule {
585
- width: 145px;
586
- height: 50px;
587
- grid-row: 7;
588
- grid-column: 3;
589
- justify-self: end;
590
- align-self: start;
591
- margin-top: 18px;
592
- }
593
-
594
- #confirmationMessage {
595
- grid-row: 3;
596
- width: 625px;
597
- }
598
-
599
- #confirmationMessage > p {
600
- font-size: 18px;
601
- }
602
-
603
- /* Loading styles: pulsing gray overlay on all the form elements */
604
-
605
- @keyframes spin {
606
- 0% {
607
- transform: none;
608
- }
609
- 50% {
610
- transform: rotateZ(180deg);
611
- }
612
- 100% {
613
- transform: rotateZ(360deg);
614
- }
615
- }
616
-
617
- svg#loadingIcon {
618
- animation: spin 2s infinite linear;
619
- }
620
-
621
- .tour-scheduler.loading #scheduleATour {
622
- display: flex;
623
- gap: 10px;
624
- }
625
-
626
- @keyframes loadingPulse {
627
- 0% {
628
- background-color: #e7e7e7;
629
- }
630
- 50% {
631
- background-color: white;
632
- }
633
- 100% {
634
- background-color: #e7e7e7;
635
- }
636
- }
637
-
638
- tour-type-option,
639
- date-picker,
640
- #yourInformationMenu .inputContainer {
641
- position: relative;
642
- }
643
-
644
- .tour-scheduler.loading
645
- :is(tour-type-option, date-picker, #yourInformationMenu
646
- .inputContainer)::after {
647
- content: "";
648
- position: absolute;
649
- top: 0;
650
- left: 0;
651
- height: 100%;
652
- z-index: 1;
653
- animation: loadingPulse 2s infinite;
654
- }
655
-
656
- .tour-scheduler.loading tour-type-option::after {
657
- border-radius: 10px;
658
- width: 200px;
659
- }
660
-
661
- .tour-scheduler.loading date-picker::after {
662
- border-radius: 10px;
663
- width: 205px;
664
- }
665
-
666
- .tour-scheduler.loading #yourInformationMenu .inputContainer input {
667
- visibility: hidden;
668
- }
669
-
670
- .tour-scheduler.loading time-picker {
671
- display: none;
672
- }
673
-
674
- #namesWrapper {
675
- display: flex;
676
- justify-content: space-between;
677
- }
678
-
679
- .nameContainer {
680
- width: 48%;
681
- }
682
-
683
- .nameInput {
684
- width: 100%;
685
- }
686
-
687
- @media (max-width: 767px) {
688
- /* TODO: separate styles into general, desktop-specific, and mobile-specific.
689
- basically everything I have "unset" or "initial" on should become desktop-specific. the grid layout is only for desktop.
690
- */
691
- .tour-scheduler {
692
- position: fixed;
693
- left: 0;
694
- right: 0;
695
- bottom: 0;
696
- top: initial;
697
- height: 93vh;
698
- width: 100vw;
699
- background: #ffffff;
700
- transform: none;
701
- box-shadow: none;
702
- border-radius: 0;
703
- padding: 25px 20px 0 22px;
704
- display: flex;
705
- flex-direction: column;
706
- }
707
-
708
- #topControls {
709
- display: flex;
710
- justify-content: space-between;
711
- align-items: center;
712
- }
713
-
714
- .tour-scheduler > :is(h1, h2) {
715
- align-self: unset;
716
- }
717
-
718
- /* TODO: this and :disabled is duplicated from Schedule button. make a class,
719
- or better a component, for the button styles
720
- */
721
- button#next {
722
- height: 50px;
723
- /* width: 74px; */
724
- padding: 13px 22px 14px 22px;
725
- align-self: flex-start;
726
- background: #202020;
727
- border: 1px solid #ffffff;
728
- border-radius: 10px;
729
- font-family: "Poppins";
730
- font-weight: 700;
731
- font-size: 14px;
732
- color: #ffffff;
733
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
734
- margin-top: 22px;
735
- }
736
-
737
- button#next:disabled {
738
- background: #e7e7e7;
739
- box-shadow: none;
740
- }
741
-
742
- h1#scheduleATour {
743
- grid-row: unset;
744
- grid-column: unset;
745
- align-self: unset;
746
- }
747
-
748
- button#closeButton {
749
- grid-row: unset;
750
- grid-column: unset;
751
- align-self: unset;
752
- justify-self: unset;
753
- }
754
-
755
- h2 {
756
- grid-row: unset;
757
- margin-top: 37px;
758
- margin-bottom: 25px;
759
- }
760
-
761
- #tourTypeMenu {
762
- display: flex;
763
- flex-direction: column;
764
- gap: 15px;
765
- margin-bottom: 36px;
766
- }
767
-
768
- h2#tourType {
769
- grid-column: unset;
770
- grid-row: unset;
771
- }
772
-
773
- #datePicker {
774
- display: flex;
775
- flex-direction: column;
776
- gap: 18px;
777
- }
778
-
779
- time-picker {
780
- /* so the Next button doesn't jump when the date is selected and the time slots appear */
781
- height: 93px;
782
- }
783
-
784
- #dateAndTimeMenu {
785
- grid-row: unset;
786
- grid-column: unset;
787
- align-self: unset;
788
- display: unset;
789
- flex-direction: unset;
790
- }
791
-
792
- #confirmationMessage {
793
- grid-row: unset;
794
- width: 90%;
795
- margin-top: 37px;
796
- }
797
- }
798
- `,
799
- InputStyles,
800
- ];
559
+ static styles = [tourSchedulerStyles, InputStyles];
801
560
 
802
561
  tourTypeMenu(): TemplateResult {
803
- return html`<h2 id="tourType">Tour Type</h2>
804
- <div id="tourTypeMenu">
805
- ${this.shouldShowTourType[TourType.Guided]
562
+ return html`<h2 class="journey-header">Tour Type</h2>
563
+ <div id="tour-type-menu">
564
+ ${this.shouldShowTourType[TourType.Self]
806
565
  ? html` <tour-type-option
807
- heading="Guided tour"
808
- subtitle="with an agent"
566
+ heading="Take a tour"
567
+ subtitle="on your own"
568
+ .brandColor=${this.brandColor}
569
+ .backgroundColor=${this.backgroundColor}
809
570
  @click="${async () => {
810
- this.tourType = TourType.Guided;
811
-
812
- if (this.escortedToursLink) {
813
- window.open(this.escortedToursLink, "_blank");
571
+ this.tourType = TourType.Self;
572
+ if (this.sgtUrl) {
573
+ window.open(this.sgtUrl, "_blank");
814
574
  }
815
575
  this.availabilitiesGroupedByDay =
816
576
  await getAvailabilitiesGroupedByDay(
817
- tourTypeMap[TourType.Guided]
577
+ tourTypeMap[TourType.Self]
818
578
  );
819
579
  }}"
820
580
  @keydown="${(e: KeyboardEvent) => {
821
581
  if ([" ", "Enter"].includes(e.key)) {
822
582
  e.preventDefault();
823
- this.tourType = TourType.Guided;
583
+ this.tourType = TourType.Self;
824
584
  }
825
585
  }}"
826
- ?selected="${this.tourType === TourType.Guided}"
586
+ ?selected="${this.tourType === TourType.Self}"
827
587
  >
828
588
  <svg
829
589
  slot="icon"
830
- width="31"
590
+ width="28"
831
591
  height="31"
832
- viewBox="0 0 31 31"
592
+ viewBox="0 0 28 31"
833
593
  fill="none"
834
594
  xmlns="http://www.w3.org/2000/svg"
835
595
  >
836
596
  <path
837
- d="M0.833252 30.1666C0.833252 27.1608 2.0273 24.2782 4.15271 22.1527C6.27812 20.0273 9.1608 18.8333 12.1666 18.8333C15.1724 18.8333 18.0551 20.0273 20.1805 22.1527C22.3059 24.2782 23.4999 27.1608 23.4999 30.1666H0.833252ZM12.1666 17.4166C7.47034 17.4166 3.66659 13.6129 3.66659 8.91663C3.66659 4.22038 7.47034 0.416626 12.1666 0.416626C16.8628 0.416626 20.6666 4.22038 20.6666 8.91663C20.6666 13.6129 16.8628 17.4166 12.1666 17.4166ZM22.5975 20.58C24.7645 21.137 26.7006 22.3634 28.13 24.0846C29.5595 25.8059 30.4096 27.9342 30.5592 30.1666H26.3333C26.3333 26.4691 24.9166 23.1031 22.5975 20.58ZM19.7316 17.3557C20.9187 16.2939 21.8681 14.9932 22.5175 13.5388C23.167 12.0845 23.5017 10.5094 23.4999 8.91663C23.5029 6.9807 23.0078 5.07657 22.062 3.38738C23.6666 3.70979 25.11 4.5779 26.1469 5.84415C27.1838 7.1104 27.7502 8.69666 27.7499 10.3333C27.7503 11.3426 27.5349 12.3404 27.1182 13.2597C26.7016 14.179 26.0932 14.9986 25.3339 15.6636C24.5746 16.3285 23.6819 16.8236 22.7157 17.1154C21.7495 17.4072 20.7321 17.4892 19.7316 17.3557V17.3557Z"
838
- fill="${this.tourType === TourType.Guided
597
+ d="M14.8334 19.1903V30.1667H0.666687C0.666248 28.4367 1.06183 26.7297 1.82311 25.1763C2.58439 23.6229 3.69118 22.2644 5.05866 21.2049C6.42614 20.1453 8.01802 19.4129 9.71232 19.0637C11.4066 18.7145 13.1584 18.7578 14.8334 19.1903ZM12 17.4167C7.30377 17.4167 3.50002 13.6129 3.50002 8.91666C3.50002 4.22041 7.30377 0.416656 12 0.416656C16.6963 0.416656 20.5 4.22041 20.5 8.91666C20.5 13.6129 16.6963 17.4167 12 17.4167ZM20.5 23.0833V18.125L27.5834 24.5L20.5 30.875V25.9167H16.25V23.0833H20.5Z"
598
+ fill="${this.tourType === TourType.Self
839
599
  ? "#ffffff"
840
600
  : "#202020"}"
841
601
  />
842
602
  </svg>
843
603
  </tour-type-option>`
844
604
  : ""}
845
- ${this.shouldShowTourType[TourType.Self]
846
- ? html`<tour-type-option
847
- heading="Take a tour"
848
- subtitle="on your own"
605
+ ${this.shouldShowTourType[TourType.Guided]
606
+ ? html` <tour-type-option
607
+ heading="Guided tour"
608
+ subtitle="with an agent"
609
+ .brandColor=${this.brandColor}
610
+ .backgroundColor=${this.backgroundColor}
849
611
  @click="${async () => {
850
- this.tourType = TourType.Self;
851
- if (this.sgtUrl) {
852
- window.open(this.sgtUrl, "_blank");
612
+ this.tourType = TourType.Guided;
613
+
614
+ if (this.escortedToursLink) {
615
+ window.open(this.escortedToursLink, "_blank");
853
616
  }
854
617
  this.availabilitiesGroupedByDay =
855
618
  await getAvailabilitiesGroupedByDay(
856
- tourTypeMap[TourType.Self]
619
+ tourTypeMap[TourType.Guided]
857
620
  );
858
621
  }}"
859
622
  @keydown="${(e: KeyboardEvent) => {
860
623
  if ([" ", "Enter"].includes(e.key)) {
861
624
  e.preventDefault();
862
- this.tourType = TourType.Self;
625
+ this.tourType = TourType.Guided;
863
626
  }
864
627
  }}"
865
- ?selected="${this.tourType === TourType.Self}"
628
+ ?selected="${this.tourType === TourType.Guided}"
866
629
  >
867
630
  <svg
868
631
  slot="icon"
869
- width="28"
632
+ width="31"
870
633
  height="31"
871
- viewBox="0 0 28 31"
634
+ viewBox="0 0 31 31"
872
635
  fill="none"
873
636
  xmlns="http://www.w3.org/2000/svg"
874
637
  >
875
638
  <path
876
- d="M14.8334 19.1903V30.1667H0.666687C0.666248 28.4367 1.06183 26.7297 1.82311 25.1763C2.58439 23.6229 3.69118 22.2644 5.05866 21.2049C6.42614 20.1453 8.01802 19.4129 9.71232 19.0637C11.4066 18.7145 13.1584 18.7578 14.8334 19.1903ZM12 17.4167C7.30377 17.4167 3.50002 13.6129 3.50002 8.91666C3.50002 4.22041 7.30377 0.416656 12 0.416656C16.6963 0.416656 20.5 4.22041 20.5 8.91666C20.5 13.6129 16.6963 17.4167 12 17.4167ZM20.5 23.0833V18.125L27.5834 24.5L20.5 30.875V25.9167H16.25V23.0833H20.5Z"
877
- fill="${this.tourType === TourType.Self
639
+ d="M0.833252 30.1666C0.833252 27.1608 2.0273 24.2782 4.15271 22.1527C6.27812 20.0273 9.1608 18.8333 12.1666 18.8333C15.1724 18.8333 18.0551 20.0273 20.1805 22.1527C22.3059 24.2782 23.4999 27.1608 23.4999 30.1666H0.833252ZM12.1666 17.4166C7.47034 17.4166 3.66659 13.6129 3.66659 8.91663C3.66659 4.22038 7.47034 0.416626 12.1666 0.416626C16.8628 0.416626 20.6666 4.22038 20.6666 8.91663C20.6666 13.6129 16.8628 17.4166 12.1666 17.4166ZM22.5975 20.58C24.7645 21.137 26.7006 22.3634 28.13 24.0846C29.5595 25.8059 30.4096 27.9342 30.5592 30.1666H26.3333C26.3333 26.4691 24.9166 23.1031 22.5975 20.58ZM19.7316 17.3557C20.9187 16.2939 21.8681 14.9932 22.5175 13.5388C23.167 12.0845 23.5017 10.5094 23.4999 8.91663C23.5029 6.9807 23.0078 5.07657 22.062 3.38738C23.6666 3.70979 25.11 4.5779 26.1469 5.84415C27.1838 7.1104 27.7502 8.69666 27.7499 10.3333C27.7503 11.3426 27.5349 12.3404 27.1182 13.2597C26.7016 14.179 26.0932 14.9986 25.3339 15.6636C24.5746 16.3285 23.6819 16.8236 22.7157 17.1154C21.7495 17.4072 20.7321 17.4892 19.7316 17.3557V17.3557Z"
640
+ fill="${this.tourType === TourType.Guided
878
641
  ? "#ffffff"
879
642
  : "#202020"}"
880
643
  />
@@ -882,9 +645,11 @@ export class TourScheduler extends LitElement {
882
645
  </tour-type-option>`
883
646
  : ""}
884
647
  ${this.shouldShowTourType[TourType.Virtual]
885
- ? html`<tour-type-option
648
+ ? html` <tour-type-option
886
649
  heading="Virtual tour"
887
650
  subtitle="over video"
651
+ .brandColor=${this.brandColor}
652
+ .backgroundColor=${this.backgroundColor}
888
653
  @click="${async () => {
889
654
  this.tourType = TourType.Virtual;
890
655
  if (this.virtualToursLink) {
@@ -923,8 +688,19 @@ export class TourScheduler extends LitElement {
923
688
  </div>`;
924
689
  }
925
690
 
926
- dateAndTimeMenu(horizontal = false): TemplateResult {
927
- return html`<h2 id="dateAndTime">Date and Time</h2>
691
+ dateAndTimeMenu(): TemplateResult {
692
+ if (this.tourType === TourType.Self && this.sgtUrl) {
693
+ return html`<h2 class="journey-header">Date and Time</h2>
694
+ <div id="dateAndTimeMenu">
695
+ <button
696
+ id="self-guided-redirect-bttn"
697
+ @click=${() => window.open(this.sgtUrl, "_blank")}
698
+ >
699
+ View Self Guided Tour Times
700
+ </button>
701
+ </div>`;
702
+ }
703
+ return html`<h2 class="journey-header">Date and Time</h2>
928
704
  <div id="dateAndTimeMenu">
929
705
  <div id="datePicker">
930
706
  <date-picker
@@ -933,54 +709,70 @@ export class TourScheduler extends LitElement {
933
709
  (dates) => dates.map((date) => new Date(date.offset))
934
710
  )}
935
711
  @change=${(e: Event) => {
712
+ // if the user clicked a tour type that is suppose to redirect, we redirect that use when they select a date
713
+ // This can happen if the user clicks, is redirect, and then comes back to the webchat
714
+ if (this.tourType === TourType.Self && this.sgtUrl) {
715
+ window.open(this.sgtUrl, "_blank");
716
+ return;
717
+ }
718
+ if (this.tourType === TourType.Guided && this.escortedToursLink) {
719
+ window.open(this.escortedToursLink, "_blank");
720
+ return;
721
+ }
722
+ if (this.tourType === TourType.Virtual && this.virtualToursLink) {
723
+ window.open(this.virtualToursLink, "_blank");
724
+ return;
725
+ }
726
+
936
727
  if (e.target instanceof DatePicker) {
937
728
  this.selectedDate = e.target.selectedDate;
938
729
  }
939
730
  }}
940
731
  ></date-picker>
941
- <time-picker
942
- ?selecteddateexists=${!!this.selectedDate}
943
- ?horizontal=${horizontal}
944
- .options=${this.selectedDate
945
- ? this.availabilitiesGroupedByDay[
946
- format(this.selectedDate, "y-MM-dd")
947
- ]
948
- ?.sort((a, b) =>
949
- compareAsc(parseISO(a.datetime), parseISO(b.datetime))
950
- )
951
- .map((date) => {
952
- return {
953
- dateWithTimeZoneOffset: date,
954
- displayTime: format(
955
- parseISO(`${date.datetime}${date.offset}`),
956
- "h:mmaaa"
957
- ),
958
- };
959
- })
960
- : []}
961
- @change=${(e: Event) => {
962
- if (e.target instanceof TimePicker) {
963
- if (!this.selectedDate) return;
964
- const daysAvailabilities =
965
- this.availabilitiesGroupedByDay[
966
- format(new Date(this.selectedDate), "y-MM-dd")
967
- ];
968
- const index = e.target.selectedTime
969
- ? daysAvailabilities.indexOf(
970
- e.target.selectedTime.dateWithTimeZoneOffset
732
+ <div>
733
+ <time-picker
734
+ ?selecteddateexists=${!!this.selectedDate}
735
+ .options=${this.selectedDate
736
+ ? this.availabilitiesGroupedByDay[
737
+ format(this.selectedDate, "y-MM-dd")
738
+ ]
739
+ ?.sort((a, b) =>
740
+ compareAsc(parseISO(a.datetime), parseISO(b.datetime))
971
741
  )
972
- : null;
973
- this.selectedTime =
974
- index !== null ? daysAvailabilities[index] : null;
975
- }
976
- }}
977
- ></time-picker>
742
+ .map((date) => {
743
+ return {
744
+ dateWithTimeZoneOffset: date,
745
+ displayTime: format(
746
+ parseISO(`${date.datetime}${date.offset}`),
747
+ "h:mmaaa"
748
+ ),
749
+ };
750
+ })
751
+ : []}
752
+ @change=${(e: Event) => {
753
+ if (e.target instanceof TimePicker) {
754
+ if (!this.selectedDate) return;
755
+ const daysAvailabilities =
756
+ this.availabilitiesGroupedByDay[
757
+ format(new Date(this.selectedDate), "y-MM-dd")
758
+ ];
759
+ const index = e.target.selectedTime
760
+ ? daysAvailabilities.indexOf(
761
+ e.target.selectedTime.dateWithTimeZoneOffset
762
+ )
763
+ : null;
764
+ this.selectedTime =
765
+ index !== null ? daysAvailabilities[index] : null;
766
+ }
767
+ }}
768
+ ></time-picker>
769
+ </div>
978
770
  </div>
979
771
  </div>`;
980
772
  }
981
773
 
982
774
  closeButton(): TemplateResult {
983
- return html`<button id="closeButton" @click=${this.onCloseClicked}>
775
+ return html` <button id="close-button" @click=${this.onCloseClicked}>
984
776
  <svg
985
777
  width="19"
986
778
  height="19"
@@ -1023,15 +815,15 @@ export class TourScheduler extends LitElement {
1023
815
  this.mobilePageIndex++;
1024
816
  },
1025
817
  render: (): TemplateResult => {
1026
- return html`${this.dateAndTimeMenu(true)}`;
818
+ return html`${this.dateAndTimeMenu()}`;
1027
819
  },
1028
820
  },
1029
821
  {
1030
822
  validate: (): boolean => this.validators.leadInfo(),
1031
823
  // last page gets <action-confirm-button> instead of the regular button
1032
824
  nextButtonText: null,
1033
- renderNextButton: (): TemplateResult => html`<action-confirm-button
1034
- id="schedule"
825
+ renderNextButton: (): TemplateResult => html` <action-confirm-button
826
+ id="schedule-bttn"
1035
827
  .onClick=${this.submit}
1036
828
  .isLoading=${this.isSubmitting}
1037
829
  height="50px"
@@ -1047,7 +839,7 @@ export class TourScheduler extends LitElement {
1047
839
  ];
1048
840
 
1049
841
  userInfoAndLayoutMenu(): TemplateResult {
1050
- return html`<h2 id="yourInformation">Your information</h2>
842
+ return html`<h2 class="journey-header">Your Information</h2>
1051
843
  <div id="yourInformationMenu">
1052
844
  <div id="namesWrapper">
1053
845
  <div class="nameContainer" id="firstName">
@@ -1055,6 +847,8 @@ export class TourScheduler extends LitElement {
1055
847
  class=${classMap({
1056
848
  ["webchat-input"]: true,
1057
849
  ["nameInput"]: true,
850
+ ["webchat-font__desktop"]: !isMobile(),
851
+ ["webchat-font__mobile"]: isMobile(),
1058
852
  })}
1059
853
  type="text"
1060
854
  placeholder="First name"
@@ -1068,6 +862,8 @@ export class TourScheduler extends LitElement {
1068
862
  class=${classMap({
1069
863
  ["webchat-input"]: true,
1070
864
  ["nameInput"]: true,
865
+ ["webchat-font__desktop"]: !isMobile(),
866
+ ["webchat-font__mobile"]: isMobile(),
1071
867
  })}
1072
868
  type="text"
1073
869
  placeholder="Last name"
@@ -1078,125 +874,74 @@ export class TourScheduler extends LitElement {
1078
874
  </div>
1079
875
  </div>
1080
876
 
1081
- <div class="inputContainer" id="email">
1082
- <input
1083
- class="webchat-input"
1084
- type="email"
1085
- inputmode="email"
1086
- placeholder="Email"
1087
- name="email"
1088
- autocomplete="email"
1089
- .value=${this.email}
1090
- @input=${this.onChangeEmail}
1091
- />
1092
- </div>
1093
- <div class="inputContainer" id="phone">
1094
- <input
1095
- class="webchat-input"
1096
- type="tel"
1097
- inputmode="tel"
1098
- placeholder="Phone"
1099
- name="phone"
1100
- autocomplete="tel-national"
1101
- maxlength="14"
1102
- .value=${this.phoneNumber}
1103
- @keydown=${this.handlePhoneKeydown}
1104
- @keyup=${this.handlePhoneKeyup}
1105
- @input=${(e: Event) => {
1106
- if (!e.target) {
1107
- return;
1108
- }
1109
- this.phoneNumber = formatToPhone(
1110
- (e.target as HTMLInputElement).value
1111
- );
1112
- }}
1113
- />
1114
- </div>
1115
- ${
1116
- this.leadSources.length > 0
1117
- ? html` <me-select
1118
- id="leadSource"
1119
- value="${this.currentLeadSource}"
1120
- placeholder="How did you hear about us?"
1121
- .options="${this.leadSources.map((i) => ({
1122
- label: i,
1123
- value: i,
1124
- }))}"
1125
- @change=${() => {
1126
- this.requestUpdate();
1127
- }}
1128
- >
1129
- </me-select>`
1130
- : ""
1131
- }
877
+ <div class="inputContainer" id="email">
878
+ <input
879
+ class=${classMap({
880
+ ["webchat-input"]: true,
881
+ ["webchat-font__desktop"]: !isMobile(),
882
+ ["webchat-font__mobile"]: isMobile(),
883
+ })}
884
+ type="email"
885
+ inputmode="email"
886
+ placeholder="Email"
887
+ name="email"
888
+ autocomplete="email"
889
+ .value=${this.email}
890
+ @input=${this.onChangeEmail}
891
+ />
1132
892
  </div>
1133
- <div class="unitLayoutChoices">
1134
- ${
1135
- this.layoutOptions.length > 0
1136
- ? html`<h2 class="unitLayoutChoice">
1137
- What would you like to view?
1138
- </h2>`
1139
- : ""
1140
- }
1141
- <div class="unitLayoutChoicesDropdowns">
1142
- ${
1143
- this.layoutOptions.length > 0
1144
- ? html`<div class="unitLayoutChoice">
1145
- <div class="unitLayoutOptions">
1146
- <me-select
1147
- id="layoutType"
1148
- placeholder="Select layout"
1149
- .options="${sortedLayouts(this.layoutOptions).map(
1150
- (i) => ({
1151
- label: getHumanReadableLayout(i),
1152
- value: i,
1153
- })
1154
- )}"
1155
- defaultOption="Studio"
1156
- @change=${() => {
1157
- // to revalidate the form.
1158
- this.requestUpdate();
1159
- }}
1160
- >Studio
1161
- </me-select>
1162
- </div>
1163
- </div>`
1164
- : ""
1165
- }
1166
- ${
1167
- this.unitOptions.filter(
1168
- (i) =>
1169
- !this.layoutTypeSelect ||
1170
- i.layout === this.layoutTypeSelect.value
1171
- ).length > 0
1172
- ? html`
1173
- <me-select
1174
- id="unitType"
1175
- placeholder="Select unit"
1176
- .options="${this.unitOptions
1177
- .filter(
1178
- (i) =>
1179
- !this.layoutTypeSelect ||
1180
- i.layout === this.layoutTypeSelect.value
1181
- )
1182
- .map((i) => ({
1183
- label: i.name,
1184
- value: i.name,
1185
- }))}"
1186
- defaultOption="Studio"
1187
- @change=${() => {
1188
- // to revalidate the form
1189
- this.requestUpdate();
1190
- }}
1191
- >Studio
1192
- </me-select>
1193
- `
1194
- : ""
1195
- }
1196
- </div>
1197
-
1198
-
893
+ <div class="inputContainer" id="phone">
894
+ <input
895
+ class=${classMap({
896
+ ["webchat-input"]: true,
897
+ ["webchat-font__desktop"]: !isMobile(),
898
+ ["webchat-font__mobile"]: isMobile(),
899
+ ["webchat-input__error"]:
900
+ this.phoneNumber.length === 14 &&
901
+ !isValidPhoneNumber(this.phoneNumber),
902
+ })}
903
+ type="tel"
904
+ inputmode="tel"
905
+ placeholder="Phone"
906
+ name="phone"
907
+ autocomplete="tel-national"
908
+ maxlength="14"
909
+ .value=${this.phoneNumber}
910
+ @keydown=${this.handlePhoneKeydown}
911
+ @keyup=${this.handlePhoneKeyup}
912
+ @input=${(e: Event) => {
913
+ if (!e.target) {
914
+ return;
915
+ }
916
+ this.phoneNumber = formatToPhoneInput(
917
+ (e.target as HTMLInputElement).value
918
+ );
919
+ }}
920
+ />
921
+ ${this.phoneNumber.length === 14 &&
922
+ !isValidPhoneNumber(this.phoneNumber)
923
+ ? html`<p class="error-message">Invalid phone number</p>`
924
+ : ""}
1199
925
  </div>
926
+ ${this.leadSources.length > 0 &&
927
+ (this.featureFlagShowDropdown === FeatureFlagsShowDropdown.always ||
928
+ (this.featureFlagShowDropdown ===
929
+ FeatureFlagsShowDropdown.onAttributionFailure &&
930
+ this.currentLeadSource.length === 0))
931
+ ? html` <me-select
932
+ id="leadSource"
933
+ value="${this.currentLeadSource}"
934
+ placeholder="How did you hear about us?"
935
+ .options="${this.leadSources.map((i) => ({
936
+ label: i,
937
+ value: i,
938
+ }))}"
939
+ @change=${() => {
940
+ this.requestUpdate();
941
+ }}
942
+ >
943
+ </me-select>`
944
+ : ""}
1200
945
  </div> `;
1201
946
  }
1202
947
 
@@ -1208,6 +953,28 @@ export class TourScheduler extends LitElement {
1208
953
  parseISO(`${this.selectedTime.datetime}${this.selectedTime.offset}`),
1209
954
  "h:mmaaa"
1210
955
  );
956
+
957
+ if (this.canceledReschedule) {
958
+ return html`
959
+ <div id="confirmationMessage">
960
+ <svg
961
+ width="20"
962
+ height="20"
963
+ viewBox="0 0 20 20"
964
+ fill="none"
965
+ xmlns="http://www.w3.org/2000/svg"
966
+ >
967
+ <path
968
+ 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"
969
+ fill="#202020"
970
+ />
971
+ </svg>
972
+ <p>Thank you!</p>
973
+ <p>We'll see you at your originally schedule tour time.</p>
974
+ </div>
975
+ `;
976
+ }
977
+
1211
978
  return html`
1212
979
  <div id="confirmationMessage">
1213
980
  <svg
@@ -1222,16 +989,17 @@ export class TourScheduler extends LitElement {
1222
989
  fill="#202020"
1223
990
  />
1224
991
  </svg>
992
+ <p>Thank you!</p>
1225
993
  <p>
1226
- Thank you!
1227
- <br />
1228
994
  Your
1229
995
  ${{
1230
996
  [TourType.Guided]: "guided",
1231
997
  [TourType.Self]: "self-guided",
1232
998
  [TourType.Virtual]: "virtual",
1233
999
  }[this.tourType]}
1234
- tour is scheduled for ${readableDateAndTime}.
1000
+ tour is
1001
+ ${this.promptForReschedule ? html`rescheduled` : html`scheduled`} for
1002
+ ${readableDateAndTime}.
1235
1003
  </p>
1236
1004
  <p>
1237
1005
  Look for an email confirmation along with instructions and directions.
@@ -1241,11 +1009,11 @@ export class TourScheduler extends LitElement {
1241
1009
  `;
1242
1010
  }
1243
1011
 
1244
- loadingIcon(): TemplateResult {
1245
- return html`<svg
1012
+ loadingIcon(size = 21): TemplateResult {
1013
+ return html` <svg
1246
1014
  id="loadingIcon"
1247
- width="21"
1248
- height="21"
1015
+ width=${size}
1016
+ height=${size}
1249
1017
  viewBox="0 0 21 21"
1250
1018
  fill="none"
1251
1019
  xmlns="http://www.w3.org/2000/svg"
@@ -1258,75 +1026,277 @@ export class TourScheduler extends LitElement {
1258
1026
  }
1259
1027
 
1260
1028
  render(): TemplateResult {
1029
+ const isLoading =
1030
+ this.waitingForAvailabilities || this.shouldAllowScheduleLoading;
1031
+ if (!this.shouldAllowScheduling && !isLoading) {
1032
+ return html` <div
1033
+ id="tour-scheduler-inner-form"
1034
+ class="${classnames({
1035
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1036
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1037
+ "tour-scheduler-mobile": isMobile(),
1038
+ })}"
1039
+ >
1040
+ <div id="top-header">
1041
+ <div></div>
1042
+ ${this.closeButton()}
1043
+ </div>
1044
+ <div class="center-tour-not-avail">
1045
+ <h1>Sorry, there are currently no tour availabilities</h1>
1046
+ <p>Please check back again later</p>
1047
+ </div>
1048
+ </div>`;
1049
+ }
1050
+ if (isLoading) {
1051
+ return html` <div
1052
+ id="tour-scheduler-inner-form"
1053
+ class="${classnames({
1054
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1055
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1056
+ "tour-scheduler-mobile": isMobile(),
1057
+ })}"
1058
+ >
1059
+ <div class="center-tour-not-avail">
1060
+ <h1>Searching for availabilities...</h1>
1061
+ <div class="loading-entire-tour-icon">${this.loadingIcon(48)}</div>
1062
+ </div>
1063
+ </div>`;
1064
+ }
1065
+ if (this.errorGettingAvailabilities) {
1066
+ return html` <div
1067
+ class="${classnames({
1068
+ "tour-scheduler-full": !this.compactDesign && !isMobile(),
1069
+ "tour-scheduler-compact": this.compactDesign && !isMobile(),
1070
+ "tour-scheduler-mobile": isMobile(),
1071
+ })}"
1072
+ >
1073
+ <div id="top-header">
1074
+ <div></div>
1075
+ ${this.closeButton()}
1076
+ </div>
1077
+ <div class="center-tour-not-avail">
1078
+ <h1>Sorry, there are currently no tour availabilities!</h1>
1079
+ <p>
1080
+ We apologize, but there are currently no tours available. This could
1081
+ be due to all slots being filled, off-season periods, or
1082
+ maintenance.
1083
+ </p>
1084
+ <p>
1085
+ We understand that this may be disappointing and we apologize for
1086
+ any inconvenience. We recommend checking back soon as our schedule
1087
+ frequently updates.
1088
+ </p>
1089
+ <p>
1090
+ In the meantime, feel free to explore our website for other
1091
+ information and attractions. Thank you for your understanding and
1092
+ patience!
1093
+ </p>
1094
+ </div>
1095
+ </div>`;
1096
+ }
1261
1097
  if (!isMobile()) {
1262
1098
  return html`
1263
1099
  <div
1264
- class="${classnames("tour-scheduler", {
1265
- loading: this.waitingForAvailabilities,
1100
+ class="${classnames({
1101
+ "tour-scheduler-full": !this.compactDesign,
1102
+ "tour-scheduler-compact": this.compactDesign,
1103
+ loading: isLoading,
1266
1104
  })}"
1267
1105
  @leadsource="${(e: CustomEvent) =>
1268
1106
  (this.selectedLeadSource = e.detail.selectedLeadSource)}"
1269
1107
  >
1270
- <h1 id="scheduleATour">
1271
- ${this.waitingForAvailabilities
1272
- ? html`${this.loadingIcon()} Searching availabilities...`
1273
- : "Schedule a tour"}
1274
- </h1>
1275
- ${this.closeButton()}
1276
- ${this.tourIsBooked
1108
+ <div id="top-header">
1109
+ <h1 id="tour-header-title">
1110
+ ${isLoading
1111
+ ? html`${this.loadingIcon()} Searching availabilities...`
1112
+ : "Schedule a Tour"}
1113
+ </h1>
1114
+ ${this.closeButton()}
1115
+ </div>
1116
+
1117
+ ${this.tourIsBooked || this.canceledReschedule
1277
1118
  ? html`
1278
1119
  <div class="tour-scheduler">${this.confirmationMessage()}</div>
1279
1120
  `
1280
- : html`${this.tourTypeMenu()} ${this.dateAndTimeMenu()}
1281
- ${this.userInfoAndLayoutMenu()}
1282
- <hr />
1283
- <p id="explanation">
1284
- We’ll send a confirmation and any follow-ups to your email
1285
- address.
1286
- </p>
1287
- <action-confirm-button
1288
- id="schedule"
1289
- .onClick=${this.submit}
1290
- .isLoading=${this.isSubmitting}
1291
- height="50px"
1292
- width="145px"
1293
- text="Schedule tour"
1294
- ?disabled=${!this.formIsValidForSubmission()}
1295
- ></action-confirm-button>`}
1121
+ : this.promptForReschedule
1122
+ ? html`
1123
+ <div id="scheduler-container">
1124
+ <div>
1125
+ <h2 class="journey-header">Reschedule Tour</h2>
1126
+ <p class="explanation">
1127
+ You already have a tour scheduled. Would you like to
1128
+ reschedule?
1129
+ </p>
1130
+ </div>
1131
+ </div>
1132
+ <div id="tour-scheduler-footer">
1133
+ <p class="explanation">
1134
+ We'll send a confirmation and any follow-ups to your email
1135
+ address.
1136
+ ${disclaimer({
1137
+ buildingName: this.buildingName,
1138
+ phoneNumberInput: this.phoneInput?.value,
1139
+ emailInput: this.emailInput?.value,
1140
+ })}
1141
+ </p>
1142
+
1143
+ <div class="reschedule-buttons-wrapper">
1144
+ <action-confirm-button
1145
+ id="cancel-reschedule-bttn"
1146
+ .onClick=${() => {
1147
+ this.canceledReschedule = true;
1148
+ }}
1149
+ .isLoading=${this.isSubmitting}
1150
+ height="50px"
1151
+ width="145px"
1152
+ text="Cancel"
1153
+ ?disabled=${!this.formIsValidForSubmission()}
1154
+ ></action-confirm-button>
1155
+ <action-confirm-button
1156
+ id="reschedule-button"
1157
+ .onClick=${this.submit}
1158
+ .isLoading=${this.isSubmitting}
1159
+ height="50px"
1160
+ width="145px"
1161
+ text="Reschedule"
1162
+ ?disabled=${!this.formIsValidForSubmission()}
1163
+ ></action-confirm-button>
1164
+ </div>
1165
+ </div>
1166
+ `
1167
+ : html`
1168
+ <div id="scheduler-container">
1169
+ <div id="book-tour-journey-items">
1170
+ <div id="tour-type-menu-outer-container">
1171
+ ${this.tourTypeMenu()}
1172
+ </div>
1173
+ <div id="date-and-time-menu-outer-container">
1174
+ ${this.dateAndTimeMenu()}
1175
+ </div>
1176
+
1177
+ <div id="user-info-and-layout-menu-outer-container">
1178
+ ${this.userInfoAndLayoutMenu()}
1179
+ </div>
1180
+ </div>
1181
+ </div>
1182
+ <div id="tour-scheduler-footer">
1183
+ <p class="explanation">
1184
+ We'll send a confirmation and any follow-ups to your email
1185
+ address.
1186
+ ${disclaimer({
1187
+ buildingName: this.buildingName,
1188
+ phoneNumberInput: this.phoneInput?.value,
1189
+ emailInput: this.emailInput?.value,
1190
+ })}
1191
+ </p>
1192
+
1193
+ <action-confirm-button
1194
+ id="schedule-bttn"
1195
+ .onClick=${this.submit}
1196
+ .isLoading=${this.isSubmitting}
1197
+ height="50px"
1198
+ width="145px"
1199
+ text="Schedule tour"
1200
+ ?disabled=${!this.formIsValidForSubmission()}
1201
+ ></action-confirm-button>
1202
+ </div>
1203
+ `}
1296
1204
  </div>
1297
1205
  `;
1298
1206
  } else {
1299
1207
  const currentPage = this.mobilePages[this.mobilePageIndex];
1300
1208
  return html`
1301
1209
  <div
1302
- class="${classnames("tour-scheduler", {
1303
- loading: this.waitingForAvailabilities,
1210
+ class="${classnames("tour-scheduler-mobile", {
1211
+ loading: isLoading,
1304
1212
  })}"
1305
1213
  >
1306
- <div id="topControls">
1214
+ <div id="top-header">
1307
1215
  <h1 id="scheduleATour">
1308
- ${this.waitingForAvailabilities
1216
+ ${isLoading
1309
1217
  ? html`${this.loadingIcon()} Searching availabilities...`
1310
1218
  : "Schedule a tour"}
1311
1219
  </h1>
1312
1220
  ${this.closeButton()}
1313
1221
  </div>
1314
- ${this.tourIsBooked
1315
- ? this.confirmationMessage()
1316
- : html` ${currentPage.render()}
1317
- ${!currentPage.renderNextButton
1318
- ? html` <button
1319
- id="next"
1320
- @click=${currentPage.nextButtonAction}
1321
- ?disabled=${(() => {
1322
- return (
1323
- !currentPage.validate() || this.waitingForAvailabilities
1324
- );
1325
- })()}
1326
- >
1327
- ${currentPage.nextButtonText}
1328
- </button>`
1329
- : currentPage.renderNextButton()}`}
1222
+ <div id="mobile-body-container">
1223
+ ${this.canceledReschedule
1224
+ ? html`
1225
+ <div id="confirmationMessage">
1226
+ <svg
1227
+ width="20"
1228
+ height="20"
1229
+ viewBox="0 0 20 20"
1230
+ fill="none"
1231
+ xmlns="http://www.w3.org/2000/svg"
1232
+ >
1233
+ <path
1234
+ 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"
1235
+ fill="#202020"
1236
+ />
1237
+ </svg>
1238
+ <p>Thank you!</p>
1239
+ <p>We'll see you at your originally schedule tour time.</p>
1240
+ </div>
1241
+ `
1242
+ : this.tourIsBooked
1243
+ ? this.confirmationMessage()
1244
+ : this.promptForReschedule
1245
+ ? html` <div id="scheduler-container">
1246
+ <div>
1247
+ <h2 class="journey-header">Reschedule Tour</h2>
1248
+ <p class="explanation">
1249
+ You already have a tour scheduled. Would you like to
1250
+ reschedule?
1251
+ </p>
1252
+ </div>
1253
+ </div>
1254
+ <div id="tour-scheduler-footer">
1255
+ <div class="reschedule-buttons-wrapper">
1256
+ <action-confirm-button
1257
+ id="cancel-reschedule-bttn"
1258
+ .onClick=${() => {
1259
+ this.canceledReschedule = true;
1260
+ }}
1261
+ .isLoading=${this.isSubmitting}
1262
+ height="50px"
1263
+ width="145px"
1264
+ text="Cancel"
1265
+ ?disabled=${!this.formIsValidForSubmission()}
1266
+ ></action-confirm-button>
1267
+ <action-confirm-button
1268
+ id="reschedule-button"
1269
+ .onClick=${this.submit}
1270
+ .isLoading=${this.isSubmitting}
1271
+ height="50px"
1272
+ width="145px"
1273
+ text="Reschedule"
1274
+ ?disabled=${!this.formIsValidForSubmission()}
1275
+ ></action-confirm-button>
1276
+ </div>
1277
+ </div>`
1278
+ : html` ${currentPage.render()}
1279
+ ${!currentPage.renderNextButton
1280
+ ? html` <button
1281
+ id="mobile-next-bttn"
1282
+ @click=${currentPage.nextButtonAction}
1283
+ ?disabled=${(() => {
1284
+ return !currentPage.validate() || isLoading;
1285
+ })()}
1286
+ >
1287
+ ${currentPage.nextButtonText}
1288
+ </button>`
1289
+ : currentPage.renderNextButton()}`}
1290
+ ${this.mobilePageIndex + 1 === this.mobilePages.length
1291
+ ? html`
1292
+ ${disclaimer({
1293
+ buildingName: this.buildingName,
1294
+ phoneNumberInput: this.phoneInput?.value,
1295
+ emailInput: this.emailInput?.value,
1296
+ })}
1297
+ `
1298
+ : html``}
1299
+ </div>
1330
1300
  </div>
1331
1301
  `;
1332
1302
  }
@@ -1353,43 +1323,3 @@ const tourTypeForSubmission = {
1353
1323
  [TourType.Self]: "self-guided-tour",
1354
1324
  [TourType.Virtual]: "live-virtual-tour",
1355
1325
  };
1356
-
1357
- const getLayoutOrder = (layout: string) => {
1358
- return {
1359
- studio: 0,
1360
- "1br": 1,
1361
- "2br": 2,
1362
- "3br": 3,
1363
- "4br": 4,
1364
- "5br": 5,
1365
- "6br": 6,
1366
- "7br": 7,
1367
- "8br": 8,
1368
- "9br": 9,
1369
- "10br": 10,
1370
- }[layout];
1371
- };
1372
-
1373
- const getLayoutFromOrder = (order: number) => {
1374
- return {
1375
- 0: "studio",
1376
- 1: "1br",
1377
- 2: "2br",
1378
- 3: "3br",
1379
- 4: "4br",
1380
- 5: "5br",
1381
- 6: "6br",
1382
- 7: "7br",
1383
- 8: "8br",
1384
- 9: "9br",
1385
- 10: "10br",
1386
- }[order];
1387
- };
1388
-
1389
- const sortedLayouts = (layouts: string[]) => {
1390
- const layoutOrder = layouts
1391
- .map((layout) => getLayoutOrder(layout))
1392
- .filter(isNumber)
1393
- .sort();
1394
- return layoutOrder.map((order) => getLayoutFromOrder(order)).filter(isString);
1395
- };