Convert AdminPanel to modal overlay with close button and add admin dashboard menu option

Refactored AdminPanel from inline component to modal overlay triggered from user menu. Added `open` and `onClose` props to control visibility, wrapped panel in modal overlay with backdrop click-to-close behavior, and added close button to header. Implemented admin-only menu item in TopBar user dropdown with shield icon and role label. Added state management for dashboard visibility in App.jsx with proper cleanup
This commit is contained in:
2026-08-02 10:04:58 +02:00
parent 295282c2bf
commit f24489b1e5
3 changed files with 59 additions and 14 deletions
+6 -1
View File
@@ -61,6 +61,7 @@ export default function App() {
const [chipOpen, setChipOpen] = useState(false);
const [chipEntry, setChipEntry] = useState(null);
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [adminOpen, setAdminOpen] = useState(false);
const [pwOpen, setPwOpen] = useState(false);
const [pw1, setPw1] = useState("");
@@ -689,11 +690,15 @@ export default function App() {
openPwModal={openPwModal}
openDesignModal={openDesignModal}
openStatsModal={openStatsModal}
openAdminPanel={() => {
setAdminOpen(true);
setUserMenuOpen(false);
}}
doLogout={doLogout}
onOpenNewGame={() => setNewGameOpen(true)}
/>
{me.role === "admin" && <AdminPanel />}
<AdminPanel open={me.role === "admin" && adminOpen} onClose={() => setAdminOpen(false)} />
<GamePickerCard
games={games}
+33 -11
View File
@@ -4,10 +4,10 @@ import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
import { createPortal } from "react-dom";
export default function AdminPanel() {
export default function AdminPanel({ open: dashboardOpen = false, onClose }) {
const [users, setUsers] = useState([]);
const [open, setOpen] = useState(false);
const [userModalOpen, setUserModalOpen] = useState(false);
const [displayName, setDisplayName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -15,7 +15,7 @@ export default function AdminPanel() {
const [msg, setMsg] = useState("");
useEffect(() => {
if (!open) return;
if (!dashboardOpen && !userModalOpen) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
@@ -23,7 +23,14 @@ export default function AdminPanel() {
return () => {
document.body.style.overflow = prev;
};
}, [open]);
}, [dashboardOpen, userModalOpen]);
useEffect(() => {
if (!dashboardOpen) {
setUserModalOpen(false);
setMsg("");
}
}, [dashboardOpen]);
const loadUsers = async () => {
const u = await api("/admin/users");
@@ -51,7 +58,7 @@ export default function AdminPanel() {
setMsg("✅ User erstellt.");
await loadUsers();
resetForm();
setOpen(false);
setUserModalOpen(false);
} catch (e) {
setMsg("❌ Fehler: " + (e?.message || "unknown"));
}
@@ -68,17 +75,29 @@ export default function AdminPanel() {
};
const closeModal = () => {
setOpen(false);
setUserModalOpen(false);
setMsg("");
};
return (
<div style={styles.adminWrap}>
if (!dashboardOpen) return null;
return createPortal(
<div style={styles.modalOverlay} onMouseDown={onClose}>
<div
style={{ ...styles.modalCard, width: "min(760px, 100%)", padding: 0 }}
onMouseDown={(e) => e.stopPropagation()}
>
<div style={{ ...styles.adminWrap, marginTop: 0, border: "none", boxShadow: "none", backdropFilter: "none", WebkitBackdropFilter: "none" }}>
<div style={styles.adminTop}>
<div style={styles.adminTitle}>Admin Dashboard</div>
<button onClick={() => setOpen(true)} style={styles.primaryBtn}>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<button onClick={() => setUserModalOpen(true)} style={styles.primaryBtn}>
+ User anlegen
</button>
<button onClick={onClose} style={styles.modalCloseBtn} aria-label="Dashboard schließen">
</button>
</div>
</div>
<div style={{ marginTop: 12, fontWeight: 900, color: stylesTokens.textGold }}>
@@ -125,7 +144,7 @@ export default function AdminPanel() {
))}
</div>
{open &&
{userModalOpen &&
createPortal(
<div style={styles.modalOverlay} onMouseDown={closeModal}>
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
@@ -200,6 +219,9 @@ export default function AdminPanel() {
document.body
)
}
</div>
</div>
</div>
</div>,
document.body
);
}
+20 -2
View File
@@ -9,10 +9,13 @@ export default function TopBar({
openPwModal,
openDesignModal,
openStatsModal,
openAdminPanel,
doLogout,
onOpenNewGame,
}) {
const displayName = me ? ((me.display_name || "").trim() || me.email) : "";
const isAdmin = me?.role === "admin";
const roleLabel = isAdmin ? "Admin" : "User";
return (
<div className="hp-topbar" style={styles.topBar}>
@@ -31,8 +34,8 @@ export default function TopBar({
style={styles.userBtn}
title="User Menü"
>
<span style={{ fontSize: 16 }}>👤</span>
<span>User</span>
<span style={{ fontSize: 16 }}>{isAdmin ? "🛡️" : "👤"}</span>
<span>{roleLabel}</span>
<span style={{ opacity: 0.7 }}></span>
</button>
@@ -60,6 +63,21 @@ export default function TopBar({
Statistik
</button>
{isAdmin && (
<>
<div style={styles.userDropdownDivider} />
<button
onClick={() => {
setUserMenuOpen(false);
openAdminPanel?.();
}}
style={styles.userDropdownItem}
>
Admin Dashboard
</button>
</>
)}
<div style={styles.userDropdownDivider} />
<button onClick={openPwModal} style={styles.userDropdownItem}>