add profile menu

This commit is contained in:
2026-02-03 16:37:47 +01:00
parent 016be73c6c
commit 39f9f16b3c
5 changed files with 163 additions and 20 deletions

View File

@@ -391,7 +391,7 @@ app-shell main {
.dropdown-menu {
position: absolute;
top: 100%;
top: 45px;
right: 0;
min-width: 150px;
background-color: var(--color-bg);
@@ -413,12 +413,20 @@ app-shell main {
}
.dropdown-item {
display: block;
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
padding: var(--space-sm) var(--space-md);
text-align: left;
border-radius: var(--radius-sm);
color: var(--color-text);
background: none;
border: none;
font-size: inherit;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
}
.dropdown-item:hover {
@@ -429,3 +437,32 @@ app-shell main {
background-color: var(--color-primary-light);
color: var(--color-primary);
}
.dropdown-item svg {
flex-shrink: 0;
opacity: 0.7;
}
.dropdown-item-danger {
color: var(--color-error);
}
.dropdown-item-danger:hover {
background-color: var(--color-error);
color: white;
}
.dropdown-item-danger:hover svg {
opacity: 1;
}
.dropdown-divider {
height: 1px;
background-color: var(--color-border);
margin: var(--space-xs) 0;
}
.dropdown-menu-right {
right: 0;
left: auto;
}

View File

@@ -6,6 +6,7 @@ class AppHeader extends HTMLElement {
constructor() {
super()
this.langDropdownOpen = false
this.profileDropdownOpen = false
this.handleOutsideClick = this.handleOutsideClick.bind(this)
this.handleKeydown = this.handleKeydown.bind(this)
this.handleScroll = this.handleScroll.bind(this)
@@ -38,7 +39,10 @@ class AppHeader extends HTMLElement {
handleOutsideClick() {
if (this.langDropdownOpen) {
this.closeDropdown()
this.closeLangDropdown()
}
if (this.profileDropdownOpen) {
this.closeProfileDropdown()
}
}
@@ -51,7 +55,7 @@ class AppHeader extends HTMLElement {
switch (e.key) {
case 'Escape':
e.preventDefault()
this.closeDropdown()
this.closeLangDropdown()
this.querySelector('#lang-toggle')?.focus()
break
case 'ArrowDown':
@@ -67,7 +71,7 @@ class AppHeader extends HTMLElement {
}
}
closeDropdown() {
closeLangDropdown() {
this.langDropdownOpen = false
const langDropdown = this.querySelector('#lang-dropdown')
const langToggle = this.querySelector('#lang-toggle')
@@ -75,13 +79,29 @@ class AppHeader extends HTMLElement {
langToggle?.setAttribute('aria-expanded', 'false')
}
openDropdown() {
openLangDropdown() {
this.langDropdownOpen = true
const langDropdown = this.querySelector('#lang-dropdown')
const langToggle = this.querySelector('#lang-toggle')
langDropdown?.classList.add('open')
langToggle?.setAttribute('aria-expanded', 'true')
this.querySelector('.dropdown-item.active')?.focus()
this.querySelector('#lang-dropdown .dropdown-item.active')?.focus()
}
closeProfileDropdown() {
this.profileDropdownOpen = false
const dropdown = this.querySelector('#profile-dropdown')
const toggle = this.querySelector('#profile-toggle')
dropdown?.classList.remove('open')
toggle?.setAttribute('aria-expanded', 'false')
}
openProfileDropdown() {
this.profileDropdownOpen = true
const dropdown = this.querySelector('#profile-dropdown')
const toggle = this.querySelector('#profile-toggle')
dropdown?.classList.add('open')
toggle?.setAttribute('aria-expanded', 'true')
}
render() {
@@ -151,13 +171,60 @@ class AppHeader extends HTMLElement {
</div>
${auth.isLoggedIn() ? `
<button class="btn btn-outline btn-profile" id="profile-btn" title="${t('header.profile')}">
<div class="dropdown" id="profile-dropdown">
<button
class="btn btn-icon btn-outline"
id="profile-toggle"
aria-haspopup="menu"
aria-expanded="${this.profileDropdownOpen}"
aria-label="${t('header.profile')}"
title="${t('header.profile')}"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span class="btn-profile-text">${t('header.profile')}</span>
</button>
<div class="dropdown-menu dropdown-menu-right" role="menu">
<a href="#/my-listings" class="dropdown-item" role="menuitem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
${t('profile.myListings')}
</a>
<a href="#/messages" class="dropdown-item" role="menuitem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
${t('profile.messages')}
</a>
<a href="#/favorites" class="dropdown-item" role="menuitem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
${t('profile.favorites')}
</a>
<div class="dropdown-divider"></div>
<a href="#/settings" class="dropdown-item" role="menuitem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
${t('profile.settings')}
</a>
<button class="dropdown-item dropdown-item-danger" id="logout-btn" role="menuitem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
${t('auth.logout')}
</button>
</div>
</div>
` : `
<button class="btn btn-outline btn-login" id="login-btn" title="${t('auth.login')}">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -182,9 +249,9 @@ class AppHeader extends HTMLElement {
langToggle.addEventListener('click', (e) => {
e.stopPropagation()
if (this.langDropdownOpen) {
this.closeDropdown()
this.closeLangDropdown()
} else {
this.openDropdown()
this.openLangDropdown()
}
})
@@ -204,10 +271,31 @@ class AppHeader extends HTMLElement {
}
})
// Profile button
const profileBtn = this.querySelector('#profile-btn')
profileBtn?.addEventListener('click', () => {
router.navigate('/profile')
// Profile dropdown
const profileDropdown = this.querySelector('#profile-dropdown')
const profileToggle = this.querySelector('#profile-toggle')
profileToggle?.addEventListener('click', (e) => {
e.stopPropagation()
if (this.profileDropdownOpen) {
this.closeProfileDropdown()
} else {
this.openProfileDropdown()
}
})
// Close dropdown when clicking menu items
profileDropdown?.querySelectorAll('.dropdown-item').forEach(item => {
item.addEventListener('click', () => {
this.closeProfileDropdown()
})
})
// Logout button
const logoutBtn = this.querySelector('#logout-btn')
logoutBtn?.addEventListener('click', async () => {
await auth.logout()
router.navigate('/')
})
this.updateThemeIcon()

View File

@@ -180,6 +180,12 @@
"attempts": "Versuche",
"error": "Fehler - erneut versuchen"
},
"profile": {
"myListings": "Meine Anzeigen",
"messages": "Nachrichten",
"favorites": "Favoriten",
"settings": "Einstellungen"
},
"auth": {
"login": "Anmelden",
"logout": "Abmelden",

View File

@@ -180,6 +180,12 @@
"attempts": "attempts",
"error": "Error - try again"
},
"profile": {
"myListings": "My Listings",
"messages": "Messages",
"favorites": "Favorites",
"settings": "Settings"
},
"auth": {
"login": "Login",
"logout": "Logout",

View File

@@ -180,6 +180,12 @@
"attempts": "tentatives",
"error": "Erreur - réessayer"
},
"profile": {
"myListings": "Mes annonces",
"messages": "Messages",
"favorites": "Favoris",
"settings": "Paramètres"
},
"auth": {
"login": "Connexion",
"logout": "Déconnexion",