@meetelise/chat 1.50.0 → 1.50.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.
@@ -27,8 +27,6 @@ import { LogType } from "../../analytics";
27
27
  import { pushGtmEvent } from "../../gtm";
28
28
  import fetchBuildingUnits from "../../services/fees/fetchBuildingUnits";
29
29
  import { layoutToBedrooms } from "../../services/fees/utils";
30
- import parseISO from "date-fns/parseISO";
31
- import isBefore from "date-fns/isBefore";
32
30
 
33
31
  import "./components";
34
32
  import "./components/fee-calculator-layout/fee-calculator-layout";
@@ -163,14 +161,7 @@ export class FeeCalculator extends LitElement {
163
161
  return;
164
162
  }
165
163
 
166
- // If lead's desired move in date is before the their selected unit's earliest available date
167
- // use the unit's earliest available date (otherwise pricing will be incorrect)
168
- const selectedMoveInDate = parseISO(this.moveInDateString);
169
- const earliestAvailableDate = parseISO(this.selectedUnit.earliestAvailable);
170
-
171
- const moveInDate = isBefore(selectedMoveInDate, earliestAvailableDate)
172
- ? this.selectedUnit.earliestAvailable
173
- : this.moveInDateString;
164
+ const moveInDate = this.moveInDateString;
174
165
 
175
166
  // Capture values before await to avoid race conditions
176
167
  const unitId = this.selectedUnit.id;
