@meetelise/chat 1.25.0 → 1.25.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.
Files changed (136) hide show
  1. package/coverage/lcov-report/base.css +224 -0
  2. package/coverage/lcov-report/block-navigation.js +79 -0
  3. package/coverage/lcov-report/favicon.png +0 -0
  4. package/coverage/lcov-report/index.html +111 -0
  5. package/coverage/lcov-report/index.js.html +17093 -0
  6. package/coverage/lcov-report/prettify.css +1 -0
  7. package/coverage/lcov-report/prettify.js +2 -0
  8. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  9. package/coverage/lcov-report/sorter.js +170 -0
  10. package/coverage/lcov.info +9986 -0
  11. package/dist/index.d.ts +1 -0
  12. package/package.json +1 -1
  13. package/.eslintignore +0 -2
  14. package/.eslintrc.cjs +0 -23
  15. package/.github/pull_request_template.md +0 -61
  16. package/.github/workflows/pull.yml +0 -46
  17. package/.github/workflows/release.yml +0 -23
  18. package/.husky/commit-msg +0 -2
  19. package/.husky/pre-commit +0 -4
  20. package/.idea/codeStyles/Project.xml +0 -57
  21. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  22. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  23. package/.idea/vcs.xml +0 -6
  24. package/.idea/workspace.xml +0 -67
  25. package/.prettierignore +0 -2
  26. package/.prettierrc.json +0 -1
  27. package/.vscode/settings.json +0 -13
  28. package/CONTRIBUTING.md +0 -36
  29. package/public/demo/index.html +0 -347
  30. package/public/demo/secret.html +0 -63
  31. package/src/MyPubnub.ts +0 -792
  32. package/src/WebComponent/FeeCalculator/components/collapsible-fee-section/collapsible-fee-section-styles.ts +0 -86
  33. package/src/WebComponent/FeeCalculator/components/collapsible-fee-section/collapsible-fee-section.ts +0 -94
  34. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item-styles.ts +0 -47
  35. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item.ts +0 -50
  36. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector-styles.ts +0 -46
  37. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts +0 -70
  38. package/src/WebComponent/FeeCalculator/components/index.ts +0 -3
  39. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card-styles.ts +0 -39
  40. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card.ts +0 -39
  41. package/src/WebComponent/FeeCalculator/fee-calculator-styles.ts +0 -280
  42. package/src/WebComponent/FeeCalculator/fee-calculator.ts +0 -256
  43. package/src/WebComponent/FeeCalculator/index.ts +0 -4
  44. package/src/WebComponent/FeeCalculator/model/building-fee.ts +0 -83
  45. package/src/WebComponent/FeeCalculator/model/transaction-category.ts +0 -23
  46. package/src/WebComponent/LeadSourceClient.ts +0 -332
  47. package/src/WebComponent/MEChat.css +0 -5
  48. package/src/WebComponent/OfficeHours.ts +0 -73
  49. package/src/WebComponent/Scheduler/date-picker.ts +0 -405
  50. package/src/WebComponent/Scheduler/time-picker.ts +0 -190
  51. package/src/WebComponent/Scheduler/tour-scheduler.ts +0 -1352
  52. package/src/WebComponent/Scheduler/tour-type-option.ts +0 -112
  53. package/src/WebComponent/Scheduler/tourSchedulerStyles.ts +0 -418
  54. package/src/WebComponent/actions/InputStyles.ts +0 -57
  55. package/src/WebComponent/actions/action-confirm-button.ts +0 -125
  56. package/src/WebComponent/actions/call-us-window.ts +0 -445
  57. package/src/WebComponent/actions/collapse-expand-button.ts +0 -65
  58. package/src/WebComponent/actions/details-window.ts +0 -150
  59. package/src/WebComponent/actions/email-us-window.ts +0 -555
  60. package/src/WebComponent/actions/formatPhoneNumber.ts +0 -72
  61. package/src/WebComponent/actions/minimize-expand-button.ts +0 -93
  62. package/src/WebComponent/chat-additional-actions.ts +0 -135
  63. package/src/WebComponent/health-chat.ts +0 -270
  64. package/src/WebComponent/healthchat-styles.ts +0 -119
  65. package/src/WebComponent/icons/ApplyOutlineIcon.ts +0 -22
  66. package/src/WebComponent/icons/BookTourOutlineIcon.ts +0 -13
  67. package/src/WebComponent/icons/CalculatorOutlineIcon.ts +0 -22
  68. package/src/WebComponent/icons/ChatOutlineIcon.ts +0 -10
  69. package/src/WebComponent/icons/ChevronLeftIcon.ts +0 -7
  70. package/src/WebComponent/icons/ChevronRightIcon.ts +0 -7
  71. package/src/WebComponent/icons/ContactResidentIcon.ts +0 -9
  72. package/src/WebComponent/icons/DollarOutlineIcon.ts +0 -18
  73. package/src/WebComponent/icons/EmailOutlineIcon.ts +0 -7
  74. package/src/WebComponent/icons/HeyThereEmojiIcon.ts +0 -12
  75. package/src/WebComponent/icons/PhoneOutlineIcon.ts +0 -7
  76. package/src/WebComponent/icons/SendMessageIcon.ts +0 -17
  77. package/src/WebComponent/icons/TourSelfGuidedIcon.ts +0 -17
  78. package/src/WebComponent/icons/TourVirtuallyIcon.ts +0 -17
  79. package/src/WebComponent/icons/TourWithAgentIcon.ts +0 -17
  80. package/src/WebComponent/icons/XOutlineIcon.ts +0 -8
  81. package/src/WebComponent/index.ts +0 -2
  82. package/src/WebComponent/launcher/Launcher.ts +0 -1193
  83. package/src/WebComponent/launcher/launcherStyles.ts +0 -500
  84. package/src/WebComponent/launcher/mobile-launcher.ts +0 -159
  85. package/src/WebComponent/launcher/typeEmojiStyles.ts +0 -161
  86. package/src/WebComponent/launcher/typeMiniStyles.ts +0 -60
  87. package/src/WebComponent/launcher/typeMobileStyles.ts +0 -50
  88. package/src/WebComponent/leasing-chat-styles.ts +0 -114
  89. package/src/WebComponent/me-chat.ts +0 -1257
  90. package/src/WebComponent/me-select.ts +0 -322
  91. package/src/WebComponent/mini-loader.ts +0 -28
  92. package/src/WebComponent/pubnub-chat-styles.ts +0 -204
  93. package/src/WebComponent/pubnub-chat.ts +0 -928
  94. package/src/WebComponent/pubnub-media.ts +0 -208
  95. package/src/WebComponent/pubnub-message-styles.ts +0 -54
  96. package/src/WebComponent/pubnub-message.ts +0 -431
  97. package/src/WebComponent/simple-launcher/simple-launcher-styles.ts +0 -34
  98. package/src/WebComponent/simple-launcher/simple-launcher.ts +0 -100
  99. package/src/WebComponent/utilities-chat.ts +0 -270
  100. package/src/WebComponent/utilities-styles.ts +0 -110
  101. package/src/WebComponent/utils.ts +0 -82
  102. package/src/analytics.ts +0 -217
  103. package/src/assetUrls.ts +0 -6
  104. package/src/disclaimers.ts +0 -58
  105. package/src/fetchBuildingABTestType.ts +0 -21
  106. package/src/fetchBuildingInfo.ts +0 -87
  107. package/src/fetchBuildingWebchatView.ts +0 -154
  108. package/src/fetchFeatureFlag.ts +0 -250
  109. package/src/fetchLeadSources.ts +0 -98
  110. package/src/fetchPhoneNumberFromSource.ts +0 -31
  111. package/src/fetchWebchatPreferences.ts +0 -54
  112. package/src/getAvailabilities.ts +0 -174
  113. package/src/getBuildingPhoneNumber.ts +0 -26
  114. package/src/getShouldAllowScheduling.ts +0 -16
  115. package/src/getShouldShowWebchat.ts +0 -114
  116. package/src/getTimezoneString.ts +0 -39
  117. package/src/globals.ts +0 -1
  118. package/src/gtm.ts +0 -17
  119. package/src/handleChatId.ts +0 -101
  120. package/src/insertDNIIntoWebsite.ts +0 -146
  121. package/src/insertLeadSourceIntoSchedulerLinks.ts +0 -71
  122. package/src/main/MEChat.test.ts +0 -110
  123. package/src/main/MEChat.ts +0 -404
  124. package/src/main/utils.ts +0 -70
  125. package/src/postLeadSources.ts +0 -44
  126. package/src/rentgrata.ts +0 -74
  127. package/src/replaceSelectButtonsWithNewLink.ts +0 -68
  128. package/src/services/fees/fetchBuildingFees.ts +0 -28
  129. package/src/svgIcons.ts +0 -14
  130. package/src/themes.ts +0 -65
  131. package/src/types/rest-sdk.types.ts +0 -13
  132. package/src/types/webchat-no-show-reason.ts +0 -6
  133. package/src/utils.ts +0 -121
  134. package/tsconfig.json +0 -84
  135. package/web-test-runner.config.js +0 -10
  136. package/webpack.config.cjs +0 -48
