diff --git a/backend/app/routes/admin.py b/backend/app/routes/admin.py
index 3a5c26a..8209ff2 100644
--- a/backend/app/routes/admin.py
+++ b/backend/app/routes/admin.py
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from ..db import get_db
from ..mailer import send_html_email, send_user_invite
-from ..models import AppSettings, InviteToken, User, Role
+from ..models import AppSettings, Game, GameChip, GameMember, InviteToken, SheetState, User, Role
from ..security import hash_password, get_session_user_id
router = APIRouter(prefix="/admin", tags=["admin"])
@@ -84,6 +84,28 @@ def create_user(req: Request, data: dict, db: Session = Depends(get_db)):
db.commit()
return {"ok": True, "id": u.id, "invite_sent": invite_sent}
+@router.delete("/users/{user_id}/permanent")
+def permanently_delete_user(req: Request, user_id: str, db: Session = Depends(get_db)):
+ admin = require_admin(req, db)
+ if admin.id == user_id:
+ raise HTTPException(400, "cannot delete yourself")
+
+ user = db.query(User).filter(User.id == user_id).first()
+ if not user:
+ raise HTTPException(404, "not found")
+ if db.query(Game).filter(Game.host_user_id == user_id).first():
+ raise HTTPException(409, "cannot delete a user who owns games")
+
+ db.query(Game).filter(Game.winner_user_id == user_id).update({Game.winner_user_id: None}, synchronize_session=False)
+ db.query(GameMember).filter(GameMember.user_id == user_id).delete(synchronize_session=False)
+ db.query(GameChip).filter(GameChip.user_id == user_id).delete(synchronize_session=False)
+ db.query(SheetState).filter(SheetState.owner_user_id == user_id).delete(synchronize_session=False)
+ db.query(InviteToken).filter(InviteToken.user_id == user_id).delete(synchronize_session=False)
+ db.delete(user)
+ db.commit()
+ return {"ok": True, "deleted": True}
+
+
@router.delete("/users/{user_id}")
def delete_user(req: Request, user_id: str, db: Session = Depends(get_db)):
admin = require_admin(req, db)
diff --git a/frontend/src/components/AdminPanel.jsx b/frontend/src/components/AdminPanel.jsx
index 2cb97f3..d5deb12 100644
--- a/frontend/src/components/AdminPanel.jsx
+++ b/frontend/src/components/AdminPanel.jsx
@@ -106,6 +106,19 @@ export default function AdminPanel({ open: dashboardOpen = false, onClose, curre
}
};
+ const permanentlyDeleteUser = async (user) => {
+ if (user.id === currentUserId) return;
+ const label = user.display_name || user.email;
+ if (!window.confirm(language === "en" ? `Delete ${label} permanently? This cannot be undone.` : `${label} wirklich dauerhaft löschen? Dieser Vorgang kann nicht rückgängig gemacht werden.`)) return;
+ try {
+ await api(`/admin/users/${user.id}/permanent`, { method: "DELETE" });
+ await loadUsers();
+ setNotice(language === "en" ? "✅ User permanently deleted." : "✅ User dauerhaft gelöscht.");
+ } catch (e) {
+ alert((language === "en" ? "Error: " : "Fehler: ") + (e?.message || "unknown"));
+ }
+ };
+
if (!dashboardOpen) return null;
return createPortal(
@@ -136,6 +149,7 @@ export default function AdminPanel({ open: dashboardOpen = false, onClose, curre
+
))}
diff --git a/frontend/src/styles/hooks/useHpGlobalStyles.js b/frontend/src/styles/hooks/useHpGlobalStyles.js
index adb0622..ee48a9c 100644
--- a/frontend/src/styles/hooks/useHpGlobalStyles.js
+++ b/frontend/src/styles/hooks/useHpGlobalStyles.js
@@ -105,10 +105,10 @@ export function useHpGlobalStyles() {
@keyframes hpStartLine { 0%, 100% { opacity: .3; transform: scaleX(.7); } 50% { opacity: 1; transform: scaleX(1); } }
.hp-topbar-actions { margin-left: auto; }
.hp-user-menu-wrap { min-width: 0; }
- .hp-admin-user-row { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1.45fr) 62px 62px 78px; white-space: nowrap; }
+ .hp-admin-user-row { grid-template-columns: minmax(90px, 1.2fr) minmax(90px, 1.5fr) 58px 58px 100px; white-space: nowrap; min-height: 42px; padding: 6px 8px !important; }
.hp-admin-action { min-width: 0; }
.hp-admin-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; min-width: 0; }
- .hp-admin-icon-action { width: 34px; height: 34px; padding: 0 !important; display: inline-flex; align-items: center; justify-content: center; font-size: 17px; }
+ .hp-admin-icon-action { width: 30px; height: 30px; padding: 0 !important; display: inline-flex; align-items: center; justify-content: center; font-size: 15px; }
.hp-admin-name, .hp-admin-email { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hp-admin-check { display: flex; align-items: center; gap: 8px; color: var(--hp-textDim); font-size: 13px; }
.hp-admin-check input { width: 18px; height: 18px; accent-color: var(--hp-textGold); }
@@ -134,13 +134,11 @@ export function useHpGlobalStyles() {
.hp-topbar-user { justify-content: center; }
.hp-user-dropdown { left: 50% !important; right: auto !important; transform: translateX(-50%); width: max-content; max-width: calc(100vw - 20px); min-width: min(220px, calc(100vw - 20px)) !important; }
.hp-user-dropdown > div:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .hp-admin-user-row { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px; padding: 11px !important; border-radius: 15px !important; }
- .hp-admin-name { grid-column: 1; grid-row: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 16px; }
- .hp-admin-email { grid-column: 1; grid-row: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px !important; }
- .hp-admin-role { grid-column: 2; grid-row: 1; }
- .hp-admin-status { grid-column: 2; grid-row: 2; }
- .hp-admin-actions { grid-column: 1 / -1; grid-row: 3; width: 100%; justify-content: flex-end; }
- .hp-admin-actions .hp-admin-action { width: 36px; min-width: 36px; min-height: 36px; padding: 0 !important; }
+ .hp-admin-user-row { grid-template-columns: minmax(65px, 1.1fr) minmax(0, 1.3fr) 42px 42px 94px; gap: 3px; padding: 7px !important; border-radius: 12px !important; min-height: 42px; }
+ .hp-admin-name, .hp-admin-email { min-width: 0; font-size: 12px !important; }
+ .hp-admin-role, .hp-admin-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 11px !important; padding-left: 2px !important; padding-right: 2px !important; }
+ .hp-admin-actions { width: auto; justify-content: flex-end; gap: 3px; }
+ .hp-admin-actions .hp-admin-action { width: 29px; min-width: 29px; height: 29px; min-height: 29px; padding: 0 !important; font-size: 14px; }
.hp-admin-editor-card { padding: 14px !important; max-height: calc(100dvh - 20px) !important; }
.hp-admin-editor-card input, .hp-admin-editor-card select { min-height: 40px; padding: 8px 10px !important; }
.hp-admin-settings-card { padding: 14px !important; max-height: calc(100dvh - 20px) !important; overflow: auto; }
diff --git a/frontend/src/styles/styles.js b/frontend/src/styles/styles.js
index 1c7012a..1bdca21 100644
--- a/frontend/src/styles/styles.js
+++ b/frontend/src/styles/styles.js
@@ -189,7 +189,6 @@ export const styles = {
},
userRow: {
display: "grid",
- gridTemplateColumns: "1fr 80px 90px",
gap: 8,
padding: 10,
borderRadius: 12,