@meetelise/chat 1.55.3 → 1.57.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/src/MyPubnub.ts CHANGED
@@ -11,6 +11,11 @@ import { shouldCreateAsDevState } from "./shouldCreateAsDevState";
11
11
  import { WidgetType } from "./main/MEChat";
12
12
  import { BuildingWebchatView } from "./fetchBuildingWebchatView";
13
13
  import { LeadSourceMultitouchClient } from "./WebComponent/LeadSourceMultitouchClient";
14
+ import {
15
+ getWebchatPageContext,
16
+ parseWebchatNavigation,
17
+ WebchatNavigation,
18
+ } from "./webchatNavigation";
14
19
 
15
20
  interface TokenResponse {
16
21
  auth: {
@@ -29,6 +34,7 @@ enum MessageType {
29
34
  noReply = "no-reply",
30
35
  text = "text",
31
36
  media = "media",
37
+ navigation = "navigation",
32
38
  }
33
39
 
34
40
  interface RawPubnubMessage {
@@ -43,6 +49,7 @@ interface RawPubnubMessage {
43
49
  order?: number;
44
50
  stream_id?: string;
45
51
  media?: ChatMediaFile[];
52
+ navigation?: unknown;
46
53
  };
47
54
  publisher: string;
48
55
  subscription: string;
@@ -73,7 +80,18 @@ export interface SimpleMediaChatMessage {
73
80
  type: SimpleMessageTypes.media;
74
81
  }
75
82
 
76
- export type SimpleChatMessage = SimpleTextChatMessage | SimpleMediaChatMessage;
83
+ /** A "Take me to {label}" button the agent sends after a reply's text. */
84
+ export interface SimpleNavigationChatMessage {
85
+ timestamp: number;
86
+ navigation: WebchatNavigation;
87
+ isLeadMessage: boolean;
88
+ type: SimpleMessageTypes.navigation;
89
+ }
90
+
91
+ export type SimpleChatMessage =
92
+ | SimpleTextChatMessage
93
+ | SimpleMediaChatMessage
94
+ | SimpleNavigationChatMessage;
77
95
 
78
96
  export const isSimpleTextChatMessage = (
79
97
  message: SimpleChatMessage
@@ -87,9 +105,16 @@ export const isSimpleMediaChatMessage = (
87
105
  return message.type === SimpleMessageTypes.media;
88
106
  };
89
107
 
108
+ export const isSimpleNavigationChatMessage = (
109
+ message: SimpleChatMessage
110
+ ): message is SimpleNavigationChatMessage => {
111
+ return message.type === SimpleMessageTypes.navigation;
112
+ };
113
+
90
114
  export enum SimpleMessageTypes {
91
115
  text = "text",
92
116
  media = "media",
117
+ navigation = "navigation",
93
118
  }
94
119
 
95
120
  class MyPubnub {
@@ -111,6 +136,22 @@ class MyPubnub {
111
136
 
112
137
  private widgetType: WidgetType = WidgetType.Default;
113
138
 
139
+ /**
140
+ * Org-level webchat (`centralConvo: true`): the session is org-anchored, so it
141
+ * must come from /webchat/init and never from the per-lead v1 token fallback.
142
+ */
143
+ centralConvo = false;
144
+
145
+ /** The numeric id of the building the snippet names, sent as `declaredBuildingId`. */
146
+ declaredBuildingId: number | null = null;
147
+
148
+ /**
149
+ * The localStorage key the conversation (lead id) is saved under. It is the
150
+ * building slug, except in org-level webchat, where the conversation stays
151
+ * org-level even after the widget adopts the org's triage general building.
152
+ */
153
+ chatStorageSlug: string;
154
+
114
155
  pubnub: Pubnub | null = null;
115
156
  leadUserId = "";
116
157
  channel = "";
@@ -193,6 +234,7 @@ class MyPubnub {
193
234
  widgetType: WidgetType = WidgetType.Default
194
235
  ) {
195
236
  this.buildingSlug = buildingSlug;
237
+ this.chatStorageSlug = buildingSlug;
196
238
  this.buildingWebchatView = buildingDetails;
197
239
  this.orgSlug = orgSlug;
198
240
  this.leadSource = leadSource;
@@ -212,12 +254,20 @@ class MyPubnub {
212
254
  lead_id: string;
213
255
  channel: string;
214
256
  expires_in_seconds: number;
257
+ /**
258
+ * Org-level webchat only: the org's triage general building, which the
259
+ * widget then runs as.
260
+ */
261
+ building_slug?: string;
215
262
  } | null> {
216
263
  try {
217
- const payload: Record<string, string> = {
264
+ const payload: Record<string, string | boolean> = {
218
265
  building_slug: buildingSlug,
219
266
  org_slug: orgSlug,
220
267
  };
268
+ if (this.centralConvo) {
269
+ payload.central_convo = true;
270
+ }
221
271
  if (existingLeadId) {
222
272
  payload.lead_id = existingLeadId;
223
273
  }
@@ -244,6 +294,18 @@ class MyPubnub {
244
294
  }
245
295
  }
246
296
 
297
+ /**
298
+ * Org-level webchat: run as the building /webchat/init resolved (the org's
299
+ * triage general building), so messages carry its id, slug and AI user.
300
+ */
301
+ adoptBuilding(
302
+ buildingSlug: string,
303
+ buildingDetails: BuildingWebchatView
304
+ ): void {
305
+ this.buildingSlug = buildingSlug;
306
+ this.buildingWebchatView = buildingDetails;
307
+ }
308
+
247
309
  private checkAndHandleDelayStreamingMessages = ({
248
310
  delayToFetchHistoryInMs = 10000,
249
311
  }: { delayToFetchHistoryInMs?: number } = {}): void => {
@@ -407,6 +469,10 @@ class MyPubnub {
407
469
  return response.data;
408
470
  }
409
471
 
472
+ // Org-level sessions only exist through /webchat/init, so there is no
473
+ // v1 session to fall back to.
474
+ if (this.centralConvo) return null;
475
+
410
476
  // v1 fallback for returning users with existing localStorage sessions
411
477
  const response = await axios.get(
412
478
  `${
@@ -665,7 +731,7 @@ class MyPubnub {
665
731
  if (!this.pubnub) {
666
732
  // ONLY create/gets a chat session if user actually wants to chat
667
733
  const chatStorageKey = createChatStorageKey(
668
- this.buildingSlug,
734
+ this.chatStorageSlug,
669
735
  true,
670
736
  this.leadUserId
671
737
  );
@@ -728,6 +794,8 @@ class MyPubnub {
728
794
  leadSource: this.leadSource,
729
795
  widgetType: this.widgetType,
730
796
  isDevState: shouldCreateAsDevState(location.href),
797
+ // Read by the backend only for orgs in the org-level webchat rollout.
798
+ ...getWebchatPageContext(this.declaredBuildingId),
731
799
  lead_sources_with_timestamps: (
732
800
  this.leadSourceMultitouchClient?.getSafeLeadSourceTouchpointsWithDefault(
733
801
  { includedUserSelectedLeadSource: null }
@@ -815,14 +883,28 @@ class MyPubnub {
815
883
  const uniqueMessages = this.removeDuplicatedRawPubnubMessages(messages);
816
884
 
817
885
  uniqueMessages.forEach((message: RawPubnubMessage) => {
818
- // Translate media messages
819
- if (message.message.messageType === "media") {
886
+ if (message.message.messageType === MessageType.navigation) {
887
+ // A "Take me there" button; dropped if its url or label is unusable.
888
+ const navigation = parseWebchatNavigation(message.message.navigation);
889
+ if (navigation) {
890
+ parsedMessages.push({
891
+ timestamp: message.timetoken,
892
+ navigation,
893
+ isLeadMessage: false,
894
+ type: SimpleMessageTypes.navigation,
895
+ });
896
+ }
897
+ } else if (message.message.messageType === "media") {
898
+ // Translate media messages
820
899
  parsedMessages.push({
821
900
  timestamp: message.timetoken,
822
901
  media: message.message.media ?? [],
823
902
  isLeadMessage: this.isLeadMessage(message),
824
903
  type: SimpleMessageTypes.media,
825
904
  });
905
+ } else if (typeof message.message.text !== "string") {
906
+ // A message type this widget doesn't know, with no text to show.
907
+ return;
826
908
  } else if (
827
909
  // Translate non-streaming messages
828
910
  !message.message.stream_id ||
@@ -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 email into the broker's empty details and leaves the client's fields empty. Once
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 && !this.brokerLastName && !this.brokerEmail;
699
+ !this.brokerFirstName &&
700
+ !this.brokerLastName &&
701
+ !this.brokerEmail &&
702
+ !this.brokerPhone;
681
703
  const clientIsEmpty =
682
- !this.firstNameInputValue && !this.lastNameInputValue && !this.email;
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) && !brokerEmailError(broker, this.email))
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 an agent or locator?</span>
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 ? this.renderBrokerToggle() : ""}
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 email into the broker's empty details and leaves the client's fields empty. Once
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 && !this.brokerLastName && !this.brokerEmail;
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 ? 170 - 79 : 0);
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 an agent or locator?</span>
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="Phone"
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
  : {};