// Primus IQ — Invite Modal const InviteModal = ({ open, project, onClose, onInviteSent, onResolved, currentUser }) => { const [email, setEmail] = React.useState(''); const [role, setRole] = React.useState('member'); const [sending, setSending] = React.useState(false); const [sent, setSent] = React.useState(false); const [members, setMembers] = React.useState([]); const [joinRequests, setJoinRequests] = React.useState([]); const [suggestions, setSuggestions] = React.useState([]); const [searchTimer, setSearchTimer] = React.useState(null); const [showSuggestions, setShowSuggestions] = React.useState(false); const refreshTeam = (projectId) => { PrimusAPI.listMembers(projectId) .then(data => setMembers(data || [])).catch(() => {}); PrimusAPI.listJoinRequests(projectId) .then(data => setJoinRequests(data || [])).catch(() => setJoinRequests([])); }; React.useEffect(() => { if (open && project?.id) { refreshTeam(project.id); } else { setMembers([]); setJoinRequests([]); setEmail(''); setSuggestions([]); } }, [open, project?.id]); if (!open) return null; const handleEmailChange = (e) => { const val = e.target.value; setEmail(val); if (searchTimer) clearTimeout(searchTimer); if (val.length >= 2) { const timer = setTimeout(async () => { try { const results = await PrimusAPI.directorySearch(val); setSuggestions(results || []); setShowSuggestions(true); } catch { setSuggestions([]); } }, 300); setSearchTimer(timer); } else { setSuggestions([]); setShowSuggestions(false); } }; const selectSuggestion = (u) => { setEmail(u.email); setShowSuggestions(false); setSuggestions([]); }; const handleSend = async () => { if (!email || !project?.id) return; setSending(true); try { const match = suggestions.find(u => u.email === email); if (match) { await PrimusAPI.inviteMember(project.id, { id: match.id, email: match.email, full_name: match.full_name, in_system: true }, role); } else { await PrimusAPI.inviteMember(project.id, { email, full_name: email, in_system: false }, role); } setSent(true); setTimeout(() => { setSent(false); setEmail(''); refreshTeam(project.id); if (onInviteSent) onInviteSent(); }, 1500); } catch (ex) { console.error('Invite failed:', ex); } finally { setSending(false); } }; const handleApprove = async (reqId) => { try { await PrimusAPI.approveJoinRequest(project.id, reqId); refreshTeam(project.id); if (onResolved) onResolved(); // refresh the owner's bell + project counts } catch (ex) { console.error('Approve failed:', ex); } }; const handleReject = async (reqId) => { try { await PrimusAPI.rejectJoinRequest(project.id, reqId); setJoinRequests(prev => prev.filter(r => r.id !== reqId)); if (onResolved) onResolved(); // refresh the owner's bell } catch (ex) { console.error('Reject failed:', ex); } }; const meInitials = currentUser?.full_name ? currentUser.full_name.split(' ').filter(Boolean).map(w => w[0].toUpperCase()).slice(0, 2).join('') : '?'; return (
setTimeout(() => setShowSuggestions(false), 150)} onFocus={() => suggestions.length > 0 && setShowSuggestions(true)} placeholder="name@primuspartners.in" style={{ width: '100%', height: 40, padding: '0 14px', borderRadius: 10, border: '1px solid var(--line-2)', background: 'white', fontSize: 14, fontFamily: 'inherit', color: 'var(--ink)', }} /> {showSuggestions && suggestions.length > 0 && (
{suggestions.map(u => (
selectSuggestion(u)} style={{ padding: '10px 14px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 10, transition: 'background 0.1s', }} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-soft)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} >
{(u.full_name || u.email || '?')[0].toUpperCase()}
{u.full_name || u.email}
{u.email} · {u.designation || ''}
))}
)}
{joinRequests.length > 0 && (
{joinRequests.length}
{joinRequests.map(req => (
{(req.requester_name || '?')[0].toUpperCase()}
{req.requester_name}
{req.requester_designation || 'Primus Partners'}
))}
)}
{currentUser && (
{meInitials}
{currentUser.full_name} (you)
{currentUser.primus_email}
OWNER
)} {members.filter(m => m.user_id !== currentUser?.id).map(m => { const av = (m.full_name || '?')[0].toUpperCase(); return (
{av}
{m.full_name}
{m.designation || m.role}
{m.role}
); })} {members.length === 0 && !currentUser && (
Loading members…
)}
); }; Object.assign(window, { InviteModal, });