import { useState } from 'react'; import { applyInviteResult, isNotWorkspaceMemberMessage, isValidEmail, NOT_WORKSPACE_MEMBER_CODE, parseEmails, } from './sharingPanelUtils'; import { useAgentBi } from '../../analytics/mixpanelContext'; import { useSuperagentCollaborators } from '../../runtime/runtimeContext'; import type { SuperagentAgent } from '../../types'; type GuestInvitePrompt = { emails: string[]; pendingInvitationEmails: string[]; }; type UseSharingPanelArgs = { agent: SuperagentAgent; onClose: () => void; }; /** Invite-collaborators state + actions for `SharingPanel` (keeps the component pure UI). */ export function useSharingPanel({ agent, onClose }: UseSharingPanelArgs) { const { onShareAgent, onRefreshCollaborators, onOpenWorkspaceMembers } = useSuperagentCollaborators(); const bi = useAgentBi(); const [emailText, setEmailText] = useState(''); const [error, setError] = useState(null); const [guestInvitePrompt, setGuestInvitePrompt] = useState(null); const [isInviting, setIsInviting] = useState(false); const [success, setSuccess] = useState(null); const hasInput = emailText.trim().length > 0; const sendInvites = async (emails: string[], addAsGuest = false) => { setError(null); setSuccess(null); setGuestInvitePrompt(null); if (!onShareAgent) { setError('Sharing is not available in this app build.'); return; } if (emails.length === 0) { setError('Enter at least one email.'); return; } const invalidEmails = emails.filter((email) => !isValidEmail(email)); if (invalidEmails.length > 0) { setError(`Invalid email: ${invalidEmails[0]}`); return; } setIsInviting(true); try { const result = await onShareAgent({agentId: agent.id, addAsGuest, emails}); const summary = applyInviteResult( result, emails.length, setError, setSuccess, addAsGuest ? [] : [NOT_WORKSPACE_MEMBER_CODE], ); if (!addAsGuest && summary.notWorkspaceMembers.length > 0) { setGuestInvitePrompt({ emails: summary.notWorkspaceMembers.map((item) => item.email), pendingInvitationEmails: summary.notWorkspaceMembers .filter((item) => item.has_pending_invitation) .map((item) => item.email), }); } else if (summary.failed.length === 0) { setEmailText(''); } // Count non-members as failed (web parity), since applyInviteResult excludes them. void bi.trackEditor('Collaboration Invite Sent', { invited_count: emails.length, success_count: summary.successful.length, failed_count: summary.failed.length + summary.notWorkspaceMembers.length, as_guest: addAsGuest, }); await onRefreshCollaborators?.(agent.id); } catch (inviteError) { const message = inviteError instanceof Error ? inviteError.message : 'Failed to invite collaborators.'; if (!addAsGuest && isNotWorkspaceMemberMessage(message)) { // Not a failure — redirects to the guest-invite prompt, so no Invite Sent here. setGuestInvitePrompt({ emails, pendingInvitationEmails: [], }); return; } // A thrown onShareAgent never reaches applyInviteResult, so report the all-failed result. void bi.trackEditor('Collaboration Invite Sent', { invited_count: emails.length, success_count: 0, failed_count: emails.length, as_guest: addAsGuest, }); setError(message); } finally { setIsInviting(false); } }; const invite = async () => { await sendInvites(parseEmails(emailText)); }; const inviteGuests = async () => { if (!guestInvitePrompt) { return; } await sendInvites(guestInvitePrompt.emails, true); }; const openWorkspaceMembers = async () => { setGuestInvitePrompt(null); onClose(); await onOpenWorkspaceMembers?.({agentId: agent.id, organizationId: agent.organizationId}); }; return { emailText, setEmailText, error, success, guestInvitePrompt, isInviting, hasInput, invite, inviteGuests, openWorkspaceMembers, }; }