@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.
@@ -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: LabeledOption[] = [];
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: string[] = [];
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: string | null = null;
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: string, closeSelect = true): void => {
33
- this.value = option;
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
- overflow: hidden;
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">${this.value ?? this.placeholder}</span>
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: LabeledOption[] = [];
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(".inputContainer#firstName input")
90
+ @query(".nameContainer#firstName input")
73
91
  firstNameInput!: HTMLInputElement;
74
- @query(".inputContainer#lastName input")
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
- const response = await axios.post(url, data, {
319
- headers: {
320
- ["building-slug"]: this.buildingSlug,
321
- ["X-SecurityKey"]: "JRL8jV4VcSCwOSir5gWkpgNLfKghmhBG",
322
- ["org-slug"]: this.orgSlug,
323
- },
324
- });
325
- if (response.status === 200) {
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
- } else {
329
- const message = response.data["detail"] || "Failed to book tour";
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 152px 1px;
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: 305px;
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
- #unitChoiceMenu {
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
- h2#unitChoice {
503
+ .unitLayoutChoice {
504
+ margin-bottom: 12px;
505
+ }
506
+
507
+ h2.unitLayoutChoice {
476
508
  margin-bottom: 7px;
477
509
  }
478
510
 
479
- #unitOptions {
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 class="inputContainer" id="firstName">
937
- <input
938
- type="text"
939
- placeholder="First name"
940
- name="firstName"
941
- autocomplete="given-name"
942
- @input=${() => this.requestUpdate()}
943
- />
944
- </div>
945
- <div class="inputContainer" id="lastName">
946
- <input
947
- type="text"
948
- placeholder="Last name"
949
- name="lastName"
950
- autocomplete="family-name"
951
- @input=${() => this.requestUpdate()}
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
- Layout dropdown would go here, but has been removed pending backend support.
989
- Here is the code to add it back:
990
- https://github.com/MeetElise/chat-ui/blob/e17aca8b39a0eed9430f22c182f2ebcdfb796417/src/WebComponent/Scheduler/tour-scheduler.ts#L846-L863
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?.layoutOptions ?? []}
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"}
@@ -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
  }