@meetelise/chat 1.56.0 → 1.58.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 +8 -2
- package/dist/src/WebComponent/actions/email-us-window.d.ts +7 -1
- package/dist/src/WebComponent/actions/international-phone-input.d.ts +1 -0
- package/dist/src/WebComponent/brokerReferral.d.ts +3 -1
- package/dist/src/WebComponent/me-chat.d.ts +11 -0
- package/dist/src/main/MEChat.d.ts +12 -1
- package/package.json +1 -1
- package/public/dist/index.js +160 -135
- package/src/WebComponent/Scheduler/tour-scheduler.ts +73 -9
- package/src/WebComponent/actions/email-us-window.ts +63 -9
- package/src/WebComponent/actions/international-phone-input.ts +4 -1
- package/src/WebComponent/brokerReferral.ts +6 -1
- package/src/WebComponent/me-chat.ts +25 -4
- package/src/brokerReferralSubmission.test.ts +93 -6
- package/src/main/MEChat.ts +75 -1
- package/src/orgLevelNavigation.test.ts +114 -1
|
@@ -206,6 +206,16 @@ export class TourScheduler extends LitElement {
|
|
|
206
206
|
brokerEmail = "";
|
|
207
207
|
@state()
|
|
208
208
|
brokerFirmName = "";
|
|
209
|
+
@state()
|
|
210
|
+
brokerPhone = "";
|
|
211
|
+
@state()
|
|
212
|
+
brokerPhoneE164: string | null = null;
|
|
213
|
+
@state()
|
|
214
|
+
brokerPhoneCountry: string | null = null;
|
|
215
|
+
@state()
|
|
216
|
+
brokerPhoneIsValid = false;
|
|
217
|
+
@state()
|
|
218
|
+
brokerPhoneHasBlurred = false;
|
|
209
219
|
|
|
210
220
|
@property({ attribute: false })
|
|
211
221
|
leadSources: string[] = [];
|
|
@@ -669,25 +679,44 @@ export class TourScheduler extends LitElement {
|
|
|
669
679
|
this.email = (e.target as HTMLInputElement).value;
|
|
670
680
|
};
|
|
671
681
|
|
|
682
|
+
onChangeBrokerPhone = (
|
|
683
|
+
event: CustomEvent<InternationalPhoneChangeDetail>
|
|
684
|
+
): void => {
|
|
685
|
+
this.brokerPhone = event.detail.formattedValue;
|
|
686
|
+
this.brokerPhoneE164 = event.detail.e164;
|
|
687
|
+
this.brokerPhoneCountry = event.detail.country;
|
|
688
|
+
this.brokerPhoneIsValid = event.detail.isValid;
|
|
689
|
+
};
|
|
690
|
+
|
|
672
691
|
/**
|
|
673
692
|
* A broker usually starts typing their own details before ticking the box, so ticking it moves
|
|
674
|
-
* the name and
|
|
675
|
-
* the broker's details are filled, re-ticking leaves both sections alone, as on the email form.
|
|
693
|
+
* the name, email and phone into the broker's empty details and leaves the client's fields empty.
|
|
694
|
+
* Once the broker's details are filled, re-ticking leaves both sections alone, as on the email form.
|
|
676
695
|
*/
|
|
677
696
|
onToggleBrokerReferral = (e: Event): void => {
|
|
678
697
|
const isBrokerReferral = (e.target as HTMLInputElement).checked;
|
|
679
698
|
const brokerIsEmpty =
|
|
680
|
-
!this.brokerFirstName &&
|
|
699
|
+
!this.brokerFirstName &&
|
|
700
|
+
!this.brokerLastName &&
|
|
701
|
+
!this.brokerEmail &&
|
|
702
|
+
!this.brokerPhone;
|
|
681
703
|
const clientIsEmpty =
|
|
682
|
-
!this.firstNameInputValue &&
|
|
704
|
+
!this.firstNameInputValue &&
|
|
705
|
+
!this.lastNameInputValue &&
|
|
706
|
+
!this.email &&
|
|
707
|
+
!this.phoneNumber;
|
|
683
708
|
if (isBrokerReferral && brokerIsEmpty) {
|
|
684
709
|
this.brokerFirstName = this.firstNameInputValue;
|
|
685
710
|
this.brokerLastName = this.lastNameInputValue;
|
|
686
711
|
this.brokerEmail = this.email;
|
|
712
|
+
this.brokerPhone = this.phoneNumber;
|
|
713
|
+
this.brokerPhoneE164 = this.phoneNumberE164;
|
|
714
|
+
this.brokerPhoneCountry = this.phoneCountry;
|
|
715
|
+
this.brokerPhoneIsValid = this.phoneNumberIsValid;
|
|
716
|
+
this.brokerPhoneHasBlurred = this.phoneNumberHasBlurred;
|
|
687
717
|
this.firstNameInputValue = "";
|
|
688
718
|
this.lastNameInputValue = "";
|
|
689
719
|
this.email = "";
|
|
690
|
-
// There's no broker phone field: what was typed is the broker's, not the client's.
|
|
691
720
|
this.phoneNumber = "";
|
|
692
721
|
this.phoneNumberE164 = null;
|
|
693
722
|
this.phoneNumberIsValid = false;
|
|
@@ -696,9 +725,18 @@ export class TourScheduler extends LitElement {
|
|
|
696
725
|
this.firstNameInputValue = this.brokerFirstName;
|
|
697
726
|
this.lastNameInputValue = this.brokerLastName;
|
|
698
727
|
this.email = this.brokerEmail;
|
|
728
|
+
this.phoneNumber = this.brokerPhone;
|
|
729
|
+
this.phoneNumberE164 = this.brokerPhoneE164;
|
|
730
|
+
this.phoneCountry = this.brokerPhoneCountry;
|
|
731
|
+
this.phoneNumberIsValid = this.brokerPhoneIsValid;
|
|
732
|
+
this.phoneNumberHasBlurred = this.brokerPhoneHasBlurred;
|
|
699
733
|
this.brokerFirstName = "";
|
|
700
734
|
this.brokerLastName = "";
|
|
701
735
|
this.brokerEmail = "";
|
|
736
|
+
this.brokerPhone = "";
|
|
737
|
+
this.brokerPhoneE164 = null;
|
|
738
|
+
this.brokerPhoneIsValid = false;
|
|
739
|
+
this.brokerPhoneHasBlurred = false;
|
|
702
740
|
}
|
|
703
741
|
this.isBrokerReferral = isBrokerReferral;
|
|
704
742
|
};
|
|
@@ -710,7 +748,8 @@ export class TourScheduler extends LitElement {
|
|
|
710
748
|
this.brokerFirstName,
|
|
711
749
|
this.brokerLastName,
|
|
712
750
|
this.brokerEmail,
|
|
713
|
-
this.brokerFirmName
|
|
751
|
+
this.brokerFirmName,
|
|
752
|
+
this.brokerPhoneE164 ?? ""
|
|
714
753
|
)
|
|
715
754
|
: null;
|
|
716
755
|
|
|
@@ -729,9 +768,12 @@ export class TourScheduler extends LitElement {
|
|
|
729
768
|
!this.bedroomPreferenceRequired || !!this.selectedLayoutValue,
|
|
730
769
|
broker: (): boolean => {
|
|
731
770
|
const broker = this.brokerReferral();
|
|
771
|
+
// The broker's phone is optional, but one that's been typed has to be valid.
|
|
732
772
|
return (
|
|
733
773
|
!broker ||
|
|
734
|
-
(!brokerNameIsMissing(broker) &&
|
|
774
|
+
(!brokerNameIsMissing(broker) &&
|
|
775
|
+
!brokerEmailError(broker, this.email) &&
|
|
776
|
+
(!this.brokerPhone || this.brokerPhoneIsValid))
|
|
735
777
|
);
|
|
736
778
|
},
|
|
737
779
|
};
|
|
@@ -1318,7 +1360,7 @@ export class TourScheduler extends LitElement {
|
|
|
1318
1360
|
|
|
1319
1361
|
renderBrokerToggle = (): TemplateResult => html`
|
|
1320
1362
|
<label class="broker-toggle">
|
|
1321
|
-
<span>Are you
|
|
1363
|
+
<span>Are you a broker or locator?</span>
|
|
1322
1364
|
<input
|
|
1323
1365
|
type="checkbox"
|
|
1324
1366
|
role="switch"
|
|
@@ -1392,6 +1434,25 @@ export class TourScheduler extends LitElement {
|
|
|
1392
1434
|
/>
|
|
1393
1435
|
${emailError ? html`<p class="error-message">${emailError}</p>` : ""}
|
|
1394
1436
|
</div>
|
|
1437
|
+
<div class="inputContainer">
|
|
1438
|
+
<international-phone-input
|
|
1439
|
+
placeholder="Your phone (optional)"
|
|
1440
|
+
.country=${this.brokerPhoneCountry ?? this.country}
|
|
1441
|
+
.value=${this.brokerPhone}
|
|
1442
|
+
.invalid=${this.brokerPhoneHasBlurred &&
|
|
1443
|
+
!!this.brokerPhone &&
|
|
1444
|
+
!this.brokerPhoneIsValid}
|
|
1445
|
+
@phone-change=${this.onChangeBrokerPhone}
|
|
1446
|
+
@phone-blur=${() => {
|
|
1447
|
+
this.brokerPhoneHasBlurred = true;
|
|
1448
|
+
}}
|
|
1449
|
+
></international-phone-input>
|
|
1450
|
+
${this.brokerPhoneHasBlurred &&
|
|
1451
|
+
!!this.brokerPhone &&
|
|
1452
|
+
!this.brokerPhoneIsValid
|
|
1453
|
+
? html`<p class="error-message">Invalid phone number</p>`
|
|
1454
|
+
: ""}
|
|
1455
|
+
</div>
|
|
1395
1456
|
<div class="inputContainer">
|
|
1396
1457
|
<input
|
|
1397
1458
|
class=${inputClasses()}
|
|
@@ -1405,6 +1466,7 @@ export class TourScheduler extends LitElement {
|
|
|
1405
1466
|
}}
|
|
1406
1467
|
/>
|
|
1407
1468
|
</div>
|
|
1469
|
+
${this.renderBrokerToggle()}
|
|
1408
1470
|
<div class="section-label">Your client</div>
|
|
1409
1471
|
`;
|
|
1410
1472
|
}
|
|
@@ -1488,7 +1550,9 @@ export class TourScheduler extends LitElement {
|
|
|
1488
1550
|
? html`<p class="error-message">Invalid phone number</p>`
|
|
1489
1551
|
: ""}
|
|
1490
1552
|
</div>
|
|
1491
|
-
${this.brokerIntakeEnabled
|
|
1553
|
+
${this.brokerIntakeEnabled && !isReferral
|
|
1554
|
+
? this.renderBrokerToggle()
|
|
1555
|
+
: ""}
|
|
1492
1556
|
${this.layoutOptions.length > 0
|
|
1493
1557
|
? html` <me-select
|
|
1494
1558
|
id="layout"
|
|
@@ -243,6 +243,16 @@ export class EmailUsWindow extends LitElement {
|
|
|
243
243
|
brokerEmail = "";
|
|
244
244
|
@state()
|
|
245
245
|
brokerFirmName = "";
|
|
246
|
+
@state()
|
|
247
|
+
brokerPhone = "";
|
|
248
|
+
@state()
|
|
249
|
+
brokerPhoneE164: string | null = null;
|
|
250
|
+
@state()
|
|
251
|
+
brokerPhoneCountry: string | null = null;
|
|
252
|
+
@state()
|
|
253
|
+
brokerPhoneIsValid = false;
|
|
254
|
+
@state()
|
|
255
|
+
hasBrokerPhoneError = false;
|
|
246
256
|
|
|
247
257
|
@query("me-select#leadSource")
|
|
248
258
|
selectedLeadSource!: MESelect;
|
|
@@ -303,6 +313,15 @@ export class EmailUsWindow extends LitElement {
|
|
|
303
313
|
this.phoneCountry = e.detail.country;
|
|
304
314
|
};
|
|
305
315
|
|
|
316
|
+
onChangeBrokerPhone = (
|
|
317
|
+
e: CustomEvent<InternationalPhoneChangeDetail>
|
|
318
|
+
): void => {
|
|
319
|
+
this.brokerPhone = e.detail.formattedValue;
|
|
320
|
+
this.brokerPhoneE164 = e.detail.e164;
|
|
321
|
+
this.brokerPhoneCountry = e.detail.country;
|
|
322
|
+
this.brokerPhoneIsValid = e.detail.isValid;
|
|
323
|
+
};
|
|
324
|
+
|
|
306
325
|
onChangeMessage = (e: Event): void => {
|
|
307
326
|
if (!e.target) {
|
|
308
327
|
return;
|
|
@@ -313,36 +332,49 @@ export class EmailUsWindow extends LitElement {
|
|
|
313
332
|
|
|
314
333
|
/**
|
|
315
334
|
* A broker usually starts typing their own details before ticking the box, so ticking it moves
|
|
316
|
-
* the name and
|
|
335
|
+
* the name, email and phone into the broker's empty details and leaves the client's fields empty. Once
|
|
317
336
|
* the broker's details are filled, re-ticking leaves both sections alone: the main fields then
|
|
318
337
|
* hold the client. Unticking with the client's fields still empty moves the details back.
|
|
319
338
|
*/
|
|
320
339
|
onToggleBrokerReferral = (e: Event): void => {
|
|
321
340
|
const isBrokerReferral = (e.target as HTMLInputElement).checked;
|
|
322
341
|
const brokerIsEmpty =
|
|
323
|
-
!this.brokerFirstName &&
|
|
342
|
+
!this.brokerFirstName &&
|
|
343
|
+
!this.brokerLastName &&
|
|
344
|
+
!this.brokerEmail &&
|
|
345
|
+
!this.brokerPhone;
|
|
324
346
|
if (isBrokerReferral && brokerIsEmpty) {
|
|
325
347
|
this.brokerFirstName = this.firstName;
|
|
326
348
|
this.brokerLastName = this.lastName;
|
|
327
349
|
this.brokerEmail = this.email;
|
|
350
|
+
this.brokerPhone = this.phoneNumber;
|
|
351
|
+
this.brokerPhoneE164 = this.phoneNumberE164;
|
|
352
|
+
this.brokerPhoneCountry = this.phoneCountry;
|
|
353
|
+
this.brokerPhoneIsValid = !!this.phoneNumberE164;
|
|
328
354
|
this.firstName = "";
|
|
329
355
|
this.lastName = "";
|
|
330
356
|
this.email = "";
|
|
331
|
-
// There's no broker phone field: what was typed is the broker's, not the client's.
|
|
332
357
|
this.phoneNumber = "";
|
|
333
358
|
this.phoneNumberE164 = null;
|
|
334
359
|
} else if (
|
|
335
360
|
!isBrokerReferral &&
|
|
336
361
|
!this.firstName &&
|
|
337
362
|
!this.lastName &&
|
|
338
|
-
!this.email
|
|
363
|
+
!this.email &&
|
|
364
|
+
!this.phoneNumber
|
|
339
365
|
) {
|
|
340
366
|
this.firstName = this.brokerFirstName;
|
|
341
367
|
this.lastName = this.brokerLastName;
|
|
342
368
|
this.email = this.brokerEmail;
|
|
369
|
+
this.phoneNumber = this.brokerPhone;
|
|
370
|
+
this.phoneNumberE164 = this.brokerPhoneE164;
|
|
371
|
+
this.phoneCountry = this.brokerPhoneCountry;
|
|
343
372
|
this.brokerFirstName = "";
|
|
344
373
|
this.brokerLastName = "";
|
|
345
374
|
this.brokerEmail = "";
|
|
375
|
+
this.brokerPhone = "";
|
|
376
|
+
this.brokerPhoneE164 = null;
|
|
377
|
+
this.brokerPhoneIsValid = false;
|
|
346
378
|
}
|
|
347
379
|
this.isBrokerReferral = isBrokerReferral;
|
|
348
380
|
this.hasNameError = false;
|
|
@@ -350,6 +382,7 @@ export class EmailUsWindow extends LitElement {
|
|
|
350
382
|
this.hasPhoneNumberError = false;
|
|
351
383
|
this.hasBrokerNameError = false;
|
|
352
384
|
this.brokerEmailError = null;
|
|
385
|
+
this.hasBrokerPhoneError = false;
|
|
353
386
|
this.windowHeight = this.baseHeight() + 30 * this.getNumErrors();
|
|
354
387
|
};
|
|
355
388
|
|
|
@@ -372,7 +405,7 @@ export class EmailUsWindow extends LitElement {
|
|
|
372
405
|
baseHeight = (): number =>
|
|
373
406
|
525 +
|
|
374
407
|
// The broker's rows, less the 79px the compact message box gives back.
|
|
375
|
-
(this.brokerIntakeEnabled && this.isBrokerReferral ?
|
|
408
|
+
(this.brokerIntakeEnabled && this.isBrokerReferral ? 222 - 79 : 0);
|
|
376
409
|
|
|
377
410
|
/** The broker's details to send, only when the submitter says they're referring a client. */
|
|
378
411
|
brokerReferral = (): BrokerReferral | null =>
|
|
@@ -381,7 +414,8 @@ export class EmailUsWindow extends LitElement {
|
|
|
381
414
|
this.brokerFirstName,
|
|
382
415
|
this.brokerLastName,
|
|
383
416
|
this.brokerEmail,
|
|
384
|
-
this.brokerFirmName
|
|
417
|
+
this.brokerFirmName,
|
|
418
|
+
this.brokerPhoneE164 ?? ""
|
|
385
419
|
)
|
|
386
420
|
: null;
|
|
387
421
|
|
|
@@ -393,10 +427,13 @@ export class EmailUsWindow extends LitElement {
|
|
|
393
427
|
this.hasSubmissionError = false;
|
|
394
428
|
this.hasBrokerNameError = false;
|
|
395
429
|
this.brokerEmailError = null;
|
|
430
|
+
this.hasBrokerPhoneError = false;
|
|
396
431
|
const broker = this.brokerReferral();
|
|
397
432
|
if (broker) {
|
|
398
433
|
this.hasBrokerNameError = brokerNameIsMissing(broker);
|
|
399
434
|
this.brokerEmailError = brokerEmailError(broker, this.email);
|
|
435
|
+
// The broker's phone is optional, but one that's been typed has to be valid.
|
|
436
|
+
this.hasBrokerPhoneError = !!this.brokerPhone && !this.brokerPhoneIsValid;
|
|
400
437
|
}
|
|
401
438
|
if (!this.firstName || !this.lastName) {
|
|
402
439
|
this.hasNameError = true;
|
|
@@ -421,7 +458,8 @@ export class EmailUsWindow extends LitElement {
|
|
|
421
458
|
this.hasPhoneNumberError ||
|
|
422
459
|
this.hasMessageError ||
|
|
423
460
|
this.hasBrokerNameError ||
|
|
424
|
-
this.brokerEmailError
|
|
461
|
+
this.brokerEmailError ||
|
|
462
|
+
this.hasBrokerPhoneError
|
|
425
463
|
) {
|
|
426
464
|
return;
|
|
427
465
|
}
|
|
@@ -496,12 +534,13 @@ export class EmailUsWindow extends LitElement {
|
|
|
496
534
|
this.hasSubmissionError,
|
|
497
535
|
this.hasBrokerNameError,
|
|
498
536
|
!!this.brokerEmailError,
|
|
537
|
+
this.hasBrokerPhoneError,
|
|
499
538
|
].filter((v) => v).length;
|
|
500
539
|
};
|
|
501
540
|
|
|
502
541
|
renderBrokerToggle = (): TemplateResult => html`
|
|
503
542
|
<label class="email-us__broker-toggle">
|
|
504
|
-
<span>Are you
|
|
543
|
+
<span>Are you a broker or locator?</span>
|
|
505
544
|
<input
|
|
506
545
|
type="checkbox"
|
|
507
546
|
role="switch"
|
|
@@ -563,6 +602,19 @@ export class EmailUsWindow extends LitElement {
|
|
|
563
602
|
</div>`
|
|
564
603
|
: ""}
|
|
565
604
|
<div class="email-us__vertical-spacer"></div>
|
|
605
|
+
<international-phone-input
|
|
606
|
+
placeholder="Your phone (optional)"
|
|
607
|
+
.country=${this.brokerPhoneCountry ?? this.country}
|
|
608
|
+
.value=${this.brokerPhone}
|
|
609
|
+
.invalid=${this.hasBrokerPhoneError}
|
|
610
|
+
@phone-change=${this.onChangeBrokerPhone}
|
|
611
|
+
></international-phone-input>
|
|
612
|
+
${this.hasBrokerPhoneError
|
|
613
|
+
? html`<div class="email-us__error-text">
|
|
614
|
+
Enter a valid phone number
|
|
615
|
+
</div>`
|
|
616
|
+
: ""}
|
|
617
|
+
<div class="email-us__vertical-spacer"></div>
|
|
566
618
|
<input
|
|
567
619
|
type="text"
|
|
568
620
|
class=${inputClasses("email-us__contact-input")}
|
|
@@ -572,6 +624,8 @@ export class EmailUsWindow extends LitElement {
|
|
|
572
624
|
@input=${this.onChangeBrokerField("brokerFirmName")}
|
|
573
625
|
/>
|
|
574
626
|
<div class="email-us__vertical-spacer"></div>
|
|
627
|
+
${this.renderBrokerToggle()}
|
|
628
|
+
<div class="email-us__vertical-spacer"></div>
|
|
575
629
|
<div class="email-us__section-label">Your client</div>
|
|
576
630
|
`
|
|
577
631
|
: ""}
|
|
@@ -706,7 +760,7 @@ export class EmailUsWindow extends LitElement {
|
|
|
706
760
|
`
|
|
707
761
|
: ""}
|
|
708
762
|
<div class="email-us__vertical-spacer"></div>
|
|
709
|
-
${this.brokerIntakeEnabled
|
|
763
|
+
${this.brokerIntakeEnabled && !this.isBrokerReferral
|
|
710
764
|
? html`${this.renderBrokerToggle()}
|
|
711
765
|
<div class="email-us__vertical-spacer"></div>`
|
|
712
766
|
: ""}
|
|
@@ -130,6 +130,9 @@ export class InternationalPhoneInput extends LitElement {
|
|
|
130
130
|
@property({ type: Boolean })
|
|
131
131
|
invalid = false;
|
|
132
132
|
|
|
133
|
+
@property({ type: String })
|
|
134
|
+
placeholder = "Phone";
|
|
135
|
+
|
|
133
136
|
@state()
|
|
134
137
|
private selectedCountry = "US";
|
|
135
138
|
|
|
@@ -287,7 +290,7 @@ export class InternationalPhoneInput extends LitElement {
|
|
|
287
290
|
class="webchat-input"
|
|
288
291
|
type="tel"
|
|
289
292
|
inputmode="tel"
|
|
290
|
-
placeholder
|
|
293
|
+
placeholder=${this.placeholder}
|
|
291
294
|
name="phone"
|
|
292
295
|
autocomplete="tel-national"
|
|
293
296
|
maxlength="30"
|
|
@@ -6,6 +6,8 @@ export type BrokerReferral = {
|
|
|
6
6
|
lastName: string;
|
|
7
7
|
email: string;
|
|
8
8
|
firmName: string;
|
|
9
|
+
/** E.164, or "" when the form has no broker phone or it was left empty. */
|
|
10
|
+
phone: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
/** The broker's typed details, trimmed, as a referral. */
|
|
@@ -13,12 +15,14 @@ export const toBrokerReferral = (
|
|
|
13
15
|
firstName: string,
|
|
14
16
|
lastName: string,
|
|
15
17
|
email: string,
|
|
16
|
-
firmName: string
|
|
18
|
+
firmName: string,
|
|
19
|
+
phone = ""
|
|
17
20
|
): BrokerReferral => ({
|
|
18
21
|
firstName: firstName.trim(),
|
|
19
22
|
lastName: lastName.trim(),
|
|
20
23
|
email: email.trim(),
|
|
21
24
|
firmName: firmName.trim(),
|
|
25
|
+
phone,
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
export const brokerNameIsMissing = (broker: BrokerReferral): boolean =>
|
|
@@ -51,5 +55,6 @@ export const brokerRequestFields = (
|
|
|
51
55
|
broker_last_name: broker.lastName,
|
|
52
56
|
broker_email: broker.email,
|
|
53
57
|
broker_firm_name: broker.firmName || null,
|
|
58
|
+
...(broker.phone ? { broker_phone: broker.phone } : {}),
|
|
54
59
|
}
|
|
55
60
|
: {};
|
|
@@ -144,6 +144,11 @@ export class MEChat extends LitElement {
|
|
|
144
144
|
*/
|
|
145
145
|
@property({ type: String })
|
|
146
146
|
private declaredBuildingSlug = "";
|
|
147
|
+
/**
|
|
148
|
+
* Org-level webchat on a host that knows the page's building by id (Elise
|
|
149
|
+
* Studio), set with `setDeclaredBuildingId`. Undefined until the host sets it.
|
|
150
|
+
*/
|
|
151
|
+
private hostDeclaredBuildingId: number | null | undefined = undefined;
|
|
147
152
|
/**
|
|
148
153
|
* Org-level webchat (`centralConvo` started without a building). Stays true
|
|
149
154
|
* after the widget adopts the org's triage general building from
|
|
@@ -700,10 +705,16 @@ export class MEChat extends LitElement {
|
|
|
700
705
|
if (this.centralConvo) {
|
|
701
706
|
const myPubnub = this.myPubnub;
|
|
702
707
|
myPubnub.centralConvo = true;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
708
|
+
if (this.hostDeclaredBuildingId !== undefined) {
|
|
709
|
+
myPubnub.declaredBuildingId = this.hostDeclaredBuildingId;
|
|
710
|
+
} else {
|
|
711
|
+
// Not awaited: only needed once the visitor sends a message.
|
|
712
|
+
void this.fetchDeclaredBuildingId().then((id) => {
|
|
713
|
+
if (this.hostDeclaredBuildingId === undefined) {
|
|
714
|
+
myPubnub.declaredBuildingId = id;
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
707
718
|
} else {
|
|
708
719
|
this.myPubnub.declaredBuildingId = this.buildingWebchatView.id;
|
|
709
720
|
}
|
|
@@ -814,6 +825,16 @@ export class MEChat extends LitElement {
|
|
|
814
825
|
};
|
|
815
826
|
};
|
|
816
827
|
|
|
828
|
+
/**
|
|
829
|
+
* Org-level webchat: the page's building, sent with later visitor messages as
|
|
830
|
+
* `declaredBuildingId`. For hosts that move between pages without restarting
|
|
831
|
+
* the widget; it doesn't change config or the conversation.
|
|
832
|
+
*/
|
|
833
|
+
setDeclaredBuildingId(id: number | null): void {
|
|
834
|
+
this.hostDeclaredBuildingId = id;
|
|
835
|
+
if (this.myPubnub) this.myPubnub.declaredBuildingId = id;
|
|
836
|
+
}
|
|
837
|
+
|
|
817
838
|
/** The numeric id of the snippet's building in org-level webchat, if it names one. */
|
|
818
839
|
private fetchDeclaredBuildingId = async (): Promise<number | null> => {
|
|
819
840
|
if (!this.declaredBuildingSlug || !this.orgSlug) return null;
|
|
@@ -85,6 +85,43 @@ it("sends a broker referral with the client as the prospect and the broker field
|
|
|
85
85
|
expect(JSON.stringify(event)).not.to.contain("bea@realty.example.com");
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
it("sends the broker's phone on a referral email when they gave one", async () => {
|
|
89
|
+
const requests = captureRequests();
|
|
90
|
+
const emailUs = createForm({
|
|
91
|
+
brokerIntakeEnabled: true,
|
|
92
|
+
isBrokerReferral: true,
|
|
93
|
+
...brokerFields,
|
|
94
|
+
brokerPhone: "(312) 555-0199",
|
|
95
|
+
brokerPhoneE164: "+13125550199",
|
|
96
|
+
brokerPhoneIsValid: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const submission = emailUs.onClick();
|
|
100
|
+
|
|
101
|
+
expect(requests).to.have.length(1);
|
|
102
|
+
expect(JSON.parse(requests[0].requestBody ?? "{}")).to.include({
|
|
103
|
+
broker_phone: "+13125550199",
|
|
104
|
+
});
|
|
105
|
+
requests[0].respond(200, { "Content-Type": "application/json" }, "{}");
|
|
106
|
+
await submission;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("won't send a referral email with an invalid broker phone", async () => {
|
|
110
|
+
const requests = captureRequests();
|
|
111
|
+
const emailUs = createForm({
|
|
112
|
+
brokerIntakeEnabled: true,
|
|
113
|
+
isBrokerReferral: true,
|
|
114
|
+
...brokerFields,
|
|
115
|
+
brokerPhone: "555",
|
|
116
|
+
brokerPhoneE164: null,
|
|
117
|
+
brokerPhoneIsValid: false,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await emailUs.onClick();
|
|
121
|
+
|
|
122
|
+
expect(requests).to.have.length(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
88
125
|
it("sends no broker fields when broker intake is off, whatever the form holds", async () => {
|
|
89
126
|
const requests = captureRequests();
|
|
90
127
|
const emailUs = createForm({
|
|
@@ -224,9 +261,15 @@ it("moves what a broker typed into their own details when they tick the referral
|
|
|
224
261
|
fill("Email", "bea@realty.example.com");
|
|
225
262
|
await emailUs.updateComplete;
|
|
226
263
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
264
|
+
// The switch moves when ticked (under Brokerage), so find it again for each click.
|
|
265
|
+
const checkbox = {
|
|
266
|
+
click: () =>
|
|
267
|
+
(
|
|
268
|
+
emailUs.shadowRoot?.querySelector(
|
|
269
|
+
"#broker-referral-toggle"
|
|
270
|
+
) as HTMLInputElement
|
|
271
|
+
).click(),
|
|
272
|
+
};
|
|
230
273
|
checkbox.click();
|
|
231
274
|
await emailUs.updateComplete;
|
|
232
275
|
|
|
@@ -257,9 +300,15 @@ it("keeps a filled client when the referral box is unticked and ticked again", a
|
|
|
257
300
|
Object.assign(emailUs, { brokerIntakeEnabled: true, buildingId: 445038 });
|
|
258
301
|
document.body.appendChild(emailUs);
|
|
259
302
|
await emailUs.updateComplete;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
303
|
+
// The switch moves when ticked (under Brokerage), so find it again for each click.
|
|
304
|
+
const checkbox = {
|
|
305
|
+
click: () =>
|
|
306
|
+
(
|
|
307
|
+
emailUs.shadowRoot?.querySelector(
|
|
308
|
+
"#broker-referral-toggle"
|
|
309
|
+
) as HTMLInputElement
|
|
310
|
+
).click(),
|
|
311
|
+
};
|
|
263
312
|
checkbox.click();
|
|
264
313
|
await emailUs.updateComplete;
|
|
265
314
|
Object.assign(emailUs, {
|
|
@@ -357,6 +406,44 @@ it("books a broker's tour with the client as the prospect and the broker fields"
|
|
|
357
406
|
expect(JSON.stringify(event)).not.to.contain("bea@realty.example.com");
|
|
358
407
|
});
|
|
359
408
|
|
|
409
|
+
it("sends the broker's phone on a referral tour when they gave one", async () => {
|
|
410
|
+
const requests = captureRequests();
|
|
411
|
+
const tour = createTour({
|
|
412
|
+
brokerIntakeEnabled: true,
|
|
413
|
+
isBrokerReferral: true,
|
|
414
|
+
...brokerFields,
|
|
415
|
+
brokerPhone: "(312) 555-0199",
|
|
416
|
+
brokerPhoneE164: "+13125550199",
|
|
417
|
+
brokerPhoneIsValid: true,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const submission = tour.submit();
|
|
421
|
+
|
|
422
|
+
expect(requests).to.have.length(1);
|
|
423
|
+
expect(JSON.parse(requests[0].requestBody ?? "{}")).to.include({
|
|
424
|
+
phone_number: "+13125550100",
|
|
425
|
+
broker_phone: "+13125550199",
|
|
426
|
+
});
|
|
427
|
+
requests[0].respond(200, { "Content-Type": "application/json" }, "{}");
|
|
428
|
+
await submission;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("won't book a referral tour with an invalid broker phone", async () => {
|
|
432
|
+
const requests = captureRequests();
|
|
433
|
+
const tour = createTour({
|
|
434
|
+
brokerIntakeEnabled: true,
|
|
435
|
+
isBrokerReferral: true,
|
|
436
|
+
...brokerFields,
|
|
437
|
+
brokerPhone: "555",
|
|
438
|
+
brokerPhoneE164: null,
|
|
439
|
+
brokerPhoneIsValid: false,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await tour.submit();
|
|
443
|
+
|
|
444
|
+
expect(requests).to.have.length(0);
|
|
445
|
+
});
|
|
446
|
+
|
|
360
447
|
it("books a tour with no broker fields when broker intake is off, whatever the form holds", async () => {
|
|
361
448
|
const requests = captureRequests();
|
|
362
449
|
const tour = createTour({
|
package/src/main/MEChat.ts
CHANGED
|
@@ -40,7 +40,8 @@ export type StudioWebchatMode = "host_managed_context";
|
|
|
40
40
|
export type StudioWebchatSessionScopeType =
|
|
41
41
|
| "building"
|
|
42
42
|
| "building_group"
|
|
43
|
-
| "site"
|
|
43
|
+
| "site"
|
|
44
|
+
| "org";
|
|
44
45
|
|
|
45
46
|
export interface StudioWebchatSessionScope {
|
|
46
47
|
type: StudioWebchatSessionScopeType;
|
|
@@ -60,6 +61,11 @@ export interface StudioWebchatContext {
|
|
|
60
61
|
building?: string | null;
|
|
61
62
|
buildingGroupId?: string | number | null;
|
|
62
63
|
sessionScope?: StudioWebchatSessionScope;
|
|
64
|
+
/**
|
|
65
|
+
* Org scope only: the page's building, sent with visitor messages as
|
|
66
|
+
* `declaredBuildingId`. Null on pages with no single building.
|
|
67
|
+
*/
|
|
68
|
+
declaredBuildingId?: number | null;
|
|
63
69
|
path?: string;
|
|
64
70
|
pageId?: string;
|
|
65
71
|
}
|
|
@@ -323,6 +329,11 @@ export default class MEChat {
|
|
|
323
329
|
return;
|
|
324
330
|
}
|
|
325
331
|
|
|
332
|
+
if (context.sessionScope?.type === "org") {
|
|
333
|
+
this.updateStudioOrgContext(context, opts, updateToken);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
326
337
|
const orgSlug = coerceStudioContextString(context.organization);
|
|
327
338
|
const buildingSlug = coerceStudioContextString(context.building);
|
|
328
339
|
|
|
@@ -368,6 +379,64 @@ export default class MEChat {
|
|
|
368
379
|
});
|
|
369
380
|
}
|
|
370
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Org-level webchat on a Studio site: one org-anchored widget for the whole
|
|
384
|
+
* site, started as the org-level pixel starts it. Route changes only move the
|
|
385
|
+
* page's declared building, so the conversation carries across pages.
|
|
386
|
+
*/
|
|
387
|
+
static updateStudioOrgContext(
|
|
388
|
+
context: StudioWebchatContext,
|
|
389
|
+
opts: Options,
|
|
390
|
+
updateToken: number
|
|
391
|
+
): void {
|
|
392
|
+
const orgSlug = coerceStudioContextString(context.organization);
|
|
393
|
+
if (!orgSlug) {
|
|
394
|
+
this.clearStudioContext({
|
|
395
|
+
source: "elise_studio",
|
|
396
|
+
reason: "missing_context",
|
|
397
|
+
preserveCache: true,
|
|
398
|
+
});
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const normalizedOrgSlug = normalizeOrgSlug(orgSlug);
|
|
403
|
+
const sessionScopeKey = context.sessionScope?.key || `org:${orgSlug}`;
|
|
404
|
+
const declaredBuildingId = coerceDeclaredBuildingId(
|
|
405
|
+
context.declaredBuildingId
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
if (
|
|
409
|
+
this.meChat &&
|
|
410
|
+
this.activeStudioOrgSlug === normalizedOrgSlug &&
|
|
411
|
+
!this.activeStudioBuildingSlug &&
|
|
412
|
+
this.activeStudioSessionScopeKey === sessionScopeKey
|
|
413
|
+
) {
|
|
414
|
+
this.meChat.setDeclaredBuildingId(declaredBuildingId);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
void this.removeWidget().then(() => {
|
|
419
|
+
if (updateToken !== this.studioUpdateToken) return;
|
|
420
|
+
|
|
421
|
+
this.activeStudioOrgSlug = normalizedOrgSlug;
|
|
422
|
+
this.activeStudioBuildingSlug = "";
|
|
423
|
+
this.activeStudioSessionScopeKey = sessionScopeKey;
|
|
424
|
+
this.orgSlug = normalizedOrgSlug;
|
|
425
|
+
|
|
426
|
+
this.start(
|
|
427
|
+
{
|
|
428
|
+
...opts,
|
|
429
|
+
studio: undefined,
|
|
430
|
+
organization: orgSlug,
|
|
431
|
+
building: undefined,
|
|
432
|
+
centralConvo: true,
|
|
433
|
+
},
|
|
434
|
+
false
|
|
435
|
+
);
|
|
436
|
+
this.meChat?.setDeclaredBuildingId(declaredBuildingId);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
371
440
|
static clearStudioContext(_context?: StudioWebchatClearContext): void {
|
|
372
441
|
void _context;
|
|
373
442
|
this.studioUpdateToken += 1;
|
|
@@ -536,6 +605,11 @@ function normalizeOrgSlug(orgSlug: string): string {
|
|
|
536
605
|
: orgSlug;
|
|
537
606
|
}
|
|
538
607
|
|
|
608
|
+
function coerceDeclaredBuildingId(value: unknown): number | null {
|
|
609
|
+
const id = typeof value === "string" ? Number(value) : value;
|
|
610
|
+
return typeof id === "number" && Number.isInteger(id) && id > 0 ? id : null;
|
|
611
|
+
}
|
|
612
|
+
|
|
539
613
|
function coerceStudioSessionScopeKey(
|
|
540
614
|
sessionScope: StudioWebchatSessionScope | undefined,
|
|
541
615
|
buildingSlug: string
|