@meetelise/chat 1.21.0 → 1.21.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 (76) hide show
  1. package/.github/pull_request_template.md +61 -0
  2. package/.idea/codeStyles/Project.xml +57 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/.idea/workspace.xml +67 -0
  7. package/README.md +29 -14
  8. package/declarations.d.ts +12 -0
  9. package/package.json +5 -1
  10. package/public/demo/index.html +62 -4
  11. package/public/demo/secret.html +63 -0
  12. package/public/dist/index.js +3184 -1105
  13. package/public/dist/index.js.LICENSE.txt +19 -9
  14. package/public/index.html +6 -4
  15. package/src/MEChat.ts +207 -52
  16. package/src/MyPubnub.ts +657 -0
  17. package/src/WebComponent/LeadSourceClient.ts +300 -0
  18. package/src/WebComponent/Scheduler/date-picker.ts +1 -1
  19. package/src/WebComponent/Scheduler/time-picker.ts +86 -76
  20. package/src/WebComponent/Scheduler/tour-scheduler.ts +694 -764
  21. package/src/WebComponent/Scheduler/tour-type-option.ts +17 -3
  22. package/src/WebComponent/Scheduler/tourSchedulerStyles.ts +418 -0
  23. package/src/WebComponent/actions/InputStyles.ts +32 -10
  24. package/src/WebComponent/actions/action-confirm-button.ts +16 -11
  25. package/src/WebComponent/actions/call-us-window.ts +341 -58
  26. package/src/WebComponent/actions/details-window.ts +30 -16
  27. package/src/WebComponent/actions/email-us-window.ts +89 -58
  28. package/src/WebComponent/actions/formatPhoneNumber.ts +15 -1
  29. package/src/WebComponent/actions/minimize-expand-button.ts +92 -0
  30. package/src/WebComponent/health-chat.ts +267 -0
  31. package/src/WebComponent/healthcare/healthcare-launcher-styles.ts +34 -0
  32. package/src/WebComponent/healthcare/healthcare-launcher.ts +100 -0
  33. package/src/WebComponent/healthchat-styles.ts +119 -0
  34. package/src/WebComponent/index.ts +1 -1
  35. package/src/WebComponent/launcher/Launcher.ts +919 -0
  36. package/src/WebComponent/{launcherStyles.ts → launcher/launcherStyles.ts} +172 -29
  37. package/src/WebComponent/launcher/mobile-launcher.ts +127 -0
  38. package/src/WebComponent/launcher/typeEmojiStyles.ts +161 -0
  39. package/src/WebComponent/launcher/typeMiniStyles.ts +60 -0
  40. package/src/WebComponent/launcher/typeMobileStyles.ts +50 -0
  41. package/src/WebComponent/leasing-chat-styles.ts +114 -0
  42. package/src/WebComponent/me-chat.ts +964 -351
  43. package/src/WebComponent/me-select.ts +48 -21
  44. package/src/WebComponent/mini-loader.ts +28 -0
  45. package/src/WebComponent/pubnub-chat-styles.ts +192 -0
  46. package/src/WebComponent/pubnub-chat.ts +707 -0
  47. package/src/WebComponent/pubnub-media.ts +208 -0
  48. package/src/WebComponent/pubnub-message-styles.ts +54 -0
  49. package/src/WebComponent/pubnub-message.ts +421 -0
  50. package/src/analytics.ts +114 -14
  51. package/src/assetUrls.ts +2 -0
  52. package/src/disclaimers.ts +56 -0
  53. package/src/fetchBuildingABTestType.ts +4 -0
  54. package/src/fetchBuildingInfo.ts +25 -17
  55. package/src/fetchFeatureFlag.ts +147 -0
  56. package/src/fetchLeadSources.ts +67 -1
  57. package/src/fetchPhoneNumberFromSource.ts +31 -0
  58. package/src/fetchWebchatPreferences.ts +55 -0
  59. package/src/getAvailabilities.ts +7 -3
  60. package/src/getBuildingPhoneNumber.ts +26 -0
  61. package/src/getShouldAllowScheduling.ts +16 -0
  62. package/src/getTimezoneString.ts +39 -0
  63. package/src/gtm.ts +17 -0
  64. package/src/handleChatId.ts +101 -0
  65. package/src/insertDNIIntoWebsite.ts +136 -0
  66. package/src/insertLeadSourceIntoSchedulerLinks.ts +50 -0
  67. package/src/postLeadSources.ts +39 -35
  68. package/src/svgIcons.ts +62 -53
  69. package/src/themes.ts +47 -121
  70. package/src/utils.ts +88 -1
  71. package/src/WebComponent/Launcher.ts +0 -559
  72. package/src/WebComponent/actions/text-us-window.ts +0 -279
  73. package/src/chatID.ts +0 -64
  74. package/src/createConversation.ts +0 -57
  75. package/src/fetchCurrentParsedLeadSource.ts +0 -24
  76. package/src/getRegisteredPhoneNumbers.ts +0 -56
