-
Anders Høvik authoredAnders Høvik authored
NavBar.vue 14.54 KiB
<template>
<nav id="navBar" class="navbar navbar-expand-xl">
<div class="container-fluid">
<router-link class="navbar-brand" id="home" :to="toSavingGoals()">
<img id="logoImg" src="/src/assets/Sparesti-logo.png" alt="Sparesti-logo" width="60">
<span id="logo" class="text-white">SpareSti</span>
</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Bytt navigasjon">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 ui-menu">
<li class="nav-item">
<router-link data-cy="savingGoals" class="nav-link text-white"
:to="toSavingGoals()" exact-active-class="active-nav">
<img src="@/assets/icons/saving.svg">Sparemål
</router-link>
</li>
<li class="nav-item">
<router-link data-cy="leaderboard" class="nav-link text-white"
:to="toLeaderboard()" exact-active-class="active-nav">
<img src="@/assets/icons/leaderboard.svg">Ledertavle
</router-link>
</li>
<li class="nav-item">
<router-link data-cy="news" class="nav-link text-white"
:to="toNews()" exact-active-class="active-nav">
<img src="@/assets/icons/newsletter.svg">Nyheter
</router-link>
</li>
<li class="nav-item" v-if="useUserInfoStore().isPremium">
<router-link data-cy="budget"
class="nav-link text-white"
:to="toBudget()"
exact-active-class="active-nav"
@click="toggleDropdown">
<img src="@/assets/icons/budget.svg">Budsjett
</router-link>
</li>
<li class="nav-item">
<router-link data-cy="store" class="nav-link text-white"
:to="toStore()" exact-active-class="active-nav">
<img src="@/assets/icons/storefront.svg">Butikk
</router-link>
</li>
<li class="nav-item dropdown d-flex flex-column">
<a data-mdb-dropdown-init class=" nav-link dropdown-toggle hidden-arrow notification" href="#" id="navbarDropdownMenuLink"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<img src="/src/assets/icons/bell-white.svg">
<span v-if="notificationListRef.length > 0" class="badge rounded-pill badge-notification bg-danger">{{ notificationListRef.length }}</span>
</a>
<ul v-if="notificationListRef.length > 0" class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li v-for="(item, index) in notificationListRef" :key="index" >
<router-link :to="notificationPathMapper[String(item.notificationType)]"
class="d-flex align-items-center"
@click="readNotification(item)">
<div class="flex-shrink-0">
<img :src="notificationImageMapper[String(item.notificationType)]" alt="Varslingsikon" class="notification-icon">
</div>
<div class="flex-grow-1 ms-3">
<div class="not-item dropdown-item" id="notificationText">{{item.message}}</div>
</div>
</router-link>
</li>
</ul>
<ul v-else-if="notificationListRef.length === 0" class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li>Ingen varslinger</li>
</ul>
</li>
<li v-if="userStore.isLoggedIn" class="nav-item dropdown d-flex flex-column">
<a
data-cy="user"
:class="['nav-link', 'dropdown-toggle', 'username-text', 'text-white', { 'underline-active': !isAnyActivePage() }]"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false">
<img src="@/assets/icons/person.svg">{{ useUserInfoStore().firstname }}
</a>
<ul class="dropdown-menu dropdown-username-content">
<li>
<router-link data-cy="profile"
class="dropdown-item dropdown-username-link"
:to="toUserProfile()"
exact-active-class="active-link"
@click="toggleDropdown">
<img src="@/assets/icons/black_person.svg">Min profil
</router-link>
</li>
<li>
<router-link data-cy="friends"
class="dropdown-item dropdown-username-link"
:to="toFriends()"
exact-active-class="active-link"
@click="toggleDropdown">
<img src="@/assets/icons/black_friends.svg">Venner
</router-link>
</li>
<li>
<router-link data-cy="settings"
class="dropdown-item dropdown-username-link"
:to="toSetting()"
exact-active-class="active-link"
@click="toggleDropdown">
<img src="@/assets/icons/settings.svg">Innstillinger
</router-link>
</li>
<li>
<router-link data-cy="feedback"
class="dropdown-item dropdown-username-link"
:to="toFeedback()"
exact-active-class="active-link"
@click="toggleDropdown">
<img src="@/assets/icons/feedback.svg">Tilbakemelding
</router-link>
</li>
<li v-if="useUserInfoStore().role === 'ADMIN'">
<router-link data-cy="admin"
class="dropdown-item dropdown-username-link"
:to="toAdmin()"
exact-active-class="active-link"
@click="toggleDropdown">
<img src="@/assets/icons/admin.svg">Admin
</router-link>
</li>
<li style="cursor: pointer">
<a data-testid="logout"
class="dropdown-item dropdown-username-link"
href="#"
@click="toLogout()">
<img src="@/assets/icons/logout.svg">Logg ut
</a>
</li>
</ul>
</li>
<li v-else class="nav-item">
<a class="nav-link" style="cursor: pointer;" href="#"
@click="toLogout">Logg inn
</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { useRouter, useRoute } from "vue-router";
import { useUserInfoStore } from '@/stores/UserStore';
import {onMounted, ref} from "vue";
import { BadgeService, type NotificationDTO, NotificationService } from '@/api'
import handleUnknownError from '@/components/Exceptions/unkownErrorHandler';
// Declaring router, route and userStore variables
const router = useRouter();
const route = useRoute();
const userStore: any = useUserInfoStore();
// Declaring profile image
let profileImage: any = ref('');
if (useUserInfoStore().profileImage !== 0) {
profileImage.value = 'http://localhost:80/api/images/' + useUserInfoStore().profileImage;
} else {
profileImage.value = 'src/assets/userprofile.png';
}
// Declaring reactive notification list for displaying notification
let notificationListRef = ref<NotificationDTO[]>([]);
/**
* Checks if the current route is any of the active pages.
*
* @returns {boolean} True if the current route is one of the active pages, otherwise false.
*/
function isAnyActivePage(): boolean {
const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/budget-overview', '/shop'];
return activeRoutes.includes(route.path);
}
/**
* Toggles the visibility of the dropdown menu based on the event target.
*
* @param {Event} event The event object.
*/
function toggleDropdown(event: any) {
const dropdownMenu = event.target.closest('.dropdown-menu');
if (dropdownMenu) {
dropdownMenu.classList.remove('show');
}
}
/**
* Maps notification types to their respective image paths.
*/
const notificationImageMapper: any = {
"FRIEND_REQUEST": "/src/assets/userprofile.png",
"BADGE": "/src/assets/icons/medal.png",
"COMPLETED_GOAL": "/src/assets/icons/piggybank.svg"
}
/**
* Maps notification types to their respective paths.
*/
const notificationPathMapper: any = {
"FRIEND_REQUEST": "/friends",
"BADGE": "/profile",
"COMPLETED_GOAL": "/roadmap"
}
/**
* Retrieves the list of notifications for the current user.
* This function updates the list of unread notifications by fetching them from the NotificationService.
* If successful, it updates the notificationListRef.value with the retrieved notifications.
* If an error occurs during the process, it catches the error, it sets the notificationListRef.value to an empty array.
*/
const getNotifications = async () => {
try {
await BadgeService.updateUnlockedBadges();
notificationListRef.value = await NotificationService.getUnreadNotificationByUser()
} catch (error) {
handleUnknownError(error);
notificationListRef.value = []
}
}
/**
* Marks a notification as read.
* This function updates the unread status of the provided notification to false,
* then sends a request to the NotificationService to update the notification in the database.
* If successful, it updates the notificationListRef.value with the updated list of unread notifications.
* If an error occurs during the process, it catches the error, it sets the notificationListRef.value to an empty array.
*
* @param {NotificationDTO} notification The notification to mark as read.
*/
const readNotification = async (notification: NotificationDTO) => {
try {
notification.unread = false;
await NotificationService.updateNotification({requestBody: notification});
notificationListRef.value = await NotificationService.getUnreadNotificationByUser()
} catch (error) {
handleUnknownError(error);
notificationListRef.value = [];
}
}
/**
* Redirects to the budget overview page.
*
* @returns {string} The URL for the budget overview page.
*/
function toBudget(): string {
return '/budget-overview';
}
/**
* Redirects to the saving goals page.
*
* @returns {string} The URL for the saving goals page.
*/
function toSavingGoals(): string {
return '/roadmap';
}
/**
* Redirects to the leaderboard page.
*
* @returns {string} The URL for the leaderboard page.
*/
function toLeaderboard(): string {
return '/leaderboard';
}
/**
* Redirects to the news page.
*
* @returns {string} The URL for the news page.
*/
function toNews(): string {
return '/news';
}
/**
* Redirects to the store page.
*
* @returns {string} The URL for the store page.
*/
function toStore(): string {
return '/shop';
}
/**
* Redirects to the user settings page.
*
* @returns {string} The URL for the user settings page.
*/
function toSetting(): string {
return '/settings/profile';
}
/**
* Redirects to the feedback page.
*
* @returns {string} The URL for the feedback page.
*/
function toFeedback(): string {
return '/feedback';
}
/**
* Redirects to the admin page.
*
* @returns {string} The URL for the admin page.
*/
function toAdmin(): string {
return '/admin';
}
/**
* Redirects to the friends page.
*
* @returns {string} The URL for the friends page.
*/
function toFriends(): string {
return '/friends';
}
/**
* Redirects to the user profile page.
*
* @returns {string} The URL for the user profile page.
*/
function toUserProfile(): string {
return '/profile';
}
/**
* Logs out the user by clearing user info and redirecting to the login page.
*/
function toLogout() {
userStore.clearUserInfo();
router.push('login');
}
/**
* Calls the getNotifications function when the component is mounted.
*/
onMounted(() => {
getNotifications()
})
</script>
<style scoped>
.navbar-brand {
display: flex;
align-items: center;
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba(255, 255, 255)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
}
.nav-item {
display: flex;
justify-content: center;
align-items: center;
padding: 0.1rem 0.3rem;
font-size: 1.7rem;
}
.active-nav {
border-radius: 0rem;
border-bottom: 4px solid #f3f3f3;
}
.active-link {
background-color: #f3f3f6;
border-bottom: 3px solid #01476b;
}
.underline-active {
border-bottom: 4px solid white;
}
.dropdown-item img {
height: 35px;
width: 35px;
margin-right: 5px;
}
.nav-item:hover {
background-color: #01476b;
border-radius: 1rem;
}
.not-item:hover {
background-color: #f3f3f3;
}
.nav-item .dropdown {
display: flex;
justify-content: center;
}
.nav-link {
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-item {
width: 100%;
display: flex;
justify-content: left;
}
.dropdown-item:hover {
width: 100%;
}
.dropdown-menu {
padding: 5px;
right: -0.5rem;
}
.dropdown-menu[data-bs-popper] {
left: auto;
}
.dropdown-username-link {
font-size: 1.7rem;
display: flex;
justify-self: center;
}
.dropdown-username-link:hover {
background-color: #f3f3f3;
}
.dropdown-item img {
height: 35px;
width: 35px;
margin-right: 5px;
}
#navBar {
background-color: #003A58;
}
.notification-icon {
height: 35px;
width: 35px;
}
.nav-item a {
font-size: 19px;
}
.navbar {
display: flex;
align-items: center;
}
.container-fluid {
font-size: 1.7rem;
margin: 0 140px;
}
@media (max-width: 768px) {
.container-fluid {
margin: 0 20px;
}
}
#logo {
font-size: 2.5rem;
height: 100%;
}
.nav-link img {
margin-right: 5px;
height: 35px;
width: 35px;
}
#logoImg {
margin-right: 0.3rem;
width: 75px;
height: auto;
aspect-ratio: 1.3/1;
}
.notification.hidden-arrow::after{
display: none;
}
#notificationText {
text-wrap: nowrap;
}
@media (max-width: 768px) {
#notificationText {
text-wrap: wrap;
}
}
</style>