diff --git a/dashboard/public/statics/locales/en.json b/dashboard/public/statics/locales/en.json index 25a715b5d..3b586764e 100644 --- a/dashboard/public/statics/locales/en.json +++ b/dashboard/public/statics/locales/en.json @@ -1873,7 +1873,11 @@ "shadowsocksPasswordGenerationFailed": "Failed to generate password", "realityTab": "Reality", "vlessTab": "VLESS", - "shadowsocksTab": "ShadowSocks" + "shadowsocksTab": "ShadowSocks", + "unsavedChanges": "Unsaved Changes", + "unsavedChangesMessage": "You have unsaved changes. Are you sure you want to exit without saving?", + "keepEditing": "Keep Editing", + "discardChanges": "Discard Changes" }, "settings.cores.title": "Cores", "settings.cores.description": "Manage Your Cores", diff --git a/dashboard/public/statics/locales/fa.json b/dashboard/public/statics/locales/fa.json index b3fa7c6ac..93093523f 100644 --- a/dashboard/public/statics/locales/fa.json +++ b/dashboard/public/statics/locales/fa.json @@ -1788,7 +1788,11 @@ "shadowsocksPasswordGenerationFailed": "تولید رمز عبور ناموفق بود", "realityTab": "Reality", "vlessTab": "VLESS", - "shadowsocksTab": "ShadowSocks" + "shadowsocksTab": "ShadowSocks", + "unsavedChanges": "تغییرات ذخیره نشده", + "unsavedChangesMessage": "شما تغییرات ذخیره نشده‌ای دارید. آیا مطمئن هستید که می‌خواهید بدون ذخیره خارج شوید؟", + "keepEditing": "ادامه ویرایش", + "discardChanges": "لغو تغییرات" }, "settings.cores.title": "هسته‌ها", "settings.cores.description": "مدیریت هسته‌های شما", diff --git a/dashboard/public/statics/locales/ru.json b/dashboard/public/statics/locales/ru.json index b3975890a..2150a1816 100644 --- a/dashboard/public/statics/locales/ru.json +++ b/dashboard/public/statics/locales/ru.json @@ -1762,7 +1762,11 @@ "shadowsocksPasswordGenerationFailed": "Не удалось сгенерировать пароль", "realityTab": "Reality", "vlessTab": "VLESS", - "shadowsocksTab": "ShadowSocks" + "shadowsocksTab": "ShadowSocks", + "unsavedChanges": "Несохраненные изменения", + "unsavedChangesMessage": "У вас есть несохраненные изменения. Вы уверены, что хотите выйти без сохранения?", + "keepEditing": "Продолжить редактирование", + "discardChanges": "Отменить изменения" }, "settings.cores.title": "Ядра", "settings.cores.description": "Управление вашими ядрами", diff --git a/dashboard/public/statics/locales/zh.json b/dashboard/public/statics/locales/zh.json index d9459bb42..a6d2a6d64 100644 --- a/dashboard/public/statics/locales/zh.json +++ b/dashboard/public/statics/locales/zh.json @@ -1835,7 +1835,11 @@ "shadowsocksPasswordGenerationFailed": "密码生成失败", "realityTab": "Reality", "vlessTab": "VLESS", - "shadowsocksTab": "ShadowSocks" + "shadowsocksTab": "ShadowSocks", + "unsavedChanges": "未保存的更改", + "unsavedChangesMessage": "您有未保存的更改。您确定要在不保存的情况下退出吗?", + "keepEditing": "继续编辑", + "discardChanges": "放弃更改" }, "settings.cores.title": "核心", "settings.cores.description": "管理您的核心", diff --git a/dashboard/src/components/dialogs/core-config-modal.tsx b/dashboard/src/components/dialogs/core-config-modal.tsx index 06f9d3ef1..b27997fea 100644 --- a/dashboard/src/components/dialogs/core-config-modal.tsx +++ b/dashboard/src/components/dialogs/core-config-modal.tsx @@ -2,6 +2,7 @@ import { CopyButton } from '@/components/common/copy-button' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -20,7 +21,7 @@ import { generateWireGuardKeyPair } from '@/utils/wireguard' import { encodeURLSafe } from '@stablelib/base64' import { generateKeyPair } from '@stablelib/x25519' import { debounce } from 'es-toolkit' -import { Info, Key, Maximize2, Minimize2, Sparkles, Shield, Pencil, Cpu } from 'lucide-react' +import { Info, Key, Maximize2, Minimize2, Sparkles, Shield, Pencil, Cpu, AlertTriangle } from 'lucide-react' import { MlKem768 } from 'mlkem' import { Suspense, lazy, useCallback, useEffect, useState } from 'react' import { UseFormReturn } from 'react-hook-form' @@ -185,6 +186,10 @@ export default function CoreConfigModal({ isDialogOpen, onOpenChange, form, edit const [generatedShadowsocksPassword, setGeneratedShadowsocksPassword] = useState<{ password: string; encryptionMethod: string } | null>(null) const [generatedMldsa65, setGeneratedMldsa65] = useState<{ seed: string; verify: string } | null>(null) const [generatedVLESS, setGeneratedVLESS] = useState(null) + + // Unsaved changes confirmation dialog state + const [isConfirmExitDialogOpen, setIsConfirmExitDialogOpen] = useState(false) + const handleVlessVariantChange = useCallback( (value: string) => { if (value === 'x25519' || value === 'mlkem768') { @@ -201,6 +206,30 @@ export default function CoreConfigModal({ isDialogOpen, onOpenChange, form, edit setIsResultsDialogOpen(true) }, []) + // Handle modal close with unsaved changes check + const handleModalCloseWithCheck = useCallback((open: boolean) => { + if (!open && form.formState.isDirty) { + setIsConfirmExitDialogOpen(true) + } else { + onOpenChange(open) + if (!open) { + form.reset() + } + } + }, [form, onOpenChange]) + + // Confirm exit without saving + const handleConfirmExit = useCallback(() => { + setIsConfirmExitDialogOpen(false) + form.reset() + onOpenChange(false) + }, [form, onOpenChange]) + + // Cancel exit + const handleCancelExit = useCallback(() => { + setIsConfirmExitDialogOpen(false) + }, []) + const relayoutEditor = useCallback( (editor = editorInstance) => { if (!editor) return @@ -736,6 +765,7 @@ export default function CoreConfigModal({ isDialogOpen, onOpenChange, form, edit setValidation({ isValid: true }) setEditorInstance(null) setIsEditorReady(false) + setIsConfirmExitDialogOpen(false) // Don't clear generated values - keep them for reuse } }, [isDialogOpen]) @@ -1325,7 +1355,7 @@ export default function CoreConfigModal({ isDialogOpen, onOpenChange, form, edit <> {renderVlessAdvancedModal()} {renderResultDialog()} - + @@ -1809,7 +1839,7 @@ export default function CoreConfigModal({ isDialogOpen, onOpenChange, form, edit )}
-
+ + {/* Unsaved Changes Confirmation Dialog */} + + + + + + {t('coreConfigModal.unsavedChanges', { defaultValue: 'Unsaved Changes' })} + + + {t('coreConfigModal.unsavedChangesMessage', { + defaultValue: 'You have unsaved changes. Are you sure you want to exit without saving?' + })} + + + + + {t('coreConfigModal.keepEditing', { defaultValue: 'Keep Editing' })} + + + + + ) }