From b96d8757764e335bb550eaddbe16bc1a4bb49c0e Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:51:42 +0100 Subject: [PATCH] fix: make the account picker and the admin OAuth and plugin code dialogs proper modals --- app/(main)/admin/_pages/auth.tsx | 163 +++++++++--------- .../admin/_pages/plugin-approvals-panel.tsx | 37 ++-- .../protocol-account-picker.test.tsx | 53 ++++++ .../protocol/protocol-account-picker.tsx | 25 ++- 4 files changed, 174 insertions(+), 104 deletions(-) create mode 100644 components/protocol/__tests__/protocol-account-picker.test.tsx diff --git a/app/(main)/admin/_pages/auth.tsx b/app/(main)/admin/_pages/auth.tsx index 04afb2e38..02538dcb6 100644 --- a/app/(main)/admin/_pages/auth.tsx +++ b/app/(main)/admin/_pages/auth.tsx @@ -8,6 +8,7 @@ import { useAdminUnsavedChanges } from '@/stores/admin-unsaved-store'; import { DEFAULT_OAUTH_SCOPES } from '@/lib/oauth/scopes'; import { SettingsSection, SettingItem, SectionNav } from '@/components/settings/settings-section'; import { Button } from '@/components/ui/button'; +import { Dialog } from '@/components/ui/dialog'; import { AdminPageHeader } from '@/components/admin/page-header'; import { AdminSaveBar } from '@/components/admin/save-bar'; import { withEdit } from '@/lib/admin/edits'; @@ -177,92 +178,86 @@ export function AuthPage() {
- Verify the URLs below before continuing. The webmail and Stalwart can live on different domains. -
-
- Used to register redirect URIs (one per locale: {setupOrigin.trim().replace(/\/+$/, '') || 'https://…'}/<locale>/auth/callback) on Stalwart.
-
Must be like https://host with no path.
- )} -
- Where Stalwart serves /.well-known/oauth-authorization-server. Saved as OAUTH_ISSUER_URL. Pre-filled from your JMAP server URL.
-
Must be like https://host with no path.
- )} -
- {source.error ?? source.code ?? ( Loading)}
-
+ {/* The shared dialog: focus moved in and given back, Tab kept inside,
+ Escape closes, and the title names the plugin. */}
+
+ {source?.error ?? source?.code ?? ( Loading)}
+
+
);
}
diff --git a/components/protocol/__tests__/protocol-account-picker.test.tsx b/components/protocol/__tests__/protocol-account-picker.test.tsx
new file mode 100644
index 000000000..81cfe8610
--- /dev/null
+++ b/components/protocol/__tests__/protocol-account-picker.test.tsx
@@ -0,0 +1,53 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { ProtocolAccountPicker } from '../protocol-account-picker';
+import type { AccountEntry } from '@/stores/account-store';
+
+// The "which account?" question for a mailto: or webcal: link is a modal:
+// the focus starts on the account in use, Escape cancels, and the focus
+// comes back to where it was.
+
+const account = (id: string, email: string) =>
+ ({ id, email, label: email, username: email, serverUrl: 'https://mail.example.org', displayName: '' }) as unknown as AccountEntry;
+
+let opener: HTMLButtonElement;
+beforeEach(() => {
+ opener = document.createElement('button');
+ document.body.appendChild(opener);
+ opener.focus();
+});
+afterEach(() => opener.remove());
+
+describe('ProtocolAccountPicker', () => {
+ it('focuses the active account, cancels on Escape and gives the focus back', () => {
+ const onCancel = vi.fn();
+ const { unmount } = render(
+ {kind === "mailto" ? t("select_mailto_account") : t("select_webcal_account")}
@@ -72,7 +88,7 @@ export function ProtocolAccountPicker({ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground" aria-label={tCommon("close")} > -