@@ -439,9 +430,9 @@ export class FeeCalculator extends LitElement {
439
430
  try {
440
431
  this.units = await fetchBuildingUnits({
441
432
  buildingSlug: this.buildingSlug,
442
- moveInDateEarliestString: this.moveInDateString,
433
+ moveInDateString: this.moveInDateString,
443
434
  ...(numBedrooms ? { numBedrooms } : {}),
444
- leaseTermMin: this.leaseTerm,
435
+ leaseTerm: this.leaseTerm,
445
436
  });
446
437
 
447
438
  if (this.selectedUnit) {
@@ -869,6 +869,7 @@ export class TourScheduler extends LitElement {
869
869
  leadSources.find((i) => i !== "property-website") || null,
870
870
  layout: selectedLayout,
871
871
  unit: selectedUnit,
872
+ conversationTrackingId: this.leadSourceClient?.chatId ?? null,
872
873
  });
873
874
  const data = {
874
875
  referrer: document.referrer,
@@ -507,6 +507,7 @@ const createTextWithUs = async (
507
507
  phone: formattedPhoneNumber,
508
508
  originatingSource:
509
509
  leadSources.find((i) => i !== "property-website") || null,
510
+ conversationTrackingId: chatId ?? null,
510
511
  });
511
512
  await axios.post(
512
513
  "https://app.meetelise.com/platformApi/state/create/textMe",
@@ -569,6 +569,7 @@ const createEmail = async (
569
569
  message,
570
570
  originatingSource:
571
571
  leadSources.find((i) => i !== "property-website") || null,
572
+ conversationTrackingId: chatId ?? null,
572
573
  });
573
574
  await axios.post(
574
575
  "https://app.meetelise.com/platformApi/state/create/emailMe",
@@ -15,6 +15,7 @@ import {
15
15
  fetchFeatureFlagUseApplicationsLinkReplacement,
16
16
  fetchFeatureFlagUseOverrideContactUsForm,
17
17
  } from "../fetchFeatureFlag";
18
+ import { HIDE_DATA_LAYER_PII_ORG_IDS, setGtmPiiRedaction } from "../gtm";
18
19
  import fetchPhoneNumberFromSource, {
19
20
  NumberForSelectedSource,
20
21
  } from "../fetchPhoneNumberFromSource";
@@ -355,6 +356,10 @@ export class MEChat extends LitElement {
355
356
  const featureFlagReplaceScheduleTourCtaWebsite =
356
357
  buildingDetails.featureFlagWebchatReplaceAnyScheduleTourCtaWebsite;
357
358
 
359
+ setGtmPiiRedaction(
360
+ HIDE_DATA_LAYER_PII_ORG_IDS.includes(buildingDetails.orgId)
361
+ );
362
+
358
363
  const [leadSources, featureFlagUseApplicationsLinkReplacement, isAip] =
359
364
  await Promise.all([
360
365
  fetchLeadSources(this.orgSlug, this.buildingSlug),
@@ -0,0 +1,74 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import { pushGtmEvent, setGtmPiiRedaction } from "./gtm";
3
+
4
+ afterEach(() => {
5
+ setGtmPiiRedaction(false);
6
+ window.dataLayer = [];
7
+ });
8
+
9
+ it("pushes the full payload when PII redaction is off", () => {
10
+ window.dataLayer = [];
11
+
12
+ pushGtmEvent("scheduleTourSubmitted", {
13
+ email: "lead@example.com",
14
+ phone: "+12125555555",
15
+ firstName: "Ada",
16
+ lastName: "Lovelace",
17
+ tourType: "in-person",
18
+ conversationTrackingId: "chat-123",
19
+ });
20
+
21
+ expect(window.dataLayer).to.deep.equal([
22
+ {
23
+ event: "scheduleTourSubmitted",
24
+ email: "lead@example.com",
25
+ phone: "+12125555555",
26
+ firstName: "Ada",
27
+ lastName: "Lovelace",
28
+ tourType: "in-person",
29
+ conversationTrackingId: "chat-123",
30
+ },
31
+ ]);
32
+ });
33
+
34
+ it("strips PII keys but keeps the rest when PII redaction is on", () => {
35
+ window.dataLayer = [];
36
+ setGtmPiiRedaction(true);
37
+
38
+ pushGtmEvent("scheduleTourSubmitted", {
39
+ email: "lead@example.com",
40
+ phone: "+12125555555",
41
+ firstName: "Ada",
42
+ lastName: "Lovelace",
43
+ message: "Hi, is the 2BR still available?",
44
+ tourDetails: { tourTime: "2026-08-01T09:00:00-07:00" },
45
+ tourType: "in-person",
46
+ originatingSource: null,
47
+ conversationTrackingId: "chat-123",
48
+ });
49
+
50
+ expect(window.dataLayer).to.deep.equal([
51
+ {
52
+ event: "scheduleTourSubmitted",
53
+ tourType: "in-person",
54
+ originatingSource: null,
55
+ conversationTrackingId: "chat-123",
56
+ },
57
+ ]);
58
+ });
59
+
60
+ it("does not mutate the caller's event object when redacting", () => {
61
+ window.dataLayer = [];
62
+ setGtmPiiRedaction(true);
63
+ const event: Record<string, unknown> = {
64
+ email: "lead@example.com",
65
+ tourType: "in-person",
66
+ };
67
+
68
+ pushGtmEvent("scheduleTourSubmitted", event);
69
+
70
+ expect(event).to.deep.equal({
71
+ email: "lead@example.com",
72
+ tourType: "in-person",
73
+ });
74
+ });
package/src/gtm.ts CHANGED
@@ -5,13 +5,39 @@ declare global {
5
5
  }
6
6
  }
7
7
 
8
+ // MAA (org 57) and the MAA test org (2575) asked us not to push lead PII
9
+ // into the host page's dataLayer
10
+ export const HIDE_DATA_LAYER_PII_ORG_IDS = [57, 2575];
11
+
12
+ const GTM_PII_KEYS = [
13
+ "email",
14
+ "phone",
15
+ "firstName",
16
+ "lastName",
17
+ "message",
18
+ "tourDetails",
19
+ ];
20
+
21
+ let hideDataLayerPii = false;
22
+
23
+ export const setGtmPiiRedaction = (enabled: boolean): void => {
24
+ hideDataLayerPii = enabled;
25
+ };
26
+
8
27
  export const pushGtmEvent = (
9
28
  eventName: string,
10
29
  event: Record<string, unknown> = {}
11
30
  ): void => {
12
31
  window.dataLayer = window.dataLayer || [];
32
+ let eventToPush = event;
33
+ if (hideDataLayerPii) {
34
+ eventToPush = { ...event };
35
+ for (const piiKey of GTM_PII_KEYS) {
36
+ delete eventToPush[piiKey];
37
+ }
38
+ }
13
39
  window.dataLayer.push({
14
40
  event: eventName,
15
- ...event,
41
+ ...eventToPush,
16
42
  });
17
43
  };
@@ -0,0 +1,35 @@
1
+ import QueryParamBuilder from "../../utils/queryParamBuilder";
2
+
3
+ type BuildingUnitsQueryParams = {
4
+ numBedrooms?: number[];
5
+ moveInDateString?: string;
6
+ leaseTerm?: number;
7
+ };
8
+
9
+ export const buildBuildingUnitsQuery = ({
10
+ numBedrooms,
11
+ moveInDateString,
12
+ leaseTerm,
13
+ }: BuildingUnitsQueryParams): string => {
14
+ const queryParamBuilder = new QueryParamBuilder();
15
+ queryParamBuilder
16
+ .addArray(
17
+ "num_bedrooms",
18
+ numBedrooms?.map((num) => num.toString())
19
+ )
20
+ .addSingle("move_in_date_earliest", moveInDateString)
21
+ .addSingle("move_in_date_latest", moveInDateString)
22
+ .addSingle("lease_term_min", leaseTerm?.toString());
23
+
24
+ return queryParamBuilder.build();
25
+ };
26
+
27
+ export const sortUnitsByStartingPrice = <
28
+ T extends { id: number; startingPrice: number }
29
+ >(
30
+ units: T[]
31
+ ): T[] =>
32
+ [...units].sort((firstUnit, secondUnit) => {
33
+ const priceDifference = firstUnit.startingPrice - secondUnit.startingPrice;
34
+ return priceDifference || firstUnit.id - secondUnit.id;
35
+ });
@@ -0,0 +1,30 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import {
3
+ buildBuildingUnitsQuery,
4
+ sortUnitsByStartingPrice,
5
+ } from "./buildBuildingUnitsQuery";
6
+
7
+ it("requests units for the selected move-in date", () => {
8
+ const queryString = buildBuildingUnitsQuery({
9
+ moveInDateString: "2026-08-01",
10
+ leaseTerm: 12,
11
+ });
12
+
13
+ expect(queryString).to.include("move_in_date_earliest=2026-08-01");
14
+ expect(queryString).to.include("move_in_date_latest=2026-08-01");
15
+ expect(queryString).not.to.include("lease_term=12");
16
+ expect(queryString).to.include("lease_term_min=12");
17
+ });
18
+
19
+ it("sorts the cheapest units first without mutating the API response", () => {
20
+ const units = [
21
+ { id: 3, startingPrice: 2699 },
22
+ { id: 2, startingPrice: 2574 },
23
+ { id: 1, startingPrice: 2574 },
24
+ ];
25
+
26
+ const sortedUnits = sortUnitsByStartingPrice(units);
27
+
28
+ expect(sortedUnits.map((unit) => unit.id)).to.deep.equal([1, 2, 3]);
29
+ expect(units.map((unit) => unit.id)).to.deep.equal([3, 2, 1]);
30
+ });
@@ -2,7 +2,10 @@ import axios from "axios";
2
2
  import { LogType, sendLoggingEvent } from "../../analytics";
3
3
  import { BASE_DOMAIN } from "../../globals";
4
4
  import { camelize } from "../../utils";
5
- import QueryParamBuilder from "../../utils/queryParamBuilder";
5
+ import {
6
+ buildBuildingUnitsQuery,
7
+ sortUnitsByStartingPrice,
8
+ } from "./buildBuildingUnitsQuery";
6
9
 
7
10
  const BASE_URL = BASE_DOMAIN;
8
11
 
@@ -30,44 +33,34 @@ export type Unit = {
30
33
  type FetchPropertyUnitsParams = {
31
34
  buildingSlug: string;
32
35
  numBedrooms?: number[];
33
- moveInDateEarliestString?: string;
34
- moveInDateLatest?: Date;
35
- leaseTermMin?: number;
36
+ moveInDateString?: string;
37
+ leaseTerm?: number;
36
38
  };
37
39
 
38
40
  const fetchBuildingUnits = async ({
39
41
  buildingSlug,
40
42
  numBedrooms,
41
- moveInDateEarliestString,
42
- moveInDateLatest,
43
- leaseTermMin,
43
+ moveInDateString,
44
+ leaseTerm,
44
45
  }: FetchPropertyUnitsParams): Promise<Unit[]> => {
45
46
  try {
46
- const latestDateStr = moveInDateLatest
47
- ? moveInDateLatest.toISOString().split("T")[0]
48
- : undefined;
49
-
50
- const queryParamBuilder = new QueryParamBuilder();
51
- queryParamBuilder
52
- .addArray(
53
- "num_bedrooms",
54
- numBedrooms?.map((num) => num.toString())
55
- )
56
- .addSingle("move_in_date_earliest", moveInDateEarliestString)
57
- .addSingle("move_in_date_latest", latestDateStr)
58
- .addSingle("lease_term_min", leaseTermMin?.toString());
59
-
60
- const queryString = queryParamBuilder.build();
47
+ const queryString = buildBuildingUnitsQuery({
48
+ numBedrooms,
49
+ moveInDateString,
50
+ leaseTerm,
51
+ });
61
52
  const propertyUnitsResponse = await axios.get(
62
53
  `${BASE_URL}/platformApi/webchat/${buildingSlug}/units${queryString}`
63
54
  );
64
55
 
65
56
  if (propertyUnitsResponse.data) {
66
57
  const camelizedUnits = camelize<Unit[]>(propertyUnitsResponse.data);
67
- return camelizedUnits.map((unit) => ({
68
- ...unit,
69
- id: parseInt(unit.id.toString().substring(1)),
70
- }));
58
+ return sortUnitsByStartingPrice(
59
+ camelizedUnits.map((unit) => ({
60
+ ...unit,
61
+ id: parseInt(unit.id.toString().substring(1)),
62
+ }))
63
+ );
71
64
  }
72
65
  } catch (error) {
73
66
  sendLoggingEvent({
@@ -76,7 +69,7 @@ const fetchBuildingUnits = async ({
76
69
  logTitle: "[ERROR_GETTING_UNITS]",
77
70
  logData: {
78
71
  error,
79
- params: { numBedrooms, moveInDateEarliestString, moveInDateLatest },
72
+ params: { numBedrooms, moveInDateString, leaseTerm },
80
73
  },
81
74
  });
82
75
  }