feat: integrate sonner for toast notifications and enhance UI feedback
Added the sonner library for toast notifications across various components, improving user feedback for actions such as saving settings, syncing rules, and handling errors. Updated the layout to include a Toaster component for consistent notification display. Refactored alert messages in the backups, gre, and filters pages to utilize the new notification system, enhancing overall user experience.
This commit is contained in:
+10
-15
@@ -7,6 +7,7 @@ import type { GrePool, GreTunnel, GreStatus, IpsecEncAlg, IpsecAuthAlg, IpsecDhG
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import { requestJson } from "@/shared/api/http-client"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toast } from "sonner"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
EyeIcon, EyeOffIcon, ChevronDownIcon, ChevronRightIcon,
|
||||
CodeXmlIcon, PencilIcon, PowerIcon, Trash2Icon, CopyIcon, CheckIcon,
|
||||
DatabaseIcon,
|
||||
AlertCircleIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
// ─── label maps ─────────────────────────────────────────────────────────────
|
||||
@@ -281,7 +281,6 @@ export default function GrePage() {
|
||||
const [dataLoading, setDataLoading] = useState(false)
|
||||
const [dataError, setDataError] = useState<string | null>(null)
|
||||
const [syncJhBusy, setSyncJhBusy] = useState(false)
|
||||
const [syncJhMessage, setSyncJhMessage] = useState<string | null>(null)
|
||||
|
||||
const [pageTab, setPageTab] = useState<PageTab>("tunnels")
|
||||
const [tabFilter, setTabFilter] = useState<TabFilter>("all")
|
||||
@@ -302,7 +301,6 @@ export default function GrePage() {
|
||||
if (!isLive) return
|
||||
setDataLoading(true)
|
||||
setDataError(null)
|
||||
setSyncJhMessage(null)
|
||||
try {
|
||||
const [backendServers, greRes] = await Promise.all([
|
||||
apiFetch<BackendServer[]>("/api/servers"),
|
||||
@@ -353,11 +351,10 @@ export default function GrePage() {
|
||||
if (!isLive || syncJhBusy) return
|
||||
const jh = displayServers.filter((s) => s.type === "jump-host" && s.enabled)
|
||||
if (jh.length === 0) {
|
||||
setSyncJhMessage("Нет включённых Jump Host в списке серверов")
|
||||
toast.info("Нет включённых Jump Host в списке серверов")
|
||||
return
|
||||
}
|
||||
setSyncJhBusy(true)
|
||||
setSyncJhMessage(null)
|
||||
const errors: string[] = []
|
||||
try {
|
||||
for (const s of jh) {
|
||||
@@ -373,17 +370,21 @@ export default function GrePage() {
|
||||
const fresh = await apiFetch<GreTunnelsApiResponse>("/api/filters/gre-tunnels")
|
||||
setLiveTunnels(fresh.tunnels)
|
||||
if (errors.length) {
|
||||
setSyncJhMessage(`Синхронизировано JH: ${jh.length - errors.length}/${jh.length}. Ошибки: ${errors.join("; ")}`)
|
||||
toast.warning(`Синхронизировано JH: ${jh.length - errors.length}/${jh.length}. Ошибки: ${errors.join("; ")}`)
|
||||
} else {
|
||||
setSyncJhMessage(`Правила с ${jh.length} JH записаны в БД, список GRE обновлён.`)
|
||||
toast.success(`Правила с ${jh.length} JH записаны в БД, список GRE обновлён.`)
|
||||
}
|
||||
} catch (e) {
|
||||
setSyncJhMessage(e instanceof Error ? e.message : "Ошибка после синхронизации")
|
||||
toast.error(e instanceof Error ? e.message : "Ошибка после синхронизации")
|
||||
} finally {
|
||||
setSyncJhBusy(false)
|
||||
}
|
||||
}, [isLive, syncJhBusy, apiFetch, displayServers])
|
||||
|
||||
useEffect(() => {
|
||||
if (dataError) toast.error(dataError)
|
||||
}, [dataError])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return displayTunnels.filter((t) => {
|
||||
if (tabFilter === "up" && t.status !== "up") return false
|
||||
@@ -413,6 +414,7 @@ export default function GrePage() {
|
||||
function handleCopy(code: string) {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
setCopied(true)
|
||||
toast.success("Команды скопированы")
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
})
|
||||
}
|
||||
@@ -453,13 +455,6 @@ export default function GrePage() {
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="flex flex-col gap-5">
|
||||
|
||||
{(dataError || syncJhMessage) && (
|
||||
<div className={`flex items-start gap-3 rounded-lg border px-4 py-3 text-sm ${dataError ? "bg-destructive/5 border-destructive/30 text-destructive" : "bg-muted/40 border-border text-muted-foreground"}`}>
|
||||
<AlertCircleIcon className="size-5 shrink-0 mt-0.5" />
|
||||
<div>{dataError ?? syncJhMessage}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Legacy banner */}
|
||||
<div className="flex items-start gap-3 rounded-lg bg-amber-500/5 border border-amber-500/20 px-4 py-3 text-sm">
|
||||
<ShieldCheckIcon className="size-5 text-amber-500 shrink-0 mt-0.5" />
|
||||
|
||||
Reference in New Issue
Block a user