From f9e641c23bc27175fecc349c1674e2508871d87c Mon Sep 17 00:00:00 2001 From: cesnimda Date: Mon, 10 Aug 2026 09:45:30 +0200 Subject: [PATCH] fix(email): clarify provider capability states --- .../src/correspondence-inbox-page.test.tsx | 19 +++++++++++++-- .../src/views/CorrespondenceInboxPage.tsx | 23 ++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/job-tracker-ui/src/correspondence-inbox-page.test.tsx b/job-tracker-ui/src/correspondence-inbox-page.test.tsx index abab7d2..284e702 100644 --- a/job-tracker-ui/src/correspondence-inbox-page.test.tsx +++ b/job-tracker-ui/src/correspondence-inbox-page.test.tsx @@ -90,8 +90,9 @@ describe('CorrespondenceInboxPage', () => { expect(await screen.findByText(/backend engineer/i)).toBeInTheDocument(); expect(screen.getByText(/2 labels/i)).toBeInTheDocument(); expect(screen.getByText(/1 attachments/i)).toBeInTheDocument(); - expect(screen.getByText(/Gmail: owner@gmail\.test · Read only/i)).toBeInTheDocument(); - expect(screen.getByText(/Outlook: Not connected · Read only/i)).toBeInTheDocument(); + expect(screen.getByText(/Gmail: owner@gmail\.test · Read only; reconnect to enable send/i)).toBeInTheDocument(); + expect(screen.getByText(/^Outlook: Not connected$/i)).toBeInTheDocument(); + expect(screen.queryByText(/Outlook: Not connected.*Read only/i)).not.toBeInTheDocument(); fireEvent.change(screen.getByLabelText(/search/i), { target: { value: 'Maria' } }); fireEvent.mouseDown(screen.getAllByRole('combobox')[0]); @@ -107,6 +108,20 @@ describe('CorrespondenceInboxPage', () => { }); }); + test('keeps saved correspondence visible when provider status is unavailable', async () => { + const original = mockedApi.get.getMockImplementation(); + mockedApi.get.mockImplementation((url: string, config?: any) => { + if (url === '/email/providers') return Promise.reject(new Error('provider status unavailable')); + return original!(url, config); + }); + + renderPage(); + + expect(await screen.findByText(/email provider status is temporarily unavailable/i)).toBeInTheDocument(); + expect(await screen.findByText(/acme systems/i)).toBeInTheDocument(); + expect(screen.getByText(/saved jobtracker correspondence remains available/i)).toBeInTheDocument(); + }); + test('opens provider-backed plain-text message detail in the hub', async () => { renderPage(); diff --git a/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx b/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx index 380311a..4f1e584 100644 --- a/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx +++ b/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx @@ -50,6 +50,13 @@ type EmailProviderStatus = { canSend: boolean; }; +export function emailProviderStatusLabel(provider: EmailProviderStatus) { + if (!provider.connected) return `${provider.displayName}: Not connected`; + const identity = provider.address || "Connected account"; + if (!provider.canRead) return `${provider.displayName}: ${identity} · Mailbox access unavailable`; + return `${provider.displayName}: ${identity}${provider.canSend ? " · Read + send" : " · Read only; reconnect to enable send"}`; +} + type EmailMessageDetail = { id: string; threadId: string; @@ -94,6 +101,7 @@ export default function CorrespondenceInboxPage() { const { confirm } = useConfirm(); const [items, setItems] = useState([]); const [providers, setProviders] = useState([]); + const [providerStatusError, setProviderStatusError] = useState(false); const [selectedMessageId, setSelectedMessageId] = useState(null); const [messageDetail, setMessageDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); @@ -134,8 +142,14 @@ export default function CorrespondenceInboxPage() { useEffect(() => { api.get("/email/providers") - .then((response) => setProviders(response.data ?? [])) - .catch(() => setProviders([])); + .then((response) => { + setProviders(response.data ?? []); + setProviderStatusError(false); + }) + .catch(() => { + setProviders([]); + setProviderStatusError(true); + }); }, []); const filteredSummary = useMemo(() => { @@ -346,10 +360,13 @@ export default function CorrespondenceInboxPage() { size="small" color={provider.connected ? "success" : "default"} variant="outlined" - label={`${provider.displayName}: ${provider.connected ? provider.address || "Connected" : "Not connected"}${provider.canSend ? " · Send access granted" : " · Read only"}`} + label={emailProviderStatusLabel(provider)} /> ))} + {providerStatusError ? ( + Email provider status is temporarily unavailable. Saved JobTracker correspondence remains available. + ) : null} {view === "review" ? : <>