@xcelsior/ui-chat 2.0.3 → 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.
Files changed (54) hide show
  1. package/.storybook/preview.tsx +2 -1
  2. package/dist/index.d.mts +58 -5
  3. package/dist/index.d.ts +58 -5
  4. package/dist/index.js +1073 -473
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1060 -463
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +4 -2
  9. package/src/components/BookingCancelledCard.tsx +103 -0
  10. package/src/components/BookingCards.stories.tsx +102 -0
  11. package/src/components/BookingConfirmationCard.tsx +170 -0
  12. package/src/components/BookingSlotPicker.stories.tsx +87 -0
  13. package/src/components/BookingSlotPicker.tsx +253 -0
  14. package/src/components/BrandIcons.stories.tsx +32 -1
  15. package/src/components/BrandIcons.tsx +21 -17
  16. package/src/components/Chat.tsx +78 -83
  17. package/src/components/ChatWidget.tsx +30 -3
  18. package/src/components/MessageItem.tsx +83 -72
  19. package/src/components/MessageList.tsx +4 -0
  20. package/src/hooks/useDraggablePosition.ts +147 -42
  21. package/src/hooks/useMessages.ts +119 -45
  22. package/src/hooks/useWebSocket.ts +17 -4
  23. package/src/index.tsx +11 -0
  24. package/src/types.ts +39 -2
  25. package/src/utils/api.ts +1 -0
  26. package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
  27. package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
  28. package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
  29. package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
  30. package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
  31. package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
  32. package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
  33. package/storybook-static/assets/{Color-YHDXOIA2-BMnd3YrF.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
  34. package/storybook-static/assets/{DocsRenderer-CFRXHY34-i_W8iCu9.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
  35. package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
  36. package/storybook-static/assets/MessageItem.stories-Cs5Vtkle.js +422 -0
  37. package/storybook-static/assets/{entry-preview-oDnntGcx.js → entry-preview-vcpiajAT.js} +1 -1
  38. package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
  39. package/storybook-static/assets/{iframe-CGBtu2Se.js → iframe-Cx1n-SeE.js} +2 -2
  40. package/storybook-static/assets/preview-B8y-wc-n.css +1 -0
  41. package/storybook-static/assets/preview-CC4t7T7W.js +1 -0
  42. package/storybook-static/assets/{preview-BRpahs9B.js → preview-Do3b3dZv.js} +2 -2
  43. package/storybook-static/iframe.html +1 -1
  44. package/storybook-static/index.json +1 -1
  45. package/storybook-static/project.json +1 -1
  46. package/tsconfig.json +4 -0
  47. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
  48. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
  49. package/storybook-static/assets/Chat.stories-J_Yp51wU.js +0 -803
  50. package/storybook-static/assets/MessageItem-DAaKZ9s9.js +0 -14
  51. package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +0 -255
  52. package/storybook-static/assets/ToastContext-Bty1K7ya.js +0 -1
  53. package/storybook-static/assets/preview-DUOvJmsz.js +0 -1
  54. package/storybook-static/assets/preview-DcGwT3kv.css +0 -1
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 import_react10 = require("react");
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 === "message" && message.data) {
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,6 +258,8 @@ async function fetchMessages(baseUrl, params, headers) {
251
258
  }
252
259
 
253
260
  // src/hooks/useMessages.ts
261
+ var BOT_THINKING_TIMEOUT = 15e3;
262
+ var AGENT_STATUSES = /* @__PURE__ */ new Set(["agent_active", "pending_agent"]);
254
263
  function useMessages(websocket, config) {
255
264
  const [messages, setMessages] = (0, import_react2.useState)([]);
256
265
  const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
@@ -259,6 +268,8 @@ function useMessages(websocket, config) {
259
268
  const [hasMore, setHasMore] = (0, import_react2.useState)(true);
260
269
  const [isLoadingMore, setIsLoadingMore] = (0, import_react2.useState)(false);
261
270
  const [isBotThinking, setIsBotThinking] = (0, import_react2.useState)(false);
271
+ const botThinkingTimerRef = (0, import_react2.useRef)(null);
272
+ const agentActiveRef = (0, import_react2.useRef)(false);
262
273
  const { httpApiUrl, conversationId, headers, onError, toast } = config;
263
274
  const headersWithApiKey = (0, import_react2.useMemo)(
264
275
  () => ({
@@ -267,11 +278,33 @@ function useMessages(websocket, config) {
267
278
  }),
268
279
  [headers, config.apiKey]
269
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
+ }, []);
270
288
  (0, import_react2.useEffect)(() => {
271
- const loadMessages = async () => {
272
- if (!httpApiUrl || !conversationId) {
273
- return;
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 {
274
301
  }
302
+ };
303
+ fetchStatus();
304
+ }, [httpApiUrl, conversationId, headersWithApiKey]);
305
+ (0, import_react2.useEffect)(() => {
306
+ if (!httpApiUrl || !conversationId) return;
307
+ const loadMessages = async () => {
275
308
  setIsLoading(true);
276
309
  setError(null);
277
310
  try {
@@ -283,6 +316,9 @@ function useMessages(websocket, config) {
283
316
  setMessages(result.data);
284
317
  setNextPageToken(result.nextPageToken);
285
318
  setHasMore(!!result.nextPageToken);
319
+ if (result.data.some((m) => m.senderType === "agent")) {
320
+ agentActiveRef.current = true;
321
+ }
286
322
  } catch (err) {
287
323
  const error2 = err instanceof Error ? err : new Error("Failed to load messages");
288
324
  setError(error2);
@@ -298,30 +334,60 @@ function useMessages(websocket, config) {
298
334
  (0, import_react2.useEffect)(() => {
299
335
  if (websocket.lastMessage?.type === "message" && websocket.lastMessage.data) {
300
336
  const newMessage = websocket.lastMessage.data;
301
- if (conversationId && newMessage.conversationId !== conversationId) {
302
- return;
303
- }
337
+ if (conversationId && newMessage.conversationId !== conversationId) return;
304
338
  setMessages((prev) => {
305
- if (prev.some((msg) => msg.id === newMessage.id)) {
306
- return prev;
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;
307
348
  }
308
349
  return [...prev, newMessage];
309
350
  });
310
- if (newMessage.senderType === "bot" || newMessage.senderType === "system") {
311
- setIsBotThinking(false);
351
+ if (newMessage.senderType === "agent") {
352
+ agentActiveRef.current = true;
353
+ }
354
+ if (newMessage.senderType !== "customer") {
355
+ clearBotThinking();
312
356
  }
313
357
  onMessageReceived?.(newMessage);
314
358
  }
315
- }, [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]);
316
376
  const addMessage = (0, import_react2.useCallback)((message) => {
377
+ const isAgentActive = agentActiveRef.current;
317
378
  setMessages((prev) => {
318
- if (prev.some((msg) => msg.id === message.id)) {
319
- return prev;
320
- }
379
+ if (prev.some((msg) => msg.id === message.id)) return prev;
321
380
  return [...prev, message];
322
381
  });
323
- if (message.senderType === "customer") {
382
+ if (message.senderType === "agent") {
383
+ agentActiveRef.current = true;
384
+ }
385
+ if (message.senderType === "customer" && !isAgentActive) {
324
386
  setIsBotThinking(true);
387
+ if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
388
+ botThinkingTimerRef.current = setTimeout(() => {
389
+ setIsBotThinking(false);
390
+ }, BOT_THINKING_TIMEOUT);
325
391
  }
326
392
  }, []);
327
393
  const updateMessageStatus = (0, import_react2.useCallback)((messageId, status) => {
@@ -329,21 +395,16 @@ function useMessages(websocket, config) {
329
395
  }, []);
330
396
  const clearMessages = (0, import_react2.useCallback)(() => {
331
397
  setMessages([]);
398
+ agentActiveRef.current = false;
332
399
  }, []);
333
400
  const loadMore = (0, import_react2.useCallback)(async () => {
334
- if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) {
335
- return;
336
- }
401
+ if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
337
402
  setIsLoadingMore(true);
338
403
  setError(null);
339
404
  try {
340
405
  const result = await fetchMessages(
341
406
  httpApiUrl,
342
- {
343
- conversationId,
344
- limit: 20,
345
- pageToken: nextPageToken
346
- },
407
+ { conversationId, limit: 20, pageToken: nextPageToken },
347
408
  headersWithApiKey
348
409
  );
349
410
  setMessages((prev) => [...result.data, ...prev]);
@@ -356,15 +417,12 @@ function useMessages(websocket, config) {
356
417
  } finally {
357
418
  setIsLoadingMore(false);
358
419
  }
359
- }, [
360
- hasMore,
361
- isLoadingMore,
362
- httpApiUrl,
363
- conversationId,
364
- nextPageToken,
365
- headersWithApiKey,
366
- onError
367
- ]);
420
+ }, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
421
+ (0, import_react2.useEffect)(() => {
422
+ return () => {
423
+ if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
424
+ };
425
+ }, []);
368
426
  return {
369
427
  messages,
370
428
  addMessage,
@@ -792,24 +850,20 @@ function XcelsiorSymbol({ size = 24, color = "white", className = "", style }) {
792
850
  }
793
851
  );
794
852
  }
795
- function ChatBubbleIcon({ size = 28, color = "white", className = "", style }) {
796
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
797
- "svg",
798
- {
799
- width: size,
800
- height: size,
801
- viewBox: "0 0 24 24",
802
- fill: "none",
803
- stroke: color,
804
- strokeWidth: 2,
805
- strokeLinecap: "round",
806
- strokeLinejoin: "round",
807
- className,
808
- style,
809
- "aria-hidden": "true",
810
- 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" })
811
- }
812
- );
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
+ ] });
813
867
  }
814
868
  function XcelsiorAvatar({ size = 40, className = "" }) {
815
869
  const iconSize = Math.round(size * 0.55);
@@ -973,7 +1027,7 @@ function ChatHeader({ agent, onClose, onMinimize, theme }) {
973
1027
  }
974
1028
 
975
1029
  // src/components/MessageList.tsx
976
- var import_react7 = require("react");
1030
+ var import_react8 = require("react");
977
1031
 
978
1032
  // src/components/Spinner.tsx
979
1033
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1021,6 +1075,7 @@ function Spinner({ size = "md", color = "currentColor" }) {
1021
1075
 
1022
1076
  // src/components/MessageItem.tsx
1023
1077
  var import_date_fns = require("date-fns");
1078
+ var import_lucide_react4 = require("lucide-react");
1024
1079
 
1025
1080
  // src/components/MarkdownMessage.tsx
1026
1081
  var import_react_markdown = __toESM(require("react-markdown"));
@@ -1260,14 +1315,481 @@ function MarkdownMessage({ content, theme }) {
1260
1315
  ) });
1261
1316
  }
1262
1317
 
1263
- // src/components/MessageItem.tsx
1318
+ // src/components/BookingSlotPicker.tsx
1319
+ var import_react6 = require("react");
1320
+ var import_lucide_react = require("lucide-react");
1264
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");
1265
1786
  function MessageItem({
1266
1787
  message,
1267
1788
  currentUser,
1268
1789
  showAvatar = true,
1269
1790
  showTimestamp = true,
1270
- theme
1791
+ theme,
1792
+ onBookingSlotSelected
1271
1793
  }) {
1272
1794
  const isOwnMessage = message.senderType === currentUser.type;
1273
1795
  const isSystemMessage = message.senderType === "system";
@@ -1286,8 +1808,63 @@ function MessageItem({
1286
1808
  const primaryStrong = theme?.primaryStrong || "#005eff";
1287
1809
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1288
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
+ }
1289
1866
  if (isSystemMessage) {
1290
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1291
1868
  "div",
1292
1869
  {
1293
1870
  className: "px-4 py-1.5 rounded-full",
@@ -1295,7 +1872,7 @@ function MessageItem({
1295
1872
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
1296
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)"
1297
1874
  },
1298
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1875
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1299
1876
  "p",
1300
1877
  {
1301
1878
  style: {
@@ -1334,12 +1911,12 @@ function MessageItem({
1334
1911
  borderRadius: "18px 18px 18px 4px",
1335
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)"
1336
1913
  };
1337
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1914
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1338
1915
  "div",
1339
1916
  {
1340
1917
  className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1341
1918
  children: [
1342
- showAvatar && !isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
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)(
1343
1920
  "div",
1344
1921
  {
1345
1922
  className: "h-7 w-7 rounded-full flex items-center justify-center",
@@ -1347,34 +1924,24 @@ function MessageItem({
1347
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))`,
1348
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)"
1349
1926
  },
1350
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1351
- "svg",
1927
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1928
+ import_lucide_react4.Headphones,
1352
1929
  {
1353
- width: "14",
1354
- height: "14",
1355
- viewBox: "0 0 24 24",
1356
- fill: "none",
1930
+ size: 14,
1357
1931
  stroke: isLightTheme ? primaryColor : "white",
1358
- strokeWidth: "2",
1359
- strokeLinecap: "round",
1360
- strokeLinejoin: "round",
1361
- "aria-hidden": "true",
1362
- children: [
1363
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Agent" }),
1364
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 18v-6a9 9 0 0 1 18 0v6" }),
1365
- /* @__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" })
1366
- ]
1932
+ strokeWidth: 2,
1933
+ "aria-hidden": "true"
1367
1934
  }
1368
1935
  )
1369
1936
  }
1370
1937
  ) }),
1371
- showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-7 flex-shrink-0" }),
1372
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1938
+ showAvatar && isOwnMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-7 flex-shrink-0" }),
1939
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1373
1940
  "div",
1374
1941
  {
1375
1942
  className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
1376
1943
  children: [
1377
- senderLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1944
+ senderLabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1378
1945
  "span",
1379
1946
  {
1380
1947
  className: "mb-1 px-1 font-medium",
@@ -1386,7 +1953,7 @@ function MessageItem({
1386
1953
  children: senderLabel
1387
1954
  }
1388
1955
  ),
1389
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1956
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1390
1957
  "div",
1391
1958
  {
1392
1959
  className: "px-4 py-2.5",
@@ -1399,12 +1966,12 @@ function MessageItem({
1399
1966
  children: [
1400
1967
  message.messageType === "text" && (isOwnMessage ? (
1401
1968
  // User messages: plain text — no markdown parsing
1402
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
1969
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
1403
1970
  ) : (
1404
1971
  // Bot / agent messages: full markdown rendering
1405
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownMessage, { content: message.content, theme })
1972
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownMessage, { content: message.content, theme })
1406
1973
  )),
1407
- message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1974
+ message.messageType === "image" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1408
1975
  "img",
1409
1976
  {
1410
1977
  src: message.content,
@@ -1413,26 +1980,9 @@ function MessageItem({
1413
1980
  loading: "lazy"
1414
1981
  }
1415
1982
  ) }),
1416
- message.messageType === "file" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
1417
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1418
- "svg",
1419
- {
1420
- width: "18",
1421
- height: "18",
1422
- viewBox: "0 0 24 24",
1423
- fill: "none",
1424
- stroke: "currentColor",
1425
- strokeWidth: "1.75",
1426
- strokeLinecap: "round",
1427
- strokeLinejoin: "round",
1428
- "aria-hidden": "true",
1429
- children: [
1430
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "File" }),
1431
- /* @__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" })
1432
- ]
1433
- }
1434
- ),
1435
- /* @__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)(
1436
1986
  "a",
1437
1987
  {
1438
1988
  href: message.content,
@@ -1450,12 +2000,12 @@ function MessageItem({
1450
2000
  ]
1451
2001
  }
1452
2002
  ),
1453
- showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2003
+ showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1454
2004
  "div",
1455
2005
  {
1456
2006
  className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
1457
2007
  children: [
1458
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2008
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1459
2009
  "span",
1460
2010
  {
1461
2011
  style: {
@@ -1468,63 +2018,10 @@ function MessageItem({
1468
2018
  })
1469
2019
  }
1470
2020
  ),
1471
- isOwnMessage && message.status && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: textMuted }, children: [
1472
- message.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1473
- "svg",
1474
- {
1475
- width: "14",
1476
- height: "14",
1477
- viewBox: "0 0 24 24",
1478
- fill: "none",
1479
- stroke: "currentColor",
1480
- strokeWidth: "2.5",
1481
- strokeLinecap: "round",
1482
- strokeLinejoin: "round",
1483
- "aria-hidden": "true",
1484
- children: [
1485
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Sent" }),
1486
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 6 9 17 4 12" })
1487
- ]
1488
- }
1489
- ),
1490
- message.status === "delivered" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1491
- "svg",
1492
- {
1493
- width: "14",
1494
- height: "14",
1495
- viewBox: "0 0 24 24",
1496
- fill: "none",
1497
- stroke: "currentColor",
1498
- strokeWidth: "2.5",
1499
- strokeLinecap: "round",
1500
- strokeLinejoin: "round",
1501
- "aria-hidden": "true",
1502
- children: [
1503
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Delivered" }),
1504
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
1505
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
1506
- ]
1507
- }
1508
- ),
1509
- message.status === "read" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1510
- "svg",
1511
- {
1512
- width: "14",
1513
- height: "14",
1514
- viewBox: "0 0 24 24",
1515
- fill: "none",
1516
- stroke: primaryColor,
1517
- strokeWidth: "2.5",
1518
- strokeLinecap: "round",
1519
- strokeLinejoin: "round",
1520
- "aria-hidden": "true",
1521
- children: [
1522
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("title", { children: "Read" }),
1523
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "18 6 7 17 2 12" }),
1524
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "22 6 11 17" })
1525
- ]
1526
- }
1527
- )
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" })
1528
2025
  ] })
1529
2026
  ]
1530
2027
  }
@@ -1538,8 +2035,8 @@ function MessageItem({
1538
2035
  }
1539
2036
 
1540
2037
  // src/components/ThinkingIndicator.tsx
1541
- var import_react6 = require("react");
1542
- var import_jsx_runtime6 = require("react/jsx-runtime");
2038
+ var import_react7 = require("react");
2039
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1543
2040
  var PHRASE_POOLS = {
1544
2041
  // ── Greetings & small talk ──
1545
2042
  greeting: [
@@ -1722,17 +2219,17 @@ function ThinkingIndicator({
1722
2219
  const isLightTheme = computeIsLightTheme2(theme?.background);
1723
2220
  const primaryColor = theme?.primary || "#337eff";
1724
2221
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
1725
- const [phraseIndex, setPhraseIndex] = (0, import_react6.useState)(0);
1726
- const [displayText, setDisplayText] = (0, import_react6.useState)("");
1727
- const [isDeleting, setIsDeleting] = (0, import_react6.useState)(false);
1728
- const phrasesRef = (0, import_react6.useRef)(getShuffledPhrases(detectContext(lastUserMessage)));
1729
- (0, import_react6.useEffect)(() => {
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)(() => {
1730
2227
  phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
1731
2228
  setPhraseIndex(0);
1732
2229
  setDisplayText("");
1733
2230
  setIsDeleting(false);
1734
2231
  }, [lastUserMessage]);
1735
- (0, import_react6.useEffect)(() => {
2232
+ (0, import_react7.useEffect)(() => {
1736
2233
  const phrases = phrasesRef.current;
1737
2234
  const phrase = phrases[phraseIndex % phrases.length];
1738
2235
  let timeout;
@@ -1758,7 +2255,7 @@ function ThinkingIndicator({
1758
2255
  }
1759
2256
  return () => clearTimeout(timeout);
1760
2257
  }, [displayText, isDeleting, phraseIndex]);
1761
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2258
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1762
2259
  "div",
1763
2260
  {
1764
2261
  className: "flex gap-2.5 mb-3",
@@ -1766,8 +2263,8 @@ function ThinkingIndicator({
1766
2263
  "aria-live": "polite",
1767
2264
  "aria-label": "Xcelsior is thinking",
1768
2265
  children: [
1769
- showAvatar && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(XcelsiorAvatar, { size: 28 }) }),
1770
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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)(
1771
2268
  "div",
1772
2269
  {
1773
2270
  style: {
@@ -1777,8 +2274,8 @@ function ThinkingIndicator({
1777
2274
  padding: "12px 16px",
1778
2275
  minWidth: 160
1779
2276
  },
1780
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1781
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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)(
1782
2279
  "span",
1783
2280
  {
1784
2281
  style: {
@@ -1792,7 +2289,7 @@ function ThinkingIndicator({
1792
2289
  },
1793
2290
  i
1794
2291
  )) }),
1795
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2292
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1796
2293
  "span",
1797
2294
  {
1798
2295
  style: {
@@ -1803,7 +2300,7 @@ function ThinkingIndicator({
1803
2300
  },
1804
2301
  children: [
1805
2302
  displayText,
1806
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2303
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1807
2304
  "span",
1808
2305
  {
1809
2306
  style: {
@@ -1829,7 +2326,7 @@ function ThinkingIndicator({
1829
2326
  }
1830
2327
 
1831
2328
  // src/components/MessageList.tsx
1832
- var import_jsx_runtime7 = require("react/jsx-runtime");
2329
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1833
2330
  function MessageList({
1834
2331
  messages,
1835
2332
  currentUser,
@@ -1842,15 +2339,16 @@ function MessageList({
1842
2339
  isLoadingMore = false,
1843
2340
  theme,
1844
2341
  onQuickAction,
1845
- isBotThinking = false
2342
+ isBotThinking = false,
2343
+ onBookingSlotSelected
1846
2344
  }) {
1847
- const messagesEndRef = (0, import_react7.useRef)(null);
1848
- const containerRef = (0, import_react7.useRef)(null);
1849
- const prevLengthRef = (0, import_react7.useRef)(messages.length);
1850
- const loadMoreTriggerRef = (0, import_react7.useRef)(null);
1851
- const prevScrollHeightRef = (0, import_react7.useRef)(0);
1852
- const hasInitialScrolledRef = (0, import_react7.useRef)(false);
1853
- const isUserScrollingRef = (0, import_react7.useRef)(false);
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);
1854
2352
  const bgColor = theme?.background || "#00001a";
1855
2353
  const isLightTheme = (() => {
1856
2354
  if (!bgColor.startsWith("#")) return false;
@@ -1863,7 +2361,7 @@ function MessageList({
1863
2361
  const primaryColor = theme?.primary || "#337eff";
1864
2362
  const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
1865
2363
  const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
1866
- (0, import_react7.useEffect)(() => {
2364
+ (0, import_react8.useEffect)(() => {
1867
2365
  if (autoScroll && messagesEndRef.current) {
1868
2366
  if (messages.length > prevLengthRef.current && !isLoadingMore) {
1869
2367
  messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
@@ -1871,7 +2369,7 @@ function MessageList({
1871
2369
  prevLengthRef.current = messages.length;
1872
2370
  }
1873
2371
  }, [messages.length, autoScroll, isLoadingMore]);
1874
- (0, import_react7.useEffect)(() => {
2372
+ (0, import_react8.useEffect)(() => {
1875
2373
  if (messages.length > 0 && messagesEndRef.current && !isLoading && !hasInitialScrolledRef.current) {
1876
2374
  setTimeout(() => {
1877
2375
  messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
@@ -1885,7 +2383,7 @@ function MessageList({
1885
2383
  hasInitialScrolledRef.current = true;
1886
2384
  }
1887
2385
  }, [isLoading, messages.length]);
1888
- (0, import_react7.useEffect)(() => {
2386
+ (0, import_react8.useEffect)(() => {
1889
2387
  if (isLoadingMore) {
1890
2388
  prevScrollHeightRef.current = containerRef.current?.scrollHeight || 0;
1891
2389
  } else if (prevScrollHeightRef.current > 0 && containerRef.current) {
@@ -1895,7 +2393,7 @@ function MessageList({
1895
2393
  prevScrollHeightRef.current = 0;
1896
2394
  }
1897
2395
  }, [isLoadingMore]);
1898
- const handleScroll = (0, import_react7.useCallback)(() => {
2396
+ const handleScroll = (0, import_react8.useCallback)(() => {
1899
2397
  if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
1900
2398
  if (!isUserScrollingRef.current) return;
1901
2399
  const { scrollTop } = containerRef.current;
@@ -1903,18 +2401,18 @@ function MessageList({
1903
2401
  onLoadMore();
1904
2402
  }
1905
2403
  }, [onLoadMore, hasMore, isLoadingMore]);
1906
- (0, import_react7.useEffect)(() => {
2404
+ (0, import_react8.useEffect)(() => {
1907
2405
  const container = containerRef.current;
1908
2406
  if (!container) return;
1909
2407
  container.addEventListener("scroll", handleScroll);
1910
2408
  return () => container.removeEventListener("scroll", handleScroll);
1911
2409
  }, [handleScroll]);
1912
2410
  if (isLoading) {
1913
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { size: "lg" }) });
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" }) });
1914
2412
  }
1915
2413
  if (messages.length === 0) {
1916
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
1917
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
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)(
1918
2416
  "h3",
1919
2417
  {
1920
2418
  className: "font-semibold mb-2",
@@ -1926,7 +2424,7 @@ function MessageList({
1926
2424
  children: "How can we help?"
1927
2425
  }
1928
2426
  ),
1929
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2427
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1930
2428
  "p",
1931
2429
  {
1932
2430
  className: "max-w-[240px]",
@@ -1940,11 +2438,11 @@ function MessageList({
1940
2438
  children: "Ask us anything. We are here to help you get the most out of Xcelsior."
1941
2439
  }
1942
2440
  ),
1943
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
2441
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap justify-center gap-2", children: [
1944
2442
  { label: "Our services", message: "What services does Xcelsior offer?" },
1945
2443
  { label: "Get a quote", message: "I would like to get a quote for a project" },
1946
2444
  { label: "Support", message: "I need help with something" }
1947
- ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2445
+ ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1948
2446
  "button",
1949
2447
  {
1950
2448
  type: "button",
@@ -1976,14 +2474,14 @@ function MessageList({
1976
2474
  )) })
1977
2475
  ] });
1978
2476
  }
1979
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1980
2478
  "div",
1981
2479
  {
1982
2480
  ref: containerRef,
1983
2481
  className: "flex-1 overflow-y-auto px-4 py-3",
1984
2482
  style: { scrollBehavior: "smooth" },
1985
2483
  children: [
1986
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
2484
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
1987
2485
  @keyframes thinkingPulse {
1988
2486
  0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
1989
2487
  30% { opacity: 1; transform: scale(1); }
@@ -1993,23 +2491,24 @@ function MessageList({
1993
2491
  50% { opacity: 0; }
1994
2492
  }
1995
2493
  ` }),
1996
- isLoadingMore && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { size: "sm" }) }),
1997
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: loadMoreTriggerRef }),
1998
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
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)(
1999
2497
  MessageItem,
2000
2498
  {
2001
2499
  message,
2002
2500
  currentUser,
2003
2501
  showAvatar: true,
2004
2502
  showTimestamp: true,
2005
- theme
2503
+ theme,
2504
+ onBookingSlotSelected
2006
2505
  },
2007
2506
  message.id
2008
2507
  )),
2009
- isTyping && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2.5 mb-3", children: [
2010
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(XcelsiorAvatar, { size: 28 }) }),
2011
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col items-start", children: [
2012
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
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)(
2013
2512
  "div",
2014
2513
  {
2015
2514
  className: "px-4 py-3",
@@ -2018,7 +2517,7 @@ function MessageList({
2018
2517
  borderRadius: "18px 18px 18px 4px",
2019
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)"
2020
2519
  },
2021
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
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)(
2022
2521
  "span",
2023
2522
  {
2024
2523
  className: "rounded-full animate-bounce",
@@ -2034,7 +2533,7 @@ function MessageList({
2034
2533
  )) })
2035
2534
  }
2036
2535
  ),
2037
- typingUser && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2536
+ typingUser && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2038
2537
  "span",
2039
2538
  {
2040
2539
  className: "mt-1 px-1",
@@ -2051,24 +2550,24 @@ function MessageList({
2051
2550
  )
2052
2551
  ] })
2053
2552
  ] }),
