refactor(SettingsPage): streamline loading and success/error message display for improved clarity and user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s

This commit is contained in:
2026-02-17 15:29:48 +07:00
parent 7b92d1eb70
commit 17cd5a8f43
+53 -77
View File
@@ -246,82 +246,58 @@ export default function SettingsPage() {
if (loading) { if (loading) {
return ( return (
<div className="page-wrapper"> <>
<div className="page-body"> <PageHeader title="Настройки интерфейса" icon={<IconSettings size={24} />} pretitle="Интерфейс" />
<div className="container-xl"> <div className="card">
<PageHeader <div className="card-body text-center py-5">
title="Настройки интерфейса" <div className="spinner-border text-primary" role="status" />
icon={<IconSettings size={28} />} <p className="mt-2 mb-0 text-muted">Загрузка настроек</p>
pretitle="Интерфейс"
/>
<div className="card">
<div className="card-body text-center py-5">
<div className="spinner-border text-primary" role="status" />
<p className="mt-2 mb-0 text-muted">Загрузка настроек</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </>
); );
} }
return ( return (
<div className="page-wrapper"> <>
<div className="page-body"> {error && (
<div className="container-xl"> <ErrorAlert message={error} onClose={() => setError('')} />
<PageHeader )}
title="Настройки интерфейса" {success && (
icon={<IconSettings size={28} />} <div className="alert alert-success alert-dismissible mb-3" role="alert">
pretitle="Интерфейс" <div className="d-flex">
meta="WebSocket, DoH, пинг, PTR зона" <div className="flex-grow-1">{success}</div>
actions={ </div>
<div className="btn-list"> <button type="button" className="btn-close" onClick={() => setSuccess('')} aria-label="Закрыть" />
<button </div>
type="button" )}
className="btn btn-primary"
onClick={onSave}
disabled={saving}
>
{saving && (
<span className="spinner-border spinner-border-sm me-2" />
)}
<IconDeviceFloppy size={18} className="me-1" />
Сохранить
</button>
</div>
}
/>
{error && ( <PageHeader
<div className="mb-3"> title="Настройки интерфейса"
<ErrorAlert message={error} onClose={() => setError('')} /> icon={<IconSettings size={24} />}
</div> pretitle="Интерфейс"
)} meta="WebSocket, DoH, пинг, PTR зона"
actions={
{success && ( <div className="btn-list">
<div <button
className="alert alert-success alert-dismissible mb-3" type="button"
role="alert" className="btn btn-primary"
onClick={onSave}
disabled={saving}
> >
<div className="d-flex"> {saving && <span className="spinner-border spinner-border-sm me-2" />}
<div className="flex-grow-1">{success}</div> <IconDeviceFloppy size={18} className="me-1" />
</div> Сохранить
<button </button>
type="button" </div>
className="btn-close" }
onClick={() => setSuccess('')} />
aria-label="Закрыть"
/>
</div>
)}
{/* Как в Tabler Settings: одна card, row g-0, col-md-3 border-end + col-md-9 */} <div className="row">
<div className="card"> {/* Сайдбар навигации — как блок в общем стиле страниц */}
<div className="row g-0"> <div className="col-12 col-md-3 mb-4 mb-md-0">
{/* Левая колонка — навигация (list-group list-group-transparent) */} <div className="card card-flat">
<div className="col-12 col-md-3 border-end"> <div className="card-body">
<div className="card-body">
<div className="input-icon mb-3"> <div className="input-icon mb-3">
<span className="input-icon-addon"> <span className="input-icon-addon">
<IconSearch size={18} className="text-muted" /> <IconSearch size={18} className="text-muted" />
@@ -360,14 +336,16 @@ export default function SettingsPage() {
</div> </div>
))} ))}
{sidebarGroupsFiltered.length === 0 && ( {sidebarGroupsFiltered.length === 0 && (
<p className="text-muted small mb-0">Нет подходящих разделов</p> <p className="text-muted small mb-0">Нет подходящих разделов</p>
)} )}
</div> </div>
</div> </div>
</div>
{/* Правая колонка — контент раздела */} {/* Контент раздела — карточка как на других страницах */}
<div className="col-12 col-md-9 d-flex flex-column"> <div className="col-12 col-md-9">
<div className="card-body"> <div className="card card-flat">
<div className="card-body">
{activeSection === 'live-doh' && ( {activeSection === 'live-doh' && (
<> <>
<SectionHeading title="BGP Live и DNS (DoH)" icon={IconPlugConnected} /> <SectionHeading title="BGP Live и DNS (DoH)" icon={IconPlugConnected} />
@@ -558,12 +536,10 @@ export default function SettingsPage() {
</div> </div>
</> </>
)} )}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </>
); );
} }