import { useEffect, useMemo, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@renderer/components/ui/dialog'; import { Input } from '@renderer/components/ui/input'; import { Label } from '@renderer/components/ui/label'; import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@renderer/components/ui/select'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { buildReplyBlock } from '@renderer/utils/agentMessageFormatting'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { X } from 'lucide-react'; import type { MentionSuggestion } from '@renderer/types/mention'; import type { ResolvedTeamMember, SendMessageResult } from '@shared/types'; interface QuotedMessage { from: string; text: string; } interface SendMessageDialogProps { open: boolean; members: ResolvedTeamMember[]; defaultRecipient?: string; quotedMessage?: QuotedMessage; sending: boolean; sendError: string | null; lastResult: SendMessageResult | null; onSend: (member: string, text: string, summary?: string) => void; onClose: () => void; } const NO_MEMBER = '__none__'; export const SendMessageDialog = ({ open, members, defaultRecipient, quotedMessage, sending, sendError, lastResult, onSend, onClose, }: SendMessageDialogProps): React.JSX.Element => { const colorMap = useMemo(() => buildMemberColorMap(members), [members]); const [quote, setQuote] = useState(undefined); const [member, setMember] = useState(''); const textDraft = useDraftPersistence({ key: 'sendMessage:text' }); const [summary, setSummary] = useState(''); const [prevOpen, setPrevOpen] = useState(false); const [prevResult, setPrevResult] = useState(null); // Reset form when dialog opens if (open && !prevOpen) { setMember(defaultRecipient ?? ''); setSummary(''); setQuote(quotedMessage); setPrevResult(lastResult); } if (open !== prevOpen) { setPrevOpen(open); } // Track whether auto-close is needed (setState in render phase is fine) const [pendingAutoClose, setPendingAutoClose] = useState(false); if (open && lastResult && lastResult !== prevResult) { setPrevResult(lastResult); setMember(''); setSummary(''); setPendingAutoClose(true); } // Side effects (onClose mutates parent state) must run in useEffect, not render phase useEffect(() => { if (pendingAutoClose) { textDraft.clearDraft(); setPendingAutoClose(false); onClose(); } // eslint-disable-next-line react-hooks/exhaustive-deps -- only trigger on pendingAutoClose flag }, [pendingAutoClose]); const mentionSuggestions = useMemo( () => members.map((m) => ({ id: m.name, name: m.name, subtitle: formatAgentRole(m.role) ?? formatAgentRole(m.agentType) ?? undefined, color: colorMap.get(m.name), })), [members, colorMap] ); const canSend = member.trim().length > 0 && textDraft.value.trim().length > 0 && summary.trim().length > 0 && !sending; const handleSubmit = (): void => { if (!canSend) return; const rawText = textDraft.value.trim(); const finalText = quote ? buildReplyBlock(quote.from, quote.text, rawText) : rawText; onSend(member.trim(), finalText, summary.trim()); textDraft.clearDraft(); }; const handleOpenChange = (nextOpen: boolean): void => { if (!nextOpen) { onClose(); } }; return ( Send Message Send a direct message to a team member.
{quote ? (
Remove quote Replying to @{quote.from}

{quote.text}

) : null}
Draft saved ) : null } />
setSummary(e.target.value)} />

Shown as notification preview. Team lead also sees this for peer messages.

{sendError ?

{sendError}

: null}
); };