feat: settings panel overlay Notion-style + avatar upload + sections
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

- Settings page refaite en panneau overlay (position:fixed + backdrop-blur)
- Layout 2 panes: nav sidebar gauche + contenu droit
- Sections: Account (profile + avatar upload), Notifications, Workspace,
  Tags management, Features (integrations), Admin (security)
- Avatar: upload image (POST /api/settings/avatar), preview instantané,
  stockage /data/avatars/, mise à jour users.avatar_url
- GET /api/avatar/{user_id} redirige vers l'image avatar
- Tags: palette couleurs, création, suppression, changement couleur
- Fermeture: bouton × ou Escape (window.history.back)
This commit is contained in:
2026-07-12 15:22:31 -04:00
parent d01b51bd5b
commit e3968356e2
2 changed files with 266 additions and 76 deletions
+39
View File
@@ -1096,6 +1096,45 @@ async def app_settings_page(request: Request):
return template.render(**ctx)
@router.post("/api/settings/avatar")
async def upload_avatar(request: Request):
"""Upload a user avatar image."""
from fastapi import UploadFile, File
from pathlib import Path
import uuid, os
form = await request.form()
file = form.get("file")
if not file:
return {"error": "No file"}, 400
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return {"error": "Not authenticated"}, 401
# Save to data/avatars
avatars_dir = Path("/data/avatars")
avatars_dir.mkdir(parents=True, exist_ok=True)
ext = os.path.splitext(file.filename)[1] or ".png"
filename = f"{user['id']}_{uuid.uuid4().hex[:8]}{ext}"
filepath = avatars_dir / filename
content = await file.read()
filepath.write_bytes(content)
# Update user avatar_url
avatar_url = f"/api/settings/avatar/{filename}"
with get_conn() as conn:
conn.execute("UPDATE users SET avatar_url = ? WHERE id = ?", (avatar_url, user["id"]))
conn.commit()
return {"avatar_url": avatar_url}
@router.get("/api/avatar/{user_id:int}")
async def get_avatar(user_id: int):
"""Redirect to the user's avatar."""
with get_conn() as conn:
row = conn.execute("SELECT avatar_url FROM users WHERE id = ?", (user_id,)).fetchone()
if row and row["avatar_url"]:
return RedirectResponse(row["avatar_url"], status_code=302)
return JSONResponse({"error": "No avatar"}, status_code=404)
# ═══════════ Tag Management API (global) ═══════════
@router.post("/api/settings/tags")