feat: Add new emoji assets and an UpdateBanner component.
Some checks failed
Build and Release / build-and-release (push) Failing after 3m28s
Some checks failed
Build and Release / build-and-release (push) Failing after 3m28s
This commit is contained in:
167
packages/shared/src/components/FriendsView.jsx
Normal file
167
packages/shared/src/components/FriendsView.jsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useQuery } from 'convex/react';
|
||||
import { api } from '../../../../convex/_generated/api';
|
||||
import Avatar from './Avatar';
|
||||
import { useOnlineUsers } from '../contexts/PresenceContext';
|
||||
import friendsIcon from '../assets/icons/friends.svg';
|
||||
|
||||
const FriendsView = ({ onOpenDM }) => {
|
||||
const [activeTab, setActiveTab] = useState('Online');
|
||||
const [addFriendSearch, setAddFriendSearch] = useState('');
|
||||
|
||||
const myId = localStorage.getItem('userId');
|
||||
const { resolveStatus } = useOnlineUsers();
|
||||
|
||||
const allUsers = useQuery(api.auth.getPublicKeys) || [];
|
||||
const users = allUsers.filter(u => u.id !== myId);
|
||||
|
||||
const getUserColor = (username) => {
|
||||
if (!username) return '#747f8d';
|
||||
const colors = ['#5865F2', '#EBA7CD', '#57F287', '#FEE75C', '#EB459E', '#ED4245'];
|
||||
let hash = 0;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
return colors[Math.abs(hash) % colors.length];
|
||||
};
|
||||
|
||||
const STATUS_COLORS = {
|
||||
online: '#3ba55c',
|
||||
idle: '#faa61a',
|
||||
dnd: '#ed4245',
|
||||
invisible: '#747f8d',
|
||||
offline: '#747f8d',
|
||||
};
|
||||
|
||||
const filteredUsers = activeTab === 'Online'
|
||||
? users.filter(u => resolveStatus(u.status, u.id) !== 'offline')
|
||||
: activeTab === 'Add Friend'
|
||||
? users.filter(u => u.username?.toLowerCase().includes(addFriendSearch.toLowerCase()))
|
||||
: users;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, backgroundColor: 'var(--bg-primary)', height: '100vh' }}>
|
||||
{/* Top Bar */}
|
||||
<div style={{
|
||||
height: '48px',
|
||||
borderBottom: '1px solid var(--border-subtle)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 16px',
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginRight: '16px', paddingRight: '16px', borderRight: '1px solid var(--border-subtle)' }}>
|
||||
<div style={{ marginRight: '12px' }}>
|
||||
<div style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<img
|
||||
src={friendsIcon}
|
||||
alt=""
|
||||
style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
transform: 'translateX(-1000px)',
|
||||
filter: `drop-shadow(1000px 0 0 var(--interactive-normal))`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
Friends
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
||||
{['Online', 'All'].map(tab => (
|
||||
<div
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className="friends-tab"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
color: activeTab === tab ? 'var(--header-primary)' : 'var(--header-secondary)',
|
||||
backgroundColor: activeTab === tab ? 'rgba(255,255,255,0.06)' : 'transparent',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
>
|
||||
{tab}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* List Header */}
|
||||
<div style={{ padding: '16px 20px 8px' }}>
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
color: 'var(--header-secondary)',
|
||||
textTransform: 'uppercase'
|
||||
}}>
|
||||
{activeTab === 'Add Friend' ? 'USERS' : activeTab} — {filteredUsers.length}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Friends List */}
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '0 20px' }}>
|
||||
{filteredUsers.map(user => {
|
||||
const effectiveStatus = resolveStatus(user.status, user.id);
|
||||
return (
|
||||
<div
|
||||
key={user.id}
|
||||
className="friend-item"
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div style={{ position: 'relative', marginRight: '12px' }}>
|
||||
<Avatar username={user.username} avatarUrl={user.avatarUrl} size={32} />
|
||||
<div style={{
|
||||
position: 'absolute', bottom: -2, right: -2,
|
||||
width: 10, height: 10, borderRadius: '50%',
|
||||
backgroundColor: STATUS_COLORS[effectiveStatus] || STATUS_COLORS.offline,
|
||||
border: '2px solid var(--bg-primary)'
|
||||
}} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ color: 'var(--header-primary)', fontWeight: '600' }}>
|
||||
{user.username ?? 'Unknown'}
|
||||
</div>
|
||||
<div style={{ color: 'var(--header-secondary)', fontSize: '12px' }}>
|
||||
{effectiveStatus === 'dnd' ? 'Do Not Disturb' : effectiveStatus.charAt(0).toUpperCase() + effectiveStatus.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<div
|
||||
className="friend-action-btn"
|
||||
onClick={() => onOpenDM && onOpenDM(user.id, user.username)}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M4.79805 3C3.80445 3 2.99805 3.8055 2.99805 4.8V15.6C2.99805 16.5936 3.80445 17.4 4.79805 17.4H8.39805L11.998 21L15.598 17.4H19.198C20.1925 17.4 20.998 16.5936 20.998 15.6V4.8C20.998 3.8055 20.1925 3 19.198 3H4.79805Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="friend-action-btn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 16C13.1046 16 14 15.1046 14 14C14 12.8954 13.1046 12 12 12C10.8954 12 10 12.8954 10 14C10 15.1046 10.8954 16 12 16Z" />
|
||||
<path d="M12 10C13.1046 10 14 9.10457 14 8C14 6.89543 13.1046 6 12 6C10.8954 6 10 6.89543 10 8C10 9.10457 10.8954 10 12 10Z" />
|
||||
<path d="M12 22C13.1046 22 14 21.1046 14 20C14 18.8954 13.1046 18 12 18C10.8954 18 10 18.8954 10 20C10 21.1046 10.8954 22 12 22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FriendsView;
|
||||
Reference in New Issue
Block a user