Replaced emoticons

This commit is contained in:
augmentedpotato
2026-04-19 13:33:52 -06:00
parent 33bb0a3ad7
commit 3c64a859b4
10 changed files with 60 additions and 18 deletions

View File

@@ -529,7 +529,10 @@ function AiChatPage() {
<span style={{ ...s.convStatusBadge, ...s.convStatusOpen }}>{conv.status}</span> <span style={{ ...s.convStatusBadge, ...s.convStatusOpen }}>{conv.status}</span>
</div> </div>
<div style={s.convItemBottom}> <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> <span style={s.convItemDate}>{conv.createdAt ? new Date(conv.createdAt).toLocaleDateString() : ""}</span>
</div> </div>
</button> </button>
@@ -552,7 +555,10 @@ function AiChatPage() {
<span style={{ ...s.convStatusBadge, ...s.convStatusClosed }}>CLOSED</span> <span style={{ ...s.convStatusBadge, ...s.convStatusClosed }}>CLOSED</span>
</div> </div>
<div style={s.convItemBottom}> <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> <span style={s.convItemDate}>{conv.createdAt ? new Date(conv.createdAt).toLocaleDateString() : ""}</span>
</div> </div>
</button> </button>
@@ -567,7 +573,7 @@ function AiChatPage() {
<div style={{ flex: 1, minWidth: 0, width: isMobile ? "100%" : undefined }}> <div style={{ flex: 1, minWidth: 0, width: isMobile ? "100%" : undefined }}>
{!conversation ? ( {!conversation ? (
<div style={s.noConvCard}> <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> <h2 style={s.noConvTitle}>No active conversation</h2>
<p style={s.noConvText}>Start a new conversation with the AI assistant.</p> <p style={s.noConvText}>Start a new conversation with the AI assistant.</p>
{error && <div style={s.errorInline}>{error}</div>} {error && <div style={s.errorInline}>{error}</div>}
@@ -582,7 +588,7 @@ function AiChatPage() {
<div style={s.chatCard}> <div style={s.chatCard}>
<div style={{ ...s.chatHeader, flexDirection: isMobile ? "column" : "row", alignItems: isMobile ? "flex-start" : "center", gap: isMobile ? "0.6rem" : 0 }}> <div style={{ ...s.chatHeader, flexDirection: isMobile ? "column" : "row", alignItems: isMobile ? "flex-start" : "center", gap: isMobile ? "0.6rem" : 0 }}>
<div style={s.chatHeaderLeft}> <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>
<div style={s.chatHeaderTitle}> <div style={s.chatHeaderTitle}>
{isEscalated ? (hasStaff ? "Support Agent" : "Leon's Pet Store Support") : "Leon's Pet Assistant"} {isEscalated ? (hasStaff ? "Support Agent" : "Leon's Pet Store Support") : "Leon's Pet Assistant"}
@@ -617,7 +623,7 @@ function AiChatPage() {
<div style={s.messagesArea} ref={messagesAreaRef}> <div style={s.messagesArea} ref={messagesAreaRef}>
{messages.length === 0 && ( {messages.length === 0 && (
<div style={s.emptyState}> <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}> <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!`} {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> </p>
@@ -634,7 +640,7 @@ function AiChatPage() {
...(isOwn ? s.messageRowUser : s.messageRowAgent), ...(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 <div
style={{ style={{
...s.messageBubble, ...s.messageBubble,
@@ -665,7 +671,7 @@ function AiChatPage() {
{botTyping && !isEscalated && ( {botTyping && !isEscalated && (
<div style={{ ...s.messageRow, ...s.messageRowAgent }}> <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" }}> <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" />
<span className="fc-dot" style={{ animationDelay: "0.2s" }} /> <span className="fc-dot" style={{ animationDelay: "0.2s" }} />
@@ -930,6 +936,7 @@ const s = {
height: 44, height: 44,
borderRadius: "50%", borderRadius: "50%",
background: "linear-gradient(135deg, #444, #666)", background: "linear-gradient(135deg, #444, #666)",
border: "3px solid #666",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@@ -1113,6 +1120,7 @@ const s = {
height: 30, height: 30,
borderRadius: "50%", borderRadius: "50%",
background: "linear-gradient(135deg, #444, #666)", background: "linear-gradient(135deg, #444, #666)",
border: "3px solid #666",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",

View File

@@ -20,6 +20,7 @@ export default function FloatingChat() {
} = useChatWidget(); } = useChatWidget();
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [fabHovered, setFabHovered] = useState(false);
const messagesEndRef = useRef(null); const messagesEndRef = useRef(null);
const prevAiLengthRef = useRef(0); const prevAiLengthRef = useRef(0);
const prevLiveLengthRef = useRef(0); const prevLiveLengthRef = useRef(0);
@@ -65,8 +66,15 @@ const prevLiveLengthRef = useRef(0);
return ( return (
<> <>
{/* Floating toggle button */} {/* Floating toggle button */}
<button onClick={toggleOpen} style={s.fab} aria-label={isOpen ? "Close chat" : "Open chat"}> <button onClick={toggleOpen} style={s.fab} aria-label={isOpen ? "Close chat" : "Open chat"} onMouseEnter={() => setFabHovered(true)} onMouseLeave={() => setFabHovered(false)}>
<span style={{ fontSize: "1.4rem", lineHeight: 1 }}>{isOpen ? "✕" : "💬"}</span> {isOpen ? (
<span style={{ fontSize: "1.4rem", lineHeight: 1 }}></span>
) : (
<span style={{ position: "relative", width: 24, height: 24, display: "inline-block" }}>
<img src="/bootstrap/chat.svg" alt="" style={{ width: 24, height: 24, position: "absolute", inset: 0, filter: "brightness(0) invert(1)", opacity: fabHovered ? 0 : 1, transition: "opacity 0.15s" }} />
<img src="/bootstrap/chat-fill.svg" alt="" style={{ width: 24, height: 24, position: "absolute", inset: 0, filter: "brightness(0) invert(1)", opacity: fabHovered ? 1 : 0, transition: "opacity 0.15s" }} />
</span>
)}
{!isOpen && openConvCount > 0 && <span style={s.fabBadge}>{openConvCount}</span>} {!isOpen && openConvCount > 0 && <span style={s.fabBadge}>{openConvCount}</span>}
</button> </button>
@@ -77,7 +85,7 @@ const prevLiveLengthRef = useRef(0);
{/* Header */} {/* Header */}
<div style={s.header}> <div style={s.header}>
<div style={s.headerLeft}> <div style={s.headerLeft}>
<div style={s.headerAvatar}>🐾</div> <div style={s.headerAvatar}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%", filter: "brightness(0) invert(1)" }} /></div>
<div> <div>
<div style={s.headerTitle}>Leon's Assistant</div> <div style={s.headerTitle}>Leon's Assistant</div>
<div style={s.headerSub}> <div style={s.headerSub}>
@@ -99,7 +107,7 @@ const prevLiveLengthRef = useRef(0);
{/* Guest */} {/* Guest */}
{!user && ( {!user && (
<div style={s.guestBody}> <div style={s.guestBody}>
<span style={{ fontSize: "2.5rem" }}>🐾</span> <img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "2.5rem", height: "2.5rem", opacity: 0.6 }} />
<p style={{ color: "#555", fontSize: "0.95rem", margin: "0.75rem 0 1.25rem", textAlign: "center" }}> <p style={{ color: "#555", fontSize: "0.95rem", margin: "0.75rem 0 1.25rem", textAlign: "center" }}>
Log in to chat with our pet assistant! Log in to chat with our pet assistant!
</p> </p>
@@ -129,7 +137,7 @@ const prevLiveLengthRef = useRef(0);
{!convsLoading && conversations.length === 0 && ( {!convsLoading && conversations.length === 0 && (
<div style={{ ...s.empty, flex: 1 }}> <div style={{ ...s.empty, flex: 1 }}>
<span style={{ fontSize: "2rem" }}>💬</span> <img src="/bootstrap/chat.svg" alt="chat" style={{ width: 32, height: 32, opacity: 0.5 }} />
<p style={s.emptyText}>No conversations yet.<br />Start a live chat above.</p> <p style={s.emptyText}>No conversations yet.<br />Start a live chat above.</p>
</div> </div>
)} )}
@@ -223,7 +231,7 @@ const prevLiveLengthRef = useRef(0);
return ( return (
<div key={msg.id} style={{ ...s.row, ...(isUser ? s.rowUser : s.rowOther) }}> <div key={msg.id} style={{ ...s.row, ...(isUser ? s.rowUser : s.rowOther) }}>
{!isUser && ( {!isUser && (
<div style={s.otherAvatar}>{msg.senderRole === "BOT" ? "🐾" : "👤"}</div> <div style={s.otherAvatar}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%" }} /></div>
)} )}
<div style={{ ...s.bubble, ...(isUser ? s.bubbleUser : s.bubbleOther) }}> <div style={{ ...s.bubble, ...(isUser ? s.bubbleUser : s.bubbleOther) }}>
{!isUser && ( {!isUser && (
@@ -282,7 +290,7 @@ const prevLiveLengthRef = useRef(0);
<div style={s.messages}> <div style={s.messages}>
{aiMessages.length === 0 && ( {aiMessages.length === 0 && (
<div style={s.empty}> <div style={s.empty}>
<span style={{ fontSize: "2rem" }}>🐾</span> <img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "2rem", height: "2rem", opacity: 0.5 }} />
<p style={s.emptyText}> <p style={s.emptyText}>
Hi{user?.fullName ? `, ${user.fullName.split(" ")[0]}` : ""}!<br /> Hi{user?.fullName ? `, ${user.fullName.split(" ")[0]}` : ""}!<br />
Ask me anything about pets. Ask me anything about pets.
@@ -292,7 +300,7 @@ const prevLiveLengthRef = useRef(0);
{aiMessages.map((msg) => ( {aiMessages.map((msg) => (
<div key={msg.id} style={{ ...s.row, ...(msg.role === "user" ? s.rowUser : s.rowOther) }}> <div key={msg.id} style={{ ...s.row, ...(msg.role === "user" ? s.rowUser : s.rowOther) }}>
{msg.role === "assistant" && <div style={s.otherAvatar}>🐾</div>} {msg.role === "assistant" && <div style={s.otherAvatar}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%" }} /></div>}
<div style={{ ...s.bubble, ...(msg.role === "user" ? s.bubbleUser : s.bubbleOther) }}> <div style={{ ...s.bubble, ...(msg.role === "user" ? s.bubbleUser : s.bubbleOther) }}>
{msg.content.split("\n").map((line, i, arr) => ( {msg.content.split("\n").map((line, i, arr) => (
<span key={i}>{line}{i < arr.length - 1 && <br />}</span> <span key={i}>{line}{i < arr.length - 1 && <br />}</span>
@@ -308,7 +316,7 @@ const prevLiveLengthRef = useRef(0);
{aiSending && ( {aiSending && (
<div style={{ ...s.row, ...s.rowOther }}> <div style={{ ...s.row, ...s.rowOther }}>
<div style={s.otherAvatar}>🐾</div> <div style={s.otherAvatar}><img src="/bootstrap/person-circle.svg" alt="assistant" style={{ width: "100%", height: "100%" }} /></div>
<div style={{ ...s.bubble, ...s.bubbleOther, ...s.typingBubble }}> <div style={{ ...s.bubble, ...s.bubbleOther, ...s.typingBubble }}>
<span className="fc-dot" /> <span className="fc-dot" />
<span className="fc-dot" style={{ animationDelay: "0.2s" }} /> <span className="fc-dot" style={{ animationDelay: "0.2s" }} />

View File

@@ -14,8 +14,11 @@ const cartBadgeCls = "absolute -top-1 -right-1.5 bg-[#e53935] text-white rounded
function CartIcon({ itemCount, onClick }) { function CartIcon({ itemCount, onClick }) {
return ( return (
<Link href="/cart" className={cartBtnCls} aria-label="Cart" onClick={onClick}> <Link href="/cart" className={`${cartBtnCls} group`} aria-label="Cart" onClick={onClick}>
🛒 <span className="relative w-6 h-6 inline-block">
<img src="/bootstrap/cart.svg" alt="" className="w-6 h-6 absolute inset-0 transition-opacity duration-150 group-hover:opacity-0" />
<img src="/bootstrap/cart-fill.svg" alt="" className="w-6 h-6 absolute inset-0 transition-opacity duration-150 opacity-0 group-hover:opacity-100" />
</span>
{itemCount > 0 && ( {itemCount > 0 && (
<span className={cartBadgeCls}>{itemCount > 99 ? "99+" : itemCount}</span> <span className={cartBadgeCls}>{itemCount > 99 ? "99+" : itemCount}</span>
)} )}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cart-fill" viewBox="0 0 16 16">
<path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .491.592l-1.5 8A.5.5 0 0 1 13 12H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5M5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4m7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2m7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
</svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cart" viewBox="0 0 16 16">
<path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .491.592l-1.5 8A.5.5 0 0 1 13 12H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5M3.102 4l1.313 7h8.17l1.313-7zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4m7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2m7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
</svg>

After

Width:  |  Height:  |  Size: 465 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-fill" viewBox="0 0 16 16">
<path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9 9 0 0 0 8 15"/>
</svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat" viewBox="0 0 16 16">
<path d="M2.678 11.894a1 1 0 0 1 .287.801 11 11 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8 8 0 0 0 8 14c3.996 0 7-2.807 7-6s-3.004-6-7-6-7 2.808-7 6c0 1.468.617 2.83 1.678 3.894m-.493 3.905a22 22 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a10 10 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9 9 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105"/>
</svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
</svg>

After

Width:  |  Height:  |  Size: 222 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-robot" viewBox="0 0 16 16">
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135"/>
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5"/>
</svg>

After

Width:  |  Height:  |  Size: 996 B