Skip to content

Commit a06dee7

Browse files
committed
fix: replace broken window.confirm() with async ConfirmDialog and improve UI
window.confirm() doesn't block in Tauri webviews, causing destructive actions (delete space, remove client, delete featureset, clear logs) to execute before the user responds. Replace all 4 usages with a promise-based useConfirm() hook and ConfirmDialog component. Also improve Add Custom Server button and ConfigEditorModal styling. Signed-off-by: mcpmux <mcpmux@users.noreply.github.com> Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent f43879a commit a06dee7

10 files changed

Lines changed: 410 additions & 31 deletions

File tree

apps/desktop/src/components/ConfigEditorModal.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,18 @@ export function ConfigEditorModal({ spaceId, spaceName, onClose, onSaved }: Conf
163163
<div className="bg-[rgb(var(--surface))] w-full max-w-4xl h-[80vh] rounded-xl shadow-2xl flex flex-col border border-[rgb(var(--border))]">
164164
{/* Header */}
165165
<div className="flex items-center justify-between p-4 border-b border-[rgb(var(--border))]">
166-
<div>
167-
<h3 className="text-lg font-semibold flex items-center gap-2">
168-
Add Custom Server
169-
</h3>
170-
<p className="text-sm text-[rgb(var(--muted))]">
171-
Edit the JSON configuration for space: {spaceName}
172-
</p>
166+
<div className="flex items-center gap-3">
167+
<div className="w-9 h-9 flex items-center justify-center rounded-lg bg-[rgb(var(--primary))]/10 border border-[rgb(var(--primary))]/20">
168+
<Save className="h-4 w-4 text-[rgb(var(--primary))]" />
169+
</div>
170+
<div>
171+
<h3 className="text-base font-semibold">
172+
Custom Server Configuration
173+
</h3>
174+
<p className="text-xs text-[rgb(var(--muted))]">
175+
{spaceName} &middot; JSON config
176+
</p>
177+
</div>
173178
</div>
174179
<button
175180
onClick={onClose}
@@ -180,36 +185,40 @@ export function ConfigEditorModal({ spaceId, spaceName, onClose, onSaved }: Conf
180185
</div>
181186

182187
{/* Toolbar */}
183-
<div className="flex items-center gap-2 p-2 border-b border-[rgb(var(--border))] bg-[rgb(var(--surface-dim))]">
188+
<div className="flex items-center gap-2 px-3 py-2 border-b border-[rgb(var(--border))] bg-[rgb(var(--surface-dim))]">
184189
<button
185190
onClick={handleSave}
186191
disabled={isSaving || isLoading || !isValidJson}
187-
className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium bg-[rgb(var(--primary))] text-[rgb(var(--primary-foreground))] rounded-md hover:bg-[rgb(var(--primary-hover))] disabled:opacity-50 transition-colors"
192+
className="flex items-center gap-2 px-3.5 py-1.5 text-sm font-medium bg-[rgb(var(--primary))] text-[rgb(var(--primary-foreground))] rounded-lg hover:bg-[rgb(var(--primary-hover))] disabled:opacity-50 shadow-sm transition-colors"
188193
>
189194
{isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
190-
Save Changes
195+
Save
191196
</button>
192-
193-
<div className="h-4 w-px bg-[rgb(var(--border))]" />
197+
198+
<div className="h-5 w-px bg-[rgb(var(--border))]" />
194199

195200
<button
196201
onClick={handleFormat}
197202
disabled={isLoading || !isValidJson}
198-
className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-[rgb(var(--foreground))] hover:bg-[rgb(var(--surface-hover))] rounded-md transition-colors disabled:opacity-50"
203+
className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-[rgb(var(--muted))] hover:text-[rgb(var(--foreground))] hover:bg-[rgb(var(--surface-hover))] rounded-lg transition-colors disabled:opacity-50"
199204
title="Format JSON (Ctrl+Shift+F)"
200205
>
201206
<Wand2 className="h-4 w-4" />
202207
Format
203208
</button>
204209

205210
<div className="flex-1" />
206-
211+
207212
{!isValidJson && (
208213
<span className="flex items-center gap-1.5 text-xs text-[rgb(var(--error))] px-2 font-medium">
209214
<AlertTriangle className="h-3 w-3" />
210215
{validationErrors.length > 0 ? 'Schema Error' : 'Invalid JSON'}
211216
</span>
212217
)}
218+
219+
<span className="text-xs text-[rgb(var(--muted))]">
220+
Ctrl+S save &middot; Ctrl+Shift+F format
221+
</span>
213222
</div>
214223

215224
{/* Editor Area */}

apps/desktop/src/components/OAuthConsentModal.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { listen } from '@tauri-apps/api/event';
1616
import { Check, X, AlertCircle, Loader2, Globe, Lock } from 'lucide-react';
1717
import { Button, Card, CardHeader, CardTitle, CardDescription, CardContent } from '@mcpmux/ui';
1818
import { listSpaces, type Space } from '@/lib/api/spaces';
19-
import { useAppStore } from '@/stores';
19+
import { useNavigateTo } from '@/stores';
2020
import { resolveKnownClientKey } from '@/lib/clientIcons';
2121
import cursorIcon from '@/assets/client-icons/cursor.svg';
2222
import vscodeIcon from '@/assets/client-icons/vscode.png';
@@ -123,6 +123,7 @@ export function OAuthConsentModal() {
123123
const [processError, setProcessError] = useState<string | null>(null);
124124
/** 2-second cooldown before the Approve button becomes active */
125125
const [approveReady, setApproveReady] = useState(false);
126+
const navigateTo = useNavigateTo();
126127

127128
// Load spaces when modal opens
128129
useEffect(() => {
@@ -293,7 +294,6 @@ export function OAuthConsentModal() {
293294

294295
// Approved state - show success with next-step guidance
295296
if (modalState.type === 'approved') {
296-
const navigateTo = useAppStore.getState().navigateTo;
297297
return (
298298
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
299299
<Card className="animate-in fade-in zoom-in mx-4 w-full max-w-md shadow-xl duration-200">
@@ -320,21 +320,21 @@ export function OAuthConsentModal() {
320320
<div className="flex gap-3">
321321
<Button
322322
variant="secondary"
323-
className="flex-1"
324323
onClick={handleDismiss}
325324
>
326325
Later
327326
</Button>
328327
<Button
329328
variant="primary"
330-
className="flex-1"
329+
className="flex-1 whitespace-nowrap"
331330
onClick={() => {
332331
navigateTo('clients');
333332
handleDismiss();
334333
}}
335334
data-testid="go-to-clients-btn"
336335
>
337-
Manage Permissions →
336+
Manage Permissions
337+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
338338
</Button>
339339
</div>
340340
</CardContent>

apps/desktop/src/components/ServerLogViewer.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState, useRef } from 'react';
22
import { X, Download, Trash2, RefreshCw } from 'lucide-react';
3-
import { useToast, ToastContainer } from '@mcpmux/ui';
3+
import { useToast, ToastContainer, useConfirm } from '@mcpmux/ui';
44
import { getServerLogs, clearServerLogs, getServerLogFile, type ServerLogEntry } from '@/lib/api/logs';
55

66
interface ServerLogViewerProps {
@@ -41,6 +41,7 @@ export function ServerLogViewer({ serverId, serverName, onClose }: ServerLogView
4141
const scrollContainerRef = useRef<HTMLDivElement>(null);
4242
const shouldScrollRef = useRef(true);
4343
const { toasts, success, error: showError, dismiss } = useToast();
44+
const { confirm, ConfirmDialogElement } = useConfirm();
4445

4546
const loadLogs = async () => {
4647
try {
@@ -93,7 +94,12 @@ export function ServerLogViewer({ serverId, serverName, onClose }: ServerLogView
9394
};
9495

9596
const handleClearLogs = async () => {
96-
if (!confirm('Clear all logs for this server? This cannot be undone.')) {
97+
if (!await confirm({
98+
title: 'Clear logs',
99+
message: `Clear all logs for "${serverName}"? This cannot be undone.`,
100+
confirmLabel: 'Clear',
101+
variant: 'danger',
102+
})) {
97103
return;
98104
}
99105

@@ -135,6 +141,7 @@ export function ServerLogViewer({ serverId, serverName, onClose }: ServerLogView
135141
return (
136142
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
137143
<ToastContainer toasts={toasts} onClose={dismiss} />
144+
{ConfirmDialogElement}
138145
<div className="bg-[rgb(var(--card))] border border-[rgb(var(--border-subtle))] rounded-xl shadow-xl w-[90vw] h-[85vh] flex flex-col">
139146
{/* Header */}
140147
<div className="flex items-center justify-between p-4 border-b border-[rgb(var(--border-subtle))]">

apps/desktop/src/features/clients/ClientsPage.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
Button,
3333
useToast,
3434
ToastContainer,
35+
useConfirm,
3536
} from '@mcpmux/ui';
3637
import type { OAuthClient, UpdateClientRequest } from '@/lib/api/gateway';
3738
import { listOAuthClients, updateOAuthClient, deleteOAuthClient } from '@/lib/api/gateway';
@@ -120,7 +121,8 @@ export default function ClientsPage() {
120121
const [selectedClient, setSelectedClient] = useState<OAuthClient | null>(null);
121122

122123
const { toasts, success, error: showError, info, dismiss } = useToast();
123-
124+
const { confirm, ConfirmDialogElement } = useConfirm();
125+
124126
// Edit state
125127
const [editAlias, setEditAlias] = useState('');
126128
const [editMode, setEditMode] = useState('follow_active');
@@ -383,9 +385,14 @@ export default function ClientsPage() {
383385
};
384386

385387
const handleDelete = async (clientId: string) => {
386-
if (!confirm('Remove this client? All tokens will be revoked.')) return;
387-
388388
const deletedClient = oauthClients.find(c => c.client_id === clientId);
389+
const name = deletedClient?.client_alias || deletedClient?.client_name || 'this client';
390+
if (!await confirm({
391+
title: 'Remove client',
392+
message: `Remove "${name}"? All tokens will be revoked.`,
393+
confirmLabel: 'Remove',
394+
variant: 'danger',
395+
})) return;
389396
const clientName = deletedClient?.client_alias || deletedClient?.client_name || 'Client';
390397

391398
try {
@@ -1321,6 +1328,7 @@ export default function ClientsPage() {
13211328
)}
13221329

13231330
<ToastContainer toasts={toasts} onClose={dismiss} />
1331+
{ConfirmDialogElement}
13241332
</div>
13251333
);
13261334
}

apps/desktop/src/features/featuresets/FeatureSetPanel.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Shield,
2121
Save,
2222
} from 'lucide-react';
23-
import { Button, useToast, ToastContainer } from '@mcpmux/ui';
23+
import { Button, useToast, ToastContainer, useConfirm } from '@mcpmux/ui';
2424
import type { FeatureSet, AddMemberInput } from '@/lib/api/featureSets';
2525
import { setFeatureSetMembers } from '@/lib/api/featureSets';
2626
import type { ServerFeature } from '@/lib/api/serverFeatures';
@@ -49,6 +49,7 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
4949
const [error, setError] = useState<string | null>(null);
5050
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
5151
const { toasts, success, error: showError, dismiss } = useToast();
52+
const { confirm, ConfirmDialogElement } = useConfirm();
5253

5354
// Collapsible sections - only one expanded at a time, features by default
5455
const [expandedSections, setExpandedSections] = useState({
@@ -279,6 +280,7 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
279280
return (
280281
<div className="fixed right-0 top-0 bottom-0 w-full max-w-[45%] min-w-[600px] bg-[rgb(var(--surface))] border-l border-[rgb(var(--border))] shadow-2xl flex flex-col animate-in slide-in-from-right duration-300 z-50">
281282
<ToastContainer toasts={toasts} onClose={dismiss} />
283+
{ConfirmDialogElement}
282284
{/* Panel Header */}
283285
<div className="flex-shrink-0 p-4 border-b border-[rgb(var(--border))] bg-[rgb(var(--surface-elevated))]">
284286
<div className="flex items-start justify-between mb-3">
@@ -598,8 +600,13 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
598600
<Button
599601
variant="ghost"
600602
size="sm"
601-
onClick={() => {
602-
if (confirm('Delete this feature set?')) {
603+
onClick={async () => {
604+
if (await confirm({
605+
title: 'Delete feature set',
606+
message: `Delete "${featureSet.name}"? This cannot be undone.`,
607+
confirmLabel: 'Delete',
608+
variant: 'danger',
609+
})) {
603610
onDelete(featureSet.id);
604611
}
605612
}}

apps/desktop/src/features/servers/ServersPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -847,9 +847,9 @@ export function ServersPage() {
847847
{viewSpace && (
848848
<button
849849
onClick={() => setEditConfigSpace({ id: viewSpace.id, name: viewSpace.name })}
850-
className="flex items-center gap-2 px-4 py-2 text-sm border border-[rgb(var(--border))] rounded-lg hover:bg-[rgb(var(--surface-hover))] transition-colors"
850+
className="flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-lg bg-[rgb(var(--surface-elevated))] border border-[rgb(var(--border))] hover:bg-[rgb(var(--surface-hover))] hover:border-[rgb(var(--border-subtle))] shadow-sm hover:shadow transition-all"
851851
>
852-
<FileJson className="h-4 w-4" />
852+
<FileJson className="h-4 w-4 text-[rgb(var(--primary))]" />
853853
Add Custom Server
854854
</button>
855855
)}
@@ -893,10 +893,11 @@ export function ServersPage() {
893893
<p className="text-lg mb-2">No servers installed</p>
894894
<button
895895
onClick={() => navigateTo('registry')}
896-
className="mt-3 px-6 py-2.5 rounded-lg text-sm font-semibold text-white bg-gradient-to-r from-primary-500 to-purple-500 hover:from-primary-600 hover:to-purple-600 shadow-md hover:shadow-lg transition-all hover:scale-[1.03]"
896+
className="mt-3 inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium bg-[rgb(var(--primary))] text-[rgb(var(--primary-foreground))] hover:bg-[rgb(var(--primary-hover))] shadow-sm hover:shadow transition-all"
897897
data-testid="discover-servers-btn"
898898
>
899899
Discover MCP Servers
900+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
900901
</button>
901902
</div>
902903
) : (

apps/desktop/src/features/spaces/SpacesPage.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Button,
1717
useToast,
1818
ToastContainer,
19+
useConfirm,
1920
} from '@mcpmux/ui';
2021
import {
2122
useAppStore,
@@ -39,6 +40,7 @@ export function SpacesPage() {
3940
const [searchQuery, setSearchQuery] = useState('');
4041
const [error, setError] = useState<string | null>(null);
4142
const [isActionLoading, setIsActionLoading] = useState<string | null>(null); // ID of space being acted on
43+
const { confirm, ConfirmDialogElement } = useConfirm();
4244
const { toasts, success, error: showError, dismiss } = useToast();
4345

4446
// Create Modal State
@@ -69,7 +71,13 @@ export function SpacesPage() {
6971
};
7072

7173
const handleDelete = async (id: string) => {
72-
if (!confirm('Are you sure you want to delete this space? This action cannot be undone.')) return;
74+
const spaceName = spaces.find(s => s.id === id)?.name || 'this space';
75+
if (!await confirm({
76+
title: 'Delete workspace',
77+
message: `Are you sure you want to delete "${spaceName}"? This action cannot be undone.`,
78+
confirmLabel: 'Delete',
79+
variant: 'danger',
80+
})) return;
7381

7482
setIsActionLoading(id);
7583
setError(null);
@@ -117,6 +125,7 @@ export function SpacesPage() {
117125
return (
118126
<>
119127
<ToastContainer toasts={toasts} onClose={dismiss} />
128+
{ConfirmDialogElement}
120129
<div className="h-full flex flex-col relative" data-testid="spaces-page">
121130
{/* Header */}
122131
<div className="flex-shrink-0 p-8 border-b border-[rgb(var(--border-subtle))]">

0 commit comments

Comments
 (0)