feat: Implement core chat functionality, shared components, and initial multi-platform project structure.
All checks were successful
Build and Release / build-and-release (push) Successful in 15m0s

This commit is contained in:
Bryan1029384756
2026-02-20 13:05:39 -06:00
parent a64ef84771
commit 5feb3f288d
10 changed files with 445 additions and 13 deletions

View File

@@ -1,10 +1,14 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useQuery } from 'convex/react';
import { api } from '../../../../convex/_generated/api';
import { useOnlineUsers } from '../contexts/PresenceContext';
import Tooltip from './Tooltip';
const ChatHeader = ({
channelName,
channelType,
channelTopic,
channelId,
onToggleMembers,
showMembers,
onTogglePinned,
@@ -13,6 +17,7 @@ const ChatHeader = ({
onMobileBack,
onStartCall,
isDMCallActive,
onOpenMembersScreen,
// Search props
searchQuery,
onSearchQueryChange,
@@ -25,6 +30,18 @@ const ChatHeader = ({
const isDM = channelType === 'dm';
const searchPlaceholder = isDM ? 'Search' : `Search ${serverName || 'Server'}`;
// Query members on mobile text channels only for online count
const shouldQueryMembers = isMobile && !isDM && channelId;
const members = useQuery(
api.members.getChannelMembers,
shouldQueryMembers ? { channelId } : "skip"
) || [];
const { resolveStatus } = useOnlineUsers();
const onlineCount = useMemo(() => {
if (!shouldQueryMembers) return 0;
return members.filter(m => resolveStatus(m.status, m.id) !== 'offline').length;
}, [members, resolveStatus, shouldQueryMembers]);
const handleSearchKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
@@ -45,15 +62,33 @@ const ChatHeader = ({
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button>
)}
<span className="chat-header-icon">{isDM ? '@' : '#'}</span>
<span className="chat-header-name">{channelName}</span>
{channelTopic && !isDM && !isMobile && (
{isMobile && !isDM ? (
<button className="mobile-channel-header-tap" onClick={onOpenMembersScreen}>
<div className="mobile-channel-header-top">
<span className="chat-header-icon">#</span>
<span className="chat-header-name">{channelName}</span>
<svg className="mobile-channel-chevron" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M9.29 6.71a1 1 0 0 0 0 1.41L13.17 12l-3.88 3.88a1 1 0 1 0 1.42 1.41l4.59-4.59a1 1 0 0 0 0-1.41L10.71 6.7a1 1 0 0 0-1.42 0Z"/>
</svg>
</div>
<div className="mobile-channel-header-bottom">
<span className="mobile-online-dot" />
<span className="mobile-online-count">{onlineCount ?? 0} Online</span>
</div>
</button>
) : (
<>
<div className="chat-header-divider" />
<span className="chat-header-topic" title={channelTopic}>{channelTopic}</span>
<span className="chat-header-icon">{isDM ? '@' : '#'}</span>
<span className="chat-header-name">{channelName}</span>
{channelTopic && !isDM && !isMobile && (
<>
<div className="chat-header-divider" />
<span className="chat-header-topic" title={channelTopic}>{channelTopic}</span>
</>
)}
{isDM && <span className="chat-header-status-text"></span>}
</>
)}
{isDM && <span className="chat-header-status-text"></span>}
</div>
<div className="chat-header-right">
{!isDM && !isMobile && (