From 944ae498d2bb81a464524880c8e0224c69070192 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 9 May 2026 01:12:56 -0700 Subject: [PATCH] refactor(ui): polish bot config settings --- web/src/components/ui/Primitives.tsx | 22 ++- web/src/index.css | 276 +++++++++++++++++++++++++++ web/src/tabs/ConfigTab.dom.test.tsx | 10 +- web/src/tabs/ConfigTab.tsx | 126 ++++++------ web/src/tabs/TabSuite.test.ts | 2 +- 5 files changed, 355 insertions(+), 81 deletions(-) diff --git a/web/src/components/ui/Primitives.tsx b/web/src/components/ui/Primitives.tsx index bbad9e4..8d8fc1b 100644 --- a/web/src/components/ui/Primitives.tsx +++ b/web/src/components/ui/Primitives.tsx @@ -301,15 +301,23 @@ export const Checkbox = React.forwardRef( ({ className, label, id, ...props }, ref) => { const generatedId = React.useId(); const checkboxId = id ?? (label ? `checkbox-${generatedId}` : undefined); + const input = ( + + ); + + if (!label) { + return input; + } + return ( ); diff --git a/web/src/index.css b/web/src/index.css index 69b575d..8cd26fe 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -549,6 +549,282 @@ body { gap: 8px; } +.settings-config-stack { + display: grid; + gap: 24px; +} + +.settings-access-panel, +.settings-loading-state, +.settings-action-panel, +.settings-control-panel, +.settings-info-footer { + border: 1px solid var(--border); + border-radius: var(--bl-radius-card); + background: var(--card); + box-shadow: var(--card-shadow); +} + +.settings-access-panel { + border-color: color-mix(in oklab, var(--bl-danger) 26%, var(--border)); + background: color-mix(in oklab, var(--bl-danger) 8%, var(--card)); + padding: 28px; +} + +.settings-access-panel h2 { + margin: 8px 0 0; + color: var(--foreground); + font-size: 22px; + font-weight: 780; + line-height: 1.2; +} + +.settings-access-panel p:not(.settings-panel-eyebrow) { + margin: 10px 0 0; + color: var(--muted-foreground); + font-size: 14px; + line-height: 1.6; +} + +.settings-panel-eyebrow { + margin: 0; + color: var(--bl-danger); + font-size: 11px; + font-weight: 850; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.settings-loading-state { + min-height: 260px; + display: grid; + place-items: center; + align-content: center; + gap: 14px; + color: var(--muted-foreground); + text-align: center; +} + +.settings-loading-state p { + margin: 0; + font-size: 13px; + font-weight: 700; +} + +.settings-loading-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--border); + border-top-color: var(--accent); + border-radius: 999px; + animation: spin 0.8s linear infinite; +} + +.settings-feedback { + display: flex; + align-items: center; + gap: 14px; + border: 1px solid var(--border); + border-radius: 16px; + padding: 16px; +} + +.settings-feedback--success { + border-color: color-mix(in oklab, var(--bl-success) 28%, var(--border)); + background: color-mix(in oklab, var(--bl-success) 10%, var(--card)); + color: var(--bl-success); +} + +.settings-feedback--error { + border-color: color-mix(in oklab, var(--bl-danger) 28%, var(--border)); + background: color-mix(in oklab, var(--bl-danger) 10%, var(--card)); + color: var(--bl-danger); +} + +.settings-feedback-icon { + display: grid; + width: 40px; + height: 40px; + flex: 0 0 auto; + place-items: center; + border-radius: 999px; + background: color-mix(in oklab, currentColor 12%, transparent); +} + +.settings-feedback span { + display: block; + color: var(--foreground); + font-size: 14px; + font-weight: 750; +} + +.settings-feedback p { + margin: 4px 0 0; + color: var(--muted-foreground); + font-size: 13px; + line-height: 1.5; +} + +.settings-action-panel { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 24px; +} + +.settings-action-heading { + display: flex; + min-width: 0; + align-items: center; + gap: 16px; +} + +.settings-action-icon, +.settings-info-icon { + display: grid; + width: 52px; + height: 52px; + flex: 0 0 auto; + place-items: center; + border: 1px solid color-mix(in oklab, var(--accent) 18%, var(--border)); + border-radius: 16px; + background: color-mix(in oklab, var(--accent) 10%, var(--card)); + color: var(--accent); +} + +.settings-action-heading h2, +.settings-control-header h3, +.settings-info-content h5 { + margin: 0; + color: var(--foreground); + font-weight: 760; + line-height: 1.25; +} + +.settings-action-heading h2 { + font-size: 20px; +} + +.settings-action-heading p, +.settings-control-header p, +.settings-info-content p { + margin: 6px 0 0; + color: var(--muted-foreground); + font-size: 13px; + line-height: 1.55; +} + +.settings-action-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 10px; +} + +.settings-control-panel { + display: grid; + gap: 18px; + padding: 20px; +} + +.settings-control-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.settings-control-header h3, +.settings-info-content h5 { + font-size: 14px; +} + +.settings-toggle-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.settings-toggle-card { + display: flex; + align-items: flex-start; + gap: 12px; + min-width: 0; + border: 1px solid var(--border); + border-radius: 16px; + background: var(--card-elevated); + padding: 16px; +} + +.settings-toggle-card > span { + min-width: 0; +} + +.settings-toggle-card span span { + display: block; + color: var(--foreground); + font-size: 13px; + font-weight: 750; +} + +.settings-toggle-card small { + display: block; + margin-top: 4px; + color: var(--muted-foreground); + font-size: 12px; + line-height: 1.45; +} + +.settings-info-footer { + position: relative; + overflow: hidden; + padding: 24px; +} + +.settings-info-watermark { + position: absolute; + top: -34px; + right: 18px; + opacity: 0.05; + pointer-events: none; +} + +.settings-info-content { + position: relative; + z-index: 1; + display: flex; + align-items: center; + gap: 18px; +} + +.settings-info-content strong { + color: var(--foreground); + font-weight: 760; +} + +@media (max-width: 760px) { + .settings-action-panel, + .settings-control-header, + .settings-info-content { + align-items: stretch; + flex-direction: column; + } + + .settings-action-controls { + justify-content: stretch; + } + + .settings-action-controls .product-button { + width: 100%; + } + + .settings-toggle-grid { + grid-template-columns: 1fr; + } +} + .ux-field-label { color: var(--muted-foreground); font-size: 11px; diff --git a/web/src/tabs/ConfigTab.dom.test.tsx b/web/src/tabs/ConfigTab.dom.test.tsx index 1e6e4d6..17d1d19 100644 --- a/web/src/tabs/ConfigTab.dom.test.tsx +++ b/web/src/tabs/ConfigTab.dom.test.tsx @@ -51,9 +51,9 @@ describe('ConfigTab DOM behavior', () => { await user.type(modeTextarea, 'disabled'); expect(screen.getByRole('button', { name: 'Commit Changes' })).toBeEnabled(); - expect(screen.getByRole('button', { name: 'Abort Changes' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Reset changes' })).toBeInTheDocument(); - await user.click(screen.getByRole('button', { name: 'Abort Changes' })); + await user.click(screen.getByRole('button', { name: 'Reset changes' })); expect(screen.getByDisplayValue('enabled')).toBeInTheDocument(); const resetModeTextarea = screen.getByDisplayValue('enabled'); @@ -63,7 +63,7 @@ describe('ConfigTab DOM behavior', () => { await waitFor(() => { expect(upsertDynamicConfigItemsMock).toHaveBeenCalledTimes(1); - expect(screen.getByText('SYNC COMPLETE')).toBeInTheDocument(); + expect(screen.getByText('Saved')).toBeInTheDocument(); }); expect(upsertDynamicConfigItemsMock.mock.calls[0][0]).toEqual( @@ -99,7 +99,7 @@ describe('ConfigTab DOM behavior', () => { await user.click(screen.getByRole('button', { name: 'Commit Changes' })); await waitFor(() => { - expect(screen.getByText('CRITICAL ERROR')).toBeInTheDocument(); + expect(screen.getByText('Update failed')).toBeInTheDocument(); expect(screen.getByText(/Sync failed: write failed/)).toBeInTheDocument(); }); }, 20000); @@ -109,7 +109,7 @@ describe('ConfigTab DOM behavior', () => { render(); - expect(screen.getByText(/Access Denied/i)).toBeInTheDocument(); + expect(screen.getByText(/Access denied/i)).toBeInTheDocument(); expect(fetchDynamicConfigItemsMock).not.toHaveBeenCalled(); }); }); diff --git a/web/src/tabs/ConfigTab.tsx b/web/src/tabs/ConfigTab.tsx index 2f8bbdf..e851451 100644 --- a/web/src/tabs/ConfigTab.tsx +++ b/web/src/tabs/ConfigTab.tsx @@ -4,6 +4,7 @@ import { clearBacktestRuntimeFlagCache } from '../backtest/flags'; import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi'; import { useAuth } from '../components/AuthContext'; import { BACKTEST_FLAG_KEYS } from '../../../shared/feature-flags.js'; +import { Button, Checkbox, Textarea } from '../components/ui/Primitives'; interface ConfigItem { key: string; @@ -102,7 +103,7 @@ export const ConfigTab = () => { await upsertDynamicConfigItems(buildConfigUpsertPayload(configs)); clearBacktestRuntimeFlagCache(); - setMessage({ type: 'success', text: 'Neural parameters synchronized successfully.' }); + setMessage({ type: 'success', text: 'Runtime configuration updated successfully.' }); setOriginalConfigs(cloneConfigItems(configs)); setTimeout(() => setMessage(null), 4000); } catch (err: any) { @@ -142,99 +143,91 @@ export const ConfigTab = () => { if (!isAdmin) { return ( -
-
-

Restricted

-

Access Denied

-

- Global bot configuration is limited to administrator accounts. -

-
+
+

Restricted

+

Access denied

+

Global bot configuration is limited to administrator accounts.

); } if (loading) { return ( -
-
-

Calibrating Infrastructure...

+
+ ); } return ( -
- {/* 1. NOTIFICATION PANEL */} +
{message && ( -
-
+
+
{message.type === 'success' ? : }
- {message.type === 'success' ? 'SYNC COMPLETE' : 'CRITICAL ERROR'} -

{message.text}

+ {message.type === 'success' ? 'Saved' : 'Update failed'} +

{message.text}

)} - {/* 2. ACTION CONTROL CENTER */} -
-
-
+
+
+
-

- Global Operational Parameters -

-

Core Infrastructure Configuration & Credentials

+

Bot Runtime Configuration

+

Core infrastructure settings, providers, and guarded credentials.

-
+
{hasChanges && ( - + Reset changes + )} - +
-
-
+
+
-

Backtest Access Control

-

+

Backtest Access Control

+

Runtime flags in bot_config. Applies automatically on dynamic config refresh.

- +
-
-
- {/* 3. RESPONSIVE TABLE OF CONFIGS */}
@@ -295,7 +286,7 @@ export const ConfigTab = () => {
-