@meetelise/chat 1.14.0 → 1.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/public/dist/index.js +68 -73
- package/src/WebComponent/Scheduler/tour-scheduler.ts +41 -69
- package/src/WebComponent/Scheduler/tour-type-option.ts +5 -1
- package/src/WebComponent/me-chat.ts +4 -1
- package/src/createConversation.ts +1 -1
- package/src/getAvailabilities.ts +23 -64
|
@@ -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";
|
|
@@ -38,12 +37,6 @@ export class TourScheduler extends LitElement {
|
|
|
38
37
|
@state()
|
|
39
38
|
private tourType = TourType.Guided;
|
|
40
39
|
@state()
|
|
41
|
-
private shouldShowTourType = {
|
|
42
|
-
[TourType.Guided]: true,
|
|
43
|
-
[TourType.Self]: true,
|
|
44
|
-
[TourType.Virtual]: true,
|
|
45
|
-
};
|
|
46
|
-
@state()
|
|
47
40
|
private email = "";
|
|
48
41
|
@state()
|
|
49
42
|
private phoneNumber = "";
|
|
@@ -60,10 +53,8 @@ export class TourScheduler extends LitElement {
|
|
|
60
53
|
@state()
|
|
61
54
|
private tourIsBooked = false;
|
|
62
55
|
|
|
63
|
-
@query("input#
|
|
64
|
-
|
|
65
|
-
@query("input#lastName")
|
|
66
|
-
lastNameInput!: HTMLInputElement;
|
|
56
|
+
@query("input#name")
|
|
57
|
+
nameInput!: HTMLInputElement;
|
|
67
58
|
@query("input#email")
|
|
68
59
|
emailInput!: HTMLInputElement;
|
|
69
60
|
@query("input#phone")
|
|
@@ -72,25 +63,9 @@ export class TourScheduler extends LitElement {
|
|
|
72
63
|
unitTypeSelect!: MESelect;
|
|
73
64
|
|
|
74
65
|
firstUpdated = async (): Promise<void> => {
|
|
75
|
-
this.availabilitiesGroupedByDay = await
|
|
66
|
+
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDayCached(
|
|
76
67
|
tourTypeMap[this.tourType]
|
|
77
68
|
);
|
|
78
|
-
|
|
79
|
-
// Show a tour type only if it is supported by the building and has
|
|
80
|
-
// time slots available.
|
|
81
|
-
const availabilitiesExistForTourType =
|
|
82
|
-
await getExistenceOfAvailabilitiesByTourType();
|
|
83
|
-
this.shouldShowTourType = {
|
|
84
|
-
[TourType.Guided]:
|
|
85
|
-
this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT") &&
|
|
86
|
-
availabilitiesExistForTourType[TourType.Guided],
|
|
87
|
-
[TourType.Self]:
|
|
88
|
-
this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED") &&
|
|
89
|
-
availabilitiesExistForTourType[TourType.Self],
|
|
90
|
-
[TourType.Virtual]:
|
|
91
|
-
this.tourTypeOptions.map((o) => o.value).includes("VIRTUAL_SHOWING") &&
|
|
92
|
-
availabilitiesExistForTourType[TourType.Virtual],
|
|
93
|
-
};
|
|
94
69
|
};
|
|
95
70
|
|
|
96
71
|
protected willUpdate = async (
|
|
@@ -99,9 +74,8 @@ export class TourScheduler extends LitElement {
|
|
|
99
74
|
| Map<PropertyKey, unknown>
|
|
100
75
|
): Promise<void> => {
|
|
101
76
|
if (_changedProperties.has("tourType")) {
|
|
102
|
-
this.availabilitiesGroupedByDay =
|
|
103
|
-
tourTypeMap[this.tourType]
|
|
104
|
-
);
|
|
77
|
+
this.availabilitiesGroupedByDay =
|
|
78
|
+
await getAvailabilitiesGroupedByDayCached(tourTypeMap[this.tourType]);
|
|
105
79
|
}
|
|
106
80
|
};
|
|
107
81
|
|
|
@@ -261,7 +235,7 @@ export class TourScheduler extends LitElement {
|
|
|
261
235
|
dateAndTime: (): boolean => !!this.selectedDate && !!this.selectedTime,
|
|
262
236
|
leadInfo: (): boolean => {
|
|
263
237
|
return (
|
|
264
|
-
|
|
238
|
+
!!this.nameInput?.value &&
|
|
265
239
|
this.emailInput?.value.includes("@") &&
|
|
266
240
|
// TODO: deleting phone number doesn't cause validation to fail, at least on mobile
|
|
267
241
|
!!this.phoneNumber &&
|
|
@@ -298,8 +272,9 @@ export class TourScheduler extends LitElement {
|
|
|
298
272
|
email_address: this.email,
|
|
299
273
|
phone_number: `+1${this.phoneNumber.match(/\d/g)?.join("")}`, // e.g. +12125555555
|
|
300
274
|
building_id: this.buildingId,
|
|
301
|
-
|
|
302
|
-
|
|
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(" "),
|
|
303
278
|
tour_type: tourTypeForSubmission[this.tourType],
|
|
304
279
|
tour_time: `${this.selectedTime.datetime}${this.selectedTime.offset}`, // e.g., "2022-06-27T09:00:00-07:00"
|
|
305
280
|
};
|
|
@@ -499,15 +474,6 @@ export class TourScheduler extends LitElement {
|
|
|
499
474
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
|
|
500
475
|
}
|
|
501
476
|
|
|
502
|
-
button#schedule:not(:disabled):hover {
|
|
503
|
-
opacity: 0.7;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
button#schedule:not(:disabled):active {
|
|
507
|
-
box-shadow: none;
|
|
508
|
-
opacity: 1;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
477
|
button#schedule:disabled {
|
|
512
478
|
background: #e7e7e7;
|
|
513
479
|
box-shadow: none;
|
|
@@ -638,8 +604,9 @@ export class TourScheduler extends LitElement {
|
|
|
638
604
|
tourTypeMenu(): TemplateResult {
|
|
639
605
|
return html`<h2 id="tourType">Tour Type</h2>
|
|
640
606
|
<div id="tourTypeMenu">
|
|
641
|
-
${this.
|
|
607
|
+
${this.tourTypeOptions.map((o) => o.value).includes("WITH_AGENT")
|
|
642
608
|
? html` <tour-type-option
|
|
609
|
+
tourtype="guided"
|
|
643
610
|
heading="Guided tour"
|
|
644
611
|
subtitle="with an agent"
|
|
645
612
|
@click="${() => {
|
|
@@ -670,8 +637,9 @@ export class TourScheduler extends LitElement {
|
|
|
670
637
|
</svg>
|
|
671
638
|
</tour-type-option>`
|
|
672
639
|
: ""}
|
|
673
|
-
${this.
|
|
640
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
674
641
|
? html`<tour-type-option
|
|
642
|
+
tourtype="self"
|
|
675
643
|
heading="Take a tour"
|
|
676
644
|
subtitle="on your own"
|
|
677
645
|
@click="${() => {
|
|
@@ -702,8 +670,9 @@ export class TourScheduler extends LitElement {
|
|
|
702
670
|
</svg>
|
|
703
671
|
</tour-type-option>`
|
|
704
672
|
: ""}
|
|
705
|
-
${this.
|
|
673
|
+
${this.tourTypeOptions.map((o) => o.value).includes("SELF_GUIDED")
|
|
706
674
|
? html`<tour-type-option
|
|
675
|
+
tourtype="guided"
|
|
707
676
|
heading="Virtual tour"
|
|
708
677
|
subtitle="over video"
|
|
709
678
|
@click="${() => {
|
|
@@ -772,8 +741,7 @@ export class TourScheduler extends LitElement {
|
|
|
772
741
|
.map((date) => format(new Date(date.datetime), "h:mmaaa"))
|
|
773
742
|
.indexOf(e.target.selectedTime)
|
|
774
743
|
: null;
|
|
775
|
-
this.selectedTime =
|
|
776
|
-
index !== null ? daysAvailabilities[index] : null;
|
|
744
|
+
this.selectedTime = index ? daysAvailabilities[index] : null; // this.selectedAvailabilityString ?
|
|
777
745
|
}
|
|
778
746
|
}}
|
|
779
747
|
></time-picker>
|
|
@@ -843,14 +811,8 @@ export class TourScheduler extends LitElement {
|
|
|
843
811
|
<div id="yourInformationMenu">
|
|
844
812
|
<input
|
|
845
813
|
type="text"
|
|
846
|
-
placeholder="
|
|
847
|
-
id="
|
|
848
|
-
@input=${() => this.requestUpdate()}
|
|
849
|
-
/>
|
|
850
|
-
<input
|
|
851
|
-
type="text"
|
|
852
|
-
placeholder="Last name"
|
|
853
|
-
id="lastName"
|
|
814
|
+
placeholder="Name"
|
|
815
|
+
id="name"
|
|
854
816
|
@input=${() => this.requestUpdate()}
|
|
855
817
|
/>
|
|
856
818
|
<input
|
|
@@ -880,11 +842,25 @@ export class TourScheduler extends LitElement {
|
|
|
880
842
|
}}
|
|
881
843
|
/>
|
|
882
844
|
</div>
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
: ""} `;
|
|
888
864
|
}
|
|
889
865
|
|
|
890
866
|
confirmationMessage(): TemplateResult {
|
|
@@ -911,13 +887,7 @@ export class TourScheduler extends LitElement {
|
|
|
911
887
|
<p>
|
|
912
888
|
Thank you!
|
|
913
889
|
<br />
|
|
914
|
-
Your
|
|
915
|
-
${{
|
|
916
|
-
[TourType.Guided]: "guided",
|
|
917
|
-
[TourType.Self]: "self-guided",
|
|
918
|
-
[TourType.Virtual]: "virtual",
|
|
919
|
-
}[this.tourType]}
|
|
920
|
-
tour is scheduled for ${readableDateAndTime}.
|
|
890
|
+
Your guided tour is scheduled for ${readableDateAndTime}.
|
|
921
891
|
</p>
|
|
922
892
|
<p>
|
|
923
893
|
Look for an email confirmation along with instructions and directions.
|
|
@@ -985,6 +955,8 @@ export enum TourType {
|
|
|
985
955
|
Virtual,
|
|
986
956
|
}
|
|
987
957
|
|
|
958
|
+
// TODO: we have three UI options and five TourAvailabilityResponseRankOrderedSupportedTourTypesEnum values
|
|
959
|
+
// how should they map?
|
|
988
960
|
const tourTypeMap = {
|
|
989
961
|
[TourType.Guided]:
|
|
990
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`
|
|
@@ -136,6 +136,8 @@ export class MEChat extends LitElement {
|
|
|
136
136
|
await this.configureTalkJSPopup(building, theme, session, avatarSrc);
|
|
137
137
|
this.configureLauncherElement();
|
|
138
138
|
|
|
139
|
+
this.analytics?.ping("load");
|
|
140
|
+
|
|
139
141
|
this.yardiDNIScriptInterval = setInterval(
|
|
140
142
|
() => this.pollForYardiCampaignSource(),
|
|
141
143
|
1000
|
|
@@ -263,7 +265,7 @@ export class MEChat extends LitElement {
|
|
|
263
265
|
};
|
|
264
266
|
|
|
265
267
|
render(): TemplateResult {
|
|
266
|
-
if (this.building?.
|
|
268
|
+
if (this.building?.id === 4930) {
|
|
267
269
|
return html``;
|
|
268
270
|
}
|
|
269
271
|
installLauncher();
|
|
@@ -321,6 +323,7 @@ declare global {
|
|
|
321
323
|
interface HTMLElementTagNameMap {
|
|
322
324
|
"me-chat": MEChat;
|
|
323
325
|
}
|
|
326
|
+
|
|
324
327
|
interface Window {
|
|
325
328
|
RCTPCampaign?: { CampaignDetails: { Source: string } };
|
|
326
329
|
}
|
|
@@ -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.
|