|
28 | 28 | </button> |
29 | 29 |
|
30 | 30 | <div class="relative"> |
31 | | - <button type="button" data-dropdown-toggle="user-menu" data-dropdown-placement="bottom-end" class="flex items-center gap-2 rounded-full border border-slate-200 bg-white p-0 shadow-sm hover:border-slate-300" aria-expanded="false" aria-haspopup="true"> |
32 | | - <img id="user-avatar" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-9 h-9 rounded-full"> |
33 | | - <!-- <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
34 | | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path> |
35 | | - </svg> --> |
| 31 | + <button type="button" id="avatar-container" data-dropdown-toggle="user-menu" data-dropdown-placement="bottom-end" class="flex items-center gap-2 rounded-full border border-slate-200 bg-white p-0 shadow-sm hover:border-slate-300" aria-expanded="false" aria-haspopup="true"> |
| 32 | + <img id="user-avatar" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-9 h-9 rounded-full object-cover"> |
| 33 | + <input type="file" id="avatar-input" class="hidden" accept="image/jpeg,image/png,image/webp"> |
36 | 34 | </button> |
37 | 35 |
|
38 | 36 | <div id="user-menu" class="hidden absolute right-0 mt-2 w-64 rounded-2xl border border-slate-200 bg-white p-3 shadow-lg"> |
39 | 37 | <div class="flex items-center gap-3 border-b border-slate-100 pb-3"> |
40 | | - <img id="user-avatar-menu" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-10 h-10 rounded-full"> |
| 38 | + <div class="relative group"> |
| 39 | + <img id="user-avatar-menu" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-10 h-10 rounded-full object-cover"> |
| 40 | + <button id="change-avatar-btn" class="absolute inset-0 bg-black/40 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity text-white" title="Change Avatar"> |
| 41 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 42 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path> |
| 43 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path> |
| 44 | + </svg> |
| 45 | + </button> |
| 46 | + </div> |
41 | 47 | <div> |
42 | 48 | <p id="user-name" class="text-sm font-semibold text-slate-900">User</p> |
43 | 49 | <p id="user-email" class="text-xs text-slate-500">user@stackdev.cloud</p> |
|
62 | 68 | </div> |
63 | 69 | </header> |
64 | 70 |
|
65 | | -<script> |
66 | | - (function () { |
67 | | - const logoutButton = document.getElementById("logout-button"); |
| 71 | +<script type="module"> |
| 72 | + import { authProvider } from '/utils/auth.js'; |
| 73 | +
|
| 74 | + document.addEventListener("DOMContentLoaded", () => { |
68 | 75 | const userName = document.getElementById("user-name"); |
69 | 76 | const userEmail = document.getElementById("user-email"); |
70 | 77 | const userAvatar = document.getElementById("user-avatar"); |
71 | 78 | const userAvatarMenu = document.getElementById("user-avatar-menu"); |
| 79 | + const avatarInput = document.getElementById("avatar-input"); |
| 80 | + const changeAvatarBtn = document.getElementById("change-avatar-btn"); |
| 81 | + const logoutButton = document.getElementById("logout-button"); |
72 | 82 |
|
73 | | - const readCookie = (name) => { |
74 | | - return document.cookie |
75 | | - .split(";") |
76 | | - .map((cookie) => cookie.trim()) |
77 | | - .find((cookie) => cookie.startsWith(`${name}=`)) |
78 | | - ?.split("=")[1]; |
79 | | - }; |
80 | | -
|
81 | | - const clearCookie = (name) => { |
82 | | - document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`; |
83 | | - }; |
| 83 | + authProvider.subscribe((user) => { |
| 84 | + if (user) { |
| 85 | + if (userName) userName.textContent = user.name; |
| 86 | + if (userEmail) userEmail.textContent = user.email || ""; |
| 87 | + |
| 88 | + const defaultAvatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=0f766e&color=fff`; |
| 89 | + const avatarUrl = user.avatar || defaultAvatar; |
84 | 90 |
|
85 | | - try { |
86 | | - const storedUser = localStorage.getItem("user"); |
87 | | - if (storedUser) { |
88 | | - const parsed = JSON.parse(storedUser); |
89 | | - if (parsed?.name && userName) userName.textContent = parsed.name; |
90 | | - if (parsed?.email && userEmail) userEmail.textContent = parsed.email; |
91 | | - if (parsed?.name && userAvatar && userAvatarMenu) { |
92 | | - const avatarUrl = `https://ui-avatars.com/api/?name=${encodeURIComponent(parsed.name)}&background=0f766e&color=fff`; |
93 | | - userAvatar.src = avatarUrl; |
94 | | - userAvatarMenu.src = avatarUrl; |
95 | | - } |
| 91 | + if (userAvatar) userAvatar.src = avatarUrl; |
| 92 | + if (userAvatarMenu) userAvatarMenu.src = avatarUrl; |
96 | 93 | } |
97 | | - } catch (error) { |
98 | | - // Ignore localStorage errors |
99 | | - } |
| 94 | + }); |
100 | 95 |
|
101 | | - if (!logoutButton) return; |
| 96 | + if (changeAvatarBtn && avatarInput) { |
| 97 | + changeAvatarBtn.addEventListener("click", (e) => { |
| 98 | + e.stopPropagation(); |
| 99 | + avatarInput.click(); |
| 100 | + }); |
102 | 101 |
|
103 | | - logoutButton.addEventListener("click", async () => { |
104 | | - const token = localStorage.getItem("accessToken") || readCookie("accessToken"); |
| 102 | + avatarInput.addEventListener("change", async (e) => { |
| 103 | + const file = e.target.files[0]; |
| 104 | + if (!file) return; |
105 | 105 |
|
106 | | - if (token) { |
107 | | - await fetch("/api/auth/logout", { |
108 | | - method: "POST", |
109 | | - headers: { |
110 | | - Authorization: `Bearer ${token}` |
111 | | - } |
112 | | - }).catch(() => null); |
113 | | - } |
| 106 | + try { |
| 107 | + changeAvatarBtn.disabled = true; |
| 108 | + await authProvider.uploadAvatar(file); |
| 109 | + } catch (err) { |
| 110 | + console.error("Upload error:", err); |
| 111 | + alert(err.message || "Failed to upload avatar"); |
| 112 | + } finally { |
| 113 | + changeAvatarBtn.disabled = false; |
| 114 | + avatarInput.value = ""; |
| 115 | + } |
| 116 | + }); |
| 117 | + } |
114 | 118 |
|
115 | | - localStorage.removeItem("accessToken"); |
116 | | - localStorage.removeItem("refreshToken"); |
117 | | - localStorage.removeItem("user"); |
118 | | - clearCookie("accessToken"); |
119 | | - clearCookie("refreshToken"); |
120 | | - window.location.href = "/login"; |
121 | | - }); |
122 | | - })(); |
| 119 | + if (logoutButton) { |
| 120 | + logoutButton.addEventListener("click", () => { |
| 121 | + authProvider.logout(); |
| 122 | + }); |
| 123 | + } |
| 124 | + }); |
123 | 125 | </script> |
0 commit comments