diff --git a/backend/app/routes/auth.py b/backend/app/routes/auth.py
index fff2f01..d6bb7b0 100644
--- a/backend/app/routes/auth.py
+++ b/backend/app/routes/auth.py
@@ -43,6 +43,41 @@ def me(req: Request, db: Session = Depends(get_db)):
return {"id": user.id, "email": user.email, "role": user.role, "display_name": user.display_name}
+@router.get("/me/stats")
+def my_stats(req: Request, db: Session = Depends(get_db)):
+ uid = get_session_user_id(req)
+ if not uid:
+ raise HTTPException(status_code=401, detail="not logged in")
+
+ # "played" = games where user is member AND winner is set (finished games)
+ from sqlalchemy import func
+ from ..models import Game, GameMember
+
+ played = (
+ db.query(func.count(Game.id))
+ .join(GameMember, GameMember.game_id == Game.id)
+ .filter(GameMember.user_id == uid, Game.winner_user_id != None)
+ .scalar()
+ or 0
+ )
+
+ wins = (
+ db.query(func.count(Game.id))
+ .join(GameMember, GameMember.game_id == Game.id)
+ .filter(GameMember.user_id == uid, Game.winner_user_id == uid)
+ .scalar()
+ or 0
+ )
+
+ losses = max(int(played) - int(wins), 0)
+ winrate = (float(wins) / float(played) * 100.0) if played else 0.0
+
+ return {
+ "played": int(played),
+ "wins": int(wins),
+ "losses": int(losses),
+ "winrate": round(winrate, 1),
+ }
@router.patch("/password")
def set_password(data: dict, req: Request, db: Session = Depends(get_db)):
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index bb7cc5f..d74593c 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -20,6 +20,7 @@ import DesignModal from "./components/DesignModal";
import WinnerCard from "./components/WinnerCard";
import WinnerBadge from "./components/WinnerBadge";
import NewGameModal from "./components/NewGameModal";
+import StatsModal from "./components/StatsModal";
export default function App() {
useHpGlobalStyles();
@@ -62,6 +63,12 @@ export default function App() {
// New Game Modal
const [newGameOpen, setNewGameOpen] = useState(false);
+ // ===== Stats Modal =====
+ const [statsOpen, setStatsOpen] = useState(false);
+ const [stats, setStats] = useState(null);
+ const [statsLoading, setStatsLoading] = useState(false);
+ const [statsError, setStatsError] = useState("");
+
const load = async () => {
const m = await api("/auth/me");
setMe(m);
@@ -201,6 +208,29 @@ export default function App() {
}
};
+ // ===== Stats (always fresh on open) =====
+ const openStatsModal = async () => {
+ setUserMenuOpen(false);
+ setStatsOpen(true);
+ setStatsError("");
+ setStatsLoading(true);
+
+ try {
+ const s = await api("/auth/me/stats");
+ setStats(s);
+ } catch (e) {
+ setStats(null);
+ setStatsError("❌ Fehler: " + (e?.message || "unknown"));
+ } finally {
+ setStatsLoading(false);
+ }
+ };
+
+ const closeStatsModal = () => {
+ setStatsOpen(false);
+ setStatsError("");
+ };
+
// ===== New game flow =====
const createGame = async () => {
const g = await api("/games", {
@@ -366,6 +396,7 @@ export default function App() {
setUserMenuOpen={setUserMenuOpen}
openPwModal={openPwModal}
openDesignModal={openDesignModal}
+ openStatsModal={openStatsModal}
doLogout={doLogout}
onOpenNewGame={() => setNewGameOpen(true)}
/>
@@ -444,6 +475,15 @@ export default function App() {
closeChipModalToDash={closeChipModalToDash}
chooseChip={chooseChip}
/>
+
+