fix(ui): replace browser confirm dialogs with ConfirmModal component (#1060)

Replace 10 instances of native browser confirm() dialogs with the
existing ConfirmModal component for consistent UX across the dashboard.

Changes:
- Add ConfirmModal to 5 files (combos, endpoints, proxy pools, providers, connections)
- Maintain same confirmation flow with improved styling
- Use 'danger' variant for destructive actions
- Preserve all existing functionality

Affected areas:
- Combo deletion (combos page)
- API key deletion/pausing (EndpointPageClient)
- Proxy pool management (single/bulk delete, disable dead proxies)
- Provider operations (disable all models, delete connection, delete compatible node)
- Connection management (ConnectionsCard)

All changes manually tested and verified.
This commit is contained in:
Zanuar Tri Romadon authored and GitHub committed 2026-05-13 15:32:17 +07:00
1 parent 5cab23d92e
commit 3c2503c4b4
5 files changed
+225 -114

No files matched your search

@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback } from "react";
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal, NoAuthProxyCard } from "@/shared/components";
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal, NoAuthProxyCard, ConfirmModal } from "@/shared/components";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS, THINKING_CONFIG } from "@/shared/constants/providers";
import { getModelsByProviderId } from "@/shared/constants/models";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
@@ -42,12 +42,13 @@ export default function ProviderDetailPage() {
const [selectedConnectionIds, setSelectedConnectionIds] = useState([]);
const [bulkProxyPoolId, setBulkProxyPoolId] = useState("__none__");
const [bulkUpdatingProxy, setBulkUpdatingProxy] = useState(false);
const [providerStrategy, setProviderStrategy] = useState(null); // null = use global, "round-robin" = override
const [providerStrategy, setProviderStrategy] = useState(null);
const [providerStickyLimit, setProviderStickyLimit] = useState("");
const [thinkingMode, setThinkingMode] = useState("auto");
const [suggestedModels, setSuggestedModels] = useState([]);
const [kiloFreeModels, setKiloFreeModels] = useState([]);
const [disabledModelIds, setDisabledModelIds] = useState([]);
const [confirmState, setConfirmState] = useState(null);
const { copied, copy } = useCopyToClipboard();
const providerInfo = providerNode
@@ -110,17 +111,23 @@ export default function ProviderDetailPage() {
const handleDisableAll = async (ids) => {
if (!ids.length) return;
if (!confirm(`Disable all ${ids.length} model(s)?`)) return;
try {
const res = await fetch("/api/models/disabled", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ providerAlias: providerStorageAlias, ids }),
});
if (res.ok) await fetchDisabledModels();
} catch (error) {
console.log("Error disabling all models:", error);
}
setConfirmState({
title: "Disable All Models",
message: `Disable all ${ids.length} model(s)?`,
onConfirm: async () => {
setConfirmState(null);
try {
const res = await fetch("/api/models/disabled", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ providerAlias: providerStorageAlias, ids }),
});
if (res.ok) await fetchDisabledModels();
} catch (error) {
console.log("Error disabling all models:", error);
}
}
});
};
const handleEnableAll = async () => {
@@ -338,15 +345,21 @@ export default function ProviderDetailPage() {
};
const handleDelete = async (id) => {
if (!confirm("Delete this connection?")) return;
try {
const res = await fetch(`/api/providers/${id}`, { method: "DELETE" });
if (res.ok) {
setConnections(connections.filter(c => c.id !== id));
setConfirmState({
title: "Delete Connection",
message: "Delete this connection?",
onConfirm: async () => {
setConfirmState(null);
try {
const res = await fetch(`/api/providers/${id}`, { method: "DELETE" });
if (res.ok) {
setConnections(connections.filter(c => c.id !== id));
}
} catch (error) {
console.log("Error deleting connection:", error);
}
}
} catch (error) {
console.log("Error deleting connection:", error);
}
});
};
const handleOAuthSuccess = () => {
@@ -954,15 +967,21 @@ export default function ProviderDetailPage() {
variant="secondary"
icon="delete"
onClick={async () => {
if (!confirm(`Delete this ${isAnthropicCompatible ? "Anthropic" : "OpenAI"} Compatible node?`)) return;
try {
const res = await fetch(`/api/provider-nodes/${providerId}`, { method: "DELETE" });
if (res.ok) {
router.push("/dashboard/providers");
setConfirmState({
title: "Delete Compatible Node",
message: `Delete this ${isAnthropicCompatible ? "Anthropic" : "OpenAI"} Compatible node?`,
onConfirm: async () => {
setConfirmState(null);
try {
const res = await fetch(`/api/provider-nodes/${providerId}`, { method: "DELETE" });
if (res.ok) {
router.push("/dashboard/providers");
}
} catch (error) {
console.log("Error deleting provider node:", error);
}
}
} catch (error) {
console.log("Error deleting provider node:", error);
}
});
}}
className="w-full sm:w-auto"
>
@@ -1222,6 +1241,16 @@ export default function ProviderDetailPage() {
onClose={() => setShowAddCustomModel(false)}
/>
)}
{/* Confirm Modal */}
<ConfirmModal
isOpen={!!confirmState}
onClose={() => setConfirmState(null)}
onConfirm={confirmState?.onConfirm}
title={confirmState?.title || "Confirm"}
message={confirmState?.message}
variant="danger"
/>
</div>
);
}
@@ -2,7 +2,7 @@
import { useState, useEffect, useCallback, useRef } from "react";
import PropTypes from "prop-types";
import { Card, Badge, Button, Modal, Select, Toggle, EditConnectionModal } from "@/shared/components";
import { Card, Badge, Button, Modal, Select, Toggle, EditConnectionModal, ConfirmModal } from "@/shared/components";
// ── CooldownTimer ──────────────────────────────────────────────
function CooldownTimer({ until }) {
@@ -308,6 +308,7 @@ export default function ConnectionsCard({ providerId, isOAuth }) {
const [selectedConnection, setSelectedConnection] = useState(null);
const [providerStrategy, setProviderStrategy] = useState(null);
const [providerStickyLimit, setProviderStickyLimit] = useState("1");
const [confirmState, setConfirmState] = useState(null);
const fetch_ = useCallback(async () => {
try {
@@ -358,11 +359,17 @@ export default function ConnectionsCard({ providerId, isOAuth }) {
};
const handleDelete = async (id) => {
if (!confirm("Delete this connection?")) return;
try {
const res = await fetch(`/api/providers/${id}`, { method: "DELETE" });
if (res.ok) setConnections((prev) => prev.filter((c) => c.id !== id));
} catch (e) { console.log("delete error:", e); }
setConfirmState({
title: "Delete Connection",
message: "Delete this connection?",
onConfirm: async () => {
setConfirmState(null);
try {
const res = await fetch(`/api/providers/${id}`, { method: "DELETE" });
if (res.ok) setConnections((prev) => prev.filter((c) => c.id !== id));
} catch (e) { console.log("delete error:", e); }
}
});
};
const handleToggleActive = async (id, isActive) => {
@@ -470,6 +477,16 @@ export default function ConnectionsCard({ providerId, isOAuth }) {
onSave={handleUpdateConnection}
onClose={() => setShowEditModal(false)}
/>
{/* Confirm Modal */}
<ConfirmModal
isOpen={!!confirmState}
onClose={() => setConfirmState(null)}
onConfirm={confirmState?.onConfirm}
title={confirmState?.title || "Confirm"}
message={confirmState?.message}
variant="danger"
/>
</>
);
}