@meetelise/chat 1.13.7 → 1.13.10
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.
|
@@ -11,7 +11,7 @@ import "./time-picker.ts";
|
|
|
11
11
|
import "./me-select.ts";
|
|
12
12
|
import {
|
|
13
13
|
DateWithTimeZoneOffset,
|
|
14
|
-
|
|
14
|
+
getAvailabilitiesGroupedByDay,
|
|
15
15
|
} from "../../getAvailabilities";
|
|
16
16
|
import { TourAvailabilityResponseRankOrderedSupportedTourTypesEnum } from "@meetelise/rest-sdk";
|
|
17
17
|
import { format } from "date-fns";
|
|
@@ -63,7 +63,7 @@ export class TourScheduler extends LitElement {
|
|
|
63
63
|
unitTypeSelect!: MESelect;
|
|
64
64
|
|
|
65
65
|
firstUpdated = async (): Promise<void> => {
|
|
66
|
-
this.availabilitiesGroupedByDay = await
|
|
66
|
+
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
|
|
67
67
|
tourTypeMap[this.tourType]
|
|
68
68
|
);
|
|
69
69
|
};
|
|
@@ -74,8 +74,9 @@ export class TourScheduler extends LitElement {
|
|
|
74
74
|
| Map<PropertyKey, unknown>
|
|
75
75
|
): Promise<void> => {
|
|
76
76
|
if (_changedProperties.has("tourType")) {
|
|
77
|
-
this.availabilitiesGroupedByDay =
|
|
78
|
-
|
|
77
|
+
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
|
|
78
|
+
tourTypeMap[this.tourType]
|
|
79
|
+
);
|
|
79
80
|
}
|
|
80
81
|
};
|
|
81
82
|
|
|
@@ -185,6 +186,9 @@ export class TourScheduler extends LitElement {
|
|
|
185
186
|
};
|
|
186
187
|
|
|
187
188
|
handlePhoneKeyup = (e: KeyboardEvent): void => {
|
|
189
|
+
if (!e.key) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
188
192
|
// 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".
|
|
189
193
|
// (We never want the cursor to be before a punctuation mark because the next digit typed will appear after the punctuation mark, not before.)
|
|
190
194
|
// If we don't do this, the cursor automatically goes to the end when we set `this.phoneNumber`.
|
|
@@ -230,12 +234,15 @@ export class TourScheduler extends LitElement {
|
|
|
230
234
|
validators = {
|
|
231
235
|
tourType: (): boolean => !isNaN(this.tourType),
|
|
232
236
|
dateAndTime: (): boolean => !!this.selectedDate && !!this.selectedTime,
|
|
233
|
-
leadInfo: (): boolean =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
leadInfo: (): boolean => {
|
|
238
|
+
return (
|
|
239
|
+
!!this.nameInput?.value &&
|
|
240
|
+
this.emailInput?.value.includes("@") &&
|
|
241
|
+
// TODO: deleting phone number doesn't cause validation to fail, at least on mobile
|
|
242
|
+
!!this.phoneNumber &&
|
|
243
|
+
this.phoneNumber.length === 14
|
|
244
|
+
);
|
|
245
|
+
},
|
|
239
246
|
};
|
|
240
247
|
|
|
241
248
|
formIsValidForSubmission = (): boolean => {
|
|
@@ -266,7 +273,7 @@ export class TourScheduler extends LitElement {
|
|
|
266
273
|
email_address: this.email,
|
|
267
274
|
phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
|
|
268
275
|
building_id: this.buildingId,
|
|
269
|
-
// TODO: this is very bad dumb name-splitting logic!
|
|
276
|
+
// TODO: this is very bad dumb name-splitting logic! Instead, split the name input into first and last name.
|
|
270
277
|
first_name: this.nameInput.value.split(" ")[0],
|
|
271
278
|
last_name: this.nameInput.value.split(" ").slice(1).join(" "),
|
|
272
279
|
tour_type: tourTypeForSubmission[this.tourType],
|
|
@@ -600,7 +607,6 @@ export class TourScheduler extends LitElement {
|
|
|
600
607
|
<div id="tourTypeMenu">
|
|
601
608
|
${this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT")
|
|
602
609
|
? html` <tour-type-option
|
|
603
|
-
tourtype="guided"
|
|
604
610
|
heading="Guided tour"
|
|
605
611
|
subtitle="with an agent"
|
|
606
612
|
@click="${() => {
|
|
@@ -633,7 +639,6 @@ export class TourScheduler extends LitElement {
|
|
|
633
639
|
: ""}
|
|
634
640
|
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
635
641
|
? html`<tour-type-option
|
|
636
|
-
tourtype="self"
|
|
637
642
|
heading="Take a tour"
|
|
638
643
|
subtitle="on your own"
|
|
639
644
|
@click="${() => {
|
|
@@ -664,9 +669,8 @@ export class TourScheduler extends LitElement {
|
|
|
664
669
|
</svg>
|
|
665
670
|
</tour-type-option>`
|
|
666
671
|
: ""}
|
|
667
|
-
${this.tourTypeOptions.map((o) => o.value).includes("
|
|
672
|
+
${this.tourTypeOptions.map((o) => o.value).includes("VIRTUAL_SHOWING")
|
|
668
673
|
? html`<tour-type-option
|
|
669
|
-
tourtype="guided"
|
|
670
674
|
heading="Virtual tour"
|
|
671
675
|
subtitle="over video"
|
|
672
676
|
@click="${() => {
|
|
@@ -803,14 +807,19 @@ export class TourScheduler extends LitElement {
|
|
|
803
807
|
userInfoAndLayoutMenu(): TemplateResult {
|
|
804
808
|
return html`<h2 id="yourInformation">Your information</h2>
|
|
805
809
|
<div id="yourInformationMenu">
|
|
806
|
-
<input
|
|
810
|
+
<input
|
|
811
|
+
type="text"
|
|
812
|
+
placeholder="Name"
|
|
813
|
+
id="name"
|
|
814
|
+
@input=${() => this.requestUpdate()}
|
|
815
|
+
/>
|
|
807
816
|
<input
|
|
808
817
|
type="email"
|
|
809
818
|
inputmode="email"
|
|
810
819
|
placeholder="Email"
|
|
811
820
|
id="email"
|
|
812
821
|
.value=${this.email}
|
|
813
|
-
@
|
|
822
|
+
@input=${this.onChangeEmail}
|
|
814
823
|
/>
|
|
815
824
|
<input
|
|
816
825
|
type="tel"
|
|
@@ -821,28 +830,21 @@ export class TourScheduler extends LitElement {
|
|
|
821
830
|
.value=${this.phoneNumber}
|
|
822
831
|
@keydown=${this.handlePhoneKeydown}
|
|
823
832
|
@keyup=${this.handlePhoneKeyup}
|
|
824
|
-
@
|
|
833
|
+
@input=${(e: Event) => {
|
|
834
|
+
if (!e.target) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
this.phoneNumber = formatToPhone(
|
|
838
|
+
(e.target as HTMLInputElement).value
|
|
839
|
+
);
|
|
840
|
+
}}
|
|
825
841
|
/>
|
|
826
842
|
</div>
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
<me-select
|
|
833
|
-
id="unitType"
|
|
834
|
-
placeholder="Select type"
|
|
835
|
-
.options="${this.layoutOptions.map((o) => o.label)}"
|
|
836
|
-
defaultOption="Studio"
|
|
837
|
-
@change=${() => {
|
|
838
|
-
// to revalidate the form
|
|
839
|
-
this.requestUpdate();
|
|
840
|
-
}}
|
|
841
|
-
>Studio
|
|
842
|
-
</me-select>
|
|
843
|
-
</div>
|
|
844
|
-
</div>`
|
|
845
|
-
: ""} `;
|
|
843
|
+
<!--
|
|
844
|
+
Layout dropdown would go here, but has been removed pending backend support.
|
|
845
|
+
Here is the code to add it back:
|
|
846
|
+
https://github.com/MeetElise/chat-ui/blob/e17aca8b39a0eed9430f22c182f2ebcdfb796417/src/WebComponent/Scheduler/tour-scheduler.ts#L846-L863
|
|
847
|
+
--> `;
|
|
846
848
|
}
|
|
847
849
|
|
|
848
850
|
confirmationMessage(): TemplateResult {
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { css, html, LitElement, TemplateResult } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
|
-
import { TourType } from "./tour-scheduler";
|
|
4
3
|
|
|
5
4
|
@customElement("tour-type-option")
|
|
6
5
|
export class TourTypeOption extends LitElement {
|
|
7
|
-
@property({ type: String })
|
|
8
|
-
tourtype = "";
|
|
9
6
|
@property({ type: String })
|
|
10
7
|
heading = "";
|
|
11
8
|
@property({ type: String })
|
|
12
9
|
subtitle = "";
|
|
13
10
|
@property({ type: Boolean })
|
|
14
11
|
selected = false;
|
|
15
|
-
|
|
16
12
|
@property({ attribute: false })
|
|
17
|
-
onClick?: (
|
|
13
|
+
onClick?: () => void;
|
|
18
14
|
|
|
19
15
|
static styles = [
|
|
20
16
|
css`
|
|
@@ -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 the following line 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
|
@@ -12,29 +12,46 @@ import {
|
|
|
12
12
|
import groupBy from "lodash/groupBy";
|
|
13
13
|
|
|
14
14
|
const availabilitiesCache: {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
buildingId?: number | null;
|
|
16
|
+
availabilities: { [buildingId: number]: TourAvailabilityResponse };
|
|
17
|
+
} = { buildingId: null, availabilities: {} };
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Returns the raw availabilities.
|
|
21
|
+
*
|
|
22
|
+
* If no `buildingId` is provided, it will use a cached buildingId from the previous call.
|
|
23
|
+
* If there is none cached, it will throw an error.
|
|
24
|
+
*/
|
|
18
25
|
export const getRawAvailabilities = async (
|
|
19
|
-
buildingId
|
|
26
|
+
buildingId?: number
|
|
20
27
|
): Promise<TourAvailabilityResponse> => {
|
|
21
|
-
if (
|
|
22
|
-
|
|
28
|
+
if (buildingId) {
|
|
29
|
+
availabilitiesCache.buildingId = buildingId;
|
|
30
|
+
}
|
|
31
|
+
const buildingIdToUse = availabilitiesCache.buildingId;
|
|
32
|
+
if (!buildingIdToUse) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"No buildingId was provided to getRawAvailabilities and there is no buildingId cached."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const availabilities = availabilitiesCache.availabilities;
|
|
38
|
+
if (availabilities[buildingIdToUse]) {
|
|
39
|
+
return availabilities[buildingIdToUse];
|
|
23
40
|
}
|
|
24
41
|
const startTime = startOfToday();
|
|
25
42
|
const endTime = formatISO(endOfDay(addDays(startTime, 30)));
|
|
26
|
-
const url = `https://app.meetelise.com/api/pub/v1/buildings/${
|
|
43
|
+
const url = `https://app.meetelise.com/api/pub/v1/buildings/${buildingIdToUse}/tour/availabilities?startTime=${formatISO(
|
|
27
44
|
startTime
|
|
28
45
|
)}&endTime=${endTime}`;
|
|
29
46
|
const result = await axios.get<TourAvailabilityResponse>(url);
|
|
30
|
-
availabilitiesCache[
|
|
47
|
+
availabilitiesCache.availabilities[buildingIdToUse] = result.data;
|
|
31
48
|
// The endpoint INCORRECTLY states that these are returned as dates. They are, in fact, strings.
|
|
32
49
|
return result.data;
|
|
33
50
|
};
|
|
34
51
|
|
|
35
52
|
export const getAvailabilitiesForTourType = async (
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum,
|
|
54
|
+
buildingId?: number
|
|
38
55
|
): Promise<{
|
|
39
56
|
/**
|
|
40
57
|
*
|
|
@@ -69,12 +86,12 @@ export interface DateWithTimeZoneOffset {
|
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
export const getAvailabilitiesGroupedByDay = async (
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum,
|
|
90
|
+
buildingId?: number
|
|
74
91
|
): Promise<{ [day: string]: DateWithTimeZoneOffset[] }> => {
|
|
75
92
|
const availabilitiesForTourTypeRaw = await getAvailabilitiesForTourType(
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
tourType,
|
|
94
|
+
buildingId
|
|
78
95
|
);
|
|
79
96
|
if (!availabilitiesForTourTypeRaw) {
|
|
80
97
|
return {};
|
|
@@ -97,16 +114,6 @@ export const getAvailabilitiesGroupedByDay = async (
|
|
|
97
114
|
);
|
|
98
115
|
};
|
|
99
116
|
|
|
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
|
-
|
|
110
117
|
/**
|
|
111
118
|
* Takes an ISO 8601 date string with time zone offset and returns
|
|
112
119
|
* an object of our custom type DateWithTimeZoneOffset.
|