Improved chat UX/UI:

- Fixed message auto-scroll bug when receiving new messages
- Adjusted bottom chat section integration with proper styles
- Updated bottom chat section layout and appearance
- Hidden scrollbars for better interaction and cleaner look
This commit is contained in:
lockbitchat
2025-08-20 03:53:58 -04:00
parent ebcddca40d
commit 773215264f
2 changed files with 368 additions and 248 deletions

View File

@@ -2309,7 +2309,6 @@
}
};
// Enhanced Chat Interface with better security indicators
const EnhancedChatInterface = ({
messages,
messageInput,
@@ -2321,16 +2320,17 @@
chatMessagesRef,
scrollToBottom,
webrtcManager
}) => {
}) => {
const [showScrollButton, setShowScrollButton] = React.useState(false);
const [showFileTransfer, setShowFileTransfer] = React.useState(false);
// Автоматическая прокрутка при изменении сообщений
React.useEffect(() => {
if (chatMessagesRef.current && messages.length > 0) {
const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current;
const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
if (isNearBottom) {
const scrollToBottom = () => {
const smoothScroll = () => {
if (chatMessagesRef.current) {
chatMessagesRef.current.scrollTo({
top: chatMessagesRef.current.scrollHeight,
@@ -2338,15 +2338,14 @@
});
}
};
scrollToBottom();
setTimeout(scrollToBottom, 50);
setTimeout(scrollToBottom, 150);
smoothScroll();
setTimeout(smoothScroll, 50);
setTimeout(smoothScroll, 150);
}
}
}, [messages]);
}, [messages, chatMessagesRef]);
// Scroll handler to show the button
// Обработчик скролла
const handleScroll = () => {
if (chatMessagesRef.current) {
const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current;
@@ -2355,12 +2354,13 @@
}
};
// Wrapper for scrollToBottom with button state update
// Прокрутка вниз по кнопке
const handleScrollToBottom = () => {
scrollToBottom();
setShowScrollButton(false);
};
// Обработчик нажатия Enter
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
@@ -2368,87 +2368,99 @@
}
};
return React.createElement('div', {
className: "chat-container"
}, [
// Chat Messages Area
React.createElement('div', {
key: 'chat-body',
className: "chat-messages-area max-w-4xl mx-auto w-full p-4 relative"
}, [
React.createElement('div', {
key: 'messages-container',
// ИСПРАВЛЕНИЕ: Проверка готовности для файловых трансферов
const isFileTransferReady = () => {
if (!webrtcManager) return false;
const connected = webrtcManager.isConnected ? webrtcManager.isConnected() : false;
const verified = webrtcManager.isVerified || false;
const hasDataChannel = webrtcManager.dataChannel && webrtcManager.dataChannel.readyState === 'open';
return connected && verified && hasDataChannel;
};
// Возврат JSX через React.createElement
return React.createElement(
'div',
{
className: "chat-container flex flex-col",
style: { backgroundColor: '#272827', height: 'calc(100vh - 64px)' }
},
[
// Область сообщений
React.createElement(
'div',
{ className: "flex-1 flex flex-col overflow-hidden" },
React.createElement(
'div',
{ className: "flex-1 max-w-4xl mx-auto w-full p-4 overflow-hidden" },
React.createElement(
'div',
{
ref: chatMessagesRef,
onScroll: handleScroll,
className: "h-full overflow-y-auto space-y-3 custom-scrollbar pr-2 scroll-smooth"
className: "h-full overflow-y-auto space-y-3 hide-scrollbar pr-2 scroll-smooth"
},
messages.length === 0 ?
React.createElement('div', {
key: 'empty-state',
className: "flex items-center justify-center h-full"
}, [
React.createElement('div', {
className: "text-center max-w-md"
}, [
React.createElement('div', {
key: 'icon',
className: "w-16 h-16 bg-green-500/10 border border-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4"
}, [
React.createElement('i', {
className: 'fas fa-comments text-2xl accent-green'
React.createElement(
'div',
{ className: "flex items-center justify-center h-full" },
React.createElement(
'div',
{ className: "text-center max-w-md" },
[
React.createElement(
'div',
{ className: "w-16 h-16 bg-green-500/10 border border-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4" },
React.createElement(
'svg',
{ className: "w-8 h-8 text-green-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
})
]),
React.createElement('h3', {
key: 'title',
className: "text-lg font-medium text-primary mb-2"
}, "Secure channel is ready!"),
React.createElement('p', {
key: 'description',
className: "text-secondary text-sm mb-4"
}, "All messages are protected by modern cryptographic algorithms"),
React.createElement('div', {
key: 'features',
className: "text-left space-y-2"
}, [
React.createElement('div', {
key: 'f1',
className: "flex items-center text-sm text-muted"
}, [
React.createElement('i', {
className: 'fas fa-check accent-green mr-3'
}),
'End-to-end encryption'
]),
React.createElement('div', {
key: 'f2',
className: "flex items-center text-sm text-muted"
}, [
React.createElement('i', {
className: 'fas fa-check accent-green mr-3'
}),
'Protection against replay attacks'
]),
React.createElement('div', {
key: 'f3',
className: "flex items-center text-sm text-muted"
}, [
React.createElement('i', {
className: 'fas fa-check accent-green mr-3'
}),
'Integrity verification'
]),
React.createElement('div', {
key: 'f4',
className: "flex items-center text-sm text-muted"
}, [
React.createElement('i', {
className: 'fas fa-sync-alt accent-purple mr-3'
}),
'Perfect Forward Secrecy'
])
])
])
]) :
)
),
React.createElement('h3', { className: "text-lg font-medium text-gray-300 mb-2" }, "Secure channel is ready!"),
React.createElement('p', { className: "text-gray-400 text-sm mb-4" }, "All messages are protected by modern cryptographic algorithms"),
React.createElement(
'div',
{ className: "text-left space-y-2" },
[
['End-to-end encryption', 'M5 13l4 4L19 7'],
['Protection against replay attacks', 'M5 13l4 4L19 7'],
['Integrity verification', 'M5 13l4 4L19 7'],
['Perfect Forward Secrecy', 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15']
].map(([text, d], i) =>
React.createElement(
'div',
{ key: `f${i}`, className: "flex items-center text-sm text-gray-400" },
[
React.createElement(
'svg',
{
className: `w-4 h-4 mr-3 ${i === 3 ? 'text-purple-500' : 'text-green-500'}`,
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24"
},
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: d
})
),
text
]
)
)
)
]
)
) :
messages.map((msg) =>
React.createElement(EnhancedChatMessage, {
key: msg.id,
@@ -2458,95 +2470,148 @@
})
)
)
]),
)
),
// Scroll down button
showScrollButton && React.createElement('button', {
key: 'scroll-down-btn',
// Кнопка прокрутки вниз
showScrollButton &&
React.createElement(
'button',
{
onClick: handleScrollToBottom,
className: "absolute bottom-4 right-4 w-10 h-10 bg-gray-600/80 hover:bg-gray-500/80 text-white rounded-full flex items-center justify-center transition-all duration-200 shadow-lg",
style: { zIndex: 10 }
}, [
React.createElement('i', {
className: 'fas fa-chevron-down'
className: "fixed right-6 w-12 h-12 bg-green-500/20 hover:bg-green-500/30 border border-green-500/30 text-green-400 rounded-full flex items-center justify-center transition-all duration-200 shadow-lg z-50",
style: { bottom: '160px' }
},
React.createElement(
'svg',
{ className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M19 14l-7 7m0 0l-7-7m7 7V3"
})
]),
React.createElement('div', {
key: 'file-section',
className: "file-transfer-section max-w-4xl mx-auto p-4 border-t border-gray-500/10"
}, [
React.createElement('button', {
key: 'toggle-files',
onClick: () => setShowFileTransfer(!showFileTransfer),
className: `flex items-center text-sm text-secondary hover:text-primary transition-colors ${showFileTransfer ? 'mb-4' : ''}`
}, [
React.createElement('i', {
key: 'icon',
className: `fas fa-${showFileTransfer ? 'chevron-up' : 'paperclip'} mr-2`
}),
showFileTransfer ? 'Hide file transfer' : 'Send files'
]),
)
),
showFileTransfer && React.createElement(FileTransferComponent, {
key: 'file-transfer',
webrtcManager: webrtcManager,
isConnected: isVerified
// Секция передачи файлов
React.createElement(
'div',
{
className: "flex-shrink-0 border-t border-gray-500/10",
style: { backgroundColor: '#272827' }
},
React.createElement(
'div',
{ className: "max-w-4xl mx-auto px-4" },
[
React.createElement(
'button',
{
onClick: () => setShowFileTransfer(!showFileTransfer),
className: `flex items-center text-sm text-gray-400 hover:text-gray-300 transition-colors py-4 ${showFileTransfer ? 'mb-4' : ''}`
},
[
React.createElement(
'svg',
{
className: `w-4 h-4 mr-2 transform transition-transform ${showFileTransfer ? 'rotate-180' : ''}`,
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24"
},
showFileTransfer ?
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M5 15l7-7 7 7"
}) :
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
})
]),
// Enhanced Chat Input Area
),
showFileTransfer ? 'Hide file transfer' : 'Send files'
]
),
// ИСПРАВЛЕНИЕ: Используем правильный компонент
showFileTransfer &&
React.createElement(window.FileTransferComponent || (() =>
React.createElement('div', {
key: 'chat-input',
className: "chat-input-area border-t border-gray-500/10"
}, [
React.createElement('div', {
key: 'input-container',
className: "max-w-4xl mx-auto p-4"
}, [
React.createElement('div', {
key: 'input-wrapper',
className: "flex items-end space-x-3"
}, [
React.createElement('div', {
key: 'text-area-wrapper',
className: "flex-1 relative"
}, [
className: "p-4 text-center text-red-400"
}, 'FileTransferComponent not loaded')
), {
webrtcManager: webrtcManager,
isConnected: isFileTransferReady()
})
]
)
),
// Область ввода сообщений
React.createElement(
'div',
{ className: "border-t border-gray-500/10" },
React.createElement(
'div',
{ className: "max-w-4xl mx-auto p-4" },
React.createElement(
'div',
{ className: "flex items-stretch space-x-3" },
[
React.createElement(
'div',
{ className: "flex-1 relative" },
[
React.createElement('textarea', {
key: 'input',
value: messageInput,
onChange: (e) => setMessageInput(e.target.value),
onKeyDown: handleKeyPress,
placeholder: "Enter message to encrypt...",
rows: 2,
maxLength: 2000,
className: "w-full p-3 bg-gray-900/30 border border-gray-500/20 rounded-lg resize-none text-primary placeholder-gray-500 focus:border-green-500/40 focus:outline-none transition-all custom-scrollbar text-sm"
style: { backgroundColor: '#272827' },
className: "w-full p-3 border border-gray-600 rounded-lg resize-none text-gray-300 placeholder-gray-500 focus:border-green-500/40 focus:outline-none transition-all custom-scrollbar text-sm"
}),
React.createElement('div', {
key: 'input-info',
className: "absolute bottom-2 right-3 flex items-center space-x-2 text-xs text-muted"
}, [
React.createElement('span', {
key: 'counter'
}, `${messageInput.length}/2000`),
React.createElement('span', {
key: 'hint'
}, "• Enter to send")
])
]),
React.createElement('button', {
key: 'send',
React.createElement(
'div',
{ className: "absolute bottom-2 right-3 flex items-center space-x-2 text-xs text-gray-400" },
[
React.createElement('span', null, `${messageInput.length}/2000`),
React.createElement('span', null, "• Enter to send")
]
)
]
),
React.createElement(
'button',
{
onClick: onSendMessage,
disabled: !messageInput.trim(),
className: "security-shield text-white p-3 rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
}, [
React.createElement('i', {
className: 'fas fa-paper-plane'
className: "bg-green-400/20 text-green-400 p-3 rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center min-h-[72px]"
},
React.createElement(
'svg',
{ className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
React.createElement('path', {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
})
])
])
])
])
]);
};
)
)
]
)
)
)
]
);
};
// Main Enhanced Application Component
const EnhancedSecureP2PChat = () => {
@@ -2670,7 +2735,7 @@
// Safe because it's a ref object and we maintain it centrally here
window.webrtcManagerRef = webrtcManagerRef;
const addMessageWithAutoScroll = (message, type) => {
const addMessageWithAutoScroll = React.useCallback((message, type) => {
const newMessage = {
message,
type,
@@ -2678,17 +2743,36 @@
timestamp: Date.now()
};
setMessages(prev => [...prev, newMessage]);
setMessages(prev => {
const updated = [...prev, newMessage];
setTimeout(() => {
if (chatMessagesRef.current) {
chatMessagesRef.current.scrollTo({
top: chatMessagesRef.current.scrollHeight,
if (chatMessagesRef?.current) {
const container = chatMessagesRef.current;
try {
const { scrollTop, scrollHeight, clientHeight } = container;
const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
if (isNearBottom || prev.length === 0) {
requestAnimationFrame(() => {
if (container && container.scrollTo) {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
}
}, 100);
};
});
}
} catch (error) {
console.warn('Scroll error:', error);
container.scrollTop = container.scrollHeight;
}
}
}, 50);
return updated;
});
}, []);
// Update security level based on real verification
const updateSecurityLevel = React.useCallback(async () => {
@@ -3617,7 +3701,6 @@
keyFingerprint: keyFingerprint,
isVerified: isVerified,
chatMessagesRef: chatMessagesRef,
scrollToBottom: scrollToBottom,
webrtcManager: webrtcManagerRef.current
})
: React.createElement(EnhancedConnectionSetup, {

View File

@@ -5,6 +5,15 @@
flex-direction: column;
}
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* ============================================ */
/* FILE TRANSFER STYLES */
/* ============================================ */
@@ -13,6 +22,31 @@
margin-top: 1rem;
}
@media (max-width: 768px) {
/* Используем dvh для динамической высоты на мобильных */
.mobile-chat-height {
height: calc(100dvh - 64px) !important;
}
/* Кнопка прокрутки на мобильном */
.scroll-to-bottom-mobile {
bottom: 140px !important;
right: 16px !important;
width: 40px !important;
height: 40px !important;
}
}
@media (max-width: 480px) {
.header-minimal {
height: 56px;
}
.mobile-chat-height {
height: calc(100dvh - 56px) !important;
}
}
.file-drop-zone {
border: 2px dashed #4b5563;
border-radius: 12px;
@@ -96,6 +130,9 @@
-webkit-backdrop-filter: blur(5px);
flex-shrink: 0;
height: 64px;
position: sticky;
top: 0;
z-index: 40;
}
.header-minimal .cursor-pointer:hover {
@@ -108,11 +145,11 @@
/* The main chat container takes up the rest of the height. */
.chat-container {
display: flex;
/* display: flex; */
flex-direction: column;
height: calc(100vh - 64px); /* 64px - header height */
min-height: 0;
flex: 1;
/* flex: 1; */
}
.chat-messages-area {