Merge pull request #335 from RecentRunner/web-lastfixes

web last fixes
This commit is contained in:
2026-04-20 11:23:59 -06:00
committed by GitHub
19 changed files with 102 additions and 44 deletions

View File

@@ -68,11 +68,23 @@ function AttachmentPreview({ url, name, token }) {
);
}
function useIsMobile() {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const check = () => setIsMobile(window.innerWidth < 640);
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, []);
return isMobile;
}
function AiChatPage() {
const { user, token, loading: authLoading } = useAuth();
const router = useRouter();
const searchParams = useSearchParams();
const conversationIdParam = searchParams.get("id");
const isMobile = useIsMobile();
const [conversation, setConversation] = useState(null);
const [messages, setMessages] = useState([]);
@@ -498,8 +510,8 @@ function AiChatPage() {
</section>
<section style={s.chatSection}>
<div style={{ display: "flex", gap: "1rem", alignItems: "flex-start" }}>
<div style={s.sidebar}>
<div style={{ display: "flex", flexDirection: isMobile ? "column" : "row", gap: "1rem", alignItems: "flex-start" }}>
<div style={{ ...s.sidebar, width: isMobile ? "100%" : 230, maxHeight: isMobile ? 260 : "calc(100vh - 220px)" }}>
<div style={s.sidebarHeader}>
<span style={s.sidebarTitle}>All Conversations</span>
</div>
@@ -519,7 +531,10 @@ function AiChatPage() {
<span style={{ ...s.convStatusBadge, ...s.convStatusOpen }}>{conv.status}</span>
</div>
<div style={s.convItemBottom}>
<span style={s.convItemMode}>{conv.mode === "HUMAN" ? "👤 Live" : "🤖 AI"}</span>
<span style={{ ...s.convItemMode, display: "flex", alignItems: "center", gap: "0.25rem" }}>
<img src={conv.mode === "HUMAN" ? "/bootstrap/person-fill.svg" : "/bootstrap/robot.svg"} alt="" style={{ width: 12, height: 12 }} />
{conv.mode === "HUMAN" ? "Live" : "AI"}
</span>
<span style={s.convItemDate}>{conv.createdAt ? new Date(conv.createdAt).toLocaleDateString() : ""}</span>
</div>
</button>
@@ -542,7 +557,10 @@ function AiChatPage() {
<span style={{ ...s.convStatusBadge, ...s.convStatusClosed }}>CLOSED</span>
</div>
<div style={s.convItemBottom}>
<span style={s.convItemMode}>{conv.mode === "HUMAN" ? "👤 Live" : "🤖 AI"}</span>
<span style={{ ...s.convItemMode, display: "flex", alignItems: "center", gap: "0.25rem" }}>
<img src={conv.mode === "HUMAN" ? "/bootstrap/person-fill.svg" : "/bootstrap/robot.svg"} alt="" style={{ width: 12, height: 12 }} />
{conv.mode === "HUMAN" ? "Live" : "AI"}
</span>
<span style={s.convItemDate}>{conv.createdAt ? new Date(conv.createdAt).toLocaleDateString() : ""}</span>
</div>
</button>
@@ -554,10 +572,10 @@ function AiChatPage() {
</button>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ flex: 1, minWidth: 0, width: isMobile ? "100%" : undefined }}>
{!conversation ? (
<div style={s.noConvCard}>
<div style={s.noConvIcon}>🐾</div>
<div style={s.noConvIcon}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "3rem", height: "3rem", opacity: 0.6 }} /></div>
<h2 style={s.noConvTitle}>No active conversation</h2>
<p style={s.noConvText}>Start a new conversation with the AI assistant.</p>
{error && <div style={s.errorInline}>{error}</div>}
@@ -570,9 +588,9 @@ function AiChatPage() {
</div>
) : (
<div style={s.chatCard}>
<div style={s.chatHeader}>
<div style={{ ...s.chatHeader, flexDirection: isMobile ? "column" : "row", alignItems: isMobile ? "flex-start" : "center", gap: isMobile ? "0.6rem" : 0 }}>
<div style={s.chatHeaderLeft}>
<div style={isEscalated ? s.agentAvatar : s.aiAvatar}>{isEscalated ? "👤" : "🐾"}</div>
<div style={isEscalated ? s.agentAvatar : s.aiAvatar}>{isEscalated ? "👤" : <img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%", filter: "brightness(0) invert(1)" }} />}</div>
<div>
<div style={s.chatHeaderTitle}>
{isEscalated ? (hasStaff ? "Support Agent" : "Leon's Pet Store Support") : "Leon's Pet Assistant"}
@@ -583,14 +601,14 @@ function AiChatPage() {
</div>
</div>
</div>
<div style={{ display: "flex", gap: "0.5rem" }}>
<div style={{ display: "flex", gap: "0.5rem", width: isMobile ? "100%" : undefined }}>
{!isEscalated && !isClosed && (
<button style={s.humanBtn} onClick={handleSwitchToHuman} title="Connect with a human support agent">
<button style={isMobile ? { ...s.humanBtn, flex: 1 } : s.humanBtn} onClick={handleSwitchToHuman} title="Connect with a human support agent">
Chat with a Real Person
</button>
)}
{!isClosed && (
<button style={s.closeConvBtn} onClick={handleCloseConversation} title="Close this conversation">
<button style={isMobile ? { ...s.closeConvBtn, flex: 1 } : s.closeConvBtn} onClick={handleCloseConversation} title="Close this conversation">
Close Chat
</button>
)}
@@ -607,7 +625,7 @@ function AiChatPage() {
<div style={s.messagesArea} ref={messagesAreaRef}>
{messages.length === 0 && (
<div style={s.emptyState}>
<div style={s.emptyIcon}>{isEscalated ? "💬" : "🐾"}</div>
<div style={s.emptyIcon}>{isEscalated ? "💬" : <img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "3rem", height: "3rem", opacity: 0.6 }} />}</div>
<p style={s.emptyText}>
{isEscalated ? "Your conversation has started. A support agent will join soon." : `Hello${user.fullName ? `, ${user.fullName.split(" ")[0]}` : ""}! I'm your pet care assistant. Ask me about pet recommendations, care tips, supplies, or anything pet-related!`}
</p>
@@ -624,7 +642,7 @@ function AiChatPage() {
...(isOwn ? s.messageRowUser : s.messageRowAgent),
}}
>
{!isOwn && <div style={isEscalated ? s.agentAvatarSmall : s.aiAvatarSmall}>{isEscalated ? "👤" : "🐾"}</div>}
{!isOwn && <div style={isEscalated ? s.agentAvatarSmall : s.aiAvatarSmall}>{isEscalated ? "👤" : <img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%", filter: "brightness(0) invert(1)" }} />}</div>}
<div
style={{
...s.messageBubble,
@@ -655,7 +673,7 @@ function AiChatPage() {
{botTyping && !isEscalated && (
<div style={{ ...s.messageRow, ...s.messageRowAgent }}>
<div style={s.aiAvatarSmall}>🐾</div>
<div style={s.aiAvatarSmall}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%", filter: "brightness(0) invert(1)" }} /></div>
<div style={{ ...s.messageBubble, ...s.bubbleAgent, display: "flex", alignItems: "center", gap: "4px", padding: "0.6rem 0.9rem" }}>
<span className="fc-dot" />
<span className="fc-dot" style={{ animationDelay: "0.2s" }} />
@@ -792,7 +810,6 @@ const s = {
padding: "1.5rem 1rem 2rem",
},
sidebar: {
width: 230,
flexShrink: 0,
background: "white",
borderRadius: 16,
@@ -800,8 +817,7 @@ const s = {
display: "flex",
flexDirection: "column",
overflow: "hidden",
maxHeight: "calc(100vh - 220px)",
minHeight: 300,
minHeight: 200,
},
sidebarHeader: {
display: "flex",
@@ -901,7 +917,7 @@ const s = {
display: "flex",
flexDirection: "column",
height: "calc(100vh - 220px)",
minHeight: 450,
minHeight: 400,
},
chatHeader: {
display: "flex",
@@ -922,6 +938,7 @@ const s = {
height: 44,
borderRadius: "50%",
background: "linear-gradient(135deg, #444, #666)",
border: "3px solid #666",
display: "flex",
alignItems: "center",
justifyContent: "center",
@@ -953,8 +970,8 @@ const s = {
border: "2px solid #ff8c00",
color: "#ff8c00",
borderRadius: 8,
padding: "0.45rem 0.9rem",
fontSize: "0.82rem",
padding: "0.3rem 0.65rem",
fontSize: "0.72rem",
fontWeight: 600,
cursor: "pointer",
whiteSpace: "nowrap",
@@ -997,8 +1014,8 @@ const s = {
border: "2px solid #c0392b",
color: "#c0392b",
borderRadius: 8,
padding: "0.45rem 0.9rem",
fontSize: "0.82rem",
padding: "0.3rem 0.65rem",
fontSize: "0.72rem",
fontWeight: 600,
cursor: "pointer",
whiteSpace: "nowrap",
@@ -1105,6 +1122,7 @@ const s = {
height: 30,
borderRadius: "50%",
background: "linear-gradient(135deg, #444, #666)",
border: "3px solid #666",
display: "flex",
alignItems: "center",
justifyContent: "center",