Migrate Mail surfaces to interface patterns

This commit is contained in:
2026-08-03 10:06:13 +02:00
parent 5b5077cde7
commit 7844d9cab4
12 changed files with 460 additions and 44 deletions
+37 -3
View File
@@ -1,8 +1,10 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { Activity, ChevronRight, Home, Mail, Paperclip, RefreshCw, Search, X } from "lucide-react";
import {
ActionBlockerHint,
Button,
DataGridPaginationBar,
DocumentationHelpLink,
DismissibleAlert,
ExplorerTree,
IconButton,
@@ -27,6 +29,11 @@ import {
"../../api/mail";
import { buildMailboxFolderTree, findFolderNodeId, folderAncestorIds, type MailFolderNode } from "./mailboxFolders";
const MAILBOX_DOCUMENTATION = {
topicId: "mail.workflow.read-mailbox",
documentationType: "user"
} as const;
export default function MailboxPage({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) {
const navigate = useGuardedNavigate();
const [profiles, setProfiles] = useState<MailServerProfile[]>([]);
@@ -73,6 +80,19 @@ export default function MailboxPage({ settings, auth }: { settings: ApiSettings;
const messageEmptyText = messageError || (!selectedProfileId ? "i18n:govoplan-mail.select_an_imap_profile.5445648c" : !foldersReady || loadingMessages ? "i18n:govoplan-mail.loading_messages.77b62232" : messages.length > 0 && filteredMessages.length === 0 ? "i18n:govoplan-mail.no_messages_match_the_current_filter_on_this_pag.9dda6916" : "i18n:govoplan-mail.no_messages_in_this_folder.5c7fa25d");
const previewEmptyText = detailError || (loadingMessage ? "i18n:govoplan-mail.loading_message.815c2094" : "i18n:govoplan-mail.select_a_message_to_inspect_its_content.5f3d1342");
const loadingLabel = loadingProfiles ? "i18n:govoplan-mail.loading_mail_profiles.87de3560" : loadingFolders ? "i18n:govoplan-mail.loading_folders.17f9f0e2" : loadingMessages ? "i18n:govoplan-mail.loading_messages.77b62232" : "i18n:govoplan-mail.loading_message.815c2094";
const profileReloadBlocker = loadingProfiles ? "Mail profiles are already loading." : "";
const folderReloadBlocker = !selectedProfileId
? "Select an IMAP-enabled Mail profile before refreshing folders."
: loadingFolders || loadingMessages
? "Wait for the current mailbox refresh to finish."
: "";
const messageReloadBlocker = !selectedProfileId
? "Select an IMAP-enabled Mail profile before refreshing messages."
: !selectedFolder || !foldersReady
? "Select a loaded mailbox folder before refreshing messages."
: loadingMessages
? "Messages are already loading."
: "";
useEffect(() => {void loadProfiles();}, [settings.apiBaseUrl, settings.apiKey, settings.accessToken]);
useEffect(() => {selectedMessageKeyRef.current = selectedMessageKeyState;}, [selectedMessageKeyState]);
@@ -397,26 +417,40 @@ export default function MailboxPage({ settings, auth }: { settings: ApiSettings;
</label>
<span className="mailbox-toolbar-meta">{selectedProfile?.imap ? transportLabel(selectedProfile) : "i18n:govoplan-mail.no_imap_profile_selected.e7d1516f"}</span>
<div className="mailbox-toolbar-actions">
<DocumentationHelpLink reference={MAILBOX_DOCUMENTATION} />
{hasAnyScope(auth, ["mail:bounce:read", "mail:bounce:manage"]) &&
<Button onClick={() => navigate("/mail/bounces")} title="Open bounce processing">
<Activity size={16} aria-hidden="true" />
Bounce status
</Button>}
<Button onClick={() => void loadProfiles()} disabled={loadingProfiles} title="i18n:govoplan-mail.reload_imap_profiles.b04c11c8">
<Button onClick={() => void loadProfiles()} disabled={Boolean(profileReloadBlocker)} disabledReason={profileReloadBlocker} title="i18n:govoplan-mail.reload_imap_profiles.b04c11c8">
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.profiles.0c2a9300
</Button>
<Button onClick={() => void loadMailboxBootstrap(selectedProfileId, true)} disabled={!selectedProfileId || loadingFolders || loadingMessages} title="i18n:govoplan-mail.refresh_mailbox_folders.d9af9963">
<Button onClick={() => void loadMailboxBootstrap(selectedProfileId, true)} disabled={Boolean(folderReloadBlocker)} disabledReason={folderReloadBlocker} title="i18n:govoplan-mail.refresh_mailbox_folders.d9af9963">
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.folders.19adc47b
</Button>
<Button onClick={() => void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize, true)} disabled={!selectedProfileId || !selectedFolder || !foldersReady || loadingMessages} title="i18n:govoplan-mail.refresh_messages_in_the_current_folder.b6546a2c">
<Button onClick={() => void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize, true)} disabled={Boolean(messageReloadBlocker)} disabledReason={messageReloadBlocker} title="i18n:govoplan-mail.refresh_messages_in_the_current_folder.b6546a2c">
<RefreshCw size={16} aria-hidden="true" />
i18n:govoplan-mail.messages.f1702b46
</Button>
</div>
</div>
{noImapProfiles &&
<ActionBlockerHint
className="mailbox-profile-blocker"
reason={{
summary: "No IMAP-enabled Mail profile is available.",
details: "The mailbox workspace is read-only and needs an active profile with an authorized IMAP server and credential.",
requiredAction: "Ask a Mail administrator to configure and authorize an IMAP-enabled profile.",
actor: "Mail profile administrator",
target: "Settings or Administration > Mail profiles"
}}
documentation={MAILBOX_DOCUMENTATION} />
}
<nav className="file-breadcrumbs" aria-label="i18n:govoplan-mail.current_mailbox_folder.55e2aea5">
<span className="file-breadcrumb mailbox-breadcrumb-static"><Home size={15} aria-hidden="true" /> {selectedProfile?.name || "i18n:govoplan-mail.mail.92379cbb"}</span>
<span className="file-breadcrumb-segment"><ChevronRight size={14} aria-hidden="true" /><span className="file-breadcrumb mailbox-breadcrumb-static">{selectedFolder}</span></span>