@meetelise/chat 1.11.0 → 1.12.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 (57) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/.github/workflows/release.yml +1 -0
  3. package/.vscode/settings.json +6 -1
  4. package/CONTRIBUTING.md +8 -0
  5. package/package.json +9 -10
  6. package/public/demo/index.html +78 -11
  7. package/public/dist/index.js +1713 -1
  8. package/public/dist/index.js.LICENSE.txt +26 -14
  9. package/public/index.html +2 -1
  10. package/src/MEChat.test.ts +5 -5
  11. package/src/MEChat.ts +53 -0
  12. package/src/WebComponent/InHouseLauncher.ts +454 -0
  13. package/src/WebComponent/MEChat.css +5 -0
  14. package/src/WebComponent/MEChat.ts +282 -0
  15. package/src/WebComponent/OfficeHours.ts +73 -0
  16. package/src/WebComponent/Scheduler/date-picker.ts +320 -0
  17. package/src/WebComponent/Scheduler/me-select.ts +244 -0
  18. package/src/WebComponent/Scheduler/time-picker.ts +101 -0
  19. package/src/WebComponent/Scheduler/tour-scheduler.ts +383 -0
  20. package/src/WebComponent/Scheduler/tour-type-option.ts +92 -0
  21. package/src/WebComponent/actions/ActionConfirmButton.ts +94 -0
  22. package/src/WebComponent/actions/CallUsWindow.ts +110 -0
  23. package/src/WebComponent/actions/DetailsWindow.ts +109 -0
  24. package/src/WebComponent/actions/EmailUsWindow.ts +431 -0
  25. package/src/WebComponent/actions/InputStyles.ts +31 -0
  26. package/src/WebComponent/actions/TextUsWindow.ts +226 -0
  27. package/src/WebComponent/actions/formatPhoneNumber.ts +42 -0
  28. package/src/WebComponent/inHouseLauncherStyles.ts +300 -0
  29. package/src/WebComponent/index.ts +2 -0
  30. package/src/WebComponent/utils.ts +82 -0
  31. package/src/fetchBuildingInfo.ts +1 -0
  32. package/src/getAvailabilities.ts +71 -0
  33. package/src/getRegisteredPhoneNumbers.ts +56 -0
  34. package/src/themes.ts +5 -3
  35. package/tsconfig.json +9 -3
  36. package/web-test-runner.config.js +0 -6
  37. package/webpack.config.cjs +8 -25
  38. package/public/dist/index.d.ts +0 -1
  39. package/public/dist/src/ChatButton.d.ts +0 -9
  40. package/public/dist/src/ChatIcon.d.ts +0 -6
  41. package/public/dist/src/InHouseLauncher.d.ts +0 -11
  42. package/public/dist/src/MEChat.d.ts +0 -73
  43. package/public/dist/src/analytics.d.ts +0 -34
  44. package/public/dist/src/assetUrls.d.ts +0 -2
  45. package/public/dist/src/chatID.d.ts +0 -11
  46. package/public/dist/src/createConversation.d.ts +0 -4
  47. package/public/dist/src/fetchBuildingInfo.d.ts +0 -25
  48. package/public/dist/src/themes.d.ts +0 -52
  49. package/public/dist/src/utils.d.ts +0 -2
  50. package/src/ChatButton.module.scss +0 -51
  51. package/src/ChatButton.tsx +0 -38
  52. package/src/ChatIcon.tsx +0 -26
  53. package/src/DemoApp.tsx +0 -113
  54. package/src/InHouseLauncher.module.scss +0 -139
  55. package/src/InHouseLauncher.tsx +0 -69
  56. package/src/MEChat.module.scss +0 -22
  57. package/src/MEChat.tsx +0 -293
