283 lines
10 KiB
JavaScript
283 lines
10 KiB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import MessageActions from './MessageActions';
|
|
import TypingIndicator from './TypingIndicator';
|
|
import EmojiPicker from './EmojiPicker';
|
|
|
|
const initialMessages = [
|
|
{
|
|
type: 'system',
|
|
division: 'foundation',
|
|
label: '[FOUNDATION] System Announcement',
|
|
text: 'Foundation authentication services upgraded to v2.1.0. Enhanced security protocols now active across all AeThex infrastructure.',
|
|
},
|
|
{
|
|
type: 'message',
|
|
id: 1,
|
|
author: 'Trevor',
|
|
badge: 'foundation',
|
|
badgeLabel: 'Foundation',
|
|
time: '10:34 AM',
|
|
avatar: { initial: 'T', gradient: 'linear-gradient(135deg, #ff0000, #cc0000)' },
|
|
text: 'Just pushed the authentication updates. All services should automatically migrate to the new protocols within 24 hours.',
|
|
reactions: [{ emoji: '🔥', count: 3, reacted: true }, { emoji: '👍', count: 2, reacted: false }],
|
|
},
|
|
{
|
|
type: 'message',
|
|
id: 2,
|
|
author: 'Marcus',
|
|
time: '10:41 AM',
|
|
avatar: { initial: 'M', gradient: 'linear-gradient(135deg, #0066ff, #003380)' },
|
|
text: "Excellent work! I've been testing the new Passport integration and it's incredibly smooth. The Trinity color-coding in the UI makes it really clear which division is handling what.",
|
|
},
|
|
{
|
|
type: 'system',
|
|
division: 'labs',
|
|
label: '[LABS] Experimental Feature Alert',
|
|
text: 'Nexus Engine v2.0-beta now available for testing. New cross-platform sync reduces latency by 40%. Join #labs-testing to participate.',
|
|
},
|
|
{
|
|
type: 'message',
|
|
id: 3,
|
|
author: 'Sarah',
|
|
badge: 'labs',
|
|
badgeLabel: 'Labs',
|
|
time: '11:15 AM',
|
|
avatar: { initial: 'S', gradient: 'linear-gradient(135deg, #ffa500, #ff8c00)' },
|
|
text: 'The Nexus v2 parallel compilation is insane. Cut my build time from 3 minutes to under 2. Still some edge cases with complex state synchronization but wow...',
|
|
reactions: [{ emoji: '🚀', count: 5, reacted: true }],
|
|
},
|
|
{
|
|
type: 'message',
|
|
id: 4,
|
|
author: 'Anderson',
|
|
badge: 'foundation',
|
|
badgeLabel: 'Founder',
|
|
time: '11:47 AM',
|
|
avatar: { initial: 'A', gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)' },
|
|
text: 'Love seeing the Trinity infrastructure working in harmony. Foundation keeping everything secure, Labs pushing the boundaries, Corporation delivering production-ready tools. This is exactly the vision.',
|
|
reactions: [{ emoji: '❤️', count: 8, reacted: false }, { emoji: '🔥', count: 4, reacted: true }],
|
|
},
|
|
{
|
|
type: 'message',
|
|
id: 5,
|
|
author: 'DevUser_2847',
|
|
time: '12:03 PM',
|
|
avatar: { initial: 'D' },
|
|
text: 'Quick question - when using AeThex Studio, does the Terminal automatically connect to all three Trinity divisions, or do I need to configure that?',
|
|
},
|
|
{
|
|
type: 'system',
|
|
division: 'corporation',
|
|
label: '[CORPORATION] Service Update',
|
|
text: 'AeThex Studio Pro users: New Railway deployment templates available. Optimized configurations for Foundation APIs, Corporation services, and Labs experiments.',
|
|
},
|
|
];
|
|
|
|
export default function ChatArea({ onOpenSearch, onOpenThread, onPinnedClick, onNotificationsClick, onContextMenu }) {
|
|
const [messages, setMessages] = useState(initialMessages);
|
|
const [inputValue, setInputValue] = useState('');
|
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
const [hoveredMessage, setHoveredMessage] = useState(null);
|
|
const [typingUsers] = useState(['Sarah', 'Marcus']); // Simulated typing
|
|
const messagesEndRef = useRef(null);
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
};
|
|
|
|
useEffect(() => {
|
|
scrollToBottom();
|
|
}, [messages]);
|
|
|
|
const handleSend = () => {
|
|
if (!inputValue.trim()) return;
|
|
|
|
const newMessage = {
|
|
type: 'message',
|
|
id: Date.now(),
|
|
author: 'You',
|
|
time: new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }),
|
|
avatar: { initial: 'Y', gradient: 'linear-gradient(135deg, #0066ff, #003380)' },
|
|
text: inputValue,
|
|
};
|
|
|
|
setMessages([...messages, newMessage]);
|
|
setInputValue('');
|
|
};
|
|
|
|
const handleKeyPress = (e) => {
|
|
if (e.key === 'Enter') {
|
|
handleSend();
|
|
}
|
|
};
|
|
|
|
const handleEmojiSelect = (emoji) => {
|
|
setInputValue((prev) => prev + emoji);
|
|
setShowEmojiPicker(false);
|
|
};
|
|
|
|
const handleReaction = (messageId, emoji) => {
|
|
setMessages((prev) =>
|
|
prev.map((msg) => {
|
|
if (msg.id !== messageId) return msg;
|
|
const reactions = msg.reactions || [];
|
|
const existing = reactions.find((r) => r.emoji === emoji);
|
|
if (existing) {
|
|
return {
|
|
...msg,
|
|
reactions: reactions.map((r) =>
|
|
r.emoji === emoji
|
|
? { ...r, count: r.reacted ? r.count - 1 : r.count + 1, reacted: !r.reacted }
|
|
: r
|
|
).filter((r) => r.count > 0),
|
|
};
|
|
}
|
|
return {
|
|
...msg,
|
|
reactions: [...reactions, { emoji, count: 1, reacted: true }],
|
|
};
|
|
})
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="chat-area">
|
|
<div className="chat-header">
|
|
<span className="channel-name-header"># general</span>
|
|
<div className="chat-tools">
|
|
<span
|
|
className="chat-tool"
|
|
onClick={onNotificationsClick}
|
|
style={{ cursor: 'pointer' }}
|
|
title="Notifications"
|
|
>
|
|
🔔
|
|
</span>
|
|
<span
|
|
className="chat-tool"
|
|
onClick={onPinnedClick}
|
|
style={{ cursor: 'pointer' }}
|
|
title="Pinned Messages"
|
|
>
|
|
📌
|
|
</span>
|
|
<span className="chat-tool">👥 128</span>
|
|
<span className="chat-tool" onClick={onOpenSearch} style={{ cursor: 'pointer' }} title="Search">🔍</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="chat-messages">
|
|
{messages.map((msg, idx) => {
|
|
if (msg.type === 'system') {
|
|
return (
|
|
<div key={`system-${idx}`} className={`message-system ${msg.division}`}>
|
|
<div className={`system-label ${msg.division}`}>{msg.label}</div>
|
|
<div>{msg.text}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={msg.id}
|
|
className="message"
|
|
onMouseEnter={() => setHoveredMessage(msg.id)}
|
|
onMouseLeave={() => setHoveredMessage(null)}
|
|
>
|
|
<div
|
|
className="message-avatar"
|
|
style={msg.avatar.gradient ? { background: msg.avatar.gradient } : undefined}
|
|
>
|
|
{msg.avatar.initial}
|
|
</div>
|
|
<div className="message-content">
|
|
<div className="message-header">
|
|
<span className="message-author">{msg.author}</span>
|
|
{msg.badge && (
|
|
<span className={`message-badge ${msg.badge}`}>{msg.badgeLabel}</span>
|
|
)}
|
|
<span className="message-time">{msg.time}</span>
|
|
</div>
|
|
<div className="message-text">{msg.text}</div>
|
|
|
|
{/* Reactions */}
|
|
{msg.reactions && msg.reactions.length > 0 && (
|
|
<div className="message-reactions" style={{ display: 'flex', gap: '6px', marginTop: '6px' }}>
|
|
{msg.reactions.map((r) => (
|
|
<button
|
|
key={r.emoji}
|
|
onClick={() => handleReaction(msg.id, r.emoji)}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '4px',
|
|
padding: '2px 8px',
|
|
background: r.reacted ? 'rgba(88, 101, 242, 0.3)' : '#2f3136',
|
|
border: r.reacted ? '1px solid #5865f2' : '1px solid transparent',
|
|
borderRadius: '4px',
|
|
cursor: 'pointer',
|
|
fontSize: '0.85em',
|
|
}}
|
|
>
|
|
<span>{r.emoji}</span>
|
|
<span style={{ color: r.reacted ? '#5865f2' : '#b9bbbe' }}>{r.count}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Message Actions on Hover */}
|
|
{hoveredMessage === msg.id && (
|
|
<MessageActions
|
|
onReact={(emoji) => handleReaction(msg.id, emoji)}
|
|
onReply={() => onOpenThread && onOpenThread(msg)}
|
|
onThread={() => onOpenThread && onOpenThread(msg)}
|
|
isOwn={msg.author === 'You'}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
|
|
{/* Typing Indicator */}
|
|
{typingUsers.length > 0 && <TypingIndicator users={typingUsers} />}
|
|
|
|
<div className="message-input-container" style={{ position: 'relative' }}>
|
|
<button
|
|
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
|
style={{
|
|
position: 'absolute',
|
|
right: '12px',
|
|
top: '50%',
|
|
transform: 'translateY(-50%)',
|
|
background: 'none',
|
|
border: 'none',
|
|
cursor: 'pointer',
|
|
fontSize: '1.2em',
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
😊
|
|
</button>
|
|
<input
|
|
type="text"
|
|
className="message-input"
|
|
placeholder="Message #general (Foundation infrastructure channel)"
|
|
value={inputValue}
|
|
onChange={(e) => setInputValue(e.target.value)}
|
|
onKeyPress={handleKeyPress}
|
|
style={{ paddingRight: '48px' }}
|
|
/>
|
|
{showEmojiPicker && (
|
|
<EmojiPicker
|
|
onSelect={handleEmojiSelect}
|
|
onClose={() => setShowEmojiPicker(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|