@meetelise/chat 1.51.15 → 1.53.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.
- package/dist/src/WebComponent/Scheduler/tour-scheduler.d.ts +1 -0
- package/dist/src/WebComponent/actions/call-us-window.d.ts +1 -0
- package/dist/src/WebComponent/actions/email-us-window.d.ts +1 -0
- package/dist/src/WebComponent/launcher/Launcher.d.ts +4 -1
- package/dist/src/WebComponent/me-chat.d.ts +1 -0
- package/dist/src/disclaimers.d.ts +5 -1
- package/dist/src/googleReserve.d.ts +17 -0
- package/package.json +1 -1
- package/public/dist/index.js +380 -306
- package/src/WebComponent/Scheduler/tour-scheduler.ts +9 -0
- package/src/WebComponent/actions/call-us-window.ts +7 -1
- package/src/WebComponent/actions/email-us-window.ts +3 -0
- package/src/WebComponent/launcher/Launcher.ts +18 -1
- package/src/WebComponent/me-chat.ts +20 -6
- package/src/disclaimers.ts +138 -0
- package/src/googleReserve.test.ts +60 -0
- package/src/googleReserve.ts +48 -0
- package/src/quebecConsentDisclaimers.test.ts +161 -0
|
@@ -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";
|
|
@@ -194,6 +195,8 @@ export class TourScheduler extends LitElement {
|
|
|
194
195
|
|
|
195
196
|
@property({ attribute: false })
|
|
196
197
|
country: string | null = null;
|
|
198
|
+
@property({ attribute: false })
|
|
199
|
+
buildingState: string | null = null;
|
|
197
200
|
|
|
198
201
|
@property({ attribute: true })
|
|
199
202
|
currentLeadSource = ""; // the default lead source based on referrer and query params.
|
|
@@ -742,6 +745,7 @@ export class TourScheduler extends LitElement {
|
|
|
742
745
|
unit: selectedUnit,
|
|
743
746
|
conversationTrackingId: this.leadSourceClient?.chatId ?? null,
|
|
744
747
|
});
|
|
748
|
+
const googleReferral = readGoogleReserveReferral();
|
|
745
749
|
const data = {
|
|
746
750
|
referrer: document.referrer,
|
|
747
751
|
email_address: this.email,
|
|
@@ -774,6 +778,8 @@ export class TourScheduler extends LitElement {
|
|
|
774
778
|
}
|
|
775
779
|
) ?? []
|
|
776
780
|
).map((source) => snakify({ ...source })),
|
|
781
|
+
google_rwg_token: googleReferral?.rwgToken,
|
|
782
|
+
google_referral_building_id: googleReferral?.buildingId,
|
|
777
783
|
};
|
|
778
784
|
|
|
779
785
|
const url = `https://app.meetelise.com/platformApi/state/create/v2/scheduleMe`;
|
|
@@ -1641,6 +1647,7 @@ export class TourScheduler extends LitElement {
|
|
|
1641
1647
|
orgLegalName: this.orgLegalName,
|
|
1642
1648
|
orgSlug: this.orgSlug,
|
|
1643
1649
|
country: this.country,
|
|
1650
|
+
buildingState: this.buildingState,
|
|
1644
1651
|
clickingButtonText: "schedule tour",
|
|
1645
1652
|
})}
|
|
1646
1653
|
</div>
|
|
@@ -1696,6 +1703,7 @@ export class TourScheduler extends LitElement {
|
|
|
1696
1703
|
orgLegalName: this.orgLegalName,
|
|
1697
1704
|
orgSlug: this.orgSlug,
|
|
1698
1705
|
country: this.country,
|
|
1706
|
+
buildingState: this.buildingState,
|
|
1699
1707
|
clickingButtonText: "schedule tour",
|
|
1700
1708
|
})}
|
|
1701
1709
|
</div>
|
|
@@ -1806,6 +1814,7 @@ export class TourScheduler extends LitElement {
|
|
|
1806
1814
|
orgLegalName: this.orgLegalName,
|
|
1807
1815
|
orgSlug: this.orgSlug,
|
|
1808
1816
|
country: this.country,
|
|
1817
|
+
buildingState: this.buildingState,
|
|
1809
1818
|
clickingButtonText: "schedule tour",
|
|
1810
1819
|
})}
|
|
1811
1820
|
`
|
|
@@ -25,6 +25,8 @@ import { InputStyles } from "./InputStyles";
|
|
|
25
25
|
import {
|
|
26
26
|
CANADA_PRIVACY_POLICY_URL,
|
|
27
27
|
isCanadianBuilding,
|
|
28
|
+
isQuebecBuilding,
|
|
29
|
+
quebecConsentDisclaimer,
|
|
28
30
|
} from "../../disclaimers";
|
|
29
31
|
|
|
30
32
|
@customElement("call-us-window")
|
|
@@ -173,6 +175,8 @@ export class CallUsWindow extends LitElement {
|
|
|
173
175
|
orgLegalName = "";
|
|
174
176
|
@property({ attribute: false })
|
|
175
177
|
country: string | null = null;
|
|
178
|
+
@property({ attribute: false })
|
|
179
|
+
buildingState: string | null = null;
|
|
176
180
|
|
|
177
181
|
@property({ attribute: true })
|
|
178
182
|
private leadSourceClient: LeadSourceClient | null = null;
|
|
@@ -321,7 +325,9 @@ export class CallUsWindow extends LitElement {
|
|
|
321
325
|
</div>
|
|
322
326
|
|
|
323
327
|
<div class="text-us-window__description">
|
|
324
|
-
${
|
|
328
|
+
${isQuebecBuilding(this.buildingState)
|
|
329
|
+
? quebecConsentDisclaimer("textUs", this.orgLegalName)
|
|
330
|
+
: isCanadianBuilding(this.country)
|
|
325
331
|
? html`By providing your number and clicking send, you consent to
|
|
326
332
|
receive recurring commercial electronic messages from or on behalf
|
|
327
333
|
of ${this.orgLegalName} at this number. Messages may be AI or
|
|
@@ -121,6 +121,8 @@ export class EmailUsWindow extends LitElement {
|
|
|
121
121
|
orgLegalName = "";
|
|
122
122
|
@property({ attribute: false })
|
|
123
123
|
country: string | null = null;
|
|
124
|
+
@property({ attribute: false })
|
|
125
|
+
buildingState: string | null = null;
|
|
124
126
|
|
|
125
127
|
@property({ attribute: true })
|
|
126
128
|
private leadSourceClient: LeadSourceClient | null = null;
|
|
@@ -492,6 +494,7 @@ export class EmailUsWindow extends LitElement {
|
|
|
492
494
|
orgLegalName: this.orgLegalName,
|
|
493
495
|
orgSlug: this.orgSlug,
|
|
494
496
|
country: this.country,
|
|
497
|
+
buildingState: this.buildingState,
|
|
495
498
|
clickingButtonText: "send",
|
|
496
499
|
})}
|
|
497
500
|
</details-window>
|
|
@@ -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
|
|
|
@@ -78,6 +81,8 @@ export class Launcher extends LitElement {
|
|
|
78
81
|
orgLegalName = "";
|
|
79
82
|
@property({ attribute: false })
|
|
80
83
|
country: string | null = null;
|
|
84
|
+
@property({ attribute: false })
|
|
85
|
+
buildingState: string | null = null;
|
|
81
86
|
@property({ attribute: true })
|
|
82
87
|
chatCallUsHeader = "";
|
|
83
88
|
@property({ attribute: false })
|
|
@@ -283,6 +288,15 @@ export class Launcher extends LitElement {
|
|
|
283
288
|
tourSchedulerRef: Ref<TourScheduler> = createRef();
|
|
284
289
|
calculatorWindowRef: Ref<FeeCalculator> = createRef();
|
|
285
290
|
|
|
291
|
+
willUpdate(changedProperties: PropertyValues<this>): void {
|
|
292
|
+
if (
|
|
293
|
+
changedProperties.has("openSSTWindowOnLoad") &&
|
|
294
|
+
this.openSSTWindowOnLoad
|
|
295
|
+
) {
|
|
296
|
+
this.isSSTWindowOpen = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
286
300
|
updated = async (): Promise<void> => {
|
|
287
301
|
this.attachOnClickToEmailUsWindow();
|
|
288
302
|
this.attachOnClickToSSTWindow();
|
|
@@ -1328,6 +1342,7 @@ export class Launcher extends LitElement {
|
|
|
1328
1342
|
.leadSourceMultitouchClient=${this.leadSourceMultitouchClient}
|
|
1329
1343
|
.orgLegalName=${this.orgLegalName}
|
|
1330
1344
|
.country=${this.country}
|
|
1345
|
+
.buildingState=${this.buildingState}
|
|
1331
1346
|
>
|
|
1332
1347
|
</email-us-window>
|
|
1333
1348
|
</div>`
|
|
@@ -1357,6 +1372,7 @@ export class Launcher extends LitElement {
|
|
|
1357
1372
|
.bedroomPreferenceRequired=${this.bedroomPreferenceRequired}
|
|
1358
1373
|
.orgLegalName=${this.orgLegalName}
|
|
1359
1374
|
.country=${this.country}
|
|
1375
|
+
.buildingState=${this.buildingState}
|
|
1360
1376
|
buildingId=${this.buildingId}
|
|
1361
1377
|
orgId=${this.orgId}
|
|
1362
1378
|
.leadSourceParams=${this.leadSourceParams}
|
|
@@ -1402,6 +1418,7 @@ export class Launcher extends LitElement {
|
|
|
1402
1418
|
.leadSourceMultitouchClient=${this.leadSourceMultitouchClient}
|
|
1403
1419
|
.orgLegalName=${this.orgLegalName}
|
|
1404
1420
|
.country=${this.country}
|
|
1421
|
+
.buildingState=${this.buildingState}
|
|
1405
1422
|
></call-us-window>
|
|
1406
1423
|
</div>
|
|
1407
1424
|
`
|
|
@@ -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
|
-
|
|
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
|
|
@@ -1032,6 +1044,7 @@ export class MEChat extends LitElement {
|
|
|
1032
1044
|
?.usesDynamicScheduling ?? false}
|
|
1033
1045
|
.orgLegalName=${this.buildingWebchatView?.orgLegalName ?? ""}
|
|
1034
1046
|
.country=${this.buildingWebchatView?.country ?? null}
|
|
1047
|
+
.buildingState=${this.buildingWebchatView?.state ?? null}
|
|
1035
1048
|
.tourTypeOptions=${this.buildingWebchatView
|
|
1036
1049
|
?.tourTypeOptions ?? []}
|
|
1037
1050
|
.layoutOptions=${this.buildingWebchatView?.layoutOptions ??
|
|
@@ -1080,6 +1093,7 @@ export class MEChat extends LitElement {
|
|
|
1080
1093
|
virtualToursTypeOffered="${this.buildingWebchatView
|
|
1081
1094
|
.virtualToursTypeOffered ?? ""}"
|
|
1082
1095
|
.showTourNextToChat="${this.showTourNextToChat ?? ""}"
|
|
1096
|
+
.openSSTWindowOnLoad=${this.openTourSchedulerOnLoad}
|
|
1083
1097
|
@closeShowTourNextToChat=${() => {
|
|
1084
1098
|
this.showTourNextToChat = false;
|
|
1085
1099
|
this.hideLauncher = true;
|
package/src/disclaimers.ts
CHANGED
|
@@ -12,6 +12,9 @@ const disclaimerStyles = {
|
|
|
12
12
|
link: {
|
|
13
13
|
color: "#4287f5",
|
|
14
14
|
},
|
|
15
|
+
paragraph: {
|
|
16
|
+
margin: "0 0 4px",
|
|
17
|
+
},
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
// States where we need to say "you are interacting with generative AI".
|
|
@@ -23,6 +26,129 @@ export const isCanadianBuilding = (
|
|
|
23
26
|
country: string | null | undefined
|
|
24
27
|
): boolean => country === "CA";
|
|
25
28
|
|
|
29
|
+
const QUEBEC_PRIVACY_POLICY_URL = "https://eliseai.com/fr/canada-policy";
|
|
30
|
+
const QUEBEC_TERMS_URL = "https://eliseai.com/fr/msgterms";
|
|
31
|
+
|
|
32
|
+
export const isQuebecBuilding = (state: string | null | undefined): boolean =>
|
|
33
|
+
state?.trim().toUpperCase() === "QC";
|
|
34
|
+
|
|
35
|
+
type QuebecConsentForm = "emailUs" | "textUs" | "scheduleTour";
|
|
36
|
+
|
|
37
|
+
const disclaimerLink = (href: string, label: string): TemplateResult =>
|
|
38
|
+
html`<a
|
|
39
|
+
style=${styleMap(disclaimerStyles.link)}
|
|
40
|
+
href=${href}
|
|
41
|
+
target="_blank"
|
|
42
|
+
rel="noopener noreferrer"
|
|
43
|
+
>${label}</a
|
|
44
|
+
>`;
|
|
45
|
+
|
|
46
|
+
const quebecConsentText = (
|
|
47
|
+
form: QuebecConsentForm,
|
|
48
|
+
orgLegalName: string
|
|
49
|
+
): [TemplateResult, TemplateResult] => {
|
|
50
|
+
const frenchLinks = html`${disclaimerLink(
|
|
51
|
+
QUEBEC_PRIVACY_POLICY_URL,
|
|
52
|
+
"Politique relative à la protection des renseignements personnels"
|
|
53
|
+
)}
|
|
54
|
+
ainsi qu’à ces ${disclaimerLink(QUEBEC_TERMS_URL, "Conditions générales")}`;
|
|
55
|
+
const englishLinks = html`${disclaimerLink(
|
|
56
|
+
QUEBEC_PRIVACY_POLICY_URL,
|
|
57
|
+
"privacy policy"
|
|
58
|
+
)}
|
|
59
|
+
and these ${disclaimerLink(QUEBEC_TERMS_URL, "terms and conditions")}`;
|
|
60
|
+
|
|
61
|
+
if (form === "textUs") {
|
|
62
|
+
return [
|
|
63
|
+
html`Lorsque vous fournissez votre numéro de téléphone et que vous cliquez
|
|
64
|
+
sur Envoyer (send), vous consentez à recevoir des messages électroniques
|
|
65
|
+
et des appels récurrents de nature commerciale de la part de
|
|
66
|
+
${orgLegalName} ou en son nom à ce numéro, dans la mesure où cela est
|
|
67
|
+
permis par les lois applicables. Les messages peuvent être générés par une
|
|
68
|
+
intelligence artificielle (IA) ou par un humain. Frais std SMS/données
|
|
69
|
+
applicables. Vous consentez à cette ${frenchLinks}, y compris à ce que
|
|
70
|
+
notre fournisseur de services, EliseAI, enregistre et utilise votre numéro
|
|
71
|
+
de téléphone et vos communications afin de générer des réponses et
|
|
72
|
+
d’améliorer ses services. Vous pouvez retirer votre consentement en tout
|
|
73
|
+
temps en vous désabonnant ou en communiquant avec nous.`,
|
|
74
|
+
html`By providing your number and clicking send, you consent to receive
|
|
75
|
+
recurring commercial electronic messages and calls from or on behalf of
|
|
76
|
+
${orgLegalName} at this number, to the extent permitted by applicable law.
|
|
77
|
+
Messages may be AI or human generated. StdMsg&DataRatesMayApply. You
|
|
78
|
+
consent to this ${englishLinks}, including having your number and
|
|
79
|
+
communications recorded and used by our service provider EliseAI to
|
|
80
|
+
generate responses and improve their services. You may withdraw your
|
|
81
|
+
consent at any time by unsubscribing or contacting us.`,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (form === "scheduleTour") {
|
|
86
|
+
return [
|
|
87
|
+
html`Lorsque vous fournissez votre numéro de téléphone ou votre adresse
|
|
88
|
+
courriel et que vous cliquez sur Planifier une visite (schedule tour),
|
|
89
|
+
vous consentez à recevoir des messages électroniques et des appels
|
|
90
|
+
récurrents de nature commerciale de la part de ${orgLegalName} ou en son
|
|
91
|
+
nom à ce numéro ou à cette adresse courriel, dans la mesure où cela est
|
|
92
|
+
permis par les lois applicables. Les messages peuvent être générés par une
|
|
93
|
+
intelligence artificielle (IA) ou par un humain. Frais std SMS/données
|
|
94
|
+
applicables. Vous consentez à cette ${frenchLinks}, y compris à ce que
|
|
95
|
+
notre fournisseur de services, EliseAI, enregistre et utilise votre numéro
|
|
96
|
+
de téléphone, votre adresse courriel et vos communications afin de générer
|
|
97
|
+
des réponses et d’améliorer ses services. Vous pouvez retirer votre
|
|
98
|
+
consentement en tout temps en vous désabonnant ou en communiquant avec
|
|
99
|
+
nous.`,
|
|
100
|
+
html`By providing your number and/or email address and clicking schedule
|
|
101
|
+
tour, you consent to receive recurring commercial electronic messages and
|
|
102
|
+
calls from or on behalf of ${orgLegalName} at the provided number and/or
|
|
103
|
+
email address, to the extent permitted by applicable law. Messages may be
|
|
104
|
+
AI or human generated. StdMsg&DataRatesMayApply. You consent to this
|
|
105
|
+
${englishLinks}, including having your number, email address, and
|
|
106
|
+
communications recorded and used by our service provider EliseAI to
|
|
107
|
+
generate responses and improve their services. You may withdraw your
|
|
108
|
+
consent at any time by unsubscribing or contacting us.`,
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
html`Lorsque vous fournissez votre adresse courriel ou votre numéro de
|
|
114
|
+
téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir
|
|
115
|
+
des messages électroniques et des appels récurrents de nature commerciale de
|
|
116
|
+
la part de ${orgLegalName} ou en son nom à cette adresse courriel ou à ce
|
|
117
|
+
numéro, dans la mesure où cela est permis par les lois applicables. Les
|
|
118
|
+
messages peuvent être générés par une intelligence artificielle (IA) ou par
|
|
119
|
+
un humain. Vous consentez à cette ${frenchLinks}, y compris à ce que notre
|
|
120
|
+
fournisseur de services, EliseAI, enregistre et utilise votre adresse
|
|
121
|
+
courriel, votre numéro de téléphone et vos communications afin de générer
|
|
122
|
+
des réponses et d’améliorer ses services. Vous pouvez retirer votre
|
|
123
|
+
consentement en tout temps en vous désabonnant ou en communiquant avec nous.`,
|
|
124
|
+
html`By providing your email address and/or number and clicking send, you
|
|
125
|
+
consent to receive recurring commercial electronic messages and calls from
|
|
126
|
+
or on behalf of ${orgLegalName} at that email address and/or number, to the
|
|
127
|
+
extent permitted by applicable law. Messages may be AI or human generated.
|
|
128
|
+
You consent to this ${englishLinks}, including having your email address,
|
|
129
|
+
number, and communications recorded and used by our service provider EliseAI
|
|
130
|
+
to generate responses and improve their services. You may withdraw your
|
|
131
|
+
consent at any time by unsubscribing or contacting us.`,
|
|
132
|
+
];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const quebecConsentDisclaimer = (
|
|
136
|
+
form: QuebecConsentForm,
|
|
137
|
+
orgLegalName: string,
|
|
138
|
+
hasPhoneNumber = false
|
|
139
|
+
): TemplateResult => {
|
|
140
|
+
const [french, english] = quebecConsentText(form, orgLegalName);
|
|
141
|
+
const paragraphStyle = styleMap(disclaimerStyles.paragraph);
|
|
142
|
+
return html`<p style=${paragraphStyle}>${french}</p>
|
|
143
|
+
<p style=${paragraphStyle}>${english}</p>
|
|
144
|
+
${form === "textUs" || hasPhoneNumber
|
|
145
|
+
? html`<p style=${paragraphStyle}>
|
|
146
|
+
Msg freq varies. Text STOP to opt-out of messages. Text HELP for
|
|
147
|
+
assistance.
|
|
148
|
+
</p>`
|
|
149
|
+
: ""}`;
|
|
150
|
+
};
|
|
151
|
+
|
|
26
152
|
export const CURRENT_CONSENT_ID = "currentConsentId";
|
|
27
153
|
|
|
28
154
|
const formDisclaimer = ({
|
|
@@ -31,6 +157,7 @@ const formDisclaimer = ({
|
|
|
31
157
|
orgLegalName,
|
|
32
158
|
orgSlug,
|
|
33
159
|
country,
|
|
160
|
+
buildingState,
|
|
34
161
|
clickingButtonText = "send",
|
|
35
162
|
}: {
|
|
36
163
|
buildingName: string;
|
|
@@ -39,8 +166,19 @@ const formDisclaimer = ({
|
|
|
39
166
|
orgLegalName: string;
|
|
40
167
|
orgSlug: string;
|
|
41
168
|
country?: string | null;
|
|
169
|
+
buildingState?: string | null;
|
|
42
170
|
clickingButtonText?: string;
|
|
43
171
|
}): TemplateResult => {
|
|
172
|
+
if (isQuebecBuilding(buildingState)) {
|
|
173
|
+
return html`<div style=${styleMap(disclaimerStyles.container)}>
|
|
174
|
+
${quebecConsentDisclaimer(
|
|
175
|
+
clickingButtonText === "schedule tour" ? "scheduleTour" : "emailUs",
|
|
176
|
+
orgLegalName,
|
|
177
|
+
!!phoneNumberInput
|
|
178
|
+
)}
|
|
179
|
+
</div>`;
|
|
180
|
+
}
|
|
181
|
+
|
|
44
182
|
const policyUrl = isCanadianBuilding(country)
|
|
45
183
|
? CANADA_PRIVACY_POLICY_URL
|
|
46
184
|
: "http://bit.ly/me_privacy_policy";
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { expect } from "@esm-bundle/chai";
|
|
2
|
+
import { restore, useFakeXMLHttpRequest } from "sinon/pkg/sinon-esm";
|
|
3
|
+
import "../public/dist/index";
|
|
4
|
+
|
|
5
|
+
type Region = { state: string | null; country: string | null };
|
|
6
|
+
type Widget = HTMLElement & { updateComplete: Promise<boolean> };
|
|
7
|
+
type WidgetCase = {
|
|
8
|
+
name: string;
|
|
9
|
+
tag: string;
|
|
10
|
+
props: Record<string, string>;
|
|
11
|
+
propsAfterMount?: Record<string, boolean>;
|
|
12
|
+
french: string;
|
|
13
|
+
english: string;
|
|
14
|
+
ontarioCopy: string;
|
|
15
|
+
newYorkCopy: string;
|
|
16
|
+
newYorkLinks: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const ORG = "Test Org Legal Inc.";
|
|
20
|
+
const QUEBEC: Region[] = [
|
|
21
|
+
{ state: "QC", country: "CA" },
|
|
22
|
+
{ state: "qc", country: null },
|
|
23
|
+
];
|
|
24
|
+
const ONTARIO: Region = { state: "ON", country: "CA" };
|
|
25
|
+
const NEW_YORK: Region = { state: "NY", country: "US" };
|
|
26
|
+
|
|
27
|
+
const FR_PRIVACY =
|
|
28
|
+
"Politique relative à la protection des renseignements personnels";
|
|
29
|
+
const PHONE_OPT_OUT =
|
|
30
|
+
"Msg freq varies. Text STOP to opt-out of messages. Text HELP for assistance.";
|
|
31
|
+
|
|
32
|
+
const EMAIL_US_FR = `Lorsque vous fournissez votre adresse courriel ou votre numéro de téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à cette adresse courriel ou à ce numéro, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre adresse courriel, votre numéro de téléphone et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
|
|
33
|
+
const EMAIL_US_EN = `By providing your email address and/or number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at that email address and/or number, to the extent permitted by applicable law. Messages may be AI or human generated. You consent to this privacy policy and these terms and conditions, including having your email address, number, and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
|
|
34
|
+
|
|
35
|
+
const TEXT_US_FR = `Lorsque vous fournissez votre numéro de téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à ce numéro, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Frais std SMS/données applicables. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre numéro de téléphone et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
|
|
36
|
+
const TEXT_US_EN = `By providing your number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at this number, to the extent permitted by applicable law. Messages may be AI or human generated. StdMsg&DataRatesMayApply. You consent to this privacy policy and these terms and conditions, including having your number and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
|
|
37
|
+
|
|
38
|
+
const SCHEDULE_TOUR_FR = `Lorsque vous fournissez votre numéro de téléphone ou votre adresse courriel et que vous cliquez sur Planifier une visite (schedule tour), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à ce numéro ou à cette adresse courriel, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Frais std SMS/données applicables. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre numéro de téléphone, votre adresse courriel et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
|
|
39
|
+
const SCHEDULE_TOUR_EN = `By providing your number and/or email address and clicking schedule tour, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at the provided number and/or email address, to the extent permitted by applicable law. Messages may be AI or human generated. StdMsg&DataRatesMayApply. You consent to this privacy policy and these terms and conditions, including having your number, email address, and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
|
|
40
|
+
|
|
41
|
+
const renderedText = (root: ShadowRoot | null | undefined): string =>
|
|
42
|
+
(root?.textContent ?? "").replace(/\s+/g, " ");
|
|
43
|
+
|
|
44
|
+
const linkHrefs = (root: ShadowRoot | null | undefined): string[] =>
|
|
45
|
+
Array.from(root?.querySelectorAll("a") ?? []).map(
|
|
46
|
+
(a) => `${a.textContent?.trim()} -> ${a.getAttribute("href")}`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const expectQuebecConsent = (
|
|
50
|
+
root: ShadowRoot | null | undefined,
|
|
51
|
+
french: string,
|
|
52
|
+
english: string
|
|
53
|
+
): void => {
|
|
54
|
+
const text = renderedText(root);
|
|
55
|
+
expect(text).to.include(french);
|
|
56
|
+
expect(text).to.include(english);
|
|
57
|
+
expect(text.indexOf(french)).to.be.lessThan(text.indexOf(english));
|
|
58
|
+
expect(text.indexOf(english)).to.be.lessThan(text.indexOf(PHONE_OPT_OUT));
|
|
59
|
+
expect(linkHrefs(root)).to.include.members([
|
|
60
|
+
`${FR_PRIVACY} -> https://eliseai.com/fr/canada-policy`,
|
|
61
|
+
"Conditions générales -> https://eliseai.com/fr/msgterms",
|
|
62
|
+
"privacy policy -> https://eliseai.com/fr/canada-policy",
|
|
63
|
+
"terms and conditions -> https://eliseai.com/fr/msgterms",
|
|
64
|
+
]);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const expectNoFrench = (root: ShadowRoot | null | undefined): void => {
|
|
68
|
+
expect(renderedText(root)).not.to.include("Lorsque vous fournissez");
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const US_PRIVACY_LINK = "privacy policy -> http://bit.ly/me_privacy_policy";
|
|
72
|
+
const PHONE = "(514) 555-0123";
|
|
73
|
+
|
|
74
|
+
const WIDGETS: WidgetCase[] = [
|
|
75
|
+
{
|
|
76
|
+
name: "Email Us form",
|
|
77
|
+
tag: "email-us-window",
|
|
78
|
+
props: { email: "local.test@example.com", phoneNumber: PHONE },
|
|
79
|
+
french: EMAIL_US_FR,
|
|
80
|
+
english: EMAIL_US_EN,
|
|
81
|
+
ontarioCopy: `By providing your email address and/or number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at that email address and/or number. Messages may be AI or human generated.`,
|
|
82
|
+
newYorkCopy: `By providing your number and clicking send, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
|
|
83
|
+
newYorkLinks: [US_PRIVACY_LINK],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "Text Us form",
|
|
87
|
+
tag: "call-us-window",
|
|
88
|
+
props: { hasTextUsEnabled: "true" },
|
|
89
|
+
french: TEXT_US_FR,
|
|
90
|
+
english: TEXT_US_EN,
|
|
91
|
+
ontarioCopy: `By providing your number and clicking send, you consent to receive recurring commercial electronic messages from or on behalf of ${ORG} at this number.`,
|
|
92
|
+
newYorkCopy: `By providing your number and clicking send, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
|
|
93
|
+
newYorkLinks: [
|
|
94
|
+
US_PRIVACY_LINK,
|
|
95
|
+
"terms and conditions -> https://eliseai.com/msgterms",
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "tour scheduler",
|
|
100
|
+
tag: "tour-scheduler",
|
|
101
|
+
props: { phoneNumber: PHONE },
|
|
102
|
+
propsAfterMount: {
|
|
103
|
+
waitingForAvailabilities: false,
|
|
104
|
+
shouldAllowScheduleLoading: false,
|
|
105
|
+
shouldAllowScheduling: true,
|
|
106
|
+
},
|
|
107
|
+
french: SCHEDULE_TOUR_FR,
|
|
108
|
+
english: SCHEDULE_TOUR_EN,
|
|
109
|
+
ontarioCopy: `By providing your number and/or email address and clicking schedule tour, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at the provided number and/or email address. Messages may be AI or human generated.`,
|
|
110
|
+
newYorkCopy: `By providing your number and clicking schedule tour, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
|
|
111
|
+
newYorkLinks: [US_PRIVACY_LINK],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const renderWidget = async (
|
|
116
|
+
widget: WidgetCase,
|
|
117
|
+
region: Region
|
|
118
|
+
): Promise<Widget> => {
|
|
119
|
+
const element = document.createElement(widget.tag) as Widget;
|
|
120
|
+
Object.assign(element, widget.props, {
|
|
121
|
+
orgLegalName: ORG,
|
|
122
|
+
country: region.country,
|
|
123
|
+
buildingState: region.state,
|
|
124
|
+
});
|
|
125
|
+
document.body.appendChild(element);
|
|
126
|
+
Object.assign(element, widget.propsAfterMount);
|
|
127
|
+
await element.updateComplete;
|
|
128
|
+
return element;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
afterEach(() => {
|
|
132
|
+
restore();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
for (const widget of WIDGETS) {
|
|
136
|
+
it(`shows legal's French then English consent on the ${widget.name} only for Quebec`, async () => {
|
|
137
|
+
useFakeXMLHttpRequest();
|
|
138
|
+
|
|
139
|
+
for (const region of QUEBEC) {
|
|
140
|
+
const quebec = await renderWidget(widget, region);
|
|
141
|
+
expectQuebecConsent(quebec.shadowRoot, widget.french, widget.english);
|
|
142
|
+
quebec.remove();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ontario = await renderWidget(widget, ONTARIO);
|
|
146
|
+
expectNoFrench(ontario.shadowRoot);
|
|
147
|
+
expect(renderedText(ontario.shadowRoot)).to.include(widget.ontarioCopy);
|
|
148
|
+
expect(linkHrefs(ontario.shadowRoot)).to.include(
|
|
149
|
+
"privacy policy -> https://eliseai.com/canada-policy"
|
|
150
|
+
);
|
|
151
|
+
ontario.remove();
|
|
152
|
+
|
|
153
|
+
const newYork = await renderWidget(widget, NEW_YORK);
|
|
154
|
+
expectNoFrench(newYork.shadowRoot);
|
|
155
|
+
expect(renderedText(newYork.shadowRoot)).to.include(widget.newYorkCopy);
|
|
156
|
+
expect(linkHrefs(newYork.shadowRoot)).to.include.members(
|
|
157
|
+
widget.newYorkLinks
|
|
158
|
+
);
|
|
159
|
+
newYork.remove();
|
|
160
|
+
});
|
|
161
|
+
}
|