package/src/analytics.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import axios from "axios";
2
+ import { getCookieValue } from "./utils";
3
+
1
4
  declare global {
2
5
  interface Window {
3
6
  RCTPCampaign?: { CampaignDetails: { Source: string } };
@@ -9,21 +12,29 @@ export interface CampaignSources {
9
12
  yardiCampaignSource: string | null;
10
13
  realpageCampaignSource: string | null;
11
14
  utmCampaignSource: string | null;
15
+ bozzutoAdSource: string | null;
12
16
  }
13
17
 
14
18
  /**
15
- * A connection to the MeetElise API to send analytics events.
19
+ * A connection to the MeetElise API to send analytics events
16
20
  */
17
21
  export default class Analytics {
18
22
  private org: string;
19
23
  private building: string;
20
24
  private featureFlags?: Record<string, boolean>;
21
25
  public chatId: string;
26
+ private incomingProcessedLeadSource: string | null;
22
27
 
23
- constructor(org: string, building: string, chatId: string) {
28
+ constructor(
29
+ org: string,
30
+ building: string,
31
+ chatId: string,
32
+ incomingProcessedLeadSource: string | null
33
+ ) {
24
34
  this.org = org;
25
35
  this.building = building;
26
36
  this.chatId = chatId;
37
+ this.incomingProcessedLeadSource = incomingProcessedLeadSource;
27
38
  this.featureFlags = {};
28
39
  }
29
40
 
@@ -57,6 +68,8 @@ export default class Analytics {
57
68
  referrer: document.referrer,
58
69
  featureFlags: this.featureFlags,
59
70
  campaignSources: getCampaignSources(),
71
+ incomingProcessedLeadSource: this.incomingProcessedLeadSource, // if we already know the lead source, send it along
72
+ webchatVersion: process.env.npm_package_version ?? "1.20.155",
60
73
  }),
61
74
  }
62
75
  );
@@ -64,17 +77,104 @@ export default class Analytics {
64
77
  }
65
78
 
66
79
  export const getCampaignSources = (): CampaignSources => {
67
- const queryParams = new URL(window.location.href).searchParams;
68
- const yardiCampaignSource =
69
- window.RCTPCampaign?.CampaignDetails?.Source ?? null;
70
- const entrataCampaignSource = queryParams.get("switch_cls[id]");
71
- const realpageCampaignSource = queryParams.get("ilm");
72
- const utmCampaignSource = queryParams.get("utm_source");
80
+ try {
81
+ const queryParams = new URL(window.location.href).searchParams;
82
+ const yardiCampaignSource =
83
+ window.RCTPCampaign?.CampaignDetails?.Source ?? null;
84
+ const entrataCampaignSource = queryParams.get("switch_cls[id]");
85
+ const realpageCampaignSource = queryParams.get("ilm");
86
+ const utmCampaignSource = queryParams.get("utm_source");
87
+ const bozzutoAdSource = getCookieValue("bozzuto_ad_source");
88
+
89
+ return {
90
+ entrataCampaignSource,
91
+ realpageCampaignSource,
92
+ utmCampaignSource,
93
+ yardiCampaignSource,
94
+ bozzutoAdSource,
95
+ };
96
+ } catch (e) {
97
+ /**
98
+ * Some clients have pages that overwrite the `URL` class and turn it into a string. This is a workaround for that.
99
+ * Example site: https://ther3.com
100
+ */
101
+ // eslint-disable-next-line no-console
102
+ console.warn(`Couldnt get campaign sources from url, error: ${e}`);
103
+ const iframe = document.createElement("iframe");
104
+ iframe.style.display = "none";
105
+ document.body.appendChild(iframe);
106
+ if (!iframe.contentWindow) {
107
+ return {
108
+ entrataCampaignSource: null,
109
+ realpageCampaignSource: null,
110
+ utmCampaignSource: null,
111
+ yardiCampaignSource: null,
112
+ bozzutoAdSource: null,
113
+ };
114
+ }
115
+
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ const OriginalURL = (iframe.contentWindow as any).URL;
118
+ if (!OriginalURL) {
119
+ return {
120
+ entrataCampaignSource: null,
121
+ realpageCampaignSource: null,
122
+ utmCampaignSource: null,
123
+ yardiCampaignSource: null,
124
+ bozzutoAdSource: null,
125
+ };
126
+ }
127
+ const queryParams = new OriginalURL(window.location.href).searchParams;
128
+ const yardiCampaignSource =
129
+ window.RCTPCampaign?.CampaignDetails?.Source ?? null;
130
+ const entrataCampaignSource = queryParams.get("switch_cls[id]");
131
+ const realpageCampaignSource = queryParams.get("ilm");
132
+ const utmCampaignSource = queryParams.get("utm_source");
133
+ const bozzutoAdSource = getCookieValue("bozzuto_ad_source");
134
+
135
+ return {
136
+ entrataCampaignSource,
137
+ realpageCampaignSource,
138
+ utmCampaignSource,
139
+ yardiCampaignSource,
140
+ bozzutoAdSource,
141
+ };
142
+ }
143
+ };
144
+
145
+ export enum LogType {
146
+ error = "error",
147
+ info = "info",
148
+ warn = "warn",
149
+ }
73
150
 
74
- return {
75
- entrataCampaignSource,
76
- realpageCampaignSource,
77
- utmCampaignSource,
78
- yardiCampaignSource,
79
- };
151
+ export const sendLoggingEvent = async ({
152
+ logType,
153
+ buildingSlug,
154
+ orgSlug,
155
+ logTitle,
156
+ logData,
157
+ }: {
158
+ logType?: LogType;
159
+ buildingSlug?: string;
160
+ orgSlug?: string;
161
+ logTitle: string;
162
+ logData: Record<string, unknown> | null;
163
+ }): Promise<void> => {
164
+ const host = "https://app.meetelise.com";
165
+ await axios.post(
166
+ `${host}/platformApi/webchat/logging`,
167
+ {
168
+ ...logData,
169
+ },
170
+ {
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ "building-slug": buildingSlug ?? "",
174
+ "org-slug": orgSlug ?? "",
175
+ "log-type": logType ?? LogType.error,
176
+ "log-title": logTitle.toUpperCase().replace(/\s/g, "_"),
177
+ },
178
+ }
179
+ );
80
180
  };
