@meetelise/chat 1.51.15 → 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`;
@@ -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;
@@ -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
+ };