@meetelise/chat 1.43.42 → 1.45.0

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.
@@ -29,6 +29,7 @@ import { DatePicker } from "./date-picker";
29
29
  import { MESelect } from "../me-select";
30
30
  import { TimePicker } from "./time-picker";
31
31
  import { LabeledOption } from "../../fetchBuildingInfo";
32
+ import { LayoutOption } from "../../fetchBuildingWebchatView";
32
33
  import {
33
34
  isMobile,
34
35
  isValidEmail,
@@ -61,6 +62,9 @@ import { getTimezoneAbbreviation } from "../../getTimezoneString";
61
62
  import startOfDay from "date-fns/startOfDay";
62
63
  import isSameDay from "date-fns/isSameDay";
63
64
  import { LeadSourceMultitouchClient } from "../LeadSourceMultitouchClient";
65
+ import fetchBuildingUnitsSummary, {
66
+ UnitSummary,
67
+ } from "../../fetchBuildingUnitsSummary";
64
68
 
65
69
  @customElement("tour-scheduler")
66
70
  export class TourScheduler extends LitElement {
@@ -79,6 +83,8 @@ export class TourScheduler extends LitElement {
79
83
 
80
84
  @property({ attribute: false })
81
85
  tourTypeOptions: LabeledOption[] = [];
86
+ @property({ attribute: false })
87
+ layoutOptions: LayoutOption[] = [];
82
88
  @property({ attribute: true })
83
89
  chatId = "";
84
90
  @property({ type: Number })
@@ -191,6 +197,10 @@ export class TourScheduler extends LitElement {
191
197
  phoneInput!: HTMLInputElement;
192
198
  @query("me-select#leadSource")
193
199
  selectedLeadSource!: MESelect;
200
+ @query("me-select#layout")
201
+ selectedLayoutEl?: MESelect;
202
+ @query("me-select#unit")
203
+ selectedUnitEl?: MESelect;
194
204
 
195
205
  @state()
196
206
  firstNameInputValue = "";
@@ -198,6 +208,14 @@ export class TourScheduler extends LitElement {
198
208
  lastNameInputValue = "";
199
209
  @state()
200
210
  leadSourceInputValue = "";
211
+ @state()
212
+ selectedLayoutValue = "";
213
+ @state()
214
+ selectedUnitValue = "";
215
+ @state()
216
+ private units: UnitSummary[] = [];
217
+ @state()
218
+ private allowOccupiedUnitTours = false;
201
219
 
202
220
  @state()
203
221
  errorGettingAvailabilities = false;
@@ -403,8 +421,51 @@ export class TourScheduler extends LitElement {
403
421
  return null;
404
422
  };
405
423
 
424
+ _getUnitOptions = (): { label: string; value: string }[] => {
425
+ const visible = this.selectedLayoutValue
426
+ ? this.units.filter(
427
+ (u) =>
428
+ layoutValueToCanonical(u.numberOfBedrooms * 10) ===
429
+ this.selectedLayoutValue
430
+ )
431
+ : this.units;
432
+ const unitOptions = visible.map((u) => ({
433
+ label: u.unitNumber,
434
+ value: u.unitNumber,
435
+ }));
436
+ return unitOptions;
437
+ };
438
+
439
+ _fetchUnits = async (): Promise<void> => {
440
+ if (!this.buildingSlug) return;
441
+ try {
442
+ this.units = await fetchBuildingUnitsSummary({
443
+ buildingSlug: this.buildingSlug,
444
+ });
445
+ } catch (e) {
446
+ this.units = [];
447
+ sendLoggingEvent({
448
+ logTitle: "ERROR_LOADING_UNITS_FOR_TOUR_SCHEDULER",
449
+ logData: { error: e },
450
+ logType: LogType.error,
451
+ buildingSlug: this.buildingSlug,
452
+ orgSlug: this.orgSlug,
453
+ });
454
+ }
455
+ };
456
+
406
457
  firstUpdated = async (): Promise<void> => {
407
- await this._setAvailabilities();
458
+ await Promise.all([
459
+ this._setAvailabilities().then(async () => {
460
+ try {
461
+ const rawAvailabilities = await getRawAvailabilities(this.buildingId);
462
+ this.allowOccupiedUnitTours = !!rawAvailabilities.allowOccupiedUnitTours;
463
+ } catch (e) {
464
+ this.allowOccupiedUnitTours = false;
465
+ }
466
+ }),
467
+ this._fetchUnits(),
468
+ ]);
408
469
  };
409
470
 
410
471
  protected willUpdate = async (
@@ -643,6 +704,10 @@ export class TourScheduler extends LitElement {
643
704
  this.lastNameInputValue = this.lastNameInput.value;
644
705
  }
645
706
  this.leadSourceInputValue = parsedLeadSource;
707
+ const selectedLayout =
708
+ this.selectedLayoutEl?.value || this.selectedLayoutValue || null;
709
+ const selectedUnit =
710
+ this.selectedUnitEl?.value || this.selectedUnitValue || null;
646
711
  pushGtmEvent("scheduleTourSubmitted", {
647
712
  email: this.email,
648
713
  phone: `+1${this.phoneNumber.match(/\d/g)?.join("")}`,
@@ -652,6 +717,8 @@ export class TourScheduler extends LitElement {
652
717
  tourTime: `${this.selectedTime.datetime}${this.selectedTime.offset}`,
653
718
  originatingSource:
654
719
  leadSources.find((i) => i !== "property-website") || null,
720
+ layout: selectedLayout,
721
+ unit: selectedUnit,
655
722
  });
656
723
  const data = {
657
724
  referrer: document.referrer,
@@ -672,6 +739,8 @@ export class TourScheduler extends LitElement {
672
739
  ],
673
740
  query_params: Object.fromEntries(queryParams.entries()),
674
741
  conversation_tracking_id: this.leadSourceClient?.chatId,
742
+ layouts: selectedLayout ? [selectedLayout] : undefined,
743
+ unit_numbers: selectedUnit ? [selectedUnit] : undefined,
675
744
  lead_sources_with_timestamps: (
676
745
  this.leadSourceMultitouchClient?.getSafeLeadSourceTouchpointsWithDefault(
677
746
  {
@@ -1225,6 +1294,61 @@ export class TourScheduler extends LitElement {
1225
1294
  ? html`<p class="error-message">Invalid phone number</p>`
1226
1295
  : ""}
1227
1296
  </div>
1297
+ ${this.layoutOptions.length > 0
1298
+ ? html` <me-select
1299
+ id="layout"
1300
+ placeholder="Bedroom preference (optional)"
1301
+ .options="${this.layoutOptions.map((o) => ({
1302
+ label: o.label,
1303
+ value: layoutValueToCanonical(o.value),
1304
+ }))}"
1305
+ @change=${() => {
1306
+ const v = this.selectedLayoutEl?.value || "";
1307
+ this.selectedLayoutValue = v;
1308
+ // Clear unit selection if it no longer matches the new layout.
1309
+ if (v && this.selectedUnitValue) {
1310
+ const unit = this.units.find(
1311
+ (u) => u.unitNumber === this.selectedUnitValue
1312
+ );
1313
+ if (
1314
+ unit &&
1315
+ layoutValueToCanonical(unit.numberOfBedrooms * 10) !== v
1316
+ ) {
1317
+ this.selectedUnitValue = "";
1318
+ if (this.selectedUnitEl) {
1319
+ this.selectedUnitEl.value = undefined;
1320
+ }
1321
+ }
1322
+ }
1323
+ if (v) {
1324
+ pushGtmEvent("tourLayoutSelected", {
1325
+ layout: v,
1326
+ buildingId: this.buildingId,
1327
+ });
1328
+ }
1329
+ }}
1330
+ >
1331
+ </me-select>`
1332
+ : ""}
1333
+ ${this.allowOccupiedUnitTours && this._getUnitOptions().length > 0
1334
+ ? html` <me-select
1335
+ id="unit"
1336
+ placeholder="Unit preference (optional)"
1337
+ .value=${this.selectedUnitValue}
1338
+ .options="${this._getUnitOptions()}"
1339
+ @change=${() => {
1340
+ const v = this.selectedUnitEl?.value || "";
1341
+ this.selectedUnitValue = v;
1342
+ if (v) {
1343
+ pushGtmEvent("tourUnitSelected", {
1344
+ unit: v,
1345
+ buildingId: this.buildingId,
1346
+ });
1347
+ }
1348
+ }}
1349
+ >
1350
+ </me-select>`
1351
+ : ""}
1228
1352
  ${this.leadSources.length > 0 &&
1229
1353
  (this.featureFlagShowDropdown === FeatureFlagsShowDropdown.always ||
1230
1354
  (this.featureFlagShowDropdown ===
@@ -1678,3 +1802,9 @@ const tourTypeForSubmission = {
1678
1802
  [TourType.Self]: "self-guided-tour",
1679
1803
  [TourType.Virtual]: "live-virtual-tour",
1680
1804
  };
1805
+
1806
+ // LayoutOption.value is bedrooms * 10; EliseAI classifier expects `studio` / `Nbr`.
1807
+ function layoutValueToCanonical(value: number): string {
1808
+ const bedrooms = Math.round(value / 10);
1809
+ return bedrooms === 0 ? "studio" : `${bedrooms}br`;
1810
+ }
@@ -21,7 +21,7 @@ import {
21
21
  } from "../../themes";
22
22
  import "./mobile-launcher";
23
23
  import "../actions/collapse-expand-button";
24
- import { DesignConcepts } from "../../fetchBuildingWebchatView";
24
+ import { DesignConcepts, LayoutOption } from "../../fetchBuildingWebchatView";
25
25
  import { isMobile, onMobileChange } from "../../utils";
26
26
  import classNames from "classnames";
27
27
  import LeadSourceClient from "../LeadSourceClient";
@@ -141,6 +141,9 @@ export class Launcher extends LitElement {
141
141
 
142
142
  @property({ attribute: false })
143
143
  tourTypeOptions: LabeledOption[] = [];
144
+
145
+ @property({ attribute: false })
146
+ layoutOptions: LayoutOption[] = [];
144
147
  @property({ attribute: true })
145
148
  hasDynamicSchedulingEnabled = false;
146
149
 
@@ -1299,6 +1302,7 @@ export class Launcher extends LitElement {
1299
1302
  .hasDynamicSchedulingEnabled=${this.hasDynamicSchedulingEnabled}
1300
1303
  .leadSources="${this.leadSources}"
1301
1304
  .tourTypeOptions=${this.tourTypeOptions}
1305
+ .layoutOptions=${this.layoutOptions}
1302
1306
  .orgLegalName=${this.orgLegalName}
1303
1307
  buildingId=${this.buildingId}
1304
1308
  featureFlagShowDropdown="${this.featureFlagShowDropdown}"
@@ -1005,6 +1005,8 @@ export class MEChat extends LitElement {
1005
1005
  .orgLegalName=${this.buildingWebchatView?.orgLegalName ?? ""}
1006
1006
  .tourTypeOptions=${this.buildingWebchatView
1007
1007
  ?.tourTypeOptions ?? []}
1008
+ .layoutOptions=${this.buildingWebchatView?.layoutOptions ??
1009
+ []}
1008
1010
  .launcherStyles=${this.launcherStyles}
1009
1011
  .primaryColor=${this.primaryColor}
1010
1012
  .backgroundColor=${this.backgroundColor}
@@ -0,0 +1,37 @@
1
+ import axios from "axios";
2
+ import { LogType, sendLoggingEvent } from "./analytics";
3
+ import { BASE_DOMAIN } from "./globals";
4
+ import { camelize } from "./utils";
5
+
6
+ export type UnitSummary = {
7
+ id: string;
8
+ unitNumber: string;
9
+ numberOfBedrooms: number;
10
+ };
11
+
12
+ type FetchBuildingUnitsSummaryParams = {
13
+ buildingSlug: string;
14
+ };
15
+
16
+ const fetchBuildingUnitsSummary = async ({
17
+ buildingSlug,
18
+ }: FetchBuildingUnitsSummaryParams): Promise<UnitSummary[]> => {
19
+ try {
20
+ const response = await axios.get(
21
+ `${BASE_DOMAIN}/platformApi/webchat/${buildingSlug}/units/summary`
22
+ );
23
+ if (response.data) {
24
+ return camelize<UnitSummary[]>(response.data);
25
+ }
26
+ } catch (error) {
27
+ sendLoggingEvent({
28
+ logType: LogType.error,
29
+ buildingSlug,
30
+ logTitle: "[ERROR_GETTING_UNITS_SUMMARY]",
31
+ logData: { error },
32
+ });
33
+ }
34
+ return [];
35
+ };
36
+
37
+ export default fetchBuildingUnitsSummary;
@@ -10,4 +10,8 @@ export enum TourAvailabilityResponseRankOrderedSupportedTourTypesEnum {
10
10
  }
11
11
 
12
12
  // Types are safe to re-export as they are not included in runtime code.
13
- export type { TourAvailabilityResponse } from "@meetelise/rest-sdk";
13
+ import type { TourAvailabilityResponse as SdkTourAvailabilityResponse } from "@meetelise/rest-sdk";
14
+
15
+ export type TourAvailabilityResponse = SdkTourAvailabilityResponse & {
16
+ allowOccupiedUnitTours?: boolean;
17
+ };