@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.
- package/dist/src/gtm.d.ts +2 -0
- package/dist/src/services/fees/buildBuildingUnitsQuery.d.ts +11 -0
- package/dist/src/services/fees/fetchBuildingUnits.d.ts +3 -4
- package/package.json +1 -1
- package/public/dist/index.js +228 -228
- package/src/WebComponent/FeeCalculator/fee-calculator.ts +3 -12
- package/src/WebComponent/Scheduler/tour-scheduler.ts +1 -0
- package/src/WebComponent/actions/call-us-window.ts +1 -0
- package/src/WebComponent/actions/email-us-window.ts +1 -0
- package/src/WebComponent/me-chat.ts +5 -0
- package/src/gtm.test.ts +74 -0
- package/src/gtm.ts +27 -1
- package/src/services/fees/buildBuildingUnitsQuery.ts +35 -0
- package/src/services/fees/fetchBuildingUnits.test.ts +30 -0
- package/src/services/fees/fetchBuildingUnits.ts +20 -27
|
@@ -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
|
-
|
|
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
|
-
|
|
433
|
+
moveInDateString: this.moveInDateString,
|
|
443
434
|
...(numBedrooms ? { numBedrooms } : {}),
|
|
444
|
-
|
|
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),
|
package/src/gtm.test.ts
ADDED
|
@@ -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
|
-
...
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
leaseTermMin,
|
|
43
|
+
moveInDateString,
|
|
44
|
+
leaseTerm,
|
|
44
45
|
}: FetchPropertyUnitsParams): Promise<Unit[]> => {
|
|
45
46
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
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,
|
|
72
|
+
params: { numBedrooms, moveInDateString, leaseTerm },
|
|
80
73
|
},
|
|
81
74
|
});
|
|
82
75
|
}
|