package/src/assetUrls.ts CHANGED
@@ -2,3 +2,5 @@ export const glowBarMp4 =
2
2
  "https://eliseusercontent.meetelise.com/webchat/HorizontalBar-Shadow.mp4";
3
3
  export const glowBarWebm =
4
4
  "https://eliseusercontent.meetelise.com/webchat/HorizontalBar-Shadow.webm";
5
+ export const glowBackgroundMp4 =
6
+ "https://eliseusercontent.meetelise.com/webchat/01-EliseAI-Hero_1.mp4";
@@ -0,0 +1,56 @@
1
+ import { html, TemplateResult } from "lit";
2
+ import { styleMap } from "lit/directives/style-map.js";
3
+
4
+ const disclaimerStyles = {
5
+ container: {
6
+ fontSize: "10px",
7
+ paddingTop: "4px",
8
+ },
9
+ link: {
10
+ color: "#4287f5",
11
+ },
12
+ };
13
+
14
+ const disclaimer = ({
15
+ buildingName,
16
+ phoneNumberInput,
17
+ emailInput,
18
+ }: {
19
+ buildingName: string;
20
+ phoneNumberInput?: string;
21
+ emailInput?: string;
22
+ }): TemplateResult => {
23
+ if (phoneNumberInput) {
24
+ return html` <div style=${styleMap(disclaimerStyles.container)}>
25
+ By providing your number and clicking submit, you consent to recurring
26
+ marketing text messages and calls from or for
27
+ ${buildingName.length > 0 ? buildingName : "this building"} at this
28
+ number, which may be sent by an autodialer system. Replies may be AI or
29
+ human generated. This consent is not required to lease at this property.
30
+ Msg & Data rates may apply. You consent to this
31
+ <a
32
+ style=${styleMap(disclaimerStyles.link)}
33
+ href="http://bit.ly/me_privacy_policy"
34
+ target="_blank"
35
+ rel="noopener noreferrer"
36
+ >privacy policy</a
37
+ >, including having your communications recorded by a third party.
38
+ </div>`;
39
+ }
40
+ if (emailInput) {
41
+ return html` <div style=${styleMap(disclaimerStyles.container)}>
42
+ By entering your email and clicking submit, you consent to this
43
+ <a
44
+ style=${styleMap(disclaimerStyles.link)}
45
+ href="http://bit.ly/me_privacy_policy"
46
+ target="_blank"
47
+ rel="noopener noreferrer"
48
+ >privacy policy</a
49
+ >, including having your email address and communications collected and
50
+ recorded by a third party.
51
+ </div>`;
52
+ }
53
+ return html``;
54
+ };
55
+
56
+ export default disclaimer;
@@ -2,6 +2,9 @@ import axios from "axios";
2
2
  interface BuildingABType {
3
3
  abTestType: string;
4
4
  }
