diff --git a/web/app/globals.css b/web/app/globals.css
index bd339347..ec01a40b 100644
--- a/web/app/globals.css
+++ b/web/app/globals.css
@@ -30,7 +30,7 @@ body {
padding: 0.5rem 2rem;
display: flex;
align-items: center;
- flex-wrap: wrap;
+ justify-content: space-between;
min-height: 70px;
/* border-radius: 0px 0px 10px 10px; */
}
@@ -65,17 +65,18 @@ body {
.nav-link {
color: #2f2f2f;
text-decoration: none;
- font-size: 1.1rem;
- font-weight: 700;
+ font-size: 1.05rem;
+ font-weight: 600;
padding: 0.5rem 1rem;
- border-radius: 4px;
- transition: all 0.3s ease;
+ border-radius: 6px;
+ transition: background-color 0.25s ease, color 0.25s ease;
position: relative;
}
/* Alternative Hover Effect - Background */
.nav-link:hover {
- background-color: rgba(255, 255, 255, 0.171);
+ background-color: rgba(255, 255, 255, 0.25);
+
}
diff --git a/web/components/FloatingChat.js b/web/components/FloatingChat.js
index 4b17232d..4c6b45ab 100644
--- a/web/components/FloatingChat.js
+++ b/web/components/FloatingChat.js
@@ -21,10 +21,20 @@ export default function FloatingChat() {
const [input, setInput] = useState("");
const messagesEndRef = useRef(null);
+ const prevAiLengthRef = useRef(0);
+const prevLiveLengthRef = useRef(0);
useEffect(() => {
- if (isOpen) messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
- }, [aiMessages, liveMessages, isOpen]);
+ 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);
@@ -130,22 +140,66 @@ export default function FloatingChat() {
)}
- {conversations.map((conv) => (
+
+ {/*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" && (
@@ -457,6 +511,14 @@ const s = {
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,
diff --git a/web/components/Navigation.js b/web/components/Navigation.js
index 39baa63a..d84f865b 100644
--- a/web/components/Navigation.js
+++ b/web/components/Navigation.js
@@ -52,7 +52,7 @@ export default function DisplayNav() {
Appointments
Help
Contact
- About
+ {/*} About */}
@@ -128,7 +128,7 @@ export default function DisplayNav() {
Appointments
Help
Contact
-
About
+ {/*
About */}
diff --git a/web/context/ChatWidgetContext.js b/web/context/ChatWidgetContext.js
index 0b506dbf..82943654 100644
--- a/web/context/ChatWidgetContext.js
+++ b/web/context/ChatWidgetContext.js
@@ -6,13 +6,23 @@ const ChatWidgetContext = createContext(null);
const API_BASE = "";
export function ChatWidgetProvider({ children }) {
- const [isOpen, setIsOpen] = useState(false);
- const [view, setView] = useState("ai"); // "ai" | "history" | "live"
+ const [isOpen, setIsOpen] = useState(false);
+ const [view, setView] = useState("ai"); // "ai" | "history" | "live"
// AI chat
- const [aiMessages, setAiMessages] = useState([]);
- const [aiSending, setAiSending] = useState(false);
- const [aiError, setAiError] = useState(null);
+ const [aiMessages, setAiMessages] = useState(() => {
+ try {
+ const saved = localStorage.getItem("fc_aiMessages");
+ return saved ? JSON.parse(saved) : [];
+ } catch { return []; }
+ });
+ const [aiSending, setAiSending] = useState(false);
+ const [aiError, setAiError] = useState(null);
+
+ // Persist aiMessages to localStorage
+ useEffect(() => {
+ localStorage.setItem("fc_aiMessages", JSON.stringify(aiMessages));
+ }, [aiMessages]);
// Keep a ref so sendAiMessage stays stable (no stale-closure over messages)
const aiMessagesRef = useRef(aiMessages);
@@ -41,17 +51,18 @@ export function ChatWidgetProvider({ children }) {
}
}, []);
- //Live chat
- const [conversations, setConversations] = useState([]);
- const [convsLoading, setConvsLoading] = useState(false);
- const [activeConvId, setActiveConvId] = useState(null);
- const [activeConv, setActiveConv] = useState(null);
- const [liveMessages, setLiveMessages] = useState([]);
- const [liveSending, setLiveSending] = useState(false);
+ // Live chat
+ const [conversations, setConversations] = useState([]);
+ const [convsLoading, setConvsLoading] = useState(false);
+ const [activeConvId, setActiveConvId] = useState(null);
+ const [activeConv, setActiveConv] = useState(null);
+ const [liveMessages, setLiveMessages] = useState([]);
+ const [liveSending, setLiveSending] = useState(false);
const [switchingToHuman, setSwitchingToHuman] = useState(false);
- const pollRef = useRef(null);
+ const pollRef = useRef(null);
const activeConvIdRef = useRef(null);
+ const tokenRef = useRef(null); // FIX: store token so polling can restart
const stopPolling = useCallback(() => {
if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
@@ -87,6 +98,7 @@ export function ChatWidgetProvider({ children }) {
const openLiveConversation = useCallback(async (convId, token) => {
if (!convId || !token) return;
stopPolling();
+ tokenRef.current = token; // FIX: save token for polling restart
setActiveConvId(convId);
activeConvIdRef.current = convId;
setLiveMessages([]);
@@ -137,12 +149,22 @@ export function ChatWidgetProvider({ children }) {
}
}, [switchingToHuman, openLiveConversation]);
- // Stop polling when navigating away from live view or closing widget
- useEffect(() => { if (view !== "live") stopPolling(); }, [view, stopPolling]);
- useEffect(() => { if (!isOpen) stopPolling(); }, [isOpen, stopPolling]);
+ // FIX: Single effect that handles both stopping AND restarting polling
+ useEffect(() => {
+ if (!isOpen || view !== "live") {
+ stopPolling();
+ } else if (isOpen && view === "live" && activeConvIdRef.current && tokenRef.current) {
+ stopPolling();
+ fetchLiveMessages(activeConvIdRef.current, tokenRef.current);
+ pollRef.current = setInterval(
+ () => fetchLiveMessages(activeConvIdRef.current, tokenRef.current),
+ 2500
+ );
+ }
+ }, [isOpen, view, stopPolling, fetchLiveMessages]);
const toggleOpen = useCallback(() => setIsOpen((o) => !o), []);
- const openView = useCallback((v) => setView(v), []);
+ const openView = useCallback((v) => setView(v), []);
return (