feat(FN-1064): replace agent sidebar with dropdown select in MailboxModal

- Replace agent sidebar layout with a compact dropdown select component
- Refactor MailboxModal.tsx to use dropdown instead of sidebar navigation
- Remove unused sidebar CSS styles and add dropdown-specific styles
- Update tests to cover dropdown behavior and interaction patterns
This commit is contained in:
gsxdsm
2026-04-07 13:37:56 -07:00
parent 814c4d2363
commit 55f5dde24d
3 changed files with 93 additions and 98 deletions

View File

@@ -510,64 +510,68 @@ export function MailboxModal({
{/* Agent Mailboxes Tab */}
{activeTab === "agents" && (
<div className="mailbox-agents" data-testid="mailbox-agents">
<div className="mailbox-agents-sidebar">
<div className="mailbox-agents-label">Select an agent</div>
{agents.length === 0 && (
<div className="mailbox-empty">
<Bot size={24} />
<p>No agents found</p>
{agents.length === 0 ? (
<div className="mailbox-empty">
<Bot size={32} />
<p>No agents found</p>
</div>
) : (
<>
<div className="mailbox-agents-dropdown">
<select
className="message-composer-select mailbox-agent-select"
value={selectedAgentId ?? ""}
onChange={(e) => setSelectedAgentId(e.target.value || null)}
data-testid="mailbox-agent-select"
>
<option value="">Select an agent</option>
{agents.map((agent) => (
<option key={agent.id} value={agent.id}>
{agent.name || agent.id}
</option>
))}
</select>
</div>
)}
{agents.map((agent) => (
<button
key={agent.id}
className={`mailbox-agent-btn ${selectedAgentId === agent.id ? "active" : ""}`}
onClick={() => setSelectedAgentId(agent.id)}
data-testid={`mailbox-agent-btn-${agent.id}`}
>
<Bot size={14} />
<span>{agent.name || agent.id}</span>
</button>
))}
</div>
<div className="mailbox-agents-content">
{!selectedAgentId && (
<div className="mailbox-empty">
<Bot size={32} />
<p>Select an agent to view their mailbox</p>
</div>
)}
{selectedAgentId && isLoading && !agentMailbox && <MailboxSkeleton />}
{agentMailbox && agentMailbox.messages.length === 0 && (
<div className="mailbox-empty">
<InboxIcon size={32} />
<p>No messages for this agent</p>
</div>
)}
{agentMailbox?.messages.map((msg) => (
<div
key={msg.id}
className={`mailbox-item ${!msg.read ? "unread" : ""}`}
onClick={() => handleOpenMessage(msg)}
data-testid={`mailbox-item-${msg.id}`}
>
<div className="mailbox-item-avatar">
{msg.fromType === "agent" ? <Bot size={16} /> : <User size={16} />}
</div>
<div className="mailbox-item-content">
<div className="mailbox-item-header">
<span className="mailbox-item-from">
{msg.fromType === "agent"
? participantLabel(msg.toId, msg.toType)
: participantLabel(msg.fromId, msg.fromType)}
</span>
<span className="mailbox-item-time">{formatTimestamp(msg.createdAt)}</span>
<div className="mailbox-agents-content">
{!selectedAgentId && (
<div className="mailbox-empty">
<Bot size={32} />
<p>Select an agent to view their mailbox</p>
</div>
<div className="mailbox-item-preview">{msg.content.slice(0, 80)}{msg.content.length > 80 ? "…" : ""}</div>
</div>
)}
{selectedAgentId && isLoading && !agentMailbox && <MailboxSkeleton />}
{agentMailbox && agentMailbox.messages.length === 0 && (
<div className="mailbox-empty">
<InboxIcon size={32} />
<p>No messages for this agent</p>
</div>
)}
{agentMailbox?.messages.map((msg) => (
<div
key={msg.id}
className={`mailbox-item ${!msg.read ? "unread" : ""}`}
onClick={() => handleOpenMessage(msg)}
data-testid={`mailbox-item-${msg.id}`}
>
<div className="mailbox-item-avatar">
{msg.fromType === "agent" ? <Bot size={16} /> : <User size={16} />}
</div>
<div className="mailbox-item-content">
<div className="mailbox-item-header">
<span className="mailbox-item-from">
{msg.fromType === "agent"
? participantLabel(msg.toId, msg.toType)
: participantLabel(msg.fromId, msg.fromType)}
</span>
<span className="mailbox-item-time">{formatTimestamp(msg.createdAt)}</span>
</div>
<div className="mailbox-item-preview">{msg.content.slice(0, 80)}{msg.content.length > 80 ? "…" : ""}</div>
</div>
</div>
))}
</div>
))}
</div>
</>
)}
</div>
)}
</>

View File

@@ -197,16 +197,31 @@ describe("MailboxModal", () => {
});
});
it("shows agent buttons in agents tab", async () => {
it("shows agent dropdown in agents tab", async () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByTestId("mailbox-agent-btn-agent-001")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-btn-agent-002")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-select")).toBeDefined();
});
// Should have placeholder plus two agent options
const select = screen.getByTestId("mailbox-agent-select") as HTMLSelectElement;
expect(select.options.length).toBe(3); // placeholder + 2 agents
expect(select.options[0].textContent).toBe("Select an agent…");
expect(select.options[1].textContent).toBe("Test Agent 1");
expect(select.options[2].textContent).toBe("Test Agent 2");
});
it("shows Select an agent… placeholder in dropdown", async () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
const select = screen.getByTestId("mailbox-agent-select") as HTMLSelectElement;
expect(select.value).toBe("");
expect(select.options[0].textContent).toBe("Select an agent…");
});
});
it("loads agent mailbox when agent is selected", async () => {
it("loads agent mailbox when selecting an agent from dropdown", async () => {
mockFetchAgentMailbox.mockResolvedValue({
ownerId: "agent-001",
ownerType: "agent",
@@ -216,14 +231,22 @@ describe("MailboxModal", () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByTestId("mailbox-agent-btn-agent-001")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-select")).toBeDefined();
});
fireEvent.click(screen.getByTestId("mailbox-agent-btn-agent-001"));
fireEvent.change(screen.getByTestId("mailbox-agent-select"), { target: { value: "agent-001" } });
await waitFor(() => {
expect(mockFetchAgentMailbox).toHaveBeenCalledWith("agent-001", undefined);
});
});
it("shows empty state when no agents exist", async () => {
render(<MailboxModal {...defaultProps} agents={[]} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByText("No agents found")).toBeDefined();
});
});
it("opens message detail when clicking a message", async () => {
render(<MailboxModal {...defaultProps} />);
await waitFor(() => {