Peercord/Peercord Source/src/components/FriendsView.jsx
0% [█ █ █ █ █ █ █ █ █ █] 100% 29e61f07f2 Full source
2026-06-14 21:28:04 -05:00

137 lines
7.3 KiB
JavaScript

import React, { useState } from 'react';
import { network } from '../p2p/index.js';
export default function FriendsView({ dms }) {
const [activeTab, setActiveTab] = useState('pending');
const [searchUsername, setSearchUsername] = useState('');
const[searchStatus, setSearchStatus] = useState(''); // 'searching', 'queued', 'found', 'error'
const pendingIncoming = Object.entries(dms).filter(([_, data]) => data.status === 'pending_incoming');
const pendingOutgoing = Object.entries(dms).filter(([_, data]) => data.status === 'pending_outgoing');
const handleAddFriend = async (e) => {
e.preventDefault();
const target = searchUsername.trim().toLowerCase();
if (!target) return;
if (target === network.username) {
setSearchStatus('error');
return;
}
setSearchStatus('searching');
const result = await network.searchUser(target);
if (result) {
await network.sendDMRequest(result.pubKey, result.profile);
setSearchStatus('found');
setSearchUsername('');
} else {
await network.queueFriendRequest(target);
setSearchStatus('queued');
setSearchUsername('');
}
};
return (
<div className="flex-1 flex flex-col bg-base min-w-0">
<div className="h-14 shadow-sm flex items-center px-4 border-b border-surface gap-6 shrink-0 bg-panel z-10">
<div className="flex items-center gap-2 text-text font-bold">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-muted"><path d="M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 2a9.985 9.985 0 0 0-8 4 9.985 9.985 0 0 0 8 4 9.985 9.985 0 0 0 8-4 9.985 9.985 0 0 0-8-4z"/></svg>
Contacts
</div>
<div className="w-[1px] h-6 bg-surface"></div>
<div className="flex gap-4">
<button
onClick={() => setActiveTab('pending')}
className={`px-3 py-1 rounded font-medium text-sm transition-colors ${activeTab === 'pending' ? 'bg-surface text-text' : 'text-muted hover:bg-surface/50 hover:text-text'}`}
>
Pending {pendingIncoming.length > 0 && <span className="bg-red-500 text-white text-xs px-1.5 rounded-full ml-1">{pendingIncoming.length}</span>}
</button>
<button
onClick={() => setActiveTab('add')}
className={`px-3 py-1 rounded font-medium text-sm transition-colors ${activeTab === 'add' ? 'bg-accent text-white' : 'bg-accent/20 text-accent hover:bg-accent/30'}`}
>
Add Contact
</button>
</div>
</div>
<div className="flex-1 p-6 overflow-y-auto">
{activeTab === 'pending' && (
<div>
<h2 className="text-xs font-bold text-muted uppercase mb-4">Pending Requests {pendingIncoming.length + pendingOutgoing.length}</h2>
<div className="space-y-2">
{pendingIncoming.map(([pubKey, data]) => (
<div key={pubKey} className="flex items-center justify-between p-3 hover:bg-panel rounded-lg border-t border-surface group transition-colors">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-md bg-indigo-500 flex items-center justify-center text-white font-bold overflow-hidden">
{data.profile?.avatar ? <img src={data.profile.avatar} className="w-full h-full object-cover"/> : data.profile?.displayName?.substring(0, 2).toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-text font-bold">{data.profile?.displayName}</span>
<span className="text-xs text-muted">@{data.profile?.username} Incoming Contact Request</span>
</div>
</div>
<div className="flex gap-2">
<button onClick={() => network.acceptDMRequest(pubKey)} className="w-9 h-9 rounded-full bg-surface flex items-center justify-center text-green-500 hover:bg-green-500 hover:text-white transition-colors border border-panel" title="Accept">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
</button>
</div>
</div>
))}
{pendingOutgoing.map(([pubKey, data]) => (
<div key={pubKey} className="flex items-center justify-between p-3 hover:bg-panel rounded-lg border-t border-surface group transition-colors">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-md bg-surface flex items-center justify-center text-muted font-bold overflow-hidden border border-panel">
{data.profile?.avatar ? <img src={data.profile.avatar} className="w-full h-full object-cover"/> : data.profile?.displayName?.substring(0, 2).toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-text font-bold">{data.profile?.displayName}</span>
<span className="text-xs text-muted">@{data.profile?.username} Outgoing Contact Request</span>
</div>
</div>
</div>
))}
{pendingIncoming.length === 0 && pendingOutgoing.length === 0 && (
<div className="text-center text-muted mt-10">No pending requests.</div>
)}
</div>
</div>
)}
{activeTab === 'add' && (
<div className="max-w-2xl">
<h2 className="text-text font-bold mb-2">ADD CONTACT</h2>
<p className="text-sm text-muted mb-4">You can add a contact with their username. It's case sensitive!</p>
<form onSubmit={handleAddFriend} className="relative flex items-center">
<input
type="text"
value={searchUsername}
onChange={(e) => setSearchUsername(e.target.value)}
placeholder="You can add a contact with their username."
className="w-full bg-panel text-text rounded-lg p-4 pr-40 outline-none focus:ring-1 focus:ring-accent border border-surface"
/>
<button
type="submit"
disabled={!searchUsername.trim() || searchStatus === 'searching'}
className="absolute right-2 bg-accent hover:opacity-90 text-white px-4 py-2 rounded text-sm font-medium transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
>
Send Request
</button>
</form>
{searchStatus === 'searching' && <p className="text-accent text-sm mt-2">Searching network...</p>}
{searchStatus === 'found' && <p className="text-green-500 text-sm mt-2">Success! Your contact request was sent.</p>}
{searchStatus === 'queued' && <p className="text-yellow-500 text-sm mt-2">User is currently offline. We queued your request and will send it automatically when they come online!</p>}
{searchStatus === 'error' && <p className="text-red-500 text-sm mt-2">You cannot send a contact request to yourself.</p>}
</div>
)}
</div>
</div>
);
}