fix: prevent page crash when deleting an agent (#514)
This commit is contained in:
committed by
GitHub
Unverified
parent
9e10c12f67
commit
158e84ce8f
@@ -2,7 +2,7 @@
|
|||||||
* ConfirmDialog - In-DOM confirmation dialog (replaces window.confirm)
|
* ConfirmDialog - In-DOM confirmation dialog (replaces window.confirm)
|
||||||
* Keeps focus within the renderer to avoid Windows focus loss after native dialogs.
|
* Keeps focus within the renderer to avoid Windows focus loss after native dialogs.
|
||||||
*/
|
*/
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
@@ -13,8 +13,9 @@ interface ConfirmDialogProps {
|
|||||||
confirmLabel?: string;
|
confirmLabel?: string;
|
||||||
cancelLabel?: string;
|
cancelLabel?: string;
|
||||||
variant?: 'default' | 'destructive';
|
variant?: 'default' | 'destructive';
|
||||||
onConfirm: () => void;
|
onConfirm: () => void | Promise<void>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
onError?: (error: unknown) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfirmDialog({
|
export function ConfirmDialog({
|
||||||
@@ -26,8 +27,19 @@ export function ConfirmDialog({
|
|||||||
variant = 'default',
|
variant = 'default',
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
onError,
|
||||||
}: ConfirmDialogProps) {
|
}: ConfirmDialogProps) {
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const [confirming, setConfirming] = useState(false);
|
||||||
|
const [prevOpen, setPrevOpen] = useState(open);
|
||||||
|
|
||||||
|
// Reset confirming when dialog closes (during render to avoid setState-in-effect)
|
||||||
|
if (prevOpen !== open) {
|
||||||
|
setPrevOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setConfirming(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && cancelRef.current) {
|
if (open && cancelRef.current) {
|
||||||
@@ -38,12 +50,27 @@ export function ConfirmDialog({
|
|||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape' && !confirming) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (confirming) return;
|
||||||
|
const result = onConfirm();
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
setConfirming(true);
|
||||||
|
result.catch((error) => {
|
||||||
|
if (onError) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
setConfirming(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||||
@@ -68,12 +95,14 @@ export function ConfirmDialog({
|
|||||||
ref={cancelRef}
|
ref={cancelRef}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
|
disabled={confirming}
|
||||||
>
|
>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={variant === 'destructive' ? 'destructive' : 'default'}
|
variant={variant === 'destructive' ? 'destructive' : 'default'}
|
||||||
onClick={onConfirm}
|
onClick={handleConfirm}
|
||||||
|
disabled={confirming}
|
||||||
>
|
>
|
||||||
{confirmLabel}
|
{confirmLabel}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"agentCreated": "Agent created",
|
"agentCreated": "Agent created",
|
||||||
"agentCreateFailed": "Failed to create agent: {{error}}",
|
"agentCreateFailed": "Failed to create agent: {{error}}",
|
||||||
"agentDeleted": "Agent deleted",
|
"agentDeleted": "Agent deleted",
|
||||||
|
"agentDeleteFailed": "Failed to delete agent: {{error}}",
|
||||||
"agentUpdated": "Agent updated",
|
"agentUpdated": "Agent updated",
|
||||||
"agentUpdateFailed": "Failed to update agent: {{error}}",
|
"agentUpdateFailed": "Failed to update agent: {{error}}",
|
||||||
"channelAssigned": "{{channel}} assigned to agent",
|
"channelAssigned": "{{channel}} assigned to agent",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"agentCreated": "Agent を作成しました",
|
"agentCreated": "Agent を作成しました",
|
||||||
"agentCreateFailed": "Agent の作成に失敗しました: {{error}}",
|
"agentCreateFailed": "Agent の作成に失敗しました: {{error}}",
|
||||||
"agentDeleted": "Agent を削除しました",
|
"agentDeleted": "Agent を削除しました",
|
||||||
|
"agentDeleteFailed": "Agent の削除に失敗しました: {{error}}",
|
||||||
"agentUpdated": "Agent を更新しました",
|
"agentUpdated": "Agent を更新しました",
|
||||||
"agentUpdateFailed": "Agent の更新に失敗しました: {{error}}",
|
"agentUpdateFailed": "Agent の更新に失敗しました: {{error}}",
|
||||||
"channelAssigned": "{{channel}} を Agent に割り当てました",
|
"channelAssigned": "{{channel}} を Agent に割り当てました",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"agentCreated": "Agent 已创建",
|
"agentCreated": "Agent 已创建",
|
||||||
"agentCreateFailed": "创建 Agent 失败:{{error}}",
|
"agentCreateFailed": "创建 Agent 失败:{{error}}",
|
||||||
"agentDeleted": "Agent 已删除",
|
"agentDeleted": "Agent 已删除",
|
||||||
|
"agentDeleteFailed": "删除 Agent 失败:{{error}}",
|
||||||
"agentUpdated": "Agent 已更新",
|
"agentUpdated": "Agent 已更新",
|
||||||
"agentUpdateFailed": "更新 Agent 失败:{{error}}",
|
"agentUpdateFailed": "更新 Agent 失败:{{error}}",
|
||||||
"channelAssigned": "{{channel}} 已分配给 Agent",
|
"channelAssigned": "{{channel}} 已分配给 Agent",
|
||||||
|
|||||||
@@ -153,12 +153,17 @@ export function Agents() {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
if (!agentToDelete) return;
|
if (!agentToDelete) return;
|
||||||
await deleteAgent(agentToDelete.id);
|
try {
|
||||||
setAgentToDelete(null);
|
await deleteAgent(agentToDelete.id);
|
||||||
if (activeAgentId === agentToDelete.id) {
|
const deletedId = agentToDelete.id;
|
||||||
setActiveAgentId(null);
|
setAgentToDelete(null);
|
||||||
|
if (activeAgentId === deletedId) {
|
||||||
|
setActiveAgentId(null);
|
||||||
|
}
|
||||||
|
toast.success(t('toast.agentDeleted'));
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('toast.agentDeleteFailed', { error: String(error) }));
|
||||||
}
|
}
|
||||||
toast.success(t('toast.agentDeleted'));
|
|
||||||
}}
|
}}
|
||||||
onCancel={() => setAgentToDelete(null)}
|
onCancel={() => setAgentToDelete(null)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user