@meetelise/chat 1.51.14 → 1.52.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.
@@ -63,6 +63,7 @@ import { getTimezoneAbbreviation } from "../../getTimezoneString";
63
63
  import startOfDay from "date-fns/startOfDay";
64
64
  import isSameDay from "date-fns/isSameDay";
65
65
  import { LeadSourceMultitouchClient } from "../LeadSourceMultitouchClient";
66
+ import { readGoogleReserveReferral } from "../../googleReserve";
66
67
  import fetchBuildingUnitsSummary, {
67
68
  UnitSummary,
68
69
  } from "../../fetchBuildingUnitsSummary";
@@ -742,6 +743,7 @@ export class TourScheduler extends LitElement {
742
743
  unit: selectedUnit,
743
744
  conversationTrackingId: this.leadSourceClient?.chatId ?? null,
744
745
  });
746
+ const googleReferral = readGoogleReserveReferral();
745
747
  const data = {
746
748
  referrer: document.referrer,
747
749
  email_address: this.email,
@@ -774,6 +776,8 @@ export class TourScheduler extends LitElement {
774
776
  }
775
777
  ) ?? []
776
778
  ).map((source) => snakify({ ...source })),
779
+ google_rwg_token: googleReferral?.rwgToken,
780
+ google_referral_building_id: googleReferral?.buildingId,
777
781
  };
778
782
 
779
783
  const url = `https://app.meetelise.com/platformApi/state/create/v2/scheduleMe`;
@@ -325,7 +325,8 @@ export class CallUsWindow extends LitElement {
325
325
  ? html`By providing your number and clicking send, you consent to
326
326
  receive recurring commercial electronic messages from or on behalf
327
327
  of ${this.orgLegalName} at this number. Messages may be AI or
328
- human generated. Msg & Data rates may apply. You consent to this
328
+ human generated. Msg freq varies. Msg & Data rates may apply. You
329
+ consent to this
329
330
  <a
330
331
  target="_blank"
331
332
  rel="noopener noreferrer"
@@ -334,28 +335,30 @@ export class CallUsWindow extends LitElement {
334
335
  >, including having your number and communications recorded and
335
336
  used by our service provider to generate responses and improve
336
337
  their services. You may withdraw your consent at any time by
337
- unsubscribing or contacting us.`
338
+ unsubscribing or contacting us. Text STOP to opt-out of messages.
339
+ Text HELP for assistance.`
338
340
  : this.buildingId === 196098 // Liss Lawndale Apartments
339
341
  ? html`By providing your number and clicking send, you consent to
340
- receive recurring marketing calls and voice and text messages from
342
+ receive recurring marketing text and voice messages and calls from
341
343
  or on behalf of ${this.orgLegalName} at this number using
342
344
  artificial voice or an autodialer system. Messages may be AI or
343
345
  human generated. This consent is not required to lease at this
344
- property. Message frequency varies. Msg & Data rates may apply.
345
- Reply STOP to opt-out or HELP for help. You consent to this
346
+ property. Msg freq varies. Msg & Data rates may apply. Text STOP
347
+ to opt-out of messages. Text HELP for assistance. You consent to
348
+ this
346
349
  <a target="_blank" href="http://bit.ly/me_privacy_policy"
347
350
  >privacy policy</a
348
351
  >, including having your number and communications recorded and
349
352
  used by a third party.`
350
353
  : this.orgSlug === "greystar"
351
354
  ? html`By providing your phone number and clicking send, you consent
352
- to receive recurring marketing calls and voice and text messages
355
+ to receive recurring marketing text and voice messages and calls
353
356
  from Greystar or on its behalf at this number using artificial
354
357
  voice and/or an autodialer. Messages may be AI or human-generated.
355
358
  Your phone number and communications are shared with and recorded
356
359
  and used by EliseAI. You are not required to provide your phone
357
- number or consent to these terms to lease at this property. Msg &
358
- Data rates may apply. You consent to
360
+ number or consent to these terms to lease at this property. Msg
361
+ freq varies. Msg & Data rates may apply. You consent to
359
362
  <a
360
363
  href="https://www.greystar.com/privacy"
361
364
  target="_blank"
@@ -368,13 +371,14 @@ export class CallUsWindow extends LitElement {
368
371
  target="_blank"
369
372
  rel="noopener noreferrer"
370
373
  >EliseAI's Privacy Policy</a
371
- >.`
374
+ >. Text STOP to opt-out of messages. Text HELP for assistance.`
372
375
  : html`By providing your number and clicking send, you consent to
373
- receive recurring marketing calls and voice and text messages from
376
+ receive recurring marketing text and voice messages and calls from
374
377
  or on behalf of ${this.orgLegalName} at this number using
375
378
  artificial voice or an autodialer system. Messages may be AI or
376
379
  human generated. This consent is not required to lease at this
377
- property. Msg & Data rates may apply. You consent to this
380
+ property. Msg freq varies. Msg & Data rates may apply. You consent
381
+ to this
378
382
  <a target="_blank" href="http://bit.ly/me_privacy_policy"
379
383
  >privacy policy</a
380
384
  >
@@ -382,7 +386,8 @@ export class CallUsWindow extends LitElement {
382
386
  <a target="_blank" href="https://eliseai.com/msgterms"
383
387
  >terms and conditions</a
384
388
  >, including having your number and communications recorded and
385
- used by a third party.`}
389
+ used by a third party. Text STOP to opt-out of messages. Text HELP
390
+ for assistance.`}
386
391
  </div>
387
392
  </div>`;
388
393
  };
