Ukweli / web /src /components /UserMenu.vue
Benard John
feat: implement full-stack architecture with database models, authentication services, and web dashboard components
37b5223
Raw
History Blame Contribute Delete
4.52 kB
<template>
<n-dropdown
trigger="click"
placement="bottom-end"
:options="menuOptions"
@select="onSelect"
>
<button class="user-menu-trigger" :aria-label="`Account menu for ${userLabel}`">
<span class="avatar">
{{ userInitial }}
</span>
<span class="user-meta">
<span class="user-name">{{ userLabel }}</span>
<span class="user-tier">{{ tierLabel }}</span>
</span>
<n-icon size="14" class="chevron">
<chevron-down-outline />
</n-icon>
</button>
</n-dropdown>
</template>
<script setup lang="ts">
import { computed, h } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { NIcon } from 'naive-ui'
import {
ChevronDownOutline,
PersonOutline,
KeyOutline,
LogOutOutline,
MoonOutline,
SunnyOutline,
} from '@vicons/ionicons5'
import { useThemeStore } from '@/stores/theme'
const authStore = useAuthStore()
const themeStore = useThemeStore()
const router = useRouter()
const userLabel = computed(() => {
if (authStore.user?.display_name) return authStore.user.display_name
if (authStore.user?.email) {
const [local] = authStore.user.email.split('@')
return local
}
return 'Account'
})
const userInitial = computed(() =>
userLabel.value.charAt(0).toUpperCase()
)
const tierLabel = computed(() => {
switch (authStore.tier) {
case 'registered': return 'REGISTERED'
case 'api': return 'API'
case 'enterprise': return 'ENTERPRISE'
default: return 'PUBLIC'
}
})
function renderIcon(component: any) {
return () => h(NIcon, null, { default: () => h(component) })
}
const menuOptions = computed(() => [
{
key: 'header',
type: 'render',
render: () =>
h('div', { class: 'menu-header' }, [
h('div', { class: 'menu-header-email' }, authStore.user?.email || ''),
h('div', { class: 'menu-header-tier' }, tierLabel.value),
]),
},
{ type: 'divider', key: 'd1' },
{
label: 'Account',
key: 'account',
icon: renderIcon(PersonOutline),
},
{
label: 'API keys',
key: 'api-keys',
icon: renderIcon(KeyOutline),
},
{
label: themeStore.theme === 'dark' ? 'Light mode' : 'Dark mode',
key: 'theme',
icon: renderIcon(themeStore.theme === 'dark' ? SunnyOutline : MoonOutline),
},
{ type: 'divider', key: 'd2' },
{
label: 'Sign out',
key: 'logout',
icon: renderIcon(LogOutOutline),
},
])
function onSelect(key: string) {
switch (key) {
case 'account':
// No dedicated page yet — open modal at user info step
authStore.openAuthModal()
break
case 'api-keys':
router.push('/api-keys')
break
case 'theme':
themeStore.toggle()
break
case 'logout':
authStore.logout()
break
}
}
</script>
<style scoped lang="scss">
.user-menu-trigger {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 10px 4px 4px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 999px;
cursor: pointer;
transition: all 0.2s var(--ease-out);
color: var(--text-primary);
&:hover {
border-color: var(--primary);
background: var(--bg-card);
}
}
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
color: white;
font-weight: 700;
font-size: 12px;
font-family: var(--font-display);
}
.user-meta {
display: flex;
flex-direction: column;
line-height: 1.1;
text-align: left;
}
.user-name {
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-tier {
font-size: 9px;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--primary);
font-family: var(--font-mono);
}
.chevron {
color: var(--text-muted);
}
@media (max-width: 768px) {
.user-meta { display: none; }
.chevron { display: none; }
}
</style>
<style lang="scss">
// Global: dropdown menu header styling
.menu-header {
padding: 4px 12px 8px;
.menu-header-email {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
word-break: break-all;
}
.menu-header-tier {
font-size: 10px;
color: var(--primary);
font-weight: 700;
letter-spacing: 0.08em;
margin-top: 2px;
}
}
</style>