@meetelise/chat 1.12.1 → 1.12.4
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/package.json +2 -2
- package/public/demo/index.html +4 -2
- package/public/dist/index.js +545 -279
- package/src/WebComponent/InHouseLauncher.ts +56 -7
- package/src/WebComponent/MEChat.ts +8 -0
- package/src/WebComponent/Scheduler/date-picker.ts +90 -21
- package/src/WebComponent/Scheduler/me-select.ts +8 -13
- package/src/WebComponent/Scheduler/time-picker.ts +105 -58
- package/src/WebComponent/Scheduler/tour-scheduler.ts +728 -179
- package/src/WebComponent/Scheduler/tour-type-option.ts +0 -3
- package/src/WebComponent/actions/ActionConfirmButton.ts +0 -1
- package/src/WebComponent/actions/CallUsWindow.ts +23 -1
- package/src/WebComponent/actions/DetailsWindow.ts +0 -1
- package/src/WebComponent/actions/EmailUsWindow.ts +0 -1
- package/src/WebComponent/actions/InputStyles.ts +0 -1
- package/src/WebComponent/actions/TextUsWindow.ts +0 -1
- package/src/WebComponent/actions/formatPhoneNumber.ts +26 -10
- package/src/WebComponent/inHouseLauncherStyles.ts +1 -2
- package/src/fetchBuildingInfo.ts +7 -0
- package/src/getAvailabilities.ts +10 -0
- package/src/getRegisteredPhoneNumbers.ts +56 -0
|
@@ -1,25 +1,278 @@
|
|
|
1
1
|
import { css, html, LitElement, TemplateResult } from "lit";
|
|
2
|
-
import { customElement, property, state } from "lit/decorators.js";
|
|
2
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
3
|
+
import {
|
|
4
|
+
shortcutKeyIsPressed,
|
|
5
|
+
formatToPhone,
|
|
6
|
+
isPrintableCharacter,
|
|
7
|
+
} from "../actions/formatPhoneNumber";
|
|
3
8
|
import "./tour-type-option.ts";
|
|
4
9
|
import "./date-picker.ts";
|
|
5
10
|
import "./time-picker.ts";
|
|
6
11
|
import "./me-select.ts";
|
|
12
|
+
import { getAvailabilitiesGroupedByDayCached } from "../../getAvailabilities";
|
|
13
|
+
import { TourAvailabilityResponseRankOrderedSupportedTourTypesEnum } from "@meetelise/rest-sdk";
|
|
14
|
+
import { format } from "date-fns";
|
|
15
|
+
import { DatePicker } from "./date-picker";
|
|
16
|
+
import { MESelect } from "./me-select";
|
|
17
|
+
import { TimePicker } from "./time-picker";
|
|
18
|
+
import { LabeledOption } from "../../fetchBuildingInfo";
|
|
19
|
+
import { isMobile } from "../../utils";
|
|
20
|
+
import axios from "axios";
|
|
7
21
|
|
|
8
22
|
@customElement("tour-scheduler")
|
|
9
23
|
export class TourScheduler extends LitElement {
|
|
10
24
|
@property({ attribute: false })
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
layoutOptions: LabeledOption[] = [];
|
|
26
|
+
@property({ attribute: false })
|
|
27
|
+
tourTypeOptions: LabeledOption[] = [];
|
|
28
|
+
@property({ type: Number })
|
|
29
|
+
buildingId = 0;
|
|
13
30
|
@property({ attribute: false })
|
|
14
|
-
|
|
31
|
+
onCloseClicked?: (e: MouseEvent) => void;
|
|
15
32
|
|
|
16
|
-
// state: type, date, time, name, email, phone, unit type, layout type
|
|
17
33
|
@state()
|
|
18
34
|
private tourType = TourType.Guided;
|
|
35
|
+
@state()
|
|
36
|
+
private email = "";
|
|
37
|
+
@state()
|
|
38
|
+
private phoneNumber = "";
|
|
39
|
+
@state()
|
|
40
|
+
private hasPhoneNumberError = false;
|
|
41
|
+
@state()
|
|
42
|
+
private availabilitiesGroupedByDay: { [day: string]: Date[] } = {};
|
|
43
|
+
@state()
|
|
44
|
+
private selectedDate?: Date;
|
|
45
|
+
@state()
|
|
46
|
+
private selectedTime?: string;
|
|
47
|
+
@state()
|
|
48
|
+
private mobilePageIndex = 0;
|
|
49
|
+
@state()
|
|
50
|
+
private tourIsBooked = false;
|
|
51
|
+
|
|
52
|
+
@query("input#name")
|
|
53
|
+
nameInput!: HTMLInputElement;
|
|
54
|
+
@query("input#email")
|
|
55
|
+
emailInput!: HTMLInputElement;
|
|
56
|
+
@query("input#phone")
|
|
57
|
+
phoneInput!: HTMLInputElement;
|
|
58
|
+
@query("me-select#unitType")
|
|
59
|
+
unitTypeSelect!: MESelect;
|
|
60
|
+
|
|
61
|
+
firstUpdated = async (): Promise<void> => {
|
|
62
|
+
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDayCached(
|
|
63
|
+
tourTypeMap[this.tourType]
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
handlePhoneKeydown = (e: Event): void => {
|
|
68
|
+
// these should always be true, this is just here to mollify TypeScript
|
|
69
|
+
if (
|
|
70
|
+
!(e instanceof KeyboardEvent) ||
|
|
71
|
+
!(e.target instanceof HTMLInputElement) ||
|
|
72
|
+
e.target.selectionStart === null
|
|
73
|
+
// !e.target.selectionStart
|
|
74
|
+
)
|
|
75
|
+
return;
|
|
76
|
+
|
|
77
|
+
const cursorPosition = e.target.selectionStart;
|
|
78
|
+
|
|
79
|
+
if (isPrintableCharacter(e) && !shortcutKeyIsPressed(e)) {
|
|
80
|
+
// If e.key is a character, and no modifier key is pressed, insert it at the cursor, filter out non-numbers, and auto-format
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
const updated =
|
|
84
|
+
this.phoneNumber.slice(0, cursorPosition) +
|
|
85
|
+
e.key +
|
|
86
|
+
this.phoneNumber.slice(cursorPosition);
|
|
87
|
+
this.phoneNumber = formatToPhone(updated.replace(/\D/g, ""));
|
|
88
|
+
this.phoneInput.value = this.phoneNumber;
|
|
89
|
+
} else if (e.key === "Backspace") {
|
|
90
|
+
/*
|
|
91
|
+
Handling backspace:
|
|
92
|
+
- 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
|
|
93
|
+
- Let the OS handle backspace combos like `Alt + Backspace`, then re-autoformat if necessary (in keyup)
|
|
94
|
+
- If the user wants to select and backspace a range of text, let them, then auto-format the remainder
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
// backspace combos
|
|
98
|
+
if (shortcutKeyIsPressed(e)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// backspace selection
|
|
103
|
+
if (
|
|
104
|
+
this.phoneInput.selectionEnd &&
|
|
105
|
+
this.phoneInput.selectionStart &&
|
|
106
|
+
this.phoneInput.selectionEnd - this.phoneInput.selectionStart > 0
|
|
107
|
+
) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// regular backspace
|
|
112
|
+
const originalCharacterCount = this.phoneNumber.length;
|
|
113
|
+
const digitsBeforeCursor = this.phoneNumber
|
|
114
|
+
.slice(0, cursorPosition)
|
|
115
|
+
.replace(/\D/g, "");
|
|
116
|
+
const digitsAfterCursor = this.phoneNumber
|
|
117
|
+
.slice(cursorPosition)
|
|
118
|
+
.replace(/\D/g, "");
|
|
119
|
+
const updatedDigits = `${digitsBeforeCursor.slice(
|
|
120
|
+
0,
|
|
121
|
+
-1
|
|
122
|
+
)}${digitsAfterCursor}`;
|
|
123
|
+
this.phoneNumber = formatToPhone(updatedDigits);
|
|
124
|
+
this.phoneInput.value = this.phoneNumber;
|
|
125
|
+
const numOfCharactersDeleted =
|
|
126
|
+
originalCharacterCount - this.phoneNumber.length;
|
|
127
|
+
const newCursorPosition = cursorPosition - numOfCharactersDeleted;
|
|
128
|
+
this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
return;
|
|
132
|
+
} else if (
|
|
133
|
+
["ArrowLeft", "ArrowRight"].includes(e.key) &&
|
|
134
|
+
!shortcutKeyIsPressed(e) &&
|
|
135
|
+
!e.shiftKey
|
|
136
|
+
) {
|
|
137
|
+
// when navigating with arrow keys, skip punctuation
|
|
138
|
+
if (e.key === "ArrowLeft") {
|
|
139
|
+
const charactersBeforeCursor = this.phoneNumber.slice(
|
|
140
|
+
0,
|
|
141
|
+
cursorPosition
|
|
142
|
+
);
|
|
143
|
+
const numberOfNonDigitsBeforeCursor =
|
|
144
|
+
charactersBeforeCursor.split(/\d+/).at(-1)?.length || 0;
|
|
145
|
+
const moveLeftBy = numberOfNonDigitsBeforeCursor + 1;
|
|
146
|
+
const newCursorPosition =
|
|
147
|
+
cursorPosition - moveLeftBy > -1 ? cursorPosition - moveLeftBy : 0;
|
|
148
|
+
this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
149
|
+
}
|
|
150
|
+
if (e.key === "ArrowRight") {
|
|
151
|
+
const charactersAfterCursor = this.phoneNumber.slice(cursorPosition);
|
|
152
|
+
const numberOfNonDigitsAfterCursor =
|
|
153
|
+
charactersAfterCursor.split(/\d+/)[0].length || 0;
|
|
154
|
+
const moveRightBy = numberOfNonDigitsAfterCursor + 1;
|
|
155
|
+
const newCursorPosition =
|
|
156
|
+
cursorPosition + moveRightBy < this.phoneNumber.length
|
|
157
|
+
? cursorPosition + moveRightBy
|
|
158
|
+
: this.phoneNumber.length;
|
|
159
|
+
this.phoneInput.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
160
|
+
}
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
e.stopPropagation();
|
|
163
|
+
} else {
|
|
164
|
+
// Let browser/OS handle anything else. We'll handle any changes to the phone input in the `keyup` handler.
|
|
165
|
+
// Could be a keyboard shortcut that modifies the input (like `Cmd/Ctrl + V`, which we'll handle in `keyup`),
|
|
166
|
+
// or a keyboard shortcut that doesn't (like `Cmd + L` to jump to URL bar or `Cmd + R` to reload the page),
|
|
167
|
+
// or Tab, an arrow key, etc.
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
handlePhoneKeyup = (e: KeyboardEvent): void => {
|
|
173
|
+
// 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".
|
|
174
|
+
// (We never want the cursor to be before a punctuation mark because the next digit typed will appear after the punctuation mark, not before.)
|
|
175
|
+
// If we don't do this, the cursor automatically goes to the end when we set `this.phoneNumber`.
|
|
176
|
+
// This is sometimes undesired: for example, if we've ended up here because a Mac user typed `Alt + Backspace` in the middle.
|
|
177
|
+
|
|
178
|
+
// Arrow keys are intended to change the cursor position, so don't get in their way
|
|
179
|
+
if (
|
|
180
|
+
e.key.includes("Arrow") ||
|
|
181
|
+
["Meta", "Shift", "Control", "Alt"].includes(e.key)
|
|
182
|
+
) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
19
185
|
|
|
20
|
-
|
|
21
|
-
|
|
186
|
+
const cursorPosition = this.phoneInput.selectionStart;
|
|
187
|
+
// find the numbers it's before and count backward from end after formatting
|
|
188
|
+
const numbersAfterCursor = cursorPosition
|
|
189
|
+
? this.phoneInput.value.slice(cursorPosition).replace(/\D/g, "")
|
|
190
|
+
: "";
|
|
191
|
+
this.phoneNumber = formatToPhone(this.phoneInput.value);
|
|
192
|
+
|
|
193
|
+
// EXAMPLES: (123)| 4 numbersAfterCursor will be '4'.
|
|
194
|
+
let cursorNegativeIndex = 0;
|
|
195
|
+
let numbersLeft = numbersAfterCursor.length;
|
|
196
|
+
while (numbersLeft) {
|
|
197
|
+
if (this.phoneNumber.at(cursorNegativeIndex)?.match(/\d/)) {
|
|
198
|
+
numbersLeft--;
|
|
199
|
+
}
|
|
200
|
+
cursorNegativeIndex++;
|
|
201
|
+
}
|
|
202
|
+
const cursorPositiveIndex =
|
|
203
|
+
this.phoneInput.value.length - cursorNegativeIndex + 1;
|
|
204
|
+
this.phoneInput.setSelectionRange(cursorPositiveIndex, cursorPositiveIndex);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
onChangeEmail = (e: Event): void => {
|
|
208
|
+
if (!e.target) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.email = (e.target as HTMLInputElement).value;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
validators = {
|
|
216
|
+
tourType: (): boolean => !isNaN(this.tourType),
|
|
217
|
+
dateAndTime: (): boolean => !!this.selectedDate && !!this.selectedTime,
|
|
218
|
+
leadInfo: (): boolean =>
|
|
219
|
+
!!this.nameInput?.value &&
|
|
220
|
+
this.emailInput?.value.includes("@") &&
|
|
221
|
+
// TODO: deleting phone number doesn't cause validation to fail, at least on mobile
|
|
222
|
+
!!this.phoneNumber &&
|
|
223
|
+
this.phoneNumber.length === 14,
|
|
224
|
+
unitType: (): boolean => !!this.unitTypeSelect.value,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
formIsValidForSubmission = (): boolean => {
|
|
228
|
+
const isValid =
|
|
229
|
+
this.validators.tourType() &&
|
|
230
|
+
this.validators.dateAndTime() &&
|
|
231
|
+
this.validators.leadInfo() &&
|
|
232
|
+
this.validators.unitType();
|
|
233
|
+
return isValid;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/** E.g., `timeStringToHoursAndMinutes("4:15pm")` -> `[16, 15]`
|
|
237
|
+
*/
|
|
238
|
+
timeStringToHoursAndMinutes = (
|
|
239
|
+
timeString: string
|
|
240
|
+
): [hours: number, minutes: number] => {
|
|
241
|
+
const [hoursString, minutesString] = timeString.split(/\D/g);
|
|
242
|
+
const hours =
|
|
243
|
+
parseInt(hoursString) +
|
|
244
|
+
(timeString.toLowerCase().includes("pm") ? 12 : 0);
|
|
245
|
+
const minutes = parseInt(minutesString);
|
|
246
|
+
|
|
247
|
+
return [hours, minutes];
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
submit = async (): Promise<void> => {
|
|
251
|
+
if (!this.selectedDate || !this.selectedTime) return;
|
|
252
|
+
const [hours, minutes] = this.timeStringToHoursAndMinutes(
|
|
253
|
+
this.selectedTime
|
|
254
|
+
);
|
|
255
|
+
const tourTime = new Date(this.selectedDate.getTime());
|
|
256
|
+
tourTime.setHours(hours, minutes);
|
|
257
|
+
const tourTimeString = tourTime.toISOString();
|
|
258
|
+
const data = {
|
|
259
|
+
email_address: this.email,
|
|
260
|
+
phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
|
|
261
|
+
building_id: this.buildingId,
|
|
262
|
+
// TODO: this is very bad dumb name-splitting logic! I'm only doing it because the design had one name field and the backend expects two
|
|
263
|
+
first_name: this.nameInput.value.split(" ")[0],
|
|
264
|
+
last_name: this.nameInput.value.split(" ").slice(1).join(" "),
|
|
265
|
+
tour_type: tourTypeForSubmission[this.tourType],
|
|
266
|
+
tour_time: tourTimeString,
|
|
267
|
+
};
|
|
268
|
+
const url = `https://app.meetelise.com/platformApi/state/create/scheduleMe`;
|
|
269
|
+
const response = await axios.post(url, data);
|
|
270
|
+
if (response.status === 200) {
|
|
271
|
+
this.tourIsBooked = true;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
22
274
|
|
|
275
|
+
static styles = css`
|
|
23
276
|
* {
|
|
24
277
|
box-sizing: border-box;
|
|
25
278
|
}
|
|
@@ -36,11 +289,11 @@ export class TourScheduler extends LitElement {
|
|
|
36
289
|
border-radius: 10px;
|
|
37
290
|
font-family: "Poppins";
|
|
38
291
|
color: #202020;
|
|
39
|
-
padding: 0
|
|
292
|
+
padding: 0 25px 0 27px;
|
|
40
293
|
|
|
41
294
|
/* grid stuff */
|
|
42
295
|
display: grid;
|
|
43
|
-
grid-template-columns:
|
|
296
|
+
grid-template-columns: 229px 432px 305px;
|
|
44
297
|
grid-template-rows: 44px 54px 32px 195px 152px 1px;
|
|
45
298
|
}
|
|
46
299
|
|
|
@@ -62,16 +315,26 @@ export class TourScheduler extends LitElement {
|
|
|
62
315
|
grid-row: 1 / 2;
|
|
63
316
|
grid-column: 1;
|
|
64
317
|
align-self: end;
|
|
318
|
+
z-index: 1; // idk why, but it's invisible on the confirmation page otherwise
|
|
65
319
|
}
|
|
66
320
|
|
|
67
321
|
button#closeButton {
|
|
68
322
|
grid-row: 1 / 2;
|
|
69
|
-
grid-column:
|
|
70
|
-
height: max-content;
|
|
71
|
-
aspect-ratio: 1;
|
|
323
|
+
grid-column: 3;
|
|
72
324
|
background: none;
|
|
73
325
|
border: none;
|
|
74
326
|
align-self: end;
|
|
327
|
+
justify-self: end;
|
|
328
|
+
z-index: 1; // idk why, but it's invisible on the confirmation page otherwise
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
makes button fit size of SVG:
|
|
333
|
+
https://stackoverflow.com/questions/45423874/button-height-is-greater-than-the-nested-contents-height
|
|
334
|
+
otherwise there's some empty space at the bottom of the button, which interferes with vertical centering
|
|
335
|
+
*/
|
|
336
|
+
button#closeButton > svg {
|
|
337
|
+
vertical-align: middle;
|
|
75
338
|
}
|
|
76
339
|
|
|
77
340
|
#tourTypeMenu {
|
|
@@ -129,9 +392,6 @@ export class TourScheduler extends LitElement {
|
|
|
129
392
|
height: 49px;
|
|
130
393
|
border: 1px solid #83818e;
|
|
131
394
|
padding: 13px 11px 14px 11px;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#yourInformationMenu > input::placeholder {
|
|
135
395
|
font-family: "Poppins";
|
|
136
396
|
font-style: normal;
|
|
137
397
|
font-weight: 400;
|
|
@@ -139,6 +399,10 @@ export class TourScheduler extends LitElement {
|
|
|
139
399
|
color: #202020;
|
|
140
400
|
}
|
|
141
401
|
|
|
402
|
+
#yourInformationMenu > input::placeholder {
|
|
403
|
+
color: #202020;
|
|
404
|
+
}
|
|
405
|
+
|
|
142
406
|
#unitChoiceMenu {
|
|
143
407
|
grid-row: 5 / 6;
|
|
144
408
|
grid-column: 3;
|
|
@@ -177,11 +441,10 @@ export class TourScheduler extends LitElement {
|
|
|
177
441
|
padding-top: 32px;
|
|
178
442
|
}
|
|
179
443
|
|
|
180
|
-
/* TODO: button styling depends on disabled which depends on whether form is ready for submission */
|
|
181
444
|
button#schedule {
|
|
182
445
|
width: 145px;
|
|
183
446
|
height: 50px;
|
|
184
|
-
background: #
|
|
447
|
+
background: #202020;
|
|
185
448
|
border: 1px solid #ffffff;
|
|
186
449
|
border-radius: 10px;
|
|
187
450
|
grid-row: 7;
|
|
@@ -193,180 +456,456 @@ export class TourScheduler extends LitElement {
|
|
|
193
456
|
font-weight: 700;
|
|
194
457
|
font-size: 14px;
|
|
195
458
|
color: #ffffff;
|
|
459
|
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
button#schedule:disabled {
|
|
463
|
+
background: #e7e7e7;
|
|
464
|
+
box-shadow: none;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#confirmationMessage {
|
|
468
|
+
grid-row: 3;
|
|
469
|
+
width: 625px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#confirmationMessage > p {
|
|
473
|
+
font-size: 18px;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@media (max-width: 767px) {
|
|
477
|
+
/* TODO: separate styles into general, desktop-specific, and mobile-specific.
|
|
478
|
+
basically everything I have "unset" or "initial" on should become desktop-specific. the grid layout is only for desktop.
|
|
479
|
+
*/
|
|
480
|
+
.tour-scheduler {
|
|
481
|
+
position: fixed;
|
|
482
|
+
left: 0;
|
|
483
|
+
right: 0;
|
|
484
|
+
bottom: 0;
|
|
485
|
+
top: initial;
|
|
486
|
+
height: 93vh;
|
|
487
|
+
width: 100vw;
|
|
488
|
+
background: #ffffff;
|
|
489
|
+
transform: none;
|
|
490
|
+
box-shadow: none;
|
|
491
|
+
border-radius: 0;
|
|
492
|
+
padding: 25px 20px 0 22px;
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-direction: column;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
#topControls {
|
|
498
|
+
display: flex;
|
|
499
|
+
justify-content: space-between;
|
|
500
|
+
align-items: center;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.tour-scheduler > :is(h1, h2) {
|
|
504
|
+
align-self: unset;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* TODO: this and :disabled is duplicated from Schedule button. make a class,
|
|
508
|
+
or better a component, for the button styles
|
|
509
|
+
*/
|
|
510
|
+
button#next {
|
|
511
|
+
height: 50px;
|
|
512
|
+
/* width: 74px; */
|
|
513
|
+
padding: 13px 22px 14px 22px;
|
|
514
|
+
align-self: flex-start;
|
|
515
|
+
background: #202020;
|
|
516
|
+
border: 1px solid #ffffff;
|
|
517
|
+
border-radius: 10px;
|
|
518
|
+
font-family: "Poppins";
|
|
519
|
+
font-weight: 700;
|
|
520
|
+
font-size: 14px;
|
|
521
|
+
color: #ffffff;
|
|
522
|
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
|
|
523
|
+
margin-top: 22px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
button#next:disabled {
|
|
527
|
+
background: #e7e7e7;
|
|
528
|
+
box-shadow: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
h1#scheduleATour {
|
|
532
|
+
grid-row: unset;
|
|
533
|
+
grid-column: unset;
|
|
534
|
+
align-self: unset;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
button#closeButton {
|
|
538
|
+
grid-row: unset;
|
|
539
|
+
grid-column: unset;
|
|
540
|
+
align-self: unset;
|
|
541
|
+
justify-self: unset;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
h2 {
|
|
545
|
+
grid-row: unset;
|
|
546
|
+
margin-top: 37px;
|
|
547
|
+
margin-bottom: 25px;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
#tourTypeMenu {
|
|
551
|
+
display: flex;
|
|
552
|
+
flex-direction: column;
|
|
553
|
+
gap: 15px;
|
|
554
|
+
margin-bottom: 36px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
h2#tourType {
|
|
558
|
+
grid-column: unset;
|
|
559
|
+
grid-row: unset;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
#datePicker {
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
gap: 18px;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
time-picker {
|
|
569
|
+
/* so the Next button doesn't jump when the date is selected and the time slots appear */
|
|
570
|
+
height: 93px;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#dateAndTimeMenu {
|
|
574
|
+
grid-row: unset;
|
|
575
|
+
grid-column: unset;
|
|
576
|
+
align-self: unset;
|
|
577
|
+
display: unset;
|
|
578
|
+
flex-direction: unset;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
#confirmationMessage {
|
|
582
|
+
grid-row: unset;
|
|
583
|
+
width: 90%;
|
|
584
|
+
margin-top: 37px;
|
|
585
|
+
}
|
|
196
586
|
}
|
|
197
587
|
`;
|
|
198
588
|
|
|
199
|
-
|
|
200
|
-
return html
|
|
201
|
-
<div
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fill="none"
|
|
209
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
210
|
-
>
|
|
211
|
-
<line
|
|
212
|
-
x1="0.986863"
|
|
213
|
-
y1="18.2746"
|
|
214
|
-
x2="18.2929"
|
|
215
|
-
y2="0.968593"
|
|
216
|
-
stroke="#202020"
|
|
217
|
-
stroke-width="2"
|
|
218
|
-
/>
|
|
219
|
-
<path
|
|
220
|
-
d="M1.01394 0.999997L18.0103 18.0243"
|
|
221
|
-
stroke="#202020"
|
|
222
|
-
stroke-width="2"
|
|
223
|
-
/>
|
|
224
|
-
</svg>
|
|
225
|
-
</button>
|
|
226
|
-
|
|
227
|
-
<h2 id="tourType">Tour Type</h2>
|
|
228
|
-
<div id="tourTypeMenu">
|
|
229
|
-
<tour-type-option
|
|
230
|
-
tourtype="guided"
|
|
231
|
-
heading="Guided tour"
|
|
232
|
-
subtitle="with an agent"
|
|
233
|
-
@click="${() => {
|
|
234
|
-
this.tourType = TourType.Guided;
|
|
235
|
-
}}"
|
|
236
|
-
@keydown="${(e: KeyboardEvent) => {
|
|
237
|
-
if ([" ", "Enter"].includes(e.key)) {
|
|
238
|
-
e.preventDefault();
|
|
589
|
+
tourTypeMenu(): TemplateResult {
|
|
590
|
+
return html`<h2 id="tourType">Tour Type</h2>
|
|
591
|
+
<div id="tourTypeMenu">
|
|
592
|
+
${this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT")
|
|
593
|
+
? html` <tour-type-option
|
|
594
|
+
tourtype="guided"
|
|
595
|
+
heading="Guided tour"
|
|
596
|
+
subtitle="with an agent"
|
|
597
|
+
@click="${() => {
|
|
239
598
|
this.tourType = TourType.Guided;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
viewBox="0 0 31 31"
|
|
249
|
-
fill="none"
|
|
250
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
599
|
+
}}"
|
|
600
|
+
@keydown="${(e: KeyboardEvent) => {
|
|
601
|
+
if ([" ", "Enter"].includes(e.key)) {
|
|
602
|
+
e.preventDefault();
|
|
603
|
+
this.tourType = TourType.Guided;
|
|
604
|
+
}
|
|
605
|
+
}}"
|
|
606
|
+
?selected="${this.tourType === TourType.Guided}"
|
|
251
607
|
>
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
608
|
+
<svg
|
|
609
|
+
slot="icon"
|
|
610
|
+
width="31"
|
|
611
|
+
height="31"
|
|
612
|
+
viewBox="0 0 31 31"
|
|
613
|
+
fill="none"
|
|
614
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
615
|
+
>
|
|
616
|
+
<path
|
|
617
|
+
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"
|
|
618
|
+
fill="${this.tourType === TourType.Guided
|
|
619
|
+
? "#ffffff"
|
|
620
|
+
: "#202020"}"
|
|
621
|
+
/>
|
|
622
|
+
</svg>
|
|
623
|
+
</tour-type-option>`
|
|
624
|
+
: ""}
|
|
625
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
626
|
+
? html`<tour-type-option
|
|
627
|
+
tourtype="self"
|
|
628
|
+
heading="Take a tour"
|
|
629
|
+
subtitle="on your own"
|
|
630
|
+
@click="${() => {
|
|
270
631
|
this.tourType = TourType.Self;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
viewBox="0 0 28 31"
|
|
280
|
-
fill="none"
|
|
281
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
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}"
|
|
282
640
|
>
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
641
|
+
<svg
|
|
642
|
+
slot="icon"
|
|
643
|
+
width="28"
|
|
644
|
+
height="31"
|
|
645
|
+
viewBox="0 0 28 31"
|
|
646
|
+
fill="none"
|
|
647
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
648
|
+
>
|
|
649
|
+
<path
|
|
650
|
+
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"
|
|
651
|
+
fill="${this.tourType === TourType.Self
|
|
652
|
+
? "#ffffff"
|
|
653
|
+
: "#202020"}"
|
|
654
|
+
/>
|
|
655
|
+
</svg>
|
|
656
|
+
</tour-type-option>`
|
|
657
|
+
: ""}
|
|
658
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
659
|
+
? html`<tour-type-option
|
|
660
|
+
tourtype="guided"
|
|
661
|
+
heading="Virtual tour"
|
|
662
|
+
subtitle="over video"
|
|
663
|
+
@click="${() => {
|
|
301
664
|
this.tourType = TourType.Virtual;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
viewBox="0 0 26 25"
|
|
311
|
-
fill="none"
|
|
312
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
665
|
+
}}"
|
|
666
|
+
@keydown="${(e: KeyboardEvent) => {
|
|
667
|
+
if ([" ", "Enter"].includes(e.key)) {
|
|
668
|
+
e.preventDefault();
|
|
669
|
+
this.tourType = TourType.Virtual;
|
|
670
|
+
}
|
|
671
|
+
}}"
|
|
672
|
+
?selected="${this.tourType === TourType.Virtual}"
|
|
313
673
|
>
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
674
|
+
<svg
|
|
675
|
+
slot="icon"
|
|
676
|
+
width="26"
|
|
677
|
+
height="25"
|
|
678
|
+
viewBox="0 0 26 25"
|
|
679
|
+
fill="none"
|
|
680
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
681
|
+
>
|
|
682
|
+
<path
|
|
683
|
+
d="M15.6 19.5V22.1L18.2 23.4V24.7H7.80004L7.79484 23.4052L10.4 22.1V19.5H1.28965C1.11894 19.4989 0.950113 19.4642 0.792841 19.3979C0.635568 19.3315 0.492943 19.2347 0.373141 19.1131C0.253339 18.9915 0.158715 18.8474 0.0946936 18.6892C0.0306718 18.5309 -0.00148955 18.3616 5.29696e-05 18.1909V1.3091C5.29696e-05 0.586299 0.591552 0 1.28965 0H24.7104C25.4228 0 26 0.583699 26 1.3091V18.1909C26 18.9137 25.4085 19.5 24.7104 19.5H15.6ZM2.60005 14.3V16.9H23.4V14.3H2.60005Z"
|
|
684
|
+
fill="${this.tourType === TourType.Virtual
|
|
685
|
+
? "#ffffff"
|
|
686
|
+
: "#202020"}"
|
|
687
|
+
/>
|
|
688
|
+
</svg>
|
|
689
|
+
</tour-type-option>`
|
|
690
|
+
: ""}
|
|
691
|
+
</div>`;
|
|
692
|
+
}
|
|
323
693
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
694
|
+
dateAndTimeMenu(horizontal = false): TemplateResult {
|
|
695
|
+
return html`<h2 id="dateAndTime">Date and Time</h2>
|
|
696
|
+
<div id="dateAndTimeMenu">
|
|
697
|
+
<div id="datePicker">
|
|
698
|
+
<date-picker
|
|
699
|
+
.availabilities=${this.availabilitiesGroupedByDay}
|
|
700
|
+
@change=${(e: Event) => {
|
|
701
|
+
if (e.target instanceof DatePicker) {
|
|
702
|
+
this.selectedDate = e.target.selectedDate;
|
|
703
|
+
}
|
|
704
|
+
}}
|
|
705
|
+
></date-picker>
|
|
706
|
+
<time-picker
|
|
707
|
+
?selecteddateexists=${!!this.selectedDate}
|
|
708
|
+
?horizontal=${horizontal}
|
|
709
|
+
.options=${this.selectedDate
|
|
710
|
+
? this.availabilitiesGroupedByDay[
|
|
711
|
+
format(this.selectedDate, "y-MM-dd")
|
|
712
|
+
]?.map((date) => format(date, "h:mmaaa"))
|
|
713
|
+
: []}
|
|
714
|
+
@change=${(e: Event) => {
|
|
715
|
+
if (e.target instanceof TimePicker) {
|
|
716
|
+
this.selectedTime = e.target.selectedTime;
|
|
717
|
+
}
|
|
718
|
+
}}
|
|
719
|
+
></time-picker>
|
|
331
720
|
</div>
|
|
721
|
+
</div>`;
|
|
722
|
+
}
|
|
332
723
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
724
|
+
closeButton(): TemplateResult {
|
|
725
|
+
return html`<button id="closeButton" @click=${this.onCloseClicked}>
|
|
726
|
+
<svg
|
|
727
|
+
width="19"
|
|
728
|
+
height="19"
|
|
729
|
+
viewBox="0 0 19 19"
|
|
730
|
+
fill="none"
|
|
731
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
732
|
+
>
|
|
733
|
+
<line
|
|
734
|
+
x1="0.986863"
|
|
735
|
+
y1="18.2746"
|
|
736
|
+
x2="18.2929"
|
|
737
|
+
y2="0.968593"
|
|
738
|
+
stroke="#202020"
|
|
739
|
+
stroke-width="2"
|
|
740
|
+
/>
|
|
741
|
+
<path
|
|
742
|
+
d="M1.01394 0.999997L18.0103 18.0243"
|
|
743
|
+
stroke="#202020"
|
|
744
|
+
stroke-width="2"
|
|
745
|
+
/>
|
|
746
|
+
</svg>
|
|
747
|
+
</button>`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
mobilePages = [
|
|
751
|
+
{
|
|
752
|
+
validate: this.validators.tourType,
|
|
753
|
+
nextButtonText: "Next",
|
|
754
|
+
nextButtonAction: (): void => {
|
|
755
|
+
this.mobilePageIndex++;
|
|
756
|
+
},
|
|
757
|
+
render: (): TemplateResult => {
|
|
758
|
+
return html`${this.tourTypeMenu()}`;
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
validate: this.validators.dateAndTime,
|
|
763
|
+
nextButtonText: "Next",
|
|
764
|
+
nextButtonAction: (): void => {
|
|
765
|
+
this.mobilePageIndex++;
|
|
766
|
+
},
|
|
767
|
+
render: (): TemplateResult => {
|
|
768
|
+
return html`${this.dateAndTimeMenu(true)}`;
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
validate: (): boolean =>
|
|
773
|
+
this.validators.leadInfo() && this.validators.unitType(),
|
|
774
|
+
nextButtonText: "Schedule tour",
|
|
775
|
+
nextButtonAction: this.submit,
|
|
776
|
+
render: (): TemplateResult => {
|
|
777
|
+
return html`${this.userInfoAndLayoutMenu()}`;
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
];
|
|
781
|
+
|
|
782
|
+
userInfoAndLayoutMenu(): TemplateResult {
|
|
783
|
+
return html`<h2 id="yourInformation">Your information</h2>
|
|
784
|
+
<div id="yourInformationMenu">
|
|
785
|
+
<input type="text" placeholder="Name" id="name" />
|
|
786
|
+
<input
|
|
787
|
+
type="email"
|
|
788
|
+
inputmode="email"
|
|
789
|
+
placeholder="Email"
|
|
790
|
+
id="email"
|
|
791
|
+
.value=${this.email}
|
|
792
|
+
@keyup=${this.onChangeEmail}
|
|
793
|
+
/>
|
|
794
|
+
<input
|
|
795
|
+
type="tel"
|
|
796
|
+
inputmode="tel"
|
|
797
|
+
placeholder="Phone"
|
|
798
|
+
id="phone"
|
|
799
|
+
maxlength="14"
|
|
800
|
+
.value=${this.phoneNumber}
|
|
801
|
+
@keydown=${this.handlePhoneKeydown}
|
|
802
|
+
@keyup=${this.handlePhoneKeyup}
|
|
803
|
+
@change=${() => this.requestUpdate()}
|
|
804
|
+
/>
|
|
805
|
+
</div>
|
|
806
|
+
<div id="unitChoiceMenu">
|
|
807
|
+
<h2 id="unitChoice">What would you like to view?</h2>
|
|
808
|
+
<div id="unitOptions">
|
|
809
|
+
<!-- TODO: display option.label, store option.value to send to backend -->
|
|
810
|
+
<me-select
|
|
811
|
+
id="unitType"
|
|
812
|
+
placeholder="Select type"
|
|
813
|
+
.options="${this.layoutOptions.map((o) => o.label)}"
|
|
814
|
+
defaultOption="Studio"
|
|
815
|
+
@change=${() => {
|
|
816
|
+
// to revalidate the form
|
|
817
|
+
this.requestUpdate();
|
|
818
|
+
}}
|
|
819
|
+
>Studio
|
|
820
|
+
</me-select>
|
|
356
821
|
</div>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
822
|
+
</div>`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
confirmationMessage(): TemplateResult {
|
|
826
|
+
if (!this.selectedDate) return html``;
|
|
827
|
+
// format example: "November 9th, 2022 at 11:00am"
|
|
828
|
+
const readableDateAndTime = `${format(
|
|
829
|
+
this.selectedDate,
|
|
830
|
+
"MMMM Lo, y"
|
|
831
|
+
)} at ${this.selectedTime}`;
|
|
832
|
+
return html`
|
|
833
|
+
<div id="confirmationMessage">
|
|
834
|
+
<svg
|
|
835
|
+
width="20"
|
|
836
|
+
height="20"
|
|
837
|
+
viewBox="0 0 20 20"
|
|
838
|
+
fill="none"
|
|
839
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
840
|
+
>
|
|
841
|
+
<path
|
|
842
|
+
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"
|
|
843
|
+
fill="#202020"
|
|
844
|
+
/>
|
|
845
|
+
</svg>
|
|
846
|
+
<p>
|
|
847
|
+
Thank you!
|
|
848
|
+
<br />
|
|
849
|
+
Your guided tour is scheduled for ${readableDateAndTime}.
|
|
850
|
+
</p>
|
|
851
|
+
<p>
|
|
852
|
+
Look for an email confirmation along with instructions and directions.
|
|
360
853
|
</p>
|
|
361
|
-
<
|
|
854
|
+
<p>You can make changes at any time, just reply to the email.</p>
|
|
362
855
|
</div>
|
|
363
856
|
`;
|
|
364
857
|
}
|
|
365
|
-
}
|
|
366
858
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
859
|
+
render(): TemplateResult {
|
|
860
|
+
if (!isMobile()) {
|
|
861
|
+
return html`
|
|
862
|
+
<div class="tour-scheduler">
|
|
863
|
+
<h1 id="scheduleATour">Schedule a tour</h1>
|
|
864
|
+
${this.closeButton()}
|
|
865
|
+
${this.tourIsBooked
|
|
866
|
+
? html`
|
|
867
|
+
<div class="tour-scheduler">${this.confirmationMessage()}</div>
|
|
868
|
+
`
|
|
869
|
+
: html`${this.tourTypeMenu()} ${this.dateAndTimeMenu()}
|
|
870
|
+
${this.userInfoAndLayoutMenu()}
|
|
871
|
+
<hr />
|
|
872
|
+
<p id="explanation">
|
|
873
|
+
We’ll send a confirmation and any follow-ups to your email
|
|
874
|
+
address.
|
|
875
|
+
</p>
|
|
876
|
+
<button
|
|
877
|
+
id="schedule"
|
|
878
|
+
?disabled=${!this.formIsValidForSubmission()}
|
|
879
|
+
@click=${this.submit}
|
|
880
|
+
>
|
|
881
|
+
Schedule tour
|
|
882
|
+
</button>`}
|
|
883
|
+
</div>
|
|
884
|
+
`;
|
|
885
|
+
} else {
|
|
886
|
+
const currentPage = this.mobilePages[this.mobilePageIndex];
|
|
887
|
+
return html`
|
|
888
|
+
<div class="tour-scheduler">
|
|
889
|
+
<div id="topControls">
|
|
890
|
+
<h1 id="scheduleATour">Schedule a tour</h1>
|
|
891
|
+
${this.closeButton()}
|
|
892
|
+
</div>
|
|
893
|
+
${this.tourIsBooked
|
|
894
|
+
? this.confirmationMessage()
|
|
895
|
+
: html`${currentPage.render()}
|
|
896
|
+
<button
|
|
897
|
+
id="next"
|
|
898
|
+
@click=${currentPage.nextButtonAction}
|
|
899
|
+
?disabled=${(() => {
|
|
900
|
+
return !currentPage.validate();
|
|
901
|
+
})()}
|
|
902
|
+
>
|
|
903
|
+
${currentPage.nextButtonText}
|
|
904
|
+
</button>`}
|
|
905
|
+
</div>
|
|
906
|
+
`;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
370
909
|
}
|
|
371
910
|
|
|
372
911
|
export enum TourType {
|
|
@@ -375,9 +914,19 @@ export enum TourType {
|
|
|
375
914
|
Virtual,
|
|
376
915
|
}
|
|
377
916
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
917
|
+
// TODO: we have three UI options and five TourAvailabilityResponseRankOrderedSupportedTourTypesEnum values
|
|
918
|
+
// how should they map?
|
|
919
|
+
const tourTypeMap = {
|
|
920
|
+
[TourType.Guided]:
|
|
921
|
+
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.WithAgent,
|
|
922
|
+
[TourType.Self]:
|
|
923
|
+
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.SelfGuided,
|
|
924
|
+
[TourType.Virtual]:
|
|
925
|
+
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.VirtualShowing,
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const tourTypeForSubmission = {
|
|
929
|
+
[TourType.Guided]: "escorted-tour",
|
|
930
|
+
[TourType.Self]: "self-guided-tour",
|
|
931
|
+
[TourType.Virtual]: "live-virtual-tour",
|
|
932
|
+
};
|