2054
- isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2553
+ isBotThinking && !isTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2055
2554
  ThinkingIndicator,
2056
2555
  {
2057
2556
  theme,
2058
2557
  lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
2059
2558
  }
2060
2559
  ),
2061
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: messagesEndRef })
2560
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
2062
2561
  ]
2063
2562
  }
2064
2563
  );
2065
2564
  }
2066
2565
 
2067
2566
  // src/components/ChatInput.tsx
2068
- var import_react8 = require("react");
2567
+ var import_react9 = require("react");
2069
2568
  var import_react_dom = require("react-dom");
2070
- var import_react9 = __toESM(require("@emoji-mart/react"));
2071
- var import_jsx_runtime8 = require("react/jsx-runtime");
2569
+ var import_react10 = __toESM(require("@emoji-mart/react"));
2570
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2072
2571
  function isLightColor(color) {
2073
2572
  let r = 0, g = 0, b = 0;
2074
2573
  if (color.startsWith("#")) {
@@ -2086,18 +2585,18 @@ function ChatInput({
2086
2585
  fileUpload,
2087
2586
  disabled = false
2088
2587
  }) {
2089
- const [message, setMessage] = (0, import_react8.useState)("");
2090
- const [showEmojiPicker, setShowEmojiPicker] = (0, import_react8.useState)(false);
2091
- const [emojiData, setEmojiData] = (0, import_react8.useState)();
2092
- const [emojiPickerPosition, setEmojiPickerPosition] = (0, import_react8.useState)(null);
2093
- const [isFocused, setIsFocused] = (0, import_react8.useState)(false);
2094
- const textAreaRef = (0, import_react8.useRef)(null);
2095
- const emojiPickerRef = (0, import_react8.useRef)(null);
2096
- const emojiButtonRef = (0, import_react8.useRef)(null);
2097
- const fileInputRef = (0, import_react8.useRef)(null);
2098
- const typingTimeoutRef = (0, import_react8.useRef)(null);
2099
- const startTypingTimeoutRef = (0, import_react8.useRef)(null);
2100
- const isTypingRef = (0, import_react8.useRef)(false);
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);
2101
2600
  const enableEmoji = config.enableEmoji ?? true;
2102
2601
  const enableFileUpload = config.enableFileUpload ?? true;
2103
2602
  const bgColor = config.theme?.background || "#00001a";
@@ -2105,7 +2604,7 @@ function ChatInput({
2105
2604
  const textColor = config.theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
2106
2605
  const textMuted = config.theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
2107
2606
  const primaryColor = config.theme?.primary || "#337eff";
2108
- (0, import_react8.useEffect)(() => {
2607
+ (0, import_react9.useEffect)(() => {
2109
2608
  if (!enableEmoji) return;
2110
2609
  (async () => {
2111
2610
  try {
@@ -2116,7 +2615,7 @@ function ChatInput({
2116
2615
  }
2117
2616
  })();
2118
2617
  }, [enableEmoji]);
2119
- (0, import_react8.useEffect)(() => {
2618
+ (0, import_react9.useEffect)(() => {
2120
2619
  const handleClickOutside = (event) => {
2121
2620
  if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) {
2122
2621
  setShowEmojiPicker(false);
@@ -2129,7 +2628,7 @@ function ChatInput({
2129
2628
  document.removeEventListener("mousedown", handleClickOutside);
2130
2629
  };
2131
2630
  }, [showEmojiPicker]);
2132
- (0, import_react8.useEffect)(() => {
2631
+ (0, import_react9.useEffect)(() => {
2133
2632
  return () => {
2134
2633
  if (typingTimeoutRef.current) {
2135
2634
  clearTimeout(typingTimeoutRef.current);
@@ -2139,7 +2638,7 @@ function ChatInput({
2139
2638
  }
2140
2639
  };
2141
2640
  }, []);
2142
- (0, import_react8.useEffect)(() => {
2641
+ (0, import_react9.useEffect)(() => {
2143
2642
  if (!showEmojiPicker) return;
2144
2643
  const updatePosition = () => {
2145
2644
  if (emojiButtonRef.current) {
@@ -2246,7 +2745,7 @@ ${uploadedFile.markdown}
2246
2745
  };
2247
2746
  const canSend = message.trim().length > 0 && !disabled;
2248
2747
  const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
2249
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2748
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2250
2749
  "div",
2251
2750
  {
2252
2751
  className: "px-4 py-3",
@@ -2254,7 +2753,7 @@ ${uploadedFile.markdown}
2254
2753
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
2255
2754
  },
2256
2755
  children: [
2257
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2756
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2258
2757
  "div",
2259
2758
  {
2260
2759
  className: "relative flex items-center rounded-full transition-all duration-200",
@@ -2266,8 +2765,8 @@ ${uploadedFile.markdown}
2266
2765
  backdropFilter: "blur(32px)"
2267
2766
  },
2268
2767
  children: [
2269
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2270
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2768
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
2769
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2271
2770
  "textarea",
2272
2771
  {
2273
2772
  ref: textAreaRef,
@@ -2296,8 +2795,8 @@ ${uploadedFile.markdown}
2296
2795
  disabled
2297
2796
  }
2298
2797
  ),
2299
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
2300
- enableEmoji && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
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)(
2301
2800
  "button",
2302
2801
  {
2303
2802
  ref: emojiButtonRef,
@@ -2327,7 +2826,7 @@ ${uploadedFile.markdown}
2327
2826
  },
2328
2827
  disabled,
2329
2828
  "aria-label": "Add emoji",
2330
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2829
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2331
2830
  "svg",
2332
2831
  {
2333
2832
  width: "18",
@@ -2340,18 +2839,18 @@ ${uploadedFile.markdown}
2340
2839
  strokeLinejoin: "round",
2341
2840
  "aria-hidden": "true",
2342
2841
  children: [
2343
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Emoji" }),
2344
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2345
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
2346
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
2347
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
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" })
2348
2847
  ]
2349
2848
  }
2350
2849
  )
2351
2850
  }
2352
2851
  ),
2353
- enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2354
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2852
+ enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2853
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2355
2854
  "button",
2356
2855
  {
2357
2856
  type: "button",
@@ -2371,7 +2870,7 @@ ${uploadedFile.markdown}
2371
2870
  },
2372
2871
  disabled: disabled || fileUpload.isUploading,
2373
2872
  "aria-label": "Attach file",
2374
- children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2873
+ children: fileUpload.isUploading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2375
2874
  "svg",
2376
2875
  {
2377
2876
  width: "18",
@@ -2383,11 +2882,11 @@ ${uploadedFile.markdown}
2383
2882
  className: "animate-spin",
2384
2883
  "aria-hidden": "true",
2385
2884
  children: [
2386
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Uploading" }),
2387
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
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" })
2388
2887
  ]
2389
2888
  }
2390
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2889
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2391
2890
  "svg",
2392
2891
  {
2393
2892
  width: "18",
@@ -2400,14 +2899,14 @@ ${uploadedFile.markdown}
2400
2899
  strokeLinejoin: "round",
2401
2900
  "aria-hidden": "true",
2402
2901
  children: [
2403
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Attach file" }),
2404
- /* @__PURE__ */ (0, import_jsx_runtime8.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" })
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" })
2405
2904
  ]
2406
2905
  }
2407
2906
  )
2408
2907
  }
2409
2908
  ),
2410
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2411
2910
  "input",
2412
2911
  {
2413
2912
  ref: fileInputRef,
@@ -2418,7 +2917,7 @@ ${uploadedFile.markdown}
2418
2917
  }
2419
2918
  )
2420
2919
  ] }),
2421
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2920
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2422
2921
  "button",
2423
2922
  {
2424
2923
  type: "button",
@@ -2433,7 +2932,7 @@ ${uploadedFile.markdown}
2433
2932
  boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
2434
2933
  },
2435
2934
  "aria-label": "Send message",
2436
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2935
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2437
2936
  "svg",
2438
2937
  {
2439
2938
  width: "18",
@@ -2446,9 +2945,9 @@ ${uploadedFile.markdown}
2446
2945
  strokeLinejoin: "round",
2447
2946
  "aria-hidden": "true",
2448
2947
  children: [
2449
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("title", { children: "Send" }),
2450
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
2451
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
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" })
2452
2951
  ]
2453
2952
  }
2454
2953
  )
@@ -2458,8 +2957,8 @@ ${uploadedFile.markdown}
2458
2957
  ]
2459
2958
  }
2460
2959
  ),
2461
- fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-2 px-1", children: [
2462
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2960
+ fileUpload.isUploading && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-2 px-1", children: [
2961
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2463
2962
  "div",
2464
2963
  {
2465
2964
  className: "w-full rounded-full overflow-hidden",
@@ -2467,7 +2966,7 @@ ${uploadedFile.markdown}
2467
2966
  height: 3,
2468
2967
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
2469
2968
  },
2470
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2969
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2471
2970
  "div",
2472
2971
  {
2473
2972
  className: "h-full rounded-full transition-all duration-300",
@@ -2479,7 +2978,7 @@ ${uploadedFile.markdown}
2479
2978
  )
2480
2979
  }
2481
2980
  ),
2482
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2981
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2483
2982
  "p",
2484
2983
  {
2485
2984
  className: "mt-1",
@@ -2497,7 +2996,7 @@ ${uploadedFile.markdown}
2497
2996
  )
2498
2997
  ] }),
2499
2998
  showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
2500
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2999
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2501
3000
  "div",
2502
3001
  {
2503
3002
  ref: emojiPickerRef,
@@ -2512,8 +3011,8 @@ ${uploadedFile.markdown}
2512
3011
  "0 16px 48px -8px rgba(0,0,0,0.5)"
2513
3012
  ].join(", ")
2514
3013
  },
2515
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2516
- import_react9.default,
3014
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3015
+ import_react10.default,
2517
3016
  {
2518
3017
  data: emojiData,
2519
3018
  onEmojiSelect: (emoji) => {
@@ -2538,7 +3037,7 @@ ${uploadedFile.markdown}
2538
3037
  }
2539
3038
 
2540
3039
  // src/components/ChatWidget.tsx
2541
- var import_jsx_runtime9 = require("react/jsx-runtime");
3040
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2542
3041
  function getAnonymousUser() {
2543
3042
  let anonId;
2544
3043
  try {
@@ -2584,14 +3083,20 @@ function ChatWidget({
2584
3083
  maxHeight: 900,
2585
3084
  enabled: !isFullPage
2586
3085
  });
2587
- const handleSendMessage = (0, import_react10.useCallback)(
3086
+ const lastSentRef = (0, import_react11.useRef)(null);
3087
+ const handleSendMessage = (0, import_react11.useCallback)(
2588
3088
  (content) => {
2589
3089
  if (!websocket.isConnected) {
2590
3090
  config.toast?.error("Not connected to chat server");
2591
3091
  return;
2592
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 };
2593
3098
  const optimisticMessage = {
2594
- id: `temp-${Date.now()}`,
3099
+ id: `temp-${now}`,
2595
3100
  conversationId: config.conversationId || "",
2596
3101
  senderId: effectiveUser.email,
2597
3102
  senderType: effectiveUser.type,
@@ -2610,7 +3115,18 @@ function ChatWidget({
2610
3115
  },
2611
3116
  [websocket, config, addMessage, effectiveUser]
2612
3117
  );
2613
- const handleTyping = (0, import_react10.useCallback)(
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)(
2614
3130
  (isTyping2) => {
2615
3131
  if (!websocket.isConnected || config.enableTypingIndicator === false) return;
2616
3132
  websocket.sendMessage("typing", {
@@ -2620,7 +3136,7 @@ function ChatWidget({
2620
3136
  },
2621
3137
  [websocket, config]
2622
3138
  );
2623
- (0, import_react10.useEffect)(() => {
3139
+ (0, import_react11.useEffect)(() => {
2624
3140
  if (websocket.error) {
2625
3141
  config.toast?.error(websocket.error.message || "An error occurred");
2626
3142
  }
@@ -2640,7 +3156,6 @@ function ChatWidget({
2640
3156
  })();
2641
3157
  const positionClass = resolvedPosition === "left" ? "left-4" : "right-4";
2642
3158
  const containerStyle = isFullPage ? { backgroundColor: bgColor, color: textColor } : {
2643
- position: "relative",
2644
3159
  width,
2645
3160
  height,
2646
3161
  maxHeight: "calc(100vh - 100px)",
@@ -2672,14 +3187,14 @@ function ChatWidget({
2672
3187
  outlineOffset: -1,
2673
3188
  transition: "outline 150ms ease"
2674
3189
  };
2675
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3190
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2676
3191
  "div",
2677
3192
  {
2678
3193
  className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
2679
3194
  style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
2680
3195
  ...!isFullPage ? containerResizeProps : {},
2681
3196
  children: [
2682
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3197
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2683
3198
  ChatHeader,
2684
3199
  {
2685
3200
  agent: effectiveUser.type === "customer" ? {
@@ -2693,7 +3208,7 @@ function ChatWidget({
2693
3208
  theme: config.theme
2694
3209
  }
2695
3210
  ),
2696
- !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3211
+ !websocket.isConnected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2697
3212
  "div",
2698
3213
  {
2699
3214
  className: "px-4 py-2",
@@ -2701,15 +3216,15 @@ function ChatWidget({
2701
3216
  backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
2702
3217
  borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
2703
3218
  },
2704
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2705
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3219
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3220
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2706
3221
  "div",
2707
3222
  {
2708
3223
  className: "w-1.5 h-1.5 rounded-full animate-pulse",
2709
3224
  style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
2710
3225
  }
2711
3226
  ),
2712
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3227
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2713
3228
  "span",
2714
3229
  {
2715
3230
  style: {
@@ -2720,7 +3235,7 @@ function ChatWidget({
2720
3235
  children: "Reconnecting..."
2721
3236
  }
2722
3237
  ),
2723
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3238
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2724
3239
  "button",
2725
3240
  {
2726
3241
  type: "button",
@@ -2744,8 +3259,8 @@ function ChatWidget({
2744
3259
  ] })
2745
3260
  }
2746
3261
  ),
2747
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "text-center", children: [
2748
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
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)(
2749
3264
  "div",
2750
3265
  {
2751
3266
  className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
@@ -2755,7 +3270,7 @@ function ChatWidget({
2755
3270
  }
2756
3271
  }
2757
3272
  ),
2758
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3273
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2759
3274
  "p",
2760
3275
  {
2761
3276
  style: {
@@ -2766,7 +3281,7 @@ function ChatWidget({
2766
3281
  children: "Loading messages..."
2767
3282
  }
2768
3283
  )
2769
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3284
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2770
3285
  MessageList,
2771
3286
  {
2772
3287
  messages,
@@ -2779,10 +3294,11 @@ function ChatWidget({
2779
3294
  isLoadingMore,
2780
3295
  theme: config.theme,
2781
3296
  onQuickAction: handleSendMessage,
2782
- isBotThinking
3297
+ isBotThinking,
3298
+ onBookingSlotSelected: handleBookingSlotSelected
2783
3299
  }
2784
3300
  ),
2785
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3301
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2786
3302
  ChatInput,
2787
3303
  {
2788
3304
  onSend: handleSendMessage,
@@ -2792,7 +3308,7 @@ function ChatWidget({
2792
3308
  disabled: !websocket.isConnected
2793
3309
  }
2794
3310
  ),
2795
- !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3311
+ !isFullPage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2796
3312
  "div",
2797
3313
  {
2798
3314
  className: "px-4 py-1.5 text-center",
@@ -2800,7 +3316,7 @@ function ChatWidget({
2800
3316
  borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
2801
3317
  backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
2802
3318
  },
2803
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3319
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2804
3320
  "p",
2805
3321
  {
2806
3322
  style: {
@@ -2811,7 +3327,7 @@ function ChatWidget({
2811
3327
  children: [
2812
3328
  "Powered by",
2813
3329
  " ",
2814
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: {
3330
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: {
2815
3331
  fontWeight: 600,
2816
3332
  color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
2817
3333
  }, children: "Xcelsior" })
@@ -2826,11 +3342,11 @@ function ChatWidget({
2826
3342
  }
2827
3343
 
2828
3344
  // src/components/Chat.tsx
2829
- var import_react14 = require("react");
3345
+ var import_react15 = require("react");
2830
3346
 
2831
3347
  // src/components/PreChatForm.tsx
2832
- var import_react11 = require("react");
2833
- var import_jsx_runtime10 = require("react/jsx-runtime");
3348
+ var import_react12 = require("react");
3349
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2834
3350
  function PreChatForm({
2835
3351
  onSubmit,
2836
3352
  className = "",
@@ -2839,11 +3355,11 @@ function PreChatForm({
2839
3355
  onMinimize,
2840
3356
  onClose
2841
3357
  }) {
2842
- const [name, setName] = (0, import_react11.useState)(initialName);
2843
- const [email, setEmail] = (0, import_react11.useState)(initialEmail);
2844
- const [errors, setErrors] = (0, import_react11.useState)({});
2845
- const [isSubmitting, setIsSubmitting] = (0, import_react11.useState)(false);
2846
- const [focusedField, setFocusedField] = (0, import_react11.useState)(null);
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);
2847
3363
  const validateForm = () => {
2848
3364
  const newErrors = {};
2849
3365
  if (!name.trim()) {
@@ -2891,7 +3407,7 @@ function PreChatForm({
2891
3407
  transition: "box-shadow 0.2s ease",
2892
3408
  caretColor: "#337eff"
2893
3409
  });
2894
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3410
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2895
3411
  "div",
2896
3412
  {
2897
3413
  className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
@@ -2911,7 +3427,7 @@ function PreChatForm({
2911
3427
  backgroundSize: "24px 24px"
2912
3428
  },
2913
3429
  children: [
2914
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3430
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2915
3431
  "div",
2916
3432
  {
2917
3433
  className: "relative text-white overflow-hidden",
@@ -2919,7 +3435,7 @@ function PreChatForm({
2919
3435
  background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
2920
3436
  },
2921
3437
  children: [
2922
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3438
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2923
3439
  "div",
2924
3440
  {
2925
3441
  className: "absolute inset-0 pointer-events-none",
@@ -2928,9 +3444,9 @@ function PreChatForm({
2928
3444
  }
2929
3445
  }
2930
3446
  ),
2931
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-start justify-between", children: [
2932
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1", children: [
2933
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
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)(
2934
3450
  "h2",
2935
3451
  {
2936
3452
  className: "font-semibold",
@@ -2941,7 +3457,7 @@ function PreChatForm({
2941
3457
  children: "Start a Conversation"
2942
3458
  }
2943
3459
  ),
2944
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3460
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2945
3461
  "p",
2946
3462
  {
2947
3463
  className: "mt-1",
@@ -2954,8 +3470,8 @@ function PreChatForm({
2954
3470
  }
2955
3471
  )
2956
3472
  ] }),
2957
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1 ml-2", children: [
2958
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3473
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 ml-2", children: [
3474
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2959
3475
  "button",
2960
3476
  {
2961
3477
  type: "button",
@@ -2969,7 +3485,7 @@ function PreChatForm({
2969
3485
  e.currentTarget.style.backgroundColor = "transparent";
2970
3486
  },
2971
3487
  "aria-label": "Minimize chat",
2972
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3488
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2973
3489
  "svg",
2974
3490
  {
2975
3491
  width: "18",
@@ -2978,8 +3494,8 @@ function PreChatForm({
2978
3494
  stroke: "currentColor",
2979
3495
  viewBox: "0 0 24 24",
2980
3496
  children: [
2981
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Minimize" }),
2982
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3497
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Minimize" }),
3498
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2983
3499
  "path",
2984
3500
  {
2985
3501
  strokeLinecap: "round",
@@ -2993,7 +3509,7 @@ function PreChatForm({
2993
3509
  )
2994
3510
  }
2995
3511
  ),
2996
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3512
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2997
3513
  "button",
2998
3514
  {
2999
3515
  type: "button",
@@ -3007,7 +3523,7 @@ function PreChatForm({
3007
3523
  e.currentTarget.style.backgroundColor = "transparent";
3008
3524
  },
3009
3525
  "aria-label": "Close chat",
3010
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3526
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3011
3527
  "svg",
3012
3528
  {
3013
3529
  width: "18",
@@ -3016,8 +3532,8 @@ function PreChatForm({
3016
3532
  stroke: "currentColor",
3017
3533
  viewBox: "0 0 24 24",
3018
3534
  children: [
3019
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Close" }),
3020
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3535
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("title", { children: "Close" }),
3536
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3021
3537
  "path",
3022
3538
  {
3023
3539
  strokeLinecap: "round",
@@ -3033,7 +3549,7 @@ function PreChatForm({
3033
3549
  )
3034
3550
  ] })
3035
3551
  ] }) }),
3036
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3552
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3037
3553
  "div",
3038
3554
  {
3039
3555
  className: "h-px",
@@ -3045,9 +3561,9 @@ function PreChatForm({
3045
3561
  ]
3046
3562
  }
3047
3563
  ),
3048
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
3049
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3050
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
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)(
3051
3567
  "label",
3052
3568
  {
3053
3569
  htmlFor: "chat-name",
@@ -3059,11 +3575,11 @@ function PreChatForm({
3059
3575
  },
3060
3576
  children: [
3061
3577
  "Name ",
3062
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3578
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3063
3579
  ]
3064
3580
  }
3065
3581
  ),
3066
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3582
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3067
3583
  "input",
3068
3584
  {
3069
3585
  type: "text",
@@ -3083,7 +3599,7 @@ function PreChatForm({
3083
3599
  autoComplete: "name"
3084
3600
  }
3085
3601
  ),
3086
- errors.name && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3602
+ errors.name && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3087
3603
  "p",
3088
3604
  {
3089
3605
  className: "mt-1.5",
@@ -3097,8 +3613,8 @@ function PreChatForm({
3097
3613
  }
3098
3614
  )
3099
3615
  ] }),
3100
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3101
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3616
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
3617
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3102
3618
  "label",
3103
3619
  {
3104
3620
  htmlFor: "chat-email",
@@ -3110,11 +3626,11 @@ function PreChatForm({
3110
3626
  },
3111
3627
  children: [
3112
3628
  "Email ",
3113
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3629
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { color: "#ff6363" }, children: "*" })
3114
3630
  ]
3115
3631
  }
3116
3632
  ),
3117
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3633
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3118
3634
  "input",
3119
3635
  {
3120
3636
  type: "email",
@@ -3134,7 +3650,7 @@ function PreChatForm({
3134
3650
  autoComplete: "email"
3135
3651
  }
3136
3652
  ),
3137
- errors.email && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3653
+ errors.email && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3138
3654
  "p",
3139
3655
  {
3140
3656
  className: "mt-1.5",
@@ -3148,7 +3664,7 @@ function PreChatForm({
3148
3664
  }
3149
3665
  )
3150
3666
  ] }),
3151
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3667
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3152
3668
  "button",
3153
3669
  {
3154
3670
  type: "submit",
@@ -3170,8 +3686,8 @@ function PreChatForm({
3170
3686
  onMouseLeave: (e) => {
3171
3687
  e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
3172
3688
  },
3173
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [
3174
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
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)(
3175
3691
  "svg",
3176
3692
  {
3177
3693
  className: "animate-spin",
@@ -3183,8 +3699,8 @@ function PreChatForm({
3183
3699
  strokeWidth: "2",
3184
3700
  "aria-label": "Loading",
3185
3701
  children: [
3186
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: "Loading" }),
3187
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
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" })
3188
3704
  ]
3189
3705
  }
3190
3706
  ),
@@ -3192,7 +3708,7 @@ function PreChatForm({
3192
3708
  ] }) : "Start Chat"
3193
3709
  }
3194
3710
  ),
3195
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3711
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3196
3712
  "p",
3197
3713
  {
3198
3714
  className: "text-center",
@@ -3205,7 +3721,7 @@ function PreChatForm({
3205
3721
  }
3206
3722
  )
3207
3723
  ] }),
3208
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3724
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3209
3725
  "div",
3210
3726
  {
3211
3727
  className: "px-4 py-1.5 text-center",
@@ -3213,7 +3729,7 @@ function PreChatForm({
3213
3729
  borderTop: "1px solid rgba(255,255,255,0.06)",
3214
3730
  backgroundColor: "rgba(0,0,0,0.2)"
3215
3731
  },
3216
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3732
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3217
3733
  "p",
3218
3734
  {
3219
3735
  style: {
@@ -3224,7 +3740,7 @@ function PreChatForm({
3224
3740
  children: [
3225
3741
  "Powered by",
3226
3742
  " ",
3227
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3743
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
3228
3744
  ]
3229
3745
  }
3230
3746
  )
@@ -3236,8 +3752,15 @@ function PreChatForm({
3236
3752
  }
3237
3753
 
3238
3754
  // src/hooks/useDraggablePosition.ts
3239
- var import_react12 = require("react");
3755
+ var import_react13 = require("react");
3240
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)";
3241
3764
  function getStoredPosition() {
3242
3765
  try {
3243
3766
  const stored = localStorage.getItem(STORAGE_KEY2);
@@ -3246,21 +3769,33 @@ function getStoredPosition() {
3246
3769
  }
3247
3770
  return "right";
3248
3771
  }
3249
- function storePosition(position) {
3772
+ function storePosition(pos) {
3250
3773
  try {
3251
- localStorage.setItem(STORAGE_KEY2, position);
3774
+ localStorage.setItem(STORAGE_KEY2, pos);
3252
3775
  } catch {
3253
3776
  }
3254
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
+ }
3255
3784
  function useDraggablePosition(configPosition = "auto") {
3256
3785
  const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
3257
- const [position, setPosition] = (0, import_react12.useState)(resolvedDefault);
3258
- const [isDragging, setIsDragging] = (0, import_react12.useState)(false);
3259
- const dragStartX = (0, import_react12.useRef)(0);
3260
- const fabRef = (0, import_react12.useRef)(null);
3261
- const hasShownHint = (0, import_react12.useRef)(false);
3262
- const [showHint, setShowHint] = (0, import_react12.useState)(false);
3263
- (0, import_react12.useEffect)(() => {
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)(() => {
3264
3799
  try {
3265
3800
  const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
3266
3801
  if (!hasVisited && !hasShownHint.current) {
@@ -3275,53 +3810,115 @@ function useDraggablePosition(configPosition = "auto") {
3275
3810
  } catch {
3276
3811
  }
3277
3812
  }, []);
3278
- const handlePointerDown = (0, import_react12.useCallback)((e) => {
3279
- dragStartX.current = e.clientX;
3280
- setIsDragging(true);
3281
- e.target.setPointerCapture(e.pointerId);
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
+ };
3282
3885
  }, []);
3283
- const handlePointerMove = (0, import_react12.useCallback)((_e) => {
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);
3284
3897
  }, []);
3285
- const handlePointerUp = (0, import_react12.useCallback)(
3286
- (e) => {
3287
- setIsDragging(false);
3288
- e.target.releasePointerCapture(e.pointerId);
3289
- const deltaX = e.clientX - dragStartX.current;
3290
- if (Math.abs(deltaX) > 50) {
3291
- const screenMid = window.innerWidth / 2;
3292
- const newPosition = e.clientX < screenMid ? "left" : "right";
3293
- if (newPosition !== position) {
3294
- setPosition(newPosition);
3295
- storePosition(newPosition);
3296
- }
3297
- }
3298
- },
3299
- [position]
3300
- );
3898
+ const shouldSuppressClick = (0, import_react13.useCallback)(() => didDragRef.current, []);
3301
3899
  return {
3302
3900
  position,
3303
3901
  isDragging,
3304
3902
  showHint,
3305
- fabRef,
3903
+ containerRef,
3904
+ shouldSuppressClick,
3306
3905
  handlers: {
3307
- onPointerDown: handlePointerDown,
3308
- onPointerMove: handlePointerMove,
3309
- onPointerUp: handlePointerUp
3906
+ onPointerDown: handlePointerDown
3310
3907
  }
3311
3908
  };
3312
3909
  }
3313
3910
 
3314
3911
  // src/hooks/useChatWidgetState.ts
3315
- var import_react13 = require("react");
3912
+ var import_react14 = require("react");
3316
3913
  function useChatWidgetState({
3317
3914
  state: controlledState,
3318
3915
  defaultState = "minimized",
3319
3916
  onStateChange
3320
3917
  }) {
3321
- const [uncontrolledState, setUncontrolledState] = (0, import_react13.useState)(defaultState);
3918
+ const [uncontrolledState, setUncontrolledState] = (0, import_react14.useState)(defaultState);
3322
3919
  const isControlled = controlledState !== void 0 && controlledState !== "undefined";
3323
3920
  const currentState = isControlled ? controlledState : uncontrolledState;
3324
- const setState = (0, import_react13.useCallback)(
3921
+ const setState = (0, import_react14.useCallback)(
3325
3922
  (newValue) => {
3326
3923
  if (!isControlled) {
3327
3924
  setUncontrolledState(newValue);
@@ -3338,7 +3935,7 @@ function useChatWidgetState({
3338
3935
  }
3339
3936
 
3340
3937
  // src/components/Chat.tsx
3341
- var import_jsx_runtime11 = require("react/jsx-runtime");
3938
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3342
3939
  function generateSessionId() {
3343
3940
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
3344
3941
  return crypto.randomUUID();
@@ -3354,62 +3951,39 @@ function Chat({
3354
3951
  defaultState = "minimized",
3355
3952
  onStateChange
3356
3953
  }) {
3357
- const [userInfo, setUserInfo] = (0, import_react14.useState)(null);
3358
- const [conversationId, setConversationId] = (0, import_react14.useState)("");
3359
- const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
3360
- const [isAnimating, setIsAnimating] = (0, import_react14.useState)(false);
3361
- const [showWidget, setShowWidget] = (0, import_react14.useState)(false);
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);
3362
3957
  const identityMode = config.identityCollection || "progressive";
3363
- const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
3364
- const { currentState, setState: setStateRaw } = useChatWidgetState({
3958
+ const { position, isDragging, showHint, containerRef, shouldSuppressClick, handlers } = useDraggablePosition(config.position);
3959
+ const sessionInitializedRef = (0, import_react15.useRef)(false);
3960
+ const { currentState, setState } = useChatWidgetState({
3365
3961
  state,
3366
3962
  defaultState,
3367
3963
  onStateChange
3368
3964
  });
3369
- const setState = (0, import_react14.useCallback)(
3370
- (newState) => {
3371
- if (newState === "open" && currentState === "minimized") {
3372
- setShowWidget(true);
3373
- setIsAnimating(true);
3374
- setStateRaw(newState);
3375
- requestAnimationFrame(() => {
3376
- requestAnimationFrame(() => {
3377
- setIsAnimating(false);
3378
- });
3379
- });
3380
- } else if ((newState === "minimized" || newState === "closed") && currentState === "open") {
3381
- setIsAnimating(true);
3382
- setTimeout(() => {
3383
- setShowWidget(false);
3384
- setIsAnimating(false);
3385
- setStateRaw(newState);
3386
- }, 200);
3387
- } else {
3388
- setStateRaw(newState);
3389
- }
3390
- },
3391
- [currentState, setStateRaw]
3392
- );
3393
- (0, import_react14.useEffect)(() => {
3394
- if (currentState === "open") {
3395
- setShowWidget(true);
3396
- }
3397
- }, [currentState]);
3398
- (0, import_react14.useEffect)(() => {
3965
+ const configConversationId = config.conversationId;
3966
+ const configUserEmail = config.currentUser?.email;
3967
+ const configUserName = config.currentUser?.name;
3968
+ const configUserAvatar = config.currentUser?.avatar;
3969
+ const configUserStatus = config.currentUser?.status;
3970
+ (0, import_react15.useEffect)(() => {
3971
+ if (sessionInitializedRef.current) return;
3399
3972
  const initializeSession = () => {
3400
3973
  try {
3401
- if (config.currentUser?.email && config.currentUser?.name) {
3402
- const convId2 = config.conversationId || generateSessionId();
3974
+ if (configUserEmail && configUserName) {
3975
+ const convId2 = configConversationId || generateSessionId();
3403
3976
  const user = {
3404
- name: config.currentUser.name,
3405
- email: config.currentUser.email,
3406
- avatar: config.currentUser.avatar,
3977
+ name: configUserName,
3978
+ email: configUserEmail,
3979
+ avatar: configUserAvatar,
3407
3980
  type: "customer",
3408
- status: config.currentUser.status
3981
+ status: configUserStatus
3409
3982
  };
3410
3983
  setUserInfo(user);
3411
3984
  setConversationId(convId2);
3412
3985
  setIsLoading(false);
3986
+ sessionInitializedRef.current = true;
3413
3987
  return;
3414
3988
  }
3415
3989
  const storedDataJson = localStorage.getItem(`${storageKeyPrefix}_user`);
@@ -3426,24 +4000,53 @@ function Chat({
3426
4000
  setUserInfo(user);
3427
4001
  setConversationId(storedData.conversationId);
3428
4002
  setIsLoading(false);
4003
+ sessionInitializedRef.current = true;
3429
4004
  return;
3430
4005
  }
3431
4006
  }
3432
- const convId = config.conversationId || generateSessionId();
4007
+ const convId = configConversationId || generateSessionId();
3433
4008
  setConversationId(convId);
3434
4009
  if (identityMode === "progressive" || identityMode === "none") {
3435
- setUserInfo(null);
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
+ }
3436
4037
  }
4038
+ sessionInitializedRef.current = true;
3437
4039
  } catch (error) {
3438
4040
  console.error("Error initializing chat session:", error);
3439
- setConversationId(config.conversationId || generateSessionId());
4041
+ setConversationId(configConversationId || generateSessionId());
4042
+ sessionInitializedRef.current = true;
3440
4043
  } finally {
3441
4044
  setIsLoading(false);
3442
4045
  }
3443
4046
  };
3444
4047
  initializeSession();
3445
- }, [config, storageKeyPrefix, identityMode]);
3446
- const handlePreChatSubmit = (0, import_react14.useCallback)(
4048
+ }, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
4049
+ const handlePreChatSubmit = (0, import_react15.useCallback)(
3447
4050
  (name, email) => {
3448
4051
  const convId = conversationId || generateSessionId();
3449
4052
  const user = { name, email, type: "customer", status: "online" };
@@ -3469,49 +4072,52 @@ function Chat({
3469
4072
  const primaryColor = config.theme?.primary || "#337eff";
3470
4073
  const primaryStrong = config.theme?.primaryStrong || "#005eff";
3471
4074
  if (currentState === "minimized") {
3472
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: `fixed bottom-5 ${positionClass} z-50 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3473
- "button",
4075
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4076
+ "div",
3474
4077
  {
3475
- type: "button",
3476
- onClick: () => setState("open"),
3477
- className: `group relative rounded-full text-white transition-all duration-300 flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
3478
- onMouseEnter: (e) => {
3479
- e.currentTarget.style.transform = "scale(1.08)";
3480
- },
3481
- onMouseLeave: (e) => {
3482
- e.currentTarget.style.transform = isDragging ? "scale(1.1)" : "scale(1)";
3483
- },
3484
- style: {
3485
- width: 64,
3486
- height: 64,
3487
- background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
3488
- boxShadow: [
3489
- `0 4px 24px 0 ${primaryColor}80`,
3490
- `0 8px 32px -4px rgba(0,0,0,0.3)`,
3491
- `inset 0 1px 0 0 rgba(255,255,255,0.2)`
3492
- ].join(", ")
3493
- },
3494
- "aria-label": "Open chat",
3495
- ...handlers,
3496
- children: [
3497
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubbleIcon, { size: 28, color: "white", className: "pointer-events-none" }),
3498
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3499
- "span",
3500
- {
3501
- className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
3502
- style: {
3503
- backgroundColor: primaryColor,
3504
- opacity: 0.15,
3505
- animationDuration: "2.5s"
3506
- }
3507
- }
3508
- )
3509
- ]
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
+ )
3510
4116
  }
3511
- ) });
4117
+ );
3512
4118
  }
3513
4119
  if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
3514
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4120
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3515
4121
  PreChatForm,
3516
4122
  {
3517
4123
  onSubmit: handlePreChatSubmit,
@@ -3528,16 +4134,7 @@ function Chat({
3528
4134
  conversationId,
3529
4135
  currentUser: userInfo || void 0
3530
4136
  };
3531
- const widgetAnimationStyle = showWidget && !isAnimating ? {
3532
- opacity: 1,
3533
- transform: "translateY(0) scale(1)",
3534
- transition: "opacity 0.25s ease-out, transform 0.25s ease-out"
3535
- } : {
3536
- opacity: 0,
3537
- transform: "translateY(12px) scale(0.97)",
3538
- transition: "opacity 0.2s ease-in, transform 0.2s ease-in"
3539
- };
3540
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: widgetAnimationStyle, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4137
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3541
4138
  ChatWidget,
3542
4139
  {
3543
4140
  config: fullConfig,
@@ -3546,16 +4143,16 @@ function Chat({
3546
4143
  onMinimize: () => setState("minimized"),
3547
4144
  resolvedPosition: position
3548
4145
  }
3549
- ) });
4146
+ );
3550
4147
  }
3551
4148
 
3552
4149
  // src/components/TypingIndicator.tsx
3553
- var import_jsx_runtime12 = require("react/jsx-runtime");
4150
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3554
4151
  function TypingIndicator({ isTyping, userName }) {
3555
4152
  if (!isTyping) {
3556
4153
  return null;
3557
4154
  }
3558
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4155
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3559
4156
  "div",
3560
4157
  {
3561
4158
  className: "px-4 py-2",
@@ -3563,8 +4160,8 @@ function TypingIndicator({ isTyping, userName }) {
3563
4160
  borderTop: "1px solid rgba(255,255,255,0.06)",
3564
4161
  backgroundColor: "rgba(0,0,0,0.15)"
3565
4162
  },
3566
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3567
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
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)(
3568
4165
  "span",
3569
4166
  {
3570
4167
  className: "rounded-full animate-bounce",
@@ -3578,7 +4175,7 @@ function TypingIndicator({ isTyping, userName }) {
3578
4175
  },
3579
4176
  i
3580
4177
  )) }),
3581
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4178
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3582
4179
  "span",
3583
4180
  {
3584
4181
  style: {
@@ -3595,6 +4192,9 @@ function TypingIndicator({ isTyping, userName }) {
3595
4192
  }
3596
4193
  // Annotate the CommonJS export names for ESM import in node:
3597
4194
  0 && (module.exports = {
4195
+ BookingCancelledCard,
4196
+ BookingConfirmationCard,
4197
+ BookingSlotPicker,
3598
4198
  Chat,
3599
4199
  ChatHeader,
3600
4200
  ChatInput,