@xcelsior/ui-chat 2.0.4 → 2.0.6
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/index.d.mts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +1043 -427
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1010 -397
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/BookingCancelledCard.tsx +103 -0
- package/src/components/BookingCards.stories.tsx +102 -0
- package/src/components/BookingConfirmationCard.tsx +170 -0
- package/src/components/BookingSlotPicker.stories.tsx +87 -0
- package/src/components/BookingSlotPicker.tsx +256 -0
- package/src/components/BrandIcons.stories.tsx +32 -1
- package/src/components/BrandIcons.tsx +21 -17
- package/src/components/Chat.tsx +43 -9
- package/src/components/ChatWidget.tsx +30 -2
- package/src/components/MessageItem.tsx +83 -72
- package/src/components/MessageList.tsx +4 -0
- package/src/hooks/useDraggablePosition.ts +147 -42
- package/src/hooks/useMessages.ts +106 -53
- package/src/hooks/useWebSocket.ts +17 -4
- package/src/index.tsx +11 -0
- package/src/types.ts +39 -2
- package/src/utils/api.ts +1 -0
- package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
- package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
- package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
- package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
- package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
- package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
- package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
- package/storybook-static/assets/{Color-YHDXOIA2-CSuNIR0a.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-CFRXHY34-dpuOKTQp.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
- package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
- package/storybook-static/assets/{MessageItem.stories-CsxqSqu-.js → MessageItem.stories-Cs5Vtkle.js} +2 -2
- package/storybook-static/assets/{entry-preview-C_-WO6GJ.js → entry-preview-vcpiajAT.js} +1 -1
- package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
- package/storybook-static/assets/{iframe-BXTccXxS.js → iframe-Cx1n-SeE.js} +2 -2
- package/storybook-static/assets/{preview-Cyx3pE7Q.js → preview-Do3b3dZv.js} +2 -2
- package/storybook-static/iframe.html +1 -1
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
- package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
- package/storybook-static/assets/Chat.stories-BkbpOOSG.js +0 -830
- package/storybook-static/assets/MessageItem-Dlb6dSKL.js +0 -14
package/dist/index.js
CHANGED
|
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.tsx
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BookingCancelledCard: () => BookingCancelledCard,
|
|
34
|
+
BookingConfirmationCard: () => BookingConfirmationCard,
|
|
35
|
+
BookingSlotPicker: () => BookingSlotPicker,
|
|
33
36
|
Chat: () => Chat,
|
|
34
37
|
ChatHeader: () => ChatHeader,
|
|
35
38
|
ChatInput: () => ChatInput,
|
|
@@ -50,7 +53,7 @@ __export(index_exports, {
|
|
|
50
53
|
module.exports = __toCommonJS(index_exports);
|
|
51
54
|
|
|
52
55
|
// src/components/ChatWidget.tsx
|
|
53
|
-
var
|
|
56
|
+
var import_react11 = require("react");
|
|
54
57
|
|
|
55
58
|
// src/hooks/useWebSocket.ts
|
|
56
59
|
var import_react = require("react");
|
|
@@ -63,6 +66,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
63
66
|
const reconnectAttemptsRef = (0, import_react.useRef)(0);
|
|
64
67
|
const messageHandlerRef = (0, import_react.useRef)(null);
|
|
65
68
|
const abortedRef = (0, import_react.useRef)(false);
|
|
69
|
+
const connectionGenerationRef = (0, import_react.useRef)(0);
|
|
66
70
|
const maxReconnectAttempts = 5;
|
|
67
71
|
const reconnectDelay = 3e3;
|
|
68
72
|
const isUsingExternalWs = !!externalWebSocket;
|
|
@@ -74,9 +78,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
74
78
|
try {
|
|
75
79
|
const message = JSON.parse(event.data);
|
|
76
80
|
setLastMessage(message);
|
|
77
|
-
if (message.type === "
|
|
78
|
-
config.onMessageReceived?.(message.data);
|
|
79
|
-
} else if (message.type === "error") {
|
|
81
|
+
if (message.type === "error") {
|
|
80
82
|
const err = new Error(message.data?.message || "WebSocket error");
|
|
81
83
|
setError(err);
|
|
82
84
|
config.onError?.(err);
|
|
@@ -96,6 +98,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
96
98
|
}, []);
|
|
97
99
|
const connect = (0, import_react.useCallback)(() => {
|
|
98
100
|
if (abortedRef.current) return;
|
|
101
|
+
const generation = ++connectionGenerationRef.current;
|
|
99
102
|
console.log("connecting to WebSocket...", config.currentUser, config.conversationId);
|
|
100
103
|
try {
|
|
101
104
|
if (wsRef.current) {
|
|
@@ -115,6 +118,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
115
118
|
}
|
|
116
119
|
const ws = new WebSocket(url.toString());
|
|
117
120
|
ws.onopen = () => {
|
|
121
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
118
122
|
if (abortedRef.current) {
|
|
119
123
|
ws.close(1e3, "Effect cleaned up");
|
|
120
124
|
return;
|
|
@@ -126,6 +130,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
126
130
|
config.onConnectionChange?.(true);
|
|
127
131
|
};
|
|
128
132
|
ws.onerror = (event) => {
|
|
133
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
129
134
|
if (abortedRef.current) return;
|
|
130
135
|
console.error("WebSocket error:", event);
|
|
131
136
|
const err = new Error("WebSocket connection error");
|
|
@@ -133,6 +138,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
133
138
|
config.onError?.(err);
|
|
134
139
|
};
|
|
135
140
|
ws.onclose = (event) => {
|
|
141
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
136
142
|
if (abortedRef.current) return;
|
|
137
143
|
console.log("WebSocket closed:", event.code, event.reason);
|
|
138
144
|
setIsConnected(false);
|
|
@@ -234,7 +240,8 @@ async function fetchMessages(baseUrl, params, headers) {
|
|
|
234
240
|
headers: {
|
|
235
241
|
"Content-Type": "application/json",
|
|
236
242
|
...headers
|
|
237
|
-
}
|
|
243
|
+
},
|
|
244
|
+
timeout: 8e3
|
|
238
245
|
});
|
|
239
246
|
return {
|
|
240
247
|
data: response.data.data ?? [],
|
|
@@ -251,7 +258,8 @@ async function fetchMessages(baseUrl, params, headers) {
|
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
// src/hooks/useMessages.ts
|
|
254
|
-
var BOT_THINKING_TIMEOUT =
|
|
261
|
+
var BOT_THINKING_TIMEOUT = 15e3;
|
|
262
|
+
var AGENT_STATUSES = /* @__PURE__ */ new Set(["agent_active", "pending_agent"]);
|
|
255
263
|
function useMessages(websocket, config) {
|
|
256
264
|
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
257
265
|
const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
|
|
@@ -261,6 +269,7 @@ function useMessages(websocket, config) {
|
|
|
261
269
|
const [isLoadingMore, setIsLoadingMore] = (0, import_react2.useState)(false);
|
|
262
270
|
const [isBotThinking, setIsBotThinking] = (0, import_react2.useState)(false);
|
|
263
271
|
const botThinkingTimerRef = (0, import_react2.useRef)(null);
|
|
272
|
+
const agentActiveRef = (0, import_react2.useRef)(false);
|
|
264
273
|
const { httpApiUrl, conversationId, headers, onError, toast } = config;
|
|
265
274
|
const headersWithApiKey = (0, import_react2.useMemo)(
|
|
266
275
|
() => ({
|
|
@@ -269,11 +278,33 @@ function useMessages(websocket, config) {
|
|
|
269
278
|
}),
|
|
270
279
|
[headers, config.apiKey]
|
|
271
280
|
);
|
|
281
|
+
const clearBotThinking = (0, import_react2.useCallback)(() => {
|
|
282
|
+
setIsBotThinking(false);
|
|
283
|
+
if (botThinkingTimerRef.current) {
|
|
284
|
+
clearTimeout(botThinkingTimerRef.current);
|
|
285
|
+
botThinkingTimerRef.current = null;
|
|
286
|
+
}
|
|
287
|
+
}, []);
|
|
272
288
|
(0, import_react2.useEffect)(() => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
if (!httpApiUrl || !conversationId) return;
|
|
290
|
+
const fetchStatus = async () => {
|
|
291
|
+
try {
|
|
292
|
+
const url = `${httpApiUrl}/conversation?conversationId=${encodeURIComponent(conversationId)}`;
|
|
293
|
+
const res = await fetch(url, { headers: headersWithApiKey });
|
|
294
|
+
if (!res.ok) return;
|
|
295
|
+
const json = await res.json();
|
|
296
|
+
const conv = json.data?.conversation ?? json.data;
|
|
297
|
+
if (conv?.status && AGENT_STATUSES.has(conv.status)) {
|
|
298
|
+
agentActiveRef.current = true;
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
276
301
|
}
|
|
302
|
+
};
|
|
303
|
+
fetchStatus();
|
|
304
|
+
}, [httpApiUrl, conversationId, headersWithApiKey]);
|
|
305
|
+
(0, import_react2.useEffect)(() => {
|
|
306
|
+
if (!httpApiUrl || !conversationId) return;
|
|
307
|
+
const loadMessages = async () => {
|
|
277
308
|
setIsLoading(true);
|
|
278
309
|
setError(null);
|
|
279
310
|
try {
|
|
@@ -285,6 +316,9 @@ function useMessages(websocket, config) {
|
|
|
285
316
|
setMessages(result.data);
|
|
286
317
|
setNextPageToken(result.nextPageToken);
|
|
287
318
|
setHasMore(!!result.nextPageToken);
|
|
319
|
+
if (result.data.some((m) => m.senderType === "agent")) {
|
|
320
|
+
agentActiveRef.current = true;
|
|
321
|
+
}
|
|
288
322
|
} catch (err) {
|
|
289
323
|
const error2 = err instanceof Error ? err : new Error("Failed to load messages");
|
|
290
324
|
setError(error2);
|
|
@@ -300,33 +334,55 @@ function useMessages(websocket, config) {
|
|
|
300
334
|
(0, import_react2.useEffect)(() => {
|
|
301
335
|
if (websocket.lastMessage?.type === "message" && websocket.lastMessage.data) {
|
|
302
336
|
const newMessage = websocket.lastMessage.data;
|
|
303
|
-
if (conversationId && newMessage.conversationId !== conversationId)
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
337
|
+
if (conversationId && newMessage.conversationId !== conversationId) return;
|
|
306
338
|
setMessages((prev) => {
|
|
307
|
-
if (prev.some((msg) => msg.id === newMessage.id))
|
|
308
|
-
|
|
339
|
+
if (prev.some((msg) => msg.id === newMessage.id)) return prev;
|
|
340
|
+
const newMsgTime = new Date(newMessage.createdAt).getTime();
|
|
341
|
+
const tempIdx = prev.findIndex(
|
|
342
|
+
(msg) => msg.id.startsWith("temp-") && msg.content === newMessage.content && msg.senderId === newMessage.senderId && Math.abs(new Date(msg.createdAt).getTime() - newMsgTime) < 1e4
|
|
343
|
+
);
|
|
344
|
+
if (tempIdx >= 0) {
|
|
345
|
+
const next = [...prev];
|
|
346
|
+
next[tempIdx] = newMessage;
|
|
347
|
+
return next;
|
|
309
348
|
}
|
|
310
349
|
return [...prev, newMessage];
|
|
311
350
|
});
|
|
312
|
-
if (newMessage.senderType === "
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
351
|
+
if (newMessage.senderType === "agent") {
|
|
352
|
+
agentActiveRef.current = true;
|
|
353
|
+
}
|
|
354
|
+
if (newMessage.senderType !== "customer") {
|
|
355
|
+
clearBotThinking();
|
|
318
356
|
}
|
|
319
357
|
onMessageReceived?.(newMessage);
|
|
320
358
|
}
|
|
321
|
-
}, [websocket.lastMessage, onMessageReceived, conversationId]);
|
|
359
|
+
}, [websocket.lastMessage, onMessageReceived, conversationId, clearBotThinking]);
|
|
360
|
+
(0, import_react2.useEffect)(() => {
|
|
361
|
+
if (!websocket.lastMessage) return;
|
|
362
|
+
const { type, data } = websocket.lastMessage;
|
|
363
|
+
if (type === "bot_not_responding") {
|
|
364
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
365
|
+
clearBotThinking();
|
|
366
|
+
agentActiveRef.current = true;
|
|
367
|
+
}
|
|
368
|
+
if (type === "conversation_updated") {
|
|
369
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
370
|
+
if (data?.status && AGENT_STATUSES.has(data.status)) {
|
|
371
|
+
agentActiveRef.current = true;
|
|
372
|
+
clearBotThinking();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}, [websocket.lastMessage, conversationId, clearBotThinking]);
|
|
322
376
|
const addMessage = (0, import_react2.useCallback)((message) => {
|
|
377
|
+
const isAgentActive = agentActiveRef.current;
|
|
323
378
|
setMessages((prev) => {
|
|
324
|
-
if (prev.some((msg) => msg.id === message.id))
|
|
325
|
-
return prev;
|
|
326
|
-
}
|
|
379
|
+
if (prev.some((msg) => msg.id === message.id)) return prev;
|
|
327
380
|
return [...prev, message];
|
|
328
381
|
});
|
|
329
|
-
if (message.senderType === "
|
|
382
|
+
if (message.senderType === "agent") {
|
|
383
|
+
agentActiveRef.current = true;
|
|
384
|
+
}
|
|
385
|
+
if (message.senderType === "customer" && !isAgentActive) {
|
|
330
386
|
setIsBotThinking(true);
|
|
331
387
|
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
332
388
|
botThinkingTimerRef.current = setTimeout(() => {
|
|
@@ -339,21 +395,16 @@ function useMessages(websocket, config) {
|
|
|
339
395
|
}, []);
|
|
340
396
|
const clearMessages = (0, import_react2.useCallback)(() => {
|
|
341
397
|
setMessages([]);
|
|
398
|
+
agentActiveRef.current = false;
|
|
342
399
|
}, []);
|
|
343
400
|
const loadMore = (0, import_react2.useCallback)(async () => {
|
|
344
|
-
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken)
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
401
|
+
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
|
|
347
402
|
setIsLoadingMore(true);
|
|
348
403
|
setError(null);
|
|
349
404
|
try {
|
|
350
405
|
const result = await fetchMessages(
|
|
351
406
|
httpApiUrl,
|
|
352
|
-
{
|
|
353
|
-
conversationId,
|
|
354
|
-
limit: 20,
|
|
355
|
-
pageToken: nextPageToken
|
|
356
|
-
},
|
|
407
|
+
{ conversationId, limit: 20, pageToken: nextPageToken },
|
|
357
408
|
headersWithApiKey
|
|
358
409
|
);
|
|
359
410
|
setMessages((prev) => [...result.data, ...prev]);
|
|
@@ -366,15 +417,7 @@ function useMessages(websocket, config) {
|
|
|
366
417
|
} finally {
|
|
367
418
|
setIsLoadingMore(false);
|
|
368
419
|
}
|
|
369
|
-
}, [
|
|
370
|
-
hasMore,
|
|
371
|
-
isLoadingMore,
|
|
372
|
-
httpApiUrl,
|
|
373
|
-
conversationId,
|
|
374
|
-
nextPageToken,
|
|
375
|
-
headersWithApiKey,
|
|
376
|
-
onError
|
|
377
|
-
]);
|
|
420
|
+
}, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
|
|
378
421
|
(0, import_react2.useEffect)(() => {
|
|
379
422
|
return () => {
|
|
380
423
|
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
@@ -807,24 +850,20 @@ function XcelsiorSymbol({ size = 24, color = "white", className = "", style }) {
|
|
|
807
850
|
}
|
|
808
851
|
);
|
|
809
852
|
}
|
|
810
|
-
function ChatBubbleIcon({ size =
|
|
811
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
812
|
-
"
|
|
813
|
-
{
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
"aria-hidden": "true",
|
|
825
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
|
|
826
|
-
}
|
|
827
|
-
);
|
|
853
|
+
function ChatBubbleIcon({ size = 36, color = "white", className = "", style }) {
|
|
854
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", xmlns: "http://www.w3.org/2000/svg", className, style, "aria-hidden": "true", children: [
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "3.5", r: "3", fill: "#67e8f9", opacity: 0.4 }),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "3.5", r: "1.5", fill: "#67e8f9" }),
|
|
857
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "15", y: "4.5", width: "2", height: "4", rx: "1", fill: color }),
|
|
858
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
|
|
859
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "27", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "5", y: "8.5", width: "22", height: "19", rx: "5", fill: color }),
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "16.5", r: "3", fill: "#1a4fd0" }),
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "20", cy: "16.5", r: "3", fill: "#1a4fd0" }),
|
|
863
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "13", cy: "15.5", r: "1.2", fill: color }),
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "21", cy: "15.5", r: "1.2", fill: color }),
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M13 22.5q3 2.5 6 0", stroke: "#1a4fd0", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
866
|
+
] });
|
|
828
867
|
}
|
|
829
868
|
function XcelsiorAvatar({ size = 40, className = "" }) {
|
|
830
869
|
const iconSize = Math.round(size * 0.55);
|
|
@@ -988,7 +1027,7 @@ function ChatHeader({ agent, onClose, onMinimize, theme }) {
|
|
|
988
1027
|
}
|
|
989
1028
|
|
|
990
1029
|
// src/components/MessageList.tsx
|
|
991
|
-
var
|
|
1030
|
+
var import_react8 = require("react");
|
|
992
1031
|
|
|
993
1032
|
// src/components/Spinner.tsx
|
|
994
1033
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -1036,6 +1075,7 @@ function Spinner({ size = "md", color = "currentColor" }) {
|
|
|
1036
1075
|
|
|
1037
1076
|
// src/components/MessageItem.tsx
|
|
1038
1077
|
var import_date_fns = require("date-fns");
|
|
1078
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1039
1079
|
|
|
1040
1080
|
// src/components/MarkdownMessage.tsx
|
|
1041
1081
|
var import_react_markdown = __toESM(require("react-markdown"));
|
|
@@ -1275,14 +1315,482 @@ function MarkdownMessage({ content, theme }) {
|
|
|
1275
1315
|
) });
|
|
1276
1316
|
}
|
|
1277
1317
|
|
|
1278
|
-
// src/components/
|
|
1318
|
+
// src/components/BookingSlotPicker.tsx
|
|
1319
|
+
var import_react6 = require("react");
|
|
1320
|
+
var import_lucide_react = require("lucide-react");
|
|
1279
1321
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1322
|
+
function BookingSlotPicker({ data, theme, onSlotSelected, disabled = false }) {
|
|
1323
|
+
const [selectedDateIndex, setSelectedDateIndex] = (0, import_react6.useState)(0);
|
|
1324
|
+
const [selectedSlot, setSelectedSlot] = (0, import_react6.useState)(null);
|
|
1325
|
+
if (!data?.availableDates?.length) return null;
|
|
1326
|
+
const bgColor = theme?.background || "#00001a";
|
|
1327
|
+
const isLightTheme = (() => {
|
|
1328
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1329
|
+
const hex = bgColor.replace("#", "");
|
|
1330
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1331
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1332
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1333
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1334
|
+
})();
|
|
1335
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1336
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1337
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1338
|
+
const cardStyle = isLightTheme ? {
|
|
1339
|
+
backgroundColor: "rgba(0,0,0,0.03)",
|
|
1340
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
|
|
1341
|
+
borderRadius: "14px",
|
|
1342
|
+
padding: "14px"
|
|
1343
|
+
} : {
|
|
1344
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
1345
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
|
|
1346
|
+
borderRadius: "14px",
|
|
1347
|
+
padding: "14px"
|
|
1348
|
+
};
|
|
1349
|
+
const activePillStyle = {
|
|
1350
|
+
backgroundColor: primaryColor,
|
|
1351
|
+
color: "#ffffff",
|
|
1352
|
+
borderRadius: "20px",
|
|
1353
|
+
padding: "5px 12px",
|
|
1354
|
+
fontSize: "12px",
|
|
1355
|
+
fontWeight: "600",
|
|
1356
|
+
letterSpacing: "0.01em",
|
|
1357
|
+
cursor: disabled ? "default" : "pointer",
|
|
1358
|
+
border: "none",
|
|
1359
|
+
flexShrink: 0
|
|
1360
|
+
};
|
|
1361
|
+
const inactivePillStyle = isLightTheme ? {
|
|
1362
|
+
backgroundColor: "rgba(0,0,0,0.04)",
|
|
1363
|
+
color: textColor,
|
|
1364
|
+
borderRadius: "20px",
|
|
1365
|
+
padding: "5px 12px",
|
|
1366
|
+
fontSize: "12px",
|
|
1367
|
+
fontWeight: "500",
|
|
1368
|
+
letterSpacing: "0.01em",
|
|
1369
|
+
cursor: disabled ? "default" : "pointer",
|
|
1370
|
+
border: "none",
|
|
1371
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.1)",
|
|
1372
|
+
flexShrink: 0
|
|
1373
|
+
} : {
|
|
1374
|
+
backgroundColor: "rgba(255,255,255,0.05)",
|
|
1375
|
+
color: textColor,
|
|
1376
|
+
borderRadius: "20px",
|
|
1377
|
+
padding: "5px 12px",
|
|
1378
|
+
fontSize: "12px",
|
|
1379
|
+
fontWeight: "500",
|
|
1380
|
+
letterSpacing: "0.01em",
|
|
1381
|
+
cursor: disabled ? "default" : "pointer",
|
|
1382
|
+
border: "none",
|
|
1383
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.1)",
|
|
1384
|
+
flexShrink: 0
|
|
1385
|
+
};
|
|
1386
|
+
const activeSlot = selectedSlot?.date === data.availableDates[selectedDateIndex]?.date ? selectedSlot.time : null;
|
|
1387
|
+
const [confirming, setConfirming] = (0, import_react6.useState)(false);
|
|
1388
|
+
const handleSlotClick = (date, time) => {
|
|
1389
|
+
if (disabled || confirming) return;
|
|
1390
|
+
setSelectedSlot({ date, time });
|
|
1391
|
+
};
|
|
1392
|
+
const handleConfirm = (0, import_react6.useCallback)(() => {
|
|
1393
|
+
if (!selectedSlot || disabled || confirming) return;
|
|
1394
|
+
setConfirming(true);
|
|
1395
|
+
onSlotSelected(selectedSlot.date, selectedSlot.time);
|
|
1396
|
+
}, [selectedSlot, disabled, confirming, onSlotSelected]);
|
|
1397
|
+
const currentDate = data.availableDates[selectedDateIndex];
|
|
1398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: cardStyle, children: [
|
|
1399
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1400
|
+
"p",
|
|
1401
|
+
{
|
|
1402
|
+
style: {
|
|
1403
|
+
fontSize: "11px",
|
|
1404
|
+
fontWeight: "600",
|
|
1405
|
+
letterSpacing: "0.06em",
|
|
1406
|
+
textTransform: "uppercase",
|
|
1407
|
+
color: textMuted,
|
|
1408
|
+
marginBottom: "10px"
|
|
1409
|
+
},
|
|
1410
|
+
children: "Select a date"
|
|
1411
|
+
}
|
|
1412
|
+
),
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1414
|
+
"div",
|
|
1415
|
+
{
|
|
1416
|
+
style: {
|
|
1417
|
+
display: "flex",
|
|
1418
|
+
gap: "6px",
|
|
1419
|
+
overflowX: "auto",
|
|
1420
|
+
paddingBottom: "2px",
|
|
1421
|
+
scrollbarWidth: "none",
|
|
1422
|
+
msOverflowStyle: "none",
|
|
1423
|
+
marginBottom: "14px"
|
|
1424
|
+
},
|
|
1425
|
+
children: data.availableDates.map((dateSlot, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1426
|
+
"button",
|
|
1427
|
+
{
|
|
1428
|
+
type: "button",
|
|
1429
|
+
onClick: () => !disabled && setSelectedDateIndex(index),
|
|
1430
|
+
style: index === selectedDateIndex ? activePillStyle : inactivePillStyle,
|
|
1431
|
+
title: dateSlot.dayName,
|
|
1432
|
+
"aria-pressed": index === selectedDateIndex,
|
|
1433
|
+
disabled,
|
|
1434
|
+
children: dateSlot.dayLabel
|
|
1435
|
+
},
|
|
1436
|
+
dateSlot.date
|
|
1437
|
+
))
|
|
1438
|
+
}
|
|
1439
|
+
),
|
|
1440
|
+
currentDate && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1441
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1442
|
+
"p",
|
|
1443
|
+
{
|
|
1444
|
+
style: {
|
|
1445
|
+
fontSize: "11px",
|
|
1446
|
+
fontWeight: "600",
|
|
1447
|
+
letterSpacing: "0.06em",
|
|
1448
|
+
textTransform: "uppercase",
|
|
1449
|
+
color: textMuted,
|
|
1450
|
+
marginBottom: "8px"
|
|
1451
|
+
},
|
|
1452
|
+
children: currentDate.dayName
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1456
|
+
"div",
|
|
1457
|
+
{
|
|
1458
|
+
style: {
|
|
1459
|
+
display: "grid",
|
|
1460
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
1461
|
+
gap: "6px",
|
|
1462
|
+
marginBottom: "14px"
|
|
1463
|
+
},
|
|
1464
|
+
children: currentDate.slots.map((time) => {
|
|
1465
|
+
const isSelected = activeSlot === time;
|
|
1466
|
+
const isConfirmed = selectedSlot?.date === currentDate.date && selectedSlot.time === time;
|
|
1467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1468
|
+
"button",
|
|
1469
|
+
{
|
|
1470
|
+
type: "button",
|
|
1471
|
+
onClick: () => handleSlotClick(currentDate.date, time),
|
|
1472
|
+
disabled: disabled && !isConfirmed,
|
|
1473
|
+
"aria-pressed": isSelected,
|
|
1474
|
+
style: {
|
|
1475
|
+
...isSelected ? activePillStyle : inactivePillStyle,
|
|
1476
|
+
padding: "7px 8px",
|
|
1477
|
+
borderRadius: "8px",
|
|
1478
|
+
fontSize: "13px",
|
|
1479
|
+
fontWeight: isSelected ? "600" : "400",
|
|
1480
|
+
textAlign: "center",
|
|
1481
|
+
opacity: disabled && !isConfirmed ? 0.4 : 1,
|
|
1482
|
+
transition: "opacity 0.15s ease"
|
|
1483
|
+
},
|
|
1484
|
+
children: time
|
|
1485
|
+
},
|
|
1486
|
+
time
|
|
1487
|
+
);
|
|
1488
|
+
})
|
|
1489
|
+
}
|
|
1490
|
+
)
|
|
1491
|
+
] }),
|
|
1492
|
+
selectedSlot && !disabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1493
|
+
"button",
|
|
1494
|
+
{
|
|
1495
|
+
type: "button",
|
|
1496
|
+
onClick: handleConfirm,
|
|
1497
|
+
disabled: confirming,
|
|
1498
|
+
style: {
|
|
1499
|
+
width: "100%",
|
|
1500
|
+
padding: "10px 16px",
|
|
1501
|
+
borderRadius: "10px",
|
|
1502
|
+
border: "none",
|
|
1503
|
+
backgroundColor: confirming ? `${primaryColor}80` : primaryColor,
|
|
1504
|
+
color: "#ffffff",
|
|
1505
|
+
fontSize: "13px",
|
|
1506
|
+
fontWeight: "600",
|
|
1507
|
+
letterSpacing: "0.01em",
|
|
1508
|
+
cursor: confirming ? "default" : "pointer",
|
|
1509
|
+
marginBottom: "12px",
|
|
1510
|
+
display: "flex",
|
|
1511
|
+
alignItems: "center",
|
|
1512
|
+
justifyContent: "center",
|
|
1513
|
+
gap: "6px",
|
|
1514
|
+
transition: "background-color 0.15s ease"
|
|
1515
|
+
},
|
|
1516
|
+
children: [
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Calendar, { size: 14, "aria-hidden": "true" }),
|
|
1518
|
+
confirming ? "Booking..." : `Book ${selectedSlot.time} on ${data.availableDates.find((d) => d.date === selectedSlot.date)?.dayLabel ?? selectedSlot.date}`
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1523
|
+
"div",
|
|
1524
|
+
{
|
|
1525
|
+
style: {
|
|
1526
|
+
display: "flex",
|
|
1527
|
+
alignItems: "center",
|
|
1528
|
+
justifyContent: "space-between",
|
|
1529
|
+
gap: "8px"
|
|
1530
|
+
},
|
|
1531
|
+
children: [
|
|
1532
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
|
|
1533
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1534
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
|
|
1535
|
+
] }),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
|
|
1537
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.Clock, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1538
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: [
|
|
1539
|
+
data.meetingDuration,
|
|
1540
|
+
" min"
|
|
1541
|
+
] })
|
|
1542
|
+
] })
|
|
1543
|
+
]
|
|
1544
|
+
}
|
|
1545
|
+
)
|
|
1546
|
+
] });
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/components/BookingConfirmationCard.tsx
|
|
1550
|
+
var import_lucide_react2 = require("lucide-react");
|
|
1551
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1552
|
+
function BookingConfirmationCard({ data, theme }) {
|
|
1553
|
+
const bgColor = theme?.background || "#00001a";
|
|
1554
|
+
const isLightTheme = (() => {
|
|
1555
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1556
|
+
const hex = bgColor.replace("#", "");
|
|
1557
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1558
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1559
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1560
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1561
|
+
})();
|
|
1562
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1563
|
+
const successColor = theme?.statusPositive || "#22c55e";
|
|
1564
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1565
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1566
|
+
const cardStyle = isLightTheme ? {
|
|
1567
|
+
backgroundColor: "rgba(0,0,0,0.03)",
|
|
1568
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
|
|
1569
|
+
borderRadius: "14px",
|
|
1570
|
+
padding: "16px",
|
|
1571
|
+
overflow: "hidden"
|
|
1572
|
+
} : {
|
|
1573
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
1574
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
|
|
1575
|
+
borderRadius: "14px",
|
|
1576
|
+
padding: "16px",
|
|
1577
|
+
overflow: "hidden"
|
|
1578
|
+
};
|
|
1579
|
+
const detailRowStyle = {
|
|
1580
|
+
display: "flex",
|
|
1581
|
+
alignItems: "flex-start",
|
|
1582
|
+
gap: "8px",
|
|
1583
|
+
marginBottom: "8px"
|
|
1584
|
+
};
|
|
1585
|
+
const dividerStyle = {
|
|
1586
|
+
height: "1px",
|
|
1587
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)",
|
|
1588
|
+
margin: "12px 0"
|
|
1589
|
+
};
|
|
1590
|
+
const actionButtonBase = {
|
|
1591
|
+
display: "flex",
|
|
1592
|
+
alignItems: "center",
|
|
1593
|
+
justifyContent: "center",
|
|
1594
|
+
gap: "6px",
|
|
1595
|
+
flex: 1,
|
|
1596
|
+
padding: "9px 12px",
|
|
1597
|
+
borderRadius: "9px",
|
|
1598
|
+
fontSize: "12px",
|
|
1599
|
+
fontWeight: "600",
|
|
1600
|
+
letterSpacing: "0.01em",
|
|
1601
|
+
cursor: "pointer",
|
|
1602
|
+
textDecoration: "none",
|
|
1603
|
+
border: "none",
|
|
1604
|
+
transition: "opacity 0.15s ease"
|
|
1605
|
+
};
|
|
1606
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: cardStyle, children: [
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
|
|
1608
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CircleCheck, { size: 22, color: successColor, "aria-hidden": "true" }),
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1611
|
+
"p",
|
|
1612
|
+
{
|
|
1613
|
+
style: {
|
|
1614
|
+
fontSize: "14px",
|
|
1615
|
+
fontWeight: "700",
|
|
1616
|
+
color: textColor,
|
|
1617
|
+
letterSpacing: "0.006em",
|
|
1618
|
+
lineHeight: 1.3
|
|
1619
|
+
},
|
|
1620
|
+
children: "Meeting Confirmed"
|
|
1621
|
+
}
|
|
1622
|
+
),
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { fontSize: "11px", color: successColor, letterSpacing: "0.01em", marginTop: "1px" }, children: "You'll receive a calendar invite shortly" })
|
|
1624
|
+
] })
|
|
1625
|
+
] }),
|
|
1626
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Calendar, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
|
|
1630
|
+
data.meetingDate,
|
|
1631
|
+
" at ",
|
|
1632
|
+
data.meetingTime
|
|
1633
|
+
] })
|
|
1634
|
+
] }),
|
|
1635
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: detailRowStyle, children: [
|
|
1636
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Clock, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1637
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
|
|
1638
|
+
data.meetingDuration,
|
|
1639
|
+
" minutes"
|
|
1640
|
+
] })
|
|
1641
|
+
] }),
|
|
1642
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.MessageSquare, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1644
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: data.meetingPurpose })
|
|
1645
|
+
] })
|
|
1646
|
+
] }),
|
|
1647
|
+
data.timezone && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "5px", marginTop: "8px" }, children: [
|
|
1648
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1649
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
|
|
1650
|
+
] }),
|
|
1651
|
+
(data.meetLink || data.calendarLink) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1652
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dividerStyle }),
|
|
1653
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1654
|
+
data.meetLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1655
|
+
"a",
|
|
1656
|
+
{
|
|
1657
|
+
href: data.meetLink,
|
|
1658
|
+
target: "_blank",
|
|
1659
|
+
rel: "noopener noreferrer",
|
|
1660
|
+
style: {
|
|
1661
|
+
...actionButtonBase,
|
|
1662
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${theme?.primaryStrong || "#005eff"})`,
|
|
1663
|
+
color: "#ffffff",
|
|
1664
|
+
boxShadow: `0 2px 8px -2px ${primaryColor}50`
|
|
1665
|
+
},
|
|
1666
|
+
children: [
|
|
1667
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Video, { size: 13, "aria-hidden": "true" }),
|
|
1668
|
+
"Join Meet"
|
|
1669
|
+
]
|
|
1670
|
+
}
|
|
1671
|
+
),
|
|
1672
|
+
data.calendarLink && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1673
|
+
"a",
|
|
1674
|
+
{
|
|
1675
|
+
href: data.calendarLink,
|
|
1676
|
+
target: "_blank",
|
|
1677
|
+
rel: "noopener noreferrer",
|
|
1678
|
+
style: {
|
|
1679
|
+
...actionButtonBase,
|
|
1680
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.07)",
|
|
1681
|
+
color: textColor,
|
|
1682
|
+
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.1)" : "inset 0 0 0 0.5px rgba(255,255,255,0.12)"
|
|
1683
|
+
},
|
|
1684
|
+
children: [
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.CalendarPlus, { size: 13, "aria-hidden": "true" }),
|
|
1686
|
+
"Add to Calendar"
|
|
1687
|
+
]
|
|
1688
|
+
}
|
|
1689
|
+
)
|
|
1690
|
+
] })
|
|
1691
|
+
] })
|
|
1692
|
+
] });
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// src/components/BookingCancelledCard.tsx
|
|
1696
|
+
var import_lucide_react3 = require("lucide-react");
|
|
1697
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1698
|
+
function BookingCancelledCard({ data, theme }) {
|
|
1699
|
+
const bgColor = theme?.background || "#00001a";
|
|
1700
|
+
const isLightTheme = (() => {
|
|
1701
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1702
|
+
const hex = bgColor.replace("#", "");
|
|
1703
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1704
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1705
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1706
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1707
|
+
})();
|
|
1708
|
+
const negativeColor = theme?.statusNegative || "#ef4444";
|
|
1709
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1710
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1711
|
+
const cardStyle = isLightTheme ? {
|
|
1712
|
+
backgroundColor: "rgba(239,68,68,0.04)",
|
|
1713
|
+
boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.12)",
|
|
1714
|
+
borderRadius: "14px",
|
|
1715
|
+
padding: "16px"
|
|
1716
|
+
} : {
|
|
1717
|
+
backgroundColor: "rgba(239,68,68,0.06)",
|
|
1718
|
+
boxShadow: "inset 0 0 0 0.5px rgba(239,68,68,0.18)",
|
|
1719
|
+
borderRadius: "14px",
|
|
1720
|
+
padding: "16px"
|
|
1721
|
+
};
|
|
1722
|
+
const strikethroughTextStyle = {
|
|
1723
|
+
fontSize: "13px",
|
|
1724
|
+
color: textMuted,
|
|
1725
|
+
lineHeight: 1.4,
|
|
1726
|
+
textDecoration: "line-through",
|
|
1727
|
+
textDecorationColor: isLightTheme ? "rgba(0,0,0,0.25)" : "rgba(255,255,255,0.2)"
|
|
1728
|
+
};
|
|
1729
|
+
const detailRowStyle = {
|
|
1730
|
+
display: "flex",
|
|
1731
|
+
alignItems: "flex-start",
|
|
1732
|
+
gap: "8px",
|
|
1733
|
+
marginBottom: "8px"
|
|
1734
|
+
};
|
|
1735
|
+
const cancelledByLabel = data.cancelledBy === "visitor" ? "Cancelled by you" : "Cancelled by support";
|
|
1736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: cardStyle, children: [
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.CircleX, { size: 22, color: negativeColor, "aria-hidden": "true" }),
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
1740
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1741
|
+
"p",
|
|
1742
|
+
{
|
|
1743
|
+
style: {
|
|
1744
|
+
fontSize: "14px",
|
|
1745
|
+
fontWeight: "700",
|
|
1746
|
+
color: textColor,
|
|
1747
|
+
letterSpacing: "0.006em",
|
|
1748
|
+
lineHeight: 1.3
|
|
1749
|
+
},
|
|
1750
|
+
children: "Meeting Cancelled"
|
|
1751
|
+
}
|
|
1752
|
+
),
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1754
|
+
"p",
|
|
1755
|
+
{
|
|
1756
|
+
style: {
|
|
1757
|
+
fontSize: "11px",
|
|
1758
|
+
color: negativeColor,
|
|
1759
|
+
letterSpacing: "0.01em",
|
|
1760
|
+
marginTop: "1px",
|
|
1761
|
+
opacity: 0.8
|
|
1762
|
+
},
|
|
1763
|
+
children: cancelledByLabel
|
|
1764
|
+
}
|
|
1765
|
+
)
|
|
1766
|
+
] })
|
|
1767
|
+
] }),
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: detailRowStyle, children: [
|
|
1770
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Calendar, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1771
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: strikethroughTextStyle, children: [
|
|
1772
|
+
data.meetingDate,
|
|
1773
|
+
" at ",
|
|
1774
|
+
data.meetingTime
|
|
1775
|
+
] })
|
|
1776
|
+
] }),
|
|
1777
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
|
|
1778
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.MessageSquare, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1779
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: strikethroughTextStyle, children: data.meetingPurpose })
|
|
1780
|
+
] })
|
|
1781
|
+
] })
|
|
1782
|
+
] });
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/components/MessageItem.tsx
|
|
1786
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1280
1787
|
function MessageItem({
|
|
1281
1788
|
message,
|
|
1282
1789
|
currentUser,
|
|
1283
1790
|
showAvatar = true,
|
|
1284
1791
|
showTimestamp = true,
|
|
1285
|
-
theme
|
|
1792
|
+
theme,
|
|
1793
|
+
onBookingSlotSelected
|
|
1286
1794
|
}) {
|
|
1287
1795
|
const isOwnMessage = message.senderType === currentUser.type;
|
|
1288
1796
|
const isSystemMessage = message.senderType === "system";
|
|
@@ -1301,8 +1809,63 @@ function MessageItem({
|
|
|
1301
1809
|
const primaryStrong = theme?.primaryStrong || "#005eff";
|
|
1302
1810
|
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1303
1811
|
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.35)");
|
|
1812
|
+
if (message.messageType === "booking_slots" || message.messageType === "booking_confirmation" || message.messageType === "booking_cancelled") {
|
|
1813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex gap-2.5 mb-3 flex-row", children: [
|
|
1814
|
+
showAvatar && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(XcelsiorAvatar, { size: 28 }) }),
|
|
1815
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col max-w-[85%] items-start", children: [
|
|
1816
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1817
|
+
"span",
|
|
1818
|
+
{
|
|
1819
|
+
className: "mb-1 px-1 font-medium",
|
|
1820
|
+
style: {
|
|
1821
|
+
color: isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)",
|
|
1822
|
+
fontSize: "11px",
|
|
1823
|
+
letterSpacing: "0.019em"
|
|
1824
|
+
},
|
|
1825
|
+
children: "AI Assistant"
|
|
1826
|
+
}
|
|
1827
|
+
),
|
|
1828
|
+
message.messageType === "booking_slots" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1829
|
+
BookingSlotPicker,
|
|
1830
|
+
{
|
|
1831
|
+
data: message.metadata?.bookingData,
|
|
1832
|
+
theme,
|
|
1833
|
+
onSlotSelected: (date, time) => {
|
|
1834
|
+
onBookingSlotSelected?.(date, time, message.id);
|
|
1835
|
+
},
|
|
1836
|
+
disabled: !!message.metadata?.slotSelected
|
|
1837
|
+
}
|
|
1838
|
+
),
|
|
1839
|
+
message.messageType === "booking_confirmation" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1840
|
+
BookingConfirmationCard,
|
|
1841
|
+
{
|
|
1842
|
+
data: message.metadata?.bookingConfirmation,
|
|
1843
|
+
theme
|
|
1844
|
+
}
|
|
1845
|
+
),
|
|
1846
|
+
message.messageType === "booking_cancelled" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1847
|
+
BookingCancelledCard,
|
|
1848
|
+
{
|
|
1849
|
+
data: message.metadata?.bookingCancelled,
|
|
1850
|
+
theme
|
|
1851
|
+
}
|
|
1852
|
+
),
|
|
1853
|
+
showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex items-center gap-1.5 mt-1 px-1", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1854
|
+
"span",
|
|
1855
|
+
{
|
|
1856
|
+
style: {
|
|
1857
|
+
fontSize: "11px",
|
|
1858
|
+
letterSpacing: "0.019em",
|
|
1859
|
+
color: textMuted
|
|
1860
|
+
},
|
|
1861
|
+
children: (0, import_date_fns.formatDistanceToNow)(new Date(message.createdAt), { addSuffix: true })
|
|
1862
|
+
}
|
|
1863
|
+
) })
|
|
1864
|
+
] })
|
|
1865
|
+
] });
|
|
1866
|
+
}
|
|
1304
1867
|
if (isSystemMessage) {
|
|
1305
|
-
return /* @__PURE__ */ (0,
|
|
1868
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1306
1869
|
"div",
|
|
1307
1870
|
{
|
|
1308
1871
|
className: "px-4 py-1.5 rounded-full",
|
|
@@ -1310,7 +1873,7 @@ function MessageItem({
|
|
|
1310
1873
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
|
|
1311
1874
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06)"
|
|
1312
1875
|
},
|
|
1313
|
-
children: /* @__PURE__ */ (0,
|
|
1876
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1314
1877
|
"p",
|
|
1315
1878
|
{
|
|
1316
1879
|
style: {
|
|
@@ -1349,12 +1912,12 @@ function MessageItem({
|
|
|
1349
1912
|
borderRadius: "18px 18px 18px 4px",
|
|
1350
1913
|
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
|
|
1351
1914
|
};
|
|
1352
|
-
return /* @__PURE__ */ (0,
|
|
1915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1353
1916
|
"div",
|
|
1354
1917
|
{
|
|
1355
1918
|
className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1356
1919
|
children: [
|
|
1357
|
-
showAvatar && !isOwnMessage && /* @__PURE__ */ (0,
|
|
1920
|
+
showAvatar && !isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1358
1921
|
"div",
|
|
1359
1922
|
{
|
|
1360
1923
|
className: "h-7 w-7 rounded-full flex items-center justify-center",
|
|
@@ -1362,34 +1925,24 @@ function MessageItem({
|
|
|
1362
1925
|
background: isLightTheme ? `linear-gradient(135deg, ${primaryColor}30, rgba(0,0,0,0.04))` : `linear-gradient(135deg, ${primaryColor}60, rgba(255,255,255,0.06))`,
|
|
1363
1926
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.08)" : "inset 0 0 0 0.5px rgba(255,255,255,0.1)"
|
|
1364
1927
|
},
|
|
1365
|
-
children: /* @__PURE__ */ (0,
|
|
1366
|
-
|
|
1928
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1929
|
+
import_lucide_react4.Headphones,
|
|
1367
1930
|
{
|
|
1368
|
-
|
|
1369
|
-
height: "14",
|
|
1370
|
-
viewBox: "0 0 24 24",
|
|
1371
|
-
fill: "none",
|
|
1931
|
+
size: 14,
|
|
1372
1932
|
stroke: isLightTheme ? primaryColor : "white",
|
|
1373
|
-
strokeWidth:
|
|
1374
|
-
|
|
1375
|
-
strokeLinejoin: "round",
|
|
1376
|
-
"aria-hidden": "true",
|
|
1377
|
-
children: [
|
|
1378
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Agent" }),
|
|
1379
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 18v-6a9 9 0 0 1 18 0v6" }),
|
|
1380
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" })
|
|
1381
|
-
]
|
|
1933
|
+
strokeWidth: 2,
|
|
1934
|
+
"aria-hidden": "true"
|
|
1382
1935
|
}
|
|
1383
1936
|
)
|
|
1384
1937
|
}
|
|
1385
1938
|
) }),
|
|
1386
|
-
showAvatar && isOwnMessage && /* @__PURE__ */ (0,
|
|
1387
|
-
/* @__PURE__ */ (0,
|
|
1939
|
+
showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-7 flex-shrink-0" }),
|
|
1940
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1388
1941
|
"div",
|
|
1389
1942
|
{
|
|
1390
1943
|
className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
|
|
1391
1944
|
children: [
|
|
1392
|
-
senderLabel && /* @__PURE__ */ (0,
|
|
1945
|
+
senderLabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1393
1946
|
"span",
|
|
1394
1947
|
{
|
|
1395
1948
|
className: "mb-1 px-1 font-medium",
|
|
@@ -1401,7 +1954,7 @@ function MessageItem({
|
|
|
1401
1954
|
children: senderLabel
|
|
1402
1955
|
}
|
|
1403
1956
|
),
|
|
1404
|
-
/* @__PURE__ */ (0,
|
|
1957
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1405
1958
|
"div",
|
|
1406
1959
|
{
|
|
1407
1960
|
className: "px-4 py-2.5",
|
|
@@ -1414,12 +1967,12 @@ function MessageItem({
|
|
|
1414
1967
|
children: [
|
|
1415
1968
|
message.messageType === "text" && (isOwnMessage ? (
|
|
1416
1969
|
// User messages: plain text — no markdown parsing
|
|
1417
|
-
/* @__PURE__ */ (0,
|
|
1970
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
|
|
1418
1971
|
) : (
|
|
1419
1972
|
// Bot / agent messages: full markdown rendering
|
|
1420
|
-
/* @__PURE__ */ (0,
|
|
1973
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownMessage, { content: message.content, theme })
|
|
1421
1974
|
)),
|
|
1422
|
-
message.messageType === "image" && /* @__PURE__ */ (0,
|
|
1975
|
+
message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1423
1976
|
"img",
|
|
1424
1977
|
{
|
|
1425
1978
|
src: message.content,
|
|
@@ -1428,26 +1981,9 @@ function MessageItem({
|
|
|
1428
1981
|
loading: "lazy"
|
|
1429
1982
|
}
|
|
1430
1983
|
) }),
|
|
1431
|
-
message.messageType === "file" && /* @__PURE__ */ (0,
|
|
1432
|
-
/* @__PURE__ */ (0,
|
|
1433
|
-
|
|
1434
|
-
{
|
|
1435
|
-
width: "18",
|
|
1436
|
-
height: "18",
|
|
1437
|
-
viewBox: "0 0 24 24",
|
|
1438
|
-
fill: "none",
|
|
1439
|
-
stroke: "currentColor",
|
|
1440
|
-
strokeWidth: "1.75",
|
|
1441
|
-
strokeLinecap: "round",
|
|
1442
|
-
strokeLinejoin: "round",
|
|
1443
|
-
"aria-hidden": "true",
|
|
1444
|
-
children: [
|
|
1445
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "File" }),
|
|
1446
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
|
|
1447
|
-
]
|
|
1448
|
-
}
|
|
1449
|
-
),
|
|
1450
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1984
|
+
message.messageType === "file" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1985
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Paperclip, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" }),
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1451
1987
|
"a",
|
|
1452
1988
|
{
|
|
1453
1989
|
href: message.content,
|
|
@@ -1465,12 +2001,12 @@ function MessageItem({
|
|
|
1465
2001
|
]
|
|
1466
2002
|
}
|
|
1467
2003
|
),
|
|
1468
|
-
showTimestamp && /* @__PURE__ */ (0,
|
|
2004
|
+
showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1469
2005
|
"div",
|
|
1470
2006
|
{
|
|
1471
2007
|
className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1472
2008
|
children: [
|
|
1473
|
-
/* @__PURE__ */ (0,
|
|
2009
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1474
2010
|
"span",
|
|
1475
2011
|
{
|
|
1476
2012
|
style: {
|
|
@@ -1483,63 +2019,10 @@ function MessageItem({
|
|
|
1483
2019
|
})
|
|
1484
2020
|
}
|
|
1485
2021
|
),
|
|
1486
|
-
isOwnMessage && message.status && /* @__PURE__ */ (0,
|
|
1487
|
-
message.status === "sent" && /* @__PURE__ */ (0,
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
width: "14",
|
|
1491
|
-
height: "14",
|
|
1492
|
-
viewBox: "0 0 24 24",
|
|
1493
|
-
fill: "none",
|
|
1494
|
-
stroke: "currentColor",
|
|
1495
|
-
strokeWidth: "2.5",
|
|
1496
|
-
strokeLinecap: "round",
|
|
1497
|
-
strokeLinejoin: "round",
|
|
1498
|
-
"aria-hidden": "true",
|
|
1499
|
-
children: [
|
|
1500
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Sent" }),
|
|
1501
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 6 9 17 4 12" })
|
|
1502
|
-
]
|
|
1503
|
-
}
|
|
1504
|
-
),
|
|
1505
|
-
message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1506
|
-
"svg",
|
|
1507
|
-
{
|
|
1508
|
-
width: "14",
|
|
1509
|
-
height: "14",
|
|
1510
|
-
viewBox: "0 0 24 24",
|
|
1511
|
-
fill: "none",
|
|
1512
|
-
stroke: "currentColor",
|
|
1513
|
-
strokeWidth: "2.5",
|
|
1514
|
-
strokeLinecap: "round",
|
|
1515
|
-
strokeLinejoin: "round",
|
|
1516
|
-
"aria-hidden": "true",
|
|
1517
|
-
children: [
|
|
1518
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Delivered" }),
|
|
1519
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
|
|
1520
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
|
|
1521
|
-
]
|
|
1522
|
-
}
|
|
1523
|
-
),
|
|
1524
|
-
message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1525
|
-
"svg",
|
|
1526
|
-
{
|
|
1527
|
-
width: "14",
|
|
1528
|
-
height: "14",
|
|
1529
|
-
viewBox: "0 0 24 24",
|
|
1530
|
-
fill: "none",
|
|
1531
|
-
stroke: primaryColor,
|
|
1532
|
-
strokeWidth: "2.5",
|
|
1533
|
-
strokeLinecap: "round",
|
|
1534
|
-
strokeLinejoin: "round",
|
|
1535
|
-
"aria-hidden": "true",
|
|
1536
|
-
children: [
|
|
1537
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Read" }),
|
|
1538
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
|
|
1539
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
|
|
1540
|
-
]
|
|
1541
|
-
}
|
|
1542
|
-
)
|
|
2022
|
+
isOwnMessage && message.status && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { color: textMuted }, children: [
|
|
2023
|
+
message.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Check, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
|
|
2024
|
+
message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
|
|
2025
|
+
message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.CheckCheck, { size: 14, strokeWidth: 2.5, stroke: primaryColor, "aria-hidden": "true" })
|
|
1543
2026
|
] })
|
|
1544
2027
|
]
|
|
1545
2028
|
}
|
|
@@ -1553,8 +2036,8 @@ function MessageItem({
|
|
|
1553
2036
|
}
|
|
1554
2037
|
|
|
1555
2038
|
// src/components/ThinkingIndicator.tsx
|
|
1556
|
-
var
|
|
1557
|
-
var
|
|
2039
|
+
var import_react7 = require("react");
|
|
2040
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1558
2041
|
var PHRASE_POOLS = {
|
|
1559
2042
|
// ── Greetings & small talk ──
|
|
1560
2043
|
greeting: [
|
|
@@ -1737,17 +2220,17 @@ function ThinkingIndicator({
|
|
|
1737
2220
|
const isLightTheme = computeIsLightTheme2(theme?.background);
|
|
1738
2221
|
const primaryColor = theme?.primary || "#337eff";
|
|
1739
2222
|
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
1740
|
-
const [phraseIndex, setPhraseIndex] = (0,
|
|
1741
|
-
const [displayText, setDisplayText] = (0,
|
|
1742
|
-
const [isDeleting, setIsDeleting] = (0,
|
|
1743
|
-
const phrasesRef = (0,
|
|
1744
|
-
(0,
|
|
2223
|
+
const [phraseIndex, setPhraseIndex] = (0, import_react7.useState)(0);
|
|
2224
|
+
const [displayText, setDisplayText] = (0, import_react7.useState)("");
|
|
2225
|
+
const [isDeleting, setIsDeleting] = (0, import_react7.useState)(false);
|
|
2226
|
+
const phrasesRef = (0, import_react7.useRef)(getShuffledPhrases(detectContext(lastUserMessage)));
|
|
2227
|
+
(0, import_react7.useEffect)(() => {
|
|
1745
2228
|
phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
|
|
1746
2229
|
setPhraseIndex(0);
|
|
1747
2230
|
setDisplayText("");
|
|
1748
2231
|
setIsDeleting(false);
|
|
1749
2232
|
}, [lastUserMessage]);
|
|
1750
|
-
(0,
|
|
2233
|
+
(0, import_react7.useEffect)(() => {
|
|
1751
2234
|
const phrases = phrasesRef.current;
|
|
1752
2235
|
const phrase = phrases[phraseIndex % phrases.length];
|
|
1753
2236
|
let timeout;
|
|
@@ -1773,7 +2256,7 @@ function ThinkingIndicator({
|
|
|
1773
2256
|
}
|
|
1774
2257
|
return () => clearTimeout(timeout);
|
|
1775
2258
|
}, [displayText, isDeleting, phraseIndex]);
|
|
1776
|
-
return /* @__PURE__ */ (0,
|
|
2259
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1777
2260
|
"div",
|
|
1778
2261
|
{
|
|
1779
2262
|
className: "flex gap-2.5 mb-3",
|
|
@@ -1781,8 +2264,8 @@ function ThinkingIndicator({
|
|
|
1781
2264
|
"aria-live": "polite",
|
|
1782
2265
|
"aria-label": "Xcelsior is thinking",
|
|
1783
2266
|
children: [
|
|
1784
|
-
showAvatar && /* @__PURE__ */ (0,
|
|
1785
|
-
/* @__PURE__ */ (0,
|
|
2267
|
+
showAvatar && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XcelsiorAvatar, { size: 28 }) }),
|
|
2268
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1786
2269
|
"div",
|
|
1787
2270
|
{
|
|
1788
2271
|
style: {
|
|
@@ -1792,8 +2275,8 @@ function ThinkingIndicator({
|
|
|
1792
2275
|
padding: "12px 16px",
|
|
1793
2276
|
minWidth: 160
|
|
1794
2277
|
},
|
|
1795
|
-
children: /* @__PURE__ */ (0,
|
|
1796
|
-
/* @__PURE__ */ (0,
|
|
2278
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1797
2280
|
"span",
|
|
1798
2281
|
{
|
|
1799
2282
|
style: {
|
|
@@ -1807,7 +2290,7 @@ function ThinkingIndicator({
|
|
|
1807
2290
|
},
|
|
1808
2291
|
i
|
|
1809
2292
|
)) }),
|
|
1810
|
-
/* @__PURE__ */ (0,
|
|
2293
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1811
2294
|
"span",
|
|
1812
2295
|
{
|
|
1813
2296
|
style: {
|
|
@@ -1818,7 +2301,7 @@ function ThinkingIndicator({
|
|
|
1818
2301
|
},
|
|
1819
2302
|
children: [
|
|
1820
2303
|
displayText,
|
|
1821
|
-
/* @__PURE__ */ (0,
|
|
2304
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1822
2305
|
"span",
|
|
1823
2306
|
{
|
|
1824
2307
|
style: {
|
|
@@ -1844,7 +2327,7 @@ function ThinkingIndicator({
|
|
|
1844
2327
|
}
|
|
1845
2328
|
|
|
1846
2329
|
// src/components/MessageList.tsx
|
|
1847
|
-
var
|
|
2330
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1848
2331
|
function MessageList({
|
|
1849
2332
|
messages,
|
|
1850
2333
|
currentUser,
|
|
@@ -1857,15 +2340,16 @@ function MessageList({
|
|
|
1857
2340
|
isLoadingMore = false,
|
|
1858
2341
|
theme,
|
|
1859
2342
|
onQuickAction,
|
|
1860
|
-
isBotThinking = false
|
|
2343
|
+
isBotThinking = false,
|
|
2344
|
+
onBookingSlotSelected
|
|
1861
2345
|
}) {
|
|
1862
|
-
const messagesEndRef = (0,
|
|
1863
|
-
const containerRef = (0,
|
|
1864
|
-
const prevLengthRef = (0,
|
|
1865
|
-
const loadMoreTriggerRef = (0,
|
|
1866
|
-
const prevScrollHeightRef = (0,
|
|
1867
|
-
const hasInitialScrolledRef = (0,
|
|
1868
|
-
const isUserScrollingRef = (0,
|
|
2346
|
+
const messagesEndRef = (0, import_react8.useRef)(null);
|
|
2347
|
+
const containerRef = (0, import_react8.useRef)(null);
|
|
2348
|
+
const prevLengthRef = (0, import_react8.useRef)(messages.length);
|
|
2349
|
+
const loadMoreTriggerRef = (0, import_react8.useRef)(null);
|
|
2350
|
+
const prevScrollHeightRef = (0, import_react8.useRef)(0);
|
|
2351
|
+
const hasInitialScrolledRef = (0, import_react8.useRef)(false);
|
|
2352
|
+
const isUserScrollingRef = (0, import_react8.useRef)(false);
|
|
1869
2353
|
const bgColor = theme?.background || "#00001a";
|
|
1870
2354
|
const isLightTheme = (() => {
|
|
1871
2355
|
if (!bgColor.startsWith("#")) return false;
|
|
@@ -1878,7 +2362,7 @@ function MessageList({
|
|
|
1878
2362
|
const primaryColor = theme?.primary || "#337eff";
|
|
1879
2363
|
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1880
2364
|
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
1881
|
-
(0,
|
|
2365
|
+
(0, import_react8.useEffect)(() => {
|
|
1882
2366
|
if (autoScroll && messagesEndRef.current) {
|
|
1883
2367
|
if (messages.length > prevLengthRef.current && !isLoadingMore) {
|
|
1884
2368
|
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
|
@@ -1886,7 +2370,7 @@ function MessageList({
|
|
|
1886
2370
|
prevLengthRef.current = messages.length;
|
|
1887
2371
|
}
|
|
1888
2372
|
}, [messages.length, autoScroll, isLoadingMore]);
|
|
1889
|
-
(0,
|
|
2373
|
+
(0, import_react8.useEffect)(() => {
|
|
1890
2374
|
if (messages.length > 0 && messagesEndRef.current && !isLoading && !hasInitialScrolledRef.current) {
|
|
1891
2375
|
setTimeout(() => {
|
|
1892
2376
|
messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
|
|
@@ -1900,7 +2384,7 @@ function MessageList({
|
|
|
1900
2384
|
hasInitialScrolledRef.current = true;
|
|
1901
2385
|
}
|
|
1902
2386
|
}, [isLoading, messages.length]);
|
|
1903
|
-
(0,
|
|
2387
|
+
(0, import_react8.useEffect)(() => {
|
|
1904
2388
|
if (isLoadingMore) {
|
|
1905
2389
|
prevScrollHeightRef.current = containerRef.current?.scrollHeight || 0;
|
|
1906
2390
|
} else if (prevScrollHeightRef.current > 0 && containerRef.current) {
|
|
@@ -1910,7 +2394,7 @@ function MessageList({
|
|
|
1910
2394
|
prevScrollHeightRef.current = 0;
|
|
1911
2395
|
}
|
|
1912
2396
|
}, [isLoadingMore]);
|
|
1913
|
-
const handleScroll = (0,
|
|
2397
|
+
const handleScroll = (0, import_react8.useCallback)(() => {
|
|
1914
2398
|
if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
|
|
1915
2399
|
if (!isUserScrollingRef.current) return;
|
|
1916
2400
|
const { scrollTop } = containerRef.current;
|
|
@@ -1918,18 +2402,18 @@ function MessageList({
|
|
|
1918
2402
|
onLoadMore();
|
|
1919
2403
|
}
|
|
1920
2404
|
}, [onLoadMore, hasMore, isLoadingMore]);
|
|
1921
|
-
(0,
|
|
2405
|
+
(0, import_react8.useEffect)(() => {
|
|
1922
2406
|
const container = containerRef.current;
|
|
1923
2407
|
if (!container) return;
|
|
1924
2408
|
container.addEventListener("scroll", handleScroll);
|
|
1925
2409
|
return () => container.removeEventListener("scroll", handleScroll);
|
|
1926
2410
|
}, [handleScroll]);
|
|
1927
2411
|
if (isLoading) {
|
|
1928
|
-
return /* @__PURE__ */ (0,
|
|
2412
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, { size: "lg" }) });
|
|
1929
2413
|
}
|
|
1930
2414
|
if (messages.length === 0) {
|
|
1931
|
-
return /* @__PURE__ */ (0,
|
|
1932
|
-
/* @__PURE__ */ (0,
|
|
2415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
|
|
2416
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1933
2417
|
"h3",
|
|
1934
2418
|
{
|
|
1935
2419
|
className: "font-semibold mb-2",
|
|
@@ -1941,7 +2425,7 @@ function MessageList({
|
|
|
1941
2425
|
children: "How can we help?"
|
|
1942
2426
|
}
|
|
1943
2427
|
),
|
|
1944
|
-
/* @__PURE__ */ (0,
|
|
2428
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1945
2429
|
"p",
|
|
1946
2430
|
{
|
|
1947
2431
|
className: "max-w-[240px]",
|
|
@@ -1955,11 +2439,11 @@ function MessageList({
|
|
|
1955
2439
|
children: "Ask us anything. We are here to help you get the most out of Xcelsior."
|
|
1956
2440
|
}
|
|
1957
2441
|
),
|
|
1958
|
-
/* @__PURE__ */ (0,
|
|
2442
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
|
|
1959
2443
|
{ label: "Our services", message: "What services does Xcelsior offer?" },
|
|
1960
2444
|
{ label: "Get a quote", message: "I would like to get a quote for a project" },
|
|
1961
2445
|
{ label: "Support", message: "I need help with something" }
|
|
1962
|
-
].map((action) => /* @__PURE__ */ (0,
|
|
2446
|
+
].map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1963
2447
|
"button",
|
|
1964
2448
|
{
|
|
1965
2449
|
type: "button",
|
|
@@ -1991,14 +2475,14 @@ function MessageList({
|
|
|
1991
2475
|
)) })
|
|
1992
2476
|
] });
|
|
1993
2477
|
}
|
|
1994
|
-
return /* @__PURE__ */ (0,
|
|
2478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1995
2479
|
"div",
|
|
1996
2480
|
{
|
|
1997
2481
|
ref: containerRef,
|
|
1998
2482
|
className: "flex-1 overflow-y-auto px-4 py-3",
|
|
1999
2483
|
style: { scrollBehavior: "smooth" },
|
|
2000
2484
|
children: [
|
|
2001
|
-
/* @__PURE__ */ (0,
|
|
2485
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
|
|
2002
2486
|
@keyframes thinkingPulse {
|
|
2003
2487
|
0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
2004
2488
|
30% { opacity: 1; transform: scale(1); }
|
|
@@ -2008,23 +2492,24 @@ function MessageList({
|
|
|
2008
2492
|
50% { opacity: 0; }
|
|
2009
2493
|
}
|
|
2010
2494
|
` }),
|
|
2011
|
-
isLoadingMore && /* @__PURE__ */ (0,
|
|
2012
|
-
/* @__PURE__ */ (0,
|
|
2013
|
-
messages.map((message) => /* @__PURE__ */ (0,
|
|
2495
|
+
isLoadingMore && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, { size: "sm" }) }),
|
|
2496
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: loadMoreTriggerRef }),
|
|
2497
|
+
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2014
2498
|
MessageItem,
|
|
2015
2499
|
{
|
|
2016
2500
|
message,
|
|
2017
2501
|
currentUser,
|
|
2018
2502
|
showAvatar: true,
|
|
2019
2503
|
showTimestamp: true,
|
|
2020
|
-
theme
|
|
2504
|
+
theme,
|
|
2505
|
+
onBookingSlotSelected
|
|
2021
2506
|
},
|
|
2022
2507
|
message.id
|
|
2023
2508
|
)),
|
|
2024
|
-
isTyping && /* @__PURE__ */ (0,
|
|
2025
|
-
/* @__PURE__ */ (0,
|
|
2026
|
-
/* @__PURE__ */ (0,
|
|
2027
|
-
/* @__PURE__ */ (0,
|
|
2509
|
+
isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2.5 mb-3", children: [
|
|
2510
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(XcelsiorAvatar, { size: 28 }) }),
|
|
2511
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-start", children: [
|
|
2512
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2028
2513
|
"div",
|
|
2029
2514
|
{
|
|
2030
2515
|
className: "px-4 py-3",
|
|
@@ -2033,7 +2518,7 @@ function MessageList({
|
|
|
2033
2518
|
borderRadius: "18px 18px 18px 4px",
|
|
2034
2519
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
|
|
2035
2520
|
},
|
|
2036
|
-
children: /* @__PURE__ */ (0,
|
|
2521
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2037
2522
|
"span",
|
|
2038
2523
|
{
|
|
2039
2524
|
className: "rounded-full animate-bounce",
|
|
@@ -2049,7 +2534,7 @@ function MessageList({
|
|
|
2049
2534
|
)) })
|
|
2050
2535
|
}
|
|
2051
2536
|
),
|
|
2052
|
-
typingUser && /* @__PURE__ */ (0,
|
|
2537
|
+
typingUser && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2053
2538
|
"span",
|
|
2054
2539
|
{
|
|
2055
2540
|
className: "mt-1 px-1",
|
|
@@ -2066,24 +2551,24 @@ function MessageList({
|
|
|
2066
2551
|
)
|
|
2067
2552
|
] })
|
|
2068
2553
|
] }),
|
|
2069
|
-
isBotThinking && !isTyping && /* @__PURE__ */ (0,
|
|
2554
|
+
isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2070
2555
|
ThinkingIndicator,
|
|
2071
2556
|
{
|
|
2072
2557
|
theme,
|
|
2073
2558
|
lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
|
|
2074
2559
|
}
|
|
2075
2560
|
),
|
|
2076
|
-
/* @__PURE__ */ (0,
|
|
2561
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
|
|
2077
2562
|
]
|
|
2078
2563
|
}
|
|
2079
2564
|
);
|
|
2080
2565
|
}
|
|
2081
2566
|
|
|
2082
2567
|
// src/components/ChatInput.tsx
|
|
2083
|
-
var
|
|
2568
|
+
var import_react9 = require("react");
|
|
2084
2569
|
var import_react_dom = require("react-dom");
|
|
2085
|
-
var
|
|
2086
|
-
var
|
|
2570
|
+
var import_react10 = __toESM(require("@emoji-mart/react"));
|
|
2571
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2087
2572
|
function isLightColor(color) {
|
|
2088
2573
|
let r = 0, g = 0, b = 0;
|
|
2089
2574
|
if (color.startsWith("#")) {
|
|
@@ -2101,18 +2586,18 @@ function ChatInput({
|
|
|
2101
2586
|
fileUpload,
|
|
2102
2587
|
disabled = false
|
|
2103
2588
|
}) {
|
|
2104
|
-
const [message, setMessage] = (0,
|
|
2105
|
-
const [showEmojiPicker, setShowEmojiPicker] = (0,
|
|
2106
|
-
const [emojiData, setEmojiData] = (0,
|
|
2107
|
-
const [emojiPickerPosition, setEmojiPickerPosition] = (0,
|
|
2108
|
-
const [isFocused, setIsFocused] = (0,
|
|
2109
|
-
const textAreaRef = (0,
|
|
2110
|
-
const emojiPickerRef = (0,
|
|
2111
|
-
const emojiButtonRef = (0,
|
|
2112
|
-
const fileInputRef = (0,
|
|
2113
|
-
const typingTimeoutRef = (0,
|
|
2114
|
-
const startTypingTimeoutRef = (0,
|
|
2115
|
-
const isTypingRef = (0,
|
|
2589
|
+
const [message, setMessage] = (0, import_react9.useState)("");
|
|
2590
|
+
const [showEmojiPicker, setShowEmojiPicker] = (0, import_react9.useState)(false);
|
|
2591
|
+
const [emojiData, setEmojiData] = (0, import_react9.useState)();
|
|
2592
|
+
const [emojiPickerPosition, setEmojiPickerPosition] = (0, import_react9.useState)(null);
|
|
2593
|
+
const [isFocused, setIsFocused] = (0, import_react9.useState)(false);
|
|
2594
|
+
const textAreaRef = (0, import_react9.useRef)(null);
|
|
2595
|
+
const emojiPickerRef = (0, import_react9.useRef)(null);
|
|
2596
|
+
const emojiButtonRef = (0, import_react9.useRef)(null);
|
|
2597
|
+
const fileInputRef = (0, import_react9.useRef)(null);
|
|
2598
|
+
const typingTimeoutRef = (0, import_react9.useRef)(null);
|
|
2599
|
+
const startTypingTimeoutRef = (0, import_react9.useRef)(null);
|
|
2600
|
+
const isTypingRef = (0, import_react9.useRef)(false);
|
|
2116
2601
|
const enableEmoji = config.enableEmoji ?? true;
|
|
2117
2602
|
const enableFileUpload = config.enableFileUpload ?? true;
|
|
2118
2603
|
const bgColor = config.theme?.background || "#00001a";
|
|
@@ -2120,7 +2605,7 @@ function ChatInput({
|
|
|
2120
2605
|
const textColor = config.theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
2121
2606
|
const textMuted = config.theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
2122
2607
|
const primaryColor = config.theme?.primary || "#337eff";
|
|
2123
|
-
(0,
|
|
2608
|
+
(0, import_react9.useEffect)(() => {
|
|
2124
2609
|
if (!enableEmoji) return;
|
|
2125
2610
|
(async () => {
|
|
2126
2611
|
try {
|
|
@@ -2131,7 +2616,7 @@ function ChatInput({
|
|
|
2131
2616
|
}
|
|
2132
2617
|
})();
|
|
2133
2618
|
}, [enableEmoji]);
|
|
2134
|
-
(0,
|
|
2619
|
+
(0, import_react9.useEffect)(() => {
|
|
2135
2620
|
const handleClickOutside = (event) => {
|
|
2136
2621
|
if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) {
|
|
2137
2622
|
setShowEmojiPicker(false);
|
|
@@ -2144,7 +2629,7 @@ function ChatInput({
|
|
|
2144
2629
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
2145
2630
|
};
|
|
2146
2631
|
}, [showEmojiPicker]);
|
|
2147
|
-
(0,
|
|
2632
|
+
(0, import_react9.useEffect)(() => {
|
|
2148
2633
|
return () => {
|
|
2149
2634
|
if (typingTimeoutRef.current) {
|
|
2150
2635
|
clearTimeout(typingTimeoutRef.current);
|
|
@@ -2154,7 +2639,7 @@ function ChatInput({
|
|
|
2154
2639
|
}
|
|
2155
2640
|
};
|
|
2156
2641
|
}, []);
|
|
2157
|
-
(0,
|
|
2642
|
+
(0, import_react9.useEffect)(() => {
|
|
2158
2643
|
if (!showEmojiPicker) return;
|
|
2159
2644
|
const updatePosition = () => {
|
|
2160
2645
|
if (emojiButtonRef.current) {
|
|
@@ -2261,7 +2746,7 @@ ${uploadedFile.markdown}
|
|
|
2261
2746
|
};
|
|
2262
2747
|
const canSend = message.trim().length > 0 && !disabled;
|
|
2263
2748
|
const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
|
|
2264
|
-
return /* @__PURE__ */ (0,
|
|
2749
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2265
2750
|
"div",
|
|
2266
2751
|
{
|
|
2267
2752
|
className: "px-4 py-3",
|
|
@@ -2269,7 +2754,7 @@ ${uploadedFile.markdown}
|
|
|
2269
2754
|
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
|
|
2270
2755
|
},
|
|
2271
2756
|
children: [
|
|
2272
|
-
/* @__PURE__ */ (0,
|
|
2757
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2273
2758
|
"div",
|
|
2274
2759
|
{
|
|
2275
2760
|
className: "relative flex items-center rounded-full transition-all duration-200",
|
|
@@ -2281,8 +2766,8 @@ ${uploadedFile.markdown}
|
|
|
2281
2766
|
backdropFilter: "blur(32px)"
|
|
2282
2767
|
},
|
|
2283
2768
|
children: [
|
|
2284
|
-
/* @__PURE__ */ (0,
|
|
2285
|
-
/* @__PURE__ */ (0,
|
|
2769
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
|
|
2770
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2286
2771
|
"textarea",
|
|
2287
2772
|
{
|
|
2288
2773
|
ref: textAreaRef,
|
|
@@ -2311,8 +2796,8 @@ ${uploadedFile.markdown}
|
|
|
2311
2796
|
disabled
|
|
2312
2797
|
}
|
|
2313
2798
|
),
|
|
2314
|
-
/* @__PURE__ */ (0,
|
|
2315
|
-
enableEmoji && /* @__PURE__ */ (0,
|
|
2799
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
|
|
2800
|
+
enableEmoji && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2316
2801
|
"button",
|
|
2317
2802
|
{
|
|
2318
2803
|
ref: emojiButtonRef,
|
|
@@ -2342,7 +2827,7 @@ ${uploadedFile.markdown}
|
|
|
2342
2827
|
},
|
|
2343
2828
|
disabled,
|
|
2344
2829
|
"aria-label": "Add emoji",
|
|
2345
|
-
children: /* @__PURE__ */ (0,
|
|
2830
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2346
2831
|
"svg",
|
|
2347
2832
|
{
|
|
2348
2833
|
width: "18",
|
|
@@ -2355,18 +2840,18 @@ ${uploadedFile.markdown}
|
|
|
2355
2840
|
strokeLinejoin: "round",
|
|
2356
2841
|
"aria-hidden": "true",
|
|
2357
2842
|
children: [
|
|
2358
|
-
/* @__PURE__ */ (0,
|
|
2359
|
-
/* @__PURE__ */ (0,
|
|
2360
|
-
/* @__PURE__ */ (0,
|
|
2361
|
-
/* @__PURE__ */ (0,
|
|
2362
|
-
/* @__PURE__ */ (0,
|
|
2843
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Emoji" }),
|
|
2844
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2845
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
2846
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
|
|
2847
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
|
|
2363
2848
|
]
|
|
2364
2849
|
}
|
|
2365
2850
|
)
|
|
2366
2851
|
}
|
|
2367
2852
|
),
|
|
2368
|
-
enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0,
|
|
2369
|
-
/* @__PURE__ */ (0,
|
|
2853
|
+
enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
2854
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2370
2855
|
"button",
|
|
2371
2856
|
{
|
|
2372
2857
|
type: "button",
|
|
@@ -2386,7 +2871,7 @@ ${uploadedFile.markdown}
|
|
|
2386
2871
|
},
|
|
2387
2872
|
disabled: disabled || fileUpload.isUploading,
|
|
2388
2873
|
"aria-label": "Attach file",
|
|
2389
|
-
children: fileUpload.isUploading ? /* @__PURE__ */ (0,
|
|
2874
|
+
children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2390
2875
|
"svg",
|
|
2391
2876
|
{
|
|
2392
2877
|
width: "18",
|
|
@@ -2398,11 +2883,11 @@ ${uploadedFile.markdown}
|
|
|
2398
2883
|
className: "animate-spin",
|
|
2399
2884
|
"aria-hidden": "true",
|
|
2400
2885
|
children: [
|
|
2401
|
-
/* @__PURE__ */ (0,
|
|
2402
|
-
/* @__PURE__ */ (0,
|
|
2886
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Uploading" }),
|
|
2887
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
2403
2888
|
]
|
|
2404
2889
|
}
|
|
2405
|
-
) : /* @__PURE__ */ (0,
|
|
2890
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2406
2891
|
"svg",
|
|
2407
2892
|
{
|
|
2408
2893
|
width: "18",
|
|
@@ -2415,14 +2900,14 @@ ${uploadedFile.markdown}
|
|
|
2415
2900
|
strokeLinejoin: "round",
|
|
2416
2901
|
"aria-hidden": "true",
|
|
2417
2902
|
children: [
|
|
2418
|
-
/* @__PURE__ */ (0,
|
|
2419
|
-
/* @__PURE__ */ (0,
|
|
2903
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Attach file" }),
|
|
2904
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
|
|
2420
2905
|
]
|
|
2421
2906
|
}
|
|
2422
2907
|
)
|
|
2423
2908
|
}
|
|
2424
2909
|
),
|
|
2425
|
-
/* @__PURE__ */ (0,
|
|
2910
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2426
2911
|
"input",
|
|
2427
2912
|
{
|
|
2428
2913
|
ref: fileInputRef,
|
|
@@ -2433,7 +2918,7 @@ ${uploadedFile.markdown}
|
|
|
2433
2918
|
}
|
|
2434
2919
|
)
|
|
2435
2920
|
] }),
|
|
2436
|
-
/* @__PURE__ */ (0,
|
|
2921
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2437
2922
|
"button",
|
|
2438
2923
|
{
|
|
2439
2924
|
type: "button",
|
|
@@ -2448,7 +2933,7 @@ ${uploadedFile.markdown}
|
|
|
2448
2933
|
boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
|
|
2449
2934
|
},
|
|
2450
2935
|
"aria-label": "Send message",
|
|
2451
|
-
children: /* @__PURE__ */ (0,
|
|
2936
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2452
2937
|
"svg",
|
|
2453
2938
|
{
|
|
2454
2939
|
width: "18",
|
|
@@ -2461,9 +2946,9 @@ ${uploadedFile.markdown}
|
|
|
2461
2946
|
strokeLinejoin: "round",
|
|
2462
2947
|
"aria-hidden": "true",
|
|
2463
2948
|
children: [
|
|
2464
|
-
/* @__PURE__ */ (0,
|
|
2465
|
-
/* @__PURE__ */ (0,
|
|
2466
|
-
/* @__PURE__ */ (0,
|
|
2949
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: "Send" }),
|
|
2950
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
2951
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
2467
2952
|
]
|
|
2468
2953
|
}
|
|
2469
2954
|
)
|
|
@@ -2473,8 +2958,8 @@ ${uploadedFile.markdown}
|
|
|
2473
2958
|
]
|
|
2474
2959
|
}
|
|
2475
2960
|
),
|
|
2476
|
-
fileUpload.isUploading && /* @__PURE__ */ (0,
|
|
2477
|
-
/* @__PURE__ */ (0,
|
|
2961
|
+
fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-2 px-1", children: [
|
|
2962
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2478
2963
|
"div",
|
|
2479
2964
|
{
|
|
2480
2965
|
className: "w-full rounded-full overflow-hidden",
|
|
@@ -2482,7 +2967,7 @@ ${uploadedFile.markdown}
|
|
|
2482
2967
|
height: 3,
|
|
2483
2968
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
|
|
2484
2969
|
},
|
|
2485
|
-
children: /* @__PURE__ */ (0,
|
|
2970
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2486
2971
|
"div",
|
|
2487
2972
|
{
|
|
2488
2973
|
className: "h-full rounded-full transition-all duration-300",
|
|
@@ -2494,7 +2979,7 @@ ${uploadedFile.markdown}
|
|
|
2494
2979
|
)
|
|
2495
2980
|
}
|
|
2496
2981
|
),
|
|
2497
|
-
/* @__PURE__ */ (0,
|
|
2982
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2498
2983
|
"p",
|
|
2499
2984
|
{
|
|
2500
2985
|
className: "mt-1",
|
|
@@ -2512,7 +2997,7 @@ ${uploadedFile.markdown}
|
|
|
2512
2997
|
)
|
|
2513
2998
|
] }),
|
|
2514
2999
|
showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
|
|
2515
|
-
/* @__PURE__ */ (0,
|
|
3000
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2516
3001
|
"div",
|
|
2517
3002
|
{
|
|
2518
3003
|
ref: emojiPickerRef,
|
|
@@ -2527,8 +3012,8 @@ ${uploadedFile.markdown}
|
|
|
2527
3012
|
"0 16px 48px -8px rgba(0,0,0,0.5)"
|
|
2528
3013
|
].join(", ")
|
|
2529
3014
|
},
|
|
2530
|
-
children: /* @__PURE__ */ (0,
|
|
2531
|
-
|
|
3015
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3016
|
+
import_react10.default,
|
|
2532
3017
|
{
|
|
2533
3018
|
data: emojiData,
|
|
2534
3019
|
onEmojiSelect: (emoji) => {
|
|
@@ -2553,7 +3038,7 @@ ${uploadedFile.markdown}
|
|
|
2553
3038
|
}
|
|
2554
3039
|
|
|
2555
3040
|
// src/components/ChatWidget.tsx
|
|
2556
|
-
var
|
|
3041
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2557
3042
|
function getAnonymousUser() {
|
|
2558
3043
|
let anonId;
|
|
2559
3044
|
try {
|
|
@@ -2599,14 +3084,20 @@ function ChatWidget({
|
|
|
2599
3084
|
maxHeight: 900,
|
|
2600
3085
|
enabled: !isFullPage
|
|
2601
3086
|
});
|
|
2602
|
-
const
|
|
3087
|
+
const lastSentRef = (0, import_react11.useRef)(null);
|
|
3088
|
+
const handleSendMessage = (0, import_react11.useCallback)(
|
|
2603
3089
|
(content) => {
|
|
2604
3090
|
if (!websocket.isConnected) {
|
|
2605
3091
|
config.toast?.error("Not connected to chat server");
|
|
2606
3092
|
return;
|
|
2607
3093
|
}
|
|
3094
|
+
const now = Date.now();
|
|
3095
|
+
if (lastSentRef.current && lastSentRef.current.content === content && now - lastSentRef.current.time < 1e3) {
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
lastSentRef.current = { content, time: now };
|
|
2608
3099
|
const optimisticMessage = {
|
|
2609
|
-
id: `temp-${
|
|
3100
|
+
id: `temp-${now}`,
|
|
2610
3101
|
conversationId: config.conversationId || "",
|
|
2611
3102
|
senderId: effectiveUser.email,
|
|
2612
3103
|
senderType: effectiveUser.type,
|
|
@@ -2625,7 +3116,18 @@ function ChatWidget({
|
|
|
2625
3116
|
},
|
|
2626
3117
|
[websocket, config, addMessage, effectiveUser]
|
|
2627
3118
|
);
|
|
2628
|
-
const
|
|
3119
|
+
const handleBookingSlotSelected = (0, import_react11.useCallback)(
|
|
3120
|
+
(date, time, _messageId) => {
|
|
3121
|
+
if (!websocket.isConnected) return;
|
|
3122
|
+
websocket.sendMessage("bookSlot", {
|
|
3123
|
+
conversationId: config.conversationId,
|
|
3124
|
+
date,
|
|
3125
|
+
time
|
|
3126
|
+
});
|
|
3127
|
+
},
|
|
3128
|
+
[websocket, config]
|
|
3129
|
+
);
|
|
3130
|
+
const handleTyping = (0, import_react11.useCallback)(
|
|
2629
3131
|
(isTyping2) => {
|
|
2630
3132
|
if (!websocket.isConnected || config.enableTypingIndicator === false) return;
|
|
2631
3133
|
websocket.sendMessage("typing", {
|
|
@@ -2635,7 +3137,7 @@ function ChatWidget({
|
|
|
2635
3137
|
},
|
|
2636
3138
|
[websocket, config]
|
|
2637
3139
|
);
|
|
2638
|
-
(0,
|
|
3140
|
+
(0, import_react11.useEffect)(() => {
|
|
2639
3141
|
if (websocket.error) {
|
|
2640
3142
|
config.toast?.error(websocket.error.message || "An error occurred");
|
|
2641
3143
|
}
|
|
@@ -2686,14 +3188,14 @@ function ChatWidget({
|
|
|
2686
3188
|
outlineOffset: -1,
|
|
2687
3189
|
transition: "outline 150ms ease"
|
|
2688
3190
|
};
|
|
2689
|
-
return /* @__PURE__ */ (0,
|
|
3191
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
2690
3192
|
"div",
|
|
2691
3193
|
{
|
|
2692
3194
|
className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
|
|
2693
3195
|
style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
|
|
2694
3196
|
...!isFullPage ? containerResizeProps : {},
|
|
2695
3197
|
children: [
|
|
2696
|
-
!isFullPage && /* @__PURE__ */ (0,
|
|
3198
|
+
!isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2697
3199
|
ChatHeader,
|
|
2698
3200
|
{
|
|
2699
3201
|
agent: effectiveUser.type === "customer" ? {
|
|
@@ -2707,7 +3209,7 @@ function ChatWidget({
|
|
|
2707
3209
|
theme: config.theme
|
|
2708
3210
|
}
|
|
2709
3211
|
),
|
|
2710
|
-
!websocket.isConnected && /* @__PURE__ */ (0,
|
|
3212
|
+
!websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2711
3213
|
"div",
|
|
2712
3214
|
{
|
|
2713
3215
|
className: "px-4 py-2",
|
|
@@ -2715,15 +3217,15 @@ function ChatWidget({
|
|
|
2715
3217
|
backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
|
|
2716
3218
|
borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
|
|
2717
3219
|
},
|
|
2718
|
-
children: /* @__PURE__ */ (0,
|
|
2719
|
-
/* @__PURE__ */ (0,
|
|
3220
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3221
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2720
3222
|
"div",
|
|
2721
3223
|
{
|
|
2722
3224
|
className: "w-1.5 h-1.5 rounded-full animate-pulse",
|
|
2723
3225
|
style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
|
|
2724
3226
|
}
|
|
2725
3227
|
),
|
|
2726
|
-
/* @__PURE__ */ (0,
|
|
3228
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2727
3229
|
"span",
|
|
2728
3230
|
{
|
|
2729
3231
|
style: {
|
|
@@ -2734,7 +3236,7 @@ function ChatWidget({
|
|
|
2734
3236
|
children: "Reconnecting..."
|
|
2735
3237
|
}
|
|
2736
3238
|
),
|
|
2737
|
-
/* @__PURE__ */ (0,
|
|
3239
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2738
3240
|
"button",
|
|
2739
3241
|
{
|
|
2740
3242
|
type: "button",
|
|
@@ -2758,8 +3260,8 @@ function ChatWidget({
|
|
|
2758
3260
|
] })
|
|
2759
3261
|
}
|
|
2760
3262
|
),
|
|
2761
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
2762
|
-
/* @__PURE__ */ (0,
|
|
3263
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-center", children: [
|
|
3264
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2763
3265
|
"div",
|
|
2764
3266
|
{
|
|
2765
3267
|
className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
|
|
@@ -2769,7 +3271,7 @@ function ChatWidget({
|
|
|
2769
3271
|
}
|
|
2770
3272
|
}
|
|
2771
3273
|
),
|
|
2772
|
-
/* @__PURE__ */ (0,
|
|
3274
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2773
3275
|
"p",
|
|
2774
3276
|
{
|
|
2775
3277
|
style: {
|
|
@@ -2780,7 +3282,7 @@ function ChatWidget({
|
|
|
2780
3282
|
children: "Loading messages..."
|
|
2781
3283
|
}
|
|
2782
3284
|
)
|
|
2783
|
-
] }) }) : /* @__PURE__ */ (0,
|
|
3285
|
+
] }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2784
3286
|
MessageList,
|
|
2785
3287
|
{
|
|
2786
3288
|
messages,
|
|
@@ -2793,10 +3295,11 @@ function ChatWidget({
|
|
|
2793
3295
|
isLoadingMore,
|
|
2794
3296
|
theme: config.theme,
|
|
2795
3297
|
onQuickAction: handleSendMessage,
|
|
2796
|
-
isBotThinking
|
|
3298
|
+
isBotThinking,
|
|
3299
|
+
onBookingSlotSelected: handleBookingSlotSelected
|
|
2797
3300
|
}
|
|
2798
3301
|
),
|
|
2799
|
-
/* @__PURE__ */ (0,
|
|
3302
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2800
3303
|
ChatInput,
|
|
2801
3304
|
{
|
|
2802
3305
|
onSend: handleSendMessage,
|
|
@@ -2806,7 +3309,7 @@ function ChatWidget({
|
|
|
2806
3309
|
disabled: !websocket.isConnected
|
|
2807
3310
|
}
|
|
2808
3311
|
),
|
|
2809
|
-
!isFullPage && /* @__PURE__ */ (0,
|
|
3312
|
+
!isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2810
3313
|
"div",
|
|
2811
3314
|
{
|
|
2812
3315
|
className: "px-4 py-1.5 text-center",
|
|
@@ -2814,7 +3317,7 @@ function ChatWidget({
|
|
|
2814
3317
|
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
|
|
2815
3318
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
|
|
2816
3319
|
},
|
|
2817
|
-
children: /* @__PURE__ */ (0,
|
|
3320
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
2818
3321
|
"p",
|
|
2819
3322
|
{
|
|
2820
3323
|
style: {
|
|
@@ -2825,7 +3328,7 @@ function ChatWidget({
|
|
|
2825
3328
|
children: [
|
|
2826
3329
|
"Powered by",
|
|
2827
3330
|
" ",
|
|
2828
|
-
/* @__PURE__ */ (0,
|
|
3331
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: {
|
|
2829
3332
|
fontWeight: 600,
|
|
2830
3333
|
color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
|
|
2831
3334
|
}, children: "Xcelsior" })
|
|
@@ -2840,11 +3343,11 @@ function ChatWidget({
|
|
|
2840
3343
|
}
|
|
2841
3344
|
|
|
2842
3345
|
// src/components/Chat.tsx
|
|
2843
|
-
var
|
|
3346
|
+
var import_react15 = require("react");
|
|
2844
3347
|
|
|
2845
3348
|
// src/components/PreChatForm.tsx
|
|
2846
|
-
var
|
|
2847
|
-
var
|
|
3349
|
+
var import_react12 = require("react");
|
|
3350
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2848
3351
|
function PreChatForm({
|
|
2849
3352
|
onSubmit,
|
|
2850
3353
|
className = "",
|
|
@@ -2853,11 +3356,11 @@ function PreChatForm({
|
|
|
2853
3356
|
onMinimize,
|
|
2854
3357
|
onClose
|
|
2855
3358
|
}) {
|
|
2856
|
-
const [name, setName] = (0,
|
|
2857
|
-
const [email, setEmail] = (0,
|
|
2858
|
-
const [errors, setErrors] = (0,
|
|
2859
|
-
const [isSubmitting, setIsSubmitting] = (0,
|
|
2860
|
-
const [focusedField, setFocusedField] = (0,
|
|
3359
|
+
const [name, setName] = (0, import_react12.useState)(initialName);
|
|
3360
|
+
const [email, setEmail] = (0, import_react12.useState)(initialEmail);
|
|
3361
|
+
const [errors, setErrors] = (0, import_react12.useState)({});
|
|
3362
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
|
|
3363
|
+
const [focusedField, setFocusedField] = (0, import_react12.useState)(null);
|
|
2861
3364
|
const validateForm = () => {
|
|
2862
3365
|
const newErrors = {};
|
|
2863
3366
|
if (!name.trim()) {
|
|
@@ -2905,7 +3408,7 @@ function PreChatForm({
|
|
|
2905
3408
|
transition: "box-shadow 0.2s ease",
|
|
2906
3409
|
caretColor: "#337eff"
|
|
2907
3410
|
});
|
|
2908
|
-
return /* @__PURE__ */ (0,
|
|
3411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2909
3412
|
"div",
|
|
2910
3413
|
{
|
|
2911
3414
|
className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
|
|
@@ -2925,7 +3428,7 @@ function PreChatForm({
|
|
|
2925
3428
|
backgroundSize: "24px 24px"
|
|
2926
3429
|
},
|
|
2927
3430
|
children: [
|
|
2928
|
-
/* @__PURE__ */ (0,
|
|
3431
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2929
3432
|
"div",
|
|
2930
3433
|
{
|
|
2931
3434
|
className: "relative text-white overflow-hidden",
|
|
@@ -2933,7 +3436,7 @@ function PreChatForm({
|
|
|
2933
3436
|
background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
|
|
2934
3437
|
},
|
|
2935
3438
|
children: [
|
|
2936
|
-
/* @__PURE__ */ (0,
|
|
3439
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2937
3440
|
"div",
|
|
2938
3441
|
{
|
|
2939
3442
|
className: "absolute inset-0 pointer-events-none",
|
|
@@ -2942,9 +3445,9 @@ function PreChatForm({
|
|
|
2942
3445
|
}
|
|
2943
3446
|
}
|
|
2944
3447
|
),
|
|
2945
|
-
/* @__PURE__ */ (0,
|
|
2946
|
-
/* @__PURE__ */ (0,
|
|
2947
|
-
/* @__PURE__ */ (0,
|
|
3448
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-start justify-between", children: [
|
|
3449
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex-1", children: [
|
|
3450
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2948
3451
|
"h2",
|
|
2949
3452
|
{
|
|
2950
3453
|
className: "font-semibold",
|
|
@@ -2955,7 +3458,7 @@ function PreChatForm({
|
|
|
2955
3458
|
children: "Start a Conversation"
|
|
2956
3459
|
}
|
|
2957
3460
|
),
|
|
2958
|
-
/* @__PURE__ */ (0,
|
|
3461
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2959
3462
|
"p",
|
|
2960
3463
|
{
|
|
2961
3464
|
className: "mt-1",
|
|
@@ -2968,8 +3471,8 @@ function PreChatForm({
|
|
|
2968
3471
|
}
|
|
2969
3472
|
)
|
|
2970
3473
|
] }),
|
|
2971
|
-
/* @__PURE__ */ (0,
|
|
2972
|
-
/* @__PURE__ */ (0,
|
|
3474
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 ml-2", children: [
|
|
3475
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2973
3476
|
"button",
|
|
2974
3477
|
{
|
|
2975
3478
|
type: "button",
|
|
@@ -2983,7 +3486,7 @@ function PreChatForm({
|
|
|
2983
3486
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
2984
3487
|
},
|
|
2985
3488
|
"aria-label": "Minimize chat",
|
|
2986
|
-
children: /* @__PURE__ */ (0,
|
|
3489
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2987
3490
|
"svg",
|
|
2988
3491
|
{
|
|
2989
3492
|
width: "18",
|
|
@@ -2992,8 +3495,8 @@ function PreChatForm({
|
|
|
2992
3495
|
stroke: "currentColor",
|
|
2993
3496
|
viewBox: "0 0 24 24",
|
|
2994
3497
|
children: [
|
|
2995
|
-
/* @__PURE__ */ (0,
|
|
2996
|
-
/* @__PURE__ */ (0,
|
|
3498
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Minimize" }),
|
|
3499
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2997
3500
|
"path",
|
|
2998
3501
|
{
|
|
2999
3502
|
strokeLinecap: "round",
|
|
@@ -3007,7 +3510,7 @@ function PreChatForm({
|
|
|
3007
3510
|
)
|
|
3008
3511
|
}
|
|
3009
3512
|
),
|
|
3010
|
-
/* @__PURE__ */ (0,
|
|
3513
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3011
3514
|
"button",
|
|
3012
3515
|
{
|
|
3013
3516
|
type: "button",
|
|
@@ -3021,7 +3524,7 @@ function PreChatForm({
|
|
|
3021
3524
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
3022
3525
|
},
|
|
3023
3526
|
"aria-label": "Close chat",
|
|
3024
|
-
children: /* @__PURE__ */ (0,
|
|
3527
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3025
3528
|
"svg",
|
|
3026
3529
|
{
|
|
3027
3530
|
width: "18",
|
|
@@ -3030,8 +3533,8 @@ function PreChatForm({
|
|
|
3030
3533
|
stroke: "currentColor",
|
|
3031
3534
|
viewBox: "0 0 24 24",
|
|
3032
3535
|
children: [
|
|
3033
|
-
/* @__PURE__ */ (0,
|
|
3034
|
-
/* @__PURE__ */ (0,
|
|
3536
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Close" }),
|
|
3537
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3035
3538
|
"path",
|
|
3036
3539
|
{
|
|
3037
3540
|
strokeLinecap: "round",
|
|
@@ -3047,7 +3550,7 @@ function PreChatForm({
|
|
|
3047
3550
|
)
|
|
3048
3551
|
] })
|
|
3049
3552
|
] }) }),
|
|
3050
|
-
/* @__PURE__ */ (0,
|
|
3553
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3051
3554
|
"div",
|
|
3052
3555
|
{
|
|
3053
3556
|
className: "h-px",
|
|
@@ -3059,9 +3562,9 @@ function PreChatForm({
|
|
|
3059
3562
|
]
|
|
3060
3563
|
}
|
|
3061
3564
|
),
|
|
3062
|
-
/* @__PURE__ */ (0,
|
|
3063
|
-
/* @__PURE__ */ (0,
|
|
3064
|
-
/* @__PURE__ */ (0,
|
|
3565
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
3567
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3065
3568
|
"label",
|
|
3066
3569
|
{
|
|
3067
3570
|
htmlFor: "chat-name",
|
|
@@ -3073,11 +3576,11 @@ function PreChatForm({
|
|
|
3073
3576
|
},
|
|
3074
3577
|
children: [
|
|
3075
3578
|
"Name ",
|
|
3076
|
-
/* @__PURE__ */ (0,
|
|
3579
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
|
|
3077
3580
|
]
|
|
3078
3581
|
}
|
|
3079
3582
|
),
|
|
3080
|
-
/* @__PURE__ */ (0,
|
|
3583
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3081
3584
|
"input",
|
|
3082
3585
|
{
|
|
3083
3586
|
type: "text",
|
|
@@ -3097,7 +3600,7 @@ function PreChatForm({
|
|
|
3097
3600
|
autoComplete: "name"
|
|
3098
3601
|
}
|
|
3099
3602
|
),
|
|
3100
|
-
errors.name && /* @__PURE__ */ (0,
|
|
3603
|
+
errors.name && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3101
3604
|
"p",
|
|
3102
3605
|
{
|
|
3103
3606
|
className: "mt-1.5",
|
|
@@ -3111,8 +3614,8 @@ function PreChatForm({
|
|
|
3111
3614
|
}
|
|
3112
3615
|
)
|
|
3113
3616
|
] }),
|
|
3114
|
-
/* @__PURE__ */ (0,
|
|
3115
|
-
/* @__PURE__ */ (0,
|
|
3617
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
|
|
3618
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3116
3619
|
"label",
|
|
3117
3620
|
{
|
|
3118
3621
|
htmlFor: "chat-email",
|
|
@@ -3124,11 +3627,11 @@ function PreChatForm({
|
|
|
3124
3627
|
},
|
|
3125
3628
|
children: [
|
|
3126
3629
|
"Email ",
|
|
3127
|
-
/* @__PURE__ */ (0,
|
|
3630
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
|
|
3128
3631
|
]
|
|
3129
3632
|
}
|
|
3130
3633
|
),
|
|
3131
|
-
/* @__PURE__ */ (0,
|
|
3634
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3132
3635
|
"input",
|
|
3133
3636
|
{
|
|
3134
3637
|
type: "email",
|
|
@@ -3148,7 +3651,7 @@ function PreChatForm({
|
|
|
3148
3651
|
autoComplete: "email"
|
|
3149
3652
|
}
|
|
3150
3653
|
),
|
|
3151
|
-
errors.email && /* @__PURE__ */ (0,
|
|
3654
|
+
errors.email && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3152
3655
|
"p",
|
|
3153
3656
|
{
|
|
3154
3657
|
className: "mt-1.5",
|
|
@@ -3162,7 +3665,7 @@ function PreChatForm({
|
|
|
3162
3665
|
}
|
|
3163
3666
|
)
|
|
3164
3667
|
] }),
|
|
3165
|
-
/* @__PURE__ */ (0,
|
|
3668
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3166
3669
|
"button",
|
|
3167
3670
|
{
|
|
3168
3671
|
type: "submit",
|
|
@@ -3184,8 +3687,8 @@ function PreChatForm({
|
|
|
3184
3687
|
onMouseLeave: (e) => {
|
|
3185
3688
|
e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
|
|
3186
3689
|
},
|
|
3187
|
-
children: isSubmitting ? /* @__PURE__ */ (0,
|
|
3188
|
-
/* @__PURE__ */ (0,
|
|
3690
|
+
children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [
|
|
3691
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3189
3692
|
"svg",
|
|
3190
3693
|
{
|
|
3191
3694
|
className: "animate-spin",
|
|
@@ -3197,8 +3700,8 @@ function PreChatForm({
|
|
|
3197
3700
|
strokeWidth: "2",
|
|
3198
3701
|
"aria-label": "Loading",
|
|
3199
3702
|
children: [
|
|
3200
|
-
/* @__PURE__ */ (0,
|
|
3201
|
-
/* @__PURE__ */ (0,
|
|
3703
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Loading" }),
|
|
3704
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
3202
3705
|
]
|
|
3203
3706
|
}
|
|
3204
3707
|
),
|
|
@@ -3206,7 +3709,7 @@ function PreChatForm({
|
|
|
3206
3709
|
] }) : "Start Chat"
|
|
3207
3710
|
}
|
|
3208
3711
|
),
|
|
3209
|
-
/* @__PURE__ */ (0,
|
|
3712
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3210
3713
|
"p",
|
|
3211
3714
|
{
|
|
3212
3715
|
className: "text-center",
|
|
@@ -3219,7 +3722,7 @@ function PreChatForm({
|
|
|
3219
3722
|
}
|
|
3220
3723
|
)
|
|
3221
3724
|
] }),
|
|
3222
|
-
/* @__PURE__ */ (0,
|
|
3725
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3223
3726
|
"div",
|
|
3224
3727
|
{
|
|
3225
3728
|
className: "px-4 py-1.5 text-center",
|
|
@@ -3227,7 +3730,7 @@ function PreChatForm({
|
|
|
3227
3730
|
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3228
3731
|
backgroundColor: "rgba(0,0,0,0.2)"
|
|
3229
3732
|
},
|
|
3230
|
-
children: /* @__PURE__ */ (0,
|
|
3733
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3231
3734
|
"p",
|
|
3232
3735
|
{
|
|
3233
3736
|
style: {
|
|
@@ -3238,7 +3741,7 @@ function PreChatForm({
|
|
|
3238
3741
|
children: [
|
|
3239
3742
|
"Powered by",
|
|
3240
3743
|
" ",
|
|
3241
|
-
/* @__PURE__ */ (0,
|
|
3744
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
|
|
3242
3745
|
]
|
|
3243
3746
|
}
|
|
3244
3747
|
)
|
|
@@ -3250,8 +3753,15 @@ function PreChatForm({
|
|
|
3250
3753
|
}
|
|
3251
3754
|
|
|
3252
3755
|
// src/hooks/useDraggablePosition.ts
|
|
3253
|
-
var
|
|
3756
|
+
var import_react13 = require("react");
|
|
3254
3757
|
var STORAGE_KEY2 = "xcelsior-chat-position";
|
|
3758
|
+
var DRAG_THRESHOLD = 5;
|
|
3759
|
+
var FAB_SIZE = 64;
|
|
3760
|
+
var MARGIN = 16;
|
|
3761
|
+
var BOTTOM = 20;
|
|
3762
|
+
var VELOCITY_THRESHOLD = 0.4;
|
|
3763
|
+
var SETTLE_DURATION = 400;
|
|
3764
|
+
var SETTLE_EASING = "cubic-bezier(0.2, 0.9, 0.3, 1.1)";
|
|
3255
3765
|
function getStoredPosition() {
|
|
3256
3766
|
try {
|
|
3257
3767
|
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
@@ -3260,21 +3770,33 @@ function getStoredPosition() {
|
|
|
3260
3770
|
}
|
|
3261
3771
|
return "right";
|
|
3262
3772
|
}
|
|
3263
|
-
function storePosition(
|
|
3773
|
+
function storePosition(pos) {
|
|
3264
3774
|
try {
|
|
3265
|
-
localStorage.setItem(STORAGE_KEY2,
|
|
3775
|
+
localStorage.setItem(STORAGE_KEY2, pos);
|
|
3266
3776
|
} catch {
|
|
3267
3777
|
}
|
|
3268
3778
|
}
|
|
3779
|
+
function restingX(side) {
|
|
3780
|
+
return side === "left" ? MARGIN : window.innerWidth - MARGIN - FAB_SIZE;
|
|
3781
|
+
}
|
|
3782
|
+
function restingY() {
|
|
3783
|
+
return window.innerHeight - BOTTOM - FAB_SIZE;
|
|
3784
|
+
}
|
|
3269
3785
|
function useDraggablePosition(configPosition = "auto") {
|
|
3270
3786
|
const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
|
|
3271
|
-
const [position, setPosition] = (0,
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3274
|
-
const
|
|
3275
|
-
const
|
|
3276
|
-
const
|
|
3277
|
-
(0,
|
|
3787
|
+
const [position, setPosition] = (0, import_react13.useState)(resolvedDefault);
|
|
3788
|
+
const positionRef = (0, import_react13.useRef)(position);
|
|
3789
|
+
positionRef.current = position;
|
|
3790
|
+
const [isDragging, setIsDragging] = (0, import_react13.useState)(false);
|
|
3791
|
+
const containerRef = (0, import_react13.useRef)(null);
|
|
3792
|
+
const draggingRef = (0, import_react13.useRef)(false);
|
|
3793
|
+
const didDragRef = (0, import_react13.useRef)(false);
|
|
3794
|
+
const startPointer = (0, import_react13.useRef)({ x: 0, y: 0 });
|
|
3795
|
+
const startRect = (0, import_react13.useRef)({ x: 0, y: 0 });
|
|
3796
|
+
const samples = (0, import_react13.useRef)([]);
|
|
3797
|
+
const hasShownHint = (0, import_react13.useRef)(false);
|
|
3798
|
+
const [showHint, setShowHint] = (0, import_react13.useState)(false);
|
|
3799
|
+
(0, import_react13.useEffect)(() => {
|
|
3278
3800
|
try {
|
|
3279
3801
|
const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
|
|
3280
3802
|
if (!hasVisited && !hasShownHint.current) {
|
|
@@ -3289,53 +3811,115 @@ function useDraggablePosition(configPosition = "auto") {
|
|
|
3289
3811
|
} catch {
|
|
3290
3812
|
}
|
|
3291
3813
|
}, []);
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3814
|
+
(0, import_react13.useEffect)(() => {
|
|
3815
|
+
const handleMove = (e) => {
|
|
3816
|
+
if (!draggingRef.current) return;
|
|
3817
|
+
const el = containerRef.current;
|
|
3818
|
+
if (!el) return;
|
|
3819
|
+
const dx = e.clientX - startPointer.current.x;
|
|
3820
|
+
const dy = e.clientY - startPointer.current.y;
|
|
3821
|
+
if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
|
|
3822
|
+
didDragRef.current = true;
|
|
3823
|
+
}
|
|
3824
|
+
el.style.transition = "none";
|
|
3825
|
+
el.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
3826
|
+
const now = performance.now();
|
|
3827
|
+
samples.current.push({ x: e.clientX, t: now });
|
|
3828
|
+
if (samples.current.length > 5) samples.current.shift();
|
|
3829
|
+
};
|
|
3830
|
+
const handleUp = (e) => {
|
|
3831
|
+
if (!draggingRef.current) return;
|
|
3832
|
+
draggingRef.current = false;
|
|
3833
|
+
const el = containerRef.current;
|
|
3834
|
+
if (!el) return;
|
|
3835
|
+
let velocityX = 0;
|
|
3836
|
+
const s = samples.current;
|
|
3837
|
+
if (s.length >= 2) {
|
|
3838
|
+
const first = s[0];
|
|
3839
|
+
const last = s[s.length - 1];
|
|
3840
|
+
const dt = last.t - first.t;
|
|
3841
|
+
if (dt > 0) velocityX = (last.x - first.x) / dt;
|
|
3842
|
+
}
|
|
3843
|
+
samples.current = [];
|
|
3844
|
+
const currentVisualX = startRect.current.x + (e.clientX - startPointer.current.x);
|
|
3845
|
+
const screenMid = window.innerWidth / 2;
|
|
3846
|
+
let targetSide;
|
|
3847
|
+
if (Math.abs(velocityX) > VELOCITY_THRESHOLD) {
|
|
3848
|
+
targetSide = velocityX < 0 ? "left" : "right";
|
|
3849
|
+
} else {
|
|
3850
|
+
targetSide = currentVisualX + FAB_SIZE / 2 < screenMid ? "left" : "right";
|
|
3851
|
+
}
|
|
3852
|
+
const oldBaseX = restingX(positionRef.current);
|
|
3853
|
+
const oldBaseY = restingY();
|
|
3854
|
+
const currentX = startRect.current.x + (e.clientX - startPointer.current.x);
|
|
3855
|
+
const currentY = startRect.current.y + (e.clientY - startPointer.current.y);
|
|
3856
|
+
const targetX = restingX(targetSide);
|
|
3857
|
+
const targetY = restingY();
|
|
3858
|
+
const fromTx = currentX - oldBaseX;
|
|
3859
|
+
const fromTy = currentY - oldBaseY;
|
|
3860
|
+
const toTx = targetX - oldBaseX;
|
|
3861
|
+
const toTy = targetY - oldBaseY;
|
|
3862
|
+
el.style.transition = "none";
|
|
3863
|
+
el.style.transform = `translate(${fromTx}px, ${fromTy}px)`;
|
|
3864
|
+
void el.offsetHeight;
|
|
3865
|
+
el.style.transition = `transform ${SETTLE_DURATION}ms ${SETTLE_EASING}`;
|
|
3866
|
+
el.style.transform = `translate(${toTx}px, ${toTy}px)`;
|
|
3867
|
+
const finish = () => {
|
|
3868
|
+
el.style.transition = "";
|
|
3869
|
+
el.style.transform = "";
|
|
3870
|
+
el.removeEventListener("transitionend", finish);
|
|
3871
|
+
setIsDragging(false);
|
|
3872
|
+
if (targetSide !== positionRef.current) {
|
|
3873
|
+
setPosition(targetSide);
|
|
3874
|
+
storePosition(targetSide);
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
el.addEventListener("transitionend", finish, { once: true });
|
|
3878
|
+
setTimeout(finish, SETTLE_DURATION + 50);
|
|
3879
|
+
};
|
|
3880
|
+
document.addEventListener("pointermove", handleMove);
|
|
3881
|
+
document.addEventListener("pointerup", handleUp);
|
|
3882
|
+
return () => {
|
|
3883
|
+
document.removeEventListener("pointermove", handleMove);
|
|
3884
|
+
document.removeEventListener("pointerup", handleUp);
|
|
3885
|
+
};
|
|
3296
3886
|
}, []);
|
|
3297
|
-
const
|
|
3887
|
+
const handlePointerDown = (0, import_react13.useCallback)((e) => {
|
|
3888
|
+
e.preventDefault();
|
|
3889
|
+
const el = containerRef.current;
|
|
3890
|
+
if (!el) return;
|
|
3891
|
+
const rect = el.getBoundingClientRect();
|
|
3892
|
+
startPointer.current = { x: e.clientX, y: e.clientY };
|
|
3893
|
+
startRect.current = { x: rect.left, y: rect.top };
|
|
3894
|
+
draggingRef.current = true;
|
|
3895
|
+
didDragRef.current = false;
|
|
3896
|
+
samples.current = [];
|
|
3897
|
+
setIsDragging(true);
|
|
3298
3898
|
}, []);
|
|
3299
|
-
const
|
|
3300
|
-
(e) => {
|
|
3301
|
-
setIsDragging(false);
|
|
3302
|
-
e.target.releasePointerCapture(e.pointerId);
|
|
3303
|
-
const deltaX = e.clientX - dragStartX.current;
|
|
3304
|
-
if (Math.abs(deltaX) > 50) {
|
|
3305
|
-
const screenMid = window.innerWidth / 2;
|
|
3306
|
-
const newPosition = e.clientX < screenMid ? "left" : "right";
|
|
3307
|
-
if (newPosition !== position) {
|
|
3308
|
-
setPosition(newPosition);
|
|
3309
|
-
storePosition(newPosition);
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
},
|
|
3313
|
-
[position]
|
|
3314
|
-
);
|
|
3899
|
+
const shouldSuppressClick = (0, import_react13.useCallback)(() => didDragRef.current, []);
|
|
3315
3900
|
return {
|
|
3316
3901
|
position,
|
|
3317
3902
|
isDragging,
|
|
3318
3903
|
showHint,
|
|
3319
|
-
|
|
3904
|
+
containerRef,
|
|
3905
|
+
shouldSuppressClick,
|
|
3320
3906
|
handlers: {
|
|
3321
|
-
onPointerDown: handlePointerDown
|
|
3322
|
-
onPointerMove: handlePointerMove,
|
|
3323
|
-
onPointerUp: handlePointerUp
|
|
3907
|
+
onPointerDown: handlePointerDown
|
|
3324
3908
|
}
|
|
3325
3909
|
};
|
|
3326
3910
|
}
|
|
3327
3911
|
|
|
3328
3912
|
// src/hooks/useChatWidgetState.ts
|
|
3329
|
-
var
|
|
3913
|
+
var import_react14 = require("react");
|
|
3330
3914
|
function useChatWidgetState({
|
|
3331
3915
|
state: controlledState,
|
|
3332
3916
|
defaultState = "minimized",
|
|
3333
3917
|
onStateChange
|
|
3334
3918
|
}) {
|
|
3335
|
-
const [uncontrolledState, setUncontrolledState] = (0,
|
|
3919
|
+
const [uncontrolledState, setUncontrolledState] = (0, import_react14.useState)(defaultState);
|
|
3336
3920
|
const isControlled = controlledState !== void 0 && controlledState !== "undefined";
|
|
3337
3921
|
const currentState = isControlled ? controlledState : uncontrolledState;
|
|
3338
|
-
const setState = (0,
|
|
3922
|
+
const setState = (0, import_react14.useCallback)(
|
|
3339
3923
|
(newValue) => {
|
|
3340
3924
|
if (!isControlled) {
|
|
3341
3925
|
setUncontrolledState(newValue);
|
|
@@ -3352,7 +3936,7 @@ function useChatWidgetState({
|
|
|
3352
3936
|
}
|
|
3353
3937
|
|
|
3354
3938
|
// src/components/Chat.tsx
|
|
3355
|
-
var
|
|
3939
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3356
3940
|
function generateSessionId() {
|
|
3357
3941
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
3358
3942
|
return crypto.randomUUID();
|
|
@@ -3368,12 +3952,12 @@ function Chat({
|
|
|
3368
3952
|
defaultState = "minimized",
|
|
3369
3953
|
onStateChange
|
|
3370
3954
|
}) {
|
|
3371
|
-
const [userInfo, setUserInfo] = (0,
|
|
3372
|
-
const [conversationId, setConversationId] = (0,
|
|
3373
|
-
const [isLoading, setIsLoading] = (0,
|
|
3955
|
+
const [userInfo, setUserInfo] = (0, import_react15.useState)(null);
|
|
3956
|
+
const [conversationId, setConversationId] = (0, import_react15.useState)("");
|
|
3957
|
+
const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
|
|
3374
3958
|
const identityMode = config.identityCollection || "progressive";
|
|
3375
|
-
const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
|
|
3376
|
-
const sessionInitializedRef = (0,
|
|
3959
|
+
const { position, isDragging, showHint, containerRef, shouldSuppressClick, handlers } = useDraggablePosition(config.position);
|
|
3960
|
+
const sessionInitializedRef = (0, import_react15.useRef)(false);
|
|
3377
3961
|
const { currentState, setState } = useChatWidgetState({
|
|
3378
3962
|
state,
|
|
3379
3963
|
defaultState,
|
|
@@ -3384,7 +3968,7 @@ function Chat({
|
|
|
3384
3968
|
const configUserName = config.currentUser?.name;
|
|
3385
3969
|
const configUserAvatar = config.currentUser?.avatar;
|
|
3386
3970
|
const configUserStatus = config.currentUser?.status;
|
|
3387
|
-
(0,
|
|
3971
|
+
(0, import_react15.useEffect)(() => {
|
|
3388
3972
|
if (sessionInitializedRef.current) return;
|
|
3389
3973
|
const initializeSession = () => {
|
|
3390
3974
|
try {
|
|
@@ -3424,7 +4008,33 @@ function Chat({
|
|
|
3424
4008
|
const convId = configConversationId || generateSessionId();
|
|
3425
4009
|
setConversationId(convId);
|
|
3426
4010
|
if (identityMode === "progressive" || identityMode === "none") {
|
|
3427
|
-
|
|
4011
|
+
let anonId;
|
|
4012
|
+
try {
|
|
4013
|
+
const stored = localStorage.getItem("xcelsior-chat-anon-id");
|
|
4014
|
+
if (stored) {
|
|
4015
|
+
anonId = stored;
|
|
4016
|
+
} else {
|
|
4017
|
+
anonId = `anon-${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`;
|
|
4018
|
+
localStorage.setItem("xcelsior-chat-anon-id", anonId);
|
|
4019
|
+
}
|
|
4020
|
+
} catch {
|
|
4021
|
+
anonId = `anon-${Math.random().toString(36).slice(2)}`;
|
|
4022
|
+
}
|
|
4023
|
+
const anonEmail = `${anonId}@anonymous.xcelsior.co`;
|
|
4024
|
+
const anonUser = { name: "Visitor", email: anonEmail, type: "customer", status: "online" };
|
|
4025
|
+
setUserInfo(anonUser);
|
|
4026
|
+
try {
|
|
4027
|
+
localStorage.setItem(
|
|
4028
|
+
`${storageKeyPrefix}_user`,
|
|
4029
|
+
JSON.stringify({
|
|
4030
|
+
name: anonUser.name,
|
|
4031
|
+
email: anonUser.email,
|
|
4032
|
+
conversationId: convId,
|
|
4033
|
+
timestamp: Date.now()
|
|
4034
|
+
})
|
|
4035
|
+
);
|
|
4036
|
+
} catch {
|
|
4037
|
+
}
|
|
3428
4038
|
}
|
|
3429
4039
|
sessionInitializedRef.current = true;
|
|
3430
4040
|
} catch (error) {
|
|
@@ -3437,7 +4047,7 @@ function Chat({
|
|
|
3437
4047
|
};
|
|
3438
4048
|
initializeSession();
|
|
3439
4049
|
}, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
|
|
3440
|
-
const handlePreChatSubmit = (0,
|
|
4050
|
+
const handlePreChatSubmit = (0, import_react15.useCallback)(
|
|
3441
4051
|
(name, email) => {
|
|
3442
4052
|
const convId = conversationId || generateSessionId();
|
|
3443
4053
|
const user = { name, email, type: "customer", status: "online" };
|
|
@@ -3463,49 +4073,52 @@ function Chat({
|
|
|
3463
4073
|
const primaryColor = config.theme?.primary || "#337eff";
|
|
3464
4074
|
const primaryStrong = config.theme?.primaryStrong || "#005eff";
|
|
3465
4075
|
if (currentState === "minimized") {
|
|
3466
|
-
return /* @__PURE__ */ (0,
|
|
3467
|
-
"
|
|
4076
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
4077
|
+
"div",
|
|
3468
4078
|
{
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
4079
|
+
ref: containerRef,
|
|
4080
|
+
className: `fixed bottom-5 ${positionClass} z-50 ${className}`,
|
|
4081
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
4082
|
+
"button",
|
|
4083
|
+
{
|
|
4084
|
+
type: "button",
|
|
4085
|
+
onClick: () => {
|
|
4086
|
+
if (!shouldSuppressClick()) setState("open");
|
|
4087
|
+
},
|
|
4088
|
+
className: `group relative rounded-full text-white flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing scale-110" : "cursor-grab hover:scale-[1.08] transition-transform duration-300"}`,
|
|
4089
|
+
style: {
|
|
4090
|
+
width: 64,
|
|
4091
|
+
height: 64,
|
|
4092
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
|
|
4093
|
+
boxShadow: [
|
|
4094
|
+
`0 4px 24px 0 ${primaryColor}80`,
|
|
4095
|
+
`0 8px 32px -4px rgba(0,0,0,0.3)`,
|
|
4096
|
+
`inset 0 1px 0 0 rgba(255,255,255,0.2)`
|
|
4097
|
+
].join(", ")
|
|
4098
|
+
},
|
|
4099
|
+
"aria-label": "Open chat",
|
|
4100
|
+
...handlers,
|
|
4101
|
+
children: [
|
|
4102
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChatBubbleIcon, { size: 36, className: "pointer-events-none" }),
|
|
4103
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
4104
|
+
"span",
|
|
4105
|
+
{
|
|
4106
|
+
className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
|
|
4107
|
+
style: {
|
|
4108
|
+
backgroundColor: primaryColor,
|
|
4109
|
+
opacity: 0.15,
|
|
4110
|
+
animationDuration: "2.5s"
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
)
|
|
4114
|
+
]
|
|
4115
|
+
}
|
|
4116
|
+
)
|
|
3504
4117
|
}
|
|
3505
|
-
)
|
|
4118
|
+
);
|
|
3506
4119
|
}
|
|
3507
4120
|
if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
|
|
3508
|
-
return /* @__PURE__ */ (0,
|
|
4121
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3509
4122
|
PreChatForm,
|
|
3510
4123
|
{
|
|
3511
4124
|
onSubmit: handlePreChatSubmit,
|
|
@@ -3522,7 +4135,7 @@ function Chat({
|
|
|
3522
4135
|
conversationId,
|
|
3523
4136
|
currentUser: userInfo || void 0
|
|
3524
4137
|
};
|
|
3525
|
-
return /* @__PURE__ */ (0,
|
|
4138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3526
4139
|
ChatWidget,
|
|
3527
4140
|
{
|
|
3528
4141
|
config: fullConfig,
|
|
@@ -3535,12 +4148,12 @@ function Chat({
|
|
|
3535
4148
|
}
|
|
3536
4149
|
|
|
3537
4150
|
// src/components/TypingIndicator.tsx
|
|
3538
|
-
var
|
|
4151
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3539
4152
|
function TypingIndicator({ isTyping, userName }) {
|
|
3540
4153
|
if (!isTyping) {
|
|
3541
4154
|
return null;
|
|
3542
4155
|
}
|
|
3543
|
-
return /* @__PURE__ */ (0,
|
|
4156
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3544
4157
|
"div",
|
|
3545
4158
|
{
|
|
3546
4159
|
className: "px-4 py-2",
|
|
@@ -3548,8 +4161,8 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3548
4161
|
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3549
4162
|
backgroundColor: "rgba(0,0,0,0.15)"
|
|
3550
4163
|
},
|
|
3551
|
-
children: /* @__PURE__ */ (0,
|
|
3552
|
-
/* @__PURE__ */ (0,
|
|
4164
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4165
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3553
4166
|
"span",
|
|
3554
4167
|
{
|
|
3555
4168
|
className: "rounded-full animate-bounce",
|
|
@@ -3563,7 +4176,7 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3563
4176
|
},
|
|
3564
4177
|
i
|
|
3565
4178
|
)) }),
|
|
3566
|
-
/* @__PURE__ */ (0,
|
|
4179
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3567
4180
|
"span",
|
|
3568
4181
|
{
|
|
3569
4182
|
style: {
|
|
@@ -3580,6 +4193,9 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3580
4193
|
}
|
|
3581
4194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3582
4195
|
0 && (module.exports = {
|
|
4196
|
+
BookingCancelledCard,
|
|
4197
|
+
BookingConfirmationCard,
|
|
4198
|
+
BookingSlotPicker,
|
|
3583
4199
|
Chat,
|
|
3584
4200
|
ChatHeader,
|
|
3585
4201
|
ChatInput,
|