// Author: Shiv // Date: April 2026 "use client"; import { useState, useRef, useEffect } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; import { useChatWidget } from "@/context/ChatWidgetContext"; //Floating chat button and popup window - handles AI chat, live support, and conversation history export default function FloatingChat() { const pathname = usePathname(); const { user, token } = useAuth(); const { isOpen, toggleOpen, view, openView, aiMessages, aiSending, aiError, setAiError, sendAiMessage, conversations, convsLoading, loadConversations, activeConvId, activeConv, liveMessages, liveSending, openLiveConversation, sendLiveMessage, startLiveChat, switchingToHuman, } = useChatWidget(); const [input, setInput] = useState(""); const [fabHovered, setFabHovered] = useState(false); const messagesEndRef = useRef(null); const prevAiLengthRef = useRef(0); const prevLiveLengthRef = useRef(0); //Scrolls to the bottom when new messages arrive, but only if already near the bottom useEffect(() => { if (!isOpen) return; const aiGrew = aiMessages.length > prevAiLengthRef.current; const liveGrew = liveMessages.length > prevLiveLengthRef.current; prevAiLengthRef.current = aiMessages.length; prevLiveLengthRef.current = liveMessages.length; if (aiGrew || liveGrew) { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); } }, [aiMessages, liveMessages, isOpen]); useEffect(() => { if (view === "history" && token && isOpen) loadConversations(token); }, [view, token, isOpen, loadConversations]); //Don't show the widget on the full chat pages since they have their own UI if (pathname === "/ai-chat" || pathname === "/chat") return null; const openConvCount = conversations.filter((c) => c.status === "OPEN").length; const isLiveClosed = activeConv?.status === "CLOSED"; //Sends the typed message to whichever chat is currently active async function handleSend(e) { e?.preventDefault(); const text = input.trim(); if (!text) return; setInput(""); if (view === "ai") { await sendAiMessage(text, token); } else if (view === "live" && activeConvId) { await sendLiveMessage(text, token, activeConvId); } } function handleKeyDown(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } } return ( <> {/* Floating toggle button */} {/* Chat window */} {isOpen && (
{/* Header */}
assistant
Leon's Assistant
{view === "live" ? (activeConv?.mode === "HUMAN" ? "Live Support" : "AI Support") : view === "history" ? "Your Conversations" : "AI Chat"}
{view !== "ai" && ( )}
{/* Guest */} {!user && (
assistant

Log in to chat with our pet assistant!

Log In
)} {/* History view */} {user && view === "history" && (
{convsLoading && (

Loading…

)} {!convsLoading && conversations.length === 0 && (
chat

No conversations yet.
Start a live chat above.

)} {/*open & active conversations*/} {conversations.filter(c => c.status === "OPEN" && c.staffId).length > 0 && (
Active
)} {conversations.filter(c => c.status === "OPEN" && c.staffId).map((conv) => ( ))} {/* Unclaimed - waiting for staff */} {conversations.filter(c => c.status === "OPEN" && !c.staffId).length > 0 && (
Waiting
)} {conversations.filter(c => c.status === "OPEN" && !c.staffId).map((conv) => ( ))} {/* Closed convesations*/} {conversations.filter(c => c.status === "CLOSED").length > 0 && (
Closed
)} {conversations.filter(c => c.status === "CLOSED").map((conv) => ( ))}
)} {/* Live chat view */} {user && view === "live" && ( <> {activeConv && (
{activeConv.status} {activeConv.mode === "HUMAN" ? "👤 Live Support" : "🤖 AI Support"} Full page ↗
)}
{liveMessages.length === 0 && (

No messages yet.

)} {liveMessages.map((msg) => { const isUser = msg.senderRole === "CUSTOMER"; return (
{!isUser && (
assistant
)}
{!isUser && (
{msg.senderName || (msg.senderRole === "BOT" ? "AI Bot" : "Staff")}
)} {msg.content}
{isUser && (
{user?.fullName ? user.fullName.charAt(0).toUpperCase() : "U"}
)}
); })}
{isLiveClosed ? (
This conversation is closed.
) : (
setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type a message…" disabled={liveSending} autoComplete="off" />
)} )} {/* AI chat view */} {user && view === "ai" && ( <>
Open full page ↗
{aiMessages.length === 0 && (
assistant

Hi{user?.fullName ? `, ${user.fullName.split(" ")[0]}` : ""}!
Ask me anything about pets.

)} {aiMessages.map((msg) => (
{msg.role === "assistant" &&
assistant
}
{msg.content.split("\n").map((line, i, arr) => ( {line}{i < arr.length - 1 &&
}
))}
{msg.role === "user" && (
{user?.fullName ? user.fullName.charAt(0).toUpperCase() : "U"}
)}
))} {aiSending && (
assistant
)} {aiError && (
{aiError}
)}
setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Ask about pet care…" disabled={aiSending} autoComplete="off" />
)}
)} ); } //Inline style objects for the floating chat widget const s = { fab: { position: "fixed", bottom: 24, right: 24, width: 56, height: 56, borderRadius: "50%", background: "#e68672", color: "#2f2f2f", border: "none", cursor: "pointer", zIndex: 9999, boxShadow: "0 4px 18px rgba(0,0,0,0.22)", display: "flex", alignItems: "center", justifyContent: "center", transition: "transform 0.15s, box-shadow 0.15s", }, fabBadge: { position: "absolute", top: 2, right: 2, background: "#e53935", color: "white", borderRadius: "999px", fontSize: "0.62rem", fontWeight: 700, minWidth: 17, height: 17, display: "flex", alignItems: "center", justifyContent: "center", padding: "0 3px", }, window: { position: "fixed", bottom: 92, right: 24, width: 370, height: 530, background: "#fff", borderRadius: 16, boxShadow: "0 8px 36px rgba(0,0,0,0.18)", zIndex: 9998, display: "flex", flexDirection: "column", overflow: "hidden", fontFamily: "Arial, sans-serif", }, header: { background: "#e68672", padding: "0.8rem 1rem", display: "flex", alignItems: "center", justifyContent: "space-between", flexShrink: 0, }, headerLeft: { display: "flex", alignItems: "center", gap: "0.6rem" }, headerAvatar: { width: 36, height: 36, borderRadius: "50%", background: "rgba(255,255,255,0.25)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "1.1rem", }, headerTitle: { fontWeight: 700, fontSize: "0.92rem", color: "#2f2f2f" }, headerSub: { fontSize: "0.7rem", color: "rgba(47,47,47,0.65)", marginTop: 1 }, headerRight: { display: "flex", alignItems: "center", gap: "0.35rem" }, headerNavBtn: { background: "rgba(255,255,255,0.22)", border: "none", borderRadius: 6, padding: "0.28rem 0.6rem", fontSize: "0.75rem", fontWeight: 600, color: "#2f2f2f", cursor: "pointer", }, headerNavBtnActive: { background: "rgba(255,255,255,0.45)" }, headerClose: { background: "transparent", border: "none", fontSize: "0.95rem", color: "#2f2f2f", cursor: "pointer", padding: "0.2rem 0.35rem", lineHeight: 1, }, guestBody: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "2rem", }, loginBtn: { background: "#e68672", color: "#2f2f2f", padding: "0.6rem 1.5rem", borderRadius: 8, textDecoration: "none", fontWeight: 700, fontSize: "0.95rem", }, toolbar: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0.5rem 0.85rem", borderBottom: "1px solid #f0f0f0", background: "#fafafa", flexShrink: 0, }, fullPageLink: { color: "#e68672", fontSize: "0.76rem", fontWeight: 600, textDecoration: "none" }, humanBtn: { background: "transparent", border: "1.5px solid #e68672", color: "#e68672", borderRadius: 6, padding: "0.28rem 0.65rem", fontSize: "0.76rem", fontWeight: 600, cursor: "pointer", }, disabledBtn: { opacity: 0.55, cursor: "not-allowed" }, messages: { flex: 1, overflowY: "auto", padding: "0.8rem", display: "flex", flexDirection: "column", gap: "0.55rem", }, empty: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "0.5rem", margin: "auto", }, emptyText: { color: "#aaa", fontSize: "0.88rem", textAlign: "center", lineHeight: 1.5, margin: 0 }, row: { display: "flex", alignItems: "flex-end", gap: "0.4rem" }, rowUser: { flexDirection: "row-reverse" }, rowOther: { flexDirection: "row" }, otherAvatar: { width: 26, height: 26, borderRadius: "50%", background: "#f0f0f0", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.75rem", flexShrink: 0, }, userAvatar: { width: 26, height: 26, borderRadius: "50%", background: "#e68672", color: "#2f2f2f", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.72rem", fontWeight: 700, flexShrink: 0, }, bubble: { maxWidth: "76%", padding: "0.5rem 0.75rem", borderRadius: 12, fontSize: "0.86rem", lineHeight: 1.5, wordBreak: "break-word", }, bubbleUser: { background: "#e68672", color: "#2f2f2f", borderBottomRightRadius: 4 }, bubbleOther: { background: "#f4f4f4", color: "#1a1a1a", borderBottomLeftRadius: 4 }, typingBubble: { display: "flex", alignItems: "center", gap: "4px", padding: "0.65rem 0.85rem" }, senderName: { fontSize: "0.7rem", fontWeight: 700, color: "#888", marginBottom: 2 }, errorBar: { background: "#fff0f0", borderTop: "1px solid #ffd0d0", color: "#c0392b", padding: "0.5rem 0.85rem", fontSize: "0.8rem", display: "flex", alignItems: "center", justifyContent: "space-between", flexShrink: 0, }, errorClose: { background: "none", border: "none", color: "#c0392b", cursor: "pointer" }, inputRow: { display: "flex", gap: "0.45rem", padding: "0.6rem 0.85rem", borderTop: "1px solid #f0f0f0", flexShrink: 0, }, input: { flex: 1, border: "1.5px solid #e0e0e0", borderRadius: 8, padding: "0.48rem 0.75rem", fontSize: "0.86rem", outline: "none", fontFamily: "inherit", }, sendBtn: { background: "#e68672", color: "#2f2f2f", border: "none", borderRadius: 8, padding: "0.48rem 0.75rem", fontSize: "0.95rem", fontWeight: 700, cursor: "pointer", flexShrink: 0, }, sendBtnDisabled: { background: "#f0c8be", cursor: "not-allowed" }, historyToolbar: { display: "flex", gap: "0.5rem", padding: "0.65rem 0.85rem", borderBottom: "1px solid #f0f0f0", flexShrink: 0, }, newChatBtn: { flex: 1, background: "#e68672", color: "#2f2f2f", border: "none", borderRadius: 8, padding: "0.5rem", fontSize: "0.8rem", fontWeight: 700, cursor: "pointer", }, aiTabBtn: { background: "#f4f4f4", color: "#555", border: "none", borderRadius: 8, padding: "0.5rem 0.85rem", fontSize: "0.8rem", fontWeight: 600, cursor: "pointer", }, convItem: { display: "flex", flexDirection: "column", gap: "0.2rem", padding: "0.7rem 0.85rem", borderBottom: "1px solid #f0f0f0", background: "white", border: "none", textAlign: "left", cursor: "pointer", width: "100%", }, convTop: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "0.5rem" }, convBottom: { display: "flex", alignItems: "center", justifyContent: "space-between" }, convSubject: { fontSize: "0.86rem", fontWeight: 600, color: "#222", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, }, convMode: { fontSize: "0.74rem", color: "#999" }, convDate: { fontSize: "0.7rem", color: "#bbb" }, statusBadge: { fontSize: "0.67rem", fontWeight: 700, borderRadius: 20, padding: "0.13rem 0.5rem", flexShrink: 0, textTransform: "uppercase", letterSpacing: "0.04em", }, statusOpen: { background: "#e6f9ee", color: "#1a7a3c" }, statusClosed: { background: "#f0f0f0", color: "#888" }, liveStatus: { display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.45rem 0.85rem", borderBottom: "1px solid #f0f0f0", background: "#fafafa", flexShrink: 0, }, statusUnclaimed: { background: "#fff8e1", color: "#f57f17" }, sectionLabel: { fontSize: "0.7rem", fontWeight: 700, color: "#aaa", padding: "0.4rem 0.85rem 0.2rem", textTransform: "uppercase", letterSpacing: "0.05em", background: "#fafafa", }, convItemClosed: { background: "#fafafa", opacity: 0.75 }, closedBanner: { background: "#f5f5f5", borderTop: "1px solid #e0e0e0", color: "#888", padding: "0.65rem 0.85rem", fontSize: "0.84rem", textAlign: "center", flexShrink: 0, }, };