diff --git a/frontend/src/state.jsx b/frontend/src/state.jsx
index b2b215f..3af912c 100644
--- a/frontend/src/state.jsx
+++ b/frontend/src/state.jsx
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useMemo, useState } from "react";
import { API_URL } from "./api";
const AuthCtx = createContext(null);
+const UI_MODE_KEY = "nexapg_ui_mode";
function loadStorage() {
try {
@@ -11,10 +12,21 @@ function loadStorage() {
}
}
+function loadUiMode() {
+ try {
+ const value = localStorage.getItem(UI_MODE_KEY);
+ if (value === "easy" || value === "dba") return value;
+ } catch {
+ // ignore storage errors
+ }
+ return "dba";
+}
+
export function AuthProvider({ children }) {
const initial = loadStorage();
const [tokens, setTokens] = useState(initial?.tokens || null);
const [me, setMe] = useState(initial?.me || null);
+ const [uiMode, setUiModeState] = useState(loadUiMode);
const persist = (nextTokens, nextMe) => {
if (nextTokens && nextMe) {
@@ -78,7 +90,13 @@ export function AuthProvider({ children }) {
}
};
- const value = useMemo(() => ({ tokens, me, login, logout, refresh }), [tokens, me]);
+ const setUiMode = (nextMode) => {
+ const mode = nextMode === "easy" ? "easy" : "dba";
+ setUiModeState(mode);
+ localStorage.setItem(UI_MODE_KEY, mode);
+ };
+
+ const value = useMemo(() => ({ tokens, me, login, logout, refresh, uiMode, setUiMode }), [tokens, me, uiMode]);
return
{children};
}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index ed1a241..687fdd3 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -140,6 +140,52 @@ a {
color: #d7e4fa;
}
+.mode-switch-block {
+ margin-bottom: 12px;
+ padding: 10px;
+ border: 1px solid #2a588d;
+ border-radius: 10px;
+ background: #0d2244;
+}
+
+.mode-switch-label {
+ font-size: 12px;
+ color: #9eb8d6;
+ margin-bottom: 6px;
+}
+
+.mode-toggle {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding: 4px;
+ border-radius: 10px;
+ border: 1px solid #3170ae;
+ background: #0b1a36;
+}
+
+.mode-pill {
+ padding: 7px 6px;
+ border-radius: 8px;
+ text-align: center;
+ font-size: 12px;
+ color: #9db4d1;
+}
+
+.mode-toggle.easy .mode-pill:first-child,
+.mode-toggle.dba .mode-pill:last-child {
+ background: linear-gradient(180deg, #165a96, #124978);
+ color: #eff8ff;
+ font-weight: 700;
+}
+
+.mode-switch-block small {
+ display: block;
+ margin-top: 6px;
+ color: #9bb5d4;
+ font-size: 11px;
+}
+
.role {
color: var(--muted);
margin-bottom: 10px;
@@ -495,6 +541,57 @@ select:-webkit-autofill {
margin: 8px 0 0 18px;
}
+.easy-status {
+ border-width: 1px;
+}
+
+.easy-status.ok {
+ border-color: #1d8d5c;
+ background: linear-gradient(180deg, #123b39, #112a31);
+}
+
+.easy-status.warning {
+ border-color: #ad7f25;
+ background: linear-gradient(180deg, #3a3214, #2d2610);
+}
+
+.easy-status.problem {
+ border-color: #b54242;
+ background: linear-gradient(180deg, #3f1d22, #2f1519);
+}
+
+.easy-badge-row {
+ margin-top: 8px;
+}
+
+.easy-badge {
+ display: inline-block;
+ padding: 5px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+ border: 1px solid transparent;
+}
+
+.easy-badge.ok {
+ color: #86efac;
+ border-color: #2f8d5d;
+}
+
+.easy-badge.warning {
+ color: #fde68a;
+ border-color: #9a7a2e;
+}
+
+.easy-badge.problem {
+ color: #fecaca;
+ border-color: #b64a4a;
+}
+
+.easy-list {
+ margin: 8px 0 10px 16px;
+}
+
.chart-tooltip {
background: #0f1934ee;
border: 1px solid #2f4a8b;