mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 18:50:49 +08:00
fix: prevent async confirmation races in API key deletion
This commit is contained in:
@@ -32,6 +32,9 @@ export function ConfirmationModal() {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
@@ -39,7 +42,7 @@ export function ConfirmationModal() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={handleCancel} title={title}>
|
||||
<Modal open={isOpen} onClose={handleCancel} title={title} closeDisabled={isLoading}>
|
||||
<p style={{ margin: '1rem 0' }}>{message}</p>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '1rem', marginTop: '2rem' }}>
|
||||
<Button variant="ghost" onClick={handleCancel} disabled={isLoading}>
|
||||
|
||||
@@ -8,6 +8,7 @@ interface ModalProps {
|
||||
onClose: () => void;
|
||||
footer?: ReactNode;
|
||||
width?: number | string;
|
||||
closeDisabled?: boolean;
|
||||
}
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = 350;
|
||||
@@ -32,7 +33,15 @@ const unlockScroll = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export function Modal({ open, title, onClose, footer, width = 520, children }: PropsWithChildren<ModalProps>) {
|
||||
export function Modal({
|
||||
open,
|
||||
title,
|
||||
onClose,
|
||||
footer,
|
||||
width = 520,
|
||||
closeDisabled = false,
|
||||
children
|
||||
}: PropsWithChildren<ModalProps>) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -106,7 +115,13 @@ export function Modal({ open, title, onClose, footer, width = 520, children }: P
|
||||
const modalContent = (
|
||||
<div className={overlayClass}>
|
||||
<div className={modalClass} style={{ width }} role="dialog" aria-modal="true">
|
||||
<button className="modal-close-floating" onClick={handleClose} aria-label="Close">
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close-floating"
|
||||
onClick={closeDisabled ? undefined : handleClose}
|
||||
aria-label="Close"
|
||||
disabled={closeDisabled}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</button>
|
||||
<div className="modal-header">
|
||||
|
||||
@@ -115,14 +115,32 @@ export function ApiKeysPage() {
|
||||
};
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const apiKeyToDelete = apiKeys[index];
|
||||
if (!apiKeyToDelete) {
|
||||
showNotification(t('notification.delete_failed'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showConfirmation({
|
||||
title: t('common.delete'),
|
||||
message: t('api_keys.delete_confirm'),
|
||||
variant: 'danger',
|
||||
onConfirm: async () => {
|
||||
const latestKeys = useConfigStore.getState().config?.apiKeys;
|
||||
const currentKeys = Array.isArray(latestKeys) ? latestKeys : [];
|
||||
const deleteIndex =
|
||||
currentKeys[index] === apiKeyToDelete
|
||||
? index
|
||||
: currentKeys.findIndex((key) => key === apiKeyToDelete);
|
||||
|
||||
if (deleteIndex < 0) {
|
||||
showNotification(t('notification.delete_failed'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiKeysApi.delete(index);
|
||||
const nextKeys = apiKeys.filter((_, idx) => idx !== index);
|
||||
await apiKeysApi.delete(deleteIndex);
|
||||
const nextKeys = currentKeys.filter((_, idx) => idx !== deleteIndex);
|
||||
setApiKeys(nextKeys);
|
||||
updateConfigValue('api-keys', nextKeys);
|
||||
clearCache('api-keys');
|
||||
|
||||
@@ -453,6 +453,18 @@ textarea {
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:disabled:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-secondary);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
||||
Reference in New Issue
Block a user