elearning/Frontend-Learner/components/user/UserMenu.vue

127 lines
3.6 KiB
Vue
Raw Normal View History

2026-01-13 10:46:40 +07:00
<script setup lang="ts">
/**
* @file UserMenu.vue
* @description User profile dropdown menu component using Quasar.
2026-01-13 10:46:40 +07:00
*/
import { ref, computed, onMounted } from 'vue'
2026-01-13 10:46:40 +07:00
import { useAuth } from '~/composables/useAuth'
import { useQuasar } from 'quasar'
2026-01-13 10:46:40 +07:00
const { currentUser, logout } = useAuth()
const { t } = useI18n()
const $q = useQuasar()
2026-01-13 10:46:40 +07:00
const isDarkMode = ref(false)
const isHydrated = ref(false)
const applyTheme = (value: boolean) => {
// Tailwind dark mode
document.documentElement.classList.toggle('dark', value)
// Quasar dark mode
$q.dark.set(value)
// persist
localStorage.setItem('theme', value ? 'dark' : 'light')
}
const toggleDarkMode = (val: boolean) => {
isDarkMode.value = val
applyTheme(val)
}
2026-01-13 10:46:40 +07:00
// Sync Dark Mode state on mount
2026-01-13 10:46:40 +07:00
onMounted(() => {
isHydrated.value = true
const savedTheme = localStorage.getItem('theme')
const preferredDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false
isDarkMode.value = savedTheme
? savedTheme === 'dark'
: preferredDark
2026-01-13 10:46:40 +07:00
applyTheme(isDarkMode.value)
})
// User Initials
2026-01-13 10:46:40 +07:00
const userInitials = computed(() => {
if (!currentUser.value) return ''
const f = currentUser.value.firstName?.charAt(0).toUpperCase() || 'U'
2026-01-13 10:46:40 +07:00
const l = currentUser.value.lastName?.charAt(0).toUpperCase() || ''
return f + l
})
const menuItems = computed(() => [
{ label: t('userMenu.home'), to: '/dashboard' },
{ label: t('userMenu.courseList'), to: '/browse/discovery' },
{ label: t('userMenu.myCourses'), to: '/dashboard/my-courses' },
{ label: t('userMenu.settings'), to: '/dashboard/profile' }
])
2026-01-13 10:46:40 +07:00
const handleLogout = async () => {
await logout()
}
</script>
<template>
<div class="q-pa-md">
<q-btn round flat class="text-slate-700 dark:text-white">
<q-avatar color="primary" text-color="white" size="40px" font-size="14px" class="font-bold shadow-md">
{{ userInitials }}
</q-avatar>
<q-menu
anchor="bottom end"
self="top end"
:offset="[0, 10]"
content-class="bg-white dark:bg-slate-800 text-slate-900 dark:text-white rounded-2xl shadow-xl border border-slate-200/70 dark:border-white/10"
style="min-width: 240px;"
2026-01-13 10:46:40 +07:00
>
<q-list class="py-2">
<q-item
v-for="item in menuItems"
:key="item.label"
clickable
v-close-popup
@click="navigateTo(item.to)"
class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors"
>
<q-item-section>
<q-item-label class="font-bold text-sm">{{ item.label }}</q-item-label>
</q-item-section>
</q-item>
<q-item class="hover:bg-slate-100 dark:hover:bg-white/10 transition-colors">
<q-item-section>
<q-item-label class="font-bold text-sm">{{ $t('userMenu.darkMode') }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-toggle
v-if="isHydrated"
:model-value="isDarkMode"
@update:model-value="toggleDarkMode"
color="blue"
keep-color
/>
</q-item-section>
</q-item>
<q-separator class="bg-slate-100 dark:bg-white/10 my-1" />
<div class="p-4">
<q-btn
unelevated
class="w-full bg-red-500/10 text-red-400 hover:bg-red-500/20 font-bold rounded-lg"
no-caps
2026-01-13 10:46:40 +07:00
@click="handleLogout"
>
{{ $t('userMenu.logout') }}
</q-btn>
2026-01-13 10:46:40 +07:00
</div>
</q-list>
</q-menu>
</q-btn>
2026-01-13 10:46:40 +07:00
</div>
</template>