@meetelise/chat 1.20.3 → 1.20.5
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 +1 -1
- package/public/dist/index.js +109 -54
- package/src/WebComponent/Launcher.ts +5 -2
- package/src/WebComponent/Scheduler/me-select.ts +31 -7
- package/src/WebComponent/Scheduler/tour-scheduler.ts +138 -45
- package/src/WebComponent/me-chat.ts +2 -1
- package/src/fetchBuildingInfo.ts +21 -1
|
@@ -11,7 +11,7 @@ import { classMap } from "lit/directives/class-map.js";
|
|
|
11
11
|
import { installCallUsWindow } from "./actions/call-us-window";
|
|
12
12
|
import { getRegisteredPhoneNumbers } from "../getRegisteredPhoneNumbers";
|
|
13
13
|
import { TourScheduler } from "./Scheduler/tour-scheduler";
|
|
14
|
-
import { LabeledOption } from "../fetchBuildingInfo";
|
|
14
|
+
import { LabeledOption, UnitV2 } from "../fetchBuildingInfo";
|
|
15
15
|
|
|
16
16
|
@customElement("meetelise-launcher")
|
|
17
17
|
export class Launcher extends LitElement {
|
|
@@ -53,7 +53,9 @@ export class Launcher extends LitElement {
|
|
|
53
53
|
@property({ type: Boolean })
|
|
54
54
|
hasTextUsEnabled = false;
|
|
55
55
|
@property({ attribute: false })
|
|
56
|
-
layoutOptions:
|
|
56
|
+
layoutOptions: string[] = [];
|
|
57
|
+
@property({ attribute: false })
|
|
58
|
+
unitOptions: UnitV2[] = [];
|
|
57
59
|
@property({ attribute: false })
|
|
58
60
|
tourTypeOptions: LabeledOption[] = [];
|
|
59
61
|
@property({ attribute: false })
|
|
@@ -294,6 +296,7 @@ export class Launcher extends LitElement {
|
|
|
294
296
|
orgSlug="${this.orgSlug}"
|
|
295
297
|
buildingSlug="${this.buildingSlug}"
|
|
296
298
|
.layoutOptions=${this.layoutOptions}
|
|
299
|
+
.unitOptions=${this.unitOptions}
|
|
297
300
|
.tourTypeOptions=${this.tourTypeOptions}
|
|
298
301
|
buildingId=${this.buildingId}
|
|
299
302
|
${ref(this.tourSchedulerRef)}
|
|
@@ -2,10 +2,15 @@ import { LitElement, html, TemplateResult, css } from "lit";
|
|
|
2
2
|
import { property, state, query, customElement } from "lit/decorators.js";
|
|
3
3
|
import { classMap } from "lit/directives/class-map.js";
|
|
4
4
|
|
|
5
|
+
type MeSelectOption = {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
5
10
|
@customElement("me-select")
|
|
6
11
|
export class MESelect extends LitElement {
|
|
7
12
|
@property({ attribute: false })
|
|
8
|
-
options:
|
|
13
|
+
options: MeSelectOption[] = [];
|
|
9
14
|
|
|
10
15
|
@property({ type: String })
|
|
11
16
|
placeholder?: string = "Select";
|
|
@@ -14,7 +19,7 @@ export class MESelect extends LitElement {
|
|
|
14
19
|
value?: string;
|
|
15
20
|
|
|
16
21
|
@state()
|
|
17
|
-
private activeOption:
|
|
22
|
+
private activeOption: MeSelectOption | null = null;
|
|
18
23
|
|
|
19
24
|
@state()
|
|
20
25
|
private isOpen?: boolean = false;
|
|
@@ -29,8 +34,13 @@ export class MESelect extends LitElement {
|
|
|
29
34
|
this.isOpen = !this.isOpen;
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
setSelectedOption = (option:
|
|
33
|
-
this.value
|
|
37
|
+
setSelectedOption = (option: MeSelectOption, closeSelect = true): void => {
|
|
38
|
+
if (this.value !== option.value) {
|
|
39
|
+
this.value = option.value;
|
|
40
|
+
} else {
|
|
41
|
+
this.value = undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
if (closeSelect) {
|
|
35
45
|
this.isOpen = !this.isOpen;
|
|
36
46
|
this.activeOption = null;
|
|
@@ -103,6 +113,7 @@ export class MESelect extends LitElement {
|
|
|
103
113
|
overflow-y: scroll;
|
|
104
114
|
position: absolute;
|
|
105
115
|
min-height: 40px;
|
|
116
|
+
min-width: 144px;
|
|
106
117
|
max-width: 400px;
|
|
107
118
|
width: max-content;
|
|
108
119
|
background-color: white;
|
|
@@ -112,7 +123,17 @@ export class MESelect extends LitElement {
|
|
|
112
123
|
box-sizing: border-box;
|
|
113
124
|
box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.15);
|
|
114
125
|
border-radius: 10px;
|
|
115
|
-
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
::-webkit-scrollbar {
|
|
129
|
+
-webkit-appearance: none;
|
|
130
|
+
width: 8px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
::-webkit-scrollbar-thumb {
|
|
134
|
+
border-radius: 10px;
|
|
135
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
136
|
+
-webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
.option {
|
|
@@ -195,7 +216,10 @@ export class MESelect extends LitElement {
|
|
|
195
216
|
}
|
|
196
217
|
}}"
|
|
197
218
|
>
|
|
198
|
-
<span id="selectText"
|
|
219
|
+
<span id="selectText"
|
|
220
|
+
>${this.options.find((o) => o.value === this.value)?.label ??
|
|
221
|
+
this.placeholder}</span
|
|
222
|
+
>
|
|
199
223
|
<svg
|
|
200
224
|
width="10"
|
|
201
225
|
height="6"
|
|
@@ -228,7 +252,7 @@ export class MESelect extends LitElement {
|
|
|
228
252
|
active: this.activeOption === option,
|
|
229
253
|
})}"
|
|
230
254
|
>
|
|
231
|
-
${option}
|
|
255
|
+
${option.label}
|
|
232
256
|
</li>`
|
|
233
257
|
)}
|
|
234
258
|
</ul>`
|
|
@@ -19,17 +19,35 @@ import { format } from "date-fns";
|
|
|
19
19
|
import { DatePicker } from "./date-picker";
|
|
20
20
|
import { MESelect } from "./me-select";
|
|
21
21
|
import { TimePicker } from "./time-picker";
|
|
22
|
-
import { LabeledOption } from "../../fetchBuildingInfo";
|
|
22
|
+
import { LabeledOption, UnitV2 } from "../../fetchBuildingInfo";
|
|
23
23
|
import { isMobile } from "../../utils";
|
|
24
24
|
import axios from "axios";
|
|
25
25
|
import { mapValues } from "lodash";
|
|
26
26
|
import classnames from "classnames";
|
|
27
27
|
import parseISO from "date-fns/parseISO";
|
|
28
28
|
|
|
29
|
+
const getHumanReadableLayout = (layout: string) => {
|
|
30
|
+
if (layout == "studio") return "Studio";
|
|
31
|
+
return {
|
|
32
|
+
"1br": "1 bedroom",
|
|
33
|
+
"2br": "2 bedrooms",
|
|
34
|
+
"3br": "3 bedrooms",
|
|
35
|
+
"4br": "4 bedrooms",
|
|
36
|
+
"5br": "5 bedrooms",
|
|
37
|
+
"6br": "6 bedrooms",
|
|
38
|
+
"7br": "7 bedrooms",
|
|
39
|
+
"8br": "8 bedrooms",
|
|
40
|
+
"9br": "9 bedrooms",
|
|
41
|
+
"10br": "10 bedroom",
|
|
42
|
+
}[layout];
|
|
43
|
+
};
|
|
44
|
+
|
|
29
45
|
@customElement("tour-scheduler")
|
|
30
46
|
export class TourScheduler extends LitElement {
|
|
31
47
|
@property({ attribute: false })
|
|
32
|
-
layoutOptions:
|
|
48
|
+
layoutOptions: string[] = [];
|
|
49
|
+
@property({ attribute: false })
|
|
50
|
+
unitOptions: UnitV2[] = [];
|
|
33
51
|
@property({ attribute: false })
|
|
34
52
|
tourTypeOptions: LabeledOption[] = [];
|
|
35
53
|
@property({ type: Number })
|
|
@@ -69,9 +87,9 @@ export class TourScheduler extends LitElement {
|
|
|
69
87
|
@state()
|
|
70
88
|
private tourIsBooked = false;
|
|
71
89
|
|
|
72
|
-
@query(".
|
|
90
|
+
@query(".nameContainer#firstName input")
|
|
73
91
|
firstNameInput!: HTMLInputElement;
|
|
74
|
-
@query(".
|
|
92
|
+
@query(".nameContainer#lastName input")
|
|
75
93
|
lastNameInput!: HTMLInputElement;
|
|
76
94
|
@query(".inputContainer#email input")
|
|
77
95
|
emailInput!: HTMLInputElement;
|
|
@@ -79,6 +97,8 @@ export class TourScheduler extends LitElement {
|
|
|
79
97
|
phoneInput!: HTMLInputElement;
|
|
80
98
|
@query("me-select#unitType")
|
|
81
99
|
unitTypeSelect!: MESelect;
|
|
100
|
+
@query("me-select#layoutType")
|
|
101
|
+
layoutTypeSelect!: MESelect;
|
|
82
102
|
|
|
83
103
|
firstUpdated = async (): Promise<void> => {
|
|
84
104
|
this.availabilitiesGroupedByDay = await getAvailabilitiesGroupedByDay(
|
|
@@ -312,21 +332,29 @@ export class TourScheduler extends LitElement {
|
|
|
312
332
|
last_name: this.lastNameInput.value,
|
|
313
333
|
tour_type: tourTypeForSubmission[this.tourType],
|
|
314
334
|
tour_time: `${this.selectedTime.datetime}${this.selectedTime.offset}`, // e.g., "2022-06-27T09:00:00-07:00"
|
|
335
|
+
layouts: this.layoutTypeSelect.value
|
|
336
|
+
? [this.layoutTypeSelect.value]
|
|
337
|
+
: null,
|
|
338
|
+
unit_numbers: this.unitTypeSelect.value
|
|
339
|
+
? [this.unitTypeSelect.value]
|
|
340
|
+
: null,
|
|
315
341
|
};
|
|
316
342
|
const url = `https://app.meetelise.com/platformApi/state/create/scheduleMe`;
|
|
317
343
|
this.isSubmitting = true;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
await axios.post(url, data, {
|
|
347
|
+
headers: {
|
|
348
|
+
["building-slug"]: this.buildingSlug,
|
|
349
|
+
["X-SecurityKey"]: "JRL8jV4VcSCwOSir5gWkpgNLfKghmhBG",
|
|
350
|
+
["org-slug"]: this.orgSlug,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
326
353
|
this.isSubmitting = false;
|
|
327
354
|
this.tourIsBooked = true;
|
|
328
|
-
|
|
329
|
-
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
356
|
+
} catch (e: any) {
|
|
357
|
+
const message = e.response.data["detail"] || "Failed to book tour";
|
|
330
358
|
alert(message);
|
|
331
359
|
this.isSubmitting = false;
|
|
332
360
|
this.tourIsBooked = false;
|
|
@@ -355,7 +383,7 @@ export class TourScheduler extends LitElement {
|
|
|
355
383
|
/* grid stuff */
|
|
356
384
|
display: grid;
|
|
357
385
|
grid-template-columns: 229px 432px 305px;
|
|
358
|
-
grid-template-rows: 44px 54px 32px 195px
|
|
386
|
+
grid-template-rows: 44px 54px 32px 195px 167px 1px;
|
|
359
387
|
}
|
|
360
388
|
|
|
361
389
|
h1,
|
|
@@ -449,7 +477,7 @@ export class TourScheduler extends LitElement {
|
|
|
449
477
|
}
|
|
450
478
|
|
|
451
479
|
#yourInformationMenu input {
|
|
452
|
-
width:
|
|
480
|
+
width: 100%;
|
|
453
481
|
height: 49px;
|
|
454
482
|
border: 1px solid #83818e;
|
|
455
483
|
padding: 13px 11px 14px 11px;
|
|
@@ -464,7 +492,7 @@ export class TourScheduler extends LitElement {
|
|
|
464
492
|
color: #202020;
|
|
465
493
|
}
|
|
466
494
|
|
|
467
|
-
|
|
495
|
+
.unitLayoutChoices {
|
|
468
496
|
grid-row: 5 / 6;
|
|
469
497
|
grid-column: 3;
|
|
470
498
|
align-self: start;
|
|
@@ -472,11 +500,15 @@ export class TourScheduler extends LitElement {
|
|
|
472
500
|
flex-direction: column;
|
|
473
501
|
}
|
|
474
502
|
|
|
475
|
-
|
|
503
|
+
.unitLayoutChoice {
|
|
504
|
+
margin-bottom: 12px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
h2.unitLayoutChoice {
|
|
476
508
|
margin-bottom: 7px;
|
|
477
509
|
}
|
|
478
510
|
|
|
479
|
-
|
|
511
|
+
.unitLayoutOptions {
|
|
480
512
|
display: flex;
|
|
481
513
|
flex-direction: column;
|
|
482
514
|
gap: 8px;
|
|
@@ -584,10 +616,6 @@ export class TourScheduler extends LitElement {
|
|
|
584
616
|
width: 205px;
|
|
585
617
|
}
|
|
586
618
|
|
|
587
|
-
.tour-scheduler.loading #yourInformationMenu .inputContainer::after {
|
|
588
|
-
width: 100%;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
619
|
.tour-scheduler.loading #yourInformationMenu .inputContainer input {
|
|
592
620
|
visibility: hidden;
|
|
593
621
|
}
|
|
@@ -596,6 +624,19 @@ export class TourScheduler extends LitElement {
|
|
|
596
624
|
display: none;
|
|
597
625
|
}
|
|
598
626
|
|
|
627
|
+
#namesWrapper {
|
|
628
|
+
display: flex;
|
|
629
|
+
justify-content: space-between;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.nameContainer {
|
|
633
|
+
width: 48%;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.nameInput {
|
|
637
|
+
width: 100%;
|
|
638
|
+
}
|
|
639
|
+
|
|
599
640
|
@media (max-width: 767px) {
|
|
600
641
|
/* TODO: separate styles into general, desktop-specific, and mobile-specific.
|
|
601
642
|
basically everything I have "unset" or "initial" on should become desktop-specific. the grid layout is only for desktop.
|
|
@@ -933,24 +974,29 @@ export class TourScheduler extends LitElement {
|
|
|
933
974
|
userInfoAndLayoutMenu(): TemplateResult {
|
|
934
975
|
return html`<h2 id="yourInformation">Your information</h2>
|
|
935
976
|
<div id="yourInformationMenu">
|
|
936
|
-
<div
|
|
937
|
-
<
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
977
|
+
<div id="namesWrapper">
|
|
978
|
+
<div class="nameContainer" id="firstName">
|
|
979
|
+
<input
|
|
980
|
+
class="nameInput"
|
|
981
|
+
type="text"
|
|
982
|
+
placeholder="First name"
|
|
983
|
+
name="firstName"
|
|
984
|
+
autocomplete="given-name"
|
|
985
|
+
@input=${() => this.requestUpdate()}
|
|
986
|
+
/>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="nameContainer" id="lastName">
|
|
989
|
+
<input
|
|
990
|
+
class="nameInput"
|
|
991
|
+
type="text"
|
|
992
|
+
placeholder="Last name"
|
|
993
|
+
name="lastName"
|
|
994
|
+
autocomplete="family-name"
|
|
995
|
+
@input=${() => this.requestUpdate()}
|
|
996
|
+
/>
|
|
997
|
+
</div>
|
|
953
998
|
</div>
|
|
999
|
+
|
|
954
1000
|
<div class="inputContainer" id="email">
|
|
955
1001
|
<input
|
|
956
1002
|
type="email"
|
|
@@ -984,11 +1030,58 @@ export class TourScheduler extends LitElement {
|
|
|
984
1030
|
/>
|
|
985
1031
|
</div>
|
|
986
1032
|
</div>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1033
|
+
<div class="unitLayoutChoices">
|
|
1034
|
+
${this.layoutOptions.length > 0
|
|
1035
|
+
? html`<div class="unitLayoutChoice">
|
|
1036
|
+
<h2 class="unitLayoutChoice">What would you like to view?</h2>
|
|
1037
|
+
<div class="unitLayoutOptions">
|
|
1038
|
+
<me-select
|
|
1039
|
+
id="layoutType"
|
|
1040
|
+
placeholder="Select layout"
|
|
1041
|
+
.options="${this.layoutOptions.map((i) => ({
|
|
1042
|
+
label: getHumanReadableLayout(i),
|
|
1043
|
+
value: i,
|
|
1044
|
+
}))}"
|
|
1045
|
+
defaultOption="Studio"
|
|
1046
|
+
@change=${() => {
|
|
1047
|
+
// to revalidate the form
|
|
1048
|
+
this.requestUpdate();
|
|
1049
|
+
}}
|
|
1050
|
+
>Studio
|
|
1051
|
+
</me-select>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>`
|
|
1054
|
+
: ""}
|
|
1055
|
+
${this.unitOptions.filter(
|
|
1056
|
+
(i) =>
|
|
1057
|
+
!this.layoutTypeSelect || i.layout === this.layoutTypeSelect.value
|
|
1058
|
+
).length > 0
|
|
1059
|
+
? html`<div class="unitLayoutChoice">
|
|
1060
|
+
<div class="unitLayoutOptions">
|
|
1061
|
+
<me-select
|
|
1062
|
+
id="unitType"
|
|
1063
|
+
placeholder="Select unit"
|
|
1064
|
+
.options="${this.unitOptions
|
|
1065
|
+
.filter(
|
|
1066
|
+
(i) =>
|
|
1067
|
+
!this.layoutTypeSelect ||
|
|
1068
|
+
i.layout === this.layoutTypeSelect.value
|
|
1069
|
+
)
|
|
1070
|
+
.map((i) => ({
|
|
1071
|
+
label: i.name,
|
|
1072
|
+
value: i.name,
|
|
1073
|
+
}))}"
|
|
1074
|
+
defaultOption="Studio"
|
|
1075
|
+
@change=${() => {
|
|
1076
|
+
// to revalidate the form
|
|
1077
|
+
this.requestUpdate();
|
|
1078
|
+
}}
|
|
1079
|
+
>Studio
|
|
1080
|
+
</me-select>
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>`
|
|
1083
|
+
: ""}
|
|
1084
|
+
</div> `;
|
|
992
1085
|
}
|
|
993
1086
|
|
|
994
1087
|
confirmationMessage(): TemplateResult {
|
|
@@ -285,7 +285,8 @@ export class MEChat extends LitElement {
|
|
|
285
285
|
.isFirstMount=${!this.hasMounted}
|
|
286
286
|
.isMini=${this.useMiniWidget}
|
|
287
287
|
.buildingId=${this.building?.id ?? 0}
|
|
288
|
-
.layoutOptions=${this.building?.
|
|
288
|
+
.layoutOptions=${this.building?.layoutOptionsV2 ?? []}
|
|
289
|
+
.unitOptions=${this.building?.unitOptionsV2 ?? []}
|
|
289
290
|
.tourTypeOptions=${this.building?.tourTypeOptions ?? []}
|
|
290
291
|
.launcherStyles=${this.launcherStyles}
|
|
291
292
|
chatCallUsHeader=${this.building?.chatCallUsHeader ?? "Call us"}
|
package/src/fetchBuildingInfo.ts
CHANGED
|
@@ -21,12 +21,18 @@ export interface Building {
|
|
|
21
21
|
conversationMaintenanceMode: boolean;
|
|
22
22
|
orgId: number;
|
|
23
23
|
phoneNumber: string;
|
|
24
|
-
layoutOptions: LabeledOption[];
|
|
25
24
|
tourTypeOptions: LabeledOption[];
|
|
26
25
|
chatWidgets?: string[] | null;
|
|
27
26
|
chatCallUsHeader?: string;
|
|
27
|
+
unitOptionsV2: UnitV2[];
|
|
28
|
+
layoutOptionsV2: string[];
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export type UnitV2 = {
|
|
32
|
+
name: string;
|
|
33
|
+
layout: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
37
|
* Load the publicly-available info for a building.
|
|
32
38
|
*
|
|
@@ -42,5 +48,19 @@ export default async function fetchBuildingInfo(
|
|
|
42
48
|
const url = `${host}/api/pub/v1/organization/${orgSlug}/building/${buildingSlug}`;
|
|
43
49
|
const response = await fetch(url);
|
|
44
50
|
const building: Building = await response.json();
|
|
51
|
+
|
|
52
|
+
// HACK
|
|
53
|
+
// We will fetch these units/layouts from elise-crm-api and supplement the DTO
|
|
54
|
+
const unitsResponse = await fetch(
|
|
55
|
+
`${host}/eliseCrmApi/pub/building/${buildingSlug}/units`
|
|
56
|
+
);
|
|
57
|
+
const units: UnitV2[] = await unitsResponse.json();
|
|
58
|
+
const layoutsResponse = await fetch(
|
|
59
|
+
`${host}/eliseCrmApi/pub/building/${buildingSlug}/layouts`
|
|
60
|
+
);
|
|
61
|
+
const layouts: string[] = await layoutsResponse.json();
|
|
62
|
+
|
|
63
|
+
building.unitOptionsV2 = units;
|
|
64
|
+
building.layoutOptionsV2 = layouts;
|
|
45
65
|
return building;
|
|
46
66
|
}
|