@@ -1,1257 +0,0 @@
1
- import { css, html, LitElement, TemplateResult } from "lit";
2
- import { customElement, property, state } from "lit/decorators.js";
3
- import { classMap } from "lit/directives/class-map.js";
4
- import { createRef, ref, Ref } from "lit/directives/ref.js";
5
- import { Launcher } from "./launcher/Launcher";
6
- import "./Scheduler/tour-scheduler";
7
- import fetchBuildingWebchatView, {
8
- BuildingWebchatView,
9
- DesignConcepts,
10
- } from "../fetchBuildingWebchatView";
11
- import Analytics, { logSignal, LogType, sendLoggingEvent } from "../analytics";
12
- import {
13
- FeatureFlagsShowDropdown,
14
- fetchFeatureFlagInsertDNIWebsite,
15
- fetchFeatureFlagReplaceScheduleTourCtaWebsite,
16
- fetchFeatureFlagShowMarketingSourceDropdown,
17
- fetchFeatureFlagUseApplicationsLinkReplacement,
18
- fetchFeatureFlagUseOverrideContactUsForm,
19
- fetchFeatureFlagUsePhoneNumberBySource,
20
- } from "../fetchFeatureFlag";
21
- import fetchBuildingABTestType from "../fetchBuildingABTestType";
22
- import fetchPhoneNumberFromSource, {
23
- NumberForSelectedSource,
24
- } from "../fetchPhoneNumberFromSource";
25
- import { defaultPrimaryColor, defaultBackgroundColor } from "../themes";
26
- import getShouldShowWebchat from "../getShouldShowWebchat";
27
- import { isMobile, isTestEnv } from "../utils";
28
- import { installLauncher } from "./launcher/Launcher";
29
- import parseISO from "date-fns/parseISO";
30
- import isPast from "date-fns/isPast";
31
-
32
- import "./MEChat.css";
33
- import { getRawAvailabilities } from "../getAvailabilities";
34
- import { StyleInfo } from "lit/directives/style-map";
35
- import addMinutes from "date-fns/addMinutes";
36
- import formatISO from "date-fns/formatISO";
37
- import fetchLeadSources from "../fetchLeadSources";
38
- import { formatPhoneNumber } from "./actions/formatPhoneNumber";
39
- import MyPubnub from "../MyPubnub";
40
- import { insertDNIIntoWebsite } from "../insertDNIIntoWebsite";
41
- import {
42
- insertLeadSourceIntoSchedulerLinks,
43
- replacePageRedirectElementWithTourWidgetTrigger,
44
- replaceSSTLinkWithTourWidgetTrigger,
45
- replaceTourTriggerElementWithTourWidgetTrigger,
46
- } from "../insertLeadSourceIntoSchedulerLinks";
47
-
48
- import "./actions/minimize-expand-button";
49
- import "./launcher/mobile-launcher";
50
- import "./pubnub-chat";
51
- import { getBuildingPhoneNumber } from "../getBuildingPhoneNumber";
52
- import {
53
- clearChatStorageKey,
54
- createChatStorageKey,
55
- getChatStorageKey,
56
- isChatKeyValid,
57
- } from "../handleChatId";
58
- import LeadSourceClient, {
59
- getDefaultLeadSourceAttribution,
60
- } from "./LeadSourceClient";
61
- import noop from "lodash/noop";
62
- import { updateRentgrataToken } from "../rentgrata";
63
- import { WidgetType } from "../main/MEChat";
64
- import { replaceSelectButtonsWithNewLink } from "../replaceSelectButtonsWithNewLink";
65
- import "./chat-additional-actions";
66
-
67
- @customElement("me-chat")
68
- export class MEChat extends LitElement {
69
- static styles = css`
70
- :host {
71
- all: initial;
72
- }
73
-
74
- #__talkjs_launcher:not(.shouldBeVisible) {
75
- display: none;
76
- }
77
-
78
- .launcherContainer.launcher__mobile {
79
- width: 100%;
80
- height: 100px;
81
- }
82
-
83
- .hideTab {
84
- display: none;
85
- }
86
- .showTab {
87
- display: flex;
88
- align-items: center;
89
- justify-content: space-between;
90
- gap: 32px;
91
- }
92
- #chatAdditionalActionsPubnub {
93
- position: fixed;
94
- box-sizing: border-box;
95
-
96
- box-sizing: border-box;
97
- width: 340px;
98
-
99
- padding-top: 0px;
100
- padding-right: 12px;
101
-
102
- z-index: 1000000000;
103
- }
104
- #chatAdditionalActionsPubnub::after {
105
- content: "";
106
- position: absolute;
107
- top: -6px;
108
- right: 0;
109
- width: 0;
110
- height: 0;
111
- border-bottom: 22px solid transparent;
112
- border-right: 30px solid black;
113
- }
114
- #pubnub-bottom {
115
- position: fixed;
116
- bottom: 0;
117
- }
118
- `;
119
- @property({ type: String })
120
- private buildingSlug = "";
121
- @property({ type: String })
122
- private orgSlug = "";
123
- @property({ type: Object })
124
- launcherStyles: StyleInfo = {};
125
- @property()
126
- mobileStyles: StyleInfo = {};
127
-
128
- @property({ type: Boolean })
129
- isMinimized = false;
130
-
131
- @property({ type: String })
132
- private primaryColor: string | null = null;
133
-
134
- @property({ type: String })
135
- private backgroundColor: string | null = null;
136
-
137
- @property({ type: String })
138
- private foregroundColorOnPrimaryBackgroundColor: string | null = null;
139
-
140
- @property({ type: String })
141
- private foregroundColorOnSecondaryBackgroundColor: string | null = null;
142
-
143
- @state()
144
- private chatId = "";
145
- @state()
146
- private analytics: Analytics | null = null;
147
- @state()
148
- private launcher: HTMLElement | null = null;
149
- @state()
150
- private buildingWebchatView: BuildingWebchatView | null = null;
151
- @state()
152
- private designConcept: string | null = null;
153
- @state()
154
- private leadSources: string[] | null = null;
155
- @state()
156
- private currentLeadSource: string | null = null;
157
- @state()
158
- private featureFlagShowDropdown: FeatureFlagsShowDropdown =
159
- FeatureFlagsShowDropdown.always;
160
- @state()
161
- private phoneNumberForSource: NumberForSelectedSource | null = null;
162
- @state()
163
- private hasMounted = false;
164
- @state()
165
- private hideLauncher = false;
166
- @state()
167
- private isLoading = true;
168
-
169
- @state()
170
- private showTourNextToChat = false;
171
-
172
- @state()
173
- private displayPubnubChat = false;
174
-
175
- @state()
176
- private myPubnub: MyPubnub | null = null;
177
-
178
- @state()
179
- private LeadSourceClient: LeadSourceClient | null = null;
180
-
181
- @state()
182
- private sampleRateToUse = 0.05;
183
-
184
- @property({ type: Number })
185
- private right = undefined;
186
-
187
- @property({ type: Number })
188
- private bottom = undefined;
189
-
190
- @property({ type: Number })
191
- private top = undefined;
192
-
193
- @property({ type: Number })
194
- private left = undefined;
195
-
196
- @property({ attribute: true })
197
- overrideRentgrata = false;
198
-
199
- @property({ attribute: true })
200
- widgetType: WidgetType = WidgetType.Default;
201
-
202
- @property()
203
- onWidgetLoaded: () => void = noop;
204
-
205
- @property({ attribute: true })
206
- onSstClose: () => void = noop;
207
-
208
- @state()
209
- private enabledChatWidgets: {
210
- callDesktop: boolean;
211
- callMobile: boolean;
212
- chatMobile: boolean;
213
- chatDesktop: boolean;
214
- emailDesktop: boolean;
215
- emailMobile: boolean;
216
- textDesktop: boolean;
217
- textMobile: boolean;
218
- sstDesktop: boolean;
219
- sstMobile: boolean;
220
- calcDesktop: boolean;
221
- calcMobile: boolean;
222
- } = {
223
- callDesktop: false,
224
- callMobile: false,
225
- chatMobile: false,
226
- chatDesktop: false,
227
- emailDesktop: false,
228
- emailMobile: false,
229
- textDesktop: false,
230
- textMobile: false,
231
- sstDesktop: false,
232
- sstMobile: false,
233
- calcDesktop: false,
234
- calcMobile: false,
235
- };
236
-
237
- @state()
238
- private webchatConfigHasAutoOpenChat = false;
239
-
240
- @state()
241
- private requiresConsent = false;
242
-
243
- @state()
244
- private hideMobileFeatures = false;
245
-
246
- @state()
247
- private privacyPolicyUrl = "http://bit.ly/me_privacy_policy";
248
-
249
- launcherRef: Ref<Launcher> = createRef();
250
-
251
- firstUpdated = async (): Promise<void> => this.setupWebchat();
252
-
253
- private setupWebchat = async (): Promise<void> => {
254
- if (!this.buildingSlug || !this.orgSlug) return;
255
- const buildingWebchatView = await fetchBuildingWebchatView(
256
- this.orgSlug,
257
- this.buildingSlug
258
- );
259
-
260
- if (Math.random() < 0.2) {
261
- try {
262
- updateRentgrataToken(this.buildingSlug, this.orgSlug);
263
- } catch (e) {
264
- // eslint-disable-next-line no-console
265
- console.warn("Error updating rentgrata token", e);
266
- }
267
- }
268
-
269
- const shouldShowWebchat = await getShouldShowWebchat(
270
- buildingWebchatView,
271
- this.buildingSlug,
272
- this.orgSlug
273
- );
274
- if (!shouldShowWebchat) {
275
- return;
276
- } else {
277
- logSignal({
278
- params: {
279
- org_slug: this.orgSlug,
280
- building_slug: this.buildingSlug,
281
- is_pixel_on_site: true,
282
- is_any_webchat_showing: true,
283
- },
284
- // We use a lower sample rate if not launched yet because likely still in onboarding process
285
- sampleRate: buildingWebchatView?.launchedDateTime ? 0.01 : 0.05,
286
- });
287
- }
288
- this.buildingWebchatView = buildingWebchatView;
289
-
290
- await this.initializeChatVariables();
291
- await this.handleChatInitializeAnalytics();
292
- await this.setBuildingDerivedInfo();
293
-
294
- this.attachOnClickToLauncher();
295
-
296
- if (this.shouldAutoOpenChat(this.webchatConfigHasAutoOpenChat)) {
297
- this.displayPubnubChat = true;
298
- this.hideLauncher = true;
299
- this.hasMounted = true;
300
- this.updateAlreadyAutoOpenedTimestamp();
301
- }
302
-
303
- this.isLoading = false;
304
-
305
- if (localStorage.getItem("isChatOpen") === "true") {
306
- this.hideLauncher = true;
307
- this.displayPubnubChat = true;
308
- }
309
- };
310
-
311
- // The general info for the buildingg
312
- setBuildingDerivedInfo = async (): Promise<void> => {
313
- const buildingDetails = this.buildingWebchatView;
314
- if (!buildingDetails) return;
315
- if (buildingDetails && this.orgSlug) {
316
- overrideContactUsForm(
317
- this.orgSlug,
318
- this.LeadSourceClient?.chatId ?? null,
319
- this.sampleRateToUse,
320
- this.buildingSlug,
321
- this.LeadSourceClient?.leadSource ?? null
322
- );
323
- }
324
-
325
- const [
326
- buildingABTest,
327
- leadSources,
328
- featureFlagShowDropdown,
329
- featureFlagUseDNI,
330
- featureFlagInsertDNIWebsite,
331
- featureFlagReplaceScheduleTourCtaWebsite,
332
- featureFlagUseApplicationsLinkReplacement,
333
- ] = await Promise.all([
334
- fetchBuildingABTestType(this.buildingSlug),
335
- fetchLeadSources(this.orgSlug, this.buildingSlug),
336
- fetchFeatureFlagShowMarketingSourceDropdown(this.buildingSlug),
337
- fetchFeatureFlagUsePhoneNumberBySource(this.buildingSlug),
338
- fetchFeatureFlagInsertDNIWebsite(this.buildingSlug),
339
- fetchFeatureFlagReplaceScheduleTourCtaWebsite(this.buildingSlug),
340
- fetchFeatureFlagUseApplicationsLinkReplacement(this.buildingSlug),
341
- ]);
342
-
343
- if (this.buildingWebchatView) {
344
- this.buildingWebchatView.phoneNumber = formatPhoneNumber(
345
- buildingDetails.phoneNumber
346
- );
347
- }
348
- this.designConcept = buildingABTest?.abTestType ?? "";
349
- this.leadSources = leadSources;
350
- this.featureFlagShowDropdown = featureFlagShowDropdown;
351
-
352
- // The backend is cached for ~4 hours, so falling back to the existing api request until the cache propagates all the changes
353
- const buildingPhoneNumber = buildingDetails.textWithUsPhoneNumber
354
- ? buildingDetails.textWithUsPhoneNumber
355
- : await getBuildingPhoneNumber(this.buildingSlug);
356
-
357
- const shouldShowCalcDesktop =
358
- (this.buildingWebchatView?.shouldShowPricingCalculatorDesktop &&
359
- isTestEnv()) ??
360
- false;
361
- const shouldShowCalcMobile =
362
- (this.buildingWebchatView?.shouldShowPricingCalculatorMobile &&
363
- isTestEnv()) ??
364
- false;
365
-
366
- this.enabledChatWidgets = {
367
- callDesktop: this.buildingWebchatView?.shouldShowPhoneDesktop ?? false,
368
- callMobile: this.buildingWebchatView?.shouldShowPhoneMobile ?? false,
369
- chatDesktop: this.buildingWebchatView?.shouldShowChatDesktop ?? false,
370
- chatMobile: this.buildingWebchatView?.shouldShowChatMobile ?? false,
371
- emailDesktop: this.buildingWebchatView?.shouldShowEmailDesktop ?? false,
372
- emailMobile: this.buildingWebchatView?.shouldShowEmailMobile ?? false,
373
- textDesktop:
374
- (this.buildingWebchatView?.shouldShowTextDesktop &&
375
- !!buildingPhoneNumber) ??
376
- false,
377
- textMobile:
378
- (this.buildingWebchatView?.shouldShowTextMobile &&
379
- !!buildingPhoneNumber) ??
380
- false,
381
- sstDesktop: this.buildingWebchatView?.shouldShowSstDesktop ?? false,
382
- sstMobile: this.buildingWebchatView?.shouldShowSstMobile ?? false,
383
- calcDesktop: shouldShowCalcDesktop,
384
- calcMobile: shouldShowCalcMobile,
385
- };
386
-
387
- if (
388
- this.enabledChatWidgets.sstDesktop ||
389
- this.enabledChatWidgets.sstMobile ||
390
- this.orgSlug === "radco"
391
- ) {
392
- replaceSSTLinkWithTourWidgetTrigger(this.handleTourClicked);
393
- if (featureFlagReplaceScheduleTourCtaWebsite) {
394
- replacePageRedirectElementWithTourWidgetTrigger(this.handleTourClicked);
395
- }
396
- if (this.orgSlug === "radco") {
397
- replaceTourTriggerElementWithTourWidgetTrigger(this.handleTourClicked);
398
- }
399
- } else {
400
- if (this.LeadSourceClient?.leadSource) {
401
- insertLeadSourceIntoSchedulerLinks(
402
- this.LeadSourceClient?.leadSource ?? null
403
- );
404
- }
405
- }
406
-
407
- if (this.buildingWebchatView) {
408
- this.primaryColor = this.buildingWebchatView.primaryColor ?? null;
409
- this.backgroundColor = this.buildingWebchatView.secondaryColor ?? null;
410
- this.foregroundColorOnPrimaryBackgroundColor =
411
- this.buildingWebchatView.foregroundColorOnPrimaryBackgroundColor ??
412
- null;
413
- this.foregroundColorOnSecondaryBackgroundColor =
414
- this.buildingWebchatView.foregroundColorOnSecondaryBackgroundColor ??
415
- null;
416
- this.webchatConfigHasAutoOpenChat =
417
- this.buildingWebchatView.autoOpenChat ?? false;
418
- this.requiresConsent =
419
- this.buildingWebchatView.requiresConsentForChat ?? false;
420
- this.hideMobileFeatures =
421
- this.buildingWebchatView.collapseMobileFeatures ?? false;
422
- this.privacyPolicyUrl =
423
- this.buildingWebchatView.privacyPolicyUrlForChat ??
424
- this.privacyPolicyUrl;
425
-
426
- if (this.buildingWebchatView.displayStyle === DesignConcepts.MINIMIZED) {
427
- // this.designConcept = DesignConcepts.MINIMIZED; // uncomment this if we want to remove the minimize/expand option
428
- this.isMinimized = true;
429
- }
430
- if (this.buildingWebchatView.displayStyle === DesignConcepts.PILLS) {
431
- this.designConcept = null; // default design concept is PILLS, no need to specify here
432
- }
433
- if (this.buildingWebchatView.displayStyle === DesignConcepts.EMOJI) {
434
- this.designConcept = DesignConcepts.EMOJI;
435
- }
436
-
437
- this.onWidgetLoaded();
438
- }
439
-
440
- if (this.primaryColor === null) this.primaryColor = defaultPrimaryColor;
441
- if (this.backgroundColor === null)
442
- this.backgroundColor = defaultBackgroundColor;
443
-
444
- const buildingsPhoneNumber = this.buildingWebchatView?.phoneNumber ?? "";
445
- let phoneNumberForSource = null;
446
- if (featureFlagUseDNI) {
447
- phoneNumberForSource = await fetchPhoneNumberFromSource(
448
- this.buildingSlug,
449
- this.LeadSourceClient?.leadSource ?? null
450
- );
451
-
452
- // By default, we want to use the building's phone number, not the default catchall number
453
- // https://meetelise.slack.com/archives/C01BT9GB9LZ/p1696437860448009
454
- if (phoneNumberForSource && !phoneNumberForSource.isMatch) {
455
- phoneNumberForSource.number = buildingsPhoneNumber;
456
- }
457
- if (!this.LeadSourceClient?.leadSource && phoneNumberForSource) {
458
- phoneNumberForSource.number = buildingsPhoneNumber;
459
- }
460
- }
461
- if (featureFlagInsertDNIWebsite && phoneNumberForSource?.number) {
462
- const totalReplacements = insertDNIIntoWebsite(
463
- phoneNumberForSource.number,
464
- this.orgSlug,
465
- this.buildingSlug
466
- );
467
- if (totalReplacements > 0) {
468
- logSignal({
469
- params: {
470
- org_slug: this.orgSlug,
471
- building_slug: this.buildingSlug,
472
- is_dni_insert_override_success: true,
473
- extra_data: {
474
- total_replacements: totalReplacements,
475
- },
476
- },
477
- sampleRate: this.sampleRateToUse,
478
- });
479
- }
480
- }
481
-
482
- const devTestPhoneNumber = localStorage.getItem(
483
- "eliseai_phone_number_devtest"
484
- );
485
- if (devTestPhoneNumber) {
486
- // eslint-disable-next-line no-console
487
- console.info("Using dev test phone number: ", devTestPhoneNumber);
488
- insertDNIIntoWebsite(devTestPhoneNumber, this.orgSlug, this.buildingSlug);
489
- }
490
-
491
- // if the building does NOT have IVR setup, we want to use the building's phone number
492
- if (!phoneNumberForSource) {
493
- this.phoneNumberForSource = {
494
- number: buildingsPhoneNumber,
495
- isMatch: false,
496
- isPropertyWebsiteCatchall: true,
497
- };
498
- } else {
499
- this.phoneNumberForSource = phoneNumberForSource;
500
- }
501
-
502
- const currentLeadSource = this.LeadSourceClient?.leadSource ?? null;
503
- if (currentLeadSource) {
504
- if (!this.leadSources.includes(currentLeadSource)) {
505
- this.leadSources.push(currentLeadSource);
506
- }
507
- }
508
- getRawAvailabilities(buildingDetails.id); // we're not using this here, just want to cache the result
509
-
510
- if (featureFlagUseApplicationsLinkReplacement && this.buildingSlug) {
511
- const totalReplacements = replaceSelectButtonsWithNewLink(
512
- `https://applications.eliseai.com/building/${this.buildingSlug}`,
513
- this.orgSlug,
514
- this.buildingSlug
515
- );
516
- if (totalReplacements > 0) {
517
- logSignal({
518
- params: {
519
- org_slug: this.orgSlug,
520
- building_slug: this.buildingSlug,
521
- is_application_link_replacement_success: true,
522
- extra_data: {
523
- total_replacements: totalReplacements,
524
- },
525
- },
526
- sampleRate: this.sampleRateToUse,
527
- });
528
- }
529
- }
530
- };
531
-
532
- private initializeChatVariables = async (): Promise<void> => {
533
- if (!this.buildingWebchatView) return;
534
-
535
- // important to distinguish between a user who has never opened the chat and a user who has opened the chat but closed it
536
- const currentChatStorageKey = getChatStorageKey(this.buildingSlug);
537
- const updatedChatStorageKey = isChatKeyValid(
538
- this.buildingSlug,
539
- currentChatStorageKey
540
- )
541
- ? currentChatStorageKey
542
- : createChatStorageKey(this.buildingSlug, false);
543
-
544
- if (!updatedChatStorageKey.leadId) {
545
- throw new Error("Lead ID is null");
546
- }
547
- this.LeadSourceClient = new LeadSourceClient();
548
- this.myPubnub = new MyPubnub(
549
- this.buildingSlug,
550
- this.buildingWebchatView,
551
- this.orgSlug,
552
- null, // initialize lead source as null initially
553
- updatedChatStorageKey.leadId,
554
- this.LeadSourceClient,
555
- this.widgetType
556
- );
557
- this.LeadSourceClient.chatId = this.myPubnub.channel;
558
- await this.LeadSourceClient?.generateUserLeadSource({
559
- leadId: this.myPubnub?.leadUserId ?? "",
560
- orgSlug: this.orgSlug,
561
- buildingSlug: this.buildingSlug,
562
- });
563
-
564
- // eslint-disable-next-line no-console
565
- console.log(
566
- "EliseAI Mapped Lead Source source: ",
567
- this.LeadSourceClient?.leadSource
568
- );
569
-
570
- if (getDefaultLeadSourceAttribution(this.orgSlug) !== "property-website") {
571
- this.myPubnub.leadSource =
572
- this.LeadSourceClient?.leadSource ??
573
- getDefaultLeadSourceAttribution(this.orgSlug);
574
- } else {
575
- this.myPubnub.leadSource = this.LeadSourceClient?.leadSource ?? null;
576
- }
577
- if (updatedChatStorageKey.initiatedChat) {
578
- await this.myPubnub.initializePubnub(currentChatStorageKey);
579
- }
580
- };
581
-
582
- private shouldAutoOpenChat = (buildingHasAutoOpen: boolean): boolean => {
583
- const alreadyAutoOpenedTimestamp = sessionStorage.getItem(
584
- "alreadyAutoOpenedTimestamp"
585
- ); // we dont want to autoopen on EVERY single page load, so we'll only do it once every 15 minutes max
586
- const shouldAutoOpen =
587
- !alreadyAutoOpenedTimestamp ||
588
- !(
589
- alreadyAutoOpenedTimestamp &&
590
- !isPast(parseISO(alreadyAutoOpenedTimestamp))
591
- );
592
- return !!buildingHasAutoOpen && !!shouldAutoOpen && !isMobile();
593
- };
594
-
595
- private async handleChatInitializeAnalytics(): Promise<void> {
596
- this.analytics = new Analytics(
597
- this.orgSlug,
598
- this.buildingSlug,
599
- this.myPubnub?.channel ?? "", // this will be empty if the user does not have a current chat session.
600
- this.LeadSourceClient?.leadSource ?? null
601
- );
602
-
603
- // Too many logs kills our DB, so we'll only log 5% of the time
604
- if (Math.random() < 0.05) {
605
- this.analytics.ping("webchat_heartbeat");
606
- }
607
- }
608
-
609
- public async restartConversation(): Promise<void> {
610
- this.myPubnub?.handleDisconnect();
611
- clearChatStorageKey(this.buildingSlug);
612
- this.myPubnub = null;
613
- this.displayPubnubChat = false;
614
- await this.setupWebchat();
615
- this.hideLauncher = false;
616
- }
617
-
618
- private updateAlreadyAutoOpenedTimestamp = (): void => {
619
- sessionStorage.setItem(
620
- "alreadyAutoOpenedTimestamp",
621
- formatISO(addMinutes(new Date(), 15))
622
- );
623
- };
624
-
625
- /** Show the chat button on the screen if it was previously hidden. */
626
- show(): void {
627
- if (!this.launcher) {
628
- return;
629
- }
630
- this.launcher.style.display = "";
631
- }
632
-
633
- /** Hide the chat button from the screen (but don't remove from the DOM). */
634
- hide(): void {
635
- if (!this.launcher) {
636
- return;
637
- }
638
- this.launcher.style.display = "none";
639
- }
640
-
641
- handleContactClicked = (e: MouseEvent): void => {
642
- this.displayPubnubChat = false;
643
- this.hideLauncher = false;
644
- this.showTourNextToChat = false;
645
-
646
- this.launcherRef.value?.onClickEmailOption(e);
647
- };
648
-
649
- handleTourClicked = (e: MouseEvent): void => {
650
- if (this.showTourNextToChat) {
651
- this.launcherRef.value?.onClickSSTOption(e);
652
- return;
653
- }
654
- if (!this.hideLauncher) {
655
- this.displayPubnubChat = false;
656
- } else {
657
- this.showTourNextToChat = true;
658
- }
659
-
660
- this.hideLauncher = false;
661
- this.launcherRef.value?.onClickSSTOption(e);
662
- };
663
-
664
- handleContactTabClicked = (e: MouseEvent): void => {
665
- this.displayPubnubChat = false;
666
- this.showTourNextToChat = false;
667
- this.hideLauncher = false;
668
- this.launcherRef.value?.onClickPhoneOption(e);
669
- };
670
-
671
- // Depending on the rendered element of the chat widget, we need to adjust the position of the chat additional actions
672
- // these will be aligned at the bottom. It is much easier to handle here at the parent of the launchers rather than
673
- // in pubnub-chat.
674
- adjustPositionChatAdditionalActions = (): void => {
675
- const headerRef = this.shadowRoot?.getElementById(
676
- "chatAdditionalActionsPubnub"
677
- );
678
- if (!headerRef) return;
679
-
680
- const pubnubContainerElement = this.shadowRoot
681
- ?.getElementById("pubnub-chat")
682
- ?.shadowRoot?.getElementById("pubnub-chat-container");
683
-
684
- const pubnubPopupCoords = pubnubContainerElement?.getBoundingClientRect();
685
- if (!pubnubPopupCoords) return;
686
- headerRef.style.left = `${pubnubPopupCoords.left}px`;
687
- headerRef.style.top = `${pubnubPopupCoords.bottom}px`;
688
- };
689
-
690
- connectedCallback(): void {
691
- super.connectedCallback();
692
- window.addEventListener("resize", this.adjustPositionChatAdditionalActions);
693
- window.addEventListener("keydown", this.handleKeydownTab);
694
- }
695
-
696
- handleKeydownTab = (e: KeyboardEvent): void => {
697
- if (e.key === "Tab") {
698
- const pubnubContainerElement = this.displayPubnubChat
699
- ? this.shadowRoot // pubnub chat
700
- ?.getElementById("pubnub-chat")
701
- ?.shadowRoot?.getElementById("pubnub-chat-container")
702
- : this.shadowRoot // email us form
703
- ?.getElementById("meetelise-launcher")
704
- ?.shadowRoot?.getElementById("email-us-window")
705
- ?.shadowRoot?.getElementById("email-us-form") ??
706
- this.shadowRoot // tour booking form
707
- ?.getElementById("meetelise-launcher")
708
- ?.shadowRoot?.getElementById("tour-scheduler-window")
709
- ?.shadowRoot?.getElementById("scheduler-container") ??
710
- this.shadowRoot // text us form
711
- ?.getElementById("meetelise-launcher")
712
- ?.shadowRoot?.getElementById("call-us-window")
713
- ?.shadowRoot?.getElementById("details-window");
714
-
715
- if (!pubnubContainerElement) return;
716
- e.preventDefault();
717
-
718
- const inputsRef = pubnubContainerElement.querySelectorAll(
719
- "input, me-select, textarea, action-confirm-button, [tabindex]"
720
- );
721
-
722
- const inputsArray = Array.from(inputsRef) as HTMLElement[];
723
- const currentFocusedIndex = inputsArray.findIndex((el) =>
724
- el.matches(":focus")
725
- );
726
-
727
- const nextIndex = (currentFocusedIndex + 1) % inputsArray.length;
728
- inputsArray[nextIndex].focus();
729
- }
730
- };
731
-
732
- getDeepActiveElement(): Element | null {
733
- let activeElement = document.activeElement;
734
- let count = 0;
735
- while (
736
- activeElement?.shadowRoot &&
737
- activeElement?.shadowRoot.activeElement
738
- ) {
739
- count += 1;
740
- activeElement = activeElement?.shadowRoot.activeElement;
741
- if (count > 100) {
742
- // just incase we get stuck in an infinite loop
743
- sendLoggingEvent({
744
- logType: LogType.error,
745
- buildingSlug: this.buildingSlug,
746
- logTitle: "[ERROR_GETTING_DEEP_ELEMENT]",
747
- logData: { message: "error getting deep active element" },
748
- });
749
- break;
750
- }
751
- }
752
- return activeElement;
753
- }
754
-
755
- updated(): void {
756
- this.adjustPositionChatAdditionalActions();
757
- }
758
-
759
- disconnectedCallback(): void {
760
- this.myPubnub?.handleDisconnect();
761
- window.removeEventListener(
762
- "resize",
763
- this.adjustPositionChatAdditionalActions
764
- );
765
- window.removeEventListener("keydown", this.handleKeydownTab);
766
-
767
- super.disconnectedCallback();
768
- }
769
-
770
- onClickMinimize = (e: MouseEvent): void => {
771
- e.preventDefault();
772
- e.stopPropagation();
773
- this.isMinimized = !this.isMinimized;
774
- this.showTourNextToChat = false;
775
- };
776
-
777
- private attachOnClickToLauncher = () => {
778
- const launcher = this.launcherRef.value;
779
- if (!launcher) {
780
- return;
781
- }
782
-
783
- launcher.onChatTapped = async () => {
784
- this.displayPubnubChat = true;
785
- this.hideLauncher = true;
786
- this.hasMounted = true;
787
- localStorage.setItem("isChatOpen", "true");
788
- };
789
- launcher.onExitChat = this.onExitChat;
790
- };
791
-
792
- onExitChat = (): void => {
793
- this.displayPubnubChat = false;
794
- this.hideLauncher = false;
795
- this.showTourNextToChat = false;
796
- localStorage.setItem("isChatOpen", "false");
797
- };
798
-
799
- render(): TemplateResult {
800
- installLauncher();
801
- const showChatAdditionalActions =
802
- this.hideLauncher && !this.isLoading && !isMobile();
803
-
804
- return html`
805
- <meta name="viewport" content="width=device-width, initial-scale=1">
806
- <div id="aria-describe-info" style="display: none;">
807
- EliseAI widget that allows you to chat with a virtual assistant, book
808
- a tour, contact the leasing office, and more.
809
- </div>
810
-
811
-
812
- ${
813
- this.displayPubnubChat
814
- ? html`
815
- <pubnub-chat
816
- id="pubnub-chat"
817
- .channel=${this.myPubnub?.channel}
818
- .myPubnub=${this.myPubnub}
819
- .orgSlug=${this.orgSlug}
820
- .buildingSlug=${this.buildingSlug}
821
- .widgetType=${WidgetType.Default}
822
- .building=${this.buildingWebchatView}
823
- .primaryColor=${this.primaryColor}
824
- .backgroundColor=${this.backgroundColor}
825
- .onClickExit=${this.onExitChat}
826
- .onMount=${() => {
827
- this.adjustPositionChatAdditionalActions();
828
- }}
829
- .requiresConsent=${this.requiresConsent}
830
- .hideMobileFeatures=${this.hideMobileFeatures}
831
- .privacyPolicyUrl=${this.privacyPolicyUrl}
832
- .top=${this.top}
833
- .bottom=${this.bottom}
834
- .left=${this.left}
835
- .right=${this.right}
836
- ></pubnub-chat>
837
- <chat-additional-actions
838
- id="chatAdditionalActionsPubnub"
839
- .showChatAdditionalActions=${showChatAdditionalActions ||
840
- this.showTourNextToChat}
841
- .buildingWebchatView=${this.buildingWebchatView}
842
- .primaryColor=${this.primaryColor}
843
- .backgroundColor=${this.backgroundColor}
844
- .enabledChatWidgets=${this.enabledChatWidgets}
845
- .onClickMinimize=${this.onClickMinimize}
846
- .onClickEmailOption=${this.handleContactClicked}
847
- .onClickPhoneOption=${this.handleContactTabClicked}
848
- .onClickSSTOption=${this.handleTourClicked}
849
- .onClose=${() => {
850
- this.displayPubnubChat = false;
851
- this.showTourNextToChat = false;
852
- this.hideLauncher = false;
853
- localStorage.setItem("isChatOpen", "false");
854
- }}
855
- ></chat-additional-actions>
856
- `
857
- : ""
858
- }
859
-
860
- <div
861
- id='meetelise-chat-launcher-container'
862
- class=${classMap({
863
- launcherContainer: true,
864
- ["launcher__mobile"]: isMobile(),
865
- ["launcher__desktop"]: !isMobile(),
866
- ["meetelise-chat"]: true,
867
- launcher: true,
868
- shouldBeVisible: true,
869
- ["hideTab"]: this.isLoading,
870
- })}
871
- >
872
- ${
873
- this.buildingWebchatView
874
- ? html`<meetelise-launcher
875
- id="meetelise-launcher"
876
- ${ref(this.launcherRef)}
877
- .isFirstMount=${!this.hasMounted}
878
- .buildingId=${this.buildingWebchatView?.id ?? 0}
879
- .hasDynamicSchedulingEnabled=${this.buildingWebchatView
880
- ?.usesDynamicScheduling ?? false}
881
- .orgLegalName=${this.buildingWebchatView?.orgLegalName ?? ""}
882
- .tourTypeOptions=${this.buildingWebchatView
883
- ?.tourTypeOptions ?? []}
884
- .launcherStyles=${this.launcherStyles}
885
- .primaryColor=${this.primaryColor}
886
- .backgroundColor=${this.backgroundColor}
887
- .foregroundColorOnPrimaryBackgroundColor=${this
888
- .foregroundColorOnPrimaryBackgroundColor ?? "white"}
889
- .foregroundColorOnSecondaryBackgroundColor=${this
890
- .foregroundColorOnSecondaryBackgroundColor ?? "black"}
891
- .isMinimized=${this.isMinimized}
892
- .onClickMinimize=${this.onClickMinimize}
893
- .onSstClose=${this.onSstClose}
894
- .overrideRentgrata=${this.overrideRentgrata}
895
- .autoOpenChatWidget=${this.buildingWebchatView
896
- ?.autoOpenChat ?? false}
897
- .mobileStyles=${isMobile() || this.isMinimized
898
- ? this.mobileStyles
899
- : {}}
900
- chatCallUsHeader=${this.buildingWebchatView
901
- ?.chatCallUsHeader ?? ""}
902
- chatId="${this.chatId}"
903
- phoneNumber="${this.phoneNumberForSource?.number ??
904
- this.buildingWebchatView?.phoneNumber ??
905
- ""}"
906
- buildingName=${this.buildingWebchatView?.name ?? ""}
907
- orgSlug="${this.orgSlug}"
908
- buildingSlug="${this.buildingSlug}"
909
- sgtUrl="${this.buildingWebchatView?.sgtUrl ?? ""}"
910
- selfGuidedToursTypeOffered="${this.buildingWebchatView
911
- ?.selfGuidedToursTypeOffered}"
912
- selfGuidedTourEnabled="${this.buildingWebchatView
913
- ?.isSelfGuidedTourEnabled}"
914
- designConcept="${this.designConcept ?? ""}"
915
- currentLeadSource="${this.LeadSourceClient?.leadSource ??
916
- null ??
917
- ""}"
918
- featureFlagShowDropdown="${this.featureFlagShowDropdown}"
919
- .leadSources="${this.leadSources ?? []}"
920
- escortedToursLink="${this.buildingWebchatView
921
- ?.escortedToursLink ?? ""}"
922
- virtualToursLink="${this.buildingWebchatView
923
- ?.virtualToursLink ?? ""}"
924
- .showTourNextToChat="${this.showTourNextToChat ?? ""}"
925
- @closeShowTourNextToChat=${() => {
926
- this.showTourNextToChat = false;
927
- this.hideLauncher = true;
928
- }}
929
- ?hidden=${this.hideLauncher}
930
- .hasCallUsEnabledDesktop=${this.enabledChatWidgets
931
- .callDesktop}
932
- .hasCallUsEnabledMobile=${this.enabledChatWidgets.callMobile}
933
- .hasChatEnabledDesktop=${this.enabledChatWidgets.chatDesktop}
934
- .hasChatEnabledMobile=${this.enabledChatWidgets.chatMobile}
935
- .hasEmailEnabledDesktop=${this.enabledChatWidgets
936
- .emailDesktop}
937
- .hasEmailEnabledMobile=${this.enabledChatWidgets.emailMobile}
938
- .hasTextUsEnabledDesktop=${this.enabledChatWidgets
939
- .textDesktop}
940
- .hasTextUsEnabledMobile=${this.enabledChatWidgets.textMobile}
941
- .hasSSTEnabledDesktop=${this.enabledChatWidgets.sstDesktop}
942
- .hasSSTEnabledMobile=${this.enabledChatWidgets.sstMobile}
943
- .hasApplyNowEnabledDesktop=${this.buildingWebchatView
944
- .shouldShowApplyNowDesktop}
945
- .hasApplyNowEnabledMobile=${this.buildingWebchatView
946
- .shouldShowApplyNowMobile}
947
- .applicationLink=${this.buildingWebchatView.applicationLink}
948
- .hasPricingCalculatorEnabledDesktop=${this.enabledChatWidgets
949
- .calcDesktop}
950
- .hasPricingCalculatorEnabledMobile=${this.enabledChatWidgets
951
- .calcMobile}
952
- .hasHideMobileFeatures=${this.hideMobileFeatures}
953
- .top=${this.top}
954
- .bottom=${this.bottom}
955
- .left=${this.left}
956
- .right=${this.right}
957
- .leadSourceClient=${this.LeadSourceClient}
958
- >
959
- </meetelise-launcher>`
960
- : ""
961
- }
962
- </div>
963
- </meta>
964
- `;
965
- }
966
- }
967
-
968
- declare global {
969
- interface HTMLElementTagNameMap {
970
- "me-chat": MEChat;
971
- }
972
-
973
- interface Window {
974
- RCTPCampaign?: { CampaignDetails: { Source: string } };
975
- }
976
- }
977
-
978
- const findElementById = (priorityIds: string[]): HTMLElement | null => {
979
- for (const id of priorityIds) {
980
- const element = document.getElementById(id);
981
- if (element) {
982
- return element;
983
- }
984
- }
985
- return null;
986
- };
987
-
988
- const overrideContactUsForm = async (
989
- orgSlug: string,
990
- chatId: string | null,
991
- sampleRateToUse: number,
992
- buildingSlug?: string,
993
- leadSource?: string | null
994
- ): Promise<void> => {
995
- if (!buildingSlug || !orgSlug) return;
996
- const shouldUseOverrideContactUsForm =
997
- await fetchFeatureFlagUseOverrideContactUsForm(buildingSlug);
998
- if (!shouldUseOverrideContactUsForm) return;
999
-
1000
- const form = findElementById([
1001
- "myContactForm",
1002
- "contactus",
1003
- "fpa-myContactForm",
1004
- ]);
1005
- let btn = undefined;
1006
-
1007
- if (!form || !(form instanceof HTMLFormElement)) {
1008
- const foundForm = document.querySelector("form");
1009
- if (window.location.pathname.toLowerCase().includes("contactus")) {
1010
- logContactUsFormError(
1011
- buildingSlug,
1012
- orgSlug,
1013
- "Could not find form",
1014
- foundForm ? { id: foundForm.id } : undefined
1015
- );
1016
- }
1017
- return;
1018
- }
1019
-
1020
- // If the form is apart of the schedule tour form, do not override
1021
- // https://meetelise.slack.com/archives/C01BT9GB9LZ/p1706132172167819
1022
- try {
1023
- if (form.parentElement && form.parentElement.id === "scheduleTourForm") {
1024
- return logContactUsFormError(
1025
- buildingSlug,
1026
- orgSlug,
1027
- "Form is apart of schedule tour form!"
1028
- );
1029
- }
1030
- } catch (error) {
1031
- // eslint-disable-next-line no-console
1032
- console.error("error", error);
1033
- }
1034
-
1035
- // Loop through the elements of the form
1036
- for (let i = 0; i < form.elements.length; i++) {
1037
- const element = form.elements[i];
1038
- if (
1039
- element.tagName.toLowerCase() === "button" &&
1040
- element.getAttribute("data-selenium-id") === "fakebutton"
1041
- ) {
1042
- btn = element;
1043
- break;
1044
- }
1045
- if (element.getAttribute("id") === "fpa-contact-submit") {
1046
- btn = element;
1047
- break;
1048
- }
1049
- }
1050
-
1051
- if (!btn) {
1052
- return logContactUsFormError(
1053
- buildingSlug,
1054
- orgSlug,
1055
- "Could not find submit button"
1056
- );
1057
- }
1058
-
1059
- const eliseUrl =
1060
- "https://app.meetelise.com/platformApi/state/create/contactMe";
1061
-
1062
- const getFormElements = () => {
1063
- const firstName = findElementById([
1064
- "firstname",
1065
- "txtName",
1066
- "fpa-firstname",
1067
- "Form_FirstName",
1068
- ]) as HTMLInputElement | null;
1069
- const lastName = findElementById([
1070
- "lastname",
1071
- "txtName2",
1072
- "fpa-lastname",
1073
- "Form_LastName",
1074
- ]) as HTMLInputElement | null;
1075
- const email = findElementById([
1076
- "email",
1077
- "txtEmail",
1078
- "fpa-email",
1079
- "Form_Email",
1080
- ]) as HTMLInputElement | null;
1081
- const phone = findElementById([
1082
- "phonenumber",
1083
- "txtPhone",
1084
- "fpa-phonenumber",
1085
- "Form_Phone",
1086
- ]) as HTMLInputElement | null;
1087
- const message = findElementById([
1088
- "message",
1089
- "txtComments",
1090
- "fpa-email",
1091
- "Form_Message",
1092
- ]) as HTMLTextAreaElement | null;
1093
-
1094
- const formElements = { firstName, lastName, email, phone, message };
1095
- return formElements;
1096
- };
1097
-
1098
- const isMissingFormElements = () => {
1099
- if (Object.values(getFormElements()).some((el) => el === null)) {
1100
- return true;
1101
- }
1102
- return false;
1103
- };
1104
-
1105
- const validateFormElements = () => {
1106
- Object.values(getFormElements()).forEach((el) => {
1107
- if (el?.getAttribute("aria-required") === "true" && !el.value) {
1108
- el.className = "form-control required is-invalid";
1109
- }
1110
- });
1111
- };
1112
-
1113
- const isValid = () => {
1114
- return Object.values(getFormElements()).every((el) => {
1115
- if (el === null) return false;
1116
- if (el.getAttribute("aria-required") === "true" && !el.value) {
1117
- return false;
1118
- }
1119
- if (
1120
- (el.id === "email" || el.id === "phonenumber") &&
1121
- el.getAttribute("aria-invalid") === "true"
1122
- ) {
1123
- return false;
1124
- }
1125
- return true;
1126
- });
1127
- };
1128
-
1129
- if (isMissingFormElements()) {
1130
- const missingElements = Object.entries(getFormElements())
1131
- .filter(([, val]) => val === null)
1132
- .map(([key]) => key);
1133
- return logContactUsFormError(
1134
- buildingSlug,
1135
- orgSlug,
1136
- "Missing the following form elements: " + missingElements.join(", ")
1137
- );
1138
- }
1139
- const originalButtonText = btn.textContent;
1140
-
1141
- // Replace the original form element with the cloned one
1142
- const clonedButton = btn.cloneNode(true) as HTMLButtonElement;
1143
- btn.parentNode?.replaceChild(clonedButton, btn);
1144
-
1145
- if (clonedButton) {
1146
- logSignal({
1147
- params: {
1148
- chat_id: chatId,
1149
- org_slug: orgSlug,
1150
- building_slug: buildingSlug,
1151
- is_form_override_success: true,
1152
- },
1153
- sampleRate: sampleRateToUse,
1154
- });
1155
- }
1156
-
1157
- clonedButton.onclick = async function (event) {
1158
- validateFormElements();
1159
-
1160
- if (!isValid()) return;
1161
- event.preventDefault();
1162
-
1163
- clonedButton.textContent = "Processing request...";
1164
- clonedButton.disabled = true;
1165
-
1166
- const formValues: { [key: string]: string | undefined } = {};
1167
- Object.entries(getFormElements()).forEach(
1168
- ([key, val]) => (formValues[key] = val?.value)
1169
- );
1170
-
1171
- const buildingWebchatView = await fetchBuildingWebchatView(
1172
- orgSlug,
1173
- buildingSlug
1174
- );
1175
-
1176
- if (!buildingWebchatView) {
1177
- logContactUsFormError(buildingSlug, orgSlug, "Could not find building");
1178
- return null;
1179
- }
1180
-
1181
- const data = {
1182
- email_address: formValues.email,
1183
- first_name: formValues.firstName,
1184
- last_name: formValues.lastName,
1185
- phone_number: formValues.phone,
1186
- first_message: formValues.message,
1187
- building_id: buildingWebchatView.id,
1188
- is_external_form: true,
1189
- lead_sources: [
1190
- ...new Set(
1191
- leadSource
1192
- ? [leadSource, getDefaultLeadSourceAttribution(orgSlug)]
1193
- : [getDefaultLeadSourceAttribution(orgSlug)]
1194
- ),
1195
- ],
1196
- };
1197
-
1198
- const jsonData = JSON.stringify(data);
1199
-
1200
- fetch(eliseUrl, {
1201
- method: "POST",
1202
- headers: {
1203
- "Content-Type": "application/json",
1204
- "building-slug": buildingSlug,
1205
- "org-slug": orgSlug,
1206
- },
1207
- body: jsonData,
1208
- })
1209
- .then((response) => {
1210
- if (!response.ok) {
1211
- throw new Error(`HTTP error ${response.status}`);
1212
- }
1213
-
1214
- if (form) {
1215
- form.reset();
1216
- }
1217
-
1218
- Object.values(getFormElements()).forEach((el) => {
1219
- if (!el) return;
1220
- el.disabled = true;
1221
- });
1222
- clonedButton.textContent = "Submitted";
1223
- clonedButton.disabled = true;
1224
-
1225
- return response.json();
1226
- })
1227
- .catch(() => {
1228
- clonedButton.textContent = originalButtonText;
1229
- });
1230
- };
1231
- };
1232
-
1233
- const logContactUsFormError = (
1234
- buildingSlug: string,
1235
- orgSlug: string,
1236
- reason?: string,
1237
- formIdInfo?: Record<string, unknown>
1238
- ) => {
1239
- const url = `https://app.meetelise.com/platformApi/webchat/form-override-error`;
1240
- const body = JSON.stringify({
1241
- orgSlug,
1242
- buildingSlug,
1243
- reason,
1244
- url: window.location.href,
1245
- formIdInfo,
1246
- });
1247
-
1248
- fetch(url, {
1249
- method: "POST",
1250
- headers: {
1251
- "Content-Type": "application/json",
1252
- "building-slug": buildingSlug,
1253
- "org-slug": orgSlug,
1254
- },
1255
- body,
1256
- });
1257
- };