fix: standardize button order, add confirmation dialogs for destructive actions

- Fix Cancel|Save order and add primary/loading props (AppConfigDetailPage)
- Add AlertDialog before stopping deployments (AppsTab)
- Add ConfirmDialog before deleting taps (TapConfigModal)
- Add AlertDialog before killing queries with toast feedback (DatabaseAdminPage)
- Add AlertDialog before removing roles from users (UsersTab)
- Standardize Cancel button to variant="ghost" (TapConfigModal, RouteDetail)
- Add loading prop to ConfirmDialogs (OidcConfigPage, RouteDetail)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-09 18:39:22 +02:00
parent 3d910af491
commit fb53dc6dfc
7 changed files with 95 additions and 31 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router';
import {
AlertDialog,
Badge,
Button,
ConfirmDialog,
@@ -506,6 +507,7 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
const fileInputRef = useRef<HTMLInputElement>(null);
const [subTab, setSubTab] = useState<'overview' | 'config'>('config');
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [stopTarget, setStopTarget] = useState<{ id: string; name: string } | null>(null);
const envMap = useMemo(() => new Map(environments.map((e) => [e.id, e])), [environments]);
const sortedVersions = useMemo(() => [...versions].sort((a, b) => b.version - a.version), [versions]);
@@ -531,11 +533,17 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
} catch { toast({ title: 'Deploy failed', variant: 'error', duration: 86_400_000 }); }
}
async function handleStop(deploymentId: string) {
function handleStop(deploymentId: string) {
setStopTarget({ id: deploymentId, name: app?.displayName ?? appSlug });
}
async function confirmStop() {
if (!stopTarget) return;
try {
await stopDeployment.mutateAsync({ appId: appSlug, deploymentId });
await stopDeployment.mutateAsync({ appId: appSlug, deploymentId: stopTarget.id });
toast({ title: 'Deployment stopped', variant: 'warning' });
} catch { toast({ title: 'Stop failed', variant: 'error', duration: 86_400_000 }); }
setStopTarget(null);
}
async function handleDelete() {
@@ -602,6 +610,15 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
confirmText={app.slug}
loading={deleteApp.isPending}
/>
<AlertDialog
open={!!stopTarget}
onClose={() => setStopTarget(null)}
onConfirm={confirmStop}
title="Stop deployment?"
description={`Stop deployment for "${stopTarget?.name}"? This will take the service offline.`}
confirmLabel="Stop"
variant="warning"
/>
</div>
);
}