feat: improve chat UI with scroll memory, disabled clear button, and styled membership gifts

This commit is contained in:
Yusuf İpek
2025-10-05 23:51:56 +03:00
parent e8f42a5676
commit 7d502c26ea
3 changed files with 121 additions and 21 deletions
+25 -10
View File
@@ -107,11 +107,13 @@ export default function DashboardPage() {
<div className="panel panel--chat"> <div className="panel panel--chat">
<div className="panel__header"> <div className="panel__header">
<h2>💬 CHAT MESSAGES</h2> <h2>💬 CHAT MESSAGES</h2>
{selection && ( <button
<button className="btn-clear-inline" onClick={handleClear}> className="btn-clear-inline"
CLEAR onClick={handleClear}
</button> disabled={!selection}
)} >
CLEAR
</button>
</div> </div>
<ChatListPanel <ChatListPanel
messages={regularMessages} messages={regularMessages}
@@ -393,15 +395,28 @@ type ChatListPanelProps = {
function ChatListPanel({ messages, renderItem, emptyState }: ChatListPanelProps) { function ChatListPanel({ messages, renderItem, emptyState }: ChatListPanelProps) {
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const wasAtBottomRef = useRef(true);
useEffect(() => { useEffect(() => {
const node = scrollRef.current; const node = scrollRef.current;
if (node) { if (!node) return;
// Check if user is near the bottom before scrolling
const isAtBottom = node.scrollHeight - node.scrollTop - node.clientHeight <= 100; const handleScroll = () => {
if (isAtBottom) { const isAtBottom = node.scrollHeight - node.scrollTop - node.clientHeight <= 150;
wasAtBottomRef.current = isAtBottom;
};
node.addEventListener('scroll', handleScroll);
return () => node.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
const node = scrollRef.current;
if (node && wasAtBottomRef.current) {
// Use requestAnimationFrame to ensure DOM has updated
requestAnimationFrame(() => {
node.scrollTop = node.scrollHeight; node.scrollTop = node.scrollHeight;
} });
} }
}, [messages]); }, [messages]);
+76 -2
View File
@@ -502,11 +502,18 @@ main {
margin-left: auto; margin-left: auto;
} }
.btn-clear-inline:hover { .btn-clear-inline:hover:not(:disabled) {
background: rgba(239, 68, 68, 0.25); background: rgba(239, 68, 68, 0.25);
transform: translateY(-1px); transform: translateY(-1px);
} }
.btn-clear-inline:disabled {
opacity: 0.4;
cursor: not-allowed;
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.15);
}
.btn-clear-fixed { .btn-clear-fixed {
position: fixed; position: fixed;
bottom: 2rem; bottom: 2rem;
@@ -565,8 +572,11 @@ main {
overflow-x: hidden; overflow-x: hidden;
padding-right: 0.5rem; /* Add space for scrollbar */ padding-right: 0.5rem; /* Add space for scrollbar */
margin-right: -0.5rem; /* Pull scrollbar into padding */ margin-right: -0.5rem; /* Pull scrollbar into padding */
padding-bottom: 1rem; /* Add space at the bottom */ padding-bottom: 2rem; /* Add more space at the bottom */
min-height: 0; min-height: 0;
width: 100%;
max-width: 100%;
box-sizing: border-box;
} }
.chatList::-webkit-scrollbar { .chatList::-webkit-scrollbar {
@@ -607,6 +617,9 @@ main {
transition: all 150ms ease; transition: all 150ms ease;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
width: 100%;
max-width: 100%;
box-sizing: border-box;
} }
.chatItem:hover { .chatItem:hover {
@@ -640,6 +653,8 @@ main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.25rem; gap: 0.25rem;
min-width: 0;
overflow: hidden;
} }
.chatItem__authorLine { .chatItem__authorLine {
@@ -647,6 +662,8 @@ main {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
min-width: 0;
max-width: 100%;
} }
.chatItem__author { .chatItem__author {
@@ -695,6 +712,8 @@ main {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
margin-left: 0.5rem; margin-left: 0.5rem;
display: inline-block; display: inline-block;
flex-shrink: 0;
white-space: nowrap;
} }
.chatItem__membership { .chatItem__membership {
@@ -1006,6 +1025,61 @@ main {
background: linear-gradient(135deg, #10b981, #059669); background: linear-gradient(135deg, #10b981, #059669);
} }
.overlay__membership-header {
padding: 1rem 1.5rem;
border-radius: 12px 12px 0 0;
background: linear-gradient(135deg, #10b981, #059669);
color: #fff;
display: flex;
align-items: center;
gap: 0.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
.overlay__membership-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.overlay__membership-info {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.overlay__membership-name {
font-size: 1.6rem;
font-weight: 700;
}
.overlay__membership-separator {
font-size: 1.4rem;
font-weight: 600;
opacity: 0.9;
}
.overlay__membership-level {
font-size: 1.6rem;
font-weight: 700;
}
.overlay__membership-text {
padding: 1rem 1.5rem;
font-size: 1.1rem;
line-height: 1.6;
color: #e5e7eb;
font-weight: 400;
text-align: left;
word-break: break-word;
background: rgba(0, 0, 0, 0.3);
border-radius: 0 0 12px 12px;
margin: 0;
}
.overlay__text { .overlay__text {
font-size: 1.1rem; font-size: 1.1rem;
line-height: 1.5; line-height: 1.5;
+20 -9
View File
@@ -83,6 +83,26 @@ export default function OverlayPage() {
<p className="overlay__superchat-text">{message.text}</p> <p className="overlay__superchat-text">{message.text}</p>
)} )}
</> </>
) : (message.membershipGift || message.membershipGiftPurchase) ? (
<>
<div className="overlay__membership-header">
{message.authorPhoto && (
<img src={message.authorPhoto} alt={message.author} className="overlay__membership-avatar" />
)}
<div className="overlay__membership-info">
<span className="overlay__membership-name">{message.author}</span>
<span className="overlay__membership-separator"> - </span>
<span className="overlay__membership-level">
{message.membershipGiftPurchase && message.giftCount
? `Sent ${message.giftCount} Gift Membership${message.giftCount > 1 ? 's' : ''}`
: message.membershipLevel || 'New Member'}
</span>
</div>
</div>
{message.text && (
<p className="overlay__membership-text">{message.text}</p>
)}
</>
) : ( ) : (
<> <>
<div className="overlay__header"> <div className="overlay__header">
@@ -102,15 +122,6 @@ export default function OverlayPage() {
</div> </div>
</div> </div>
</div> </div>
{(message.membershipGift || message.membershipGiftPurchase) && (
<div className="overlay__membership">
{message.membershipGiftPurchase && message.giftCount
? `🎁 Sent ${message.giftCount} Gift Membership${message.giftCount > 1 ? 's' : ''}!`
: message.membershipGiftPurchase
? '🎁 Gift Purchase'
: '🎁 New Member!'}
</div>
)}
{message.text && ( {message.text && (
<p className="overlay__text">{message.text}</p> <p className="overlay__text">{message.text}</p>
)} )}