@@ -1,4 +1,4 @@
1
- import { html, LitElement, TemplateResult, css } from "lit";
1
+ import { html, LitElement, PropertyValues, TemplateResult, css } from "lit";
2
2
  import { customElement, property, state } from "lit/decorators.js";
3
3
  import { createRef, ref, Ref } from "lit/directives/ref.js";
4
4
  import { launcherStyles } from "./launcherStyles";
@@ -65,6 +65,9 @@ export class Launcher extends LitElement {
65
65
  @property({ type: Boolean })
66
66
  isFirstMount = false;
67
67
 
68
+ @property({ attribute: false })
69
+ openSSTWindowOnLoad = false;
70
+
68
71
  @property({ attribute: false })
69
72
  autoOpenChatWidget = false;
70
73
 
@@ -283,6 +286,15 @@ export class Launcher extends LitElement {
283
286
  tourSchedulerRef: Ref<TourScheduler> = createRef();
284
287
  calculatorWindowRef: Ref<FeeCalculator> = createRef();
285
288
 
289
+ willUpdate(changedProperties: PropertyValues<this>): void {
290
+ if (
291
+ changedProperties.has("openSSTWindowOnLoad") &&
292
+ this.openSSTWindowOnLoad
293
+ ) {
294
+ this.isSSTWindowOpen = true;
295
+ }
296
+ }
297
+
286
298
  updated = async (): Promise<void> => {
287
299
  this.attachOnClickToEmailUsWindow();
288
300
  this.attachOnClickToSSTWindow();
@@ -71,6 +71,10 @@ import {
71
71
 
72
72
  import "./chat-additional-actions";
73
73
  import { LeadSourceMultitouchClient } from "./LeadSourceMultitouchClient";
74
+ import {
75
+ shouldOpenTourSchedulerFromUrl,
76
+ storeGoogleReserveReferral,
77
+ } from "../googleReserve";
74
78
 
75
79
  @customElement("me-chat")
76
80
  export class MEChat extends LitElement {
@@ -187,6 +191,9 @@ export class MEChat extends LitElement {
187
191
  @state()
188
192
  private showTourNextToChat = false;
189
193
 
194
+ @state()
195
+ private openTourSchedulerOnLoad = false;
196
+
190
197
  @state()
191
198
  private displayPubnubChat = false;
192
199
 
@@ -321,19 +328,24 @@ export class MEChat extends LitElement {
321
328
 
322
329
  this.attachOnClickToLauncher();
323
330
 
324
- if (this.shouldAutoOpenChat(this.webchatConfigHasAutoOpenChat)) {
331
+ storeGoogleReserveReferral(this.buildingWebchatView?.id ?? 0);
332
+
333
+ if (
334
+ shouldOpenTourSchedulerFromUrl() &&
335
+ (this.enabledChatWidgets.sstDesktop || this.enabledChatWidgets.sstMobile)
336
+ ) {
337
+ this.openTourSchedulerOnLoad = true;
338
+ } else if (this.shouldAutoOpenChat(this.webchatConfigHasAutoOpenChat)) {
325
339
  this.displayPubnubChat = true;
326
340
  this.hideLauncher = true;
327
341
  this.hasMounted = true;
328
342
  this.updateAlreadyAutoOpenedTimestamp();
329
- }
330
-
331
- this.isLoading = false;
332
-
333
- if (localStorage.getItem("isChatOpen") === "true") {
343
+ } else if (localStorage.getItem("isChatOpen") === "true") {
334
344
  this.hideLauncher = true;
335
345
  this.displayPubnubChat = true;
336
346
  }
347
+
348
+ this.isLoading = false;
337
349
  };
338
350
 
339
351
  // The general info for the buildingg
@@ -1080,6 +1092,7 @@ export class MEChat extends LitElement {
1080
1092
  virtualToursTypeOffered="${this.buildingWebchatView
1081
1093
  .virtualToursTypeOffered ?? ""}"
1082
1094
  .showTourNextToChat="${this.showTourNextToChat ?? ""}"
1095
+ .openSSTWindowOnLoad=${this.openTourSchedulerOnLoad}
1083
1096
  @closeShowTourNextToChat=${() => {
1084
1097
  this.showTourNextToChat = false;
1085
1098
  this.hideLauncher = true;
@@ -63,6 +63,10 @@ const formDisclaimer = ({
63
63
  recorded and used by our service provider EliseAI to generate responses
64
64
  and improve their services. You may withdraw your consent at any time by
65
65
  unsubscribing or contacting us.
66
+ ${phoneNumberInput
67
+ ? html`Msg freq varies. Text STOP to opt-out of messages. Text HELP
68
+ for assistance.`
69
+ : ""}
66
70
  </div>`;
67
71
  }
68
72
 
@@ -81,19 +85,23 @@ const formDisclaimer = ({
81
85
  recorded and used by our service provider EliseAI to generate responses
82
86
  and improve their services. You may withdraw your consent at any time by
83
87
  unsubscribing or contacting us.
88
+ ${phoneNumberInput
89
+ ? html`Msg freq varies. Text STOP to opt-out of messages. Text HELP for
90
+ assistance.`
91
+ : ""}
84
92
  </div>`;
85
93
  }
86
94
 
87
95
  if (phoneNumberInput && orgSlug === "greystar") {
88
96
  return html`<div style=${styleMap(disclaimerStyles.container)}>
89
97
  By providing your phone number and clicking ${clickingButtonText}, you
90
- consent to receive recurring marketing calls and voice and text messages
98
+ consent to receive recurring marketing text and voice messages and calls
91
99
  from Greystar or on its behalf at this number using artificial voice
92
100
  and/or an autodialer. Messages may be AI or human-generated. Your phone
93
101
  number and communications are shared with and recorded and used by
94
102
  EliseAI. You are not required to provide your phone number or consent to
95
- these terms to lease at this property. Msg & Data rates may apply. You
96
- consent to
103
+ these terms to lease at this property. Msg freq varies. Msg & Data rates
104
+ may apply. You consent to
97
105
  <a
98
106
  style=${styleMap(disclaimerStyles.link)}
99
107
  href="https://www.greystar.com/privacy"
@@ -108,17 +116,17 @@ const formDisclaimer = ({
108
116
  target="_blank"
109
117
  rel="noopener noreferrer"
110
118
  >EliseAI’s Privacy Policy</a
111
- >.
119
+ >. Text STOP to opt-out of messages. Text HELP for assistance.
112
120
  </div>`;
113
121
  }
114
122
  if (phoneNumberInput) {
115
123
  return html` <div style=${styleMap(disclaimerStyles.container)}>
116
124
  By providing your number and clicking ${clickingButtonText}, you consent
117
- to receive recurring marketing calls and voice and text messages from or
125
+ to receive recurring marketing text and voice messages and calls from or
118
126
  on behalf of ${orgLegalName} at this number using artificial voice or an
119
127
  autodialer system. Messages may be AI or human generated. This consent is
120
- not required to lease at this property. Msg & Data rates may apply. You
121
- consent to this
128
+ not required to lease at this property. Msg freq varies. Msg & Data rates
129
+ may apply. You consent to this
122
130
  <a
123
131
  style=${styleMap(disclaimerStyles.link)}
124
132
  href=${policyUrl}
@@ -126,7 +134,7 @@ const formDisclaimer = ({
126
134
  rel="noopener noreferrer"
127
135
  >privacy policy</a
128
136
  >, including having your number and communications recorded and used by a
129
- third party.
137
+ third party. Text STOP to opt-out of messages. Text HELP for assistance.
130
138
  </div>`;
131
139
  }
132
140
  if (emailInput) {
@@ -394,6 +402,7 @@ export const inChatThreadDisclaimer = ({
394
402
  </li>
395
403
  `;
396
404
  }
405
+
397
406
  return html``;
398
407
  };
399
408
 
@@ -0,0 +1,60 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import {
3
+ readGoogleReserveReferral,
4
+ shouldOpenTourSchedulerFromUrl,
5
+ storeGoogleReserveReferral,
6
+ } from "./googleReserve";
7
+
8
+ const STORAGE_KEY = "com.eliseai.webchat.googlereserve.referral";
9
+
10
+ const visit = (search: string): void =>
11
+ history.replaceState(null, "", `${location.pathname}${search}`);
12
+
13
+ describe("Google Reserve", () => {
14
+ const originalSearch = location.search;
15
+
16
+ beforeEach(() => {
17
+ localStorage.removeItem(STORAGE_KEY);
18
+ });
19
+
20
+ afterEach(() => {
21
+ localStorage.removeItem(STORAGE_KEY);
22
+ visit(originalSearch);
23
+ });
24
+
25
+ it("remembers a click from a Google listing, keeping the token intact", () => {
26
+ visit("?elise_open=tour&rwg_token=AFd1x%3D%3D");
27
+ storeGoogleReserveReferral(7);
28
+ visit("");
29
+
30
+ expect(readGoogleReserveReferral()).to.include({
31
+ rwgToken: "AFd1x==",
32
+ buildingId: 7,
33
+ });
34
+ });
35
+
36
+ it("has no referral without a token or once it has expired", () => {
37
+ visit("?utm_source=website");
38
+ storeGoogleReserveReferral(7);
39
+ expect(readGoogleReserveReferral()).to.equal(null);
40
+
41
+ localStorage.setItem(
42
+ STORAGE_KEY,
43
+ JSON.stringify({
44
+ rwgToken: "AFd1x==",
45
+ buildingId: 7,
46
+ expiresAt: Date.now() - 1,
47
+ })
48
+ );
49
+ expect(readGoogleReserveReferral()).to.equal(null);
50
+ });
51
+
52
+ it("opens the tour scheduler only when the URL asks for it", () => {
53
+ visit("?elise_open=tour");
54
+ expect(shouldOpenTourSchedulerFromUrl()).to.equal(true);
55
+ visit("?elise_open=chat");
56
+ expect(shouldOpenTourSchedulerFromUrl()).to.equal(false);
57
+ visit("");
58
+ expect(shouldOpenTourSchedulerFromUrl()).to.equal(false);
59
+ });
60
+ });
@@ -0,0 +1,48 @@
1
+ // Google requires the referral token kept for 30 days after the click.
2
+ const REFERRAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
3
+ const REFERRAL_STORAGE_KEY = "com.eliseai.webchat.googlereserve.referral";
4
+ // Google "Book" links to a customer's page add this so the widget opens the tour scheduler.
5
+ const OPEN_PARAM = "elise_open";
6
+
7
+ type GoogleReserveReferral = {
8
+ rwgToken: string;
9
+ buildingId: number;
10
+ expiresAt: number;
11
+ };
12
+
13
+ export const shouldOpenTourSchedulerFromUrl = (): boolean =>
14
+ new URLSearchParams(window.location.search).get(OPEN_PARAM) === "tour";
15
+
16
+ /**
17
+ * Remembers a click from a building's Google listing, which arrives with an
18
+ * `rwg_token` query param, so a booking within 30 days can be reported back.
19
+ */
20
+ export const storeGoogleReserveReferral = (buildingId: number): void => {
21
+ const rwgToken = new URLSearchParams(window.location.search).get("rwg_token");
22
+ if (!rwgToken || !buildingId) {
23
+ return;
24
+ }
25
+ const referral: GoogleReserveReferral = {
26
+ rwgToken,
27
+ buildingId,
28
+ expiresAt: Date.now() + REFERRAL_TTL_MS,
29
+ };
30
+ localStorage.setItem(REFERRAL_STORAGE_KEY, JSON.stringify(referral));
31
+ };
32
+
33
+ /**
34
+ * The visitor's click from a building's Google listing within the last 30 days,
35
+ * sent with a booking so the backend can report it to Google.
36
+ */
37
+ export const readGoogleReserveReferral = (): GoogleReserveReferral | null => {
38
+ try {
39
+ const referral: GoogleReserveReferral | null = JSON.parse(
40
+ localStorage.getItem(REFERRAL_STORAGE_KEY) ?? "null"
41
+ );
42
+ return referral?.rwgToken && Date.now() < referral.expiresAt
43
+ ? referral
44
+ : null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ };