vbv/client/src/components/MainNavigationBar.vue

315 lines
10 KiB
Vue

<script setup lang="ts">
import log from "loglevel";
import IconLogout from "@/components/icons/IconLogout.vue";
import IconSettings from "@/components/icons/IconSettings.vue";
import MobileMenu from "@/components/MobileMenu.vue";
import NotificationPopover from "@/components/notifications/NotificationPopover.vue";
import NotificationPopoverContent from "@/components/notifications/NotificationPopoverContent.vue";
import ItDropdown from "@/components/ui/ItDropdown.vue";
import { useAppStore } from "@/stores/app";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useNotificationsStore } from "@/stores/notifications";
import { useUserStore } from "@/stores/user";
import type { DropdownListItem } from "@/types";
import type { Component } from "vue";
import { onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
type DropdownActions = "logout" | "settings" | "profile";
interface DropdownData {
action: DropdownActions;
}
log.debug("MainNavigationBar created");
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const appStore = useAppStore();
const courseSessionsStore = useCourseSessionsStore();
const notificationsStore = useNotificationsStore();
const { t } = useI18n();
const state = reactive({ showMenu: false });
function toggleNav() {
state.showMenu = !state.showMenu;
}
function inCourse() {
return route.path.startsWith("/course/");
}
function inCockpit() {
const regex = new RegExp("/course/[^/]+/cockpit");
return regex.test(route.path);
}
function inLearningPath() {
const regex = new RegExp("/course/[^/]+/learn");
return regex.test(route.path);
}
function inCompetenceProfile() {
const regex = new RegExp("/course/[^/]+/competence");
return regex.test(route.path);
}
function inMediaLibrary() {
return route.path.startsWith("/media/");
}
function handleDropdownSelect(data: DropdownData) {
switch (data.action) {
case "profile":
router.push("/profile");
break;
case "settings":
router.push("/settings");
break;
case "logout":
userStore.handleLogout();
break;
default:
console.log("No action");
}
}
function logout() {
userStore.handleLogout();
}
onMounted(() => {
log.debug("MainNavigationBar mounted");
if (userStore.loggedIn) {
// fixme: only when i'm logged in? should this be handled in the store?
// courseSessionsStore.loadCourseSessionsData();
}
});
const profileDropdownData: DropdownListItem[] = [
{
title: t("mainNavigation.profile"),
icon: IconSettings as Component,
data: {
action: "profile",
},
},
{
title: t("general.settings"),
icon: IconSettings as Component,
data: {
action: "settings",
},
},
{
title: t("mainNavigation.logout"),
icon: IconLogout as Component,
data: {
action: "logout",
},
},
];
</script>
<template>
<div>
<Teleport to="body">
<MobileMenu
v-if="userStore.loggedIn"
:show="state.showMenu"
:course-session="courseSessionsStore.courseSessionForRoute"
@closemodal="state.showMenu = false"
/>
</Teleport>
<Transition name="nav">
<div v-if="appStore.showMainNavigationBar" class="navigation bg-blue-900">
<nav class="mx-auto px-8 py-2 lg:flex lg:items-center lg:justify-start lg:py-4">
<div class="flex items-center justify-between">
<div class="flex items-center">
<router-link to="/" class="flex">
<it-icon-vbv class="mr-3 -mt-6 -ml-3 h-8 w-16" />
</router-link>
<router-link to="/" class="flex">
<div class="ml-1 border-l border-white pr-10 pl-3 text-2xl text-white">
myVBV
</div>
</router-link>
</div>
<div class="flex items-center lg:hidden">
<div v-if="userStore.loggedIn" class="mr-6 flex flex-row items-center">
<NotificationPopover>
<template #toggleButtonContent>
<div class="nav-item flex">
<it-icon-notification class="h-6 w-6" />
<div
v-if="notificationsStore.hasUnread"
aria-label="unread notifications"
class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"
/>
</div>
</template>
<template #popoverContent>
<NotificationPopoverContent />
</template>
</NotificationPopover>
</div>
<router-link
v-if="userStore.loggedIn"
to="/messages"
class="nav-item flex flex-row items-center"
data-cy="messages-link"
>
<it-icon-persons class="mr-6 h-6 w-6" />
</router-link>
<!-- Mobile menu button -->
<div class="flex" @click="toggleNav">
<button
type="button"
class="h-8 w-8 text-white hover:text-sky-500 focus:text-sky-500 focus:outline-none"
>
<it-icon-menu class="h-8 w-8" />
</button>
</div>
</div>
</div>
<!-- Mobile Menu open: "block", Menu closed: "hidden" -->
<div
v-if="appStore.userLoaded && appStore.routingFinished && userStore.loggedIn"
:class="state.showMenu ? 'flex' : 'hidden'"
class="mt-8 flex-auto lg:mt-0 lg:flex lg:flex-row lg:items-center lg:space-y-0 lg:space-x-10"
>
<!-- <router-link-->
<!-- v-if="inCourse() && courseSessionsStore.courseSessionForRoute"-->
<!-- :to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"-->
<!-- class="nav-item"-->
<!-- :class="{ 'nav-item&#45;&#45;active': inCockpit() }"-->
<!-- >-->
<!-- Cockpit-->
<!-- </router-link>-->
<router-link
v-if="
inCourse() &&
courseSessionsStore.courseSessionForRoute &&
courseSessionsStore.hasCockpit
"
:to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"
class="nav-item"
:class="{ 'nav-item--active': inCockpit() }"
>
{{ $t("cockpit.title") }}
</router-link>
<router-link
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.learning_path_url"
class="nav-item"
:class="{ 'nav-item--active': inLearningPath() }"
>
{{ $t("general.learningPath") }}
</router-link>
<router-link
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.competence_url"
class="nav-item"
:class="{ 'nav-item--active': inCompetenceProfile() }"
>
{{ $t("competences.title") }}
</router-link>
<div class="hidden flex-auto lg:block"></div>
<a
class="nav-item"
target="_blank"
href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf"
>
{{ $t("general.shop") }}
</a>
<router-link
v-if="courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.media_library_url"
class="nav-item"
:class="{ 'nav-item--active': inMediaLibrary() }"
data-cy="medialibrary-link"
>
{{ $t("mediaLibrary.title") }}
</router-link>
<div v-if="userStore.loggedIn" class="mr-6 flex items-center">
<NotificationPopover>
<template #toggleButtonContent>
<div class="nav-item flex">
<it-icon-notification class="h-6 w-6" />
<div
v-if="notificationsStore.hasUnread"
aria-label="unread notifications"
class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"
/>
</div>
</template>
<template #popoverContent>
<NotificationPopoverContent />
</template>
</NotificationPopover>
</div>
<router-link
to="/messages"
class="nav-item flex flex-row items-center"
data-cy="messages-link"
>
<it-icon-persons class="mr-6 h-6 w-6" />
</router-link>
<div v-if="userStore.loggedIn" class="nav-item flex items-center">
<ItDropdown
:button-classes="[]"
:list-items="profileDropdownData"
:align="'right'"
@select="handleDropdownSelect"
>
<div v-if="userStore.avatar_url">
<img
class="inline-block h-8 w-8 rounded-full"
:src="userStore.avatar_url"
alt=""
/>
</div>
<div v-else>
{{ userStore.getFullName }}
</div>
</ItDropdown>
</div>
<div v-else><a class="" href="/login">Login</a></div>
</div>
</nav>
</div>
</Transition>
</div>
</template>
<style lang="postcss" scoped>
.nav-item {
@apply text-2xl font-bold text-white hover:text-sky-500 lg:text-base lg:font-normal;
}
.nav-item--active {
@apply underline decoration-sky-500 decoration-4 underline-offset-[21px];
}
.nav-enter-active,
.nav-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.nav-enter-from,
.nav-leave-to {
opacity: 0;
transform: translateY(-80px);
}
</style>