@meetelise/chat 1.15.0 → 1.15.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/package.json +5 -2
- package/public/dist/index.js +98 -210
- package/src/WebComponent/Launcher.ts +2 -8
- package/src/WebComponent/Scheduler/tour-scheduler.ts +80 -233
- package/src/WebComponent/Scheduler/tour-type-option.ts +5 -1
- package/src/WebComponent/me-chat.ts +2 -1
- package/src/createConversation.ts +1 -1
- package/src/getAvailabilities.ts +23 -64
|
@@ -50,7 +50,7 @@ export class Launcher extends LitElement {
|
|
|
50
50
|
onChatTapped: () => void = () => {
|
|
51
51
|
return;
|
|
52
52
|
};
|
|
53
|
-
@property(
|
|
53
|
+
@property()
|
|
54
54
|
launcherStyles: StyleInfo = {};
|
|
55
55
|
|
|
56
56
|
@state()
|
|
@@ -85,13 +85,7 @@ export class Launcher extends LitElement {
|
|
|
85
85
|
);
|
|
86
86
|
this.hasTextUsEnabled =
|
|
87
87
|
registeredPhoneNumbers.length > 0 && this.buildingId !== 4895;
|
|
88
|
-
|
|
89
|
-
const schedulingIsEnabled = await (async function putApiCallHere(
|
|
90
|
-
buildingId: number
|
|
91
|
-
) {
|
|
92
|
-
return !!buildingId;
|
|
93
|
-
})(this.buildingId);
|
|
94
|
-
if (this.buildingId === 3660 && schedulingIsEnabled) {
|
|
88
|
+
if (this.buildingId === 3660) {
|
|
95
89
|
this.hasSSTEnabled = true;
|
|
96
90
|
}
|
|
97
91
|
}
|
|
@@ -11,8 +11,7 @@ import "./time-picker.ts";
|
|
|
11
11
|
import "./me-select.ts";
|
|
12
12
|
import {
|
|
13
13
|
DateWithTimeZoneOffset,
|
|
14
|
-
|
|
15
|
-
getExistenceOfAvailabilitiesByTourType,
|
|
14
|
+
getAvailabilitiesGroupedByDayCached,
|
|
16
15
|
} from "../../getAvailabilities";
|
|
17
16
|
import { TourAvailabilityResponseRankOrderedSupportedTourTypesEnum } from "@meetelise/rest-sdk";
|
|
18
17
|
import { format } from "date-fns";
|
|
@@ -23,7 +22,6 @@ import { LabeledOption } from "../../fetchBuildingInfo";
|
|
|
23
22
|
import { isMobile } from "../../utils";
|
|
24
23
|
import axios from "axios";
|
|
25
24
|
import { mapValues } from "lodash";
|
|
26
|
-
import classnames from "classnames";
|
|
27
25
|
|
|
28
26
|
@customElement("tour-scheduler")
|
|
29
27
|
export class TourScheduler extends LitElement {
|
|
@@ -39,12 +37,6 @@ export class TourScheduler extends LitElement {
|
|
|
39
37
|
@state()
|
|
40
38
|
private tourType = TourType.Guided;
|
|
41
39
|
@state()
|
|
42
|
-
private shouldShowTourType = {
|
|
43
|
-
[TourType.Guided]: true,
|
|
44
|
-
[TourType.Self]: true,
|
|
45
|
-
[TourType.Virtual]: true,
|
|
46
|
-
};
|
|
47
|
-
@state()
|
|
48
40
|
private email = "";
|
|
49
41
|
@state()
|
|
50
42
|
private phoneNumber = "";
|
|
@@ -53,8 +45,6 @@ export class TourScheduler extends LitElement {
|
|
|
53
45
|
[day: string]: DateWithTimeZoneOffset[];
|
|
54
46
|
} = {};
|
|
55
47
|
@state()
|
|
56
|
-
private waitingForAvailabilities = true;
|
|
57
|
-
@state()
|
|
58
48
|
private selectedDate?: Date;
|
|
59
49
|
@state()
|
|
60
50
|
private selectedTime?: DateWithTimeZoneOffset | null;
|
|
@@ -63,38 +53,19 @@ export class TourScheduler extends LitElement {
|
|
|
63
53
|
@state()
|
|
64
54
|
private tourIsBooked = false;
|
|
65
55
|
|
|
66
|
-
@query("
|
|
67
|
-
|
|
68
|
-
@query("
|
|
69
|
-
lastNameInput!: HTMLInputElement;
|
|
70
|
-
@query(".inputContainer#email input")
|
|
56
|
+
@query("input#name")
|
|
57
|
+
nameInput!: HTMLInputElement;
|
|
58
|
+
@query("input#email")
|
|
71
59
|
emailInput!: HTMLInputElement;
|
|
72
|
-
@query("
|
|
60
|
+
@query("input#phone")
|
|
73
61
|
phoneInput!: HTMLInputElement;
|
|
74
62
|
@query("me-select#unitType")
|
|
75
63
|
unitTypeSelect!: MESelect;
|
|
76
64
|
|
|
77
65
|
firstUpdated = async (): Promise<void> => {
|
|
78
|
-
this.availabilitiesGroupedByDay = await
|
|
66
|
+
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDayCached(
|
|
79
67
|
tourTypeMap[this.tourType]
|
|
80
68
|
);
|
|
81
|
-
this.waitingForAvailabilities = false;
|
|
82
|
-
|
|
83
|
-
// Show a tour type only if it is supported by the building and has
|
|
84
|
-
// time slots available.
|
|
85
|
-
const availabilitiesExistForTourType =
|
|
86
|
-
await getExistenceOfAvailabilitiesByTourType();
|
|
87
|
-
this.shouldShowTourType = {
|
|
88
|
-
[TourType.Guided]:
|
|
89
|
-
this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT") &&
|
|
90
|
-
availabilitiesExistForTourType[TourType.Guided],
|
|
91
|
-
[TourType.Self]:
|
|
92
|
-
this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED") &&
|
|
93
|
-
availabilitiesExistForTourType[TourType.Self],
|
|
94
|
-
[TourType.Virtual]:
|
|
95
|
-
this.tourTypeOptions.map((o) => o.value).includes("VIRTUAL_SHOWING") &&
|
|
96
|
-
availabilitiesExistForTourType[TourType.Virtual],
|
|
97
|
-
};
|
|
98
69
|
};
|
|
99
70
|
|
|
100
71
|
protected willUpdate = async (
|
|
@@ -103,9 +74,8 @@ export class TourScheduler extends LitElement {
|
|
|
103
74
|
| Map<PropertyKey, unknown>
|
|
104
75
|
): Promise<void> => {
|
|
105
76
|
if (_changedProperties.has("tourType")) {
|
|
106
|
-
this.availabilitiesGroupedByDay =
|
|
107
|
-
tourTypeMap[this.tourType]
|
|
108
|
-
);
|
|
77
|
+
this.availabilitiesGroupedByDay =
|
|
78
|
+
await getAvailabilitiesGroupedByDayCached(tourTypeMap[this.tourType]);
|
|
109
79
|
}
|
|
110
80
|
};
|
|
111
81
|
|
|
@@ -265,7 +235,7 @@ export class TourScheduler extends LitElement {
|
|
|
265
235
|
dateAndTime: (): boolean => !!this.selectedDate && !!this.selectedTime,
|
|
266
236
|
leadInfo: (): boolean => {
|
|
267
237
|
return (
|
|
268
|
-
|
|
238
|
+
!!this.nameInput?.value &&
|
|
269
239
|
this.emailInput?.value.includes("@") &&
|
|
270
240
|
// TODO: deleting phone number doesn't cause validation to fail, at least on mobile
|
|
271
241
|
!!this.phoneNumber &&
|
|
@@ -302,8 +272,9 @@ export class TourScheduler extends LitElement {
|
|
|
302
272
|
email_address: this.email,
|
|
303
273
|
phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
|
|
304
274
|
building_id: this.buildingId,
|
|
305
|
-
|
|
306
|
-
|
|
275
|
+
// 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
|
|
276
|
+
first_name: this.nameInput.value.split(" ")[0],
|
|
277
|
+
last_name: this.nameInput.value.split(" ").slice(1).join(" "),
|
|
307
278
|
tour_type: tourTypeForSubmission[this.tourType],
|
|
308
279
|
tour_time: `${this.selectedTime.datetime}${this.selectedTime.offset}`, // e.g., "2022-06-27T09:00:00-07:00"
|
|
309
280
|
};
|
|
@@ -431,7 +402,7 @@ export class TourScheduler extends LitElement {
|
|
|
431
402
|
gap: 12px;
|
|
432
403
|
}
|
|
433
404
|
|
|
434
|
-
#yourInformationMenu input {
|
|
405
|
+
#yourInformationMenu > input {
|
|
435
406
|
width: 305px;
|
|
436
407
|
height: 49px;
|
|
437
408
|
border: 1px solid #83818e;
|
|
@@ -443,7 +414,7 @@ export class TourScheduler extends LitElement {
|
|
|
443
414
|
color: #202020;
|
|
444
415
|
}
|
|
445
416
|
|
|
446
|
-
#yourInformationMenu input::placeholder {
|
|
417
|
+
#yourInformationMenu > input::placeholder {
|
|
447
418
|
color: #202020;
|
|
448
419
|
}
|
|
449
420
|
|
|
@@ -503,15 +474,6 @@ export class TourScheduler extends LitElement {
|
|
|
503
474
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
|
|
504
475
|
}
|
|
505
476
|
|
|
506
|
-
button#schedule:not(:disabled):hover {
|
|
507
|
-
opacity: 0.7;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
button#schedule:not(:disabled):active {
|
|
511
|
-
box-shadow: none;
|
|
512
|
-
opacity: 1;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
477
|
button#schedule:disabled {
|
|
516
478
|
background: #e7e7e7;
|
|
517
479
|
box-shadow: none;
|
|
@@ -526,81 +488,6 @@ export class TourScheduler extends LitElement {
|
|
|
526
488
|
font-size: 18px;
|
|
527
489
|
}
|
|
528
490
|
|
|
529
|
-
/* Loading styles: pulsing gray overlay on all the form elements */
|
|
530
|
-
|
|
531
|
-
@keyframes spin {
|
|
532
|
-
0% {
|
|
533
|
-
transform: none;
|
|
534
|
-
}
|
|
535
|
-
50% {
|
|
536
|
-
transform: rotateZ(180deg);
|
|
537
|
-
}
|
|
538
|
-
100% {
|
|
539
|
-
transform: rotateZ(360deg);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
svg#loadingIcon {
|
|
544
|
-
animation: spin 2s infinite linear;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.tour-scheduler.loading #scheduleATour {
|
|
548
|
-
display: flex;
|
|
549
|
-
gap: 10px;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
@keyframes loadingPulse {
|
|
553
|
-
0% {
|
|
554
|
-
background-color: #e7e7e7;
|
|
555
|
-
}
|
|
556
|
-
50% {
|
|
557
|
-
background-color: white;
|
|
558
|
-
}
|
|
559
|
-
100% {
|
|
560
|
-
background-color: #e7e7e7;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
tour-type-option,
|
|
565
|
-
date-picker,
|
|
566
|
-
#yourInformationMenu .inputContainer {
|
|
567
|
-
position: relative;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
.tour-scheduler.loading
|
|
571
|
-
:is(tour-type-option, date-picker, #yourInformationMenu
|
|
572
|
-
.inputContainer)::after {
|
|
573
|
-
content: "";
|
|
574
|
-
position: absolute;
|
|
575
|
-
top: 0;
|
|
576
|
-
left: 0;
|
|
577
|
-
height: 100%;
|
|
578
|
-
z-index: 1;
|
|
579
|
-
animation: loadingPulse 2s infinite;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
.tour-scheduler.loading tour-type-option::after {
|
|
583
|
-
border-radius: 10px;
|
|
584
|
-
width: 200px;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
.tour-scheduler.loading date-picker::after {
|
|
588
|
-
border-radius: 10px;
|
|
589
|
-
width: 205px;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
.tour-scheduler.loading #yourInformationMenu .inputContainer::after {
|
|
593
|
-
width: 100%;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
.tour-scheduler.loading #yourInformationMenu .inputContainer input {
|
|
597
|
-
visibility: hidden;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
.tour-scheduler.loading time-picker {
|
|
601
|
-
display: none;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
491
|
@media (max-width: 767px) {
|
|
605
492
|
/* TODO: separate styles into general, desktop-specific, and mobile-specific.
|
|
606
493
|
basically everything I have "unset" or "initial" on should become desktop-specific. the grid layout is only for desktop.
|
|
@@ -717,8 +604,9 @@ export class TourScheduler extends LitElement {
|
|
|
717
604
|
tourTypeMenu(): TemplateResult {
|
|
718
605
|
return html`<h2 id="tourType">Tour Type</h2>
|
|
719
606
|
<div id="tourTypeMenu">
|
|
720
|
-
${this.
|
|
607
|
+
${this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT")
|
|
721
608
|
? html` <tour-type-option
|
|
609
|
+
tourtype="guided"
|
|
722
610
|
heading="Guided tour"
|
|
723
611
|
subtitle="with an agent"
|
|
724
612
|
@click="${() => {
|
|
@@ -749,8 +637,9 @@ export class TourScheduler extends LitElement {
|
|
|
749
637
|
</svg>
|
|
750
638
|
</tour-type-option>`
|
|
751
639
|
: ""}
|
|
752
|
-
${this.
|
|
640
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
753
641
|
? html`<tour-type-option
|
|
642
|
+
tourtype="self"
|
|
754
643
|
heading="Take a tour"
|
|
755
644
|
subtitle="on your own"
|
|
756
645
|
@click="${() => {
|
|
@@ -781,8 +670,9 @@ export class TourScheduler extends LitElement {
|
|
|
781
670
|
</svg>
|
|
782
671
|
</tour-type-option>`
|
|
783
672
|
: ""}
|
|
784
|
-
${this.
|
|
673
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
785
674
|
? html`<tour-type-option
|
|
675
|
+
tourtype="guided"
|
|
786
676
|
heading="Virtual tour"
|
|
787
677
|
subtitle="over video"
|
|
788
678
|
@click="${() => {
|
|
@@ -851,8 +741,7 @@ export class TourScheduler extends LitElement {
|
|
|
851
741
|
.map((date) => format(new Date(date.datetime), "h:mmaaa"))
|
|
852
742
|
.indexOf(e.target.selectedTime)
|
|
853
743
|
: null;
|
|
854
|
-
this.selectedTime =
|
|
855
|
-
index !== null ? daysAvailabilities[index] : null;
|
|
744
|
+
this.selectedTime = index ? daysAvailabilities[index] : null; // this.selectedAvailabilityString ?
|
|
856
745
|
}
|
|
857
746
|
}}
|
|
858
747
|
></time-picker>
|
|
@@ -920,62 +809,58 @@ export class TourScheduler extends LitElement {
|
|
|
920
809
|
userInfoAndLayoutMenu(): TemplateResult {
|
|
921
810
|
return html`<h2 id="yourInformation">Your information</h2>
|
|
922
811
|
<div id="yourInformationMenu">
|
|
923
|
-
<
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
inputmode="tel"
|
|
956
|
-
placeholder="Phone"
|
|
957
|
-
name="phone"
|
|
958
|
-
autocomplete="tel-national"
|
|
959
|
-
maxlength="14"
|
|
960
|
-
.value=${this.phoneNumber}
|
|
961
|
-
@keydown=${this.handlePhoneKeydown}
|
|
962
|
-
@keyup=${this.handlePhoneKeyup}
|
|
963
|
-
@input=${(e: Event) => {
|
|
964
|
-
if (!e.target) {
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
this.phoneNumber = formatToPhone(
|
|
968
|
-
(e.target as HTMLInputElement).value
|
|
969
|
-
);
|
|
970
|
-
}}
|
|
971
|
-
/>
|
|
972
|
-
</div>
|
|
812
|
+
<input
|
|
813
|
+
type="text"
|
|
814
|
+
placeholder="Name"
|
|
815
|
+
id="name"
|
|
816
|
+
@input=${() => this.requestUpdate()}
|
|
817
|
+
/>
|
|
818
|
+
<input
|
|
819
|
+
type="email"
|
|
820
|
+
inputmode="email"
|
|
821
|
+
placeholder="Email"
|
|
822
|
+
id="email"
|
|
823
|
+
.value=${this.email}
|
|
824
|
+
@input=${this.onChangeEmail}
|
|
825
|
+
/>
|
|
826
|
+
<input
|
|
827
|
+
type="tel"
|
|
828
|
+
inputmode="tel"
|
|
829
|
+
placeholder="Phone"
|
|
830
|
+
id="phone"
|
|
831
|
+
maxlength="14"
|
|
832
|
+
.value=${this.phoneNumber}
|
|
833
|
+
@keydown=${this.handlePhoneKeydown}
|
|
834
|
+
@keyup=${this.handlePhoneKeyup}
|
|
835
|
+
@input=${(e: Event) => {
|
|
836
|
+
if (!e.target) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
this.phoneNumber = formatToPhone(
|
|
840
|
+
(e.target as HTMLInputElement).value
|
|
841
|
+
);
|
|
842
|
+
}}
|
|
843
|
+
/>
|
|
973
844
|
</div>
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
845
|
+
|
|
846
|
+
${this.layoutOptions.length > 0
|
|
847
|
+
? html`<div id="unitChoiceMenu">
|
|
848
|
+
<h2 id="unitChoice">What would you like to view?</h2>
|
|
849
|
+
<div id="unitOptions">
|
|
850
|
+
<me-select
|
|
851
|
+
id="unitType"
|
|
852
|
+
placeholder="Select type"
|
|
853
|
+
.options="${this.layoutOptions.map((o) => o.label)}"
|
|
854
|
+
defaultOption="Studio"
|
|
855
|
+
@change=${() => {
|
|
856
|
+
// to revalidate the form
|
|
857
|
+
this.requestUpdate();
|
|
858
|
+
}}
|
|
859
|
+
>Studio
|
|
860
|
+
</me-select>
|
|
861
|
+
</div>
|
|
862
|
+
</div>`
|
|
863
|
+
: ""} `;
|
|
979
864
|
}
|
|
980
865
|
|
|
981
866
|
confirmationMessage(): TemplateResult {
|
|
@@ -1002,13 +887,7 @@ export class TourScheduler extends LitElement {
|
|
|
1002
887
|
<p>
|
|
1003
888
|
Thank you!
|
|
1004
889
|
<br />
|
|
1005
|
-
Your
|
|
1006
|
-
${{
|
|
1007
|
-
[TourType.Guided]: "guided",
|
|
1008
|
-
[TourType.Self]: "self-guided",
|
|
1009
|
-
[TourType.Virtual]: "virtual",
|
|
1010
|
-
}[this.tourType]}
|
|
1011
|
-
tour is scheduled for ${readableDateAndTime}.
|
|
890
|
+
Your guided tour is scheduled for ${readableDateAndTime}.
|
|
1012
891
|
</p>
|
|
1013
892
|
<p>
|
|
1014
893
|
Look for an email confirmation along with instructions and directions.
|
|
@@ -1018,35 +897,11 @@ export class TourScheduler extends LitElement {
|
|
|
1018
897
|
`;
|
|
1019
898
|
}
|
|
1020
899
|
|
|
1021
|
-
loadingIcon(): TemplateResult {
|
|
1022
|
-
return html`<svg
|
|
1023
|
-
id="loadingIcon"
|
|
1024
|
-
width="21"
|
|
1025
|
-
height="21"
|
|
1026
|
-
viewBox="0 0 21 21"
|
|
1027
|
-
fill="none"
|
|
1028
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
1029
|
-
>
|
|
1030
|
-
<path
|
|
1031
|
-
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"
|
|
1032
|
-
fill="#1E1E1E"
|
|
1033
|
-
/>
|
|
1034
|
-
</svg>`;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
900
|
render(): TemplateResult {
|
|
1038
901
|
if (!isMobile()) {
|
|
1039
902
|
return html`
|
|
1040
|
-
<div
|
|
1041
|
-
|
|
1042
|
-
loading: this.waitingForAvailabilities,
|
|
1043
|
-
})}"
|
|
1044
|
-
>
|
|
1045
|
-
<h1 id="scheduleATour">
|
|
1046
|
-
${this.waitingForAvailabilities
|
|
1047
|
-
? html`${this.loadingIcon()} Searching availabilities...`
|
|
1048
|
-
: "Schedule a tour"}
|
|
1049
|
-
</h1>
|
|
903
|
+
<div class="tour-scheduler">
|
|
904
|
+
<h1 id="scheduleATour">Schedule a tour</h1>
|
|
1050
905
|
${this.closeButton()}
|
|
1051
906
|
${this.tourIsBooked
|
|
1052
907
|
? html`
|
|
@@ -1071,17 +926,9 @@ export class TourScheduler extends LitElement {
|
|
|
1071
926
|
} else {
|
|
1072
927
|
const currentPage = this.mobilePages[this.mobilePageIndex];
|
|
1073
928
|
return html`
|
|
1074
|
-
<div
|
|
1075
|
-
class="${classnames("tour-scheduler", {
|
|
1076
|
-
loading: this.waitingForAvailabilities,
|
|
1077
|
-
})}"
|
|
1078
|
-
>
|
|
929
|
+
<div class="tour-scheduler">
|
|
1079
930
|
<div id="topControls">
|
|
1080
|
-
<h1 id="scheduleATour">
|
|
1081
|
-
${this.waitingForAvailabilities
|
|
1082
|
-
? html`${this.loadingIcon()} Searching availabilities...`
|
|
1083
|
-
: "Schedule a tour"}
|
|
1084
|
-
</h1>
|
|
931
|
+
<h1 id="scheduleATour">Schedule a tour</h1>
|
|
1085
932
|
${this.closeButton()}
|
|
1086
933
|
</div>
|
|
1087
934
|
${this.tourIsBooked
|
|
@@ -1091,9 +938,7 @@ export class TourScheduler extends LitElement {
|
|
|
1091
938
|
id="next"
|
|
1092
939
|
@click=${currentPage.nextButtonAction}
|
|
1093
940
|
?disabled=${(() => {
|
|
1094
|
-
return (
|
|
1095
|
-
!currentPage.validate() || this.waitingForAvailabilities
|
|
1096
|
-
);
|
|
941
|
+
return !currentPage.validate();
|
|
1097
942
|
})()}
|
|
1098
943
|
>
|
|
1099
944
|
${currentPage.nextButtonText}
|
|
@@ -1110,6 +955,8 @@ export enum TourType {
|
|
|
1110
955
|
Virtual,
|
|
1111
956
|
}
|
|
1112
957
|
|
|
958
|
+
// TODO: we have three UI options and five TourAvailabilityResponseRankOrderedSupportedTourTypesEnum values
|
|
959
|
+
// how should they map?
|
|
1113
960
|
const tourTypeMap = {
|
|
1114
961
|
[TourType.Guided]:
|
|
1115
962
|
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.WithAgent,
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { css, html, LitElement, TemplateResult } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
import { TourType } from "./tour-scheduler";
|
|
3
4
|
|
|
4
5
|
@customElement("tour-type-option")
|
|
5
6
|
export class TourTypeOption extends LitElement {
|
|
7
|
+
@property({ type: String })
|
|
8
|
+
tourtype = "";
|
|
6
9
|
@property({ type: String })
|
|
7
10
|
heading = "";
|
|
8
11
|
@property({ type: String })
|
|
9
12
|
subtitle = "";
|
|
10
13
|
@property({ type: Boolean })
|
|
11
14
|
selected = false;
|
|
15
|
+
|
|
12
16
|
@property({ attribute: false })
|
|
13
|
-
onClick?: () => void;
|
|
17
|
+
onClick?: (tourType: TourType) => void;
|
|
14
18
|
|
|
15
19
|
static styles = [
|
|
16
20
|
css`
|
|
@@ -263,7 +263,7 @@ export class MEChat extends LitElement {
|
|
|
263
263
|
};
|
|
264
264
|
|
|
265
265
|
render(): TemplateResult {
|
|
266
|
-
if (this.building?.
|
|
266
|
+
if (this.building?.id === 4930) {
|
|
267
267
|
return html``;
|
|
268
268
|
}
|
|
269
269
|
installLauncher();
|
|
@@ -321,6 +321,7 @@ declare global {
|
|
|
321
321
|
interface HTMLElementTagNameMap {
|
|
322
322
|
"me-chat": MEChat;
|
|
323
323
|
}
|
|
324
|
+
|
|
324
325
|
interface Window {
|
|
325
326
|
RCTPCampaign?: { CampaignDetails: { Source: string } };
|
|
326
327
|
}
|
|
@@ -46,7 +46,7 @@ export default function createConversation(
|
|
|
46
46
|
building.avatarType === "image" && building.avatarSrc
|
|
47
47
|
? avatarSrc
|
|
48
48
|
: defaultAvatarUrl,
|
|
49
|
-
// uncomment
|
|
49
|
+
// uncomment this to test changes to the default avatar if your test building has its own avatar
|
|
50
50
|
// avatarUrl: defaultAvatarUrl,
|
|
51
51
|
};
|
|
52
52
|
return conversation;
|
package/src/getAvailabilities.ts
CHANGED
|
@@ -10,49 +10,31 @@ import {
|
|
|
10
10
|
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum,
|
|
11
11
|
} from "@meetelise/rest-sdk";
|
|
12
12
|
import groupBy from "lodash/groupBy";
|
|
13
|
-
import { TourType } from "./WebComponent/Scheduler/tour-scheduler";
|
|
14
13
|
|
|
15
14
|
const availabilitiesCache: {
|
|
16
|
-
buildingId
|
|
17
|
-
|
|
18
|
-
} = { buildingId: null, availabilities: {} };
|
|
15
|
+
[buildingId: number]: TourAvailabilityResponse;
|
|
16
|
+
} = {};
|
|
19
17
|
|
|
20
|
-
/**
|
|
21
|
-
* Returns the raw availabilities.
|
|
22
|
-
*
|
|
23
|
-
* If no `buildingId` is provided, it will use a cached buildingId from the previous call.
|
|
24
|
-
* If there is none cached, it will throw an error.
|
|
25
|
-
*/
|
|
26
18
|
export const getRawAvailabilities = async (
|
|
27
|
-
buildingId
|
|
19
|
+
buildingId: number
|
|
28
20
|
): Promise<TourAvailabilityResponse> => {
|
|
29
|
-
if (buildingId) {
|
|
30
|
-
availabilitiesCache
|
|
31
|
-
}
|
|
32
|
-
const buildingIdToUse = availabilitiesCache.buildingId;
|
|
33
|
-
if (!buildingIdToUse) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"No buildingId was provided to getRawAvailabilities and there is no buildingId cached."
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
const availabilities = availabilitiesCache.availabilities;
|
|
39
|
-
if (availabilities[buildingIdToUse]) {
|
|
40
|
-
return availabilities[buildingIdToUse];
|
|
21
|
+
if (availabilitiesCache[buildingId]) {
|
|
22
|
+
return availabilitiesCache[buildingId];
|
|
41
23
|
}
|
|
42
24
|
const startTime = startOfToday();
|
|
43
25
|
const endTime = formatISO(endOfDay(addDays(startTime, 30)));
|
|
44
|
-
const url = `https://app.meetelise.com/api/pub/v1/buildings/${
|
|
26
|
+
const url = `https://app.meetelise.com/api/pub/v1/buildings/${buildingId}/tour/availabilities?startTime=${formatISO(
|
|
45
27
|
startTime
|
|
46
28
|
)}&endTime=${endTime}`;
|
|
47
29
|
const result = await axios.get<TourAvailabilityResponse>(url);
|
|
48
|
-
availabilitiesCache
|
|
30
|
+
availabilitiesCache[buildingId] = result.data;
|
|
49
31
|
// The endpoint INCORRECTLY states that these are returned as dates. They are, in fact, strings.
|
|
50
32
|
return result.data;
|
|
51
33
|
};
|
|
52
34
|
|
|
53
35
|
export const getAvailabilitiesForTourType = async (
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
buildingId: number,
|
|
37
|
+
tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum
|
|
56
38
|
): Promise<{
|
|
57
39
|
/**
|
|
58
40
|
*
|
|
@@ -86,46 +68,13 @@ export interface DateWithTimeZoneOffset {
|
|
|
86
68
|
offset: string;
|
|
87
69
|
}
|
|
88
70
|
|
|
89
|
-
/**
|
|
90
|
-
* Returns an object that reveals whether each tour type supported by
|
|
91
|
-
* `tour-scheduler` has availabilities (time slots available for scheduling) in
|
|
92
|
-
* the time window of interest.
|
|
93
|
-
*
|
|
94
|
-
* Note that the existence of current availabilities is distinct from the
|
|
95
|
-
* question of whether the community supports the tour type at all. The first
|
|
96
|
-
* implies the second but not vice versa.
|
|
97
|
-
*/
|
|
98
|
-
export const getExistenceOfAvailabilitiesByTourType = async (): Promise<{
|
|
99
|
-
[TourType.Guided]: boolean;
|
|
100
|
-
[TourType.Self]: boolean;
|
|
101
|
-
[TourType.Virtual]: boolean;
|
|
102
|
-
}> => {
|
|
103
|
-
return {
|
|
104
|
-
[TourType.Guided]: !!(
|
|
105
|
-
await getAvailabilitiesForTourType(
|
|
106
|
-
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.WithAgent
|
|
107
|
-
)
|
|
108
|
-
)?.availableTourStartTimes?.length,
|
|
109
|
-
[TourType.Self]: !!(
|
|
110
|
-
await getAvailabilitiesForTourType(
|
|
111
|
-
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.SelfGuided
|
|
112
|
-
)
|
|
113
|
-
)?.availableTourStartTimes?.length,
|
|
114
|
-
[TourType.Virtual]: !!(
|
|
115
|
-
await getAvailabilitiesForTourType(
|
|
116
|
-
TourAvailabilityResponseRankOrderedSupportedTourTypesEnum.VirtualShowing
|
|
117
|
-
)
|
|
118
|
-
)?.availableTourStartTimes?.length,
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
|
|
122
71
|
export const getAvailabilitiesGroupedByDay = async (
|
|
123
|
-
|
|
124
|
-
|
|
72
|
+
buildingId: number,
|
|
73
|
+
tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum
|
|
125
74
|
): Promise<{ [day: string]: DateWithTimeZoneOffset[] }> => {
|
|
126
75
|
const availabilitiesForTourTypeRaw = await getAvailabilitiesForTourType(
|
|
127
|
-
|
|
128
|
-
|
|
76
|
+
buildingId,
|
|
77
|
+
tourType
|
|
129
78
|
);
|
|
130
79
|
if (!availabilitiesForTourTypeRaw) {
|
|
131
80
|
return {};
|
|
@@ -148,6 +97,16 @@ export const getAvailabilitiesGroupedByDay = async (
|
|
|
148
97
|
);
|
|
149
98
|
};
|
|
150
99
|
|
|
100
|
+
// TODO: if cache is empty, observe it and wait for it to be filled in.
|
|
101
|
+
// TODO: alternative to this: cache the building id when getRawAvailabilities is called. Then I can call the normal methods.
|
|
102
|
+
export const getAvailabilitiesGroupedByDayCached = async (
|
|
103
|
+
tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum
|
|
104
|
+
): Promise<{ [day: string]: DateWithTimeZoneOffset[] }> =>
|
|
105
|
+
getAvailabilitiesGroupedByDay(
|
|
106
|
+
Object.keys(availabilitiesCache).map(Number)[0],
|
|
107
|
+
tourType
|
|
108
|
+
);
|
|
109
|
+
|
|
151
110
|
/**
|
|
152
111
|
* Takes an ISO 8601 date string with time zone offset and returns
|
|
153
112
|
* an object of our custom type DateWithTimeZoneOffset.
|