fix(site): replace inline delete confirmations with modals (#23710)

This commit is contained in:
Danielle Maywood
2026-03-27 17:02:18 +00:00
committed by GitHub
parent bcdc35ee3e
commit 60065f6c08
9 changed files with 410 additions and 206 deletions
@@ -776,14 +776,15 @@ export const ModelDeleteConfirmation: Story = {
const deleteButton = await body.findByRole("button", { name: "Delete" });
await expect(deleteButton).toBeInTheDocument();
// Click Delete to show the inline confirmation.
// Click Delete to show the confirmation dialog.
await userEvent.click(deleteButton);
// The confirmation strip should appear — leave it visible
// The confirmation dialog should appear — leave it visible
// so the Chromatic snapshot captures this state.
await expect(
await body.findByText(/Are you sure\? This action is irreversible/i),
await body.findByText(/Are you sure you want to delete this model/i),
).toBeInTheDocument();
await expect(body.getByRole("dialog")).toBeInTheDocument();
await expect(
body.getByRole("button", { name: "Delete model" }),
).toBeInTheDocument();
@@ -820,19 +821,16 @@ export const ModelDeleteCancelled: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Navigate to edit form, trigger confirmation, then cancel.
// Navigate to edit form, open delete dialog, then cancel.
await userEvent.click(await body.findByText("GPT-4o"));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await body.findByText(/Are you sure/i);
await userEvent.click(body.getByRole("button", { name: "Cancel" }));
// Normal footer should be restored.
await expect(
await body.findByRole("button", { name: "Delete" }),
).toBeInTheDocument();
await expect(
await body.findByRole("button", { name: "Save" }),
).toBeInTheDocument();
// The dialog should be closed.
await waitFor(() => {
expect(body.queryByRole("dialog")).not.toBeInTheDocument();
});
},
};
@@ -863,7 +861,7 @@ export const ModelDeleteConfirmed: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Navigate to edit form, trigger delete confirmation, then confirm.
// Navigate to edit form, open delete dialog, then confirm.
await userEvent.click(await body.findByText("GPT-4o"));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await userEvent.click(
@@ -903,15 +901,16 @@ export const ProviderDeleteConfirmation: Story = {
// Navigate to the provider detail view.
await userEvent.click(await body.findByRole("button", { name: /OpenAI/i }));
// Click Delete to show the inline confirmation.
// Click Delete to show the confirmation dialog.
const deleteButton = await body.findByRole("button", { name: "Delete" });
await userEvent.click(deleteButton);
// The confirmation strip should appear — leave it visible
// The confirmation dialog should appear — leave it visible
// so the Chromatic snapshot captures this state.
await expect(
await body.findByText(/Are you sure\? This action is irreversible/i),
await body.findByText(/Are you sure you want to delete this provider/i),
).toBeInTheDocument();
await expect(body.getByRole("dialog")).toBeInTheDocument();
await expect(
body.getByRole("button", { name: "Delete provider" }),
).toBeInTheDocument();
@@ -941,19 +940,16 @@ export const ProviderDeleteCancelled: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Navigate to provider detail, trigger confirmation, then cancel.
// Navigate to provider detail, open delete dialog, then cancel.
await userEvent.click(await body.findByRole("button", { name: /OpenAI/i }));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await body.findByText(/Are you sure/i);
await userEvent.click(body.getByRole("button", { name: "Cancel" }));
// Normal footer should be restored.
await expect(
await body.findByRole("button", { name: "Delete" }),
).toBeInTheDocument();
await expect(
await body.findByRole("button", { name: "Save changes" }),
).toBeInTheDocument();
// The dialog should be closed.
await waitFor(() => {
expect(body.queryByRole("dialog")).not.toBeInTheDocument();
});
},
};
@@ -977,7 +973,7 @@ export const ProviderDeleteConfirmed: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Navigate to provider detail, trigger delete, then confirm.
// Navigate to provider detail, open delete dialog, then confirm.
await userEvent.click(await body.findByRole("button", { name: /OpenAI/i }));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await userEvent.click(
@@ -8,6 +8,14 @@ import { type FC, useState } from "react";
import * as Yup from "yup";
import type * as TypesGen from "#/api/typesGenerated";
import { Button } from "#/components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { Input } from "#/components/Input/Input";
import { Label } from "#/components/Label/Label";
import {
@@ -307,7 +315,6 @@ export const ModelForm: FC<ModelFormProps> = ({
<ChevronLeftIcon className="h-4 w-4" />
Back
</button>
{/* Header — editable display name */}
<div className="flex items-center gap-3">
{selectedProviderState && (
@@ -352,7 +359,6 @@ export const ModelForm: FC<ModelFormProps> = ({
)}
</div>
<hr className="my-4 border-0 border-t border-solid border-border" />
{/* Form body */}
<form className="flex flex-1 flex-col" onSubmit={form.handleSubmit}>
<div className="space-y-5">
@@ -539,68 +545,72 @@ export const ModelForm: FC<ModelFormProps> = ({
{/* Footer — pushed to bottom */}
<div className="mt-auto py-6">
<hr className="mb-4 border-0 border-t border-solid border-border" />
{confirmingDelete && onDeleteModel && editingModel ? (
<div className="flex items-center gap-3">
<p className="m-0 flex-1 text-sm text-content-secondary">
Are you sure? This action is irreversible.
</p>
<div className="flex shrink-0 items-center gap-2">
<Button
variant="outline"
size="lg"
type="button"
onClick={() => setConfirmingDelete(false)}
disabled={isDeleting}
>
Cancel
</Button>
<Button
variant="destructive"
size="lg"
type="button"
disabled={isDeleting}
onClick={() => void onDeleteModel(editingModel.id)}
>
{isDeleting && <Spinner className="h-4 w-4" loading />}
Delete model
</Button>
</div>
</div>
) : (
<div className="flex items-center justify-between">
{isEditing && editingModel && onDeleteModel ? (
<Button
variant="outline"
size="lg"
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isSaving}
onClick={() => setConfirmingDelete(true)}
>
Delete
</Button>
) : (
<Button
variant="outline"
size="lg"
type="button"
onClick={onCancel}
>
Cancel
</Button>
)}
<div className="flex items-center justify-between">
{isEditing && editingModel && onDeleteModel ? (
<Button
variant="outline"
size="lg"
type="submit"
disabled={isSaving || !form.isValid || hasFieldErrors}
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isSaving}
onClick={() => setConfirmingDelete(true)}
>
{isSaving && <Spinner className="h-4 w-4" loading />}{" "}
{isEditing ? "Save" : "Add model"}
Delete
</Button>
</div>
)}
) : (
<Button
variant="outline"
size="lg"
type="button"
onClick={onCancel}
>
Cancel
</Button>
)}
<Button
size="lg"
type="submit"
disabled={isSaving || !form.isValid || hasFieldErrors}
>
{isSaving && <Spinner className="h-4 w-4" loading />}{" "}
{isEditing ? "Save" : "Add model"}
</Button>
</div>
</div>
</form>
{editingModel && onDeleteModel && (
<Dialog
open={confirmingDelete}
onOpenChange={(open) => !open && setConfirmingDelete(false)}
>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Delete model</DialogTitle>
<DialogDescription>
Are you sure you want to delete this model? This action is
irreversible.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setConfirmingDelete(false)}
disabled={isDeleting}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => void onDeleteModel(editingModel.id)}
disabled={isDeleting}
>
{isDeleting && <Spinner className="h-4 w-4" loading />}
Delete model
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}{" "}
</div>
);
};
@@ -3,6 +3,14 @@ import { type FC, type FormEvent, useId, useState } from "react";
import type * as TypesGen from "#/api/typesGenerated";
import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert";
import { Button } from "#/components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { Input } from "#/components/Input/Input";
import { Spinner } from "#/components/Spinner/Spinner";
import {
@@ -267,62 +275,67 @@ export const ProviderForm: FC<ProviderFormProps> = ({
{/* Footer — pushed to bottom */}
<div className="mt-auto pt-6">
<hr className="mb-4 border-0 border-t border-solid border-border" />
{confirmingDelete && providerConfig ? (
<div className="flex items-center gap-3">
<p className="m-0 flex-1 text-sm text-content-secondary">
Are you sure? This action is irreversible.
</p>
<div className="flex shrink-0 items-center gap-2">
<Button
variant="outline"
size="lg"
type="button"
onClick={() => setConfirmingDelete(false)}
disabled={isProviderMutationPending}
>
Cancel
</Button>
<Button
variant="destructive"
size="lg"
type="button"
disabled={isProviderMutationPending}
onClick={() => void onDeleteProvider(providerConfig.id)}
>
{isProviderMutationPending && (
<Spinner className="h-4 w-4" loading />
)}
Delete provider
</Button>
</div>
</div>
) : (
<div className="flex items-center justify-between">
{providerConfig ? (
<Button
variant="outline"
size="lg"
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isDisabled}
onClick={() => setConfirmingDelete(true)}
>
Delete
</Button>
) : (
<div />
)}
<Button size="lg" type="submit" disabled={!canSave}>
{isProviderMutationPending && (
<Spinner className="h-4 w-4" loading />
)}
{providerConfig ? "Save changes" : "Create provider config"}
<div className="flex items-center justify-between">
{providerConfig ? (
<Button
variant="outline"
size="lg"
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isDisabled}
onClick={() => setConfirmingDelete(true)}
>
Delete
</Button>
</div>
)}
) : (
<div />
)}
<Button size="lg" type="submit" disabled={!canSave}>
{isProviderMutationPending && (
<Spinner className="h-4 w-4" loading />
)}
{providerConfig ? "Save changes" : "Create provider config"}
</Button>
</div>
</div>
</form>
)}
{providerConfig && (
<Dialog
open={confirmingDelete}
onOpenChange={(open) => !open && setConfirmingDelete(false)}
>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Delete provider</DialogTitle>
<DialogDescription>
Are you sure you want to delete this provider? This action is
irreversible.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setConfirmingDelete(false)}
disabled={isProviderMutationPending}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => void onDeleteProvider(providerConfig.id)}
disabled={isProviderMutationPending}
>
{isProviderMutationPending && (
<Spinner className="h-4 w-4" loading />
)}
Delete provider
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</div>
);
};
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, fn, within } from "storybook/test";
import { expect, fn, userEvent, waitFor, within } from "storybook/test";
import type { Group } from "#/api/typesGenerated";
import { GroupLimitsSection } from "./GroupLimitsSection";
@@ -131,3 +131,46 @@ export const EditForm: Story = {
expect(nameElements.length).toBeGreaterThanOrEqual(2);
},
};
export const DeleteGroupOverride: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Click the Delete button for the first group override.
const deleteButtons = await body.findAllByRole("button", {
name: "Delete",
});
await userEvent.click(deleteButtons[0]);
// The confirmation dialog should appear.
const dialog = await body.findByRole("dialog");
await expect(dialog).toBeInTheDocument();
await expect(
body.getByText(
/Are you sure you want to delete this group limit override/i,
),
).toBeInTheDocument();
},
};
export const DeleteGroupOverrideCancelled: Story = {
play: async ({ canvasElement, args }) => {
const body = within(canvasElement.ownerDocument.body);
// Click the Delete button for the first group override.
const deleteButtons = await body.findAllByRole("button", {
name: "Delete",
});
await userEvent.click(deleteButtons[0]);
// Cancel the dialog.
await body.findByRole("dialog");
await userEvent.click(body.getByRole("button", { name: "Cancel" }));
// The dialog should be closed and the callback should not have been called.
await waitFor(() => {
expect(body.queryByRole("dialog")).not.toBeInTheDocument();
});
expect(args.onDeleteGroupOverride).not.toHaveBeenCalled();
},
};
@@ -1,10 +1,18 @@
import { Check } from "lucide-react";
import { type FC, useId } from "react";
import { type FC, useId, useState } from "react";
import { getErrorMessage } from "#/api/errors";
import type { Group } from "#/api/typesGenerated";
import { Autocomplete } from "#/components/Autocomplete/Autocomplete";
import { AvatarData } from "#/components/Avatar/AvatarData";
import { Button } from "#/components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { Input } from "#/components/Input/Input";
import { Label } from "#/components/Label/Label";
import { Spinner } from "#/components/Spinner/Spinner";
@@ -84,6 +92,9 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
const groupAutocompleteId = useId();
const groupAmountId = useId();
const isEditing = editingGroupOverride !== null;
const [pendingDeleteGroupId, setPendingDeleteGroupId] = useState<
string | null
>(null);
return (
<section className="space-y-4">
@@ -91,7 +102,6 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
label="Group Limits"
description="Override the default limit for specific groups. When a user belongs to multiple groups, the lowest group limit applies."
/>
<div className="space-y-4">
{groupOverrides.length > 0 ? (
<Table>
@@ -136,7 +146,7 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
size="sm"
type="button"
onClick={() =>
void onDeleteGroupOverride(override.group_id)
setPendingDeleteGroupId(override.group_id)
}
disabled={deletePending || upsertPending || isEditing}
>
@@ -287,6 +297,42 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
</p>
)}
</div>
{pendingDeleteGroupId && (
<Dialog
open
onOpenChange={(open) => !open && setPendingDeleteGroupId(null)}
>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Delete group override</DialogTitle>
<DialogDescription>
Are you sure you want to delete this group limit override? This
action is irreversible.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setPendingDeleteGroupId(null)}
disabled={deletePending}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => {
void onDeleteGroupOverride(pendingDeleteGroupId);
setPendingDeleteGroupId(null);
}}
disabled={deletePending}
>
{deletePending && <Spinner className="h-4 w-4" loading />}
Delete override
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}{" "}
</section>
);
};
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { QueryClient, QueryClientProvider } from "react-query";
import { expect, fn, within } from "storybook/test";
import { expect, fn, userEvent, waitFor, within } from "storybook/test";
import { UserOverridesSection } from "./UserOverridesSection";
const queryClient = new QueryClient({
@@ -108,3 +108,46 @@ export const EditForm: Story = {
expect(nameElements.length).toBeGreaterThanOrEqual(2);
},
};
export const DeleteUserOverride: Story = {
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
// Click the Delete button for the first user override.
const deleteButtons = await body.findAllByRole("button", {
name: "Delete",
});
await userEvent.click(deleteButtons[0]);
// The confirmation dialog should appear.
const dialog = await body.findByRole("dialog");
await expect(dialog).toBeInTheDocument();
await expect(
body.getByText(
/Are you sure you want to delete this user limit override/i,
),
).toBeInTheDocument();
},
};
export const DeleteUserOverrideCancelled: Story = {
play: async ({ canvasElement, args }) => {
const body = within(canvasElement.ownerDocument.body);
// Click the Delete button for the first user override.
const deleteButtons = await body.findAllByRole("button", {
name: "Delete",
});
await userEvent.click(deleteButtons[0]);
// Cancel the dialog.
await body.findByRole("dialog");
await userEvent.click(body.getByRole("button", { name: "Cancel" }));
// The dialog should be closed and the callback should not have been called.
await waitFor(() => {
expect(body.queryByRole("dialog")).not.toBeInTheDocument();
});
expect(args.onDeleteOverride).not.toHaveBeenCalled();
},
};
@@ -1,8 +1,16 @@
import { type FC, useId } from "react";
import { type FC, useId, useState } from "react";
import { getErrorMessage } from "#/api/errors";
import type { User } from "#/api/typesGenerated";
import { AvatarData } from "#/components/Avatar/AvatarData";
import { Button } from "#/components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { Input } from "#/components/Input/Input";
import { Label } from "#/components/Label/Label";
import { Spinner } from "#/components/Spinner/Spinner";
@@ -73,6 +81,9 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
}) => {
const userOverrideAmountId = useId();
const isEditing = editingUserOverride !== null;
const [pendingDeleteUserId, setPendingDeleteUserId] = useState<string | null>(
null,
);
return (
<section className="space-y-4">
@@ -80,7 +91,6 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
label="Per-User Overrides"
description="Override the deployment default spend limit for specific users. User overrides take highest priority, followed by group limits, then the deployment default."
/>
<div className="space-y-4">
{overrides.length > 0 ? (
<Table>
@@ -122,7 +132,7 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
variant="outline"
size="sm"
type="button"
onClick={() => void onDeleteOverride(override.user_id)}
onClick={() => setPendingDeleteUserId(override.user_id)}
disabled={deletePending || upsertPending || isEditing}
>
Delete
@@ -246,6 +256,42 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
</p>
)}
</div>
{pendingDeleteUserId && (
<Dialog
open
onOpenChange={(open) => !open && setPendingDeleteUserId(null)}
>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Delete user override</DialogTitle>
<DialogDescription>
Are you sure you want to delete this user limit override? This
action is irreversible.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setPendingDeleteUserId(null)}
disabled={deletePending}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => {
void onDeleteOverride(pendingDeleteUserId);
setPendingDeleteUserId(null);
}}
disabled={deletePending}
>
{deletePending && <Spinner className="h-4 w-4" loading />}
Delete override
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}{" "}
</section>
);
};
@@ -480,7 +480,7 @@ export const EditServerWithCustomHeaders: Story = {
},
};
/** Delete a server with confirmation step. */
/** Delete a server shows confirmation dialog. */
export const DeleteServerConfirmation: Story = {
beforeEach: () => {
setupMCPSpies({
@@ -501,14 +501,15 @@ export const DeleteServerConfirmation: Story = {
// Click Delete.
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
// Confirmation should appear.
// Confirmation dialog should appear.
await expect(
await body.findByText(/Are you sure\? This action is irreversible/i),
await body.findByText(/Are you sure you want to delete this MCP server/i),
).toBeInTheDocument();
await expect(body.getByRole("dialog")).toBeInTheDocument();
},
};
/** Cancel delete returns to normal form footer. */
/** Cancel delete closes the dialog. */
export const DeleteServerCancelled: Story = {
beforeEach: () => {
setupMCPSpies({
@@ -526,20 +527,17 @@ export const DeleteServerCancelled: Story = {
await userEvent.click(await body.findByRole("button", { name: /Sentry/ }));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await body.findByText(/Are you sure/i);
await body.findByText(/Are you sure you want to delete this MCP server/i);
await userEvent.click(body.getByRole("button", { name: "Cancel" }));
// Normal footer should be restored.
await expect(
await body.findByRole("button", { name: "Delete" }),
).toBeInTheDocument();
expect(
body.getByRole("button", { name: /Save changes/i }),
).toBeInTheDocument();
// The dialog should be closed.
await waitFor(() => {
expect(body.queryByRole("dialog")).not.toBeInTheDocument();
});
},
};
/** Confirm delete calls the API. */
/** Confirm delete in dialog calls the API. */
export const DeleteServerConfirmed: Story = {
beforeEach: () => {
setupMCPSpies({
@@ -557,7 +555,6 @@ export const DeleteServerConfirmed: Story = {
await userEvent.click(await body.findByRole("button", { name: /Sentry/ }));
await userEvent.click(await body.findByRole("button", { name: "Delete" }));
await body.findByText(/Are you sure/i);
await userEvent.click(body.getByRole("button", { name: /Delete server/i }));
await waitFor(() => {
@@ -20,6 +20,14 @@ import {
import type * as TypesGen from "#/api/typesGenerated";
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
import { Button } from "#/components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { ExternalImage } from "#/components/ExternalImage/ExternalImage";
import { IconField } from "#/components/IconField/IconField";
import { Input } from "#/components/Input/Input";
@@ -361,7 +369,6 @@ const ServerForm: FC<ServerFormProps> = ({
<ChevronLeftIcon className="h-4 w-4" />
Back
</button>
{/* Header with icon + editable name + enabled toggle */}
<div className="flex items-center gap-3">
<MCPServerIcon
@@ -402,7 +409,6 @@ const ServerForm: FC<ServerFormProps> = ({
</Tooltip>
</div>
<hr className="my-4 border-0 border-t border-solid border-border" />
<form
id={formId}
onSubmit={form.handleSubmit}
@@ -826,57 +832,61 @@ const ServerForm: FC<ServerFormProps> = ({
{/* Footer — pushed to bottom, matches ProviderForm */}
<div className="mt-auto pt-6">
<hr className="mb-4 border-0 border-t border-solid border-border" />
{confirmingDelete && server ? (
<div className="flex items-center gap-3">
<p className="m-0 flex-1 text-sm text-content-secondary">
Are you sure? This action is irreversible.
</p>
<div className="flex shrink-0 items-center gap-2">
<Button
variant="outline"
size="lg"
type="button"
onClick={() => setConfirmingDelete(false)}
disabled={isDisabled}
>
Cancel
</Button>
<Button
variant="destructive"
size="lg"
type="button"
disabled={isDisabled}
onClick={() => void onDelete(server.id)}
>
{isDeleting && <Spinner className="h-4 w-4" loading />}
Delete server
</Button>
</div>
</div>
) : (
<div className="flex items-center justify-between">
{isEditing ? (
<Button
variant="outline"
size="lg"
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isDisabled}
onClick={() => setConfirmingDelete(true)}
>
Delete
</Button>
) : (
<div />
)}
<Button size="lg" type="submit" disabled={!canSubmit}>
{isSaving && <Spinner className="h-4 w-4" loading />}
{isEditing ? "Save changes" : "Create server"}
<div className="flex items-center justify-between">
{isEditing ? (
<Button
variant="outline"
size="lg"
type="button"
className="text-content-secondary hover:text-content-destructive hover:border-border-destructive"
disabled={isDisabled}
onClick={() => setConfirmingDelete(true)}
>
Delete
</Button>
</div>
)}
) : (
<div />
)}
<Button size="lg" type="submit" disabled={!canSubmit}>
{isSaving && <Spinner className="h-4 w-4" loading />}
{isEditing ? "Save changes" : "Create server"}
</Button>
</div>
</div>
</form>
{server && (
<Dialog
open={confirmingDelete}
onOpenChange={(open) => !open && setConfirmingDelete(false)}
>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Delete server</DialogTitle>
<DialogDescription>
Are you sure you want to delete this MCP server? This action is
irreversible.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setConfirmingDelete(false)}
disabled={isDisabled}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => void onDelete(server.id)}
disabled={isDisabled}
>
{isDeleting && <Spinner className="h-4 w-4" loading />}
Delete server
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}{" "}
</div>
);
};