merge websitefinal #309
@@ -32,10 +32,16 @@ function ChatPage() {
|
||||
}
|
||||
}, [authLoading, user, router, conversationIdParam]);
|
||||
|
||||
useEffect(() => {
|
||||
const prevMsgLengthRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (messages.length > prevMsgLengthRef.current) {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
prevMsgLengthRef.current = messages.length;
|
||||
}, [messages]);
|
||||
|
||||
|
||||
const fetchMessages = useCallback(async (convId) => {
|
||||
if (!token || !convId) return;
|
||||
try {
|
||||
|
||||
@@ -36,7 +36,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; */
|
||||
}
|
||||
@@ -71,17 +71,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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const navImages = [
|
||||
{id: "nav-adopt", src: "/images/home/navimages/adopt.jpg", alt: "Adopt a Pet", link: "/adopt", title: "Adopt a Pet"},
|
||||
{id: "nav-products", src: "/images/home/navimages/store.jpg", alt: "Online Store", link: "/products", title: "Online Store"},
|
||||
{id: "nav-appointments", src: "/images/home/navimages/appointments.jpg", alt: "Appointments", link: "/appointments", title: "Appointments"},
|
||||
{id: "nav-about", src: "/images/home/navimages/about.jpg", alt: "About Us", link: "/about", title: "About Us"},
|
||||
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
@@ -76,6 +76,42 @@ export default function Home() {
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About Us Section */}
|
||||
<section className="info-page">
|
||||
<div className="info-hero">
|
||||
<h2 className="info-title">About Leon's Pet Store</h2>
|
||||
<p className="info-subtitle">Pet care, adoption support, grooming, and everyday essentials in one place.</p>
|
||||
<div className="title-decoration"></div>
|
||||
</div>
|
||||
|
||||
<div className="info-content">
|
||||
<div className="info-card">
|
||||
<h2>What We Do</h2>
|
||||
<p>
|
||||
Leon's Pet Store connects families with adoptable pets, helpful services, and quality products for day-to-day pet care.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="info-card">
|
||||
<h2>Our Focus</h2>
|
||||
<ul className="info-list">
|
||||
<li>Support responsible pet adoption</li>
|
||||
<li>Provide grooming and care services</li>
|
||||
<li>Offer reliable pet supplies and essentials</li>
|
||||
<li>Create a friendly experience for customers and staff</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="info-card">
|
||||
<h2>Visit the Store</h2>
|
||||
<p>
|
||||
Browse adoptable pets, schedule appointments, shop products, or contact the team for help finding the right fit for a pet and household.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -137,7 +147,7 @@ export default function FloatingChat() {
|
||||
</div>
|
||||
)}
|
||||
{conversations.filter(c => c.status === "OPEN" && c.staffId).map((conv) => (
|
||||
<button key={conv.Id} style={s.convItem} onClick={() => openLiveConversation(conv.id, token)}>
|
||||
<button key={conv.id} style={s.convItem} onClick={() => openLiveConversation(conv.id, token)}>
|
||||
<div style={s.convTop}>
|
||||
<span style={s.convSubject}>{conv.subject || `Conversation #${conv.id}`}</span>
|
||||
<span style={{ ...s.statusBadge, ...s.statusOpen }}>Active</span>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function DisplayNav() {
|
||||
<Link href="/appointments" className="nav-link">Appointments</Link>
|
||||
<Link href="/ai-chat" className="nav-link">Help</Link>
|
||||
<Link href="/contact" className="nav-link">Contact</Link>
|
||||
<Link href="/about" className="nav-link">About</Link>
|
||||
{/*} <Link href="/about" className="nav-link">About</Link> */}
|
||||
</div>
|
||||
|
||||
<div className="nav-auth">
|
||||
@@ -131,7 +131,7 @@ export default function DisplayNav() {
|
||||
<Link href="/appointments" className="nav-drawer-link" onClick={closeMenu}>Appointments</Link>
|
||||
<Link href="/ai-chat" className="nav-drawer-link" onClick={closeMenu}>Help</Link>
|
||||
<Link href="/contact" className="nav-drawer-link" onClick={closeMenu}>Contact</Link>
|
||||
<Link href="/about" className="nav-drawer-link" onClick={closeMenu}>About</Link>
|
||||
{/* <Link href="/about" className="nav-drawer-link" onClick={closeMenu}>About</Link> */}
|
||||
|
||||
<div className="nav-drawer-divider" />
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<ChatWidgetContext.Provider value={{
|
||||
@@ -163,4 +185,4 @@ export function useChatWidget() {
|
||||
const ctx = useContext(ChatWidgetContext);
|
||||
if (!ctx) throw new Error("useChatWidget must be used within ChatWidgetProvider");
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user