@product7/feedback-sdk 1.3.0 → 1.3.1
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 +2002 -431
- 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 +203 -19
- 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
|
|
|
@@ -4798,6 +5066,37 @@
|
|
|
4798
5066
|
`;
|
|
4799
5067
|
}
|
|
4800
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
|
+
|
|
4801
5100
|
_renderFeaturedCard() {
|
|
4802
5101
|
if (!this.options.featuredContent) {
|
|
4803
5102
|
return '';
|
|
@@ -4873,11 +5172,25 @@
|
|
|
4873
5172
|
this.state.setOpen(false);
|
|
4874
5173
|
});
|
|
4875
5174
|
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
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
|
+
}
|
|
4880
5192
|
});
|
|
5193
|
+
}
|
|
4881
5194
|
|
|
4882
5195
|
this.element
|
|
4883
5196
|
.querySelectorAll('.messenger-home-changelog-item')
|
|
@@ -4911,15 +5224,516 @@
|
|
|
4911
5224
|
});
|
|
4912
5225
|
}
|
|
4913
5226
|
}
|
|
4914
|
-
|
|
4915
|
-
destroy() {
|
|
4916
|
-
if (this._unsubscribe) {
|
|
4917
|
-
this._unsubscribe();
|
|
4918
|
-
}
|
|
4919
|
-
if (this.element && this.element.parentNode) {
|
|
4920
|
-
this.element.parentNode.removeChild(this.element);
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
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;
|
|
5649
|
+
|
|
5650
|
+
if (this.pingInterval) {
|
|
5651
|
+
clearInterval(this.pingInterval);
|
|
5652
|
+
this.pingInterval = null;
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
5656
|
+
this._scheduleReconnect();
|
|
5657
|
+
}
|
|
5658
|
+
|
|
5659
|
+
_onError(error) {
|
|
5660
|
+
console.error('[WebSocket] Error:', error);
|
|
5661
|
+
this._emit('error', { error });
|
|
5662
|
+
}
|
|
5663
|
+
|
|
5664
|
+
_scheduleReconnect() {
|
|
5665
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
5666
|
+
console.log('[WebSocket] Max reconnect attempts reached');
|
|
5667
|
+
this._emit('reconnect_failed', {});
|
|
5668
|
+
return;
|
|
5669
|
+
}
|
|
5670
|
+
|
|
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
|
+
);
|
|
5676
|
+
|
|
5677
|
+
setTimeout(() => {
|
|
5678
|
+
this.connect();
|
|
5679
|
+
}, delay);
|
|
5680
|
+
}
|
|
5681
|
+
|
|
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
|
+
}
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
|
|
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,
|
|
5706
|
+
});
|
|
5707
|
+
|
|
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);
|
|
5717
|
+
}
|
|
5718
|
+
|
|
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
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
4923
5737
|
}
|
|
4924
5738
|
|
|
4925
5739
|
/**
|
|
@@ -4967,6 +5781,7 @@
|
|
|
4967
5781
|
this._handleWebSocketMessage = this._handleWebSocketMessage.bind(this);
|
|
4968
5782
|
this._handleTypingStarted = this._handleTypingStarted.bind(this);
|
|
4969
5783
|
this._handleTypingStopped = this._handleTypingStopped.bind(this);
|
|
5784
|
+
this._handleConversationClosed = this._handleConversationClosed.bind(this);
|
|
4970
5785
|
}
|
|
4971
5786
|
|
|
4972
5787
|
_render() {
|
|
@@ -4998,6 +5813,8 @@
|
|
|
4998
5813
|
// Conversation list callbacks
|
|
4999
5814
|
onSelectConversation: this._handleSelectConversation.bind(this),
|
|
5000
5815
|
onStartNewConversation: this._handleNewConversationClick.bind(this),
|
|
5816
|
+
// Pre-chat form callbacks
|
|
5817
|
+
onIdentifyContact: this._handleIdentifyContact.bind(this),
|
|
5001
5818
|
// Article/changelog callbacks
|
|
5002
5819
|
onArticleClick: this.messengerOptions.onArticleClick,
|
|
5003
5820
|
onChangelogClick: this.messengerOptions.onChangelogClick,
|
|
@@ -5007,6 +5824,7 @@
|
|
|
5007
5824
|
this.panel.registerView('home', HomeView);
|
|
5008
5825
|
this.panel.registerView('messages', ConversationsView);
|
|
5009
5826
|
this.panel.registerView('chat', ChatView);
|
|
5827
|
+
this.panel.registerView('prechat', PreChatFormView);
|
|
5010
5828
|
this.panel.registerView('help', HelpView);
|
|
5011
5829
|
this.panel.registerView('changelog', ChangelogView);
|
|
5012
5830
|
|
|
@@ -5022,6 +5840,9 @@
|
|
|
5022
5840
|
if (type === 'openChange') {
|
|
5023
5841
|
this._handleOpenChange(data.isOpen);
|
|
5024
5842
|
}
|
|
5843
|
+
if (type === 'conversationChange') {
|
|
5844
|
+
this._handleActiveConversationChange(data.conversationId, data.previousConversationId);
|
|
5845
|
+
}
|
|
5025
5846
|
});
|
|
5026
5847
|
}
|
|
5027
5848
|
|
|
@@ -5036,14 +5857,44 @@
|
|
|
5036
5857
|
}
|
|
5037
5858
|
}
|
|
5038
5859
|
|
|
5860
|
+
/**
|
|
5861
|
+
* Subscribe/unsubscribe to conversation WebSocket channel
|
|
5862
|
+
*/
|
|
5863
|
+
_handleActiveConversationChange(conversationId, previousConversationId) {
|
|
5864
|
+
if (previousConversationId && this.wsService) {
|
|
5865
|
+
this.wsService.send('conversation:unsubscribe', { conversation_id: previousConversationId });
|
|
5866
|
+
}
|
|
5867
|
+
if (conversationId && this.wsService) {
|
|
5868
|
+
this.wsService.send('conversation:subscribe', { conversation_id: conversationId });
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
|
|
5039
5872
|
/**
|
|
5040
5873
|
* Handle starting a new conversation
|
|
5874
|
+
* If there's an existing open conversation, send the message there instead
|
|
5041
5875
|
*/
|
|
5042
|
-
async _handleStartConversation(messageContent) {
|
|
5876
|
+
async _handleStartConversation(messageContent, pendingAttachments) {
|
|
5043
5877
|
try {
|
|
5044
|
-
|
|
5878
|
+
// Check for existing open conversation first
|
|
5879
|
+
const openConversation = this.messengerState.conversations.find(
|
|
5880
|
+
(c) => c.status === 'open'
|
|
5881
|
+
);
|
|
5882
|
+
|
|
5883
|
+
if (openConversation) {
|
|
5884
|
+
// Route message to existing open conversation
|
|
5885
|
+
this.messengerState.setActiveConversation(openConversation.id);
|
|
5886
|
+
await this._handleSendMessage(
|
|
5887
|
+
openConversation.id,
|
|
5888
|
+
{ content: messageContent },
|
|
5889
|
+
pendingAttachments
|
|
5890
|
+
);
|
|
5891
|
+
return openConversation;
|
|
5892
|
+
}
|
|
5893
|
+
|
|
5894
|
+
return await this.startNewConversation(messageContent, '', pendingAttachments);
|
|
5045
5895
|
} catch (error) {
|
|
5046
5896
|
console.error('[MessengerWidget] Failed to start conversation:', error);
|
|
5897
|
+
return null;
|
|
5047
5898
|
}
|
|
5048
5899
|
}
|
|
5049
5900
|
|
|
@@ -5060,13 +5911,88 @@
|
|
|
5060
5911
|
|
|
5061
5912
|
/**
|
|
5062
5913
|
* Handle clicking "new conversation" button
|
|
5914
|
+
* Reuses the most recent open conversation if one exists
|
|
5063
5915
|
*/
|
|
5064
5916
|
_handleNewConversationClick() {
|
|
5065
|
-
//
|
|
5066
|
-
|
|
5917
|
+
// Check for an existing open conversation to reuse
|
|
5918
|
+
const openConversation = this.messengerState.conversations.find(
|
|
5919
|
+
(c) => c.status === 'open'
|
|
5920
|
+
);
|
|
5921
|
+
|
|
5922
|
+
if (openConversation) {
|
|
5923
|
+
// Reuse existing open conversation
|
|
5924
|
+
this.messengerState.setActiveConversation(openConversation.id);
|
|
5925
|
+
this.messengerState.setView('chat');
|
|
5926
|
+
this._handleSelectConversation(openConversation.id);
|
|
5927
|
+
} else {
|
|
5928
|
+
// No open conversation — start a new one
|
|
5929
|
+
this.messengerState.setActiveConversation(null);
|
|
5930
|
+
this.messengerState.setView('chat');
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
|
|
5934
|
+
/**
|
|
5935
|
+
* Handle identifying contact from pre-chat form
|
|
5936
|
+
*/
|
|
5937
|
+
async _handleIdentifyContact(contactData) {
|
|
5938
|
+
try {
|
|
5939
|
+
// Call API to identify/update contact
|
|
5940
|
+
const response = await this.apiService.identifyContact({
|
|
5941
|
+
name: contactData.name,
|
|
5942
|
+
email: contactData.email,
|
|
5943
|
+
});
|
|
5944
|
+
|
|
5945
|
+
if (response.status) {
|
|
5946
|
+
console.log('[MessengerWidget] Contact identified:', contactData.email);
|
|
5947
|
+
|
|
5948
|
+
// Update local user context
|
|
5949
|
+
if (!this.messengerState.userContext) {
|
|
5950
|
+
this.messengerState.userContext = {};
|
|
5951
|
+
}
|
|
5952
|
+
this.messengerState.userContext.name = contactData.name;
|
|
5953
|
+
this.messengerState.userContext.email = contactData.email;
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
return response;
|
|
5957
|
+
} catch (error) {
|
|
5958
|
+
console.error('[MessengerWidget] Failed to identify contact:', error);
|
|
5959
|
+
throw error;
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5963
|
+
async _handleUploadFile(base64Data, filename) {
|
|
5964
|
+
try {
|
|
5965
|
+
const response = await this.apiService.uploadFile(base64Data, filename);
|
|
5966
|
+
if (response.status && response.url) {
|
|
5967
|
+
return response.url;
|
|
5968
|
+
}
|
|
5969
|
+
throw new Error('Upload failed');
|
|
5970
|
+
} catch (error) {
|
|
5971
|
+
console.error('[MessengerWidget] Failed to upload file:', error);
|
|
5972
|
+
throw error;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
async _uploadPendingAttachments(pendingAttachments) {
|
|
5977
|
+
if (!pendingAttachments || pendingAttachments.length === 0) return [];
|
|
5978
|
+
|
|
5979
|
+
const uploaded = [];
|
|
5980
|
+
for (const att of pendingAttachments) {
|
|
5981
|
+
try {
|
|
5982
|
+
const cdnUrl = await this._handleUploadFile(att.preview, att.file.name);
|
|
5983
|
+
uploaded.push({
|
|
5984
|
+
url: cdnUrl,
|
|
5985
|
+
type: att.type.startsWith('image') ? 'image' : 'file',
|
|
5986
|
+
name: att.file.name,
|
|
5987
|
+
});
|
|
5988
|
+
} catch (err) {
|
|
5989
|
+
console.error('[MessengerWidget] Skipping failed attachment upload:', att.file.name, err);
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
return uploaded;
|
|
5067
5993
|
}
|
|
5068
5994
|
|
|
5069
|
-
async _handleSendMessage(conversationId, message) {
|
|
5995
|
+
async _handleSendMessage(conversationId, message, pendingAttachments) {
|
|
5070
5996
|
// Emit event for external listeners
|
|
5071
5997
|
this.sdk.eventBus.emit('messenger:messageSent', {
|
|
5072
5998
|
widget: this,
|
|
@@ -5075,9 +6001,13 @@
|
|
|
5075
6001
|
});
|
|
5076
6002
|
|
|
5077
6003
|
try {
|
|
6004
|
+
// Upload attachments to CDN first
|
|
6005
|
+
const uploadedAttachments = await this._uploadPendingAttachments(pendingAttachments);
|
|
6006
|
+
|
|
5078
6007
|
// Send message through API
|
|
5079
6008
|
const response = await this.apiService.sendMessage(conversationId, {
|
|
5080
6009
|
content: message.content,
|
|
6010
|
+
attachments: uploadedAttachments,
|
|
5081
6011
|
});
|
|
5082
6012
|
|
|
5083
6013
|
if (response.status && response.data) {
|
|
@@ -5114,12 +6044,25 @@
|
|
|
5114
6044
|
_handleWebSocketMessage(data) {
|
|
5115
6045
|
const { conversation_id, message } = data;
|
|
5116
6046
|
|
|
6047
|
+
// Parse attachments from server message
|
|
6048
|
+
let attachments = [];
|
|
6049
|
+
if (message.attachments) {
|
|
6050
|
+
try {
|
|
6051
|
+
attachments = typeof message.attachments === 'string'
|
|
6052
|
+
? JSON.parse(message.attachments)
|
|
6053
|
+
: message.attachments;
|
|
6054
|
+
} catch (e) {
|
|
6055
|
+
// ignore parse errors
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
|
|
5117
6059
|
// Transform message to local format
|
|
5118
6060
|
const localMessage = {
|
|
5119
6061
|
id: message.id,
|
|
5120
6062
|
content: message.content,
|
|
5121
6063
|
isOwn: message.sender_type === 'customer',
|
|
5122
6064
|
timestamp: message.created_at,
|
|
6065
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
5123
6066
|
sender: {
|
|
5124
6067
|
name: message.sender_name || 'Support',
|
|
5125
6068
|
avatarUrl: message.sender_avatar || null,
|
|
@@ -5159,6 +6102,19 @@
|
|
|
5159
6102
|
});
|
|
5160
6103
|
}
|
|
5161
6104
|
|
|
6105
|
+
/**
|
|
6106
|
+
* Handle conversation closed event
|
|
6107
|
+
*/
|
|
6108
|
+
_handleConversationClosed(data) {
|
|
6109
|
+
const conversationId =
|
|
6110
|
+
data?.conversation_id ||
|
|
6111
|
+
data?.id ||
|
|
6112
|
+
data?.conversation?.id;
|
|
6113
|
+
if (!conversationId) return;
|
|
6114
|
+
|
|
6115
|
+
this.messengerState.updateConversation(conversationId, { status: 'closed' });
|
|
6116
|
+
}
|
|
6117
|
+
|
|
5162
6118
|
/**
|
|
5163
6119
|
* Update unread count from API
|
|
5164
6120
|
*/
|
|
@@ -5201,9 +6157,18 @@
|
|
|
5201
6157
|
this._wsUnsubscribers.push(
|
|
5202
6158
|
this.wsService.on('typing_stopped', this._handleTypingStopped)
|
|
5203
6159
|
);
|
|
6160
|
+
this._wsUnsubscribers.push(
|
|
6161
|
+
this.wsService.on('conversation_closed', this._handleConversationClosed)
|
|
6162
|
+
);
|
|
5204
6163
|
this._wsUnsubscribers.push(
|
|
5205
6164
|
this.wsService.on('connected', () => {
|
|
5206
6165
|
console.log('[MessengerWidget] WebSocket connected');
|
|
6166
|
+
// Re-subscribe to active conversation on reconnect
|
|
6167
|
+
if (this.messengerState.activeConversationId) {
|
|
6168
|
+
this.wsService.send('conversation:subscribe', {
|
|
6169
|
+
conversation_id: this.messengerState.activeConversationId,
|
|
6170
|
+
});
|
|
6171
|
+
}
|
|
5207
6172
|
})
|
|
5208
6173
|
);
|
|
5209
6174
|
this._wsUnsubscribers.push(
|
|
@@ -5396,18 +6361,29 @@
|
|
|
5396
6361
|
try {
|
|
5397
6362
|
const response = await this.apiService.getConversation(conversationId);
|
|
5398
6363
|
if (response.status && response.data) {
|
|
5399
|
-
const messages = (response.data.messages || []).map((msg) =>
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
}
|
|
5410
|
-
|
|
6364
|
+
const messages = (response.data.messages || []).map((msg) => {
|
|
6365
|
+
let attachments;
|
|
6366
|
+
if (msg.attachments) {
|
|
6367
|
+
try {
|
|
6368
|
+
attachments = typeof msg.attachments === 'string'
|
|
6369
|
+
? JSON.parse(msg.attachments)
|
|
6370
|
+
: msg.attachments;
|
|
6371
|
+
} catch (e) {
|
|
6372
|
+
// ignore parse errors
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
return {
|
|
6376
|
+
id: msg.id,
|
|
6377
|
+
content: msg.content,
|
|
6378
|
+
isOwn: msg.sender_type === 'customer',
|
|
6379
|
+
timestamp: msg.created_at,
|
|
6380
|
+
attachments: attachments && attachments.length > 0 ? attachments : undefined,
|
|
6381
|
+
sender: {
|
|
6382
|
+
name: msg.sender_name || (msg.sender_type === 'customer' ? 'You' : 'Support'),
|
|
6383
|
+
avatarUrl: msg.sender_avatar || null,
|
|
6384
|
+
},
|
|
6385
|
+
};
|
|
6386
|
+
});
|
|
5411
6387
|
this.messengerState.setMessages(conversationId, messages);
|
|
5412
6388
|
|
|
5413
6389
|
// Mark as read
|
|
@@ -5426,13 +6402,28 @@
|
|
|
5426
6402
|
/**
|
|
5427
6403
|
* Start a new conversation
|
|
5428
6404
|
*/
|
|
5429
|
-
async startNewConversation(message, subject = '') {
|
|
6405
|
+
async startNewConversation(message, subject = '', pendingAttachments = []) {
|
|
5430
6406
|
try {
|
|
6407
|
+
// Upload attachments to CDN first
|
|
6408
|
+
const uploadedAttachments = await this._uploadPendingAttachments(pendingAttachments);
|
|
6409
|
+
|
|
6410
|
+
console.log('[MessengerWidget] Starting conversation...', {
|
|
6411
|
+
message,
|
|
6412
|
+
attachmentCount: uploadedAttachments.length,
|
|
6413
|
+
hasSession: this.apiService.isSessionValid(),
|
|
6414
|
+
sessionToken: this.apiService.sessionToken ? this.apiService.sessionToken.substring(0, 10) + '...' : null,
|
|
6415
|
+
baseURL: this.apiService.baseURL,
|
|
6416
|
+
mock: this.apiService.mock,
|
|
6417
|
+
});
|
|
6418
|
+
|
|
5431
6419
|
const response = await this.apiService.startConversation({
|
|
5432
6420
|
message,
|
|
5433
6421
|
subject,
|
|
6422
|
+
attachments: uploadedAttachments,
|
|
5434
6423
|
});
|
|
5435
6424
|
|
|
6425
|
+
console.log('[MessengerWidget] Conversation response:', response);
|
|
6426
|
+
|
|
5436
6427
|
if (response.status && response.data) {
|
|
5437
6428
|
const conv = response.data;
|
|
5438
6429
|
const newConversation = {
|
|
@@ -5492,6 +6483,12 @@
|
|
|
5492
6483
|
this.messengerState.agentsOnline = response.data.agents_online;
|
|
5493
6484
|
this.messengerState.onlineCount = response.data.online_count || 0;
|
|
5494
6485
|
this.messengerState.responseTime = response.data.response_time || '';
|
|
6486
|
+
|
|
6487
|
+
// Update team avatars from online agents
|
|
6488
|
+
if (response.data.available_agents) {
|
|
6489
|
+
this.messengerState.setTeamAvatarsFromAgents(response.data.available_agents);
|
|
6490
|
+
}
|
|
6491
|
+
|
|
5495
6492
|
this.messengerState._notify('availabilityUpdate', response.data);
|
|
5496
6493
|
return response.data;
|
|
5497
6494
|
}
|
|
@@ -6404,8 +7401,11 @@
|
|
|
6404
7401
|
this.apiService = new APIService({
|
|
6405
7402
|
apiUrl: this.config.apiUrl,
|
|
6406
7403
|
workspace: this.config.workspace,
|
|
7404
|
+
siteId: this.config.siteId,
|
|
7405
|
+
sessionToken: this.config.sessionToken,
|
|
6407
7406
|
userContext: this.config.userContext,
|
|
6408
7407
|
mock: this.config.mock,
|
|
7408
|
+
debug: this.config.debug,
|
|
6409
7409
|
env: this.config.env,
|
|
6410
7410
|
});
|
|
6411
7411
|
|
|
@@ -7201,6 +8201,39 @@
|
|
|
7201
8201
|
opacity: 0.6;
|
|
7202
8202
|
}
|
|
7203
8203
|
|
|
8204
|
+
/* Continue conversation variant */
|
|
8205
|
+
.messenger-home-continue-btn {
|
|
8206
|
+
flex-direction: column;
|
|
8207
|
+
align-items: flex-start;
|
|
8208
|
+
gap: 2px;
|
|
8209
|
+
position: relative;
|
|
8210
|
+
}
|
|
8211
|
+
|
|
8212
|
+
.messenger-home-continue-btn > i {
|
|
8213
|
+
position: absolute;
|
|
8214
|
+
right: 20px;
|
|
8215
|
+
top: 50%;
|
|
8216
|
+
transform: translateY(-50%);
|
|
8217
|
+
}
|
|
8218
|
+
|
|
8219
|
+
.messenger-home-continue-info {
|
|
8220
|
+
display: flex;
|
|
8221
|
+
flex-direction: column;
|
|
8222
|
+
gap: 2px;
|
|
8223
|
+
text-align: left;
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
.messenger-home-continue-label {
|
|
8227
|
+
font-size: 14px;
|
|
8228
|
+
font-weight: 600;
|
|
8229
|
+
}
|
|
8230
|
+
|
|
8231
|
+
.messenger-home-continue-preview {
|
|
8232
|
+
font-size: 12px;
|
|
8233
|
+
opacity: 0.6;
|
|
8234
|
+
font-weight: 400;
|
|
8235
|
+
}
|
|
8236
|
+
|
|
7204
8237
|
/* Featured Card */
|
|
7205
8238
|
.messenger-home-featured {
|
|
7206
8239
|
background: #2c2c2e;
|
|
@@ -7329,7 +8362,7 @@
|
|
|
7329
8362
|
.messenger-conversations-body {
|
|
7330
8363
|
flex: 1;
|
|
7331
8364
|
overflow-y: auto;
|
|
7332
|
-
padding: 12px;
|
|
8365
|
+
padding: 4px 12px 12px 12px;
|
|
7333
8366
|
}
|
|
7334
8367
|
|
|
7335
8368
|
.messenger-conversations-empty {
|
|
@@ -7364,7 +8397,7 @@
|
|
|
7364
8397
|
display: flex;
|
|
7365
8398
|
align-items: flex-start;
|
|
7366
8399
|
gap: 12px;
|
|
7367
|
-
padding: 16px;
|
|
8400
|
+
padding: 10px 16px;
|
|
7368
8401
|
border-radius: 12px;
|
|
7369
8402
|
cursor: pointer;
|
|
7370
8403
|
transition: background 0.2s ease;
|
|
@@ -7496,6 +8529,11 @@
|
|
|
7496
8529
|
color: white;
|
|
7497
8530
|
}
|
|
7498
8531
|
|
|
8532
|
+
.messenger-chat-view {
|
|
8533
|
+
position: relative;
|
|
8534
|
+
overflow: visible;
|
|
8535
|
+
}
|
|
8536
|
+
|
|
7499
8537
|
.messenger-chat-messages {
|
|
7500
8538
|
flex: 1;
|
|
7501
8539
|
overflow-y: auto;
|
|
@@ -7570,7 +8608,7 @@
|
|
|
7570
8608
|
}
|
|
7571
8609
|
|
|
7572
8610
|
.messenger-message-own .messenger-message-bubble {
|
|
7573
|
-
background:
|
|
8611
|
+
background: rgb(29, 78, 216);
|
|
7574
8612
|
color: white;
|
|
7575
8613
|
border-bottom-right-radius: 4px;
|
|
7576
8614
|
}
|
|
@@ -7598,10 +8636,25 @@
|
|
|
7598
8636
|
margin-top: auto;
|
|
7599
8637
|
}
|
|
7600
8638
|
|
|
8639
|
+
/* Conversation Closed Banner */
|
|
8640
|
+
.messenger-closed-banner {
|
|
8641
|
+
display: flex;
|
|
8642
|
+
align-items: center;
|
|
8643
|
+
justify-content: center;
|
|
8644
|
+
gap: 8px;
|
|
8645
|
+
padding: 12px 16px;
|
|
8646
|
+
margin: 16px;
|
|
8647
|
+
background: rgba(52, 199, 89, 0.12);
|
|
8648
|
+
color: #34c759;
|
|
8649
|
+
border-radius: 12px;
|
|
8650
|
+
font-size: 13px;
|
|
8651
|
+
font-weight: 500;
|
|
8652
|
+
}
|
|
8653
|
+
|
|
7601
8654
|
/* Compose Area */
|
|
7602
8655
|
.messenger-chat-compose {
|
|
7603
8656
|
display: flex;
|
|
7604
|
-
align-items:
|
|
8657
|
+
align-items: center;
|
|
7605
8658
|
gap: 8px;
|
|
7606
8659
|
padding: 12px 16px;
|
|
7607
8660
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
@@ -7611,7 +8664,7 @@
|
|
|
7611
8664
|
.messenger-compose-input-wrapper {
|
|
7612
8665
|
flex: 1;
|
|
7613
8666
|
background: #2c2c2e;
|
|
7614
|
-
border-radius:
|
|
8667
|
+
border-radius: 10px;
|
|
7615
8668
|
padding: 8px 16px;
|
|
7616
8669
|
}
|
|
7617
8670
|
|
|
@@ -7647,15 +8700,176 @@
|
|
|
7647
8700
|
flex-shrink: 0;
|
|
7648
8701
|
}
|
|
7649
8702
|
|
|
7650
|
-
.messenger-compose-send:hover:not(:disabled) {
|
|
7651
|
-
background: #0066d6;
|
|
7652
|
-
transform: scale(1.05);
|
|
8703
|
+
.messenger-compose-send:hover:not(:disabled) {
|
|
8704
|
+
background: #0066d6;
|
|
8705
|
+
transform: scale(1.05);
|
|
8706
|
+
}
|
|
8707
|
+
|
|
8708
|
+
.messenger-compose-send:disabled {
|
|
8709
|
+
background: #3c3c3e;
|
|
8710
|
+
color: rgba(255, 255, 255, 0.3);
|
|
8711
|
+
cursor: not-allowed;
|
|
8712
|
+
}
|
|
8713
|
+
|
|
8714
|
+
/* Attach Button */
|
|
8715
|
+
.messenger-compose-attach {
|
|
8716
|
+
width: 40px;
|
|
8717
|
+
height: 40px;
|
|
8718
|
+
background: transparent;
|
|
8719
|
+
border: none;
|
|
8720
|
+
border-radius: 50%;
|
|
8721
|
+
color: rgba(255, 255, 255, 0.5);
|
|
8722
|
+
cursor: pointer;
|
|
8723
|
+
display: flex;
|
|
8724
|
+
align-items: center;
|
|
8725
|
+
justify-content: center;
|
|
8726
|
+
transition: all 0.2s ease;
|
|
8727
|
+
flex-shrink: 0;
|
|
8728
|
+
}
|
|
8729
|
+
|
|
8730
|
+
.messenger-compose-attach:hover:not(:disabled) {
|
|
8731
|
+
color: rgba(255, 255, 255, 0.85);
|
|
8732
|
+
background: rgba(255, 255, 255, 0.08);
|
|
8733
|
+
}
|
|
8734
|
+
|
|
8735
|
+
.messenger-compose-attach:disabled {
|
|
8736
|
+
opacity: 0.3;
|
|
8737
|
+
cursor: not-allowed;
|
|
8738
|
+
}
|
|
8739
|
+
|
|
8740
|
+
/* Attachment Preview Strip */
|
|
8741
|
+
.messenger-compose-attachments-preview {
|
|
8742
|
+
display: none;
|
|
8743
|
+
flex-wrap: wrap;
|
|
8744
|
+
gap: 8px;
|
|
8745
|
+
padding: 8px 16px;
|
|
8746
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
8747
|
+
background: #1c1c1e;
|
|
8748
|
+
}
|
|
8749
|
+
|
|
8750
|
+
.messenger-attachment-preview {
|
|
8751
|
+
position: relative;
|
|
8752
|
+
width: 56px;
|
|
8753
|
+
height: 56px;
|
|
8754
|
+
border-radius: 8px;
|
|
8755
|
+
overflow: hidden;
|
|
8756
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
8757
|
+
}
|
|
8758
|
+
|
|
8759
|
+
.messenger-attachment-thumb {
|
|
8760
|
+
width: 100%;
|
|
8761
|
+
height: 100%;
|
|
8762
|
+
object-fit: cover;
|
|
8763
|
+
display: block;
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
.messenger-attachment-file-icon {
|
|
8767
|
+
display: flex;
|
|
8768
|
+
align-items: center;
|
|
8769
|
+
justify-content: center;
|
|
8770
|
+
background: #2c2c2e;
|
|
8771
|
+
color: rgba(255, 255, 255, 0.5);
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
.messenger-attachment-remove {
|
|
8775
|
+
position: absolute;
|
|
8776
|
+
top: 2px;
|
|
8777
|
+
right: 2px;
|
|
8778
|
+
width: 18px;
|
|
8779
|
+
height: 18px;
|
|
8780
|
+
background: rgba(0, 0, 0, 0.7);
|
|
8781
|
+
border: none;
|
|
8782
|
+
border-radius: 50%;
|
|
8783
|
+
color: white;
|
|
8784
|
+
font-size: 12px;
|
|
8785
|
+
line-height: 1;
|
|
8786
|
+
cursor: pointer;
|
|
8787
|
+
display: flex;
|
|
8788
|
+
align-items: center;
|
|
8789
|
+
justify-content: center;
|
|
8790
|
+
padding: 0;
|
|
8791
|
+
transition: background 0.15s ease;
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
.messenger-attachment-remove:hover {
|
|
8795
|
+
background: rgba(255, 59, 48, 0.85);
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8798
|
+
/* Message Attachments (inline images & file links) */
|
|
8799
|
+
.messenger-message-image {
|
|
8800
|
+
max-width: 220px;
|
|
8801
|
+
max-height: 200px;
|
|
8802
|
+
width: auto;
|
|
8803
|
+
height: auto;
|
|
8804
|
+
border-radius: 8px;
|
|
8805
|
+
margin-top: 4px;
|
|
8806
|
+
cursor: pointer;
|
|
8807
|
+
object-fit: contain;
|
|
8808
|
+
display: block;
|
|
8809
|
+
}
|
|
8810
|
+
|
|
8811
|
+
.messenger-message-file {
|
|
8812
|
+
display: inline-flex;
|
|
8813
|
+
align-items: center;
|
|
8814
|
+
gap: 6px;
|
|
8815
|
+
margin-top: 4px;
|
|
8816
|
+
padding: 8px 12px;
|
|
8817
|
+
border-radius: 8px;
|
|
8818
|
+
background: #2c2c2e;
|
|
8819
|
+
color: #60a5fa;
|
|
8820
|
+
text-decoration: none;
|
|
8821
|
+
font-size: 13px;
|
|
8822
|
+
transition: background 0.15s ease;
|
|
8823
|
+
max-width: 100%;
|
|
8824
|
+
word-break: break-all;
|
|
8825
|
+
cursor: pointer;
|
|
8826
|
+
}
|
|
8827
|
+
|
|
8828
|
+
.messenger-message-file:hover {
|
|
8829
|
+
background: #3c3c3e;
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
.messenger-file-download-icon {
|
|
8833
|
+
margin-left: auto;
|
|
8834
|
+
opacity: 0.5;
|
|
8835
|
+
flex-shrink: 0;
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8838
|
+
.messenger-message-file:hover .messenger-file-download-icon {
|
|
8839
|
+
opacity: 1;
|
|
8840
|
+
}
|
|
8841
|
+
|
|
8842
|
+
/* Light theme overrides for attachments */
|
|
8843
|
+
.theme-light .messenger-compose-attach {
|
|
8844
|
+
color: #9ca3af;
|
|
8845
|
+
}
|
|
8846
|
+
|
|
8847
|
+
.theme-light .messenger-compose-attach:hover:not(:disabled) {
|
|
8848
|
+
color: #374151;
|
|
8849
|
+
background: #f3f4f6;
|
|
8850
|
+
}
|
|
8851
|
+
|
|
8852
|
+
.theme-light .messenger-compose-attachments-preview {
|
|
8853
|
+
background: #ffffff;
|
|
8854
|
+
border-top-color: #e5e7eb;
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8857
|
+
.theme-light .messenger-attachment-preview {
|
|
8858
|
+
border-color: #e5e7eb;
|
|
8859
|
+
}
|
|
8860
|
+
|
|
8861
|
+
.theme-light .messenger-attachment-file-icon {
|
|
8862
|
+
background: #f3f4f6;
|
|
8863
|
+
color: #6b7280;
|
|
8864
|
+
}
|
|
8865
|
+
|
|
8866
|
+
.theme-light .messenger-message-file {
|
|
8867
|
+
background: #f3f4f6;
|
|
8868
|
+
color: #2563eb;
|
|
7653
8869
|
}
|
|
7654
8870
|
|
|
7655
|
-
.messenger-
|
|
7656
|
-
background: #
|
|
7657
|
-
color: rgba(255, 255, 255, 0.3);
|
|
7658
|
-
cursor: not-allowed;
|
|
8871
|
+
.theme-light .messenger-message-file:hover {
|
|
8872
|
+
background: #e5e7eb;
|
|
7659
8873
|
}
|
|
7660
8874
|
|
|
7661
8875
|
/* ========================================
|
|
@@ -7972,7 +9186,7 @@
|
|
|
7972
9186
|
|
|
7973
9187
|
.messenger-nav {
|
|
7974
9188
|
display: flex;
|
|
7975
|
-
padding: 8px
|
|
9189
|
+
padding: 4px 8px;
|
|
7976
9190
|
gap: 4px;
|
|
7977
9191
|
}
|
|
7978
9192
|
|
|
@@ -8025,8 +9239,8 @@
|
|
|
8025
9239
|
}
|
|
8026
9240
|
|
|
8027
9241
|
.messenger-nav-label {
|
|
8028
|
-
font-size:
|
|
8029
|
-
font-weight:
|
|
9242
|
+
font-size: 11px;
|
|
9243
|
+
font-weight: 500;
|
|
8030
9244
|
color: rgba(255, 255, 255, 0.5);
|
|
8031
9245
|
transition: color 0.2s ease;
|
|
8032
9246
|
}
|
|
@@ -8285,7 +9499,7 @@
|
|
|
8285
9499
|
}
|
|
8286
9500
|
|
|
8287
9501
|
.theme-light .messenger-message-own .messenger-message-bubble {
|
|
8288
|
-
background:
|
|
9502
|
+
background: rgb(29, 78, 216);
|
|
8289
9503
|
color: #ffffff;
|
|
8290
9504
|
}
|
|
8291
9505
|
|
|
@@ -8293,6 +9507,11 @@
|
|
|
8293
9507
|
color: #86868b;
|
|
8294
9508
|
}
|
|
8295
9509
|
|
|
9510
|
+
.theme-light .messenger-closed-banner {
|
|
9511
|
+
background: rgba(52, 199, 89, 0.1);
|
|
9512
|
+
color: #22883a;
|
|
9513
|
+
}
|
|
9514
|
+
|
|
8296
9515
|
.theme-light .messenger-chat-compose {
|
|
8297
9516
|
background: #ffffff;
|
|
8298
9517
|
border-top-color: #e5e5e7;
|
|
@@ -8559,6 +9778,191 @@
|
|
|
8559
9778
|
}
|
|
8560
9779
|
}
|
|
8561
9780
|
|
|
9781
|
+
/* ========================================
|
|
9782
|
+
Pre-Chat Form View (Transparent Overlay)
|
|
9783
|
+
======================================== */
|
|
9784
|
+
|
|
9785
|
+
.messenger-prechat-view {
|
|
9786
|
+
background: transparent;
|
|
9787
|
+
position: relative;
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9790
|
+
.messenger-prechat-overlay {
|
|
9791
|
+
position: absolute;
|
|
9792
|
+
top: 0;
|
|
9793
|
+
left: 0;
|
|
9794
|
+
right: 0;
|
|
9795
|
+
bottom: 0;
|
|
9796
|
+
background: rgba(0, 0, 0, 0.5);
|
|
9797
|
+
backdrop-filter: blur(2px);
|
|
9798
|
+
display: flex;
|
|
9799
|
+
align-items: flex-end;
|
|
9800
|
+
padding: 16px;
|
|
9801
|
+
animation: messenger-fade-in 0.2s ease;
|
|
9802
|
+
}
|
|
9803
|
+
|
|
9804
|
+
.messenger-prechat-card {
|
|
9805
|
+
background: #1c1c1e;
|
|
9806
|
+
border-radius: 16px;
|
|
9807
|
+
padding: 20px;
|
|
9808
|
+
width: 100%;
|
|
9809
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
9810
|
+
animation: messenger-slide-up 0.25s ease;
|
|
9811
|
+
}
|
|
9812
|
+
|
|
9813
|
+
.messenger-prechat-card h4 {
|
|
9814
|
+
margin: 0 0 14px;
|
|
9815
|
+
font-size: 15px;
|
|
9816
|
+
font-weight: 600;
|
|
9817
|
+
color: white;
|
|
9818
|
+
text-align: center;
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9821
|
+
.messenger-prechat-form {
|
|
9822
|
+
display: flex;
|
|
9823
|
+
flex-direction: column;
|
|
9824
|
+
gap: 10px;
|
|
9825
|
+
}
|
|
9826
|
+
|
|
9827
|
+
.messenger-prechat-fields {
|
|
9828
|
+
display: flex;
|
|
9829
|
+
flex-direction: column;
|
|
9830
|
+
gap: 8px;
|
|
9831
|
+
}
|
|
9832
|
+
|
|
9833
|
+
.messenger-prechat-input {
|
|
9834
|
+
width: 100%;
|
|
9835
|
+
padding: 11px 14px;
|
|
9836
|
+
background: #2c2c2e;
|
|
9837
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
9838
|
+
border-radius: 10px;
|
|
9839
|
+
color: white;
|
|
9840
|
+
font-size: 14px;
|
|
9841
|
+
font-family: inherit;
|
|
9842
|
+
outline: none;
|
|
9843
|
+
transition: border-color 0.2s ease;
|
|
9844
|
+
}
|
|
9845
|
+
|
|
9846
|
+
.messenger-prechat-input:focus {
|
|
9847
|
+
border-color: #007aff;
|
|
9848
|
+
}
|
|
9849
|
+
|
|
9850
|
+
.messenger-prechat-input::placeholder {
|
|
9851
|
+
color: rgba(255, 255, 255, 0.4);
|
|
9852
|
+
}
|
|
9853
|
+
|
|
9854
|
+
.messenger-prechat-error {
|
|
9855
|
+
font-size: 12px;
|
|
9856
|
+
color: #ef4444;
|
|
9857
|
+
display: none;
|
|
9858
|
+
text-align: center;
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
.messenger-prechat-actions {
|
|
9862
|
+
display: flex;
|
|
9863
|
+
gap: 10px;
|
|
9864
|
+
margin-top: 4px;
|
|
9865
|
+
}
|
|
9866
|
+
|
|
9867
|
+
.messenger-prechat-skip {
|
|
9868
|
+
flex: 1;
|
|
9869
|
+
padding: 11px 14px;
|
|
9870
|
+
background: transparent;
|
|
9871
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
9872
|
+
border-radius: 20px;
|
|
9873
|
+
color: rgba(255, 255, 255, 0.7);
|
|
9874
|
+
font-size: 14px;
|
|
9875
|
+
font-weight: 500;
|
|
9876
|
+
cursor: pointer;
|
|
9877
|
+
transition: all 0.2s ease;
|
|
9878
|
+
}
|
|
9879
|
+
|
|
9880
|
+
.messenger-prechat-skip:hover {
|
|
9881
|
+
background: rgba(255, 255, 255, 0.05);
|
|
9882
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
9883
|
+
}
|
|
9884
|
+
|
|
9885
|
+
.messenger-prechat-submit {
|
|
9886
|
+
flex: 1;
|
|
9887
|
+
display: flex;
|
|
9888
|
+
align-items: center;
|
|
9889
|
+
justify-content: center;
|
|
9890
|
+
gap: 6px;
|
|
9891
|
+
padding: 11px 14px;
|
|
9892
|
+
background: #007aff;
|
|
9893
|
+
border: none;
|
|
9894
|
+
border-radius: 20px;
|
|
9895
|
+
color: white;
|
|
9896
|
+
font-size: 14px;
|
|
9897
|
+
font-weight: 600;
|
|
9898
|
+
cursor: pointer;
|
|
9899
|
+
transition: all 0.2s ease;
|
|
9900
|
+
}
|
|
9901
|
+
|
|
9902
|
+
.messenger-prechat-submit:hover:not(:disabled) {
|
|
9903
|
+
background: #0066d6;
|
|
9904
|
+
}
|
|
9905
|
+
|
|
9906
|
+
.messenger-prechat-submit:disabled {
|
|
9907
|
+
background: #3c3c3e;
|
|
9908
|
+
color: rgba(255, 255, 255, 0.4);
|
|
9909
|
+
cursor: not-allowed;
|
|
9910
|
+
}
|
|
9911
|
+
|
|
9912
|
+
.messenger-prechat-submit-loading {
|
|
9913
|
+
display: inline-flex;
|
|
9914
|
+
align-items: center;
|
|
9915
|
+
animation: messenger-spin 1s linear infinite;
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9918
|
+
@keyframes messenger-spin {
|
|
9919
|
+
from { transform: rotate(0deg); }
|
|
9920
|
+
to { transform: rotate(360deg); }
|
|
9921
|
+
}
|
|
9922
|
+
|
|
9923
|
+
/* Light Theme - Pre-Chat Form */
|
|
9924
|
+
.theme-light .messenger-prechat-overlay {
|
|
9925
|
+
background: rgba(255, 255, 255, 0.6);
|
|
9926
|
+
}
|
|
9927
|
+
|
|
9928
|
+
.theme-light .messenger-prechat-card {
|
|
9929
|
+
background: #ffffff;
|
|
9930
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
9931
|
+
}
|
|
9932
|
+
|
|
9933
|
+
.theme-light .messenger-prechat-card h4 {
|
|
9934
|
+
color: #1d1d1f;
|
|
9935
|
+
}
|
|
9936
|
+
|
|
9937
|
+
.theme-light .messenger-prechat-input {
|
|
9938
|
+
background: #f5f5f7;
|
|
9939
|
+
border-color: #e5e5e7;
|
|
9940
|
+
color: #1d1d1f;
|
|
9941
|
+
}
|
|
9942
|
+
|
|
9943
|
+
.theme-light .messenger-prechat-input:focus {
|
|
9944
|
+
border-color: #007aff;
|
|
9945
|
+
background: #ffffff;
|
|
9946
|
+
}
|
|
9947
|
+
|
|
9948
|
+
.theme-light .messenger-prechat-input::placeholder {
|
|
9949
|
+
color: #86868b;
|
|
9950
|
+
}
|
|
9951
|
+
|
|
9952
|
+
.theme-light .messenger-prechat-skip {
|
|
9953
|
+
border-color: #e5e5e7;
|
|
9954
|
+
color: #6e6e73;
|
|
9955
|
+
}
|
|
9956
|
+
|
|
9957
|
+
.theme-light .messenger-prechat-skip:hover {
|
|
9958
|
+
background: #f5f5f7;
|
|
9959
|
+
}
|
|
9960
|
+
|
|
9961
|
+
.theme-light .messenger-prechat-submit:disabled {
|
|
9962
|
+
background: #e5e5e7;
|
|
9963
|
+
color: #c7c7cc;
|
|
9964
|
+
}
|
|
9965
|
+
|
|
8562
9966
|
/* ========================================
|
|
8563
9967
|
Animations
|
|
8564
9968
|
======================================== */
|
|
@@ -8590,6 +9994,173 @@
|
|
|
8590
9994
|
transition: none;
|
|
8591
9995
|
}
|
|
8592
9996
|
}
|
|
9997
|
+
|
|
9998
|
+
/* ========================================
|
|
9999
|
+
Email Collection Overlay (Bottom Sheet)
|
|
10000
|
+
======================================== */
|
|
10001
|
+
|
|
10002
|
+
.messenger-email-overlay {
|
|
10003
|
+
position: absolute;
|
|
10004
|
+
bottom: -56px;
|
|
10005
|
+
left: 0;
|
|
10006
|
+
right: 0;
|
|
10007
|
+
top: 0;
|
|
10008
|
+
display: flex;
|
|
10009
|
+
align-items: flex-end;
|
|
10010
|
+
z-index: 20;
|
|
10011
|
+
background: rgba(0, 0, 0, 0.08);
|
|
10012
|
+
pointer-events: auto;
|
|
10013
|
+
}
|
|
10014
|
+
|
|
10015
|
+
.messenger-email-card {
|
|
10016
|
+
width: 100%;
|
|
10017
|
+
background: #ffffff;
|
|
10018
|
+
border-radius: 0;
|
|
10019
|
+
padding: 16px 16px 72px;
|
|
10020
|
+
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
|
|
10021
|
+
animation: messenger-slide-up 0.25s ease;
|
|
10022
|
+
}
|
|
10023
|
+
|
|
10024
|
+
.messenger-email-card h4 {
|
|
10025
|
+
margin: 0 0 2px;
|
|
10026
|
+
font-size: 13px;
|
|
10027
|
+
font-weight: 600;
|
|
10028
|
+
color: #1d1d1f;
|
|
10029
|
+
text-align: center;
|
|
10030
|
+
}
|
|
10031
|
+
|
|
10032
|
+
.messenger-email-card p {
|
|
10033
|
+
margin: 0 0 10px;
|
|
10034
|
+
font-size: 11px;
|
|
10035
|
+
color: #6b7280;
|
|
10036
|
+
text-align: center;
|
|
10037
|
+
}
|
|
10038
|
+
|
|
10039
|
+
.messenger-email-name,
|
|
10040
|
+
.messenger-email-input {
|
|
10041
|
+
width: 100%;
|
|
10042
|
+
padding: 8px 10px;
|
|
10043
|
+
background: #f3f4f6;
|
|
10044
|
+
border: 1px solid transparent;
|
|
10045
|
+
border-radius: 8px;
|
|
10046
|
+
color: #1d1d1f;
|
|
10047
|
+
font-size: 12px;
|
|
10048
|
+
font-family: inherit;
|
|
10049
|
+
outline: none;
|
|
10050
|
+
margin-bottom: 6px;
|
|
10051
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
10052
|
+
}
|
|
10053
|
+
|
|
10054
|
+
.messenger-email-name:focus,
|
|
10055
|
+
.messenger-email-input:focus {
|
|
10056
|
+
border-color: #007aff;
|
|
10057
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.12);
|
|
10058
|
+
background: #ffffff;
|
|
10059
|
+
}
|
|
10060
|
+
|
|
10061
|
+
.messenger-email-name::placeholder,
|
|
10062
|
+
.messenger-email-input::placeholder {
|
|
10063
|
+
color: #9ca3af;
|
|
10064
|
+
}
|
|
10065
|
+
|
|
10066
|
+
.messenger-email-actions {
|
|
10067
|
+
display: flex;
|
|
10068
|
+
gap: 8px;
|
|
10069
|
+
margin-top: 4px;
|
|
10070
|
+
}
|
|
10071
|
+
|
|
10072
|
+
.messenger-email-submit {
|
|
10073
|
+
flex: 1.2;
|
|
10074
|
+
padding: 7px 12px;
|
|
10075
|
+
background: #007aff;
|
|
10076
|
+
border: none;
|
|
10077
|
+
border-radius: 8px;
|
|
10078
|
+
color: white;
|
|
10079
|
+
font-size: 12px;
|
|
10080
|
+
font-weight: 600;
|
|
10081
|
+
cursor: pointer;
|
|
10082
|
+
transition: all 0.2s ease;
|
|
10083
|
+
}
|
|
10084
|
+
|
|
10085
|
+
.messenger-email-submit:hover:not(:disabled) {
|
|
10086
|
+
background: #0066d6;
|
|
10087
|
+
}
|
|
10088
|
+
|
|
10089
|
+
.messenger-email-submit:disabled {
|
|
10090
|
+
background: #d1d5db;
|
|
10091
|
+
color: #9ca3af;
|
|
10092
|
+
cursor: not-allowed;
|
|
10093
|
+
}
|
|
10094
|
+
|
|
10095
|
+
.messenger-email-skip {
|
|
10096
|
+
flex: 0.8;
|
|
10097
|
+
padding: 7px 12px;
|
|
10098
|
+
background: #ffffff;
|
|
10099
|
+
border: 1px solid #e5e5e7;
|
|
10100
|
+
border-radius: 8px;
|
|
10101
|
+
color: #4b5563;
|
|
10102
|
+
font-size: 12px;
|
|
10103
|
+
font-weight: 500;
|
|
10104
|
+
cursor: pointer;
|
|
10105
|
+
transition: all 0.2s ease;
|
|
10106
|
+
}
|
|
10107
|
+
|
|
10108
|
+
.messenger-email-skip:hover {
|
|
10109
|
+
background: #f9fafb;
|
|
10110
|
+
border-color: #d1d5db;
|
|
10111
|
+
}
|
|
10112
|
+
|
|
10113
|
+
/* Dark Theme - Email Overlay */
|
|
10114
|
+
.theme-dark .messenger-email-overlay {
|
|
10115
|
+
background: rgba(0, 0, 0, 0.3);
|
|
10116
|
+
}
|
|
10117
|
+
|
|
10118
|
+
.theme-dark .messenger-email-card {
|
|
10119
|
+
background: #1c1c1e;
|
|
10120
|
+
}
|
|
10121
|
+
|
|
10122
|
+
.theme-dark .messenger-email-card h4 {
|
|
10123
|
+
color: white;
|
|
10124
|
+
}
|
|
10125
|
+
|
|
10126
|
+
.theme-dark .messenger-email-card p {
|
|
10127
|
+
color: rgba(255, 255, 255, 0.6);
|
|
10128
|
+
}
|
|
10129
|
+
|
|
10130
|
+
.theme-dark .messenger-email-name,
|
|
10131
|
+
.theme-dark .messenger-email-input {
|
|
10132
|
+
background: #2c2c2e;
|
|
10133
|
+
border-color: transparent;
|
|
10134
|
+
color: white;
|
|
10135
|
+
}
|
|
10136
|
+
|
|
10137
|
+
.theme-dark .messenger-email-name:focus,
|
|
10138
|
+
.theme-dark .messenger-email-input:focus {
|
|
10139
|
+
border-color: #007aff;
|
|
10140
|
+
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.25);
|
|
10141
|
+
background: #1c1c1e;
|
|
10142
|
+
}
|
|
10143
|
+
|
|
10144
|
+
.theme-dark .messenger-email-name::placeholder,
|
|
10145
|
+
.theme-dark .messenger-email-input::placeholder {
|
|
10146
|
+
color: rgba(255, 255, 255, 0.4);
|
|
10147
|
+
}
|
|
10148
|
+
|
|
10149
|
+
.theme-dark .messenger-email-submit:disabled {
|
|
10150
|
+
background: #3c3c3e;
|
|
10151
|
+
color: rgba(255, 255, 255, 0.4);
|
|
10152
|
+
}
|
|
10153
|
+
|
|
10154
|
+
.theme-dark .messenger-email-skip {
|
|
10155
|
+
background: transparent;
|
|
10156
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
10157
|
+
color: rgba(255, 255, 255, 0.8);
|
|
10158
|
+
}
|
|
10159
|
+
|
|
10160
|
+
.theme-dark .messenger-email-skip:hover {
|
|
10161
|
+
background: rgba(255, 255, 255, 0.05);
|
|
10162
|
+
border-color: rgba(255, 255, 255, 0.25);
|
|
10163
|
+
}
|
|
8593
10164
|
`;
|
|
8594
10165
|
|
|
8595
10166
|
const baseStyles = `
|
|
@@ -10029,7 +11600,7 @@
|
|
|
10029
11600
|
EventBus,
|
|
10030
11601
|
APIService,
|
|
10031
11602
|
SDKError,
|
|
10032
|
-
APIError,
|
|
11603
|
+
APIError: APIError$1,
|
|
10033
11604
|
WidgetError,
|
|
10034
11605
|
ConfigError,
|
|
10035
11606
|
ValidationError,
|
|
@@ -10099,7 +11670,7 @@
|
|
|
10099
11670
|
handleDOMReady();
|
|
10100
11671
|
}
|
|
10101
11672
|
|
|
10102
|
-
exports.APIError = APIError;
|
|
11673
|
+
exports.APIError = APIError$1;
|
|
10103
11674
|
exports.APIService = APIService;
|
|
10104
11675
|
exports.BaseWidget = BaseWidget;
|
|
10105
11676
|
exports.ButtonWidget = ButtonWidget;
|