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