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