From 0881db2c6017f5d46ea11187afdf031e47768c0d Mon Sep 17 00:00:00 2001 From: bitsearch Date: Thu, 22 Jan 2026 16:33:03 +0100 Subject: [PATCH 1/2] recuperation d'avatar depuis le du backend depuis le frontend --- srcs/backend/index.js | 2 +- srcs/backend/routes/avatar.js | 2 +- srcs/frontend/src/app.js | 15 ++-- srcs/frontend/src/avatar.js | 120 +++++++++++++++++++++++++++++ srcs/frontend/src/avatarWindows.js | 7 -- srcs/frontend/src/login.js | 5 +- 6 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 srcs/frontend/src/avatar.js delete mode 100644 srcs/frontend/src/avatarWindows.js diff --git a/srcs/backend/index.js b/srcs/backend/index.js index e8c91f7..c7a9be8 100644 --- a/srcs/backend/index.js +++ b/srcs/backend/index.js @@ -51,4 +51,4 @@ async function startServer() }); } -startServer(); +startServer(); \ No newline at end of file diff --git a/srcs/backend/routes/avatar.js b/srcs/backend/routes/avatar.js index 6df63ae..321fed1 100644 --- a/srcs/backend/routes/avatar.js +++ b/srcs/backend/routes/avatar.js @@ -47,4 +47,4 @@ router.get('/user/:userId', async(req, res) => res.status(result.status).json(result.data); }); -export default router; +export default router; \ No newline at end of file diff --git a/srcs/frontend/src/app.js b/srcs/frontend/src/app.js index 555e56c..19d705a 100644 --- a/srcs/frontend/src/app.js +++ b/srcs/frontend/src/app.js @@ -1,8 +1,7 @@ import {Element, MenuElement} from "./element.js"; -import {fenetre} from "./windows.js"; import {LoginWindow} from "./login.js"; import { GlobalChat } from "./global_chat.js"; -import {avatarWindows} from "./avatarWindows.js"; +import { AvatarWindow } from "./avatar.js"; function direBonjour() { alert("clicked !"); @@ -17,9 +16,9 @@ const accueilElement = new MenuElement("accueil"); const globalChatElement = new MenuElement("global_chat"); const avatarElement = new MenuElement("avatar"); // Windows and screens +export const avatarWindow = new AvatarWindow(); const loginWindow = new LoginWindow(); const global_chat = new GlobalChat(); -const avatar_windows = new avatarWindows(); // Actions UI @@ -43,10 +42,10 @@ document.getElementById("global_chat").addEventListener("click", () => { document.getElementById("avatar").addEventListener("click", () => { - // Toggle global chat visibility - if (avatarWindows.main && avatarWindows.main.style.display !== "none") { - avatarWindows.hide(); + // Toggle avatar window visibility + if (avatarWindow.main && avatarWindow.main.style.display !== "none") { + avatarWindow.hide(); } else { - avatarWindows.show(); + avatarWindow.show(); } -}); \ No newline at end of file +}); diff --git a/srcs/frontend/src/avatar.js b/srcs/frontend/src/avatar.js new file mode 100644 index 0000000..3b77cdb --- /dev/null +++ b/srcs/frontend/src/avatar.js @@ -0,0 +1,120 @@ +import {fenetre} from "./windows.js"; + +export class AvatarWindow extends fenetre { + constructor() { + super(360, 320, "Avatar"); + // Avatar preview + this.avatarPreview = document.createElement("img"); + this.avatarPreview.style.width = "120px"; + this.avatarPreview.style.height = "120px"; + this.avatarPreview.style.objectFit = "cover"; + this.avatarPreview.style.borderRadius = "50%"; + this.avatarPreview.style.border = "2px solid #fff"; + + + this.fileInput = document.createElement("input"); + this.fileInput.type = "file"; + this.fileInput.accept = "image/*"; + // Hide the raw file input to keep only one visible control + this.fileInput.style.display = "none"; + + this.chooseBtn = document.createElement("button"); + this.chooseBtn.textContent = "Choisir image"; + + this.saveBtn = document.createElement("button"); + this.saveBtn.textContent = "Enregistrer avatar"; + + // Refresh button to re-fetch avatar from server + this.refreshBtn = document.createElement("button"); + this.refreshBtn.textContent = "Rafraîchir photo"; + + this.message = document.createElement("div"); + this.message.style.fontSize = "0.9em"; + + this.body.append( + this.avatarPreview, + this.fileInput, + this.chooseBtn, + this.saveBtn, + this.refreshBtn, + this.message + ); + + this.applyStyles(); + this.bindEvents(); + } + + applyStyles() { + // Center avatar in the window body + this.body.style.display = "flex"; + this.body.style.flexDirection = "column"; + this.body.style.alignItems = "center"; + this.body.style.gap = "12px"; + // Style helpers + this.avatarPreview.style.boxShadow = "0 0 8px rgba(0,0,0,0.5)"; + this.chooseBtn.style.padding = "6px 12px"; + this.chooseBtn.style.cursor = "pointer"; + this.saveBtn.style.padding = "6px 12px"; + this.saveBtn.style.cursor = "pointer"; + } + + bindEvents() { + this.fileInput.addEventListener("change", (e) => { + const file = e.target.files && e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (ev) => { + this.avatarPreview.src = ev.target.result; + }; + reader.readAsDataURL(file); + }); + + this.chooseBtn.addEventListener("click", () => { + // trigger file input + this.fileInput.click(); + }); + + this.saveBtn.addEventListener("click", () => { + const url = this.avatarPreview.src; + if (url) { + localStorage.setItem("avatar_url", url); + this.message.textContent = "Avatar enregistré !"; + this.message.style.color = "#3cff01"; + } + }); + + // Bind refresh button to re-fetch avatar from server + this.refreshBtn.addEventListener("click", () => { + this.getPhoto(); + }); + } + async getPhoto(){ + console.log("getPhoto launched..."); + const token = localStorage.getItem("auth_token"); + if (!token) { + console.log("No auth token found; skipping avatar fetch"); + return; + } + try { + const response = await fetch("/api/avatar/me", { + method: "GET", + headers: { + "Authorization": `Bearer ${token}` + } + }); + if (!response.ok) { + console.warn("Failed to fetch avatar (status", response.status, ")"); + return; + } + const data = await response.json(); + if (data && data.avatar_url) { + this.avatarPreview.src = data.avatar_url; + } else { + console.warn("Avatar URL not found in response"); + } + } catch (err) { + console.error("Error while fetching avatar:", err); + } + } + +} diff --git a/srcs/frontend/src/avatarWindows.js b/srcs/frontend/src/avatarWindows.js deleted file mode 100644 index f4cc3f4..0000000 --- a/srcs/frontend/src/avatarWindows.js +++ /dev/null @@ -1,7 +0,0 @@ -import {fenetre} from "./windows.js"; - -export class avatarWindows extends fenetre { - constructor(){ - super(320, 240, "Avatar"); - } -} \ No newline at end of file diff --git a/srcs/frontend/src/login.js b/srcs/frontend/src/login.js index 29c82ac..52b614b 100644 --- a/srcs/frontend/src/login.js +++ b/srcs/frontend/src/login.js @@ -1,5 +1,5 @@ import {fenetre} from "./windows.js"; - +import {avatarWindow} from "./app.js"; export class LoginWindow extends fenetre { constructor() { super(320, 240, "Connexion"); @@ -50,6 +50,7 @@ export class LoginWindow extends fenetre { if (ev.data && ev.data.token) { localStorage.setItem('auth_token', ev.data.token); this.message.innerText = 'Connexion GitHub réussie ! Bienvenue.'; + avatarWindow.getPhoto(); this.message.style.color = '#3cff01'; window.removeEventListener('message', listener); if (popup) popup.close(); @@ -98,7 +99,7 @@ export class LoginWindow extends fenetre { localStorage.setItem("auth_token", data.token); this.message.innerText = "Connexion réussie ! Bienvenue."; this.message.style.color = "#3cff01"; - + avatarWindow.getPhoto(); // mask the window after 1.5s setTimeout(() => this.hide(), 1500); From 4975bcc4ec509d0c0af3b9d895f18f3417887b84 Mon Sep 17 00:00:00 2001 From: bitsearch Date: Thu, 22 Jan 2026 20:17:10 +0100 Subject: [PATCH 2/2] gestion des photo ajoute (frontend), cependant petit bug photo HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHHA --- srcs/backend/avatar/default.png | Bin 2595 -> 0 bytes srcs/frontend/src/avatar.js | 57 ++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) delete mode 100644 srcs/backend/avatar/default.png diff --git a/srcs/backend/avatar/default.png b/srcs/backend/avatar/default.png deleted file mode 100644 index 28f6a850b50d53156607dabe5cb5477ee6e762a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2595 zcmai$S6mVb8;4CL*GHtOl_y2XK_0C%EsqNa3QC$A2RXu#Gdm8#kn%(+h2hANd*)Ws zIPS32G|L>RxN}u1m5K{7@N_P}i|_Ycyzle-|8L%l_vRtn*&=}Aa^eC40zfMy+~F5{ z{~b}GUpwo8XZ0_{+g*0DI5;>E5(4n~{9gqC0N`@DVq#((4hM-uR#jCA35wwH_zM>< zV6oWd=4Ns6|12(khCm=rPEKq#8w3K;Xf$(kb6Hv0XV1#Bva*7MgX81lzkdC?x3`;- zk|VHk&BMwp4PRoEt5Z zO%#uc3Jb>lPn;Eh$e5VBX;M;Oc8Ia;I9k_ooZVqst_@q*LIJS% ze#dkg&fSBdx`q|y!UBsYImA%B5Z$lC_am58TIlL5VjDE)J|=r*f8HOg>*}|+lD`-i z!CWU*PLlr6#f%>XQ(iopmlynui8N}h#765rz1nZgen-^w_iA|XP8G$`7gIRkX8n8= z%{Nt0PprkxP`)l`?|WSG+~$_pbat+fqZ%r%yfLp3!CrGjR43vc?gvkJ^rL~I6MlC_ zFd}s-%l5L+GPPH7>*q%&){kZ}-dMus^*fXVBI85}zUWt!;<$A6ti{q8J0ZhM+^2Rp z-&>+djfl`bc&0doZB>B*3PSW^u<7<|aE`(U%X7~!J_}0rJ&#m0ZqvyiI~@+%l2R+a z^Jp-}g6NzVxaJY_<7d{?<*tr(ui^;B*F{95?(zo{nwCk+!k5`g^&u6CDOa^m9Z?$J z9v+ryZU8*gC}1#9ZV zMCpMYOVYMbmmuuV)NsQ4(;2KDS%@c$tG2M+`KdNs6NAqmI+a}2US#HbvLb7dXDInY zEs5AwIxWt)8;gYfoe`&iUloREI%O+B&8BJ6(g-8RxHbxrQ@kFoRA z)j16}<)hIWZL_vctJfZ6=#Nny9L%KR)ptXFJZCT%8!w^&2*z1JvQ&s2=ifS)NLOv z+yD(K!nFq^+v)a;P?#^4?{D`C8raORy?HeI8vcp!jkvuZ_NPwTe6Dw1Mue!#6f&tyXu?A5 ztc=&zhG(RLL~6&^L>95EtO#7nJ$-h|Tz=7gy=bE)r3x>19fY)T-n4a*P>|t4#nPFe z01)nwd+oO`@*~optTV{VXS$kqN5kZ+s==ksDooIATEIr$$d{KRWADfviF^mlu(4}~ zQyWoPnW2j|;f34NJH}HJXs#(d-FlWFKlG;k+38&R(xPN?C}W5T?8HMxuvcZ z56W?eS^La7z&a#rrjopz6K?U4229WnZ;eGQ-V&OPX#0>pnPL$hef~o$6SN=8yGbF! zbxgBw_mxND(SG69JlZEFIG!bP7Jbo-!EXQf@bNO>En=7J!>IfBkS-q>apPxtq^U3q z)PQ}d_TD-1zAPBDA18MvSSNncd3v+ zn1)k1cfl9rp+y3&(mFf7Mq-{U@bN3@P3}Mr&J%}IEOux+$DQZ2&vq6qgjAk6SFyc5 z<@TUQz=epalsB2KDK-6820m5q2U%~%gIZOj_awSKo%L->XcJ6TC+QD4YmOPY0=QhQ zD1!6;Y{3}$9^ukbT$+E#4@?2LkLekj=`HL{@NQ`DB3&HZmP)@MzIC$z>p<) zO*N2)q~IZ7hpP9~VyXS>a?;qKEmt`1EmX)Ax0?$M8gC|0`zQNDn{bb0=k;(88^}LD zN8fmN12D}^-i4)zi_v=bXJ(exuH9^GngX<3+ZZrZle+5TDgvkQOHxVOor$(J#*^|o zrAP52QTKe^>YkBq(>@ot-OYXtX{s4qazC&G<&QP7Y3O{ACRTWMpc`l#eu6L)vly$Z z=j3?w>Bv46s&_GWrO`!y_@rc5jJzqyC402Y6!JYY(hLr^B8^?nCaxZ7*@*Q}PE0~n zBSIZ}h~$oz)~q3Xn!SOX(XqHcAKx-r)edU)84ad%#6-PA+?c%aU?(_vRBcM=TFLik zVOQYm>JluOl`u7t!!b&)=}H?Xl#_BiH6#bH7uN&d1CG7YI~d8yU45tW*TX!b=@1Ld zz(8zE(SJOWP?aiOd7p&EpUtrDG9`3r4I|4t!mg0A)*X+{*2_zxL7@%3PznkoQdDB;Ct?a;PbWRwHGkU4Rce5jlK7)qVd$rVM57g~;b^hcY zdRc>WC)p0(FAJbNHQ76~t*D}!#)Q3CE%L?e97eHiA<5k*t_K-kmS&&7*-wWCh9HDm z<-*XP)bDk_Y@_eClBXAl$kgD9QJG~#)D7t=c=2NNjy3#9w&>)bD-+?WwlGMSOX!NZ z#&`kSv;*#Bl#kXzzgH^QX>Jx~?_Y&Ob}PzmN-2(^kMT}i!FK7KT%u+NvK)447xgrg z!_*|9-aU!q)y5aMIv*NGR2yR&4bZI}v0JyVPQ~?+IgIX^OJCTt8`Q;v#NIvggq+_< zz0Icoh50-8MZqSH6VHbh^6BgT{Wh>Ods+pXZ)%@Q`fmXw=MP|hcd*^Sf^$F3tLLZk zA1RqhCQa&)=wWm*{#`a9?sO^EO?^KN<*aXpOJOh!fXf9DAAM}nLV=#*oDye_*P;Cr qsNcu?UNufS9`8l`ANdhZ { - const url = this.avatarPreview.src; - if (url) { - localStorage.setItem("avatar_url", url); - this.message.textContent = "Avatar enregistré !"; - this.message.style.color = "#3cff01"; - } + // Send the selected photo to the server + this.postPhoto(); }); // Bind refresh button to re-fetch avatar from server @@ -107,6 +105,7 @@ export class AvatarWindow extends fenetre { return; } const data = await response.json(); + console.log(data); if (data && data.avatar_url) { this.avatarPreview.src = data.avatar_url; } else { @@ -117,4 +116,50 @@ export class AvatarWindow extends fenetre { } } + async postPhoto(){ + console.log("postPhoto launched..."); + const token = localStorage.getItem("auth_token"); + if (!token) { + this.message.textContent = "No auth. plz connect."; + this.message.style.color = "#f00"; + return; + } + const file = this.fileInput.files && this.fileInput.files[0]; + if (!file) { + this.message.textContent = "take image before"; + this.message.style.color = "#f00"; + return; + } + + const formData = new FormData(); + formData.append('avatar', file); + + try { + const response = await fetch('/api/avatar/upload', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formData + }); + const data = await response.json(); + if (!response.ok) { + const err = data?.error || data?.message || 'Upload failed'; + this.message.textContent = err; + this.message.style.color = '#f00'; + return; + } + if (data && data.avatar_url) { + this.avatarPreview.src = data.avatar_url; + } + this.message.textContent = 'Avatar enregistré !'; + this.message.style.color = '#3cff01'; + } catch (err) { + console.error('Avatar upload error:', err); + this.message.textContent = 'Erreur lors de l’envoi'; + this.message.style.color = '#f00'; + } + } + + }