@getwidgets/live-chat-widget 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19540,6 +19540,21 @@
19540
19540
  if (!res.ok) throw new Error("Failed to fetch widget config");
19541
19541
  return res.json();
19542
19542
  };
19543
+ const checkSessionStatus = async (widgetId, sessionId) => {
19544
+ try {
19545
+ const url = `https://api.getwidgets.app/api/widgets/livechat/${widgetId}/status/?session_id=${sessionId}`;
19546
+ const res = await fetch(url);
19547
+ if (!res.ok) {
19548
+ const txt = await res.text().catch(() => null);
19549
+ throw new Error(txt || "Failed to check session status");
19550
+ }
19551
+ const data = await res.json();
19552
+ return data;
19553
+ } catch (e) {
19554
+ console.warn("checkSessionStatus failed", e);
19555
+ return { error: "Failed to check session status" };
19556
+ }
19557
+ };
19543
19558
  const getSessionId = (widgetId) => {
19544
19559
  if (typeof window === "undefined") return null;
19545
19560
  const key = `live-chat-widget/session-id`;
@@ -19565,13 +19580,19 @@
19565
19580
  const [inputMessage, setInputMessage] = reactExports.useState("");
19566
19581
  const [isLoading, setIsLoading] = reactExports.useState(false);
19567
19582
  const [isOpen, setIsOpen] = reactExports.useState(false);
19583
+ const [showNotificationPreview, setShowNotificationPreview] = reactExports.useState(true);
19568
19584
  const chatContainerRef = reactExports.useRef(null);
19569
19585
  const inputRef = reactExports.useRef(null);
19570
19586
  const [sessionId, setSessionId] = reactExports.useState(null);
19571
19587
  const [localMessages, setLocalMessages] = reactExports.useState([]);
19572
19588
  const [localLoading, setLocalLoading] = reactExports.useState(false);
19589
+ const [isSessionInitialized, setIsSessionInitialized] = reactExports.useState(false);
19590
+ const [isConnecting, setIsConnecting] = reactExports.useState(false);
19591
+ const [queuedFirstMessage, setQueuedFirstMessage] = reactExports.useState(null);
19573
19592
  const wsRef = reactExports.useRef(null);
19574
19593
  const audioRef = reactExports.useRef(null);
19594
+ const [hasCheckedSession, setHasCheckedSession] = reactExports.useState(false);
19595
+ const [windowWidth, setWindowWidth] = reactExports.useState(typeof window !== "undefined" ? window.innerWidth : 1024);
19575
19596
  const normalizeIncomingMessage = (m2) => {
19576
19597
  if (!m2) return { id: void 0, role: "agent", content: "", timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), created_at: Date.now() };
19577
19598
  if (m2.sent_by) {
@@ -19597,6 +19618,13 @@
19597
19618
  created_at: ts
19598
19619
  };
19599
19620
  };
