@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.
@@ -11,8 +11,7 @@ import "./time-picker.ts";
11
11
  import "./me-select.ts";
12
12
  import {
13
13
  DateWithTimeZoneOffset,
14
- getAvailabilitiesGroupedByDay,
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#firstName")
64
- firstNameInput!: HTMLInputElement;
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 getAvailabilitiesGroupedByDay(
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 = await getAvailabilitiesGroupedByDay(
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
- (!!this.firstNameInput?.value || !!this.lastNameInput?.value) &&
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
- first_name: this.firstNameInput.value,
302
- last_name: this.lastNameInput.value,
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.shouldShowTourType[TourType.Guided]
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.shouldShowTourType[TourType.Self]
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.shouldShowTourType[TourType.Virtual]
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="First name"
847
- id="firstName"
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
- Layout dropdown would go here, but has been removed pending backend support.
885
- Here is the code to add it back:
886
- https://github.com/MeetElise/chat-ui/blob/e17aca8b39a0eed9430f22c182f2ebcdfb796417/src/WebComponent/Scheduler/tour-scheduler.ts#L846-L863
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?.orgId === 182 || this.building?.id === 4930) {
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 the following line to test changes to the default avatar if your test building has its own avatar
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;
@@ -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?: number | null;
17
- availabilities: { [buildingId: number]: TourAvailabilityResponse };
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?: number
19
+ buildingId: number
28
20
  ): Promise<TourAvailabilityResponse> => {
29
- if (buildingId) {
30
- availabilitiesCache.buildingId = buildingId;
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/${buildingIdToUse}/tour/availabilities?startTime=${formatISO(
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.availabilities[buildingIdToUse] = result.data;
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
- tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum,
55
- buildingId?: number
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
- tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum,
124
- buildingId?: number
72
+ buildingId: number,
73
+ tourType: TourAvailabilityResponseRankOrderedSupportedTourTypesEnum
125
74
  ): Promise<{ [day: string]: DateWithTimeZoneOffset[] }> => {
126
75
  const availabilitiesForTourTypeRaw = await getAvailabilitiesForTourType(
127
- tourType,
128
- buildingId
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.