@@ -0,0 +1,109 @@
1
+ import { css, html, LitElement, TemplateResult } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import { styleMap } from "lit/directives/style-map.js";
4
+ import { isMobile } from "../../utils";
5
+
6
+ @customElement("details-window")
7
+ export class DetailsWindow extends LitElement {
8
+ static styles = css`
9
+ @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap");
10
+ .details-window-wrapper {
11
+ background: rgba(255, 255, 255, 0.75);
12
+ box-shadow: 0px 0px 14px 5px rgba(0, 0, 0, 0.25);
13
+ backdrop-filter: blur(10px);
14
+ font-family: "Poppins";
15
+ color: #202020;
16
+ padding: 20px;
17
+ border-radius: 10px 0px 0px 10px;
18
+ }
19
+
20
+ .details-window-header {
21
+ display: flex;
22
+ justify-content: space-between;
23
+ font-weight: 700;
24
+ font-size: 14px;
25
+ line-height: 22px;
26
+ margin-bottom: 25px;
27
+ }
28
+
29
+ @media screen and (max-width: 767px) {
30
+ .details-window-wrapper {
31
+ border-radius: 0px 0px 0px 0px;
32
+ }
33
+ }
34
+ `;
35
+ @property({ type: String })
36
+ height = "425px";
37
+
38
+ @property({ type: String })
39
+ width = "326px";
40
+
41
+ @property({ type: Boolean })
42
+ useRoundedBorders = true;
43
+
44
+ @property({ type: String })
45
+ headerText = "";
46
+
47
+ @property({ attribute: false })
48
+ onCloseClick?: (e: MouseEvent) => void;
49
+
50
+ @state()
51
+ isMobileSized = false;
52
+
53
+ async firstUpdated(): Promise<void> {
54
+ // Give the browser a chance to paint
55
+ await new Promise((resolve) => setTimeout(resolve, 0));
56
+ this.isMobileSized = isMobile();
57
+ window.addEventListener("resize", () => {
58
+ this.isMobileSized = isMobile();
59
+ });
60
+ }
61
+
62
+ render(): TemplateResult {
63
+ return html`
64
+ <div
65
+ class="details-window-wrapper"
66
+ style=${styleMap({
67
+ height: this.height,
68
+ width: this.isMobileSized ? "-webkit-fill-available" : this.width,
69
+ })}
70
+ >
71
+ <div class="details-window-header">
72
+ <div>${this.headerText}</div>
73
+ ${this.onCloseClick
74
+ ? html`<div @click=${this.onCloseClick}>
75
+ <svg
76
+ width="19"
77
+ height="19"
78
+ viewBox="0 0 19 19"
79
+ fill="none"
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ >
82
+ <line
83
+ x1="0.986863"
84
+ y1="18.2746"
85
+ x2="18.2929"
86
+ y2="0.968589"
87
+ stroke="#202020"
88
+ stroke-width="2"
89
+ />
90
+ <path
91
+ d="M1.01394 0.999974L18.0103 18.0242"
92
+ stroke="#202020"
93
+ stroke-width="2"
94
+ />
95
+ </svg>
96
+ </div>`
97
+ : ""}
98
+ </div>
99
+ <slot class="details-window__slot-content"></slot>
100
+ </div>
101
+ `;
102
+ }
103
+ }
104
+
105
+ export const installDetailsWindow = (): void => {
106
+ if (!window.customElements.get("details-window")) {
107
+ window.customElements.define("details-window", DetailsWindow);
108
+ }
109
+ };
@@ -0,0 +1,431 @@
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 { installActionConfirmButton } from "./ActionConfirmButton";
6
+ import { installDetailsWindow } from "./DetailsWindow";
7
+ import {
8
+ formatToPhone,
9
+ isModifierKey,
10
+ isNumericInput,
11
+ } from "./formatPhoneNumber";
12
+ import { InputStyles } from "./InputStyles";
13
+ import axios from "axios";
14
+
15
+ @customElement("email-us-window")
16
+ export class EmailUsWindow extends LitElement {
17
+ static styles = [
18
+ css`
19
+ @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap");
20
+ .email-us__name-input {
21
+ width: 134px;
22
+ height: 49px;
23
+ }
24
+
25
+ .email-us__name-wrapper {
26
+ display: flex;
27
+ }
28
+
29
+ .email-us__horizontal-spacer {
30
+ margin-left: 2.5px;
31
+ margin-right: 2.5px;
32
+ }
33
+
34
+ .email-us__contact-input {
35
+ width: -webkit-fill-available;
36
+ height: 49px;
37
+ }
38
+
39
+ .email-us__vertical-spacer {
40
+ margin-top: 15px;
41
+ margin-bottom: 15px;
42
+ }
43
+
44
+ .email-us__message-input {
45
+ resize: none;
46
+ width: -webkit-fill-available;
47
+ height: 159px;
48
+ padding: 8px;
49
+ }
50
+
51
+ .email-us__legal-terms {
52
+ color: #202020;
53
+ font-family: "Poppins";
54
+ font-style: normal;
55
+ font-weight: 400;
56
+ font-size: 12px;
57
+ line-height: 22px;
58
+ }
59
+
60
+ .email-us__legal-term-action {
61
+ color: #202020;
62
+ }
63
+
64
+ .email-us__submitted-info {
65
+ font-size: 18px;
66
+ line-height: 24px;
67
+ color: #202020;
68
+ }
69
+
70
+ .email-us__error-text {
71
+ color: #202020;
72
+ font-family: "Poppins";
73
+ font-size: 10px;
74
+ line-height: 22px;
75
+ margin-top: 8px;
76
+ }
77
+
78
+ @media screen and (max-width: 767px) {
79
+ .email-us__name-input {
80
+ width: -webkit-fill-available;
81
+ }
82
+ }
83
+ `,
84
+ InputStyles,
85
+ ];
86
+
87
+ @property({ attribute: false })
88
+ onCloseClicked?: (e: MouseEvent) => void;
89
+
90
+ @property({ attribute: false })
91
+ buildingId = 0;
92
+
93
+ phoneNumberInputRef: Ref<HTMLInputElement> = createRef();
94
+
95
+ @property()
96
+ privatePolicyUrl = "https://www.meetelise.com/privacy";
97
+ @property()
98
+ termsOfServiceUrl = "_blank";
99
+
100
+ @state()
101
+ firstName = "";
102
+ @state()
103
+ lastName = "";
104
+ @state()
105
+ email = "";
106
+ @state()
107
+ phoneNumber = "";
108
+ @state()
109
+ message = "";
110
+
111
+ @state()
112
+ hasNameError = false;
113
+ @state()
114
+ hasEmailError = false;
115
+ @state()
116
+ hasPhoneNumberError = false;
117
+ @state()
118
+ hasMessageError = false;
119
+ @state()
120
+ hasSubmittedForm = false;
121
+ @state()
122
+ windowHeight = 525;
123
+ @state()
124
+ hasSubmissionError = false;
125
+ @state()
126
+ isSubmitting = false;
127
+
128
+ onChangeFirstName = (e: Event): void => {
129
+ if (!e.target) {
130
+ return;
131
+ }
132
+
133
+ this.firstName = (e.target as HTMLInputElement).value;
134
+ };
135
+
136
+ onChangeLastName = (e: Event): void => {
137
+ if (!e.target) {
138
+ return;
139
+ }
140
+
141
+ this.lastName = (e.target as HTMLInputElement).value;
142
+ };
143
+
144
+ onChangeEmail = (e: Event): void => {
145
+ if (!e.target) {
146
+ return;
147
+ }
148
+
149
+ this.email = (e.target as HTMLInputElement).value;
150
+ };
151
+
152
+ onChangePhoneNumber = (e: Event): void => {
153
+ if (!e.target || !this.phoneNumberInputRef.value) {
154
+ return;
155
+ }
156
+ if (isModifierKey(e as KeyboardEvent)) {
157
+ return;
158
+ }
159
+ const inputElement = e.target as HTMLInputElement;
160
+
161
+ this.phoneNumber = formatToPhone(inputElement.value);
162
+
163
+ this.phoneNumberInputRef.value.value = this.phoneNumber;
164
+ };
165
+
166
+ onChangeMessage = (e: Event): void => {
167
+ if (!e.target) {
168
+ return;
169
+ }
170
+
171
+ this.message = (e.target as HTMLTextAreaElement).value;
172
+ };
173
+
174
+ enforceFormat = (e: KeyboardEvent): void => {
175
+ if (!isNumericInput(e) && !isModifierKey(e)) {
176
+ e.preventDefault();
177
+ }
178
+ };
179
+
180
+ validateFormFields = (): void => {
181
+ this.hasNameError = false;
182
+ this.hasEmailError = false;
183
+ this.hasPhoneNumberError = false;
184
+ this.hasMessageError = false;
185
+ this.hasSubmissionError = false;
186
+ if (!this.firstName || !this.lastName) {
187
+ this.hasNameError = true;
188
+ }
189
+ if (!this.email || !this.email.includes("@")) {
190
+ this.hasEmailError = true;
191
+ }
192
+ if (!this.phoneNumber || this.phoneNumber.length !== 14) {
193
+ this.hasPhoneNumberError = true;
194
+ }
195
+ if (!this.message) {
196
+ this.hasMessageError = true;
197
+ }
198
+ this.windowHeight = 525 + 30 * this.getNumErrors();
199
+ };
200
+
201
+ onClick = async (): Promise<void> => {
202
+ if (this.isSubmitting) {
203
+ return;
204
+ }
205
+ this.validateFormFields();
206
+ if (
207
+ this.hasNameError ||
208
+ this.hasEmailError ||
209
+ this.hasPhoneNumberError ||
210
+ this.hasMessageError
211
+ ) {
212
+ return;
213
+ }
214
+ try {
215
+ this.isSubmitting = true;
216
+ // Height of the button when it's in the loading state
217
+ this.windowHeight = 525 + 60;
218
+ await createEmail(
219
+ this.firstName,
220
+ this.lastName,
221
+ this.email,
222
+ this.phoneNumber,
223
+ this.message,
224
+ this.buildingId
225
+ );
226
+ this.hasSubmittedForm = true;
227
+ this.isSubmitting = false;
228
+ // Remove the height of the button when it leaves the loading state
229
+ this.windowHeight = 525;
230
+ } catch (e) {
231
+ this.hasSubmissionError = true;
232
+ this.isSubmitting = false;
233
+ // Remove the height of the button when it leaves the loading state
234
+ this.windowHeight = 525;
235
+ }
236
+ };
237
+
238
+ getNumErrors = (): number => {
239
+ return [
240
+ this.hasNameError,
241
+ this.hasEmailError,
242
+ this.hasPhoneNumberError,
243
+ this.hasMessageError,
244
+ this.hasSubmissionError,
245
+ ].filter((v) => v).length;
246
+ };
247
+
248
+ render = (): TemplateResult => {
249
+ installDetailsWindow();
250
+ installActionConfirmButton();
251
+
252
+ if (this.hasSubmittedForm) {
253
+ return html`
254
+ <details-window
255
+ headerText="Email us"
256
+ .onCloseClick=${this.onCloseClicked}
257
+ >
258
+ <div class="email-us-wrapper">
259
+ <div class="email-us__vertical-spacer"></div>
260
+ <svg
261
+ width="22"
262
+ height="18"
263
+ viewBox="0 0 22 18"
264
+ fill="none"
265
+ xmlns="http://www.w3.org/2000/svg"
266
+ >
267
+ <path
268
+ d="M22 17.007C21.9982 17.2696 21.8931 17.521 21.7075 17.7068C21.5219 17.8926 21.2706 17.9979 21.008 18H2.992C2.72881 17.9997 2.4765 17.895 2.29049 17.7088C2.10448 17.5226 2 17.2702 2 17.007V16H20V4.3L12 11.5L2 2.5V1C2 0.734784 2.10536 0.48043 2.29289 0.292893C2.48043 0.105357 2.73478 0 3 0H21C21.2652 0 21.5196 0.105357 21.7071 0.292893C21.8946 0.48043 22 0.734784 22 1V17.007ZM4.434 2L12 8.81L19.566 2H4.434ZM0 12H8V14H0V12ZM0 7H5V9H0V7Z"
269
+ fill="#202020"
270
+ />
271
+ </svg>
272
+ <div class="email-us__vertical-spacer"></div>
273
+ <div class="email-us__submitted-info">
274
+ Thank you for your message. A member of our leasing team will
275
+ contact you.
276
+ </div>
277
+ </div>
278
+ </details-window>
279
+ `;
280
+ }
281
+
282
+ return html`
283
+ <details-window
284
+ headerText="Email us"
285
+ .onCloseClick=${this.onCloseClicked}
286
+ height="${this.windowHeight}px"
287
+ >
288
+ <div class="email-us-wrapper">
289
+ <div class="email-us__name-wrapper">
290
+ <input
291
+ type="text"
292
+ class=${classMap({
293
+ ["webchat-input"]: true,
294
+ ["email-us__name-input"]: true,
295
+ })}
296
+ placeholder="First name"
297
+ value="${this.firstName}"
298
+ @keyup=${this.onChangeFirstName}
299
+ />
300
+ <div class="email-us__horizontal-spacer"></div>
301
+ <input
302
+ type="text"
303
+ class=${classMap({
304
+ ["webchat-input"]: true,
305
+ ["email-us__name-input"]: true,
306
+ })}
307
+ placeholder="Last name"
308
+ value="${this.lastName}"
309
+ @keyup=${this.onChangeLastName}
310
+ />
311
+ </div>
312
+ ${this.hasNameError
313
+ ? html`
314
+ <div class="email-us__error-text">Enter your full name</div>
315
+ `
316
+ : ""}
317
+ <div class="email-us__vertical-spacer"></div>
318
+ <input
319
+ type="email"
320
+ placeholder="Email"
321
+ class=${classMap({
322
+ ["webchat-input"]: true,
323
+ ["email-us__contact-input"]: true,
324
+ })}
325
+ .value=${this.email}
326
+ @keyup=${this.onChangeEmail}
327
+ />
328
+ ${this.hasEmailError
329
+ ? html`
330
+ <div class="email-us__error-text">
331
+ Enter valid email address
332
+ </div>
333
+ `
334
+ : ""}
335
+ <div class="email-us__vertical-spacer"></div>
336
+ <input
337
+ ${ref(this.phoneNumberInputRef)}
338
+ type="text"
339
+ placeholder="Phone number"
340
+ inputmode="tel"
341
+ class=${classMap({
342
+ ["webchat-input"]: true,
343
+ ["email-us__contact-input"]: true,
344
+ })}
345
+ .value=${this.phoneNumber}
346
+ maxlength="14"
347
+ @keydown=${this.enforceFormat}
348
+ @keyup=${this.onChangePhoneNumber}
349
+ />
350
+ ${this.hasPhoneNumberError
351
+ ? html`
352
+ <div class="email-us__error-text">
353
+ Enter a valid phone number
354
+ </div>
355
+ `
356
+ : ""}
357
+ <div class="email-us__vertical-spacer"></div>
358
+ <textarea
359
+ placeholder="Message"
360
+ .value=${this.message}
361
+ class=${classMap({
362
+ ["email-us__message-input"]: true,
363
+ ["webchat-input"]: true,
364
+ })}
365
+ @keyup=${this.onChangeMessage}
366
+ ></textarea>
367
+ ${this.hasMessageError
368
+ ? html` <div class="email-us__error-text">Include a message</div> `
369
+ : ""}
370
+ <div class="email-us__vertical-spacer"></div>
371
+ <action-confirm-button
372
+ .onClick=${this.onClick}
373
+ .isLoading=${this.isSubmitting}
374
+ text="Send message"
375
+ ></action-confirm-button>
376
+ ${this.hasSubmissionError
377
+ ? html`
378
+ <div class="email-us__error-text">Error submitting message</div>
379
+ `
380
+ : ""}
381
+ <div class="email-us__vertical-spacer"></div>
382
+ <div class="email-us__legal-terms">
383
+ <a
384
+ class="email-us__legal-term-action"
385
+ href="${this.privatePolicyUrl}"
386
+ >Privacy Policy</a
387
+ >
388
+ </div>
389
+ </div>
390
+ </details-window>
391
+ `;
392
+ };
393
+ }
394
+
395
+ export const installEmailUsWindow = (): void => {
396
+ if (!window.customElements.get("email-us-window")) {
397
+ window.customElements.define("email-us-window", EmailUsWindow);
398
+ }
399
+ };
400
+
401
+ const createEmail = async (
402
+ firstName: string,
403
+ lastName: string,
404
+ email: string,
405
+ rawPhoneNumber: string,
406
+ message: string,
407
+ buildingId: number
408
+ ) => {
409
+ const formattedPhoneNumber =
410
+ "+1" +
411
+ rawPhoneNumber
412
+ .replace("(", "")
413
+ .replace(")", "")
414
+ .replace(" ", "")
415
+ .replace("-", "");
416
+ const requestBody = {
417
+ email_address: email,
418
+ building_id: buildingId,
419
+ lead_source: "property-website",
420
+ first_message: message,
421
+ first_name: firstName,
422
+ last_name: lastName,
423
+ phone_number: formattedPhoneNumber,
424
+ };
425
+
426
+ await axios.post(
427
+ "https://app.meetelise.com/platformApi/state/create/emailMe",
428
+ requestBody,
429
+ { headers: { ["X-SecurityKey"]: "JRL8jV4VcSCwOSir5gWkpgNLfKghmhBG" } }
430
+ );
431
+ };
@@ -0,0 +1,31 @@
1
+ import { css } from "lit";
2
+
3
+ export const InputStyles = css`
4
+ @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap");
5
+ .webchat-input {
6
+ border: 1px solid #83818e;
7
+ outline: none;
8
+ font-size: 14px;
9
+ color: #202020;
10
+ font-weight: 400;
11
+ font-size: 14px;
12
+ line-height: 22px;
13
+ font-family: "Poppins";
14
+ padding-left: 12px;
15
+ padding-right: 12px;
16
+ }
17
+
18
+ .webchat-input:active {
19
+ border: 1px solid #202020;
20
+ }
21
+
22
+ .webchat-input:focus-within {
23
+ border: 1px solid #202020;
24
+ }
25
+
26
+ .webchat-input::placeholder {
27
+ color: #202020;
28
+ font-family: "Poppins";
29
+ font-size: 14px;
30
+ }
31
+ `;