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:
@@ -61,6 +61,7 @@ export default function App() {
|
|||||||
const [chipOpen, setChipOpen] = useState(false);
|
const [chipOpen, setChipOpen] = useState(false);
|
||||||
const [chipEntry, setChipEntry] = useState(null);
|
const [chipEntry, setChipEntry] = useState(null);
|
||||||
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
||||||
|
const [adminOpen, setAdminOpen] = useState(false);
|
||||||
|
|
||||||
const [pwOpen, setPwOpen] = useState(false);
|
const [pwOpen, setPwOpen] = useState(false);
|
||||||
const [pw1, setPw1] = useState("");
|
const [pw1, setPw1] = useState("");
|
||||||
@@ -689,11 +690,15 @@ export default function App() {
|
|||||||
openPwModal={openPwModal}
|
openPwModal={openPwModal}
|
||||||
openDesignModal={openDesignModal}
|
openDesignModal={openDesignModal}
|
||||||
openStatsModal={openStatsModal}
|
openStatsModal={openStatsModal}
|
||||||
|
openAdminPanel={() => {
|
||||||
|
setAdminOpen(true);
|
||||||
|
setUserMenuOpen(false);
|
||||||
|
}}
|
||||||
doLogout={doLogout}
|
doLogout={doLogout}
|
||||||
onOpenNewGame={() => setNewGameOpen(true)}
|
onOpenNewGame={() => setNewGameOpen(true)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{me.role === "admin" && <AdminPanel />}
|
<AdminPanel open={me.role === "admin" && adminOpen} onClose={() => setAdminOpen(false)} />
|
||||||
|
|
||||||
<GamePickerCard
|
<GamePickerCard
|
||||||
games={games}
|
games={games}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { styles } from "../styles/styles";
|
|||||||
import { stylesTokens } from "../styles/theme";
|
import { stylesTokens } from "../styles/theme";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
export default function AdminPanel() {
|
export default function AdminPanel({ open: dashboardOpen = false, onClose }) {
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [userModalOpen, setUserModalOpen] = useState(false);
|
||||||
const [displayName, setDisplayName] = useState("");
|
const [displayName, setDisplayName] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -15,7 +15,7 @@ export default function AdminPanel() {
|
|||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!dashboardOpen && !userModalOpen) return;
|
||||||
|
|
||||||
const prev = document.body.style.overflow;
|
const prev = document.body.style.overflow;
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
@@ -23,7 +23,14 @@ export default function AdminPanel() {
|
|||||||
return () => {
|
return () => {
|
||||||
document.body.style.overflow = prev;
|
document.body.style.overflow = prev;
|
||||||
};
|
};
|
||||||
}, [open]);
|
}, [dashboardOpen, userModalOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dashboardOpen) {
|
||||||
|
setUserModalOpen(false);
|
||||||
|
setMsg("");
|
||||||
|
}
|
||||||
|
}, [dashboardOpen]);
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
const u = await api("/admin/users");
|
const u = await api("/admin/users");
|
||||||
@@ -51,7 +58,7 @@ export default function AdminPanel() {
|
|||||||
setMsg("✅ User erstellt.");
|
setMsg("✅ User erstellt.");
|
||||||
await loadUsers();
|
await loadUsers();
|
||||||
resetForm();
|
resetForm();
|
||||||
setOpen(false);
|
setUserModalOpen(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg("❌ Fehler: " + (e?.message || "unknown"));
|
setMsg("❌ Fehler: " + (e?.message || "unknown"));
|
||||||
}
|
}
|
||||||
@@ -68,17 +75,29 @@ export default function AdminPanel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setOpen(false);
|
setUserModalOpen(false);
|
||||||
setMsg("");
|
setMsg("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
if (!dashboardOpen) return null;
|
||||||
<div style={styles.adminWrap}>
|
|
||||||
|
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.adminTop}>
|
||||||
<div style={styles.adminTitle}>Admin Dashboard</div>
|
<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
|
+ User anlegen
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={onClose} style={styles.modalCloseBtn} aria-label="Dashboard schließen">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 12, fontWeight: 900, color: stylesTokens.textGold }}>
|
<div style={{ marginTop: 12, fontWeight: 900, color: stylesTokens.textGold }}>
|
||||||
@@ -125,7 +144,7 @@ export default function AdminPanel() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{open &&
|
{userModalOpen &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div style={styles.modalOverlay} onMouseDown={closeModal}>
|
<div style={styles.modalOverlay} onMouseDown={closeModal}>
|
||||||
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
|
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
|
||||||
@@ -201,5 +220,8 @@ export default function AdminPanel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ export default function TopBar({
|
|||||||
openPwModal,
|
openPwModal,
|
||||||
openDesignModal,
|
openDesignModal,
|
||||||
openStatsModal,
|
openStatsModal,
|
||||||
|
openAdminPanel,
|
||||||
doLogout,
|
doLogout,
|
||||||
onOpenNewGame,
|
onOpenNewGame,
|
||||||
}) {
|
}) {
|
||||||
const displayName = me ? ((me.display_name || "").trim() || me.email) : "";
|
const displayName = me ? ((me.display_name || "").trim() || me.email) : "";
|
||||||
|
const isAdmin = me?.role === "admin";
|
||||||
|
const roleLabel = isAdmin ? "Admin" : "User";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hp-topbar" style={styles.topBar}>
|
<div className="hp-topbar" style={styles.topBar}>
|
||||||
@@ -31,8 +34,8 @@ export default function TopBar({
|
|||||||
style={styles.userBtn}
|
style={styles.userBtn}
|
||||||
title="User Menü"
|
title="User Menü"
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 16 }}>👤</span>
|
<span style={{ fontSize: 16 }}>{isAdmin ? "🛡️" : "👤"}</span>
|
||||||
<span>User</span>
|
<span>{roleLabel}</span>
|
||||||
<span style={{ opacity: 0.7 }}>▾</span>
|
<span style={{ opacity: 0.7 }}>▾</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -60,6 +63,21 @@ export default function TopBar({
|
|||||||
Statistik
|
Statistik
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<>
|
||||||
|
<div style={styles.userDropdownDivider} />
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setUserMenuOpen(false);
|
||||||
|
openAdminPanel?.();
|
||||||
|
}}
|
||||||
|
style={styles.userDropdownItem}
|
||||||
|
>
|
||||||
|
Admin Dashboard
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={styles.userDropdownDivider} />
|
<div style={styles.userDropdownDivider} />
|
||||||
|
|
||||||
<button onClick={openPwModal} style={styles.userDropdownItem}>
|
<button onClick={openPwModal} style={styles.userDropdownItem}>
|
||||||
|
|||||||
Reference in New Issue
Block a user