feat: Implement agent-user connection request system with a new service and dynamic profile card buttons.
This commit is contained in:
79
src/app/(agent)/agent/network/component/ConnectionCard.tsx
Normal file
79
src/app/(agent)/agent/network/component/ConnectionCard.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface ConnectionCardProps {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
connectedAt?: string;
|
||||
onMessage?: (id: string) => void;
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
export function ConnectionCard({
|
||||
id,
|
||||
name,
|
||||
avatar,
|
||||
email,
|
||||
connectedAt,
|
||||
onMessage,
|
||||
}: ConnectionCardProps) {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<Image
|
||||
src={avatar}
|
||||
alt={name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="rounded-full object-cover w-[80px] h-[80px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Name */}
|
||||
<h3 className="font-serif font-bold text-[14px] leading-[19px] text-[#00293D] mb-1">
|
||||
{name}
|
||||
</h3>
|
||||
|
||||
{/* Email */}
|
||||
<p className="font-serif font-normal text-[13px] leading-[18px] text-[#00293D]/70 mb-1">
|
||||
{email}
|
||||
</p>
|
||||
|
||||
{/* Connected date */}
|
||||
{connectedAt && (
|
||||
<p className="font-serif font-normal text-[12px] text-[#00293D]/50">
|
||||
Connected on {formatDate(connectedAt)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => onMessage?.(id)}
|
||||
className="h-[27px] px-4 rounded-[15px] bg-[#e58625] font-fractul font-medium text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<Image
|
||||
src="/assets/icons/message-dark-icon.svg"
|
||||
alt="Message"
|
||||
width={14}
|
||||
height={13}
|
||||
/>
|
||||
Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,22 +7,41 @@ interface InvitationCardProps {
|
||||
name: string;
|
||||
avatar: string;
|
||||
description: string;
|
||||
mutualConnection?: string;
|
||||
createdAt?: string;
|
||||
isProcessing?: boolean;
|
||||
onAccept?: (id: string) => void;
|
||||
onIgnore?: (id: string) => void;
|
||||
}
|
||||
|
||||
// Format relative time
|
||||
function formatRelativeTime(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
export function InvitationCard({
|
||||
id,
|
||||
name,
|
||||
avatar,
|
||||
description,
|
||||
mutualConnection,
|
||||
createdAt,
|
||||
isProcessing = false,
|
||||
onAccept,
|
||||
onIgnore,
|
||||
}: InvitationCardProps) {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
|
||||
<div className={`flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4 ${isProcessing ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<Image
|
||||
@@ -36,53 +55,39 @@ export function InvitationCard({
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Name with verified icon */}
|
||||
{/* Name with time */}
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="font-serif font-bold text-[14px] leading-[19px] text-[#00293D]">
|
||||
{name}
|
||||
</h3>
|
||||
<Image
|
||||
src="/assets/icons/shield-verified-icon.svg"
|
||||
alt="Verified"
|
||||
width={19}
|
||||
height={19}
|
||||
/>
|
||||
{createdAt && (
|
||||
<span className="font-serif font-normal text-[12px] text-[#00293D]/50">
|
||||
• {formatRelativeTime(createdAt)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] mb-1">
|
||||
{/* Description/Message */}
|
||||
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] line-clamp-2">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
{/* Mutual Connection */}
|
||||
{mutualConnection && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Image
|
||||
src="/assets/icons/chain-icon.svg"
|
||||
alt="Connection"
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D]">
|
||||
Mutual Connection With {mutualConnection}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => onIgnore?.(id)}
|
||||
className="h-[27px] px-4 rounded-[15px] border border-[#00293d] font-serif font-light text-[14px] text-[#00293D] hover:bg-[#00293d]/5 transition-colors cursor-pointer"
|
||||
disabled={isProcessing}
|
||||
className="h-[27px] px-4 rounded-[15px] border border-[#00293d] font-serif font-light text-[14px] text-[#00293D] hover:bg-[#00293d]/5 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Ignore
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAccept?.(id)}
|
||||
className="h-[27px] px-4 rounded-[15px] bg-[#e58625] font-fractul font-medium text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors cursor-pointer"
|
||||
disabled={isProcessing}
|
||||
className="h-[27px] px-4 rounded-[15px] bg-[#e58625] font-fractul font-medium text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Accept
|
||||
{isProcessing ? 'Processing...' : 'Accept'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { ConnectionRequestCounts } from '@/services/connection-requests.service';
|
||||
|
||||
interface NavItem {
|
||||
label: string;
|
||||
href: string;
|
||||
icon: string;
|
||||
isActive?: boolean;
|
||||
interface NetworkSidebarProps {
|
||||
counts?: ConnectionRequestCounts;
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
label: 'Connections',
|
||||
href: '/agent/network/connections',
|
||||
icon: '/assets/icons/people-icon.svg',
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
interface StatItem {
|
||||
label: string;
|
||||
icon: string;
|
||||
count: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export function NetworkSidebar({ counts }: NetworkSidebarProps) {
|
||||
const statItems: StatItem[] = [
|
||||
{
|
||||
label: 'Pending Requests',
|
||||
icon: '/assets/icons/people-icon.svg',
|
||||
count: counts?.pending || 0,
|
||||
color: '#E58625',
|
||||
},
|
||||
{
|
||||
label: 'Connections',
|
||||
icon: '/assets/icons/people-icon.svg',
|
||||
count: counts?.accepted || 0,
|
||||
color: '#5ba4a4',
|
||||
},
|
||||
];
|
||||
|
||||
export function NetworkSidebar() {
|
||||
return (
|
||||
<div className="border border-[#00293d] rounded-[7px] p-4 w-full lg:w-[280px] bg-white h-fit">
|
||||
<h2 className="font-fractul font-medium text-[20px] leading-[24px] text-[#00293D] mb-4">
|
||||
@@ -28,16 +38,11 @@ export function NetworkSidebar() {
|
||||
|
||||
<div className="w-full h-0 border-t border-[#00293d]/30 mb-4" />
|
||||
|
||||
<nav className="space-y-3">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex items-center gap-3 py-2 px-1 rounded transition-colors cursor-pointer ${
|
||||
item.isActive
|
||||
? 'text-[#00293D]'
|
||||
: 'text-[#00293D]/70 hover:text-[#00293D]'
|
||||
}`}
|
||||
<div className="space-y-3">
|
||||
{statItems.map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="flex items-center gap-3 py-2 px-1"
|
||||
>
|
||||
<Image
|
||||
src={item.icon}
|
||||
@@ -45,12 +50,30 @@ export function NetworkSidebar() {
|
||||
width={31}
|
||||
height={31}
|
||||
/>
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px]">
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] flex-1">
|
||||
{item.label}
|
||||
</span>
|
||||
</Link>
|
||||
<span
|
||||
className="text-white text-[12px] font-bold rounded-full px-2 py-0.5 min-w-[20px] text-center"
|
||||
style={{ backgroundColor: item.color }}
|
||||
>
|
||||
{item.count}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Total Connections Summary */}
|
||||
<div className="mt-4 pt-4 border-t border-[#00293d]/30">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-serif font-normal text-[13px] text-[#00293D]/70">
|
||||
Total in network
|
||||
</span>
|
||||
<span className="font-serif font-bold text-[14px] text-[#00293D]">
|
||||
{counts?.accepted || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,80 +1,252 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { NetworkSidebar } from './component/NetworkSidebar';
|
||||
import { InvitationCard } from './component/InvitationCard';
|
||||
|
||||
// Mock data for invitations
|
||||
const invitations = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Sathish R',
|
||||
avatar: '/assets/icons/user-placeholder-icon.svg',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
mutualConnection: 'Anvar',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Ragunath',
|
||||
avatar: '/assets/icons/user-placeholder-icon.svg',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
mutualConnection: 'Anvar',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Sabari',
|
||||
avatar: '/assets/icons/user-placeholder-icon.svg',
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
mutualConnection: 'Anvar',
|
||||
},
|
||||
];
|
||||
import { ConnectionCard } from './component/ConnectionCard';
|
||||
import { connectionRequestsService, ConnectionRequest, ConnectionRequestCounts } from '@/services/connection-requests.service';
|
||||
|
||||
export default function NetworkPage() {
|
||||
const handleAccept = (id: string) => {
|
||||
console.log('Accepted invitation:', id);
|
||||
const [pendingRequests, setPendingRequests] = useState<ConnectionRequest[]>([]);
|
||||
const [connections, setConnections] = useState<ConnectionRequest[]>([]);
|
||||
const [counts, setCounts] = useState<ConnectionRequestCounts>({ pending: 0, accepted: 0, rejected: 0, total: 0 });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// Fetch connection requests on mount
|
||||
useEffect(() => {
|
||||
fetchRequests();
|
||||
}, []);
|
||||
|
||||
const fetchRequests = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Fetch pending requests, accepted connections, and counts in parallel
|
||||
const [pending, accepted, requestCounts] = await Promise.all([
|
||||
connectionRequestsService.getReceivedRequests('PENDING'),
|
||||
connectionRequestsService.getReceivedRequests('ACCEPTED'),
|
||||
connectionRequestsService.getRequestCounts(),
|
||||
]);
|
||||
|
||||
setPendingRequests(pending);
|
||||
setConnections(accepted);
|
||||
setCounts(requestCounts);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch connection requests:', err);
|
||||
setError('Failed to load connection requests');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIgnore = (id: string) => {
|
||||
console.log('Ignored invitation:', id);
|
||||
const handleAccept = async (id: string) => {
|
||||
if (processingIds.has(id)) return;
|
||||
|
||||
try {
|
||||
setProcessingIds(prev => new Set([...prev, id]));
|
||||
const updatedRequest = await connectionRequestsService.respondToRequest(id, { status: 'ACCEPTED' });
|
||||
|
||||
// Move from pending to connections
|
||||
const acceptedRequest = pendingRequests.find(r => r.id === id);
|
||||
if (acceptedRequest) {
|
||||
setPendingRequests(prev => prev.filter(r => r.id !== id));
|
||||
setConnections(prev => [{ ...acceptedRequest, ...updatedRequest }, ...prev]);
|
||||
}
|
||||
|
||||
setCounts(prev => ({
|
||||
...prev,
|
||||
pending: prev.pending - 1,
|
||||
accepted: prev.accepted + 1,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Failed to accept request:', err);
|
||||
} finally {
|
||||
setProcessingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleIgnore = async (id: string) => {
|
||||
if (processingIds.has(id)) return;
|
||||
|
||||
try {
|
||||
setProcessingIds(prev => new Set([...prev, id]));
|
||||
await connectionRequestsService.respondToRequest(id, { status: 'REJECTED' });
|
||||
|
||||
// Remove from list and update counts
|
||||
setPendingRequests(prev => prev.filter(r => r.id !== id));
|
||||
setCounts(prev => ({
|
||||
...prev,
|
||||
pending: prev.pending - 1,
|
||||
rejected: prev.rejected + 1,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Failed to reject request:', err);
|
||||
} finally {
|
||||
setProcessingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMessage = (userId: string) => {
|
||||
// TODO: Navigate to message page or open chat modal
|
||||
console.log('Message user:', userId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
{/* Left Sidebar */}
|
||||
<NetworkSidebar />
|
||||
<NetworkSidebar counts={counts} />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 space-y-6">
|
||||
{/* Header Card */}
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] px-6 py-4 mb-4 bg-white">
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] px-6 py-4 bg-white">
|
||||
<h1 className="font-fractul font-bold text-[15px] leading-[18px] text-[#00293D]">
|
||||
Manage my network
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Invitations Card */}
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white">
|
||||
{/* Invitations Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[#00293d]/10">
|
||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D]">
|
||||
Invitations ({invitations.length})
|
||||
</h2>
|
||||
<button className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] hover:text-[#e58625] transition-colors cursor-pointer">
|
||||
Show All
|
||||
</button>
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#E58625]"></div>
|
||||
<p className="text-[14px] font-serif text-[#00293D]/70">Loading network...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Invitations List */}
|
||||
<div className="px-6 divide-y divide-[#00293d]/10">
|
||||
{invitations.map((invitation) => (
|
||||
<InvitationCard
|
||||
key={invitation.id}
|
||||
{...invitation}
|
||||
onAccept={handleAccept}
|
||||
onIgnore={handleIgnore}
|
||||
/>
|
||||
))}
|
||||
{/* Error State */}
|
||||
{error && !loading && (
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="text-center">
|
||||
<p className="text-[14px] font-serif text-red-500 mb-2">{error}</p>
|
||||
<button
|
||||
onClick={fetchRequests}
|
||||
className="px-4 py-2 bg-[#E58625] rounded-full text-[14px] font-semibold text-white hover:bg-[#E58625]/90 transition-colors"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<>
|
||||
{/* Pending Connection Requests Card */}
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white">
|
||||
{/* Requests Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[#00293d]/10">
|
||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D]">
|
||||
Pending Requests ({counts.pending})
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Empty State for Pending */}
|
||||
{pendingRequests.length === 0 && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-[#00293D]/5 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Image
|
||||
src="/assets/icons/people-icon.svg"
|
||||
alt="No requests"
|
||||
width={24}
|
||||
height={24}
|
||||
className="opacity-30"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-[14px] font-bold font-serif text-[#00293D] mb-1">No pending requests</h3>
|
||||
<p className="text-[13px] font-serif text-[#00293D]/70">
|
||||
New connection requests will appear here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pending Requests List */}
|
||||
{pendingRequests.length > 0 && (
|
||||
<div className="px-6 divide-y divide-[#00293d]/10">
|
||||
{pendingRequests.map((request) => (
|
||||
<InvitationCard
|
||||
key={request.id}
|
||||
id={request.id}
|
||||
name={request.user?.email || 'Unknown User'}
|
||||
avatar={request.user?.avatar || '/assets/icons/user-placeholder-icon.svg'}
|
||||
description={request.message || 'No message provided'}
|
||||
createdAt={request.createdAt}
|
||||
isProcessing={processingIds.has(request.id)}
|
||||
onAccept={handleAccept}
|
||||
onIgnore={handleIgnore}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* My Connections Card */}
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white">
|
||||
{/* Connections Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[#00293d]/10">
|
||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D]">
|
||||
My Connections ({counts.accepted})
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Empty State for Connections */}
|
||||
{connections.length === 0 && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-[#00293D]/5 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Image
|
||||
src="/assets/icons/people-icon.svg"
|
||||
alt="No connections"
|
||||
width={24}
|
||||
height={24}
|
||||
className="opacity-30"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-[14px] font-bold font-serif text-[#00293D] mb-1">No connections yet</h3>
|
||||
<p className="text-[13px] font-serif text-[#00293D]/70">
|
||||
Accept connection requests to grow your network.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connections List */}
|
||||
{connections.length > 0 && (
|
||||
<div className="px-6 divide-y divide-[#00293d]/10">
|
||||
{connections.map((connection) => (
|
||||
<ConnectionCard
|
||||
key={connection.id}
|
||||
id={connection.userId}
|
||||
name={connection.user?.email || 'Unknown User'}
|
||||
avatar={connection.user?.avatar || '/assets/icons/user-placeholder-icon.svg'}
|
||||
email={connection.user?.email || ''}
|
||||
connectedAt={connection.respondedAt || connection.updatedAt}
|
||||
onMessage={handleMessage}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user