93d32d2a9f
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
122 lines
4.9 KiB
Vue
122 lines
4.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useUserStore } from '@KTXC/stores/userStore';
|
|
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
|
import { useLayoutStore } from '@KTXC/stores/layoutStore';
|
|
import { useRouter } from 'vue-router';
|
|
import { useL10n, t as tGlobal } from '@KTXC/composables/useL10n';
|
|
import defaultAvatar from '@KTXC/assets/images/users/avatar-1.png';
|
|
|
|
const { t } = useL10n('core');
|
|
|
|
const router = useRouter();
|
|
const userStore = useUserStore();
|
|
const integrationStore = useIntegrationStore();
|
|
const layoutStore = useLayoutStore();
|
|
|
|
const userAuth = computed(() => userStore.auth);
|
|
const profileMenuItems = computed(() => integrationStore.getItems('profile_menu'));
|
|
const userAvatar = computed(() => userStore.getProfileField('avatar') || defaultAvatar);
|
|
const userName = computed(() => {
|
|
const given = userStore.getProfileField('name_given');
|
|
const family = userStore.getProfileField('name_family');
|
|
if (given && family) return `${given} ${family}`;
|
|
return userAuth.value?.label || 'User';
|
|
});
|
|
const userEmail = computed(() => userStore.getProfileField('email') || '');
|
|
|
|
// Theme mode cycle: light -> dark -> system, driven by the stored preference
|
|
// (not the resolved theme name, which never reads 'system')
|
|
const THEME_MODES = ['light', 'dark', 'system'] as const;
|
|
type ThemeMode = (typeof THEME_MODES)[number];
|
|
|
|
const MODE_PRESENTATION: Record<ThemeMode, { icon: string; l10n: string; label: string }> = {
|
|
light: { icon: 'mdi-weather-sunny', l10n: 'userMenu.lightMode', label: 'Light Mode' },
|
|
dark: { icon: 'mdi-weather-night', l10n: 'userMenu.darkMode', label: 'Dark Mode' },
|
|
system: { icon: 'mdi-theme-light-dark', l10n: 'userMenu.systemMode', label: 'System Mode' },
|
|
};
|
|
|
|
const themeMode = computed(() => (layoutStore.theme ?? 'light') as ThemeMode);
|
|
const nextThemeMode = computed(() => THEME_MODES[(THEME_MODES.indexOf(themeMode.value) + 1) % THEME_MODES.length]);
|
|
const cycleTheme = () => {
|
|
// App.vue watches layoutStore.theme and applies it via theme.change()
|
|
layoutStore.setTheme(nextThemeMode.value);
|
|
};
|
|
|
|
// Navigate to settings
|
|
const goToSettings = () => {
|
|
layoutStore.setMenuMode('settings');
|
|
// Navigate to first settings item or a default settings route
|
|
router.push('/modules'); // TODO: Make this dynamic based on first settings menu item
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<!-- ---------------------------------------------- -->
|
|
<!-- Profile Dropdown -->
|
|
<!-- ---------------------------------------------- -->
|
|
<div>
|
|
<!-- User Info Header -->
|
|
<div class="d-flex align-center pa-5 pb-4">
|
|
<v-avatar size="40" class="mr-3">
|
|
<v-img :src="userAvatar" :alt="userName" cover />
|
|
</v-avatar>
|
|
<div class="flex-grow-1">
|
|
<h6 class="text-h6 mb-0 font-weight-medium">
|
|
{{ userName }}
|
|
</h6>
|
|
<p class="text-caption mb-0 text-medium-emphasis">{{ userEmail }}</p>
|
|
</div>
|
|
</div>
|
|
<v-divider />
|
|
|
|
<perfect-scrollbar style="max-height: 280px">
|
|
<v-list class="py-0" aria-label="profile menu" aria-busy="true">
|
|
<!-- Dynamic Profile Menu Items (from modules) -->
|
|
<v-list-item
|
|
v-for="item in profileMenuItems"
|
|
:key="item.id"
|
|
:to="item.toType === 'external' ? undefined : item.to"
|
|
:href="item.toType === 'external' ? item.to : undefined"
|
|
:target="item.toType === 'external' ? '_blank' : undefined"
|
|
color="primary"
|
|
rounded="0"
|
|
>
|
|
<template v-slot:prepend>
|
|
<v-icon v-if="item.icon">{{ item.icon }}</v-icon>
|
|
</template>
|
|
<v-list-item-title class="text-h6">{{ tGlobal(item.l10n, item.label) }}</v-list-item-title>
|
|
</v-list-item>
|
|
|
|
<v-divider v-if="profileMenuItems.length" class="my-2" />
|
|
|
|
<!-- Theme Mode Cycle (light -> dark -> system) -->
|
|
<v-list-item @click="cycleTheme" color="primary" rounded="0">
|
|
<template v-slot:prepend>
|
|
<v-icon>{{ MODE_PRESENTATION[nextThemeMode].icon }}</v-icon>
|
|
</template>
|
|
<v-list-item-title class="text-h6">{{ t(MODE_PRESENTATION[nextThemeMode].l10n, MODE_PRESENTATION[nextThemeMode].label) }}</v-list-item-title>
|
|
</v-list-item>
|
|
|
|
<!-- Go to Settings -->
|
|
<v-list-item @click="goToSettings" color="primary" rounded="0">
|
|
<template v-slot:prepend>
|
|
<v-icon>mdi-cog-outline</v-icon>
|
|
</template>
|
|
<v-list-item-title class="text-h6">{{ t('userMenu.settings', 'Settings') }}</v-list-item-title>
|
|
</v-list-item>
|
|
|
|
<v-divider class="my-2" />
|
|
|
|
<!-- Logout -->
|
|
<v-list-item @click="userStore.logout()" color="secondary" rounded="0">
|
|
<template v-slot:prepend>
|
|
<v-icon>mdi-logout</v-icon>
|
|
</template>
|
|
<v-list-item-title class="text-h6">{{ t('userMenu.logout', 'Logout') }}</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</perfect-scrollbar>
|
|
</div>
|
|
</template>
|