feat: Add initial frontend components and their corresponding build assets, along with generated API types and configuration.
Some checks failed
Build and Release / build-and-release (push) Failing after 7m50s
Some checks failed
Build and Release / build-and-release (push) Failing after 7m50s
This commit is contained in:
@@ -187,7 +187,7 @@ const UserControlPanel = ({ username, userId }) => {
|
||||
)}
|
||||
<div className="user-control-info" onClick={() => setShowStatusMenu(!showStatusMenu)}>
|
||||
<div style={{ position: 'relative', marginRight: '8px' }}>
|
||||
<Avatar username={username} size={32} />
|
||||
<Avatar username={username} avatarUrl={myUser?.avatarUrl} size={32} />
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '-2px',
|
||||
@@ -235,15 +235,6 @@ const UserControlPanel = ({ username, userId }) => {
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Log Out" position="top">
|
||||
<button style={controlButtonStyle} onClick={handleLogout}>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M16 17L21 12L16 7" stroke={ICON_COLOR_DEFAULT} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M21 12H9" stroke={ICON_COLOR_DEFAULT} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M9 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H9" stroke={ICON_COLOR_DEFAULT} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{showUserSettings && (
|
||||
<UserSettings
|
||||
@@ -356,6 +347,32 @@ const Sidebar = ({ channels, activeChannel, onSelectChannel, username, channelKe
|
||||
|
||||
const convex = useConvex();
|
||||
|
||||
// Unread tracking
|
||||
const channelIds = React.useMemo(() => channels.map(c => c._id), [channels]);
|
||||
const allReadStates = useQuery(
|
||||
api.readState.getAllReadStates,
|
||||
userId ? { userId } : "skip"
|
||||
) || [];
|
||||
const latestTimestamps = useQuery(
|
||||
api.readState.getLatestMessageTimestamps,
|
||||
channelIds.length > 0 ? { channelIds } : "skip"
|
||||
) || [];
|
||||
|
||||
const unreadChannels = React.useMemo(() => {
|
||||
const set = new Set();
|
||||
const readMap = new Map();
|
||||
for (const rs of allReadStates) {
|
||||
readMap.set(rs.channelId, rs.lastReadTimestamp);
|
||||
}
|
||||
for (const lt of latestTimestamps) {
|
||||
const lastRead = readMap.get(lt.channelId);
|
||||
if (lastRead === undefined || lt.latestTimestamp > lastRead) {
|
||||
set.add(lt.channelId);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}, [allReadStates, latestTimestamps]);
|
||||
|
||||
const onRenameChannel = () => {};
|
||||
|
||||
const onDeleteChannel = (id) => {
|
||||
@@ -610,7 +627,9 @@ const Sidebar = ({ channels, activeChannel, onSelectChannel, username, channelKe
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
{!collapsedCategories[category] && catChannels.map(channel => (
|
||||
{!collapsedCategories[category] && catChannels.map(channel => {
|
||||
const isUnread = activeChannel !== channel._id && unreadChannels.has(channel._id);
|
||||
return (
|
||||
<React.Fragment key={channel._id}>
|
||||
<div
|
||||
className={`channel-item ${activeChannel === channel._id ? 'active' : ''} ${voiceChannelId === channel._id ? 'voice-active' : ''}`}
|
||||
@@ -623,6 +642,7 @@ const Sidebar = ({ channels, activeChannel, onSelectChannel, username, channelKe
|
||||
paddingRight: '8px'
|
||||
}}
|
||||
>
|
||||
{isUnread && <div className="channel-unread-indicator" />}
|
||||
<div style={{ display: 'flex', alignItems: 'center', overflow: 'hidden', flex: 1 }}>
|
||||
{channel.type === 'voice' ? (
|
||||
<div style={{ marginRight: 6 }}>
|
||||
@@ -635,7 +655,7 @@ const Sidebar = ({ channels, activeChannel, onSelectChannel, username, channelKe
|
||||
) : (
|
||||
<span style={{ color: 'var(--interactive-normal)', marginRight: '6px', flexShrink: 0 }}>#</span>
|
||||
)}
|
||||
<span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{channel.name}</span>
|
||||
<span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', ...(isUnread ? { color: 'var(--header-primary)', fontWeight: 600 } : {}) }}>{channel.name}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -661,7 +681,8 @@ const Sidebar = ({ channels, activeChannel, onSelectChannel, username, channelKe
|
||||
</div>
|
||||
{renderVoiceUsers(channel)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user