@product7/feedback-sdk 1.3.0 → 1.3.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.
- package/dist/feedback-sdk.js +2158 -663
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +2 -1
- package/src/core/APIService.js +191 -5
- package/src/core/FeedbackSDK.js +3 -0
- package/src/styles/messengerStyles.js +580 -9
- package/src/widgets/MessengerWidget.js +247 -137
- package/src/widgets/messenger/MessengerState.js +31 -1
- package/src/widgets/messenger/views/ChatView.js +347 -29
- package/src/widgets/messenger/views/ConversationsView.js +20 -5
- package/src/widgets/messenger/views/HomeView.js +50 -10
- package/src/widgets/messenger/views/PreChatFormView.js +224 -0
package/dist/feedback-sdk.js
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
];
|
|
56
56
|
|
|
57
|
-
const MOCK_CONVERSATIONS = [
|
|
57
|
+
const MOCK_CONVERSATIONS$1 = [
|
|
58
58
|
{
|
|
59
59
|
id: 'conv_1',
|
|
60
60
|
subject: 'Question about pricing',
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
];
|
|
87
87
|
|
|
88
|
-
const MOCK_MESSAGES = {
|
|
88
|
+
const MOCK_MESSAGES$1 = {
|
|
89
89
|
conv_1: [
|
|
90
90
|
{
|
|
91
91
|
id: 'msg_1',
|
|
@@ -408,8 +408,8 @@
|
|
|
408
408
|
await delay$1(300);
|
|
409
409
|
return {
|
|
410
410
|
status: true,
|
|
411
|
-
data: MOCK_CONVERSATIONS,
|
|
412
|
-
meta: { total: MOCK_CONVERSATIONS.length, page: 1, limit: 20 },
|
|
411
|
+
data: MOCK_CONVERSATIONS$1,
|
|
412
|
+
meta: { total: MOCK_CONVERSATIONS$1.length, page: 1, limit: 20 },
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
415
|
|
|
@@ -428,10 +428,10 @@
|
|
|
428
428
|
|
|
429
429
|
if (this.api.mock) {
|
|
430
430
|
await delay$1(200);
|
|
431
|
-
const conv = MOCK_CONVERSATIONS.find((c) => c.id === conversationId);
|
|
431
|
+
const conv = MOCK_CONVERSATIONS$1.find((c) => c.id === conversationId);
|
|
432
432
|
return {
|
|
433
433
|
status: true,
|
|
434
|
-
data: { ...conv, messages: MOCK_MESSAGES[conversationId] || [] },
|
|
434
|
+
data: { ...conv, messages: MOCK_MESSAGES$1[conversationId] || [] },
|
|
435
435
|
};
|
|
436
436
|
}
|
|
437
437
|
|
|
@@ -464,8 +464,8 @@
|
|
|
464
464
|
},
|
|
465
465
|
],
|
|
466
466
|
};
|
|
467
|
-
MOCK_CONVERSATIONS.unshift(newConv);
|
|
468
|
-
MOCK_MESSAGES[newConv.id] = newConv.messages;
|
|
467
|
+
MOCK_CONVERSATIONS$1.unshift(newConv);
|
|
468
|
+
MOCK_MESSAGES$1[newConv.id] = newConv.messages;
|
|
469
469
|
return { status: true, data: newConv };
|
|
470
470
|
}
|
|
471
471
|
|
|
@@ -493,10 +493,10 @@
|
|
|
493
493
|
sender_type: 'customer',
|
|
494
494
|
created_at: new Date().toISOString(),
|
|
495
495
|
};
|
|
496
|
-
if (!MOCK_MESSAGES[conversationId]) {
|
|
497
|
-
MOCK_MESSAGES[conversationId] = [];
|
|
496
|
+
if (!MOCK_MESSAGES$1[conversationId]) {
|
|
497
|
+
MOCK_MESSAGES$1[conversationId] = [];
|
|
498
498
|
}
|
|
499
|
-
MOCK_MESSAGES[conversationId].push(newMessage);
|
|
499
|
+
MOCK_MESSAGES$1[conversationId].push(newMessage);
|
|
500
500
|
return { status: true, data: newMessage };
|
|
501
501
|
}
|
|
502
502
|
|
|
@@ -517,7 +517,7 @@
|
|
|
517
517
|
await this.api._ensureSession();
|
|
518
518
|
|
|
519
519
|
if (this.api.mock) {
|
|
520
|
-
const count = MOCK_CONVERSATIONS.reduce(
|
|
520
|
+
const count = MOCK_CONVERSATIONS$1.reduce(
|
|
521
521
|
(sum, c) => sum + (c.unread || 0),
|
|
522
522
|
0
|
|
523
523
|
);
|
|
@@ -562,7 +562,7 @@
|
|
|
562
562
|
}
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
class APIError extends Error {
|
|
565
|
+
let APIError$1 = class APIError extends Error {
|
|
566
566
|
constructor(status, message, response) {
|
|
567
567
|
super(message);
|
|
568
568
|
this.name = 'APIError';
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
isServerError() {
|
|
586
586
|
return this.status >= 500 && this.status < 600;
|
|
587
587
|
}
|
|
588
|
-
}
|
|
588
|
+
};
|
|
589
589
|
|
|
590
590
|
class WidgetError extends Error {
|
|
591
591
|
constructor(message, widgetType, widgetId) {
|
|
@@ -661,7 +661,7 @@
|
|
|
661
661
|
}
|
|
662
662
|
|
|
663
663
|
async submitSurveyResponse(surveyId, responseData) {
|
|
664
|
-
if (!surveyId) throw new APIError(400, 'Survey ID is required');
|
|
664
|
+
if (!surveyId) throw new APIError$1(400, 'Survey ID is required');
|
|
665
665
|
|
|
666
666
|
await this.api._ensureSession();
|
|
667
667
|
|
|
@@ -700,7 +700,7 @@
|
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
async dismissSurvey(surveyId) {
|
|
703
|
-
if (!surveyId) throw new APIError(400, 'Survey ID is required');
|
|
703
|
+
if (!surveyId) throw new APIError$1(400, 'Survey ID is required');
|
|
704
704
|
|
|
705
705
|
await this.api._ensureSession();
|
|
706
706
|
|
|
@@ -764,7 +764,7 @@
|
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
if (!this.workspace || !this.userContext) {
|
|
767
|
-
throw new APIError(
|
|
767
|
+
throw new APIError$1(
|
|
768
768
|
400,
|
|
769
769
|
`Missing ${!this.workspace ? 'workspace' : 'user context'} for initialization`
|
|
770
770
|
);
|
|
@@ -818,7 +818,7 @@
|
|
|
818
818
|
expiresIn: response.expires_in,
|
|
819
819
|
};
|
|
820
820
|
} catch (error) {
|
|
821
|
-
throw new APIError(
|
|
821
|
+
throw new APIError$1(
|
|
822
822
|
error.status || 500,
|
|
823
823
|
`Failed to initialize widget: ${error.message}`,
|
|
824
824
|
error.response
|
|
@@ -831,7 +831,7 @@
|
|
|
831
831
|
await this.init();
|
|
832
832
|
}
|
|
833
833
|
if (!this.sessionToken) {
|
|
834
|
-
throw new APIError(401, 'No valid session token available');
|
|
834
|
+
throw new APIError$1(401, 'No valid session token available');
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
|
|
@@ -931,7 +931,7 @@
|
|
|
931
931
|
errorMessage = (await response.text()) || errorMessage;
|
|
932
932
|
}
|
|
933
933
|
|
|
934
|
-
throw new APIError(response.status, errorMessage, responseData);
|
|
934
|
+
throw new APIError$1(response.status, errorMessage, responseData);
|
|
935
935
|
}
|
|
936
936
|
|
|
937
937
|
const contentType = response.headers.get('content-type');
|
|
@@ -941,8 +941,8 @@
|
|
|
941
941
|
|
|
942
942
|
return await response.text();
|
|
943
943
|
} catch (error) {
|
|
944
|
-
if (error instanceof APIError) throw error;
|
|
945
|
-
throw new APIError(0, error.message, null);
|
|
944
|
+
if (error instanceof APIError$1) throw error;
|
|
945
|
+
throw new APIError$1(0, error.message, null);
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
948
|
|
|
@@ -997,7 +997,29 @@
|
|
|
997
997
|
}
|
|
998
998
|
|
|
999
999
|
async checkAgentsOnline() {
|
|
1000
|
-
|
|
1000
|
+
if (!this.isSessionValid()) {
|
|
1001
|
+
await this.init();
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (this.mock) {
|
|
1005
|
+
return {
|
|
1006
|
+
status: true,
|
|
1007
|
+
data: {
|
|
1008
|
+
agents_online: true,
|
|
1009
|
+
online_count: 2,
|
|
1010
|
+
response_time: 'Usually replies within a few minutes',
|
|
1011
|
+
available_agents: [
|
|
1012
|
+
{ full_name: 'Sarah', picture: '' },
|
|
1013
|
+
{ full_name: 'Tom', picture: '' },
|
|
1014
|
+
],
|
|
1015
|
+
},
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return this._makeRequest('/widget/messenger/agents/online', {
|
|
1020
|
+
method: 'GET',
|
|
1021
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
1022
|
+
});
|
|
1001
1023
|
}
|
|
1002
1024
|
|
|
1003
1025
|
async getConversations(options) {
|
|
@@ -1013,11 +1035,119 @@
|
|
|
1013
1035
|
}
|
|
1014
1036
|
|
|
1015
1037
|
async startConversation(data) {
|
|
1016
|
-
|
|
1038
|
+
if (!this.isSessionValid()) {
|
|
1039
|
+
console.log('[APIService] startConversation: session invalid, calling init...');
|
|
1040
|
+
try {
|
|
1041
|
+
await this.init();
|
|
1042
|
+
console.log('[APIService] startConversation: init result, token:', this.sessionToken ? 'set' : 'null');
|
|
1043
|
+
} catch (initError) {
|
|
1044
|
+
console.error('[APIService] startConversation: init failed:', initError.message);
|
|
1045
|
+
throw initError;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (!this.sessionToken) {
|
|
1050
|
+
console.error('[APIService] startConversation: no session token after init');
|
|
1051
|
+
throw new APIError(401, 'No valid session token available');
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
console.log('[APIService] startConversation: sending to', `${this.baseURL}/widget/messenger/conversations`, 'mock:', this.mock);
|
|
1055
|
+
|
|
1056
|
+
if (this.mock) {
|
|
1057
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1058
|
+
const newConv = {
|
|
1059
|
+
id: 'conv_' + Date.now(),
|
|
1060
|
+
subject: data.subject || 'New conversation',
|
|
1061
|
+
status: 'open',
|
|
1062
|
+
last_message_at: new Date().toISOString(),
|
|
1063
|
+
created_at: new Date().toISOString(),
|
|
1064
|
+
messages: [
|
|
1065
|
+
{
|
|
1066
|
+
id: 'msg_' + Date.now(),
|
|
1067
|
+
content: data.message,
|
|
1068
|
+
sender_type: 'customer',
|
|
1069
|
+
created_at: new Date().toISOString(),
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
};
|
|
1073
|
+
MOCK_CONVERSATIONS.unshift(newConv);
|
|
1074
|
+
MOCK_MESSAGES[newConv.id] = newConv.messages;
|
|
1075
|
+
return { status: true, data: newConv };
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return this._makeRequest('/widget/messenger/conversations', {
|
|
1079
|
+
method: 'POST',
|
|
1080
|
+
headers: {
|
|
1081
|
+
'Content-Type': 'application/json',
|
|
1082
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
1083
|
+
},
|
|
1084
|
+
body: JSON.stringify({
|
|
1085
|
+
message: data.message,
|
|
1086
|
+
subject: data.subject || '',
|
|
1087
|
+
}),
|
|
1088
|
+
});
|
|
1017
1089
|
}
|
|
1018
1090
|
|
|
1019
1091
|
async sendMessage(conversationId, data) {
|
|
1020
|
-
|
|
1092
|
+
if (!this.isSessionValid()) {
|
|
1093
|
+
await this.init();
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (this.mock) {
|
|
1097
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1098
|
+
const newMessage = {
|
|
1099
|
+
id: 'msg_' + Date.now(),
|
|
1100
|
+
content: data.content,
|
|
1101
|
+
attachments: data.attachments || [],
|
|
1102
|
+
sender_type: 'customer',
|
|
1103
|
+
created_at: new Date().toISOString(),
|
|
1104
|
+
};
|
|
1105
|
+
if (!MOCK_MESSAGES[conversationId]) {
|
|
1106
|
+
MOCK_MESSAGES[conversationId] = [];
|
|
1107
|
+
}
|
|
1108
|
+
MOCK_MESSAGES[conversationId].push(newMessage);
|
|
1109
|
+
return { status: true, data: newMessage };
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const payload = { content: data.content };
|
|
1113
|
+
if (data.attachments && data.attachments.length > 0) {
|
|
1114
|
+
payload.attachments = data.attachments;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}/messages`, {
|
|
1118
|
+
method: 'POST',
|
|
1119
|
+
headers: {
|
|
1120
|
+
'Content-Type': 'application/json',
|
|
1121
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
1122
|
+
},
|
|
1123
|
+
body: JSON.stringify(payload),
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Upload a file to CDN via widget endpoint
|
|
1129
|
+
* @param {string} base64Data - Base64 encoded file data (with or without data URI prefix)
|
|
1130
|
+
* @param {string} filename - Original filename
|
|
1131
|
+
* @returns {Promise<Object>} Response with url
|
|
1132
|
+
*/
|
|
1133
|
+
async uploadFile(base64Data, filename) {
|
|
1134
|
+
if (!this.isSessionValid()) {
|
|
1135
|
+
await this.init();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (this.mock) {
|
|
1139
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1140
|
+
return { status: true, url: `https://mock-cdn.example.com/${filename}` };
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
return this._makeRequest('/widget/messenger/upload', {
|
|
1144
|
+
method: 'POST',
|
|
1145
|
+
headers: {
|
|
1146
|
+
'Content-Type': 'application/json',
|
|
1147
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
1148
|
+
},
|
|
1149
|
+
body: JSON.stringify({ file: base64Data, filename }),
|
|
1150
|
+
});
|
|
1021
1151
|
}
|
|
1022
1152
|
|
|
1023
1153
|
async sendTypingIndicator(conversationId, isTyping) {
|
|
@@ -1048,8 +1178,64 @@
|
|
|
1048
1178
|
return this.help.searchHelpArticles(query, options);
|
|
1049
1179
|
}
|
|
1050
1180
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1181
|
+
_loadStoredSession() {
|
|
1182
|
+
if (typeof localStorage === 'undefined') return false;
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
const stored = localStorage.getItem('feedbackSDK_session');
|
|
1186
|
+
if (!stored) return false;
|
|
1187
|
+
|
|
1188
|
+
const sessionData = JSON.parse(stored);
|
|
1189
|
+
|
|
1190
|
+
// Invalidate mock tokens when not in mock mode (and vice versa)
|
|
1191
|
+
const isMockToken = sessionData.token && sessionData.token.startsWith('mock_');
|
|
1192
|
+
if (isMockToken !== this.mock) {
|
|
1193
|
+
localStorage.removeItem('feedbackSDK_session');
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
this.sessionToken = sessionData.token;
|
|
1198
|
+
this.sessionExpiry = new Date(sessionData.expiry);
|
|
1199
|
+
|
|
1200
|
+
return this.isSessionValid();
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
async _makeRequest(endpoint, options = {}) {
|
|
1207
|
+
const url = `${this.baseURL}${endpoint}`;
|
|
1208
|
+
|
|
1209
|
+
try {
|
|
1210
|
+
const response = await fetch(url, options);
|
|
1211
|
+
|
|
1212
|
+
if (!response.ok) {
|
|
1213
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
1214
|
+
let responseData = null;
|
|
1215
|
+
|
|
1216
|
+
try {
|
|
1217
|
+
responseData = await response.json();
|
|
1218
|
+
errorMessage =
|
|
1219
|
+
responseData.message || responseData.error || errorMessage;
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
errorMessage = (await response.text()) || errorMessage;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
throw new APIError(response.status, errorMessage, responseData);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const contentType = response.headers.get('content-type');
|
|
1228
|
+
if (contentType && contentType.includes('application/json')) {
|
|
1229
|
+
return await response.json();
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return await response.text();
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
if (error instanceof APIError) {
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1237
|
+
throw new APIError(0, error.message, null);
|
|
1238
|
+
}
|
|
1053
1239
|
}
|
|
1054
1240
|
}
|
|
1055
1241
|
|
|
@@ -2772,333 +2958,57 @@
|
|
|
2772
2958
|
}
|
|
2773
2959
|
|
|
2774
2960
|
/**
|
|
2775
|
-
*
|
|
2961
|
+
* MessengerState - State management for the Messenger widget
|
|
2776
2962
|
*/
|
|
2963
|
+
class MessengerState {
|
|
2964
|
+
constructor(options = {}) {
|
|
2965
|
+
this.currentView = 'home'; // 'home', 'messages', 'chat', 'help', 'changelog'
|
|
2966
|
+
this.isOpen = false;
|
|
2967
|
+
this.unreadCount = 0;
|
|
2968
|
+
this.activeConversationId = null;
|
|
2777
2969
|
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
this.
|
|
2781
|
-
this.workspace = config.workspace || '';
|
|
2782
|
-
this.sessionToken = config.sessionToken || null;
|
|
2783
|
-
this.mock = config.mock || false;
|
|
2970
|
+
// Conversations
|
|
2971
|
+
this.conversations = [];
|
|
2972
|
+
this.messages = {}; // { conversationId: [messages] }
|
|
2784
2973
|
|
|
2785
|
-
|
|
2786
|
-
this.
|
|
2787
|
-
this.
|
|
2788
|
-
this.reconnectDelay = 1000;
|
|
2789
|
-
this.pingInterval = null;
|
|
2790
|
-
this.isConnected = false;
|
|
2974
|
+
// Help articles
|
|
2975
|
+
this.helpArticles = [];
|
|
2976
|
+
this.helpSearchQuery = '';
|
|
2791
2977
|
|
|
2792
|
-
//
|
|
2793
|
-
this.
|
|
2978
|
+
// Changelog
|
|
2979
|
+
this.homeChangelogItems = [];
|
|
2980
|
+
this.changelogItems = [];
|
|
2794
2981
|
|
|
2795
|
-
//
|
|
2796
|
-
this.
|
|
2797
|
-
this.
|
|
2798
|
-
this.
|
|
2799
|
-
this._onError = this._onError.bind(this);
|
|
2800
|
-
}
|
|
2982
|
+
// Team info
|
|
2983
|
+
this.teamName = options.teamName || 'Support';
|
|
2984
|
+
this.teamAvatars = options.teamAvatars || [];
|
|
2985
|
+
this.welcomeMessage = options.welcomeMessage || 'How can we help?';
|
|
2801
2986
|
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
*/
|
|
2805
|
-
connect(sessionToken = null) {
|
|
2806
|
-
if (sessionToken) {
|
|
2807
|
-
this.sessionToken = sessionToken;
|
|
2808
|
-
}
|
|
2987
|
+
// User info
|
|
2988
|
+
this.userContext = options.userContext || null;
|
|
2809
2989
|
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
}
|
|
2990
|
+
// Feature flags
|
|
2991
|
+
this.enableHelp = options.enableHelp !== false;
|
|
2992
|
+
this.enableChangelog = options.enableChangelog !== false;
|
|
2814
2993
|
|
|
2815
|
-
//
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
this._startMockResponses();
|
|
2820
|
-
return;
|
|
2821
|
-
}
|
|
2994
|
+
// Agent availability
|
|
2995
|
+
this.agentsOnline = false;
|
|
2996
|
+
this.onlineCount = 0;
|
|
2997
|
+
this.responseTime = 'Usually replies within a few minutes';
|
|
2822
2998
|
|
|
2823
|
-
//
|
|
2824
|
-
|
|
2825
|
-
let wsURL = this.baseURL.replace(/^https?:/, wsProtocol);
|
|
2826
|
-
wsURL = wsURL.replace('/api/v1', '');
|
|
2827
|
-
wsURL = `${wsURL}/api/v1/widget/messenger/ws?token=${encodeURIComponent(this.sessionToken)}`;
|
|
2999
|
+
// Typing indicators
|
|
3000
|
+
this.typingUsers = {}; // { conversationId: { userName, timestamp } }
|
|
2828
3001
|
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
} catch (error) {
|
|
2836
|
-
console.error('[WebSocket] Connection error:', error);
|
|
2837
|
-
this._scheduleReconnect();
|
|
2838
|
-
}
|
|
3002
|
+
// Loading states
|
|
3003
|
+
this.isLoading = false;
|
|
3004
|
+
this.isLoadingMessages = false;
|
|
3005
|
+
|
|
3006
|
+
// Listeners
|
|
3007
|
+
this._listeners = new Set();
|
|
2839
3008
|
}
|
|
2840
3009
|
|
|
2841
3010
|
/**
|
|
2842
|
-
*
|
|
2843
|
-
*/
|
|
2844
|
-
disconnect() {
|
|
2845
|
-
this.isConnected = false;
|
|
2846
|
-
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
|
|
2847
|
-
|
|
2848
|
-
if (this.pingInterval) {
|
|
2849
|
-
clearInterval(this.pingInterval);
|
|
2850
|
-
this.pingInterval = null;
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
if (this.ws) {
|
|
2854
|
-
this.ws.close();
|
|
2855
|
-
this.ws = null;
|
|
2856
|
-
}
|
|
2857
|
-
|
|
2858
|
-
if (this._mockInterval) {
|
|
2859
|
-
clearInterval(this._mockInterval);
|
|
2860
|
-
this._mockInterval = null;
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
/**
|
|
2865
|
-
* Subscribe to events
|
|
2866
|
-
* @param {string} event - Event name
|
|
2867
|
-
* @param {Function} callback - Event handler
|
|
2868
|
-
* @returns {Function} Unsubscribe function
|
|
2869
|
-
*/
|
|
2870
|
-
on(event, callback) {
|
|
2871
|
-
if (!this._listeners.has(event)) {
|
|
2872
|
-
this._listeners.set(event, new Set());
|
|
2873
|
-
}
|
|
2874
|
-
this._listeners.get(event).add(callback);
|
|
2875
|
-
return () => this._listeners.get(event).delete(callback);
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
/**
|
|
2879
|
-
* Remove event listener
|
|
2880
|
-
*/
|
|
2881
|
-
off(event, callback) {
|
|
2882
|
-
if (this._listeners.has(event)) {
|
|
2883
|
-
this._listeners.get(event).delete(callback);
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
|
-
/**
|
|
2888
|
-
* Send message through WebSocket
|
|
2889
|
-
*/
|
|
2890
|
-
send(type, payload = {}) {
|
|
2891
|
-
if (!this.isConnected) {
|
|
2892
|
-
console.warn('[WebSocket] Not connected, cannot send message');
|
|
2893
|
-
return;
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
if (this.mock) {
|
|
2897
|
-
// Mock mode - just log
|
|
2898
|
-
console.log('[WebSocket Mock] Sending:', type, payload);
|
|
2899
|
-
return;
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
2903
|
-
this.ws.send(JSON.stringify({ type, payload }));
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
// Private methods
|
|
2908
|
-
|
|
2909
|
-
_onOpen() {
|
|
2910
|
-
console.log('[WebSocket] Connected');
|
|
2911
|
-
this.isConnected = true;
|
|
2912
|
-
this.reconnectAttempts = 0;
|
|
2913
|
-
this._emit('connected', {});
|
|
2914
|
-
|
|
2915
|
-
// Start ping interval to keep connection alive
|
|
2916
|
-
this.pingInterval = setInterval(() => {
|
|
2917
|
-
this.send('ping', {});
|
|
2918
|
-
}, 30000);
|
|
2919
|
-
}
|
|
2920
|
-
|
|
2921
|
-
_onMessage(event) {
|
|
2922
|
-
try {
|
|
2923
|
-
const data = JSON.parse(event.data);
|
|
2924
|
-
const { type, payload } = data;
|
|
2925
|
-
|
|
2926
|
-
// Handle different event types
|
|
2927
|
-
switch (type) {
|
|
2928
|
-
case 'message:new':
|
|
2929
|
-
this._emit('message', payload);
|
|
2930
|
-
break;
|
|
2931
|
-
case 'typing:started':
|
|
2932
|
-
this._emit('typing_started', payload);
|
|
2933
|
-
break;
|
|
2934
|
-
case 'typing:stopped':
|
|
2935
|
-
this._emit('typing_stopped', payload);
|
|
2936
|
-
break;
|
|
2937
|
-
case 'conversation:updated':
|
|
2938
|
-
this._emit('conversation_updated', payload);
|
|
2939
|
-
break;
|
|
2940
|
-
case 'conversation:closed':
|
|
2941
|
-
this._emit('conversation_closed', payload);
|
|
2942
|
-
break;
|
|
2943
|
-
case 'availability:changed':
|
|
2944
|
-
this._emit('availability_changed', payload);
|
|
2945
|
-
break;
|
|
2946
|
-
case 'pong':
|
|
2947
|
-
// Ping response, ignore
|
|
2948
|
-
break;
|
|
2949
|
-
default:
|
|
2950
|
-
console.log('[WebSocket] Unknown event:', type, payload);
|
|
2951
|
-
}
|
|
2952
|
-
} catch (error) {
|
|
2953
|
-
console.error('[WebSocket] Failed to parse message:', error);
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
_onClose(event) {
|
|
2958
|
-
console.log('[WebSocket] Disconnected:', event.code, event.reason);
|
|
2959
|
-
this.isConnected = false;
|
|
2960
|
-
|
|
2961
|
-
if (this.pingInterval) {
|
|
2962
|
-
clearInterval(this.pingInterval);
|
|
2963
|
-
this.pingInterval = null;
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
|
-
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
2967
|
-
this._scheduleReconnect();
|
|
2968
|
-
}
|
|
2969
|
-
|
|
2970
|
-
_onError(error) {
|
|
2971
|
-
console.error('[WebSocket] Error:', error);
|
|
2972
|
-
this._emit('error', { error });
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
|
-
_scheduleReconnect() {
|
|
2976
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
2977
|
-
console.log('[WebSocket] Max reconnect attempts reached');
|
|
2978
|
-
this._emit('reconnect_failed', {});
|
|
2979
|
-
return;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
this.reconnectAttempts++;
|
|
2983
|
-
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
2984
|
-
console.log(
|
|
2985
|
-
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
2986
|
-
);
|
|
2987
|
-
|
|
2988
|
-
setTimeout(() => {
|
|
2989
|
-
this.connect();
|
|
2990
|
-
}, delay);
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
_emit(event, data) {
|
|
2994
|
-
if (this._listeners.has(event)) {
|
|
2995
|
-
this._listeners.get(event).forEach((callback) => {
|
|
2996
|
-
try {
|
|
2997
|
-
callback(data);
|
|
2998
|
-
} catch (error) {
|
|
2999
|
-
console.error(`[WebSocket] Error in ${event} handler:`, error);
|
|
3000
|
-
}
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
// Mock support for development
|
|
3006
|
-
_startMockResponses() {
|
|
3007
|
-
// Simulate agent typing and responses
|
|
3008
|
-
this._mockInterval = setInterval(() => {
|
|
3009
|
-
// Randomly emit typing or message events for demo
|
|
3010
|
-
const random = Math.random();
|
|
3011
|
-
if (random < 0.1) {
|
|
3012
|
-
this._emit('typing_started', {
|
|
3013
|
-
conversation_id: 'conv_1',
|
|
3014
|
-
user_id: 'agent_1',
|
|
3015
|
-
user_name: 'Sarah',
|
|
3016
|
-
is_agent: true,
|
|
3017
|
-
});
|
|
3018
|
-
|
|
3019
|
-
// Stop typing after 2 seconds
|
|
3020
|
-
setTimeout(() => {
|
|
3021
|
-
this._emit('typing_stopped', {
|
|
3022
|
-
conversation_id: 'conv_1',
|
|
3023
|
-
user_id: 'agent_1',
|
|
3024
|
-
});
|
|
3025
|
-
}, 2000);
|
|
3026
|
-
}
|
|
3027
|
-
}, 10000);
|
|
3028
|
-
}
|
|
3029
|
-
|
|
3030
|
-
/**
|
|
3031
|
-
* Simulate receiving a message (for mock mode)
|
|
3032
|
-
*/
|
|
3033
|
-
simulateMessage(conversationId, message) {
|
|
3034
|
-
if (this.mock) {
|
|
3035
|
-
this._emit('message', {
|
|
3036
|
-
conversation_id: conversationId,
|
|
3037
|
-
message: {
|
|
3038
|
-
id: 'msg_' + Date.now(),
|
|
3039
|
-
content: message.content,
|
|
3040
|
-
sender_type: message.sender_type || 'agent',
|
|
3041
|
-
sender_name: message.sender_name || 'Support',
|
|
3042
|
-
sender_avatar: message.sender_avatar || null,
|
|
3043
|
-
created_at: new Date().toISOString(),
|
|
3044
|
-
},
|
|
3045
|
-
});
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
/**
|
|
3051
|
-
* MessengerState - State management for the Messenger widget
|
|
3052
|
-
*/
|
|
3053
|
-
class MessengerState {
|
|
3054
|
-
constructor(options = {}) {
|
|
3055
|
-
this.currentView = 'home'; // 'home', 'messages', 'chat', 'help', 'changelog'
|
|
3056
|
-
this.isOpen = false;
|
|
3057
|
-
this.unreadCount = 0;
|
|
3058
|
-
this.activeConversationId = null;
|
|
3059
|
-
|
|
3060
|
-
// Conversations
|
|
3061
|
-
this.conversations = [];
|
|
3062
|
-
this.messages = {}; // { conversationId: [messages] }
|
|
3063
|
-
|
|
3064
|
-
// Help articles
|
|
3065
|
-
this.helpArticles = [];
|
|
3066
|
-
this.helpSearchQuery = '';
|
|
3067
|
-
|
|
3068
|
-
// Changelog
|
|
3069
|
-
this.homeChangelogItems = [];
|
|
3070
|
-
this.changelogItems = [];
|
|
3071
|
-
|
|
3072
|
-
// Team info
|
|
3073
|
-
this.teamName = options.teamName || 'Support';
|
|
3074
|
-
this.teamAvatars = options.teamAvatars || [];
|
|
3075
|
-
this.welcomeMessage = options.welcomeMessage || 'How can we help?';
|
|
3076
|
-
|
|
3077
|
-
// User info
|
|
3078
|
-
this.userContext = options.userContext || null;
|
|
3079
|
-
|
|
3080
|
-
// Feature flags
|
|
3081
|
-
this.enableHelp = options.enableHelp !== false;
|
|
3082
|
-
this.enableChangelog = options.enableChangelog !== false;
|
|
3083
|
-
|
|
3084
|
-
// Agent availability
|
|
3085
|
-
this.agentsOnline = false;
|
|
3086
|
-
this.onlineCount = 0;
|
|
3087
|
-
this.responseTime = 'Usually replies within a few minutes';
|
|
3088
|
-
|
|
3089
|
-
// Typing indicators
|
|
3090
|
-
this.typingUsers = {}; // { conversationId: { userName, timestamp } }
|
|
3091
|
-
|
|
3092
|
-
// Loading states
|
|
3093
|
-
this.isLoading = false;
|
|
3094
|
-
this.isLoadingMessages = false;
|
|
3095
|
-
|
|
3096
|
-
// Listeners
|
|
3097
|
-
this._listeners = new Set();
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
/**
|
|
3101
|
-
* Subscribe to state changes
|
|
3011
|
+
* Subscribe to state changes
|
|
3102
3012
|
*/
|
|
3103
3013
|
subscribe(callback) {
|
|
3104
3014
|
this._listeners.add(callback);
|
|
@@ -3133,8 +3043,9 @@
|
|
|
3133
3043
|
* Set active conversation for chat view
|
|
3134
3044
|
*/
|
|
3135
3045
|
setActiveConversation(conversationId) {
|
|
3046
|
+
const previousConversationId = this.activeConversationId;
|
|
3136
3047
|
this.activeConversationId = conversationId;
|
|
3137
|
-
this._notify('conversationChange', { conversationId });
|
|
3048
|
+
this._notify('conversationChange', { conversationId, previousConversationId });
|
|
3138
3049
|
}
|
|
3139
3050
|
|
|
3140
3051
|
/**
|
|
@@ -3186,6 +3097,20 @@
|
|
|
3186
3097
|
this._notify('messageAdded', { conversationId, message });
|
|
3187
3098
|
}
|
|
3188
3099
|
|
|
3100
|
+
/**
|
|
3101
|
+
* Update a conversation by id
|
|
3102
|
+
*/
|
|
3103
|
+
updateConversation(conversationId, updates) {
|
|
3104
|
+
const conv = this.conversations.find((c) => c.id === conversationId);
|
|
3105
|
+
if (!conv) {
|
|
3106
|
+
return null;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
Object.assign(conv, updates);
|
|
3110
|
+
this._notify('conversationUpdated', { conversationId, conversation: conv });
|
|
3111
|
+
return conv;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3189
3114
|
/**
|
|
3190
3115
|
* Mark conversation as read
|
|
3191
3116
|
*/
|
|
@@ -3255,6 +3180,21 @@
|
|
|
3255
3180
|
return this.messages[this.activeConversationId] || [];
|
|
3256
3181
|
}
|
|
3257
3182
|
|
|
3183
|
+
/**
|
|
3184
|
+
* Update team avatars from backend agent data.
|
|
3185
|
+
* Converts available_agents ({full_name, picture}) into avatar strings
|
|
3186
|
+
* the views already support (URL strings or initial strings).
|
|
3187
|
+
*/
|
|
3188
|
+
setTeamAvatarsFromAgents(agents) {
|
|
3189
|
+
if (!agents || agents.length === 0) return;
|
|
3190
|
+
|
|
3191
|
+
this.teamAvatars = agents.map((agent) => {
|
|
3192
|
+
if (agent.picture) return agent.picture;
|
|
3193
|
+
return agent.full_name || '?';
|
|
3194
|
+
});
|
|
3195
|
+
this._notify('teamAvatarsUpdate', { teamAvatars: this.teamAvatars });
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3258
3198
|
/**
|
|
3259
3199
|
* Get filtered help articles
|
|
3260
3200
|
*/
|
|
@@ -3887,6 +3827,9 @@
|
|
|
3887
3827
|
this._typingTimeout = null;
|
|
3888
3828
|
this._isTyping = false;
|
|
3889
3829
|
this._typingIndicator = null;
|
|
3830
|
+
this._isConversationClosed = false;
|
|
3831
|
+
this._showEmailOverlayFlag = false;
|
|
3832
|
+
this._pendingAttachments = []; // { file, preview, type }
|
|
3890
3833
|
}
|
|
3891
3834
|
|
|
3892
3835
|
render() {
|
|
@@ -3914,10 +3857,12 @@
|
|
|
3914
3857
|
) {
|
|
3915
3858
|
this._hideTypingIndicator();
|
|
3916
3859
|
} else if (
|
|
3917
|
-
type === '
|
|
3860
|
+
type === 'conversationUpdated' &&
|
|
3918
3861
|
data.conversationId === this.state.activeConversationId
|
|
3919
3862
|
) {
|
|
3920
3863
|
this._updateContent();
|
|
3864
|
+
} else if (type === 'messagesUpdate' && data.conversationId === this.state.activeConversationId) {
|
|
3865
|
+
this._updateContent();
|
|
3921
3866
|
}
|
|
3922
3867
|
});
|
|
3923
3868
|
|
|
@@ -3928,6 +3873,8 @@
|
|
|
3928
3873
|
const conversation = this.state.getActiveConversation();
|
|
3929
3874
|
const messages = this.state.getActiveMessages();
|
|
3930
3875
|
const isNewConversation = !this.state.activeConversationId;
|
|
3876
|
+
const isClosed = !isNewConversation && conversation?.status === 'closed';
|
|
3877
|
+
this._isConversationClosed = isClosed;
|
|
3931
3878
|
|
|
3932
3879
|
const messagesHtml =
|
|
3933
3880
|
messages.length === 0
|
|
@@ -3940,7 +3887,11 @@
|
|
|
3940
3887
|
: conversation?.title || 'Chat with team';
|
|
3941
3888
|
const placeholder = isNewConversation
|
|
3942
3889
|
? 'Start typing your message...'
|
|
3943
|
-
:
|
|
3890
|
+
: isClosed
|
|
3891
|
+
? 'Conversation closed'
|
|
3892
|
+
: 'Write a message...';
|
|
3893
|
+
|
|
3894
|
+
const existingName = this.state.userContext?.name || '';
|
|
3944
3895
|
|
|
3945
3896
|
this.element.innerHTML = `
|
|
3946
3897
|
<div class="messenger-chat-header">
|
|
@@ -3962,6 +3913,12 @@
|
|
|
3962
3913
|
|
|
3963
3914
|
<div class="messenger-chat-messages">
|
|
3964
3915
|
${messagesHtml}
|
|
3916
|
+
${isClosed ? `
|
|
3917
|
+
<div class="messenger-closed-banner">
|
|
3918
|
+
<i class="ph ph-check-circle" style="font-size: 18px;"></i>
|
|
3919
|
+
<span>This conversation has been resolved</span>
|
|
3920
|
+
</div>
|
|
3921
|
+
` : ''}
|
|
3965
3922
|
<div class="messenger-typing-indicator" style="display: none;">
|
|
3966
3923
|
<div class="messenger-typing-dots">
|
|
3967
3924
|
<span></span><span></span><span></span>
|
|
@@ -3970,7 +3927,13 @@
|
|
|
3970
3927
|
</div>
|
|
3971
3928
|
</div>
|
|
3972
3929
|
|
|
3930
|
+
${isClosed ? '' : `
|
|
3931
|
+
<div class="messenger-compose-attachments-preview"></div>
|
|
3932
|
+
|
|
3973
3933
|
<div class="messenger-chat-compose">
|
|
3934
|
+
<button class="messenger-compose-attach" aria-label="Attach file">
|
|
3935
|
+
<i class="ph ph-paperclip" style="font-size: 20px;"></i>
|
|
3936
|
+
</button>
|
|
3974
3937
|
<div class="messenger-compose-input-wrapper">
|
|
3975
3938
|
<textarea class="messenger-compose-input" placeholder="${placeholder}" rows="1"></textarea>
|
|
3976
3939
|
</div>
|
|
@@ -3979,6 +3942,21 @@
|
|
|
3979
3942
|
<path d="M227.32,28.68a16,16,0,0,0-15.66-4.08l-.15,0L19.57,82.84a16,16,0,0,0-2.49,29.8L102,154l41.3,84.87A15.86,15.86,0,0,0,157.74,248q.69,0,1.38-.06a15.88,15.88,0,0,0,14-11.51l58.2-191.94c0-.05,0-.1,0-.15A16,16,0,0,0,227.32,28.68ZM157.83,231.85l-.05.14L118.42,148.9l47.24-47.25a8,8,0,0,0-11.31-11.31L107.1,137.58,24,98.22l.14,0L216,40Z"></path>
|
|
3980
3943
|
</svg>
|
|
3981
3944
|
</button>
|
|
3945
|
+
<input type="file" class="messenger-compose-file-input" style="display:none;" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
|
|
3946
|
+
</div>
|
|
3947
|
+
`}
|
|
3948
|
+
|
|
3949
|
+
<div class="messenger-email-overlay" style="display: none;">
|
|
3950
|
+
<div class="messenger-email-card">
|
|
3951
|
+
<h4>What is your email address?</h4>
|
|
3952
|
+
<p>Enter your email to know when we reply:</p>
|
|
3953
|
+
<input type="text" class="messenger-email-name" placeholder="Name (optional)" value="${this._escapeHtml(existingName)}" autocomplete="name" />
|
|
3954
|
+
<input type="email" class="messenger-email-input" placeholder="Enter your email address..." autocomplete="email" />
|
|
3955
|
+
<div class="messenger-email-actions">
|
|
3956
|
+
<button class="messenger-email-submit" disabled>Set my email</button>
|
|
3957
|
+
<button class="messenger-email-skip">Skip</button>
|
|
3958
|
+
</div>
|
|
3959
|
+
</div>
|
|
3982
3960
|
</div>
|
|
3983
3961
|
`;
|
|
3984
3962
|
|
|
@@ -3987,6 +3965,12 @@
|
|
|
3987
3965
|
);
|
|
3988
3966
|
this._attachEvents();
|
|
3989
3967
|
this._scrollToBottom();
|
|
3968
|
+
this._renderAttachmentPreviews();
|
|
3969
|
+
|
|
3970
|
+
// Show email overlay after first message sent without email
|
|
3971
|
+
if (this._showEmailOverlayFlag) {
|
|
3972
|
+
this._showEmailOverlay();
|
|
3973
|
+
}
|
|
3990
3974
|
}
|
|
3991
3975
|
|
|
3992
3976
|
_renderEmptyState(isNewConversation = false) {
|
|
@@ -4008,19 +3992,37 @@
|
|
|
4008
3992
|
`;
|
|
4009
3993
|
}
|
|
4010
3994
|
|
|
3995
|
+
_renderMessageAttachments(attachments) {
|
|
3996
|
+
if (!attachments || attachments.length === 0) return '';
|
|
3997
|
+
return attachments.map((att) => {
|
|
3998
|
+
if (att.type === 'image') {
|
|
3999
|
+
return `<img class="messenger-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
|
|
4000
|
+
}
|
|
4001
|
+
return `<a class="messenger-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
|
|
4002
|
+
<i class="ph ph-file" style="font-size:16px;"></i>
|
|
4003
|
+
<span>${this._escapeHtml(att.name || 'file')}</span>
|
|
4004
|
+
<i class="ph ph-download-simple messenger-file-download-icon" style="font-size:14px;"></i>
|
|
4005
|
+
</a>`;
|
|
4006
|
+
}).join('');
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4011
4009
|
_renderMessage(message) {
|
|
4012
4010
|
const isOwn = message.isOwn;
|
|
4013
4011
|
const messageClass = isOwn
|
|
4014
4012
|
? 'messenger-message-own'
|
|
4015
4013
|
: 'messenger-message-received';
|
|
4016
4014
|
const timeStr = this._formatMessageTime(message.timestamp);
|
|
4015
|
+
const attachmentsHtml = this._renderMessageAttachments(message.attachments);
|
|
4016
|
+
|
|
4017
|
+
const contentHtml = message.content ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>` : '';
|
|
4018
|
+
|
|
4019
|
+
const bubbleHtml = contentHtml ? `<div class="messenger-message-bubble">${contentHtml}</div>` : '';
|
|
4017
4020
|
|
|
4018
4021
|
if (isOwn) {
|
|
4019
4022
|
return `
|
|
4020
4023
|
<div class="messenger-message ${messageClass}">
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
</div>
|
|
4024
|
+
${bubbleHtml}
|
|
4025
|
+
${attachmentsHtml}
|
|
4024
4026
|
<div class="messenger-message-time">${timeStr}</div>
|
|
4025
4027
|
</div>
|
|
4026
4028
|
`;
|
|
@@ -4032,9 +4034,8 @@
|
|
|
4032
4034
|
<div class="messenger-message-avatar">${avatarHtml}</div>
|
|
4033
4035
|
<div class="messenger-message-wrapper">
|
|
4034
4036
|
<div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
</div>
|
|
4037
|
+
${bubbleHtml}
|
|
4038
|
+
${attachmentsHtml}
|
|
4038
4039
|
<div class="messenger-message-time">${timeStr}</div>
|
|
4039
4040
|
</div>
|
|
4040
4041
|
</div>
|
|
@@ -4130,6 +4131,49 @@
|
|
|
4130
4131
|
}
|
|
4131
4132
|
}
|
|
4132
4133
|
|
|
4134
|
+
_updateSendButtonState() {
|
|
4135
|
+
const input = this.element.querySelector('.messenger-compose-input');
|
|
4136
|
+
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
4137
|
+
if (input && sendBtn) {
|
|
4138
|
+
sendBtn.disabled = !input.value.trim() && this._pendingAttachments.length === 0;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
_renderAttachmentPreviews() {
|
|
4143
|
+
const container = this.element.querySelector('.messenger-compose-attachments-preview');
|
|
4144
|
+
if (!container) return;
|
|
4145
|
+
|
|
4146
|
+
if (this._pendingAttachments.length === 0) {
|
|
4147
|
+
container.innerHTML = '';
|
|
4148
|
+
container.style.display = 'none';
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
container.style.display = 'flex';
|
|
4153
|
+
container.innerHTML = this._pendingAttachments.map((att, i) => {
|
|
4154
|
+
const isImage = att.type.startsWith('image');
|
|
4155
|
+
const thumb = isImage
|
|
4156
|
+
? `<img class="messenger-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
|
|
4157
|
+
: `<div class="messenger-attachment-thumb messenger-attachment-file-icon"><i class="ph ph-file" style="font-size:20px;"></i></div>`;
|
|
4158
|
+
return `
|
|
4159
|
+
<div class="messenger-attachment-preview" data-index="${i}">
|
|
4160
|
+
${thumb}
|
|
4161
|
+
<button class="messenger-attachment-remove" data-index="${i}" aria-label="Remove">×</button>
|
|
4162
|
+
</div>
|
|
4163
|
+
`;
|
|
4164
|
+
}).join('');
|
|
4165
|
+
|
|
4166
|
+
// Attach remove button events
|
|
4167
|
+
container.querySelectorAll('.messenger-attachment-remove').forEach((btn) => {
|
|
4168
|
+
btn.addEventListener('click', (e) => {
|
|
4169
|
+
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
4170
|
+
this._pendingAttachments.splice(idx, 1);
|
|
4171
|
+
this._renderAttachmentPreviews();
|
|
4172
|
+
this._updateSendButtonState();
|
|
4173
|
+
});
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4133
4177
|
_attachEvents() {
|
|
4134
4178
|
this.element
|
|
4135
4179
|
.querySelector('.messenger-back-btn')
|
|
@@ -4143,45 +4187,250 @@
|
|
|
4143
4187
|
this.state.setOpen(false);
|
|
4144
4188
|
});
|
|
4145
4189
|
|
|
4190
|
+
// Compose input (not rendered when conversation is closed)
|
|
4146
4191
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
4147
4192
|
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
4148
4193
|
|
|
4149
|
-
|
|
4150
|
-
input.
|
|
4151
|
-
|
|
4194
|
+
if (input && sendBtn) {
|
|
4195
|
+
input.addEventListener('input', () => {
|
|
4196
|
+
// Auto-resize textarea
|
|
4197
|
+
input.style.height = 'auto';
|
|
4198
|
+
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
|
|
4152
4199
|
|
|
4153
|
-
|
|
4200
|
+
// Enable/disable send button
|
|
4201
|
+
this._updateSendButtonState();
|
|
4154
4202
|
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4203
|
+
// Send typing indicator
|
|
4204
|
+
if (input.value.trim()) {
|
|
4205
|
+
this._startTyping();
|
|
4206
|
+
}
|
|
4207
|
+
});
|
|
4208
|
+
|
|
4209
|
+
input.addEventListener('keydown', (e) => {
|
|
4210
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
4211
|
+
e.preventDefault();
|
|
4212
|
+
this._sendMessage();
|
|
4213
|
+
}
|
|
4214
|
+
});
|
|
4159
4215
|
|
|
4160
|
-
|
|
4161
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
4162
|
-
e.preventDefault();
|
|
4216
|
+
sendBtn.addEventListener('click', () => {
|
|
4163
4217
|
this._sendMessage();
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
// Attach button + file input
|
|
4222
|
+
const attachBtn = this.element.querySelector('.messenger-compose-attach');
|
|
4223
|
+
const fileInput = this.element.querySelector('.messenger-compose-file-input');
|
|
4224
|
+
|
|
4225
|
+
if (attachBtn && fileInput) {
|
|
4226
|
+
attachBtn.addEventListener('click', () => {
|
|
4227
|
+
fileInput.click();
|
|
4228
|
+
});
|
|
4229
|
+
|
|
4230
|
+
fileInput.addEventListener('change', (e) => {
|
|
4231
|
+
const files = e.target.files;
|
|
4232
|
+
if (!files) return;
|
|
4233
|
+
Array.from(files).forEach((file) => {
|
|
4234
|
+
const reader = new FileReader();
|
|
4235
|
+
reader.onload = (ev) => {
|
|
4236
|
+
this._pendingAttachments.push({
|
|
4237
|
+
file,
|
|
4238
|
+
preview: ev.target.result,
|
|
4239
|
+
type: file.type,
|
|
4240
|
+
});
|
|
4241
|
+
this._renderAttachmentPreviews();
|
|
4242
|
+
this._updateSendButtonState();
|
|
4243
|
+
};
|
|
4244
|
+
reader.readAsDataURL(file);
|
|
4245
|
+
});
|
|
4246
|
+
fileInput.value = '';
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
// Email overlay events
|
|
4251
|
+
const emailInput = this.element.querySelector('.messenger-email-input');
|
|
4252
|
+
const emailSubmit = this.element.querySelector('.messenger-email-submit');
|
|
4253
|
+
const emailSkip = this.element.querySelector('.messenger-email-skip');
|
|
4254
|
+
|
|
4255
|
+
if (emailInput) {
|
|
4256
|
+
emailInput.addEventListener('input', () => {
|
|
4257
|
+
const isValid = this._isValidEmail(emailInput.value.trim());
|
|
4258
|
+
emailSubmit.disabled = !isValid;
|
|
4259
|
+
});
|
|
4260
|
+
|
|
4261
|
+
emailInput.addEventListener('keydown', (e) => {
|
|
4262
|
+
if (e.key === 'Enter' && !emailSubmit.disabled) {
|
|
4263
|
+
e.preventDefault();
|
|
4264
|
+
this._handleEmailSubmit();
|
|
4265
|
+
}
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
if (emailSubmit) {
|
|
4270
|
+
emailSubmit.addEventListener('click', () => {
|
|
4271
|
+
this._handleEmailSubmit();
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
if (emailSkip) {
|
|
4276
|
+
emailSkip.addEventListener('click', () => {
|
|
4277
|
+
this._hideEmailOverlay();
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
// Delegated events for attachment clicks
|
|
4282
|
+
const messagesContainer = this.element.querySelector('.messenger-chat-messages');
|
|
4283
|
+
if (messagesContainer) {
|
|
4284
|
+
messagesContainer.addEventListener('click', (e) => {
|
|
4285
|
+
// File click -> download
|
|
4286
|
+
const fileLink = e.target.closest('.messenger-message-file');
|
|
4287
|
+
if (fileLink) {
|
|
4288
|
+
e.preventDefault();
|
|
4289
|
+
const url = fileLink.dataset.url;
|
|
4290
|
+
const name = fileLink.dataset.name;
|
|
4291
|
+
this._downloadFile(url, name);
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
// Image click -> open in new tab
|
|
4296
|
+
const img = e.target.closest('.messenger-message-image');
|
|
4297
|
+
if (img) {
|
|
4298
|
+
const url = img.dataset.url || img.src;
|
|
4299
|
+
window.open(url, '_blank');
|
|
4300
|
+
}
|
|
4301
|
+
});
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
async _downloadFile(url, name) {
|
|
4306
|
+
try {
|
|
4307
|
+
const response = await fetch(url);
|
|
4308
|
+
const blob = await response.blob();
|
|
4309
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
4310
|
+
const a = document.createElement('a');
|
|
4311
|
+
a.href = blobUrl;
|
|
4312
|
+
a.download = name || 'download';
|
|
4313
|
+
a.style.display = 'none';
|
|
4314
|
+
document.body.appendChild(a);
|
|
4315
|
+
a.click();
|
|
4316
|
+
document.body.removeChild(a);
|
|
4317
|
+
URL.revokeObjectURL(blobUrl);
|
|
4318
|
+
} catch {
|
|
4319
|
+
// Fallback: open in new tab
|
|
4320
|
+
window.open(url, '_blank');
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
_escapeHtml(text) {
|
|
4325
|
+
if (!text) return '';
|
|
4326
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
_isValidEmail(email) {
|
|
4330
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
_showEmailOverlay() {
|
|
4334
|
+
const overlay = this.element.querySelector('.messenger-email-overlay');
|
|
4335
|
+
if (overlay) {
|
|
4336
|
+
overlay.style.display = 'flex';
|
|
4337
|
+
const emailInput = overlay.querySelector('.messenger-email-input');
|
|
4338
|
+
if (emailInput) {
|
|
4339
|
+
setTimeout(() => emailInput.focus(), 100);
|
|
4164
4340
|
}
|
|
4165
|
-
}
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4166
4343
|
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4344
|
+
_startPendingConversation() {
|
|
4345
|
+
if (this._pendingMessage && this.options.onStartConversation) {
|
|
4346
|
+
this.options.onStartConversation(this._pendingMessage, this._pendingAttachmentsForSend || []);
|
|
4347
|
+
this._pendingMessage = null;
|
|
4348
|
+
this._pendingAttachmentsForSend = null;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
_hideEmailOverlay() {
|
|
4353
|
+
this._showEmailOverlayFlag = false;
|
|
4354
|
+
const overlay = this.element.querySelector('.messenger-email-overlay');
|
|
4355
|
+
if (overlay) {
|
|
4356
|
+
overlay.style.display = 'none';
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
async _handleEmailSubmit() {
|
|
4361
|
+
const nameInput = this.element.querySelector('.messenger-email-name');
|
|
4362
|
+
const emailInput = this.element.querySelector('.messenger-email-input');
|
|
4363
|
+
const submitBtn = this.element.querySelector('.messenger-email-submit');
|
|
4364
|
+
|
|
4365
|
+
const name = nameInput?.value.trim() || '';
|
|
4366
|
+
const email = emailInput?.value.trim();
|
|
4367
|
+
|
|
4368
|
+
if (!email || !this._isValidEmail(email)) return;
|
|
4369
|
+
|
|
4370
|
+
submitBtn.disabled = true;
|
|
4371
|
+
submitBtn.textContent = 'Saving...';
|
|
4372
|
+
|
|
4373
|
+
try {
|
|
4374
|
+
if (this.options.onIdentifyContact) {
|
|
4375
|
+
await this.options.onIdentifyContact({ name, email });
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
if (!this.state.userContext) {
|
|
4379
|
+
this.state.userContext = {};
|
|
4380
|
+
}
|
|
4381
|
+
this.state.userContext.name = name;
|
|
4382
|
+
this.state.userContext.email = email;
|
|
4383
|
+
|
|
4384
|
+
this._hideEmailOverlay();
|
|
4385
|
+
this._startPendingConversation();
|
|
4386
|
+
} catch (error) {
|
|
4387
|
+
console.error('[ChatView] Failed to save email:', error);
|
|
4388
|
+
submitBtn.disabled = false;
|
|
4389
|
+
submitBtn.textContent = 'Set my email';
|
|
4390
|
+
}
|
|
4170
4391
|
}
|
|
4171
4392
|
|
|
4172
|
-
_sendMessage() {
|
|
4393
|
+
async _sendMessage() {
|
|
4394
|
+
if (this._isConversationClosed) return;
|
|
4173
4395
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
4174
4396
|
const content = input.value.trim();
|
|
4397
|
+
const hasAttachments = this._pendingAttachments.length > 0;
|
|
4175
4398
|
|
|
4176
|
-
if (!content) return;
|
|
4399
|
+
if (!content && !hasAttachments) return;
|
|
4177
4400
|
|
|
4178
4401
|
this._stopTyping();
|
|
4179
4402
|
|
|
4403
|
+
// Collect attachments to upload
|
|
4404
|
+
const attachmentsToSend = [...this._pendingAttachments];
|
|
4405
|
+
|
|
4180
4406
|
const isNewConversation = !this.state.activeConversationId;
|
|
4407
|
+
const needsContactInfo = !this.state.userContext?.email;
|
|
4181
4408
|
|
|
4182
4409
|
if (isNewConversation) {
|
|
4183
|
-
|
|
4184
|
-
|
|
4410
|
+
// Show user's message in chat immediately
|
|
4411
|
+
const localMessage = {
|
|
4412
|
+
id: 'msg_' + Date.now(),
|
|
4413
|
+
content: content,
|
|
4414
|
+
isOwn: true,
|
|
4415
|
+
timestamp: new Date().toISOString(),
|
|
4416
|
+
attachments: attachmentsToSend.map((a) => ({
|
|
4417
|
+
url: a.preview,
|
|
4418
|
+
type: a.type.startsWith('image') ? 'image' : 'file',
|
|
4419
|
+
name: a.file.name,
|
|
4420
|
+
})),
|
|
4421
|
+
};
|
|
4422
|
+
this._appendMessage(localMessage);
|
|
4423
|
+
this._scrollToBottom();
|
|
4424
|
+
|
|
4425
|
+
if (needsContactInfo) {
|
|
4426
|
+
this._pendingMessage = content;
|
|
4427
|
+
this._pendingAttachmentsForSend = attachmentsToSend;
|
|
4428
|
+
this._showEmailOverlayFlag = true;
|
|
4429
|
+
setTimeout(() => this._showEmailOverlay(), 300);
|
|
4430
|
+
} else {
|
|
4431
|
+
if (this.options.onStartConversation) {
|
|
4432
|
+
this.options.onStartConversation(content, attachmentsToSend);
|
|
4433
|
+
}
|
|
4185
4434
|
}
|
|
4186
4435
|
} else {
|
|
4187
4436
|
const message = {
|
|
@@ -4189,21 +4438,30 @@
|
|
|
4189
4438
|
content: content,
|
|
4190
4439
|
isOwn: true,
|
|
4191
4440
|
timestamp: new Date().toISOString(),
|
|
4441
|
+
attachments: attachmentsToSend.map((a) => ({
|
|
4442
|
+
url: a.preview,
|
|
4443
|
+
type: a.type.startsWith('image') ? 'image' : 'file',
|
|
4444
|
+
name: a.file.name,
|
|
4445
|
+
})),
|
|
4192
4446
|
};
|
|
4193
4447
|
|
|
4194
4448
|
this.state.addMessage(this.state.activeConversationId, message);
|
|
4195
4449
|
|
|
4196
4450
|
if (this.options.onSendMessage) {
|
|
4197
|
-
this.options.onSendMessage(this.state.activeConversationId, message);
|
|
4451
|
+
this.options.onSendMessage(this.state.activeConversationId, message, attachmentsToSend);
|
|
4198
4452
|
}
|
|
4199
4453
|
}
|
|
4200
4454
|
|
|
4455
|
+
// Clear input and attachments
|
|
4201
4456
|
input.value = '';
|
|
4202
4457
|
input.style.height = 'auto';
|
|
4203
|
-
this.
|
|
4458
|
+
this._pendingAttachments = [];
|
|
4459
|
+
this._renderAttachmentPreviews();
|
|
4460
|
+
this._updateSendButtonState();
|
|
4204
4461
|
}
|
|
4205
4462
|
|
|
4206
4463
|
_startTyping() {
|
|
4464
|
+
if (this._isConversationClosed) return;
|
|
4207
4465
|
if (!this._isTyping && this.state.activeConversationId) {
|
|
4208
4466
|
this._isTyping = true;
|
|
4209
4467
|
if (this.options.onTyping) {
|
|
@@ -4284,7 +4542,8 @@
|
|
|
4284
4542
|
if (
|
|
4285
4543
|
type === 'conversationsUpdate' ||
|
|
4286
4544
|
type === 'conversationAdded' ||
|
|
4287
|
-
type === 'conversationRead'
|
|
4545
|
+
type === 'conversationRead' ||
|
|
4546
|
+
type === 'conversationUpdated'
|
|
4288
4547
|
) {
|
|
4289
4548
|
this._updateContent();
|
|
4290
4549
|
}
|
|
@@ -4468,11 +4727,25 @@
|
|
|
4468
4727
|
}
|
|
4469
4728
|
|
|
4470
4729
|
_startNewConversation() {
|
|
4471
|
-
|
|
4472
|
-
this.state.
|
|
4730
|
+
// If there's an open conversation, route to it instead of creating new
|
|
4731
|
+
const openConversation = this.state.conversations.find(
|
|
4732
|
+
(c) => c.status === 'open'
|
|
4733
|
+
);
|
|
4473
4734
|
|
|
4474
|
-
if (
|
|
4475
|
-
this.
|
|
4735
|
+
if (openConversation) {
|
|
4736
|
+
this.state.setActiveConversation(openConversation.id);
|
|
4737
|
+
this.state.markAsRead(openConversation.id);
|
|
4738
|
+
this.state.setView('chat');
|
|
4739
|
+
if (this.options.onSelectConversation) {
|
|
4740
|
+
this.options.onSelectConversation(openConversation.id);
|
|
4741
|
+
}
|
|
4742
|
+
} else {
|
|
4743
|
+
this.state.setActiveConversation(null);
|
|
4744
|
+
if (this.options.onStartNewConversation) {
|
|
4745
|
+
this.options.onStartNewConversation();
|
|
4746
|
+
} else {
|
|
4747
|
+
this.state.setView('chat');
|
|
4748
|
+
}
|
|
4476
4749
|
}
|
|
4477
4750
|
}
|
|
4478
4751
|
|
|
@@ -4731,12 +5004,7 @@
|
|
|
4731
5004
|
</div>
|
|
4732
5005
|
|
|
4733
5006
|
<div class="messenger-home-body">
|
|
4734
|
-
|
|
4735
|
-
<span>Send us a message</span>
|
|
4736
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
|
|
4737
|
-
<path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path>
|
|
4738
|
-
</svg>
|
|
4739
|
-
</button>
|
|
5007
|
+
${this._renderMessageButton()}
|
|
4740
5008
|
|
|
4741
5009
|
${this._renderFeaturedCard()}
|
|
4742
5010
|
|
|
@@ -4776,156 +5044,698 @@
|
|
|
4776
5044
|
return colors[index % colors.length];
|
|
4777
5045
|
}
|
|
4778
5046
|
|
|
4779
|
-
_renderAvailabilityStatus() {
|
|
4780
|
-
const isOnline = this.state.agentsOnline;
|
|
4781
|
-
const responseTime =
|
|
4782
|
-
this.state.responseTime || 'We typically reply within a few minutes';
|
|
5047
|
+
_renderAvailabilityStatus() {
|
|
5048
|
+
const isOnline = this.state.agentsOnline;
|
|
5049
|
+
const responseTime =
|
|
5050
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
5051
|
+
|
|
5052
|
+
if (isOnline) {
|
|
5053
|
+
return `
|
|
5054
|
+
<div class="messenger-home-availability">
|
|
5055
|
+
<span class="messenger-availability-dot messenger-availability-online"></span>
|
|
5056
|
+
<span class="messenger-availability-text">We're online now</span>
|
|
5057
|
+
</div>
|
|
5058
|
+
`;
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5061
|
+
return `
|
|
5062
|
+
<div class="messenger-home-availability">
|
|
5063
|
+
<span class="messenger-availability-dot messenger-availability-away"></span>
|
|
5064
|
+
<span class="messenger-availability-text">${responseTime}</span>
|
|
5065
|
+
</div>
|
|
5066
|
+
`;
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5069
|
+
_renderMessageButton() {
|
|
5070
|
+
const openConversation = this.state.conversations.find(
|
|
5071
|
+
(c) => c.status === 'open'
|
|
5072
|
+
);
|
|
5073
|
+
|
|
5074
|
+
if (openConversation) {
|
|
5075
|
+
const preview = openConversation.lastMessage
|
|
5076
|
+
? (openConversation.lastMessage.length > 40
|
|
5077
|
+
? openConversation.lastMessage.substring(0, 40) + '...'
|
|
5078
|
+
: openConversation.lastMessage)
|
|
5079
|
+
: 'Continue your conversation';
|
|
5080
|
+
|
|
5081
|
+
return `
|
|
5082
|
+
<button class="messenger-home-message-btn messenger-home-continue-btn" data-conversation-id="${openConversation.id}">
|
|
5083
|
+
<div class="messenger-home-continue-info">
|
|
5084
|
+
<span class="messenger-home-continue-label">Continue conversation</span>
|
|
5085
|
+
<span class="messenger-home-continue-preview">${preview}</span>
|
|
5086
|
+
</div>
|
|
5087
|
+
<i class="ph ph-arrow-right" style="font-size: 16px;"></i>
|
|
5088
|
+
</button>
|
|
5089
|
+
`;
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
return `
|
|
5093
|
+
<button class="messenger-home-message-btn">
|
|
5094
|
+
<span>Send us a message</span>
|
|
5095
|
+
<i class="ph ph-arrow-right" style="font-size: 16px;"></i>
|
|
5096
|
+
</button>
|
|
5097
|
+
`;
|
|
5098
|
+
}
|
|
5099
|
+
|
|
5100
|
+
_renderFeaturedCard() {
|
|
5101
|
+
if (!this.options.featuredContent) {
|
|
5102
|
+
return '';
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5105
|
+
const { title, description, imageUrl, action } =
|
|
5106
|
+
this.options.featuredContent;
|
|
5107
|
+
|
|
5108
|
+
return `
|
|
5109
|
+
<div class="messenger-home-featured">
|
|
5110
|
+
${imageUrl ? `<img src="${imageUrl}" alt="${title}" class="messenger-home-featured-image" onerror="this.style.display='none';" />` : ''}
|
|
5111
|
+
<div class="messenger-home-featured-content">
|
|
5112
|
+
<h3>${title}</h3>
|
|
5113
|
+
<p>${description}</p>
|
|
5114
|
+
</div>
|
|
5115
|
+
${action ? `<button class="messenger-home-featured-btn" data-action="${action.type}" data-value="${action.value}">${action.label}</button>` : ''}
|
|
5116
|
+
</div>
|
|
5117
|
+
`;
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
_renderRecentChangelog() {
|
|
5121
|
+
const changelogItems = this.state.homeChangelogItems;
|
|
5122
|
+
if (changelogItems.length === 0) {
|
|
5123
|
+
return '';
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
const changelogHtml = changelogItems
|
|
5127
|
+
.map(
|
|
5128
|
+
(item) => `
|
|
5129
|
+
<div class="messenger-home-changelog-card" data-changelog-id="${item.id}">
|
|
5130
|
+
${
|
|
5131
|
+
item.coverImage
|
|
5132
|
+
? `
|
|
5133
|
+
<div class="messenger-home-changelog-cover">
|
|
5134
|
+
<img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
|
|
5135
|
+
${item.coverText ? `<span class="messenger-home-changelog-cover-text">${item.coverText}</span>` : ''}
|
|
5136
|
+
</div>
|
|
5137
|
+
`
|
|
5138
|
+
: ''
|
|
5139
|
+
}
|
|
5140
|
+
<div class="messenger-home-changelog-card-content">
|
|
5141
|
+
<h4 class="messenger-home-changelog-card-title">${item.title}</h4>
|
|
5142
|
+
<p class="messenger-home-changelog-card-desc">${item.description || ''}</p>
|
|
5143
|
+
</div>
|
|
5144
|
+
</div>
|
|
5145
|
+
`
|
|
5146
|
+
)
|
|
5147
|
+
.join('');
|
|
5148
|
+
|
|
5149
|
+
return `
|
|
5150
|
+
<div class="messenger-home-changelog-section">
|
|
5151
|
+
${changelogHtml}
|
|
5152
|
+
</div>
|
|
5153
|
+
`;
|
|
5154
|
+
}
|
|
5155
|
+
|
|
5156
|
+
_formatDate(dateString) {
|
|
5157
|
+
if (!dateString) return '';
|
|
5158
|
+
const date = new Date(dateString);
|
|
5159
|
+
const now = new Date();
|
|
5160
|
+
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
5161
|
+
|
|
5162
|
+
if (diffDays === 0) return 'Today';
|
|
5163
|
+
if (diffDays === 1) return 'Yesterday';
|
|
5164
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5165
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
_attachEvents() {
|
|
5169
|
+
this.element
|
|
5170
|
+
.querySelector('.messenger-close-btn')
|
|
5171
|
+
.addEventListener('click', () => {
|
|
5172
|
+
this.state.setOpen(false);
|
|
5173
|
+
});
|
|
5174
|
+
|
|
5175
|
+
// Send message / continue conversation button
|
|
5176
|
+
const msgBtn = this.element.querySelector('.messenger-home-message-btn');
|
|
5177
|
+
if (msgBtn) {
|
|
5178
|
+
msgBtn.addEventListener('click', () => {
|
|
5179
|
+
const convId = msgBtn.dataset.conversationId;
|
|
5180
|
+
if (convId) {
|
|
5181
|
+
// Continue existing open conversation
|
|
5182
|
+
this.state.setActiveConversation(convId);
|
|
5183
|
+
this.state.setView('chat');
|
|
5184
|
+
if (this.options.onSelectConversation) {
|
|
5185
|
+
this.options.onSelectConversation(convId);
|
|
5186
|
+
}
|
|
5187
|
+
} else {
|
|
5188
|
+
// No open conversation — start new
|
|
5189
|
+
this.state.setActiveConversation(null);
|
|
5190
|
+
this.state.setView('chat');
|
|
5191
|
+
}
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
this.element
|
|
5196
|
+
.querySelectorAll('.messenger-home-changelog-item')
|
|
5197
|
+
.forEach((item) => {
|
|
5198
|
+
item.addEventListener('click', () => {
|
|
5199
|
+
this.state.setView('changelog');
|
|
5200
|
+
});
|
|
5201
|
+
});
|
|
5202
|
+
|
|
5203
|
+
const seeAllBtn = this.element.querySelector(
|
|
5204
|
+
'.messenger-home-changelog-all'
|
|
5205
|
+
);
|
|
5206
|
+
if (seeAllBtn) {
|
|
5207
|
+
seeAllBtn.addEventListener('click', () => {
|
|
5208
|
+
this.state.setView('changelog');
|
|
5209
|
+
});
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5212
|
+
const featuredBtn = this.element.querySelector(
|
|
5213
|
+
'.messenger-home-featured-btn'
|
|
5214
|
+
);
|
|
5215
|
+
if (featuredBtn) {
|
|
5216
|
+
featuredBtn.addEventListener('click', () => {
|
|
5217
|
+
const action = featuredBtn.dataset.action;
|
|
5218
|
+
const value = featuredBtn.dataset.value;
|
|
5219
|
+
if (action === 'url') {
|
|
5220
|
+
window.open(value, '_blank');
|
|
5221
|
+
} else if (action === 'view') {
|
|
5222
|
+
this.state.setView(value);
|
|
5223
|
+
}
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
destroy() {
|
|
5229
|
+
if (this._unsubscribe) {
|
|
5230
|
+
this._unsubscribe();
|
|
5231
|
+
}
|
|
5232
|
+
if (this.element && this.element.parentNode) {
|
|
5233
|
+
this.element.parentNode.removeChild(this.element);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
/**
|
|
5239
|
+
* PreChatFormView - Collects user info after the first message
|
|
5240
|
+
*/
|
|
5241
|
+
class PreChatFormView {
|
|
5242
|
+
constructor(state, options = {}) {
|
|
5243
|
+
this.state = state;
|
|
5244
|
+
this.options = options;
|
|
5245
|
+
this.element = null;
|
|
5246
|
+
this._isSubmitting = false;
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
render() {
|
|
5250
|
+
this.element = document.createElement('div');
|
|
5251
|
+
this.element.className = 'messenger-view messenger-prechat-view';
|
|
5252
|
+
|
|
5253
|
+
this._updateContent();
|
|
5254
|
+
|
|
5255
|
+
return this.element;
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5258
|
+
_updateContent() {
|
|
5259
|
+
// Pre-fill from userContext if available
|
|
5260
|
+
const existingName = this.state.userContext?.name || '';
|
|
5261
|
+
const existingEmail = this.state.userContext?.email || '';
|
|
5262
|
+
|
|
5263
|
+
this.element.innerHTML = `
|
|
5264
|
+
<div class="messenger-prechat-overlay">
|
|
5265
|
+
<div class="messenger-prechat-card">
|
|
5266
|
+
<h4>Get notified when we reply</h4>
|
|
5267
|
+
<form class="messenger-prechat-form" novalidate>
|
|
5268
|
+
<div class="messenger-prechat-fields">
|
|
5269
|
+
<input
|
|
5270
|
+
type="text"
|
|
5271
|
+
id="messenger-prechat-name"
|
|
5272
|
+
name="name"
|
|
5273
|
+
placeholder="Name (optional)"
|
|
5274
|
+
value="${this._escapeHtml(existingName)}"
|
|
5275
|
+
autocomplete="name"
|
|
5276
|
+
class="messenger-prechat-input"
|
|
5277
|
+
/>
|
|
5278
|
+
<input
|
|
5279
|
+
type="email"
|
|
5280
|
+
id="messenger-prechat-email"
|
|
5281
|
+
name="email"
|
|
5282
|
+
placeholder="Email address"
|
|
5283
|
+
value="${this._escapeHtml(existingEmail)}"
|
|
5284
|
+
required
|
|
5285
|
+
autocomplete="email"
|
|
5286
|
+
class="messenger-prechat-input"
|
|
5287
|
+
/>
|
|
5288
|
+
</div>
|
|
5289
|
+
<span class="messenger-prechat-error" id="messenger-email-error"></span>
|
|
5290
|
+
<div class="messenger-prechat-actions">
|
|
5291
|
+
<button type="button" class="messenger-prechat-skip">Skip</button>
|
|
5292
|
+
<button type="submit" class="messenger-prechat-submit" disabled>
|
|
5293
|
+
<span class="messenger-prechat-submit-text">Continue</span>
|
|
5294
|
+
<span class="messenger-prechat-submit-loading" style="display: none;">
|
|
5295
|
+
<i class="ph ph-spinner" style="font-size: 16px;"></i>
|
|
5296
|
+
</span>
|
|
5297
|
+
</button>
|
|
5298
|
+
</div>
|
|
5299
|
+
</form>
|
|
5300
|
+
</div>
|
|
5301
|
+
</div>
|
|
5302
|
+
`;
|
|
5303
|
+
|
|
5304
|
+
this._attachEvents();
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5307
|
+
_renderTeamAvatars() {
|
|
5308
|
+
const avatars = this.state.teamAvatars;
|
|
5309
|
+
if (!avatars || avatars.length === 0) {
|
|
5310
|
+
return `
|
|
5311
|
+
<div class="messenger-avatar-stack">
|
|
5312
|
+
<div class="messenger-avatar" style="background: #5856d6;">S</div>
|
|
5313
|
+
<div class="messenger-avatar" style="background: #007aff;">T</div>
|
|
5314
|
+
</div>
|
|
5315
|
+
`;
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5318
|
+
const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500', '#ff3b30'];
|
|
5319
|
+
const avatarItems = avatars
|
|
5320
|
+
.slice(0, 3)
|
|
5321
|
+
.map((avatar, i) => {
|
|
5322
|
+
if (typeof avatar === 'string' && avatar.startsWith('http')) {
|
|
5323
|
+
return `<img class="messenger-avatar" src="${avatar}" alt="Team member" style="z-index: ${3 - i};" />`;
|
|
5324
|
+
}
|
|
5325
|
+
return `<div class="messenger-avatar" style="background: ${colors[i % colors.length]}; z-index: ${3 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
|
|
5326
|
+
})
|
|
5327
|
+
.join('');
|
|
5328
|
+
|
|
5329
|
+
return `<div class="messenger-avatar-stack">${avatarItems}</div>`;
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
_escapeHtml(text) {
|
|
5333
|
+
if (!text) return '';
|
|
5334
|
+
return text
|
|
5335
|
+
.replace(/&/g, '&')
|
|
5336
|
+
.replace(/</g, '<')
|
|
5337
|
+
.replace(/>/g, '>')
|
|
5338
|
+
.replace(/"/g, '"');
|
|
5339
|
+
}
|
|
5340
|
+
|
|
5341
|
+
_attachEvents() {
|
|
5342
|
+
// Form validation
|
|
5343
|
+
const form = this.element.querySelector('.messenger-prechat-form');
|
|
5344
|
+
const emailInput = this.element.querySelector('#messenger-prechat-email');
|
|
5345
|
+
const submitBtn = this.element.querySelector('.messenger-prechat-submit');
|
|
5346
|
+
const skipBtn = this.element.querySelector('.messenger-prechat-skip');
|
|
5347
|
+
|
|
5348
|
+
const validateForm = () => {
|
|
5349
|
+
const email = emailInput.value.trim();
|
|
5350
|
+
const isEmailValid = this._isValidEmail(email);
|
|
5351
|
+
submitBtn.disabled = !isEmailValid;
|
|
5352
|
+
return isEmailValid;
|
|
5353
|
+
};
|
|
5354
|
+
|
|
5355
|
+
emailInput.addEventListener('input', () => {
|
|
5356
|
+
this._clearError('messenger-email-error');
|
|
5357
|
+
validateForm();
|
|
5358
|
+
});
|
|
5359
|
+
|
|
5360
|
+
emailInput.addEventListener('blur', () => {
|
|
5361
|
+
const email = emailInput.value.trim();
|
|
5362
|
+
if (email && !this._isValidEmail(email)) {
|
|
5363
|
+
this._showError('messenger-email-error', 'Please enter a valid email');
|
|
5364
|
+
}
|
|
5365
|
+
});
|
|
5366
|
+
|
|
5367
|
+
// Skip button - go back to chat without collecting info
|
|
5368
|
+
skipBtn.addEventListener('click', () => {
|
|
5369
|
+
this.state.setView('chat');
|
|
5370
|
+
});
|
|
5371
|
+
|
|
5372
|
+
// Form submission
|
|
5373
|
+
form.addEventListener('submit', async (e) => {
|
|
5374
|
+
e.preventDefault();
|
|
5375
|
+
if (this._isSubmitting) return;
|
|
5376
|
+
if (!validateForm()) {
|
|
5377
|
+
this._showError('messenger-email-error', 'Please enter a valid email');
|
|
5378
|
+
return;
|
|
5379
|
+
}
|
|
5380
|
+
await this._handleSubmit();
|
|
5381
|
+
});
|
|
5382
|
+
|
|
5383
|
+
// Set initial button state
|
|
5384
|
+
validateForm();
|
|
5385
|
+
|
|
5386
|
+
// Focus email input
|
|
5387
|
+
setTimeout(() => emailInput.focus(), 100);
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
_isValidEmail(email) {
|
|
5391
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5392
|
+
return emailRegex.test(email);
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
_showError(elementId, message) {
|
|
5396
|
+
const errorEl = this.element.querySelector(`#${elementId}`);
|
|
5397
|
+
if (errorEl) {
|
|
5398
|
+
errorEl.textContent = message;
|
|
5399
|
+
errorEl.style.display = 'block';
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
|
|
5403
|
+
_clearError(elementId) {
|
|
5404
|
+
const errorEl = this.element.querySelector(`#${elementId}`);
|
|
5405
|
+
if (errorEl) {
|
|
5406
|
+
errorEl.textContent = '';
|
|
5407
|
+
errorEl.style.display = 'none';
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
async _handleSubmit() {
|
|
5412
|
+
const nameInput = this.element.querySelector('#messenger-prechat-name');
|
|
5413
|
+
const emailInput = this.element.querySelector('#messenger-prechat-email');
|
|
5414
|
+
const submitBtn = this.element.querySelector('.messenger-prechat-submit');
|
|
5415
|
+
const submitText = submitBtn.querySelector('.messenger-prechat-submit-text');
|
|
5416
|
+
const submitLoading = submitBtn.querySelector('.messenger-prechat-submit-loading');
|
|
5417
|
+
|
|
5418
|
+
const name = nameInput.value.trim();
|
|
5419
|
+
const email = emailInput.value.trim();
|
|
5420
|
+
|
|
5421
|
+
// Show loading state
|
|
5422
|
+
this._isSubmitting = true;
|
|
5423
|
+
submitBtn.disabled = true;
|
|
5424
|
+
submitText.style.display = 'none';
|
|
5425
|
+
submitLoading.style.display = 'inline-flex';
|
|
5426
|
+
|
|
5427
|
+
try {
|
|
5428
|
+
// First, identify the contact with collected info
|
|
5429
|
+
if (this.options.onIdentifyContact) {
|
|
5430
|
+
await this.options.onIdentifyContact({ name, email });
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
// Update state with user info
|
|
5434
|
+
if (!this.state.userContext) {
|
|
5435
|
+
this.state.userContext = {};
|
|
5436
|
+
}
|
|
5437
|
+
this.state.userContext.name = name;
|
|
5438
|
+
this.state.userContext.email = email;
|
|
5439
|
+
|
|
5440
|
+
this._isSubmitting = false;
|
|
5441
|
+
|
|
5442
|
+
// Go to chat after collecting contact info
|
|
5443
|
+
this.state.setView('chat');
|
|
5444
|
+
} catch (error) {
|
|
5445
|
+
console.error('[PreChatFormView] Error submitting form:', error);
|
|
5446
|
+
this._showError('messenger-email-error', 'Something went wrong. Please try again.');
|
|
5447
|
+
|
|
5448
|
+
// Reset button state
|
|
5449
|
+
this._isSubmitting = false;
|
|
5450
|
+
submitBtn.disabled = false;
|
|
5451
|
+
submitText.style.display = 'inline';
|
|
5452
|
+
submitLoading.style.display = 'none';
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
destroy() {
|
|
5457
|
+
if (this.element && this.element.parentNode) {
|
|
5458
|
+
this.element.parentNode.removeChild(this.element);
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
/**
|
|
5464
|
+
* WebSocketService - Real-time communication for messenger widget
|
|
5465
|
+
*/
|
|
5466
|
+
|
|
5467
|
+
class WebSocketService {
|
|
5468
|
+
constructor(config = {}) {
|
|
5469
|
+
this.baseURL = config.baseURL || '';
|
|
5470
|
+
this.workspace = config.workspace || '';
|
|
5471
|
+
this.sessionToken = config.sessionToken || null;
|
|
5472
|
+
this.mock = config.mock || false;
|
|
5473
|
+
|
|
5474
|
+
this.ws = null;
|
|
5475
|
+
this.reconnectAttempts = 0;
|
|
5476
|
+
this.maxReconnectAttempts = 5;
|
|
5477
|
+
this.reconnectDelay = 1000;
|
|
5478
|
+
this.pingInterval = null;
|
|
5479
|
+
this.isConnected = false;
|
|
5480
|
+
|
|
5481
|
+
// Event listeners
|
|
5482
|
+
this._listeners = new Map();
|
|
5483
|
+
|
|
5484
|
+
// Bind methods
|
|
5485
|
+
this._onOpen = this._onOpen.bind(this);
|
|
5486
|
+
this._onMessage = this._onMessage.bind(this);
|
|
5487
|
+
this._onClose = this._onClose.bind(this);
|
|
5488
|
+
this._onError = this._onError.bind(this);
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5491
|
+
/**
|
|
5492
|
+
* Connect to WebSocket server
|
|
5493
|
+
*/
|
|
5494
|
+
connect(sessionToken = null) {
|
|
5495
|
+
if (sessionToken) {
|
|
5496
|
+
this.sessionToken = sessionToken;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
if (!this.sessionToken) {
|
|
5500
|
+
console.warn('[WebSocket] No session token provided');
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
// Mock mode - simulate connection
|
|
5505
|
+
if (this.mock) {
|
|
5506
|
+
this.isConnected = true;
|
|
5507
|
+
this._emit('connected', {});
|
|
5508
|
+
this._startMockResponses();
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
|
|
5512
|
+
// Build WebSocket URL
|
|
5513
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
5514
|
+
let wsURL = this.baseURL.replace(/^https?:/, wsProtocol);
|
|
5515
|
+
wsURL = wsURL.replace('/api/v1', '');
|
|
5516
|
+
wsURL = `${wsURL}/api/v1/widget/messenger/ws?token=${encodeURIComponent(this.sessionToken)}`;
|
|
5517
|
+
|
|
5518
|
+
try {
|
|
5519
|
+
this.ws = new WebSocket(wsURL);
|
|
5520
|
+
this.ws.onopen = this._onOpen;
|
|
5521
|
+
this.ws.onmessage = this._onMessage;
|
|
5522
|
+
this.ws.onclose = this._onClose;
|
|
5523
|
+
this.ws.onerror = this._onError;
|
|
5524
|
+
} catch (error) {
|
|
5525
|
+
console.error('[WebSocket] Connection error:', error);
|
|
5526
|
+
this._scheduleReconnect();
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
|
|
5530
|
+
/**
|
|
5531
|
+
* Disconnect from WebSocket server
|
|
5532
|
+
*/
|
|
5533
|
+
disconnect() {
|
|
5534
|
+
this.isConnected = false;
|
|
5535
|
+
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
|
|
5536
|
+
|
|
5537
|
+
if (this.pingInterval) {
|
|
5538
|
+
clearInterval(this.pingInterval);
|
|
5539
|
+
this.pingInterval = null;
|
|
5540
|
+
}
|
|
5541
|
+
|
|
5542
|
+
if (this.ws) {
|
|
5543
|
+
this.ws.close();
|
|
5544
|
+
this.ws = null;
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5547
|
+
if (this._mockInterval) {
|
|
5548
|
+
clearInterval(this._mockInterval);
|
|
5549
|
+
this._mockInterval = null;
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5553
|
+
/**
|
|
5554
|
+
* Subscribe to events
|
|
5555
|
+
* @param {string} event - Event name
|
|
5556
|
+
* @param {Function} callback - Event handler
|
|
5557
|
+
* @returns {Function} Unsubscribe function
|
|
5558
|
+
*/
|
|
5559
|
+
on(event, callback) {
|
|
5560
|
+
if (!this._listeners.has(event)) {
|
|
5561
|
+
this._listeners.set(event, new Set());
|
|
5562
|
+
}
|
|
5563
|
+
this._listeners.get(event).add(callback);
|
|
5564
|
+
return () => this._listeners.get(event).delete(callback);
|
|
5565
|
+
}
|
|
5566
|
+
|
|
5567
|
+
/**
|
|
5568
|
+
* Remove event listener
|
|
5569
|
+
*/
|
|
5570
|
+
off(event, callback) {
|
|
5571
|
+
if (this._listeners.has(event)) {
|
|
5572
|
+
this._listeners.get(event).delete(callback);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
/**
|
|
5577
|
+
* Send message through WebSocket
|
|
5578
|
+
*/
|
|
5579
|
+
send(type, payload = {}) {
|
|
5580
|
+
if (!this.isConnected) {
|
|
5581
|
+
console.warn('[WebSocket] Not connected, cannot send message');
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5585
|
+
if (this.mock) {
|
|
5586
|
+
// Mock mode - just log
|
|
5587
|
+
console.log('[WebSocket Mock] Sending:', type, payload);
|
|
5588
|
+
return;
|
|
5589
|
+
}
|
|
5590
|
+
|
|
5591
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
5592
|
+
this.ws.send(JSON.stringify({ type, payload }));
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5596
|
+
// Private methods
|
|
5597
|
+
|
|
5598
|
+
_onOpen() {
|
|
5599
|
+
console.log('[WebSocket] Connected');
|
|
5600
|
+
this.isConnected = true;
|
|
5601
|
+
this.reconnectAttempts = 0;
|
|
5602
|
+
this._emit('connected', {});
|
|
5603
|
+
|
|
5604
|
+
// Start ping interval to keep connection alive
|
|
5605
|
+
this.pingInterval = setInterval(() => {
|
|
5606
|
+
this.send('ping', {});
|
|
5607
|
+
}, 30000);
|
|
5608
|
+
}
|
|
5609
|
+
|
|
5610
|
+
_onMessage(event) {
|
|
5611
|
+
try {
|
|
5612
|
+
const data = JSON.parse(event.data);
|
|
5613
|
+
const { type, payload } = data;
|
|
5614
|
+
|
|
5615
|
+
// Handle different event types
|
|
5616
|
+
switch (type) {
|
|
5617
|
+
case 'message:new':
|
|
5618
|
+
this._emit('message', payload);
|
|
5619
|
+
break;
|
|
5620
|
+
case 'typing:started':
|
|
5621
|
+
this._emit('typing_started', payload);
|
|
5622
|
+
break;
|
|
5623
|
+
case 'typing:stopped':
|
|
5624
|
+
this._emit('typing_stopped', payload);
|
|
5625
|
+
break;
|
|
5626
|
+
case 'conversation:updated':
|
|
5627
|
+
this._emit('conversation_updated', payload);
|
|
5628
|
+
break;
|
|
5629
|
+
case 'conversation:closed':
|
|
5630
|
+
this._emit('conversation_closed', payload);
|
|
5631
|
+
break;
|
|
5632
|
+
case 'availability:changed':
|
|
5633
|
+
this._emit('availability_changed', payload);
|
|
5634
|
+
break;
|
|
5635
|
+
case 'pong':
|
|
5636
|
+
// Ping response, ignore
|
|
5637
|
+
break;
|
|
5638
|
+
default:
|
|
5639
|
+
console.log('[WebSocket] Unknown event:', type, payload);
|
|
5640
|
+
}
|
|
5641
|
+
} catch (error) {
|
|
5642
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5646
|
+
_onClose(event) {
|
|
5647
|
+
console.log('[WebSocket] Disconnected:', event.code, event.reason);
|
|
5648
|
+
this.isConnected = false;
|
|
4783
5649
|
|
|
4784
|
-
if (
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
<span class="messenger-availability-dot messenger-availability-online"></span>
|
|
4788
|
-
<span class="messenger-availability-text">We're online now</span>
|
|
4789
|
-
</div>
|
|
4790
|
-
`;
|
|
5650
|
+
if (this.pingInterval) {
|
|
5651
|
+
clearInterval(this.pingInterval);
|
|
5652
|
+
this.pingInterval = null;
|
|
4791
5653
|
}
|
|
4792
5654
|
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
<span class="messenger-availability-dot messenger-availability-away"></span>
|
|
4796
|
-
<span class="messenger-availability-text">${responseTime}</span>
|
|
4797
|
-
</div>
|
|
4798
|
-
`;
|
|
5655
|
+
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
5656
|
+
this._scheduleReconnect();
|
|
4799
5657
|
}
|
|
4800
5658
|
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
}
|
|
4805
|
-
|
|
4806
|
-
const { title, description, imageUrl, action } =
|
|
4807
|
-
this.options.featuredContent;
|
|
4808
|
-
|
|
4809
|
-
return `
|
|
4810
|
-
<div class="messenger-home-featured">
|
|
4811
|
-
${imageUrl ? `<img src="${imageUrl}" alt="${title}" class="messenger-home-featured-image" onerror="this.style.display='none';" />` : ''}
|
|
4812
|
-
<div class="messenger-home-featured-content">
|
|
4813
|
-
<h3>${title}</h3>
|
|
4814
|
-
<p>${description}</p>
|
|
4815
|
-
</div>
|
|
4816
|
-
${action ? `<button class="messenger-home-featured-btn" data-action="${action.type}" data-value="${action.value}">${action.label}</button>` : ''}
|
|
4817
|
-
</div>
|
|
4818
|
-
`;
|
|
5659
|
+
_onError(error) {
|
|
5660
|
+
console.error('[WebSocket] Error:', error);
|
|
5661
|
+
this._emit('error', { error });
|
|
4819
5662
|
}
|
|
4820
5663
|
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
5664
|
+
_scheduleReconnect() {
|
|
5665
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
5666
|
+
console.log('[WebSocket] Max reconnect attempts reached');
|
|
5667
|
+
this._emit('reconnect_failed', {});
|
|
5668
|
+
return;
|
|
4825
5669
|
}
|
|
4826
5670
|
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
item.coverImage
|
|
4833
|
-
? `
|
|
4834
|
-
<div class="messenger-home-changelog-cover">
|
|
4835
|
-
<img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
|
|
4836
|
-
${item.coverText ? `<span class="messenger-home-changelog-cover-text">${item.coverText}</span>` : ''}
|
|
4837
|
-
</div>
|
|
4838
|
-
`
|
|
4839
|
-
: ''
|
|
4840
|
-
}
|
|
4841
|
-
<div class="messenger-home-changelog-card-content">
|
|
4842
|
-
<h4 class="messenger-home-changelog-card-title">${item.title}</h4>
|
|
4843
|
-
<p class="messenger-home-changelog-card-desc">${item.description || ''}</p>
|
|
4844
|
-
</div>
|
|
4845
|
-
</div>
|
|
4846
|
-
`
|
|
4847
|
-
)
|
|
4848
|
-
.join('');
|
|
4849
|
-
|
|
4850
|
-
return `
|
|
4851
|
-
<div class="messenger-home-changelog-section">
|
|
4852
|
-
${changelogHtml}
|
|
4853
|
-
</div>
|
|
4854
|
-
`;
|
|
4855
|
-
}
|
|
4856
|
-
|
|
4857
|
-
_formatDate(dateString) {
|
|
4858
|
-
if (!dateString) return '';
|
|
4859
|
-
const date = new Date(dateString);
|
|
4860
|
-
const now = new Date();
|
|
4861
|
-
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
5671
|
+
this.reconnectAttempts++;
|
|
5672
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
5673
|
+
console.log(
|
|
5674
|
+
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
5675
|
+
);
|
|
4862
5676
|
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
5677
|
+
setTimeout(() => {
|
|
5678
|
+
this.connect();
|
|
5679
|
+
}, delay);
|
|
4867
5680
|
}
|
|
4868
5681
|
|
|
4869
|
-
|
|
4870
|
-
this.
|
|
4871
|
-
.
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
.querySelector('.messenger-home-message-btn')
|
|
4878
|
-
.addEventListener('click', () => {
|
|
4879
|
-
this.state.setView('messages');
|
|
5682
|
+
_emit(event, data) {
|
|
5683
|
+
if (this._listeners.has(event)) {
|
|
5684
|
+
this._listeners.get(event).forEach((callback) => {
|
|
5685
|
+
try {
|
|
5686
|
+
callback(data);
|
|
5687
|
+
} catch (error) {
|
|
5688
|
+
console.error(`[WebSocket] Error in ${event} handler:`, error);
|
|
5689
|
+
}
|
|
4880
5690
|
});
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
4881
5693
|
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
5694
|
+
// Mock support for development
|
|
5695
|
+
_startMockResponses() {
|
|
5696
|
+
// Simulate agent typing and responses
|
|
5697
|
+
this._mockInterval = setInterval(() => {
|
|
5698
|
+
// Randomly emit typing or message events for demo
|
|
5699
|
+
const random = Math.random();
|
|
5700
|
+
if (random < 0.1) {
|
|
5701
|
+
this._emit('typing_started', {
|
|
5702
|
+
conversation_id: 'conv_1',
|
|
5703
|
+
user_id: 'agent_1',
|
|
5704
|
+
user_name: 'Sarah',
|
|
5705
|
+
is_agent: true,
|
|
4887
5706
|
});
|
|
4888
|
-
});
|
|
4889
|
-
|
|
4890
|
-
const seeAllBtn = this.element.querySelector(
|
|
4891
|
-
'.messenger-home-changelog-all'
|
|
4892
|
-
);
|
|
4893
|
-
if (seeAllBtn) {
|
|
4894
|
-
seeAllBtn.addEventListener('click', () => {
|
|
4895
|
-
this.state.setView('changelog');
|
|
4896
|
-
});
|
|
4897
|
-
}
|
|
4898
5707
|
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
} else if (action === 'view') {
|
|
4909
|
-
this.state.setView(value);
|
|
4910
|
-
}
|
|
4911
|
-
});
|
|
4912
|
-
}
|
|
5708
|
+
// Stop typing after 2 seconds
|
|
5709
|
+
setTimeout(() => {
|
|
5710
|
+
this._emit('typing_stopped', {
|
|
5711
|
+
conversation_id: 'conv_1',
|
|
5712
|
+
user_id: 'agent_1',
|
|
5713
|
+
});
|
|
5714
|
+
}, 2000);
|
|
5715
|
+
}
|
|
5716
|
+
}, 10000);
|
|
4913
5717
|
}
|
|
4914
5718
|
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
if (this.
|
|
4920
|
-
this.
|
|
5719
|
+
/**
|
|
5720
|
+
* Simulate receiving a message (for mock mode)
|
|
5721
|
+
*/
|
|
5722
|
+
simulateMessage(conversationId, message) {
|
|
5723
|
+
if (this.mock) {
|
|
5724
|
+
this._emit('message', {
|
|
5725
|
+
conversation_id: conversationId,
|
|
5726
|
+
message: {
|
|
5727
|
+
id: 'msg_' + Date.now(),
|
|
5728
|
+
content: message.content,
|
|
5729
|
+
sender_type: message.sender_type || 'agent',
|
|
5730
|
+
sender_name: message.sender_name || 'Support',
|
|
5731
|
+
sender_avatar: message.sender_avatar || null,
|
|
5732
|
+
created_at: new Date().toISOString(),
|
|
5733
|
+
},
|
|
5734
|
+
});
|
|
4921
5735
|
}
|
|
4922
5736
|
}
|
|
4923
5737
|
}
|
|
4924
5738
|
|
|
4925
|
-
/**
|
|
4926
|
-
* MessengerWidget - Full-featured Messenger/Chat widget
|
|
4927
|
-
*/
|
|
4928
|
-
|
|
4929
5739
|
class MessengerWidget extends BaseWidget {
|
|
4930
5740
|
constructor(options) {
|
|
4931
5741
|
super({ ...options, type: 'messenger' });
|
|
@@ -4941,13 +5751,11 @@
|
|
|
4941
5751
|
logoUrl: options.logoUrl || 'https://product7.io/p7logo.svg',
|
|
4942
5752
|
featuredContent: options.featuredContent || null,
|
|
4943
5753
|
primaryColor: options.primaryColor || '#1c1c1e',
|
|
4944
|
-
// Callbacks
|
|
4945
5754
|
onSendMessage: options.onSendMessage || null,
|
|
4946
5755
|
onArticleClick: options.onArticleClick || null,
|
|
4947
5756
|
onChangelogClick: options.onChangelogClick || null,
|
|
4948
5757
|
};
|
|
4949
5758
|
|
|
4950
|
-
// Create state
|
|
4951
5759
|
this.messengerState = new MessengerState({
|
|
4952
5760
|
teamName: this.messengerOptions.teamName,
|
|
4953
5761
|
teamAvatars: this.messengerOptions.teamAvatars,
|
|
@@ -4962,51 +5770,46 @@
|
|
|
4962
5770
|
this.wsService = null;
|
|
4963
5771
|
this._wsUnsubscribers = [];
|
|
4964
5772
|
|
|
4965
|
-
// Bind methods
|
|
4966
5773
|
this._handleOpenChange = this._handleOpenChange.bind(this);
|
|
4967
5774
|
this._handleWebSocketMessage = this._handleWebSocketMessage.bind(this);
|
|
4968
5775
|
this._handleTypingStarted = this._handleTypingStarted.bind(this);
|
|
4969
5776
|
this._handleTypingStopped = this._handleTypingStopped.bind(this);
|
|
5777
|
+
this._handleConversationClosed = this._handleConversationClosed.bind(this);
|
|
4970
5778
|
}
|
|
4971
5779
|
|
|
4972
5780
|
_render() {
|
|
4973
|
-
// Create container
|
|
4974
5781
|
const container = document.createElement('div');
|
|
4975
5782
|
container.className = `messenger-widget theme-${this.messengerOptions.theme}`;
|
|
4976
5783
|
container.style.zIndex = '999999';
|
|
4977
5784
|
|
|
4978
|
-
// Create launcher
|
|
4979
5785
|
this.launcher = new MessengerLauncher(this.messengerState, {
|
|
4980
5786
|
position: this.messengerOptions.position,
|
|
4981
5787
|
primaryColor: this.messengerOptions.primaryColor,
|
|
4982
5788
|
});
|
|
4983
5789
|
container.appendChild(this.launcher.render());
|
|
4984
5790
|
|
|
4985
|
-
// Create panel with all callbacks
|
|
4986
5791
|
this.panel = new MessengerPanel(this.messengerState, {
|
|
4987
5792
|
position: this.messengerOptions.position,
|
|
4988
5793
|
theme: this.messengerOptions.theme,
|
|
4989
5794
|
primaryColor: this.messengerOptions.primaryColor,
|
|
4990
5795
|
logoUrl: this.messengerOptions.logoUrl,
|
|
4991
5796
|
featuredContent: this.messengerOptions.featuredContent,
|
|
4992
|
-
// Chat callbacks
|
|
4993
5797
|
onSendMessage:
|
|
4994
5798
|
this.messengerOptions.onSendMessage ||
|
|
4995
5799
|
this._handleSendMessage.bind(this),
|
|
4996
5800
|
onStartConversation: this._handleStartConversation.bind(this),
|
|
4997
5801
|
onTyping: this.sendTypingIndicator.bind(this),
|
|
4998
|
-
// Conversation list callbacks
|
|
4999
5802
|
onSelectConversation: this._handleSelectConversation.bind(this),
|
|
5000
5803
|
onStartNewConversation: this._handleNewConversationClick.bind(this),
|
|
5001
|
-
|
|
5804
|
+
onIdentifyContact: this._handleIdentifyContact.bind(this),
|
|
5002
5805
|
onArticleClick: this.messengerOptions.onArticleClick,
|
|
5003
5806
|
onChangelogClick: this.messengerOptions.onChangelogClick,
|
|
5004
5807
|
});
|
|
5005
5808
|
|
|
5006
|
-
// Register views
|
|
5007
5809
|
this.panel.registerView('home', HomeView);
|
|
5008
5810
|
this.panel.registerView('messages', ConversationsView);
|
|
5009
5811
|
this.panel.registerView('chat', ChatView);
|
|
5812
|
+
this.panel.registerView('prechat', PreChatFormView);
|
|
5010
5813
|
this.panel.registerView('help', HelpView);
|
|
5011
5814
|
this.panel.registerView('changelog', ChangelogView);
|
|
5012
5815
|
|
|
@@ -5017,11 +5820,16 @@
|
|
|
5017
5820
|
}
|
|
5018
5821
|
|
|
5019
5822
|
_attachEvents() {
|
|
5020
|
-
// Subscribe to state changes
|
|
5021
5823
|
this._stateUnsubscribe = this.messengerState.subscribe((type, data) => {
|
|
5022
5824
|
if (type === 'openChange') {
|
|
5023
5825
|
this._handleOpenChange(data.isOpen);
|
|
5024
5826
|
}
|
|
5827
|
+
if (type === 'conversationChange') {
|
|
5828
|
+
this._handleActiveConversationChange(
|
|
5829
|
+
data.conversationId,
|
|
5830
|
+
data.previousConversationId
|
|
5831
|
+
);
|
|
5832
|
+
}
|
|
5025
5833
|
});
|
|
5026
5834
|
}
|
|
5027
5835
|
|
|
@@ -5036,38 +5844,155 @@
|
|
|
5036
5844
|
}
|
|
5037
5845
|
}
|
|
5038
5846
|
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5847
|
+
_handleActiveConversationChange(conversationId, previousConversationId) {
|
|
5848
|
+
if (previousConversationId && this.wsService) {
|
|
5849
|
+
this.wsService.send('conversation:unsubscribe', {
|
|
5850
|
+
conversation_id: previousConversationId,
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
if (conversationId && this.wsService) {
|
|
5854
|
+
this.wsService.send('conversation:subscribe', {
|
|
5855
|
+
conversation_id: conversationId,
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5860
|
+
async _handleStartConversation(messageContent, pendingAttachments) {
|
|
5043
5861
|
try {
|
|
5044
|
-
|
|
5862
|
+
const userContext = this.messengerState.userContext;
|
|
5863
|
+
const isIdentified = userContext?.email;
|
|
5864
|
+
|
|
5865
|
+
if (!isIdentified) {
|
|
5866
|
+
this.messengerState.pendingMessage = {
|
|
5867
|
+
content: messageContent,
|
|
5868
|
+
attachments: pendingAttachments,
|
|
5869
|
+
};
|
|
5870
|
+
this.messengerState.setView('prechat');
|
|
5871
|
+
return null;
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
const openConversation = this.messengerState.conversations.find(
|
|
5875
|
+
(c) => c.status === 'open'
|
|
5876
|
+
);
|
|
5877
|
+
|
|
5878
|
+
if (openConversation) {
|
|
5879
|
+
this.messengerState.setActiveConversation(openConversation.id);
|
|
5880
|
+
await this._handleSendMessage(
|
|
5881
|
+
openConversation.id,
|
|
5882
|
+
{ content: messageContent },
|
|
5883
|
+
pendingAttachments
|
|
5884
|
+
);
|
|
5885
|
+
return openConversation;
|
|
5886
|
+
}
|
|
5887
|
+
|
|
5888
|
+
return await this.startNewConversation(
|
|
5889
|
+
messageContent,
|
|
5890
|
+
'',
|
|
5891
|
+
pendingAttachments
|
|
5892
|
+
);
|
|
5045
5893
|
} catch (error) {
|
|
5046
5894
|
console.error('[MessengerWidget] Failed to start conversation:', error);
|
|
5895
|
+
return null;
|
|
5047
5896
|
}
|
|
5048
5897
|
}
|
|
5049
5898
|
|
|
5050
|
-
/**
|
|
5051
|
-
* Handle selecting a conversation from the list
|
|
5052
|
-
*/
|
|
5053
5899
|
async _handleSelectConversation(conversationId) {
|
|
5054
5900
|
try {
|
|
5055
|
-
await this.fetchMessages(conversationId);
|
|
5901
|
+
await this.fetchMessages(conversationId);
|
|
5902
|
+
} catch (error) {
|
|
5903
|
+
console.error('[MessengerWidget] Failed to fetch messages:', error);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
|
|
5907
|
+
_handleNewConversationClick() {
|
|
5908
|
+
const openConversation = this.messengerState.conversations.find(
|
|
5909
|
+
(c) => c.status === 'open'
|
|
5910
|
+
);
|
|
5911
|
+
|
|
5912
|
+
if (openConversation) {
|
|
5913
|
+
this.messengerState.setActiveConversation(openConversation.id);
|
|
5914
|
+
this.messengerState.setView('chat');
|
|
5915
|
+
this._handleSelectConversation(openConversation.id);
|
|
5916
|
+
} else {
|
|
5917
|
+
this.messengerState.setActiveConversation(null);
|
|
5918
|
+
this.messengerState.setView('chat');
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
|
|
5922
|
+
async _handleIdentifyContact(contactData) {
|
|
5923
|
+
try {
|
|
5924
|
+
const response = await this.apiService.identifyContact({
|
|
5925
|
+
name: contactData.name,
|
|
5926
|
+
email: contactData.email,
|
|
5927
|
+
});
|
|
5928
|
+
|
|
5929
|
+
if (response.status) {
|
|
5930
|
+
console.log('[MessengerWidget] Contact identified:', contactData.email);
|
|
5931
|
+
|
|
5932
|
+
if (!this.messengerState.userContext) {
|
|
5933
|
+
this.messengerState.userContext = {};
|
|
5934
|
+
}
|
|
5935
|
+
this.messengerState.userContext.name = contactData.name;
|
|
5936
|
+
this.messengerState.userContext.email = contactData.email;
|
|
5937
|
+
|
|
5938
|
+
const pendingMessage = this.messengerState.pendingMessage;
|
|
5939
|
+
if (pendingMessage) {
|
|
5940
|
+
this.messengerState.pendingMessage = null;
|
|
5941
|
+
|
|
5942
|
+
await this.startNewConversation(
|
|
5943
|
+
pendingMessage.content,
|
|
5944
|
+
'',
|
|
5945
|
+
pendingMessage.attachments
|
|
5946
|
+
);
|
|
5947
|
+
} else {
|
|
5948
|
+
this.messengerState.setView('chat');
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5952
|
+
return response;
|
|
5953
|
+
} catch (error) {
|
|
5954
|
+
console.error('[MessengerWidget] Failed to identify contact:', error);
|
|
5955
|
+
throw error;
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
async _handleUploadFile(base64Data, filename) {
|
|
5960
|
+
try {
|
|
5961
|
+
const response = await this.apiService.uploadFile(base64Data, filename);
|
|
5962
|
+
if (response.status && response.url) {
|
|
5963
|
+
return response.url;
|
|
5964
|
+
}
|
|
5965
|
+
throw new Error('Upload failed');
|
|
5056
5966
|
} catch (error) {
|
|
5057
|
-
console.error('[MessengerWidget] Failed to
|
|
5967
|
+
console.error('[MessengerWidget] Failed to upload file:', error);
|
|
5968
|
+
throw error;
|
|
5058
5969
|
}
|
|
5059
5970
|
}
|
|
5060
5971
|
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5972
|
+
async _uploadPendingAttachments(pendingAttachments) {
|
|
5973
|
+
if (!pendingAttachments || pendingAttachments.length === 0) return [];
|
|
5974
|
+
|
|
5975
|
+
const uploaded = [];
|
|
5976
|
+
for (const att of pendingAttachments) {
|
|
5977
|
+
try {
|
|
5978
|
+
const cdnUrl = await this._handleUploadFile(att.preview, att.file.name);
|
|
5979
|
+
uploaded.push({
|
|
5980
|
+
url: cdnUrl,
|
|
5981
|
+
type: att.type.startsWith('image') ? 'image' : 'file',
|
|
5982
|
+
name: att.file.name,
|
|
5983
|
+
});
|
|
5984
|
+
} catch (err) {
|
|
5985
|
+
console.error(
|
|
5986
|
+
'[MessengerWidget] Skipping failed attachment upload:',
|
|
5987
|
+
att.file.name,
|
|
5988
|
+
err
|
|
5989
|
+
);
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
return uploaded;
|
|
5067
5993
|
}
|
|
5068
5994
|
|
|
5069
|
-
async _handleSendMessage(conversationId, message) {
|
|
5070
|
-
// Emit event for external listeners
|
|
5995
|
+
async _handleSendMessage(conversationId, message, pendingAttachments) {
|
|
5071
5996
|
this.sdk.eventBus.emit('messenger:messageSent', {
|
|
5072
5997
|
widget: this,
|
|
5073
5998
|
conversationId,
|
|
@@ -5075,18 +6000,18 @@
|
|
|
5075
6000
|
});
|
|
5076
6001
|
|
|
5077
6002
|
try {
|
|
5078
|
-
|
|
6003
|
+
const uploadedAttachments =
|
|
6004
|
+
await this._uploadPendingAttachments(pendingAttachments);
|
|
6005
|
+
|
|
5079
6006
|
const response = await this.apiService.sendMessage(conversationId, {
|
|
5080
6007
|
content: message.content,
|
|
6008
|
+
attachments: uploadedAttachments,
|
|
5081
6009
|
});
|
|
5082
6010
|
|
|
5083
6011
|
if (response.status && response.data) {
|
|
5084
|
-
// Update the message ID with server-assigned ID
|
|
5085
|
-
// Message is already added to state optimistically in ChatView
|
|
5086
6012
|
console.log('[MessengerWidget] Message sent:', response.data.id);
|
|
5087
6013
|
}
|
|
5088
6014
|
|
|
5089
|
-
// In mock mode, simulate an agent response after a delay
|
|
5090
6015
|
if (this.apiService?.mock) {
|
|
5091
6016
|
setTimeout(() => {
|
|
5092
6017
|
const mockResponse = {
|
|
@@ -5104,32 +6029,38 @@
|
|
|
5104
6029
|
}
|
|
5105
6030
|
} catch (error) {
|
|
5106
6031
|
console.error('[MessengerWidget] Failed to send message:', error);
|
|
5107
|
-
// Could add error handling UI here
|
|
5108
6032
|
}
|
|
5109
6033
|
}
|
|
5110
6034
|
|
|
5111
|
-
/**
|
|
5112
|
-
* Handle incoming WebSocket message
|
|
5113
|
-
*/
|
|
5114
6035
|
_handleWebSocketMessage(data) {
|
|
5115
6036
|
const { conversation_id, message } = data;
|
|
5116
6037
|
|
|
5117
|
-
|
|
6038
|
+
let attachments = [];
|
|
6039
|
+
if (message.attachments) {
|
|
6040
|
+
try {
|
|
6041
|
+
attachments =
|
|
6042
|
+
typeof message.attachments === 'string'
|
|
6043
|
+
? JSON.parse(message.attachments)
|
|
6044
|
+
: message.attachments;
|
|
6045
|
+
} catch (e) {
|
|
6046
|
+
// ignore
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
|
|
5118
6050
|
const localMessage = {
|
|
5119
6051
|
id: message.id,
|
|
5120
6052
|
content: message.content,
|
|
5121
6053
|
isOwn: message.sender_type === 'customer',
|
|
5122
6054
|
timestamp: message.created_at,
|
|
6055
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
5123
6056
|
sender: {
|
|
5124
6057
|
name: message.sender_name || 'Support',
|
|
5125
6058
|
avatarUrl: message.sender_avatar || null,
|
|
5126
6059
|
},
|
|
5127
6060
|
};
|
|
5128
6061
|
|
|
5129
|
-
// Add message to state
|
|
5130
6062
|
this.messengerState.addMessage(conversation_id, localMessage);
|
|
5131
6063
|
|
|
5132
|
-
// Update unread count if panel is closed or viewing different conversation
|
|
5133
6064
|
if (
|
|
5134
6065
|
!this.messengerState.isOpen ||
|
|
5135
6066
|
this.messengerState.activeConversationId !== conversation_id
|
|
@@ -5138,9 +6069,6 @@
|
|
|
5138
6069
|
}
|
|
5139
6070
|
}
|
|
5140
6071
|
|
|
5141
|
-
/**
|
|
5142
|
-
* Handle typing started event
|
|
5143
|
-
*/
|
|
5144
6072
|
_handleTypingStarted(data) {
|
|
5145
6073
|
if (data.is_agent) {
|
|
5146
6074
|
this.messengerState._notify('typingStarted', {
|
|
@@ -5150,18 +6078,20 @@
|
|
|
5150
6078
|
}
|
|
5151
6079
|
}
|
|
5152
6080
|
|
|
5153
|
-
/**
|
|
5154
|
-
* Handle typing stopped event
|
|
5155
|
-
*/
|
|
5156
6081
|
_handleTypingStopped(data) {
|
|
5157
6082
|
this.messengerState._notify('typingStopped', {
|
|
5158
6083
|
conversationId: data.conversation_id,
|
|
5159
6084
|
});
|
|
5160
6085
|
}
|
|
5161
6086
|
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
6087
|
+
_handleConversationClosed(data) {
|
|
6088
|
+
const conversationId =
|
|
6089
|
+
data?.conversation_id || data?.id || data?.conversation?.id;
|
|
6090
|
+
if (!conversationId) return;
|
|
6091
|
+
|
|
6092
|
+
this.messengerState.updateConversation(conversationId, { status: 'closed' });
|
|
6093
|
+
}
|
|
6094
|
+
|
|
5165
6095
|
async _updateUnreadCount() {
|
|
5166
6096
|
try {
|
|
5167
6097
|
const response = await this.apiService.getUnreadCount();
|
|
@@ -5176,9 +6106,6 @@
|
|
|
5176
6106
|
}
|
|
5177
6107
|
}
|
|
5178
6108
|
|
|
5179
|
-
/**
|
|
5180
|
-
* Initialize WebSocket connection
|
|
5181
|
-
*/
|
|
5182
6109
|
_initWebSocket() {
|
|
5183
6110
|
if (this.wsService) {
|
|
5184
6111
|
this.wsService.disconnect();
|
|
@@ -5191,7 +6118,6 @@
|
|
|
5191
6118
|
mock: this.apiService.mock,
|
|
5192
6119
|
});
|
|
5193
6120
|
|
|
5194
|
-
// Subscribe to WebSocket events
|
|
5195
6121
|
this._wsUnsubscribers.push(
|
|
5196
6122
|
this.wsService.on('message', this._handleWebSocketMessage)
|
|
5197
6123
|
);
|
|
@@ -5201,9 +6127,17 @@
|
|
|
5201
6127
|
this._wsUnsubscribers.push(
|
|
5202
6128
|
this.wsService.on('typing_stopped', this._handleTypingStopped)
|
|
5203
6129
|
);
|
|
6130
|
+
this._wsUnsubscribers.push(
|
|
6131
|
+
this.wsService.on('conversation_closed', this._handleConversationClosed)
|
|
6132
|
+
);
|
|
5204
6133
|
this._wsUnsubscribers.push(
|
|
5205
6134
|
this.wsService.on('connected', () => {
|
|
5206
6135
|
console.log('[MessengerWidget] WebSocket connected');
|
|
6136
|
+
if (this.messengerState.activeConversationId) {
|
|
6137
|
+
this.wsService.send('conversation:subscribe', {
|
|
6138
|
+
conversation_id: this.messengerState.activeConversationId,
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
5207
6141
|
})
|
|
5208
6142
|
);
|
|
5209
6143
|
this._wsUnsubscribers.push(
|
|
@@ -5212,34 +6146,21 @@
|
|
|
5212
6146
|
})
|
|
5213
6147
|
);
|
|
5214
6148
|
|
|
5215
|
-
// Connect
|
|
5216
6149
|
this.wsService.connect();
|
|
5217
6150
|
}
|
|
5218
6151
|
|
|
5219
|
-
/**
|
|
5220
|
-
* Open the messenger panel
|
|
5221
|
-
*/
|
|
5222
6152
|
open() {
|
|
5223
6153
|
this.messengerState.setOpen(true);
|
|
5224
6154
|
}
|
|
5225
6155
|
|
|
5226
|
-
/**
|
|
5227
|
-
* Close the messenger panel
|
|
5228
|
-
*/
|
|
5229
6156
|
close() {
|
|
5230
6157
|
this.messengerState.setOpen(false);
|
|
5231
6158
|
}
|
|
5232
6159
|
|
|
5233
|
-
/**
|
|
5234
|
-
* Toggle the messenger panel
|
|
5235
|
-
*/
|
|
5236
6160
|
toggle() {
|
|
5237
6161
|
this.messengerState.setOpen(!this.messengerState.isOpen);
|
|
5238
6162
|
}
|
|
5239
6163
|
|
|
5240
|
-
/**
|
|
5241
|
-
* Navigate to a specific view
|
|
5242
|
-
*/
|
|
5243
6164
|
navigateTo(view) {
|
|
5244
6165
|
this.messengerState.setView(view);
|
|
5245
6166
|
if (!this.messengerState.isOpen) {
|
|
@@ -5247,52 +6168,31 @@
|
|
|
5247
6168
|
}
|
|
5248
6169
|
}
|
|
5249
6170
|
|
|
5250
|
-
/**
|
|
5251
|
-
* Set conversations
|
|
5252
|
-
*/
|
|
5253
6171
|
setConversations(conversations) {
|
|
5254
6172
|
this.messengerState.setConversations(conversations);
|
|
5255
6173
|
}
|
|
5256
6174
|
|
|
5257
|
-
/**
|
|
5258
|
-
* Add a message to a conversation
|
|
5259
|
-
*/
|
|
5260
6175
|
addMessage(conversationId, message) {
|
|
5261
6176
|
this.messengerState.addMessage(conversationId, message);
|
|
5262
6177
|
}
|
|
5263
6178
|
|
|
5264
|
-
/**
|
|
5265
|
-
* Set help articles
|
|
5266
|
-
*/
|
|
5267
6179
|
setHelpArticles(articles) {
|
|
5268
6180
|
this.messengerState.setHelpArticles(articles);
|
|
5269
6181
|
}
|
|
5270
6182
|
|
|
5271
|
-
/**
|
|
5272
|
-
* Set home changelog items
|
|
5273
|
-
*/
|
|
5274
6183
|
setHomeChangelogItems(items) {
|
|
5275
6184
|
this.messengerState.setHomeChangelogItems(items);
|
|
5276
6185
|
}
|
|
5277
6186
|
|
|
5278
|
-
/**
|
|
5279
|
-
* Set changelog items
|
|
5280
|
-
*/
|
|
5281
6187
|
setChangelogItems(items) {
|
|
5282
6188
|
this.messengerState.setChangelogItems(items);
|
|
5283
6189
|
}
|
|
5284
6190
|
|
|
5285
|
-
/**
|
|
5286
|
-
* Update unread count (for external updates)
|
|
5287
|
-
*/
|
|
5288
6191
|
setUnreadCount(count) {
|
|
5289
6192
|
this.messengerState.unreadCount = count;
|
|
5290
6193
|
this.messengerState._notify('unreadCountChange', { count });
|
|
5291
6194
|
}
|
|
5292
6195
|
|
|
5293
|
-
/**
|
|
5294
|
-
* Get current state
|
|
5295
|
-
*/
|
|
5296
6196
|
getState() {
|
|
5297
6197
|
return {
|
|
5298
6198
|
isOpen: this.messengerState.isOpen,
|
|
@@ -5302,11 +6202,7 @@
|
|
|
5302
6202
|
};
|
|
5303
6203
|
}
|
|
5304
6204
|
|
|
5305
|
-
/**
|
|
5306
|
-
* Load initial data (mock or API)
|
|
5307
|
-
*/
|
|
5308
6205
|
async loadInitialData() {
|
|
5309
|
-
// Load conversations
|
|
5310
6206
|
try {
|
|
5311
6207
|
const conversations = await this._fetchConversations();
|
|
5312
6208
|
this.messengerState.setConversations(conversations);
|
|
@@ -5314,7 +6210,6 @@
|
|
|
5314
6210
|
console.error('[MessengerWidget] Failed to load conversations:', error);
|
|
5315
6211
|
}
|
|
5316
6212
|
|
|
5317
|
-
// Load help articles if enabled
|
|
5318
6213
|
if (this.messengerOptions.enableHelp) {
|
|
5319
6214
|
try {
|
|
5320
6215
|
const articles = await this._fetchHelpArticles();
|
|
@@ -5324,7 +6219,6 @@
|
|
|
5324
6219
|
}
|
|
5325
6220
|
}
|
|
5326
6221
|
|
|
5327
|
-
// Load changelog if enabled
|
|
5328
6222
|
if (this.messengerOptions.enableChangelog) {
|
|
5329
6223
|
try {
|
|
5330
6224
|
const { homeItems, changelogItems } = await this._fetchChangelog();
|
|
@@ -5340,7 +6234,6 @@
|
|
|
5340
6234
|
try {
|
|
5341
6235
|
const response = await this.apiService.getConversations();
|
|
5342
6236
|
if (response.status && response.data) {
|
|
5343
|
-
// Transform API response to local format
|
|
5344
6237
|
return response.data.map((conv) => ({
|
|
5345
6238
|
id: conv.id,
|
|
5346
6239
|
title:
|
|
@@ -5371,7 +6264,6 @@
|
|
|
5371
6264
|
try {
|
|
5372
6265
|
const response = await this.apiService.getHelpCollections();
|
|
5373
6266
|
if (response.status && response.data) {
|
|
5374
|
-
// Transform API response to local format
|
|
5375
6267
|
return response.data.map((collection) => ({
|
|
5376
6268
|
id: collection.id,
|
|
5377
6269
|
title: collection.title || collection.name,
|
|
@@ -5389,28 +6281,39 @@
|
|
|
5389
6281
|
}
|
|
5390
6282
|
}
|
|
5391
6283
|
|
|
5392
|
-
/**
|
|
5393
|
-
* Fetch messages for a conversation
|
|
5394
|
-
*/
|
|
5395
6284
|
async fetchMessages(conversationId) {
|
|
5396
6285
|
try {
|
|
5397
6286
|
const response = await this.apiService.getConversation(conversationId);
|
|
5398
6287
|
if (response.status && response.data) {
|
|
5399
|
-
const messages = (response.data.messages || []).map((msg) =>
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
6288
|
+
const messages = (response.data.messages || []).map((msg) => {
|
|
6289
|
+
let attachments;
|
|
6290
|
+
if (msg.attachments) {
|
|
6291
|
+
try {
|
|
6292
|
+
attachments =
|
|
6293
|
+
typeof msg.attachments === 'string'
|
|
6294
|
+
? JSON.parse(msg.attachments)
|
|
6295
|
+
: msg.attachments;
|
|
6296
|
+
} catch (e) {
|
|
6297
|
+
// ignore
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
return {
|
|
6301
|
+
id: msg.id,
|
|
6302
|
+
content: msg.content,
|
|
6303
|
+
isOwn: msg.sender_type === 'customer',
|
|
6304
|
+
timestamp: msg.created_at,
|
|
6305
|
+
attachments:
|
|
6306
|
+
attachments && attachments.length > 0 ? attachments : undefined,
|
|
6307
|
+
sender: {
|
|
6308
|
+
name:
|
|
6309
|
+
msg.sender_name ||
|
|
6310
|
+
(msg.sender_type === 'customer' ? 'You' : 'Support'),
|
|
6311
|
+
avatarUrl: msg.sender_avatar || null,
|
|
6312
|
+
},
|
|
6313
|
+
};
|
|
6314
|
+
});
|
|
5411
6315
|
this.messengerState.setMessages(conversationId, messages);
|
|
5412
6316
|
|
|
5413
|
-
// Mark as read
|
|
5414
6317
|
await this.apiService.markConversationAsRead(conversationId);
|
|
5415
6318
|
this.messengerState.markAsRead(conversationId);
|
|
5416
6319
|
|
|
@@ -5423,16 +6326,30 @@
|
|
|
5423
6326
|
}
|
|
5424
6327
|
}
|
|
5425
6328
|
|
|
5426
|
-
|
|
5427
|
-
* Start a new conversation
|
|
5428
|
-
*/
|
|
5429
|
-
async startNewConversation(message, subject = '') {
|
|
6329
|
+
async startNewConversation(message, subject = '', pendingAttachments = []) {
|
|
5430
6330
|
try {
|
|
6331
|
+
const uploadedAttachments =
|
|
6332
|
+
await this._uploadPendingAttachments(pendingAttachments);
|
|
6333
|
+
|
|
6334
|
+
console.log('[MessengerWidget] Starting conversation...', {
|
|
6335
|
+
message,
|
|
6336
|
+
attachmentCount: uploadedAttachments.length,
|
|
6337
|
+
hasSession: this.apiService.isSessionValid(),
|
|
6338
|
+
sessionToken: this.apiService.sessionToken
|
|
6339
|
+
? this.apiService.sessionToken.substring(0, 10) + '...'
|
|
6340
|
+
: null,
|
|
6341
|
+
baseURL: this.apiService.baseURL,
|
|
6342
|
+
mock: this.apiService.mock,
|
|
6343
|
+
});
|
|
6344
|
+
|
|
5431
6345
|
const response = await this.apiService.startConversation({
|
|
5432
6346
|
message,
|
|
5433
6347
|
subject,
|
|
6348
|
+
attachments: uploadedAttachments,
|
|
5434
6349
|
});
|
|
5435
6350
|
|
|
6351
|
+
console.log('[MessengerWidget] Conversation response:', response);
|
|
6352
|
+
|
|
5436
6353
|
if (response.status && response.data) {
|
|
5437
6354
|
const conv = response.data;
|
|
5438
6355
|
const newConversation = {
|
|
@@ -5445,10 +6362,8 @@
|
|
|
5445
6362
|
status: 'open',
|
|
5446
6363
|
};
|
|
5447
6364
|
|
|
5448
|
-
// Add to state
|
|
5449
6365
|
this.messengerState.addConversation(newConversation);
|
|
5450
6366
|
|
|
5451
|
-
// Set initial message in messages cache
|
|
5452
6367
|
this.messengerState.setMessages(conv.id, [
|
|
5453
6368
|
{
|
|
5454
6369
|
id: 'msg_' + Date.now(),
|
|
@@ -5458,7 +6373,6 @@
|
|
|
5458
6373
|
},
|
|
5459
6374
|
]);
|
|
5460
6375
|
|
|
5461
|
-
// Navigate to chat
|
|
5462
6376
|
this.messengerState.setActiveConversation(conv.id);
|
|
5463
6377
|
this.messengerState.setView('chat');
|
|
5464
6378
|
|
|
@@ -5471,20 +6385,14 @@
|
|
|
5471
6385
|
}
|
|
5472
6386
|
}
|
|
5473
6387
|
|
|
5474
|
-
/**
|
|
5475
|
-
* Send typing indicator
|
|
5476
|
-
*/
|
|
5477
6388
|
async sendTypingIndicator(conversationId, isTyping) {
|
|
5478
6389
|
try {
|
|
5479
6390
|
await this.apiService.sendTypingIndicator(conversationId, isTyping);
|
|
5480
6391
|
} catch (error) {
|
|
5481
|
-
//
|
|
6392
|
+
// Silent fail
|
|
5482
6393
|
}
|
|
5483
6394
|
}
|
|
5484
6395
|
|
|
5485
|
-
/**
|
|
5486
|
-
* Check agent availability
|
|
5487
|
-
*/
|
|
5488
6396
|
async checkAgentAvailability() {
|
|
5489
6397
|
try {
|
|
5490
6398
|
const response = await this.apiService.checkAgentsOnline();
|
|
@@ -5492,6 +6400,13 @@
|
|
|
5492
6400
|
this.messengerState.agentsOnline = response.data.agents_online;
|
|
5493
6401
|
this.messengerState.onlineCount = response.data.online_count || 0;
|
|
5494
6402
|
this.messengerState.responseTime = response.data.response_time || '';
|
|
6403
|
+
|
|
6404
|
+
if (response.data.available_agents) {
|
|
6405
|
+
this.messengerState.setTeamAvatarsFromAgents(
|
|
6406
|
+
response.data.available_agents
|
|
6407
|
+
);
|
|
6408
|
+
}
|
|
6409
|
+
|
|
5495
6410
|
this.messengerState._notify('availabilityUpdate', response.data);
|
|
5496
6411
|
return response.data;
|
|
5497
6412
|
}
|
|
@@ -5581,11 +6496,9 @@
|
|
|
5581
6496
|
};
|
|
5582
6497
|
}
|
|
5583
6498
|
|
|
5584
|
-
// Fetch changelogs from API
|
|
5585
6499
|
const response = await this.apiService.getChangelogs({ limit: 20 });
|
|
5586
6500
|
const changelogs = response.data || [];
|
|
5587
6501
|
|
|
5588
|
-
// Map API response to expected format
|
|
5589
6502
|
const mappedItems = changelogs.map((item) => ({
|
|
5590
6503
|
id: item.id,
|
|
5591
6504
|
title: item.title,
|
|
@@ -5603,19 +6516,30 @@
|
|
|
5603
6516
|
};
|
|
5604
6517
|
}
|
|
5605
6518
|
|
|
5606
|
-
onMount() {
|
|
5607
|
-
|
|
6519
|
+
async onMount() {
|
|
6520
|
+
const userContext = this.messengerState.userContext;
|
|
6521
|
+
if (userContext?.email && userContext?.name) {
|
|
6522
|
+
try {
|
|
6523
|
+
await this.apiService.identifyContact({
|
|
6524
|
+
name: userContext.name,
|
|
6525
|
+
email: userContext.email,
|
|
6526
|
+
});
|
|
6527
|
+
console.log('[MessengerWidget] User identified successfully');
|
|
6528
|
+
} catch (error) {
|
|
6529
|
+
if (error?.code !== 'ALREADY_IDENTIFIED') {
|
|
6530
|
+
console.warn('[MessengerWidget] Identification failed:', error);
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
|
|
5608
6535
|
this.loadInitialData();
|
|
5609
6536
|
|
|
5610
|
-
// Initialize WebSocket for real-time updates
|
|
5611
6537
|
if (this.apiService?.sessionToken) {
|
|
5612
6538
|
this._initWebSocket();
|
|
5613
6539
|
}
|
|
5614
6540
|
|
|
5615
|
-
// Check agent availability
|
|
5616
6541
|
this.checkAgentAvailability();
|
|
5617
6542
|
|
|
5618
|
-
// Periodically check availability (every 60 seconds)
|
|
5619
6543
|
this._availabilityInterval = setInterval(() => {
|
|
5620
6544
|
this.checkAgentAvailability();
|
|
5621
6545
|
}, 60000);
|
|
@@ -5626,16 +6550,13 @@
|
|
|
5626
6550
|
this._stateUnsubscribe();
|
|
5627
6551
|
}
|
|
5628
6552
|
|
|
5629
|
-
// Clean up WebSocket
|
|
5630
6553
|
if (this.wsService) {
|
|
5631
6554
|
this.wsService.disconnect();
|
|
5632
6555
|
}
|
|
5633
6556
|
|
|
5634
|
-
// Clean up WebSocket event listeners
|
|
5635
6557
|
this._wsUnsubscribers.forEach((unsub) => unsub());
|
|
5636
6558
|
this._wsUnsubscribers = [];
|
|
5637
6559
|
|
|
5638
|
-
// Clean up availability interval
|
|
5639
6560
|
if (this._availabilityInterval) {
|
|
5640
6561
|
clearInterval(this._availabilityInterval);
|
|
5641
6562
|
}
|
|
@@ -6404,8 +7325,11 @@
|
|
|
6404
7325
|
this.apiService = new APIService({
|
|
6405
7326
|
apiUrl: this.config.apiUrl,
|
|
6406
7327
|
workspace: this.config.workspace,
|
|
7328
|
+
siteId: this.config.siteId,
|
|
7329
|
+
sessionToken: this.config.sessionToken,
|
|
6407
7330
|
userContext: this.config.userContext,
|
|
6408
7331
|
mock: this.config.mock,
|
|
7332
|
+
debug: this.config.debug,
|
|
6409
7333
|
env: this.config.env,
|
|
6410
7334
|
});
|
|
6411
7335
|
|
|
@@ -7201,6 +8125,39 @@
|
|
|
7201
8125
|
opacity: 0.6;
|
|
7202
8126
|
}
|
|
7203
8127
|
|
|
8128
|
+
/* Continue conversation variant */
|
|
8129
|
+
.messenger-home-continue-btn {
|
|
8130
|
+
flex-direction: column;
|
|
8131
|
+
align-items: flex-start;
|
|
8132
|
+
gap: 2px;
|
|
8133
|
+
position: relative;
|
|
8134
|
+
}
|
|
8135
|
+
|
|
8136
|
+
.messenger-home-continue-btn > i {
|
|
8137
|
+
position: absolute;
|
|
8138
|
+
right: 20px;
|
|
8139
|
+
top: 50%;
|
|
8140
|
+
transform: translateY(-50%);
|
|
8141
|
+
}
|
|
8142
|
+
|
|
8143
|
+
.messenger-home-continue-info {
|
|
8144
|
+
display: flex;
|
|
8145
|
+
flex-direction: column;
|
|
8146
|
+
gap: 2px;
|
|
8147
|
+
text-align: left;
|
|
8148
|
+
}
|
|
8149
|
+
|
|
8150
|
+
.messenger-home-continue-label {
|
|
8151
|
+
font-size: 14px;
|
|
8152
|
+
font-weight: 600;
|
|
8153
|
+
}
|
|
8154
|
+
|
|
8155
|
+
.messenger-home-continue-preview {
|
|
8156
|
+
font-size: 12px;
|
|
8157
|
+
opacity: 0.6;
|
|
8158
|
+
font-weight: 400;
|
|
8159
|
+
}
|
|
8160
|
+
|
|
7204
8161
|
/* Featured Card */
|
|
7205
8162
|
.messenger-home-featured {
|
|
7206
8163
|
background: #2c2c2e;
|
|
@@ -7329,7 +8286,7 @@
|
|
|
7329
8286
|
.messenger-conversations-body {
|
|
7330
8287
|
flex: 1;
|
|
7331
8288
|
overflow-y: auto;
|
|
7332
|
-
padding: 12px;
|
|
8289
|
+
padding: 4px 12px 12px 12px;
|
|
7333
8290
|
}
|
|
7334
8291
|
|
|
7335
8292
|
.messenger-conversations-empty {
|
|
@@ -7364,7 +8321,7 @@
|
|
|
7364
8321
|
display: flex;
|
|
7365
8322
|
align-items: flex-start;
|
|
7366
8323
|
gap: 12px;
|
|
7367
|
-
padding: 16px;
|
|
8324
|
+
padding: 10px 16px;
|
|
7368
8325
|
border-radius: 12px;
|
|
7369
8326
|
cursor: pointer;
|
|
7370
8327
|
transition: background 0.2s ease;
|
|
@@ -7496,6 +8453,11 @@
|
|
|
7496
8453
|
color: white;
|
|
7497
8454
|
}
|
|
7498
8455
|
|
|
8456
|
+
.messenger-chat-view {
|
|
8457
|
+
position: relative;
|
|
8458
|
+
overflow: visible;
|
|
8459
|
+
}
|
|
8460
|
+
|
|
7499
8461
|
.messenger-chat-messages {
|
|
7500
8462
|
flex: 1;
|
|
7501
8463
|
overflow-y: auto;
|
|
@@ -7570,7 +8532,7 @@
|
|
|
7570
8532
|
}
|
|
7571
8533
|
|
|
7572
8534
|
.messenger-message-own .messenger-message-bubble {
|
|
7573
|
-
background:
|
|
8535
|
+
background: rgb(29, 78, 216);
|
|
7574
8536
|
color: white;
|
|
7575
8537
|
border-bottom-right-radius: 4px;
|
|
7576
8538
|
}
|
|
@@ -7598,10 +8560,25 @@
|
|
|
7598
8560
|
margin-top: auto;
|
|
7599
8561
|
}
|
|
7600
8562
|
|
|
8563
|
+
/* Conversation Closed Banner */
|
|
8564
|
+
.messenger-closed-banner {
|
|
8565
|
+
display: flex;
|
|
8566
|
+
align-items: center;
|
|
8567
|
+
justify-content: center;
|
|
8568
|
+
gap: 8px;
|
|
8569
|
+
padding: 12px 16px;
|
|
8570
|
+
margin: 16px;
|
|
8571
|
+
background: rgba(52, 199, 89, 0.12);
|
|
8572
|
+
color: #34c759;
|
|
8573
|
+
border-radius: 12px;
|
|
8574
|
+
font-size: 13px;
|
|
8575
|
+
font-weight: 500;
|
|
8576
|
+
}
|
|
8577
|
+
|
|
7601
8578
|
/* Compose Area */
|
|
7602
8579
|
.messenger-chat-compose {
|
|
7603
8580
|
display: flex;
|
|
7604
|
-
align-items:
|
|
8581
|
+
align-items: center;
|
|
7605
8582
|
gap: 8px;
|
|
7606
8583
|
padding: 12px 16px;
|
|
7607
8584
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
@@ -7611,7 +8588,7 @@
|
|
|
7611
8588
|
.messenger-compose-input-wrapper {
|
|
7612
8589
|
flex: 1;
|
|
7613
8590
|
background: #2c2c2e;
|
|
7614
|
-
border-radius:
|
|
8591
|
+
border-radius: 10px;
|
|
7615
8592
|
padding: 8px 16px;
|
|
7616
8593
|
}
|
|
7617
8594
|
|
|
@@ -7647,15 +8624,176 @@
|
|
|
7647
8624
|
flex-shrink: 0;
|
|
7648
8625
|
}
|
|
7649
8626
|
|
|
7650
|
-
.messenger-compose-send:hover:not(:disabled) {
|
|
7651
|
-
background: #0066d6;
|
|
7652
|
-
transform: scale(1.05);
|
|
8627
|
+
.messenger-compose-send:hover:not(:disabled) {
|
|
8628
|
+
background: #0066d6;
|
|
8629
|
+
transform: scale(1.05);
|
|
8630
|
+
}
|
|
8631
|
+
|
|
8632
|
+
.messenger-compose-send:disabled {
|
|
8633
|
+
background: #3c3c3e;
|
|
8634
|
+
color: rgba(255, 255, 255, 0.3);
|
|
8635
|
+
cursor: not-allowed;
|
|
8636
|
+
}
|
|
8637
|
+
|
|
8638
|
+
/* Attach Button */
|
|
8639
|
+
.messenger-compose-attach {
|
|
8640
|
+
width: 40px;
|
|
8641
|
+
height: 40px;
|
|
8642
|
+
background: transparent;
|
|
8643
|
+
border: none;
|
|
8644
|
+
border-radius: 50%;
|
|
8645
|
+
color: rgba(255, 255, 255, 0.5);
|
|
8646
|
+
cursor: pointer;
|
|
8647
|
+
display: flex;
|
|
8648
|
+
align-items: center;
|
|
8649
|
+
justify-content: center;
|
|
8650
|
+
transition: all 0.2s ease;
|
|
8651
|
+
flex-shrink: 0;
|
|
8652
|
+
}
|
|
8653
|
+
|
|
8654
|
+
.messenger-compose-attach:hover:not(:disabled) {
|
|
8655
|
+
color: rgba(255, 255, 255, 0.85);
|
|
8656
|
+
background: rgba(255, 255, 255, 0.08);
|
|
8657
|
+
}
|
|
8658
|
+
|
|
8659
|
+
.messenger-compose-attach:disabled {
|
|
8660
|
+
opacity: 0.3;
|
|
8661
|
+
cursor: not-allowed;
|
|
8662
|
+
}
|
|
8663
|
+
|
|
8664
|
+
/* Attachment Preview Strip */
|
|
8665
|
+
.messenger-compose-attachments-preview {
|
|
8666
|
+
display: none;
|
|
8667
|
+
flex-wrap: wrap;
|
|
8668
|
+
gap: 8px;
|
|
8669
|
+
padding: 8px 16px;
|
|
8670
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
8671
|
+
background: #1c1c1e;
|
|
8672
|
+
}
|
|
8673
|
+
|
|
8674
|
+
.messenger-attachment-preview {
|
|
8675
|
+
position: relative;
|
|
8676
|
+
width: 56px;
|
|
8677
|
+
height: 56px;
|
|
8678
|
+
border-radius: 8px;
|
|
8679
|
+
overflow: hidden;
|
|
8680
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
8681
|
+
}
|
|
8682
|
+
|
|
8683
|
+
.messenger-attachment-thumb {
|
|
8684
|
+
width: 100%;
|
|
8685
|
+
height: 100%;
|
|
8686
|
+
object-fit: cover;
|
|
8687
|
+
display: block;
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8690
|
+
.messenger-attachment-file-icon {
|
|
8691
|
+
display: flex;
|
|
8692
|
+
align-items: center;
|
|
8693
|
+
justify-content: center;
|
|
8694
|
+
background: #2c2c2e;
|
|
8695
|
+
color: rgba(255, 255, 255, 0.5);
|
|
8696
|
+
}
|
|
8697
|
+
|
|
8698
|
+
.messenger-attachment-remove {
|
|
8699
|
+
position: absolute;
|
|
8700
|
+
top: 2px;
|
|
8701
|
+
right: 2px;
|
|
8702
|
+
width: 18px;
|
|
8703
|
+
height: 18px;
|
|
8704
|
+
background: rgba(0, 0, 0, 0.7);
|
|
8705
|
+
border: none;
|
|
8706
|
+
border-radius: 50%;
|
|
8707
|
+
color: white;
|
|
8708
|
+
font-size: 12px;
|
|
8709
|
+
line-height: 1;
|
|
8710
|
+
cursor: pointer;
|
|
8711
|
+
display: flex;
|
|
8712
|
+
align-items: center;
|
|
8713
|
+
justify-content: center;
|
|
8714
|
+
padding: 0;
|
|
8715
|
+
transition: background 0.15s ease;
|
|
8716
|
+
}
|
|
8717
|
+
|
|
8718
|
+
.messenger-attachment-remove:hover {
|
|
8719
|
+
background: rgba(255, 59, 48, 0.85);
|
|
8720
|
+
}
|
|
8721
|
+
|
|
8722
|
+
/* Message Attachments (inline images & file links) */
|
|
8723
|
+
.messenger-message-image {
|
|
8724
|
+
max-width: 220px;
|
|
8725
|
+
max-height: 200px;
|
|
8726
|
+
width: auto;
|
|
8727
|
+
height: auto;
|
|
8728
|
+
border-radius: 8px;
|
|
8729
|
+
margin-top: 4px;
|
|
8730
|
+
cursor: pointer;
|
|
8731
|
+
object-fit: contain;
|
|
8732
|
+
display: block;
|
|
8733
|
+
}
|
|
8734
|
+
|
|
8735
|
+
.messenger-message-file {
|
|
8736
|
+
display: inline-flex;
|
|
8737
|
+
align-items: center;
|
|
8738
|
+
gap: 6px;
|
|
8739
|
+
margin-top: 4px;
|
|
8740
|
+
padding: 8px 12px;
|
|
8741
|
+
border-radius: 8px;
|
|
8742
|
+
background: #2c2c2e;
|
|
8743
|
+
color: #60a5fa;
|
|
8744
|
+
text-decoration: none;
|
|
8745
|
+
font-size: 13px;
|
|
8746
|
+
transition: background 0.15s ease;
|
|
8747
|
+
max-width: 100%;
|
|
8748
|
+
word-break: break-all;
|
|
8749
|
+
cursor: pointer;
|
|
8750
|
+
}
|
|
8751
|
+
|
|
8752
|
+
.messenger-message-file:hover {
|
|
8753
|
+
background: #3c3c3e;
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8756
|
+
.messenger-file-download-icon {
|
|
8757
|
+
margin-left: auto;
|
|
8758
|
+
opacity: 0.5;
|
|
8759
|
+
flex-shrink: 0;
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
.messenger-message-file:hover .messenger-file-download-icon {
|
|
8763
|
+
opacity: 1;
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
/* Light theme overrides for attachments */
|
|
8767
|
+
.theme-light .messenger-compose-attach {
|
|
8768
|
+
color: #9ca3af;
|
|
8769
|
+
}
|
|
8770
|
+
|
|
8771
|
+
.theme-light .messenger-compose-attach:hover:not(:disabled) {
|
|
8772
|
+
color: #374151;
|
|
8773
|
+
background: #f3f4f6;
|
|
8774
|
+
}
|
|
8775
|
+
|
|
8776
|
+
.theme-light .messenger-compose-attachments-preview {
|
|
8777
|
+
background: #ffffff;
|
|
8778
|
+
border-top-color: #e5e7eb;
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
.theme-light .messenger-attachment-preview {
|
|
8782
|
+
border-color: #e5e7eb;
|
|
8783
|
+
}
|
|
8784
|
+
|
|
8785
|
+
.theme-light .messenger-attachment-file-icon {
|
|
8786
|
+
background: #f3f4f6;
|
|
8787
|
+
color: #6b7280;
|
|
8788
|
+
}
|
|
8789
|
+
|
|
8790
|
+
.theme-light .messenger-message-file {
|
|
8791
|
+
background: #f3f4f6;
|
|
8792
|
+
color: #2563eb;
|
|
7653
8793
|
}
|
|
7654
8794
|
|
|
7655
|
-
.messenger-
|
|
7656
|
-
background: #
|
|
7657
|
-
color: rgba(255, 255, 255, 0.3);
|
|
7658
|
-
cursor: not-allowed;
|
|
8795
|
+
.theme-light .messenger-message-file:hover {
|
|
8796
|
+
background: #e5e7eb;
|
|
7659
8797
|
}
|
|
7660
8798
|
|
|
7661
8799
|
/* ========================================
|
|
@@ -7972,7 +9110,7 @@
|
|
|
7972
9110
|
|
|
7973
9111
|
.messenger-nav {
|
|
7974
9112
|
display: flex;
|
|
7975
|
-
padding: 8px
|
|
9113
|
+
padding: 4px 8px;
|
|
7976
9114
|
gap: 4px;
|
|
7977
9115
|
}
|
|
7978
9116
|
|
|
@@ -8025,8 +9163,8 @@
|
|
|
8025
9163
|
}
|
|
8026
9164
|
|
|
8027
9165
|
.messenger-nav-label {
|
|
8028
|
-
font-size:
|
|
8029
|
-
font-weight:
|
|
9166
|
+
font-size: 11px;
|
|
9167
|
+
font-weight: 500;
|
|
8030
9168
|
color: rgba(255, 255, 255, 0.5);
|
|
8031
9169
|
transition: color 0.2s ease;
|
|
8032
9170
|
}
|
|
@@ -8285,7 +9423,7 @@
|
|
|
8285
9423
|
}
|
|
8286
9424
|
|
|
8287
9425
|
.theme-light .messenger-message-own .messenger-message-bubble {
|
|
8288
|
-
background:
|
|
9426
|
+
background: rgb(29, 78, 216);
|
|
8289
9427
|
color: #ffffff;
|
|
8290
9428
|
}
|
|
8291
9429
|
|
|
@@ -8293,6 +9431,11 @@
|
|
|
8293
9431
|
color: #86868b;
|
|
8294
9432
|
}
|
|
8295
9433
|
|
|
9434
|
+
.theme-light .messenger-closed-banner {
|
|
9435
|
+
background: rgba(52, 199, 89, 0.1);
|
|
9436
|
+
color: #22883a;
|
|
9437
|
+
}
|
|
9438
|
+
|
|
8296
9439
|
.theme-light .messenger-chat-compose {
|
|
8297
9440
|
background: #ffffff;
|
|
8298
9441
|
border-top-color: #e5e5e7;
|
|
@@ -8559,6 +9702,191 @@
|
|
|
8559
9702
|
}
|
|
8560
9703
|
}
|
|
8561
9704
|
|
|
9705
|
+
/* ========================================
|
|
9706
|
+
Pre-Chat Form View (Transparent Overlay)
|
|
9707
|
+
======================================== */
|
|
9708
|
+
|
|
9709
|
+
.messenger-prechat-view {
|
|
9710
|
+
background: transparent;
|
|
9711
|
+
position: relative;
|
|
9712
|
+
}
|
|
9713
|
+
|
|
9714
|
+
.messenger-prechat-overlay {
|
|
9715
|
+
position: absolute;
|
|
9716
|
+
top: 0;
|
|
9717
|
+
left: 0;
|
|
9718
|
+
right: 0;
|
|
9719
|
+
bottom: 0;
|
|
9720
|
+
background: rgba(0, 0, 0, 0.5);
|
|
9721
|
+
backdrop-filter: blur(2px);
|
|
9722
|
+
display: flex;
|
|
9723
|
+
align-items: flex-end;
|
|
9724
|
+
padding: 16px;
|
|
9725
|
+
animation: messenger-fade-in 0.2s ease;
|
|
9726
|
+
}
|
|
9727
|
+
|
|
9728
|
+
.messenger-prechat-card {
|
|
9729
|
+
background: #1c1c1e;
|
|
9730
|
+
border-radius: 16px;
|
|
9731
|
+
padding: 20px;
|
|
9732
|
+
width: 100%;
|
|
9733
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
9734
|
+
animation: messenger-slide-up 0.25s ease;
|
|
9735
|
+
}
|
|
9736
|
+
|
|
9737
|
+
.messenger-prechat-card h4 {
|
|
9738
|
+
margin: 0 0 14px;
|
|
9739
|
+
font-size: 15px;
|
|
9740
|
+
font-weight: 600;
|
|
9741
|
+
color: white;
|
|
9742
|
+
text-align: center;
|
|
9743
|
+
}
|
|
9744
|
+
|
|
9745
|
+
.messenger-prechat-form {
|
|
9746
|
+
display: flex;
|
|
9747
|
+
flex-direction: column;
|
|
9748
|
+
gap: 10px;
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9751
|
+
.messenger-prechat-fields {
|
|
9752
|
+
display: flex;
|
|
9753
|
+
flex-direction: column;
|
|
9754
|
+
gap: 8px;
|
|
9755
|
+
}
|
|
9756
|
+
|
|
9757
|
+
.messenger-prechat-input {
|
|
9758
|
+
width: 100%;
|
|
9759
|
+
padding: 11px 14px;
|
|
9760
|
+
background: #2c2c2e;
|
|
9761
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
9762
|
+
border-radius: 10px;
|
|
9763
|
+
color: white;
|
|
9764
|
+
font-size: 14px;
|
|
9765
|
+
font-family: inherit;
|
|
9766
|
+
outline: none;
|
|
9767
|
+
transition: border-color 0.2s ease;
|
|
9768
|
+
}
|
|
9769
|
+
|
|
9770
|
+
.messenger-prechat-input:focus {
|
|
9771
|
+
border-color: #007aff;
|
|
9772
|
+
}
|
|
9773
|
+
|
|
9774
|
+
.messenger-prechat-input::placeholder {
|
|
9775
|
+
color: rgba(255, 255, 255, 0.4);
|
|
9776
|
+
}
|
|
9777
|
+
|
|
9778
|
+
.messenger-prechat-error {
|
|
9779
|
+
font-size: 12px;
|
|
9780
|
+
color: #ef4444;
|
|
9781
|
+
display: none;
|
|
9782
|
+
text-align: center;
|
|
9783
|
+
}
|
|
9784
|
+
|
|
9785
|
+
.messenger-prechat-actions {
|
|
9786
|
+
display: flex;
|
|
9787
|
+
gap: 10px;
|
|
9788
|
+
margin-top: 4px;
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9791
|
+
.messenger-prechat-skip {
|
|
9792
|
+
flex: 1;
|
|
9793
|
+
padding: 11px 14px;
|
|
9794
|
+
background: transparent;
|
|
9795
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
9796
|
+
border-radius: 20px;
|
|
9797
|
+
color: rgba(255, 255, 255, 0.7);
|
|
9798
|
+
font-size: 14px;
|
|
9799
|
+
font-weight: 500;
|
|
9800
|
+
cursor: pointer;
|
|
9801
|
+
transition: all 0.2s ease;
|
|
9802
|
+
}
|
|
9803
|
+
|
|
9804
|
+
.messenger-prechat-skip:hover {
|
|
9805
|
+
background: rgba(255, 255, 255, 0.05);
|
|
9806
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
9807
|
+
}
|
|
9808
|
+
|
|
9809
|
+
.messenger-prechat-submit {
|
|
9810
|
+
flex: 1;
|
|
9811
|
+
display: flex;
|
|
9812
|
+
align-items: center;
|
|
9813
|
+
justify-content: center;
|
|
9814
|
+
gap: 6px;
|
|
9815
|
+
padding: 11px 14px;
|
|
9816
|
+
background: #007aff;
|
|
9817
|
+
border: none;
|
|
9818
|
+
border-radius: 20px;
|
|
9819
|
+
color: white;
|
|
9820
|
+
font-size: 14px;
|
|
9821
|
+
font-weight: 600;
|
|
9822
|
+
cursor: pointer;
|
|
9823
|
+
transition: all 0.2s ease;
|
|
9824
|
+
}
|
|
9825
|
+
|
|
9826
|
+
.messenger-prechat-submit:hover:not(:disabled) {
|
|
9827
|
+
background: #0066d6;
|
|
9828
|
+
}
|
|
9829
|
+
|
|
9830
|
+
.messenger-prechat-submit:disabled {
|
|
9831
|
+
background: #3c3c3e;
|
|
9832
|
+
color: rgba(255, 255, 255, 0.4);
|
|
9833
|
+
cursor: not-allowed;
|
|
9834
|
+
}
|
|
9835
|
+
|
|
9836
|
+
.messenger-prechat-submit-loading {
|
|
9837
|
+
display: inline-flex;
|
|
9838
|
+
align-items: center;
|
|
9839
|
+
animation: messenger-spin 1s linear infinite;
|
|
9840
|
+
}
|
|
9841
|
+
|
|
9842
|
+
@keyframes messenger-spin {
|
|
9843
|
+
from { transform: rotate(0deg); }
|
|
9844
|
+
to { transform: rotate(360deg); }
|
|
9845
|
+
}
|
|
9846
|
+
|
|
9847
|
+
/* Light Theme - Pre-Chat Form */
|
|
9848
|
+
.theme-light .messenger-prechat-overlay {
|
|
9849
|
+
background: rgba(255, 255, 255, 0.6);
|
|
9850
|
+
}
|
|
9851
|
+
|
|
9852
|
+
.theme-light .messenger-prechat-card {
|
|
9853
|
+
background: #ffffff;
|
|
9854
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
9855
|
+
}
|
|
9856
|
+
|
|
9857
|
+
.theme-light .messenger-prechat-card h4 {
|
|
9858
|
+
color: #1d1d1f;
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
.theme-light .messenger-prechat-input {
|
|
9862
|
+
background: #f5f5f7;
|
|
9863
|
+
border-color: #e5e5e7;
|
|
9864
|
+
color: #1d1d1f;
|
|
9865
|
+
}
|
|
9866
|
+
|
|
9867
|
+
.theme-light .messenger-prechat-input:focus {
|
|
9868
|
+
border-color: #007aff;
|
|
9869
|
+
background: #ffffff;
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
.theme-light .messenger-prechat-input::placeholder {
|
|
9873
|
+
color: #86868b;
|
|
9874
|
+
}
|
|
9875
|
+
|
|
9876
|
+
.theme-light .messenger-prechat-skip {
|
|
9877
|
+
border-color: #e5e5e7;
|
|
9878
|
+
color: #6e6e73;
|
|
9879
|
+
}
|
|
9880
|
+
|
|
9881
|
+
.theme-light .messenger-prechat-skip:hover {
|
|
9882
|
+
background: #f5f5f7;
|
|
9883
|
+
}
|
|
9884
|
+
|
|
9885
|
+
.theme-light .messenger-prechat-submit:disabled {
|
|
9886
|
+
background: #e5e5e7;
|
|
9887
|
+
color: #c7c7cc;
|
|
9888
|
+
}
|
|
9889
|
+
|
|
8562
9890
|
/* ========================================
|
|
8563
9891
|
Animations
|
|
8564
9892
|
======================================== */
|
|
@@ -8590,6 +9918,173 @@
|
|
|
8590
9918
|
transition: none;
|
|
8591
9919
|
}
|
|
8592
9920
|
}
|
|
9921
|
+
|
|
9922
|
+
/* ========================================
|
|
9923
|
+
Email Collection Overlay (Bottom Sheet)
|
|
9924
|
+
======================================== */
|
|
9925
|
+
|
|
9926
|
+
.messenger-email-overlay {
|
|
9927
|
+
position: absolute;
|
|
9928
|
+
bottom: -56px;
|
|
9929
|
+
left: 0;
|
|
9930
|
+
right: 0;
|
|
9931
|
+
top: 0;
|
|
9932
|
+
display: flex;
|
|
9933
|
+
align-items: flex-end;
|
|
9934
|
+
z-index: 20;
|
|
9935
|
+
background: rgba(0, 0, 0, 0.08);
|
|
9936
|
+
pointer-events: auto;
|
|
9937
|
+
}
|
|
9938
|
+
|
|
9939
|
+
.messenger-email-card {
|
|
9940
|
+
width: 100%;
|
|
9941
|
+
background: #ffffff;
|
|
9942
|
+
border-radius: 0;
|
|
9943
|
+
padding: 16px 16px 72px;
|
|
9944
|
+
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
|
|
9945
|
+
animation: messenger-slide-up 0.25s ease;
|
|
9946
|
+
}
|
|
9947
|
+
|
|
9948
|
+
.messenger-email-card h4 {
|
|
9949
|
+
margin: 0 0 2px;
|
|
9950
|
+
font-size: 13px;
|
|
9951
|
+
font-weight: 600;
|
|
9952
|
+
color: #1d1d1f;
|
|
9953
|
+
text-align: center;
|
|
9954
|
+
}
|
|
9955
|
+
|
|
9956
|
+
.messenger-email-card p {
|
|
9957
|
+
margin: 0 0 10px;
|
|
9958
|
+
font-size: 11px;
|
|
9959
|
+
color: #6b7280;
|
|
9960
|
+
text-align: center;
|
|
9961
|
+
}
|
|
9962
|
+
|
|
9963
|
+
.messenger-email-name,
|
|
9964
|
+
.messenger-email-input {
|
|
9965
|
+
width: 100%;
|
|
9966
|
+
padding: 8px 10px;
|
|
9967
|
+
background: #f3f4f6;
|
|
9968
|
+
border: 1px solid transparent;
|
|
9969
|
+
border-radius: 8px;
|
|
9970
|
+
color: #1d1d1f;
|
|
9971
|
+
font-size: 12px;
|
|
9972
|
+
font-family: inherit;
|
|
9973
|
+
outline: none;
|
|
9974
|
+
margin-bottom: 6px;
|
|
9975
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
9976
|
+
}
|
|
9977
|
+
|
|
9978
|
+
.messenger-email-name:focus,
|
|
9979
|
+
.messenger-email-input:focus {
|
|
9980
|
+
border-color: #007aff;
|
|
9981
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.12);
|
|
9982
|
+
background: #ffffff;
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9985
|
+
.messenger-email-name::placeholder,
|
|
9986
|
+
.messenger-email-input::placeholder {
|
|
9987
|
+
color: #9ca3af;
|
|
9988
|
+
}
|
|
9989
|
+
|
|
9990
|
+
.messenger-email-actions {
|
|
9991
|
+
display: flex;
|
|
9992
|
+
gap: 8px;
|
|
9993
|
+
margin-top: 4px;
|
|
9994
|
+
}
|
|
9995
|
+
|
|
9996
|
+
.messenger-email-submit {
|
|
9997
|
+
flex: 1.2;
|
|
9998
|
+
padding: 7px 12px;
|
|
9999
|
+
background: #007aff;
|
|
10000
|
+
border: none;
|
|
10001
|
+
border-radius: 8px;
|
|
10002
|
+
color: white;
|
|
10003
|
+
font-size: 12px;
|
|
10004
|
+
font-weight: 600;
|
|
10005
|
+
cursor: pointer;
|
|
10006
|
+
transition: all 0.2s ease;
|
|
10007
|
+
}
|
|
10008
|
+
|
|
10009
|
+
.messenger-email-submit:hover:not(:disabled) {
|
|
10010
|
+
background: #0066d6;
|
|
10011
|
+
}
|
|
10012
|
+
|
|
10013
|
+
.messenger-email-submit:disabled {
|
|
10014
|
+
background: #d1d5db;
|
|
10015
|
+
color: #9ca3af;
|
|
10016
|
+
cursor: not-allowed;
|
|
10017
|
+
}
|
|
10018
|
+
|
|
10019
|
+
.messenger-email-skip {
|
|
10020
|
+
flex: 0.8;
|
|
10021
|
+
padding: 7px 12px;
|
|
10022
|
+
background: #ffffff;
|
|
10023
|
+
border: 1px solid #e5e5e7;
|
|
10024
|
+
border-radius: 8px;
|
|
10025
|
+
color: #4b5563;
|
|
10026
|
+
font-size: 12px;
|
|
10027
|
+
font-weight: 500;
|
|
10028
|
+
cursor: pointer;
|
|
10029
|
+
transition: all 0.2s ease;
|
|
10030
|
+
}
|
|
10031
|
+
|
|
10032
|
+
.messenger-email-skip:hover {
|
|
10033
|
+
background: #f9fafb;
|
|
10034
|
+
border-color: #d1d5db;
|
|
10035
|
+
}
|
|
10036
|
+
|
|
10037
|
+
/* Dark Theme - Email Overlay */
|
|
10038
|
+
.theme-dark .messenger-email-overlay {
|
|
10039
|
+
background: rgba(0, 0, 0, 0.3);
|
|
10040
|
+
}
|
|
10041
|
+
|
|
10042
|
+
.theme-dark .messenger-email-card {
|
|
10043
|
+
background: #1c1c1e;
|
|
10044
|
+
}
|
|
10045
|
+
|
|
10046
|
+
.theme-dark .messenger-email-card h4 {
|
|
10047
|
+
color: white;
|
|
10048
|
+
}
|
|
10049
|
+
|
|
10050
|
+
.theme-dark .messenger-email-card p {
|
|
10051
|
+
color: rgba(255, 255, 255, 0.6);
|
|
10052
|
+
}
|
|
10053
|
+
|
|
10054
|
+
.theme-dark .messenger-email-name,
|
|
10055
|
+
.theme-dark .messenger-email-input {
|
|
10056
|
+
background: #2c2c2e;
|
|
10057
|
+
border-color: transparent;
|
|
10058
|
+
color: white;
|
|
10059
|
+
}
|
|
10060
|
+
|
|
10061
|
+
.theme-dark .messenger-email-name:focus,
|
|
10062
|
+
.theme-dark .messenger-email-input:focus {
|
|
10063
|
+
border-color: #007aff;
|
|
10064
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.25);
|
|
10065
|
+
background: #1c1c1e;
|
|
10066
|
+
}
|
|
10067
|
+
|
|
10068
|
+
.theme-dark .messenger-email-name::placeholder,
|
|
10069
|
+
.theme-dark .messenger-email-input::placeholder {
|
|
10070
|
+
color: rgba(255, 255, 255, 0.4);
|
|
10071
|
+
}
|
|
10072
|
+
|
|
10073
|
+
.theme-dark .messenger-email-submit:disabled {
|
|
10074
|
+
background: #3c3c3e;
|
|
10075
|
+
color: rgba(255, 255, 255, 0.4);
|
|
10076
|
+
}
|
|
10077
|
+
|
|
10078
|
+
.theme-dark .messenger-email-skip {
|
|
10079
|
+
background: transparent;
|
|
10080
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
10081
|
+
color: rgba(255, 255, 255, 0.8);
|
|
10082
|
+
}
|
|
10083
|
+
|
|
10084
|
+
.theme-dark .messenger-email-skip:hover {
|
|
10085
|
+
background: rgba(255, 255, 255, 0.05);
|
|
10086
|
+
border-color: rgba(255, 255, 255, 0.25);
|
|
10087
|
+
}
|
|
8593
10088
|
`;
|
|
8594
10089
|
|
|
8595
10090
|
const baseStyles = `
|
|
@@ -10029,7 +11524,7 @@
|
|
|
10029
11524
|
EventBus,
|
|
10030
11525
|
APIService,
|
|
10031
11526
|
SDKError,
|
|
10032
|
-
APIError,
|
|
11527
|
+
APIError: APIError$1,
|
|
10033
11528
|
WidgetError,
|
|
10034
11529
|
ConfigError,
|
|
10035
11530
|
ValidationError,
|
|
@@ -10099,7 +11594,7 @@
|
|
|
10099
11594
|
handleDOMReady();
|
|
10100
11595
|
}
|
|
10101
11596
|
|
|
10102
|
-
exports.APIError = APIError;
|
|
11597
|
+
exports.APIError = APIError$1;
|
|
10103
11598
|
exports.APIService = APIService;
|
|
10104
11599
|
exports.BaseWidget = BaseWidget;
|
|
10105
11600
|
exports.ButtonWidget = ButtonWidget;
|