5
+ export enum abTestTypes {
6
+ "ConceptEmoji" = "Concept 2 (Blue/White)",
7
+ }
5
8
  export default async function fetchBuildingABTestType(
6
9
  buildingSlug: string
7
10
  ): Promise<BuildingABType | null> {
@@ -10,6 +13,7 @@ export default async function fetchBuildingABTestType(
10
13
  const abTestTypeResponse = await axios.get(
11
14
  `${host}/platformApi/webchat/${buildingSlug}/ab-test-type`
12
15
  );
16
+ //return { abTestType: "Concept 2 (Blue/White)" };
13
17
  return abTestTypeResponse.data;
14
18
  } catch (_) {
15
19
  return null;
@@ -1,3 +1,5 @@
1
+ import axios from "axios";
2
+
1
3
  export interface LabeledOption {
2
4
  label: string;
3
5
  value: string | number;
@@ -24,17 +26,21 @@ export interface Building {
24
26
  tourTypeOptions: LabeledOption[];
25
27
  chatWidgets?: string[] | null;
26
28
  chatCallUsHeader?: string;
27
- unitOptionsV2: UnitV2[];
28
- layoutOptionsV2: string[];
29
29
  autoOpenChatWidget: boolean;
30
30
  sgtUrl: string;
31
31
  escortedToursLink: string;
32
32
  virtualToursLink: string;
33
+ active: number;
34
+ usesDynamicScheduling: boolean;
35
+ textWithUsPhoneNumber: string | null;
33
36
  }
34
37
 
35
38
  export type UnitV2 = {
36
39
  name: string;
37
40
  layout: string;
41
+ availabilityStage: number;
42
+ active: number;
43
+ occupied: boolean;
38
44
  };
39
45
 
40
46
  /**
@@ -49,22 +55,24 @@ export default async function fetchBuildingInfo(
49
55
  buildingSlug: string
50
56
  ): Promise<Building> {
51
57
  const host = "https://app.meetelise.com";
52
- const url = `${host}/api/pub/v1/organization/${orgSlug}/building/${buildingSlug}`;
53
- const response = await fetch(url);
54
- const building: Building = await response.json();
58
+ const buildingUrl = `${host}/api/pub/v1/organization/${orgSlug}/building/${buildingSlug}`;
55
59
 
56
- // HACK
57
- // We will fetch these units/layouts from elise-crm-api and supplement the DTO
58
- const unitsResponse = await fetch(
59
- `${host}/eliseCrmApi/pub/building/${buildingSlug}/units`
60
- );
61
- const units: UnitV2[] = await unitsResponse.json();
62
- const layoutsResponse = await fetch(
63
- `${host}/eliseCrmApi/pub/building/${buildingSlug}/layouts`
64
- );
65
- const layouts: string[] = await layoutsResponse.json();
60
+ const buildingResponse = await axios.get(buildingUrl);
66
61
 
67
- building.unitOptionsV2 = units;
68
- building.layoutOptionsV2 = layouts;
62
+ const building: Building = buildingResponse.data;
63
+ try {
64
+ building.welcomeMessage = replaceName(
65
+ building.welcomeMessage ?? "",
66
+ building.userFirstName
67
+ );
68
+ } catch (e) {
69
+ // eslint-disable-next-line no-console
70
+ console.error(e);
71
+ }
69
72
  return building;
70
73
  }
74
+
75
+ const replaceName = (originalText: string, newName: string) => {
76
+ const regex = /elise/gi;
77
+ return originalText.replace(regex, newName);
78
+ };
@@ -0,0 +1,147 @@
1
+ import axios from "axios";
2
+
3
+ // V1 reads from the launch darkly flag
4
+ const featureFlagEndpointV1 = (buildingSlug: string): string => {
5
+ const host = "https://app.meetelise.com";
6
+ return `${host}/platformApi/webchat/${buildingSlug}/chat-ui-feature-flag`;
7
+ };
8
+
9
+ // V2 reads from conversation flag table (preferred)
10
+ const featureFlagEndpointV2 = (buildingSlug: string): string => {
11
+ const host = "https://app.meetelise.com";
12
+ return `${host}/platformApi/webchat/${buildingSlug}/v2/chat-ui-feature-flag`;
13
+ };
14
+
15
+ export enum FeatureFlagsShowDropdown {
16
+ onAttributionFailure = "on-attribution-failure",
17
+ never = "never", // note, the dropdown will NOT show up if there are also no lead sources (list)!
18
+ always = "always",
19
+ }
20
+
21
+ export async function fetchFeatureFlagShowMarketingSourceDropdown(
22
+ buildingSlug: string
23
+ ): Promise<FeatureFlagsShowDropdown> {
24
+ if (!buildingSlug) {
25
+ return FeatureFlagsShowDropdown.always;
26
+ }
27
+ try {
28
+ const featureFlagResponse = await axios.get(
29
+ featureFlagEndpointV1(buildingSlug),
30
+ {
31
+ params: {
32
+ building_slug: buildingSlug,
33
+ flag_type: "string",
34
+ feature_flag: "webchat-marketing-source-dropdown-configuration",
35
+ default_str: "always",
36
+ default_bool: true,
37
+ },
38
+ }
39
+ );
40
+ if (featureFlagResponse.data === "on-attribution-failure") {
41
+ return FeatureFlagsShowDropdown.onAttributionFailure;
42
+ }
43
+ if (featureFlagResponse.data === "never") {
44
+ return FeatureFlagsShowDropdown.never;
45
+ }
46
+ return FeatureFlagsShowDropdown.always;
47
+ } catch (_) {
48
+ return FeatureFlagsShowDropdown.always;
49
+ }
50
+ }
51
+
52
+ export async function fetchFeatureFlagUsePhoneNumberBySource(
53
+ buildingSlug: string
54
+ ): Promise<boolean> {
55
+ if (!buildingSlug) {
56
+ return false;
57
+ }
58
+ try {
59
+ const featureFlagResponse = await axios.get(
60
+ featureFlagEndpointV1(buildingSlug),
61
+ {
62
+ params: {
63
+ building_slug: buildingSlug,
64
+ flag_type: "bool",
65
+ feature_flag: "webchat-use-dni-phone-number-by-source",
66
+ default_str: null,
67
+ default_bool: false,
68
+ },
69
+ }
70
+ );
71
+ return featureFlagResponse.data;
72
+ } catch (_) {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ export async function fetchFeatureFlagUseOverrideContactUsForm(
78
+ buildingSlug: string
79
+ ): Promise<boolean> {
80
+ if (!buildingSlug) {
81
+ return false;
82
+ }
83
+ try {
84
+ const featureFlagResponse = await axios.get(
85
+ featureFlagEndpointV1(buildingSlug),
86
+ {
87
+ params: {
88
+ building_slug: buildingSlug,
89
+ flag_type: "bool",
90
+ feature_flag: "webchat-use-override-contact-us-form",
91
+ default_str: null,
92
+ default_bool: false,
93
+ },
94
+ }
95
+ );
96
+ return featureFlagResponse.data;
97
+ } catch (_) {
98
+ return false;
99
+ }
100
+ }
101
+ export async function fetchFeatureFlagUsePubnub(
102
+ buildingSlug?: string
103
+ ): Promise<boolean> {
104
+ if (!buildingSlug) {
105
+ return false;
106
+ }
107
+ try {
108
+ const featureFlagResponse = await axios.get(
109
+ featureFlagEndpointV1(buildingSlug),
110
+ {
111
+ params: {
112
+ building_slug: buildingSlug,
113
+ flag_type: "bool",
114
+ feature_flag: "use-pubnub-chat-provider",
115
+ default_str: null,
116
+ default_bool: false,
117
+ },
118
+ }
119
+ );
120
+ return featureFlagResponse.data;
121
+ } catch (_) {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ export async function fetchFeatureFlagInsertDNIWebsite(
127
+ buildingSlug?: string
128
+ ): Promise<boolean> {
129
+ if (!buildingSlug) {
130
+ return false;
131
+ }
132
+ try {
133
+ const featureFlagResponse = await axios.get(
134
+ featureFlagEndpointV2(buildingSlug),
135
+ {
136
+ params: {
137
+ building_slug: buildingSlug,
138
+ feature_flag_name: "insert_dni_into_website",
139
+ default_value: false,
140
+ },
141
+ }
142
+ );
143
+ return featureFlagResponse.data;
144
+ } catch (_) {
145
+ return false;
146
+ }
147
+ }
@@ -10,8 +10,74 @@ export default async function fetchLeadSources(
10
10
  const leadSourcesResponse = await axios.get(
11
11
  `${host}/platformApi/webchat/${buildingSlug}/lead-sources`
12
12
  );
13
- return leadSourcesResponse.data;
13
+ if (leadSourcesResponse.data) {
14
+ return filterOutBuildingSpecificLeadSources(
15
+ leadSourcesResponse.data,
16
+ buildingSlug
17
+ );
18
+ }
19
+ return [];
14
20
  } catch (_) {
15
21
  return [];
16
22
  }
17
23
  }
24
+
25
+ const filterOutBuildingSpecificLeadSources = (
26
+ leadSources: string[],
27
+ buildingSlug: string
28
+ ): string[] => {
29
+ // Dinerstien asked to filter out some sources based on their buildings
30
+ let removeLeadSources: string[] = [];
31
+ switch (buildingSlug) {
32
+ case "d41619ec-0f79-11ee-8439-93855926ad58": // Infinity Lofts in the Gulch
33
+ removeLeadSources = [
34
+ "Forthea - PPC",
35
+ "HotPads",
36
+ "Forthea",
37
+ "Locator",
38
+ "Website-apartmentguide.com",
39
+ "Forthea - organic search",
40
+ "Tdc employee",
41
+ "University newspaper",
42
+ "Nashville apartment locators",
43
+ "Forthea - paid social",
44
+ "Apartment list",
45
+ "Off campus partners",
46
+ "Waze ads",
47
+ "Athletic event",
48
+ "Website-apartment guide",
49
+ ];
50
+ break;
51
+ case "d4160560-0f79-11ee-8438-fba837bf3a46": // Sawyer on Lincoln
52
+ removeLeadSources = [
53
+ "Forthea - PPC",
54
+ "Rentgrata",
55
+ "Website-ApartmentFinder.com",
56
+ "Word of Mouth",
57
+ "Texas A&M A-Frame on Campus",
58
+ "Rent.com",
59
+ "Website-craigslist",
60
+ "Apartment List",
61
+ "Website-apartments.com",
62
+ "Forthea - paid social",
63
+ "Forthea - organic search",
64
+ "Website-apartmentguide.com",
65
+ "Forthea",
66
+ "Uhomes",
67
+ "Phone book",
68
+ "Property - website",
69
+ ];
70
+ break;
71
+ default:
72
+ removeLeadSources = [];
73
+ }
74
+ leadSources.sort();
75
+ return leadSources.filter((leadSource) => {
76
+ const lowerCaseLeadSource = leadSource.toLowerCase();
77
+ const lowerCaseRemoveLeadSources = removeLeadSources.map((source) =>
78
+ source.toLowerCase()
79
+ );
80
+
81
+ return !lowerCaseRemoveLeadSources.includes(lowerCaseLeadSource);
82
+ });
83
+ };
@@ -0,0 +1,31 @@
1
+ import axios from "axios";
2
+ import { formatPhoneNumber } from "./WebComponent/actions/formatPhoneNumber";
3
+
4
+ export interface NumberForSelectedSource {
5
+ number: string;
6
+ isMatch: boolean;
7
+ isPropertyWebsiteCatchall: boolean;
8
+ }
9
+
10
+ export default async function fetchPhoneNumberFromSource(
11
+ buildingSlug: string,
12
+ source: string | null
13
+ ): Promise<NumberForSelectedSource | null> {
14
+ const host = "https://app.meetelise.com";
15
+ try {
16
+ const phoneNumberResponse = await axios.get(
17
+ `${host}/platformApi/webchat/${buildingSlug}/phone-number-by-source?source=${source}`
18
+ );
19
+ if (phoneNumberResponse.data) {
20
+ return {
21
+ number: formatPhoneNumber(phoneNumberResponse.data.number),
22
+ isMatch: phoneNumberResponse.data.match,
23
+ isPropertyWebsiteCatchall:
24
+ phoneNumberResponse.data.isPropertyWebsiteCatchall,
25
+ };
26
+ }
27
+ return null;
28
+ } catch (_) {
29
+ return null;
30
+ }
31
+ }
@@ -0,0 +1,55 @@
1
+ import axios from "axios";
2
+ import { camelize } from "./utils";
3
+
4
+ export interface WebchatSettings {
5
+ config: WebchatConfigurationPreferences;
6
+ isInheriting: boolean;
7
+ isGlobalDefault: boolean;
8
+ }
9
+ export interface WebchatConfigurationPreferences {
10
+ autoOpenChat: boolean;
11
+ shouldShowChat: boolean;
12
+ shouldShowChatDesktop: boolean;
13
+ shouldShowChatMobile: boolean;
14
+ shouldShowEmail: boolean;
15
+ shouldShowEmailDesktop: boolean;
16
+ shouldShowEmailMobile: boolean;
17
+ shouldShowPhone: boolean;
18
+ shouldShowPhoneDesktop: boolean;
19
+ shouldShowPhoneMobile: boolean;
20
+ shouldShowText: boolean;
21
+ shouldShowTextDesktop: boolean;
22
+ shouldShowTextMobile: boolean;
23
+ shouldShowSst: boolean;
24
+ shouldShowSstDesktop: boolean;
25
+ shouldShowSstMobile: boolean;
26
+ displayStyle: DesignConcepts;
27
+ primaryColor: string;
28
+ backgroundColor: string;
29
+ requiresConsent: boolean;
30
+ privacyPolicyUrl: string;
31
+ }
32
+
33
+ export enum DesignConcepts {
34
+ EMOJI = "emoji",
35
+ PILLS = "pills",
36
+ MINIMIZED = "minimized", // this is also mobile
37
+ }
38
+
39
+ export default async function fetchWebchatPreferences(
40
+ buildingId: number
41
+ ): Promise<WebchatSettings | null> {
42
+ const host = "https://app.meetelise.com";
43
+ try {
44
+ const webchatPreferencesResponse = await axios.get(
45
+ `${host}/eliseCrmApi/webchat/config/building/${buildingId}`
46
+ );
47
+
48
+ if (webchatPreferencesResponse.data) {
49
+ return camelize(webchatPreferencesResponse.data);
50
+ }
51
+ } catch (_) {
52
+ return null;
53
+ }
54
+ return null;
55
+ }
@@ -44,9 +44,13 @@ export const getRawAvailabilities = async (
44
44
  }
45
45
  const startTime = startOfToday();
46
46
  const endTime = formatISO(endOfDay(addDays(startTime, 30)));
47
- const url = `https://app.meetelise.com/api/pub/v1/buildings/${buildingIdToUse}/tour/availabilities?startTime=${formatISO(
48
- startTime
49
- )}&endTime=${endTime}`;
47
+
48
+ // We MUST encode the URI components because a positive timezone offset counts as a space in urls!
49
+ const timeParams = `startTime=${encodeURIComponent(
50
+ formatISO(startTime)
51
+ )}&endTime=${encodeURIComponent(endTime)}`;
52
+ const url = `https://app.meetelise.com/api/pub/v1/buildings/${buildingIdToUse}/tour/availabilities?${timeParams}`;
53
+
50
54
  const result = await axios.get<TourAvailabilityResponse>(url);
51
55
  availabilitiesCache.availabilities[buildingIdToUse] = result.data;
52
56
  // The endpoint INCORRECTLY states that these are returned as dates. They are, in fact, strings.
@@ -0,0 +1,26 @@
1
+ import axios from "axios";
2
+ import { LogType, sendLoggingEvent } from "./analytics";
3
+
4
+ export const getBuildingPhoneNumber = async (
5
+ buildingSlug: string
6
+ ): Promise<string | null> => {
7
+ try {
8
+ const host = "https://app.meetelise.com";
9
+ const webchatPreferencesResponse = await axios.get(
10
+ `${host}/platformApi/webchat/${buildingSlug}/phone-number`
11
+ );
12
+ if (webchatPreferencesResponse.data) {
13
+ return webchatPreferencesResponse.data;
14
+ }
15
+ } catch (error) {
16
+ sendLoggingEvent({
17
+ logType: LogType.error,
18
+ buildingSlug,
19
+ logTitle: "[ERROR_GETTING_PHONE_NUMBERS]",
20
+ logData: { error },
21
+ });
22
+
23
+ return null;
24
+ }
25
+ return null;
26
+ };
@@ -0,0 +1,16 @@
1
+ import axios from "axios";
2
+
3
+ export const getShouldAllowScheduling = async (
4
+ buildingId: number,
5
+ isDynamicSchedulingEnabled = false
6
+ ): Promise<boolean> => {
7
+ const host = "https://app.meetelise.com";
8
+ const url = `${host}/eliseCrmApi/building/${buildingId}/scheduling_status?observe_dynamic_scheduling=${isDynamicSchedulingEnabled}`;
9
+
10
+ try {
11
+ const response = await axios.get(url);
12
+ return response.data.is_enabled;
13
+ } catch (e) {
14
+ return false;
15
+ }
16
+ };