19621
+ reactExports.useEffect(() => {
19622
+ const handleResize = () => {
19623
+ setWindowWidth(window.innerWidth);
19624
+ };
19625
+ window.addEventListener("resize", handleResize);
19626
+ return () => window.removeEventListener("resize", handleResize);
19627
+ }, []);
19600
19628
  reactExports.useEffect(() => {
19601
19629
  if (chatContainerRef.current) {
19602
19630
  chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
@@ -19606,38 +19634,51 @@
19606
19634
  const id2 = getSessionId();
19607
19635
  setSessionId(id2);
19608
19636
  }, [widgetId]);
19637
+ reactExports.useEffect(() => {
19638
+ const dismissalKey = `live-chat-notification-dismissed/${widgetId}`;
19639
+ const dismissalTime = localStorage.getItem(dismissalKey);
19640
+ if (dismissalTime) {
19641
+ const dismissedAt = parseInt(dismissalTime);
19642
+ const now = Date.now();
19643
+ const timeSinceDismissal = now - dismissedAt;
19644
+ const oneDayInMs = 24 * 60 * 60 * 1e3;
19645
+ if (timeSinceDismissal < oneDayInMs) {
19646
+ setShowNotificationPreview(false);
19647
+ return;
19648
+ } else {
19649
+ localStorage.removeItem(dismissalKey);
19650
+ setShowNotificationPreview(true);
19651
+ }
19652
+ } else {
19653
+ setShowNotificationPreview(true);
19654
+ }
19655
+ }, [widgetId]);
19609
19656
  reactExports.useEffect(() => {
19610
19657
  if (!widgetId) return;
19611
19658
  fetchWidgetConfig(widgetId).then((data) => setConfig(data)).catch((err) => console.error("Failed to load widget config:", err));
19612
19659
  }, [widgetId, apiKey]);
19613
19660
  reactExports.useEffect(() => {
19614
- if (!widgetId) return;
19615
- let mounted = true;
19616
- const init2 = async () => {
19617
- const existing = getSessionId();
19618
- try {
19619
- localStorage.setItem("live-chat-widget/session-id", existing);
19620
- } catch (e) {
19621
- }
19622
- const sid = await startLiveSession(widgetId, existing);
19623
- if (!mounted) return;
19661
+ const checkAndInitializeSession = async () => {
19662
+ var _a2;
19663
+ if (!widgetId || !sessionId || hasCheckedSession) return;
19624
19664
  try {
19625
- localStorage.setItem("live-chat-widget/session-id", sid);
19626
- } catch (e) {
19627
- }
19628
- setSessionId(sid);
19629
- setLocalLoading(true);
19630
- openLiveWebsocket(sid);
19631
- };
19632
- init2();
19633
- return () => {
19634
- mounted = false;
19635
- if (wsRef.current) try {
19636
- wsRef.current.close();
19637
- } catch (e) {
19665
+ const status = await checkSessionStatus(widgetId, sessionId);
19666
+ if (status.success && ((_a2 = status.data) == null ? void 0 : _a2.is_active)) {
19667
+ console.log("Session is active, connecting to websocket...");
19668
+ setIsConnecting(true);
19669
+ openLiveWebsocket(sessionId);
19670
+ setIsSessionInitialized(true);
19671
+ } else {
19672
+ console.log("Session not active, will create new session on first message");
19673
+ }
19674
+ } catch (error) {
19675
+ console.error("Failed to check session status:", error);
19676
+ } finally {
19677
+ setHasCheckedSession(true);
19638
19678
  }
19639
19679
  };
19640
- }, [widgetId]);
19680
+ checkAndInitializeSession();
19681
+ }, [widgetId, sessionId, hasCheckedSession]);
19641
19682
  reactExports.useEffect(() => {
19642
19683
  try {
19643
19684
  audioRef.current = new Audio("https://res.cloudinary.com/dtqjv8s9r/video/upload/v1699024420/new-notification-014-363678_iatlfa.mp3");
@@ -19662,10 +19703,10 @@
19662
19703
  return data.session_id || data.id || currentSessionId;
19663
19704
  } catch (e) {
19664
19705
  console.warn("startLiveSession failed", e);
19665
- return currentSessionId;
19706
+ throw e;
19666
19707
  }
19667
19708
  };
19668
- const openLiveWebsocket = (sid) => {
19709
+ const openLiveWebsocket = (sid, onOpenCallback) => {
19669
19710
  if (!sid) return;
19670
19711
  try {
19671
19712
  if (wsRef.current) {
@@ -19681,6 +19722,10 @@
19681
19722
  ws.onopen = () => {
19682
19723
  console.log("LiveChat WS connected", url);
19683
19724
  setLocalLoading(false);
19725
+ setIsConnecting(false);
19726
+ if (onOpenCallback) {
19727
+ onOpenCallback();
19728
+ }
19684
19729
  };
19685
19730
  ws.onmessage = (evt) => {
19686
19731
  try {
@@ -19737,10 +19782,68 @@
19737
19782
  } catch (e) {
19738
19783
  }
19739
19784
  wsRef.current = null;
19785
+ setIsSessionInitialized(false);
19786
+ setIsConnecting(false);
19787
+ };
19788
+ ws.onerror = (e) => {
19789
+ console.error("LiveChat WS error", e);
19790
+ setIsConnecting(false);
19740
19791
  };
19741
- ws.onerror = (e) => console.error("LiveChat WS error", e);
19742
19792
  } catch (e) {
19743
19793
  console.error("Failed to open LiveChat WS", e);
19794
+ setIsConnecting(false);
19795
+ }
19796
+ };
19797
+ const sendMessageOverWebSocket = (message) => {
19798
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
19799
+ try {
19800
+ wsRef.current.send(JSON.stringify({ message }));
19801
+ return true;
19802
+ } catch (e) {
19803
+ console.error("Failed to send live message", e);
19804
+ return false;
19805
+ }
19806
+ }
19807
+ return false;
19808
+ };
19809
+ const initializeSession = async (firstMessage) => {
19810
+ if (isSessionInitialized) return true;
19811
+ setIsConnecting(true);
19812
+ setLocalLoading(true);
19813
+ try {
19814
+ const newSessionId = v4();
19815
+ const sid = await startLiveSession(widgetId, newSessionId);
19816
+ try {
19817
+ localStorage.setItem("live-chat-widget/session-id", sid);
19818
+ } catch (e) {
19819
+ }
19820
+ setSessionId(sid);
19821
+ setIsSessionInitialized(true);
19822
+ openLiveWebsocket(sid, () => {
19823
+ if (firstMessage) {
19824
+ const sent = sendMessageOverWebSocket(firstMessage);
19825
+ if (!sent) {
19826
+ setLocalMessages((prev) => [...prev, {
19827
+ id: `error-${Date.now()}`,
19828
+ role: "system",
19829
+ content: "Failed to send message. Please try again.",
19830
+ isError: true
19831
+ }]);
19832
+ }
19833
+ }
19834
+ });
19835
+ return true;
19836
+ } catch (error) {
19837
+ console.error("Failed to initialize session:", error);
19838
+ setLocalLoading(false);
19839
+ setIsConnecting(false);
19840
+ setLocalMessages((prev) => [...prev, {
19841
+ id: `error-${Date.now()}`,
19842
+ role: "system",
19843
+ content: "Failed to connect to support. Please try again.",
19844
+ isError: true
19845
+ }]);
19846
+ return false;
19744
19847
  }
19745
19848
  };
19746
19849
  const performSend = async () => {
@@ -19755,19 +19858,28 @@
19755
19858
  pending: true
19756
19859
  };
19757
19860
  setLocalMessages((prev) => [...prev, userMsg]);
19758
- setLocalLoading(false);
19759
19861
  setInputMessage("");
19760
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
19761
- try {
19762
- wsRef.current.send(JSON.stringify({ message: msg }));
19763
- } catch (e) {
19764
- console.error("Failed to send live message", e);
19862
+ if (!isSessionInitialized) {
19863
+ setLocalMessages((prev) => [...prev, {
19864
+ id: `sys-${Date.now()}`,
19865
+ role: "system",
19866
+ content: "Connecting to support..."
19867
+ }]);
19868
+ const initialized = await initializeSession(msg);
19869
+ if (!initialized) {
19870
+ setLocalMessages((prev) => prev.filter((m2) => m2.role !== "system" || !m2.content.includes("Connecting")));
19765
19871
  }
19766
19872
  } else {
19767
- setTimeout(() => {
19768
- setLocalMessages((prev) => [...prev, { id: `sys-${Date.now()}`, role: "system", content: "Message queued (offline)" }]);
19769
- setLocalLoading(false);
19770
- }, 300);
19873
+ const sent = sendMessageOverWebSocket(msg);
19874
+ if (!sent) {
19875
+ setTimeout(() => {
19876
+ setLocalMessages((prev) => [...prev, {
19877
+ id: `sys-${Date.now()}`,
19878
+ role: "system",
19879
+ content: "Reconnecting..."
19880
+ }]);
19881
+ }, 300);
19882
+ }
19771
19883
  }
19772
19884
  };
19773
19885
  const handleKeyPress = (e) => {
@@ -19776,7 +19888,15 @@
19776
19888
  performSend();
19777
19889
  }
19778
19890
  };
19779
- const toggleWidget = () => setIsOpen((v2) => !v2);
19891
+ const toggleWidget = () => {
19892
+ setIsOpen((v2) => !v2);
19893
+ };
19894
+ const handleCloseNotification = (e) => {
19895
+ e.stopPropagation();
19896
+ const dismissalKey = `live-chat-notification-dismissed/${widgetId}`;
19897
+ localStorage.setItem(dismissalKey, Date.now().toString());
19898
+ setShowNotificationPreview(false);
19899
+ };
19780
19900
  if (!config) return null;
19781
19901
  const { header, chat_area, input_area, appearance } = ((_a = config.config) == null ? void 0 : _a.chat_widget) || {};
19782
19902
  const headerConfig = header || {};
@@ -19784,13 +19904,17 @@
19784
19904
  const chatAreaConfig = chat_area || {};
19785
19905
  const inputAreaConfig = input_area || {};
19786
19906
  const getResponsiveDimensions = () => {
19787
- const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
19788
- if (isMobile) {
19907
+ const isMobile2 = windowWidth < 768;
19908
+ if (isMobile2) {
19789
19909
  return {
19790
19910
  width: "95vw",
19791
- height: "60vh",
19911
+ height: "85vh",
19912
+ // Changed from 60vh to 85vh for better mobile experience
19792
19913
  maxWidth: "95vw",
19793
- maxHeight: "80vh"
19914
+ maxHeight: "95vh",
19915
+ bottom: "0",
19916
+ right: "0",
19917
+ borderRadius: "12px 12px 0 0"
19794
19918
  };
19795
19919
  }
19796
19920
  return {
@@ -19802,7 +19926,11 @@
19802
19926
  };
19803
19927
  const getPositionStyles = () => {
19804
19928
  const position2 = appearanceConfig.position || "bottom-right";
19929
+ const isMobile2 = windowWidth < 768;
19805
19930
  const baseStyles = { position: "fixed", zIndex: 9999 };
19931
+ if (isMobile2) {
19932
+ return { ...baseStyles, left: 0, right: 0, bottom: 0 };
19933
+ }
19806
19934
  const desktopSpacing = "20px";
19807
19935
  switch (position2) {
19808
19936
  case "bottom-left":
@@ -19818,6 +19946,7 @@
19818
19946
  const responsiveDimensions = getResponsiveDimensions();
19819
19947
  const combinedMessages = [...messages || [], ...localMessages];
19820
19948
  const getContrastColor = (hex) => {
19949
+ if (!hex) return "#000";
19821
19950
  const c = hex.replace("#", "");
19822
19951
  const r2 = parseInt(c.substr(0, 2), 16);
19823
19952
  const g = parseInt(c.substr(2, 2), 16);
@@ -19825,7 +19954,70 @@
19825
19954
  const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b) / 255;
19826
19955
  return luminance > 0.5 ? "#000000" : "#FFFFFF";
19827
19956
  };
19828
- return /* @__PURE__ */ React.createElement("div", { style: getPositionStyles() }, !isOpen && /* @__PURE__ */ React.createElement(
19957
+ const isMobile = windowWidth < 768;
19958
+ return /* @__PURE__ */ React.createElement("div", { style: getPositionStyles() }, !isOpen && showNotificationPreview && /* @__PURE__ */ React.createElement(
19959
+ "button",
19960
+ {
19961
+ onClick: toggleWidget,
19962
+ className: "rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 flex flex-col gap-2 p-4 max-w-xs responsive-notification",
19963
+ style: {
19964
+ backgroundColor: headerConfig.background_color || "#111827",
19965
+ color: "#FFFFFF",
19966
+ border: "none",
19967
+ cursor: "pointer",
19968
+ textAlign: "left",
19969
+ minWidth: isMobile ? "90vw" : "280px",
19970
+ maxWidth: isMobile ? "90vw" : "400px",
19971
+ margin: isMobile ? "0 auto" : "0"
19972
+ }
19973
+ },
19974
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 flex-1" }, headerConfig.logo_url ? /* @__PURE__ */ React.createElement(
19975
+ "img",
19976
+ {
19977
+ src: headerConfig.logo_url,
19978
+ alt: "Chat",
19979
+ className: "responsive-notification-logo",
19980
+ style: {
19981
+ width: "32px",
19982
+ height: "32px",
19983
+ borderRadius: "50%",
19984
+ flexShrink: 0
19985
+ }
19986
+ }
19987
+ ) : /* @__PURE__ */ React.createElement(
19988
+ "div",
19989
+ {
19990
+ className: "rounded-full flex items-center justify-center responsive-notification-icon",
19991
+ style: {
19992
+ width: "32px",
19993
+ height: "32px",
19994
+ backgroundColor: headerConfig.background_color || "#111827",
19995
+ flexShrink: 0
19996
+ }
19997
+ },
19998
+ /* @__PURE__ */ React.createElement(
19999
+ "svg",
20000
+ {
20001
+ viewBox: "0 0 24 24",
20002
+ fill: "currentColor",
20003
+ style: {
20004
+ width: "16px",
20005
+ height: "16px"
20006
+ }
20007
+ },
20008
+ /* @__PURE__ */ React.createElement("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" })
20009
+ )
20010
+ ), /* @__PURE__ */ React.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React.createElement("h3", { className: "font-semibold text-sm" }, headerConfig.title || "Live Chat"), /* @__PURE__ */ React.createElement("p", { className: "text-xs opacity-70" }, "Just now"))), /* @__PURE__ */ React.createElement(
20011
+ "button",
20012
+ {
20013
+ onClick: handleCloseNotification,
20014
+ className: "text-gray-400 hover:text-white transition-colors flex-shrink-0 responsive-notification-close",
20015
+ style: { fontSize: "20px", padding: "0", border: "none", background: "none", cursor: "pointer" }
20016
+ },
20017
+ "×"
20018
+ )),
20019
+ /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-relaxed text-gray-200", style: { fontSize: isMobile ? "13px" : "14px" } }, (inputAreaConfig == null ? void 0 : inputAreaConfig.first_ai_message) || headerConfig.subtitle || "Connect with visitors")
20020
+ ), !isOpen && !showNotificationPreview && /* @__PURE__ */ React.createElement(
19829
20021
  "button",
19830
20022
  {
19831
20023
  onClick: toggleWidget,
@@ -19833,48 +20025,314 @@
19833
20025
  style: {
19834
20026
  backgroundColor: headerConfig.background_color || "#111827",
19835
20027
  color: headerConfig.font_color || "#FFFFFF",
19836
- width: "56px",
19837
- height: "56px",
20028
+ width: isMobile ? "52px" : "56px",
20029
+ height: isMobile ? "52px" : "56px",
19838
20030
  border: "none",
19839
- cursor: "pointer"
20031
+ cursor: "pointer",
20032
+ margin: isMobile ? "16px" : "0"
19840
20033
  }
19841
20034
  },
19842
- headerConfig.logo_url ? /* @__PURE__ */ React.createElement("img", { src: headerConfig.logo_url, alt: "logo", style: { width: 28, height: 28 } }) : /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", style: { width: 20, height: 20 } }, /* @__PURE__ */ React.createElement("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" }))
20035
+ headerConfig.logo_url ? /* @__PURE__ */ React.createElement(
20036
+ "img",
20037
+ {
20038
+ src: headerConfig.logo_url,
20039
+ alt: "logo",
20040
+ style: {
20041
+ width: isMobile ? 24 : 28,
20042
+ height: isMobile ? 24 : 28
20043
+ }
20044
+ }
20045
+ ) : /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", style: { width: isMobile ? 18 : 20, height: isMobile ? 18 : 20 } }, /* @__PURE__ */ React.createElement("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z" }))
19843
20046
  ), isOpen && /* @__PURE__ */ React.createElement(
19844
20047
  "div",
19845
20048
  {
19846
- className: "rounded-xl border shadow-lg overflow-hidden transition-all duration-300 flex flex-col",
20049
+ className: "rounded-xl shadow-lg overflow-hidden transition-all duration-300 flex flex-col",
19847
20050
  style: {
19848
20051
  width: responsiveDimensions.width,
19849
20052
  height: responsiveDimensions.height,
19850
20053
  maxWidth: responsiveDimensions.maxWidth,
19851
20054
  maxHeight: responsiveDimensions.maxHeight,
19852
20055
  backgroundColor: appearanceConfig.background_color || "#F9FAFB",
19853
- borderRadius: appearanceConfig.border_radius || "16px"
20056
+ borderRadius: isMobile ? responsiveDimensions.borderRadius : appearanceConfig.border_radius || "16px",
20057
+ ...isMobile ? { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 } : {}
19854
20058
  }
19855
20059
  },
19856
20060
  /* @__PURE__ */ React.createElement(
19857
20061
  "div",
19858
20062
  {
19859
- className: "p-3 sm:p-4 text-center flex-shrink-0 relative",
19860
- style: { backgroundColor: headerConfig.background_color || "#111827", color: headerConfig.font_color || "#FFFFFF", minHeight: 60 }
20063
+ className: "text-center flex-shrink-0 relative transition-colors duration-300",
20064
+ style: {
20065
+ backgroundColor: headerConfig.background_color || "#111827",
20066
+ color: headerConfig.font_color || "#FFFFFF",
20067
+ padding: isMobile ? "12px 16px" : "16px 20px",
20068
+ minHeight: "auto"
20069
+ }
20070
+ },
20071
+ /* @__PURE__ */ React.createElement(
20072
+ "button",
20073
+ {
20074
+ onClick: toggleWidget,
20075
+ className: "absolute right-3 top-1/2 transform -translate-y-1/2 hover:opacity-80 transition-opacity",
20076
+ style: {
20077
+ background: "none",
20078
+ border: "none",
20079
+ cursor: "pointer",
20080
+ fontSize: isMobile ? "28px" : "24px",
20081
+ color: getContrastColor(headerConfig.background_color || "#111827"),
20082
+ width: isMobile ? "40px" : "36px",
20083
+ height: isMobile ? "40px" : "36px",
20084
+ display: "flex",
20085
+ alignItems: "center",
20086
+ justifyContent: "center",
20087
+ padding: "0"
20088
+ }
20089
+ },
20090
+ "×"
20091
+ ),
20092
+ /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center justify-center" }, headerConfig.logo_url && /* @__PURE__ */ React.createElement(
20093
+ "img",
20094
+ {
20095
+ src: headerConfig.logo_url,
20096
+ alt: "logo",
20097
+ style: {
20098
+ width: isMobile ? "28px" : "32px",
20099
+ height: isMobile ? "28px" : "32px",
20100
+ marginBottom: isMobile ? "6px" : "8px",
20101
+ borderRadius: "4px"
20102
+ }
20103
+ }
20104
+ ), /* @__PURE__ */ React.createElement("h2", { className: "font-semibold leading-tight", style: { fontSize: isMobile ? "14px" : "16px", margin: "0" } }, headerConfig.title || "Live Chat"), /* @__PURE__ */ React.createElement("p", { className: "opacity-80 leading-tight", style: { fontSize: isMobile ? "11px" : "13px", margin: "2px 0 0 0" } }, headerConfig.subtitle || "Connect with visitors"))
20105
+ ),
20106
+ /* @__PURE__ */ React.createElement(
20107
+ "div",
20108
+ {
20109
+ ref: chatContainerRef,
20110
+ className: "flex-1 relative overflow-y-auto",
20111
+ style: {
20112
+ backgroundColor: appearanceConfig.background_color || "#F9FAFB",
20113
+ paddingBottom: isMobile ? "8px" : "0"
20114
+ }
20115
+ },
20116
+ appearanceConfig.background_image && /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 pointer-events-none", style: {
20117
+ backgroundImage: `url(${appearanceConfig.background_image})`,
20118
+ backgroundSize: appearanceConfig.background_size || "cover",
20119
+ backgroundPosition: appearanceConfig.background_position || "center",
20120
+ backgroundRepeat: appearanceConfig.background_repeat || "no-repeat",
20121
+ opacity: appearanceConfig.background_opacity ?? 1,
20122
+ zIndex: 0
20123
+ } }),
20124
+ /* @__PURE__ */ React.createElement("div", { className: "relative z-10 p-3 sm:p-4 space-y-2 sm:space-y-3", style: {
20125
+ backgroundColor: appearanceConfig.inner_background_color !== void 0 ? appearanceConfig.inner_background_color : "transparent",
20126
+ paddingBottom: isMobile ? "20px" : "16px"
20127
+ } }, combinedMessages.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement(
20128
+ "div",
20129
+ {
20130
+ className: `text-left p-3 inline-block rounded-lg`,
20131
+ style: {
20132
+ backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0",
20133
+ borderRadius: chatAreaConfig.chat_bubble_radius || "16px",
20134
+ color: chatAreaConfig.ai_font_color || "#111827",
20135
+ fontSize: isMobile ? "13px" : "14px",
20136
+ maxWidth: isMobile ? "85%" : "80%",
20137
+ wordBreak: "break-word"
20138
+ }
20139
+ },
20140
+ inputAreaConfig.first_ai_message || "Hello! How can I assist you today?"
20141
+ )) : combinedMessages.map((message) => /* @__PURE__ */ React.createElement("div", { key: message.id, className: `flex ${message.role === "user" ? "justify-end" : "justify-start"}` }, /* @__PURE__ */ React.createElement(
20142
+ "div",
20143
+ {
20144
+ className: `p-3 inline-block rounded-lg`,
20145
+ style: {
20146
+ backgroundColor: message.role === "user" ? chatAreaConfig.user_response_color || "#4ADE80" : message.isError ? "#FEE2E2" : chatAreaConfig.ai_response_color || "#E2E8F0",
20147
+ borderRadius: chatAreaConfig.chat_bubble_radius || "16px",
20148
+ fontSize: isMobile ? "13px" : "14px",
20149
+ color: message.role === "user" ? chatAreaConfig.user_font_color || "#111827" : message.isError ? "#991B1B" : chatAreaConfig.ai_font_color || "#111827",
20150
+ marginBottom: isMobile ? "4px" : "2px",
20151
+ maxWidth: isMobile ? "85%" : "80%",
20152
+ wordBreak: "break-word"
20153
+ }
20154
+ },
20155
+ message.role !== "system" ? /* @__PURE__ */ React.createElement(Markdown, { remarkPlugins: [remarkGfm] }, message.content) : /* @__PURE__ */ React.createElement("em", { className: "text-xs opacity-70" }, message.content),
20156
+ chatAreaConfig.show_timestamps && message.timestamp && /* @__PURE__ */ React.createElement("div", { className: "text-xs opacity-70 mt-1 text-right" }, message.timestamp)
20157
+ ))), localLoading && /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement(
20158
+ "div",
20159
+ {
20160
+ className: "text-left p-3 inline-block max-w-[85%]",
20161
+ style: {
20162
+ backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0",
20163
+ borderRadius: chatAreaConfig.chat_bubble_radius || "18px",
20164
+ fontSize: isMobile ? "14px" : "14px",
20165
+ color: chatAreaConfig.ai_font_color || "#111827"
20166
+ }
20167
+ },
20168
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce" }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })), /* @__PURE__ */ React.createElement("span", { className: "text-sm", style: { fontSize: isMobile ? "13px" : "14px" } }, isConnecting ? "Connecting..." : "Thinking..."))
20169
+ )))
20170
+ ),
20171
+ /* @__PURE__ */ React.createElement(
20172
+ "div",
20173
+ {
20174
+ className: "flex items-center gap-2 border-t flex-shrink-0",
20175
+ style: {
20176
+ backgroundColor: inputAreaConfig.background_color || "#FFF",
20177
+ borderColor: inputAreaConfig.border_color || "#D1D5DB",
20178
+ minHeight: isMobile ? 64 : 70,
20179
+ position: "relative",
20180
+ padding: isMobile ? "10px 12px" : "12px 16px",
20181
+ gap: isMobile ? "8px" : "12px"
20182
+ }
19861
20183
  },
19862
- /* @__PURE__ */ React.createElement("button", { onClick: toggleWidget, className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-white", style: { background: "none", border: "none", cursor: "pointer" } }, "×"),
19863
- /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center justify-center h-full" }, headerConfig.logo_url && /* @__PURE__ */ React.createElement("img", { src: headerConfig.logo_url, alt: "logo", style: { width: 36, height: 36 } }), /* @__PURE__ */ React.createElement("h2", { className: "font-semibold leading-tight text-sm sm:text-base" }, headerConfig.title || "Live Chat"), /* @__PURE__ */ React.createElement("p", { className: "text-xs sm:text-sm opacity-80 leading-tight" }, headerConfig.subtitle || "Connect with visitors"))
20184
+ /* @__PURE__ */ React.createElement(
20185
+ "input",
20186
+ {
20187
+ ref: inputRef,
20188
+ value: inputMessage,
20189
+ onChange: (e) => setInputMessage(e.target.value),
20190
+ onKeyPress: handleKeyPress,
20191
+ disabled: localLoading,
20192
+ className: "flex-1 px-3 py-2 border rounded-lg",
20193
+ style: {
20194
+ borderColor: inputAreaConfig.border_color || "#D1D5DB",
20195
+ color: inputAreaConfig.text_color || "#111827",
20196
+ fontSize: isMobile ? "14px" : "14px",
20197
+ backgroundColor: inputAreaConfig.background_color || "#FFF",
20198
+ minHeight: "40px",
20199
+ padding: isMobile ? "8px 12px" : "10px 12px",
20200
+ lineHeight: "1.5"
20201
+ },
20202
+ placeholder: inputAreaConfig.placeholder_text || "Type your message..."
20203
+ }
20204
+ ),
20205
+ /* @__PURE__ */ React.createElement(
20206
+ "button",
20207
+ {
20208
+ onClick: performSend,
20209
+ disabled: localLoading || !inputMessage.trim(),
20210
+ className: "rounded-full flex items-center justify-center flex-shrink-0 hover:opacity-90 transition-opacity",
20211
+ style: {
20212
+ backgroundColor: ((_b = inputAreaConfig.send_button) == null ? void 0 : _b.color) || "#2563EB",
20213
+ width: isMobile ? "40px" : "44px",
20214
+ height: isMobile ? "40px" : "44px",
20215
+ minWidth: isMobile ? "40px" : "44px",
20216
+ minHeight: isMobile ? "40px" : "44px",
20217
+ opacity: localLoading || !inputMessage.trim() ? 0.5 : 1,
20218
+ transition: "opacity 0.2s",
20219
+ border: "none",
20220
+ cursor: "pointer"
20221
+ }
20222
+ },
20223
+ localLoading ? /* @__PURE__ */ React.createElement(
20224
+ "div",
20225
+ {
20226
+ className: "rounded-full animate-spin",
20227
+ style: {
20228
+ width: isMobile ? 18 : 16,
20229
+ height: isMobile ? 18 : 16,
20230
+ border: "2px solid #FFFFFF",
20231
+ borderTop: "2px solid transparent"
20232
+ }
20233
+ }
20234
+ ) : /* @__PURE__ */ React.createElement(
20235
+ "svg",
20236
+ {
20237
+ viewBox: "0 0 24 24",
20238
+ fill: "currentColor",
20239
+ style: {
20240
+ width: isMobile ? 20 : 18,
20241
+ height: isMobile ? 20 : 18,
20242
+ color: "#fff"
20243
+ }
20244
+ },
20245
+ /* @__PURE__ */ React.createElement("path", { d: "M2 21l21-9L2 3v7l15 2-15 2v7z" })
20246
+ )
20247
+ )
19864
20248
  ),
19865
- /* @__PURE__ */ React.createElement("div", { ref: chatContainerRef, className: "flex-1 relative overflow-y-auto", style: { backgroundColor: appearanceConfig.background_color || "#F9FAFB" } }, appearanceConfig.background_image && /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 pointer-events-none", style: {
19866
- backgroundImage: `url(${appearanceConfig.background_image})`,
19867
- backgroundSize: appearanceConfig.background_size || "cover",
19868
- backgroundPosition: appearanceConfig.background_position || "center",
19869
- backgroundRepeat: appearanceConfig.background_repeat || "no-repeat",
19870
- opacity: appearanceConfig.background_opacity ?? 1,
19871
- zIndex: 0
19872
- } }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 p-3 sm:p-4 space-y-2 sm:space-y-3", style: {
19873
- backgroundColor: appearanceConfig.inner_background_color !== void 0 ? appearanceConfig.inner_background_color : "transparent"
19874
- } }, combinedMessages.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: `text-left p-3 inline-block max-w-[80%]`, style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", color: chatAreaConfig.ai_font_color || "#111827", fontSize: "14px" } }, inputAreaConfig.first_ai_message || "Hello! How can I assist you today?")) : combinedMessages.map((message) => /* @__PURE__ */ React.createElement("div", { key: message.id, className: `flex ${message.role === "user" ? "justify-end" : "justify-start"}` }, /* @__PURE__ */ React.createElement("div", { className: `p-3 inline-block max-w-[80%]`, style: { backgroundColor: message.role === "user" ? chatAreaConfig.user_response_color || "#4ADE80" : message.isError ? "#FEE2E2" : chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: message.role === "user" ? chatAreaConfig.user_font_color || "#111827" : message.isError ? "#991B1B" : chatAreaConfig.ai_font_color || "#111827" } }, message.role !== "system" ? /* @__PURE__ */ React.createElement(Markdown, { remarkPlugins: [remarkGfm] }, message.content) : /* @__PURE__ */ React.createElement("em", { className: "text-xs opacity-70" }, message.content), chatAreaConfig.show_timestamps && message.timestamp && /* @__PURE__ */ React.createElement("div", { className: "text-xs opacity-70 mt-1 text-right" }, message.timestamp)))), isLoading && /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: "text-left p-3 inline-block max-w-[85%]", style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: chatAreaConfig.ai_font_color || "#111827" } }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce" }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, headerConfig.ai_processing_msg || "Thinking...")))))),
19875
- /* @__PURE__ */ React.createElement("div", { className: "p-3 flex items-center gap-2 border-t flex-shrink-0", style: { backgroundColor: inputAreaConfig.background_color || "#FFF", borderColor: inputAreaConfig.border_color || "#D1D5DB", minHeight: 70 } }, /* @__PURE__ */ React.createElement("input", { ref: inputRef, value: inputMessage, onChange: (e) => setInputMessage(e.target.value), onKeyPress: handleKeyPress, disabled: isLoading && false, className: "flex-1 px-3 py-2 border rounded-lg", style: { borderColor: inputAreaConfig.border_color || "#D1D5DB", color: inputAreaConfig.text_color || "#111827", fontSize: "14px", backgroundColor: inputAreaConfig.background_color || "#FFF", minHeight: "44px" }, placeholder: inputAreaConfig.placeholder_text || "Type your message..." }), /* @__PURE__ */ React.createElement("button", { onClick: performSend, disabled: isLoading || !inputMessage.trim(), className: "rounded-full flex items-center justify-center", style: { backgroundColor: ((_b = inputAreaConfig.send_button) == null ? void 0 : _b.color) || "#2563EB", width: 44, height: 44 } }, isLoading ? /* @__PURE__ */ React.createElement("div", { className: "rounded-full animate-spin", style: { width: 16, height: 16, border: "2px solid #FFFFFF", borderTop: "2px solid transparent" } }) : /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", style: { width: 18, height: 18, color: "#fff" } }, /* @__PURE__ */ React.createElement("path", { d: "M2 21l21-9L2 3v7l15 2-15 2v7z" })))),
19876
- inputAreaConfig.show_branding !== false && /* @__PURE__ */ React.createElement("div", { className: "px-3 pb-3 text-center text-xs opacity-80", style: { backgroundColor: inputAreaConfig.background_color } }, /* @__PURE__ */ React.createElement("a", { href: "https://www.getwidgets.app", target: "_blank", rel: "noopener noreferrer", style: { color: getContrastColor(inputAreaConfig.background_color || "#d52929ff") } }, "Powered by WidgetKraft"))
19877
- ));
20249
+ inputAreaConfig.show_branding !== false && /* @__PURE__ */ React.createElement(
20250
+ "div",
20251
+ {
20252
+ className: "text-center text-xs",
20253
+ style: {
20254
+ backgroundColor: inputAreaConfig.background_color,
20255
+ padding: isMobile ? "8px 12px" : "12px 16px"
20256
+ }
20257
+ },
20258
+ /* @__PURE__ */ React.createElement(
20259
+ "a",
20260
+ {
20261
+ href: "https://www.widgetkraft.com",
20262
+ target: "_blank",
20263
+ rel: "noopener noreferrer",
20264
+ style: {
20265
+ color: getContrastColor(inputAreaConfig.background_color || "#d52929ff"),
20266
+ fontSize: isMobile ? "11px" : "12px",
20267
+ textDecoration: "none"
20268
+ }
20269
+ },
20270
+ "Powered by WidgetKraft"
20271
+ )
20272
+ )
20273
+ ), /* @__PURE__ */ React.createElement("style", { jsx: true }, `
20274
+ /* Hide scrollbar for all browsers */
20275
+ .hide-scrollbar::-webkit-scrollbar { display: none; }
20276
+ .hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
20277
+
20278
+ /* Mobile-first responsive design */
20279
+ @media (max-width: 640px) {
20280
+ .responsive-notification {
20281
+ margin: 16px auto !important;
20282
+ }
20283
+
20284
+ /* Improve text readability on small screens */
20285
+ .responsive-message-bubble {
20286
+ word-wrap: break-word;
20287
+ overflow-wrap: break-word;
20288
+ }
20289
+ }
20290
+
20291
+ @media (max-width: 480px) {
20292
+ .responsive-notification {
20293
+ margin: 12px auto !important;
20294
+ min-width: 85vw;
20295
+ }
20296
+ }
20297
+
20298
+ /* Touch-friendly sizes for mobile */
20299
+ @media (hover: none) and (pointer: coarse) {
20300
+ button {
20301
+ min-width: 44px;
20302
+ min-height: 44px;
20303
+ }
20304
+
20305
+ input {
20306
+ min-height: 44px;
20307
+ }
20308
+ }
20309
+
20310
+ /* Desktop optimizations */
20311
+ @media (min-width: 769px) {
20312
+ .responsive-notification:hover {
20313
+ transform: translateY(-2px);
20314
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
20315
+ }
20316
+ }
20317
+
20318
+ /* Scrollbar styling for chat container */
20319
+ .responsive-messages-area::-webkit-scrollbar {
20320
+ width: 6px;
20321
+ }
20322
+
20323
+ .responsive-messages-area::-webkit-scrollbar-track {
20324
+ background: transparent;
20325
+ }
20326
+
20327
+ .responsive-messages-area::-webkit-scrollbar-thumb {
20328
+ background: #ccc;
20329
+ border-radius: 3px;
20330
+ }
20331
+
20332
+ .responsive-messages-area::-webkit-scrollbar-thumb:hover {
20333
+ background: #999;
20334
+ }
20335
+ `));
19878
20336
  };
19879
20337
  function init({ widgetId, apiKey, mode = "inline" }) {
19880
20338
  const container = document.getElementById("livechat-root");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getwidgets/live-chat-widget",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "dist/live-chat-widget.umd.js",
5
5
  "unpkg": "dist/live-chat-widget.umd.js",
6
6
  "type": "module",
@@ -11,6 +11,8 @@
11
11
  "build": "vite build"
12
12
  },
13
13
  "dependencies": {
14
+ "@emoji-mart/data": "^1.2.1",
15
+ "@emoji-mart/react": "^1.1.1",
14
16
  "axios": "^1.6.8",
15
17
  "lucide-react": "^0.456.0",
16
18
  "react": "^18.3.0",