Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 95 additions & 31 deletions public/js/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ function initializeDarkMode() {
}

// ── Layout injection ──────────────────────────────────────────────────
async function inject(id, file, callback) {
const _navbarPromise = fetch('/partials/navbar.html').then(res => res.text()).catch(e => { console.error('Layout fetch error:', e); return null; });
const _footerPromise = fetch('/partials/footer.html').then(res => res.text()).catch(e => { console.error('Layout fetch error:', e); return null; });

async function inject(id, textPromise, callback) {
const el = document.getElementById(id);
if (!el) return;
if (el.dataset.inline === 'true') {
Expand All @@ -109,13 +112,12 @@ async function inject(id, file, callback) {
return;
}
try {
const res = await fetch(file);
el.innerHTML = await res.text();
if (callback && typeof callback === 'function') {
callback();
}
} catch (err) {
console.error(`Failed to load ${file}:`, err);
const html = await textPromise;
if (html === null) return;
el.innerHTML = html;
if (typeof callback === 'function') callback();
} catch (e) {
console.error('Layout inject error:', e);
}
}

Expand All @@ -128,6 +130,49 @@ window.toggleProfileDropdown = function () {
};

// ── Update auth section with profile ─────────────────────────────────
// ── Avatar helpers ───────────────────────────────────────────────────
function _setNavAvatar(initialsId, imgId, name, username, avatarUrl) {
const initials = (name || username || '?').split(' ').map(w => w[0]).slice(0, 2).join('').toUpperCase();
const initialsEl = document.getElementById(initialsId);
const imgEl = document.getElementById(imgId);
if (!initialsEl) return;
if (avatarUrl) {
initialsEl.textContent = initials;
initialsEl.classList.add('hidden');
if (imgEl) {
imgEl.onerror = function () {
this.classList.add('hidden');
initialsEl.classList.remove('hidden');
};
imgEl.src = avatarUrl;
imgEl.classList.remove('hidden');
}
} else {
initialsEl.textContent = initials;
initialsEl.classList.remove('hidden');
if (imgEl) imgEl.classList.add('hidden');
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Keep the old profile-avatar element (button circle) in sync too
function _setNavButtonAvatar(avatarElId, name, username, avatarUrl) {
const el = document.getElementById(avatarElId);
if (!el) return;
const initials = (name || username || '?').split(' ').map(w => w[0]).slice(0, 2).join('').toUpperCase();
if (avatarUrl) {
// Build element programmatically — avoids XSS via inline onerror with user-derived initials
const img = document.createElement('img');
img.src = avatarUrl;
img.className = 'w-full h-full object-cover rounded-full';
img.alt = '';
img.addEventListener('error', () => { el.textContent = initials; });
el.innerHTML = '';
el.appendChild(img);
} else {
el.textContent = initials;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function updateAuthSection() {
const { token, user } = getAuth();
const notLoggedInDiv = document.getElementById('auth-not-logged-in');
Expand All @@ -136,42 +181,58 @@ function updateAuthSection() {
const mobileLoggedIn = document.getElementById('mobile-auth-logged-in');

if (token && user) {
// User is logged in
const firstName = user.name ? user.name.split(' ')[0] : user.username;
const firstLetter = firstName.charAt(0).toUpperCase();
const firstName = user.name ? user.name.split(' ')[0] : user.username;
const avatarUrl = user.avatar_url || '';
const role = user.role || '';

// Desktop view
// Desktop
if (notLoggedInDiv) notLoggedInDiv.classList.add('hidden');
if (loggedInDiv) loggedInDiv.classList.remove('hidden');

const avatarEl = document.getElementById('profile-avatar');
const greetingEl = document.getElementById('profile-greeting');
const usernameEl = document.getElementById('dropdown-username');

if (avatarEl) avatarEl.textContent = firstLetter;
if (greetingEl) greetingEl.textContent = `Hi, ${firstName}`;
if (usernameEl) usernameEl.textContent = user.username;

const notifLink = document.getElementById('notif-bell-link');
if (notifLink) notifLink.classList.remove('hidden');
const cartLink = document.getElementById('cart-nav-link');
if (cartLink) cartLink.classList.remove('hidden');

// Mobile view
// Trigger button circle (existing id="profile-avatar")
_setNavButtonAvatar('profile-avatar', user.name, user.username, avatarUrl);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const greetingEl = document.getElementById('profile-greeting');
const usernameEl = document.getElementById('dropdown-username');
const roleEl = document.getElementById('dropdown-user-role');

if (greetingEl) greetingEl.textContent = `Hi, ${firstName}`;
if (usernameEl) usernameEl.textContent = `@${user.username}`;
if (roleEl) roleEl.textContent = role;

// Dropdown avatar (new elements)
_setNavAvatar('dropdown-avatar-initials', 'dropdown-avatar-img', user.name, user.username, avatarUrl);

// "My Public Profile" deep link
const profileLink = document.getElementById('dropdown-view-profile-link');
if (profileLink) profileLink.href = `/public-profile.html?username=${encodeURIComponent(user.username)}`;

// Mobile
if (mobileNotLoggedIn) mobileNotLoggedIn.classList.add('hidden');
if (mobileLoggedIn) mobileLoggedIn.classList.remove('hidden');

const mobileAvatarEl = document.getElementById('mobile-profile-avatar');
if (mobileLoggedIn) mobileLoggedIn.classList.remove('hidden');

_setNavAvatar('mobile-avatar-initials', 'mobile-avatar-img', user.name, user.username, avatarUrl);

const mobileGreetingEl = document.getElementById('mobile-profile-greeting');

if (mobileAvatarEl) mobileAvatarEl.textContent = firstLetter;
const mobileRoleEl = document.getElementById('mobile-user-role');
if (mobileGreetingEl) mobileGreetingEl.textContent = `Hi, ${firstName}`;
if (mobileRoleEl) mobileRoleEl.textContent = role;

const mobileProfileLink = document.getElementById('mobile-view-profile-link');
if (mobileProfileLink) mobileProfileLink.href = `/public-profile.html?username=${encodeURIComponent(user.username)}`;

runLayoutIdle(startUnreadPolling, 1200);
runLayoutIdle(refreshCartBadge, 1200);
} else {
// User is not logged in
if (notLoggedInDiv) notLoggedInDiv.classList.remove('hidden');
if (loggedInDiv) loggedInDiv.classList.add('hidden');
if (loggedInDiv) loggedInDiv.classList.add('hidden');
if (mobileNotLoggedIn) mobileNotLoggedIn.classList.remove('hidden');
if (mobileLoggedIn) mobileLoggedIn.classList.add('hidden');
if (mobileLoggedIn) mobileLoggedIn.classList.add('hidden');

const badge = document.getElementById('notif-unread-badge');
if (badge) badge.classList.add('hidden');
Expand Down Expand Up @@ -257,6 +318,7 @@ async function refreshCartBadge() {
if (!res.ok) return;
const body = await res.json();
const count = (body.items || []).reduce((sum, item) => sum + Number(item.quantity || 1), 0);
if (link) link.classList.remove('hidden');
if (count > 0) {
badge.textContent = count > 99 ? '99+' : String(count);
badge.classList.remove('hidden');
Expand Down Expand Up @@ -330,8 +392,10 @@ async function initLayout() {
if (!layoutInitPromise) {
layoutInitPromise = (async () => {
initializeDarkMode();
await inject('site-navbar', '/partials/navbar.html', updateAuthSection);
await inject('site-footer', '/partials/footer.html');
await Promise.all([
inject('site-navbar', _navbarPromise, updateAuthSection),
inject('site-footer', _footerPromise),
]);
updateDarkModeIcon();
})();
}
Expand Down
23 changes: 19 additions & 4 deletions public/partials/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,19 @@
<i class="fas fa-chevron-down text-xs"></i>
</button>
<div id="profile-dropdown" class="hidden absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 rounded-lg shadow-lg py-2 z-50">
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
<p id="dropdown-username" class="text-sm font-semibold text-gray-800 dark:text-gray-200"></p>
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700 flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-orange-500 flex items-center justify-center text-white font-bold text-sm overflow-hidden flex-shrink-0">
<span id="dropdown-avatar-initials"></span>
<img id="dropdown-avatar-img" class="hidden w-full h-full object-cover" alt="" />
</div>
<div class="min-w-0">
<p id="dropdown-username" class="text-sm font-semibold text-gray-800 dark:text-gray-200 truncate"></p>
<p id="dropdown-user-role" class="text-xs text-gray-500 dark:text-gray-400 capitalize"></p>
</div>
</div>
<a href="/dashboard" class="block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-tachometer-alt mr-2 text-teal-500"></i>Dashboard</a>
<a href="/profile" class="block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-user mr-2 text-teal-500"></i>Profile</a>
<a id="dropdown-view-profile-link" href="/users.html" class="block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-id-badge mr-2 text-teal-500"></i>My Public Profile</a>
<a href="/notification-preferences" class="block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-sliders mr-2 text-teal-500"></i>Notifications</a>
<button onclick="logout()" class="w-full text-left block px-4 py-2 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30"><i class="fas fa-sign-out-alt mr-2"></i>Logout</button>
</div>
Expand Down Expand Up @@ -95,11 +103,18 @@
</div>
<div id="mobile-auth-logged-in" class="hidden pt-3 border-t border-teal-500/40 space-y-2">
<div class="flex items-center gap-2 px-3 py-2">
<div id="mobile-profile-avatar" class="w-8 h-8 rounded-full bg-orange-500 flex items-center justify-center text-white font-bold text-sm"></div>
<span id="mobile-profile-greeting" class="font-semibold"></span>
<div class="w-8 h-8 rounded-full bg-orange-500 flex items-center justify-center text-white font-bold text-sm overflow-hidden flex-shrink-0">
<span id="mobile-avatar-initials"></span>
<img id="mobile-avatar-img" class="hidden w-full h-full object-cover" alt="" />
</div>
<div class="min-w-0">
<span id="mobile-profile-greeting" class="font-semibold block truncate"></span>
<span id="mobile-user-role" class="text-xs text-teal-100 capitalize block"></span>
</div>
</div>
<a href="/dashboard" class="block px-3 py-2 rounded-lg hover:bg-teal-800"><i class="fas fa-tachometer-alt mr-2"></i>Dashboard</a>
<a href="/profile" class="block px-3 py-2 rounded-lg hover:bg-teal-800"><i class="fas fa-user mr-2"></i>Profile</a>
<a id="mobile-view-profile-link" href="/users.html" class="block px-3 py-2 rounded-lg hover:bg-teal-800"><i class="fas fa-id-badge mr-2"></i>My Public Profile</a>
<button onclick="logout()" class="w-full text-left px-3 py-2 rounded-lg hover:bg-teal-800"><i class="fas fa-sign-out-alt mr-2"></i>Logout</button>
</div>
</div>
Expand Down
Loading
Loading