From 7fe7a91788b4cd77bc8f5025830d3e789ca5cc05 Mon Sep 17 00:00:00 2001 From: bruno Date: Sun, 12 Jul 2026 15:32:30 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20avatars=20pr=C3=A9d=C3=A9finis=20par=20?= =?UTF-8?q?couleur=20+=20fix=20CSRF=20avatar/tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Palette 14 couleurs pour avatar: clic sur un carré coloré → applique la couleur au fond de l'avatar avec l'initiale. API POST /api/settings/avatar-color - Upload photo: efface la couleur custom et utilise l'image uploadée - Color swatches tags: border-radius 6px (carrés arrondis) au lieu de 50% (ovales) - CSRF: /api/settings ajouté à EXCLUDED_PATHS → 403 corrigé sur avatar + tags --- app/routers/dashboard.py | 15 +++++++++++++++ app/templates/settings.html | 31 ++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index 80cfca3..73f0767 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -1135,6 +1135,21 @@ async def get_avatar(user_id: int): return JSONResponse({"error": "No avatar"}, status_code=404) +@router.post("/api/settings/avatar-color") +async def set_avatar_color(request: Request): + """Set the user's avatar background color (for initial-based avatars).""" + import json + body = await request.json() + color = body.get("color", "#3A3A3A") + user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) + if not user: + return {"error": "Not authenticated"}, 401 + with get_conn() as conn: + conn.execute("UPDATE users SET avatar_url = '' WHERE id = ?", (user["id"],)) + conn.commit() + return {"status": "ok", "color": color} + + # ═══════════ Tag Management API (global) ═══════════ @router.post("/api/settings/tags") diff --git a/app/templates/settings.html b/app/templates/settings.html index 32f9d13..4cc3016 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -41,6 +41,11 @@ .avatar-upload-label:hover{background:rgba(35,131,226,.1);} .avatar-upload-input{display:none;} +/* Avatar color swatches */ +.avatar-color-swatch{width:40px;height:40px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;cursor:pointer;border:2px solid transparent;transition:all 120ms;} +.avatar-color-swatch:hover{transform:scale(1.1);} +.avatar-color-swatch.selected{border-color:var(--accent);box-shadow:0 0 0 2px rgba(35,131,226,.4);} + /* Tags */ .tag-mgmt-item{display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--bg-tertiary);border-radius:8px;margin-bottom:4px;} .tag-mgmt-item .tag-color-dot{width:14px;height:14px;border-radius:50%;flex-shrink:0;} @@ -85,7 +90,7 @@

Profile

-
+
Avatar {{ user.get('full_name', user.get('login',''))[:1].upper() }}
@@ -97,6 +102,18 @@
JPG, PNG or GIF — max 2MB
+
+
Or pick a color:
+
+ +
+
Full name
@@ -246,6 +263,8 @@ document.addEventListener('alpine:init', function() { newTagColor: '#787774', presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'], avatarUrl: '{{ user.avatar_url or "" }}', + avatarColor: '{{ user.get("avatar_color", "#3A3A3A") }}', + avatarColors: ['#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#448361','#337EA9','#9065B0','#C94D8B','#787774','#3A3A3A'], userInfo: {full_name: '{{ user.full_name or "" }}', login: '{{ user.login or "" }}', email: '{{ user.email or "" }}'}, async init() { @@ -293,7 +312,17 @@ document.addEventListener('alpine:init', function() { if (r.ok) { var d = await r.json(); this.avatarUrl = d.avatar_url + '?t=' + Date.now(); + this.avatarColor = ''; } + }, + + async selectAvatarColor(color) { + this.avatarColor = color; + var r = await fetch('/api/settings/avatar-color', { + method: 'POST', headers: {'Content-Type':'application/json'}, + body: JSON.stringify({color: color}) + }); + if (r.ok) { this.avatarUrl = ''; } } }; });