diff --git a/cypress.config.ts b/cypress.config.ts index f80530a2a66e24534ac18223b7603fd3d0206f49..e7662e1eef97ffff84557d58d19242b20e07975b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,6 +3,8 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', - baseUrl: 'http://localhost:5173' + baseUrl: 'http://localhost:5173', + viewportHeight: 1080, + viewportWidth: 1920, } }) diff --git a/cypress/e2e/BasePage/BasePageView.cy.ts b/cypress/e2e/BasePage/BasePageView.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..223d228f2edf3c22bb88cdd7d22bbbc7fcc49b37 --- /dev/null +++ b/cypress/e2e/BasePage/BasePageView.cy.ts @@ -0,0 +1,67 @@ +describe('BasePage test', () => { + beforeEach(() => { + cy.visit('/login'); + cy.get('#emailInput input').type('user@example.com'); + cy.get('#passwordInput input').type('John1'); + cy.get('form').submit(); + cy.wait(1000); + cy.visit('/') + }); + + it('uses menu to visit leaderboard', () => { + cy.get('[data-cy="menu"]').get('[data-cy="leaderboard"]').click(); + cy.url().should('include', '/leaderboard'); + }); + + it('uses menu to visit news', () => { + cy.get('[data-cy="menu"]').get('[data-cy="news"]').click(); + cy.url().should('include', '/news'); + }); + + it('uses menu to visit store', () => { + cy.get('[data-cy="menu"]').get('[data-cy="store"]').click(); + cy.url().should('include', '/shop'); + }); + + it('uses menu to visit roadmap', () => { + cy.get('[data-cy="menu"]').get('[data-cy="savingGoals"]').click(); + cy.url().should('include', '/roadmap'); + }); + + it('uses menu to visit user profile', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-cy="profile"]').click(); + cy.url().should('include', '/profile'); + }); + + it('uses menu to visit budget', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-cy="budget"]').click(); + cy.url().should('include', '/budget'); + }); + + it('uses menu to visit friends', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-cy="friends"]').click(); + cy.url().should('include', '/friends'); + }); + + it('uses menu to visit settings', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-cy="settings"]').click(); + cy.url().should('include', '/settings'); + }); + + it('uses menu to visit feedback', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-cy="feedback"]').click(); + cy.url().should('include', '/feedback'); + }); + + it('uses menu to log out', () => { + cy.get('[data-cy="menu"]').get('[data-cy="user"]').click(); + cy.get('[data-testid="logout"]').click(); + cy.wait(1000); + cy.url().should('include', '/login'); + }); +}) \ No newline at end of file diff --git a/src/assets/icons/bell-white.svg b/src/assets/icons/bell-white.svg new file mode 100644 index 0000000000000000000000000000000000000000..e6a45e0c8bd5f0c5eff7d618ec56bed074f2cae1 --- /dev/null +++ b/src/assets/icons/bell-white.svg @@ -0,0 +1,7 @@ +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools --> +<svg fill="white" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 459.334 459.334" xml:space="preserve" stroke="white"> + <g id="SVGRepo_bgCarrier" stroke-width="0"/> + <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> + <g id="SVGRepo_iconCarrier"> <g> <g> <g> <path d="M175.216,404.514c-0.001,0.12-0.009,0.239-0.009,0.359c0,30.078,24.383,54.461,54.461,54.461 s54.461-24.383,54.461-54.461c0-0.12-0.008-0.239-0.009-0.359H175.216z"/> <path d="M403.549,336.438l-49.015-72.002c0-22.041,0-75.898,0-89.83c0-60.581-43.144-111.079-100.381-122.459V24.485 C254.152,10.963,243.19,0,229.667,0s-24.485,10.963-24.485,24.485v27.663c-57.237,11.381-100.381,61.879-100.381,122.459 c0,23.716,0,76.084,0,89.83l-49.015,72.002c-5.163,7.584-5.709,17.401-1.419,25.511c4.29,8.11,12.712,13.182,21.887,13.182 H383.08c9.175,0,17.597-5.073,21.887-13.182C409.258,353.839,408.711,344.022,403.549,336.438z"/> </g> </g> </g> </g> + </svg> \ No newline at end of file diff --git a/src/assets/icons/bell.svg b/src/assets/icons/bell.svg new file mode 100644 index 0000000000000000000000000000000000000000..93d55580e45e4dc3ef635603b0b62d3654953644 --- /dev/null +++ b/src/assets/icons/bell.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 459.334 459.334" xml:space="preserve"> +<g> + <g> + <g> + <path d="M175.216,404.514c-0.001,0.12-0.009,0.239-0.009,0.359c0,30.078,24.383,54.461,54.461,54.461 + s54.461-24.383,54.461-54.461c0-0.12-0.008-0.239-0.009-0.359H175.216z"/> + <path d="M403.549,336.438l-49.015-72.002c0-22.041,0-75.898,0-89.83c0-60.581-43.144-111.079-100.381-122.459V24.485 + C254.152,10.963,243.19,0,229.667,0s-24.485,10.963-24.485,24.485v27.663c-57.237,11.381-100.381,61.879-100.381,122.459 + c0,23.716,0,76.084,0,89.83l-49.015,72.002c-5.163,7.584-5.709,17.401-1.419,25.511c4.29,8.11,12.712,13.182,21.887,13.182 + H383.08c9.175,0,17.597-5.073,21.887-13.182C409.258,353.839,408.711,344.022,403.549,336.438z"/> + </g> + </g> +</g> +</svg> \ No newline at end of file diff --git a/src/assets/icons/medal.png b/src/assets/icons/medal.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2bc72357ee90b4db973bfb5c86560dd084df89 Binary files /dev/null and b/src/assets/icons/medal.png differ diff --git a/src/components/BaseComponents/Menu.vue b/src/components/BaseComponents/Menu.vue index 9b013b672565bdc395e7721b664c83ecc929a8e3..74ed16328a0cbc6d181fa70bb96ebce1a809b624 100644 --- a/src/components/BaseComponents/Menu.vue +++ b/src/components/BaseComponents/Menu.vue @@ -1,7 +1,7 @@ <template> <nav id="navBar" class="navbar navbar-expand-xl"> <div class="container-fluid"> - <router-link class="navbar-brand" id="home" :to="toSavingGoals()"> + <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> @@ -13,45 +13,79 @@ <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 class="nav-link text-white" :to="toSavingGoals()"><img + <router-link data-cy="savingGoals" class="nav-link text-white" + :to="toSavingGoals()"><img src="@/assets/icons/saving.svg">Saving goals</router-link> </li> <li class="nav-item"> - <router-link class="nav-link text-white" :to="toLeaderboard()"><img + <router-link data-cy="leaderboard" class="nav-link text-white" + :to="toLeaderboard()"><img src="@/assets/icons/leaderboard.svg">Leaderboard</router-link> </li> <li class="nav-item"> - <router-link class="nav-link text-white" :to="toNews()"><img - src="@/assets/icons/newsletter.svg">News</router-link> + <router-link data-cy="news" class="nav-link text-white" :to="toNews()"><img + src="@/assets/icons/newsletter.svg">News</router-link> </li> <li class="nav-item"> - <router-link class="nav-link text-white" :to="toStore()"><img - src="@/assets/icons/storefront.svg">Store</router-link> + <router-link data-cy="store" class="nav-link text-white" :to="toStore()"><img + src="@/assets/icons/storefront.svg">Store</router-link> + </li> + <li class="nav-item dropdown"> + <a data-mdb-dropdown-init class=" nav-link me-3 dropdown-toggle hidden-arrow notification" href="#" id="navbarDropdownMenuLink" + role="button" data-bs-toggle="dropdown" aria-expanded="false"> + <i class="fas fa-bell text-white"></i> + <span class="badge rounded-pill badge-notification bg-danger">{{counter}}</span> + </a> + <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> + <li v-for="(array,key) in notifMap" :key="key" > + <div class="d-flex align-items-center"> + <div v-if="array[1][0] === '1'" class="flex-shrink-0"> + <img src="/src/assets/icons/medal.png" alt="Notification Icon" class="notification-icon" style="height: 20px; width: 20px"> + </div> + <div v-if="array[1][0] === '2'" class="flex-shrink-0"> + <img src="/src/assets/userprofile.png" alt="Notification Icon" class="notification-icon" style="height: 20px; width: 20px"> + </div> + <div v-if="array[1][0] === '3'" class="flex-shrink-0"> + <img src="/src/assets/icons/piggybank.svg" alt="Notification Icon" class="notification-icon" style="height: 20px; width: 20px"> + </div> + <div class="flex-grow-1 ms-3"> + <router-link class="not-item dropdown-item text-white" :to="getPath(array[1][0])">{{array[1][1]}}</router-link> + </div> + </div> + + + </li> + </ul> </li> <li v-if="userStore.isLoggedIn" class="nav-item dropdown"> - <a class="nav-link dropdown-toggle username-text text-white " href="#" role="button" + <a data-cy="user" + class="nav-link dropdown-toggle username-text text-white " + href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <img :src="useUserInfoStore().profileImage ? 'http://localhost:8080/api/images/' + useUserInfoStore().profileImage : 'src/assets/userprofile.png'" style="width: 50px; border: 2px solid black; border-radius: 50%">{{ useUserInfoStore().firstname }} </a> <ul class="dropdown-menu dropdown-username-content"> - <li><router-link class="dropdown-item text-white dropdown-username-link" - :to="toUserProfile()"><img src="@/assets/icons/person.svg">User - Profile</router-link></li> - <li v-if="useUserInfoStore().isPremium"><router-link class="dropdown-item text-white dropdown-username-link" - :to="toBudget()"><img>Budget</router-link></li> - <li><router-link class="dropdown-item text-white dropdown-username-link" - :to="toFriends()"><img src="@/assets/icons/friends.svg">Friends</router-link></li> - <li><router-link class="dropdown-item text-white dropdown-username-link" - :to="toSetting()"><img src="@/assets/icons/settings.svg">Settings</router-link></li> - <li><router-link class="dropdown-item text-white dropdown-username-link" - :to="toFeedback()"><img src="@/assets/icons/feedback.svg">Feedback</router-link> - </li> - <li><router-link class="dropdown-item text-white dropdown-username-link" - :to="toSetting()"><img src="@/assets/icons/admin.svg">Admin</router-link></li> - <li><a data-testid="logout" class="dropdown-item text-white dropdown-username-link" ref="#" - @click="toLogout()"><img src="@/assets/icons/logout.svg">Log out</a></li> + <li><router-link data-cy="profile" + class="dropdown-item text-white dropdown-username-link" :to="toUserProfile()"><img + src="@/assets/icons/person.svg">User Profile</router-link></li> + <li v-if="useUserInfoStore().isPremium"><router-link data-cy="budget" + class="dropdown-item text-white dropdown-username-link" :to="toBudget()"><img>Budget</router-link></li> + <li><router-link data-cy="friends" + class="dropdown-item text-white dropdown-username-link" :to="toFriends()"><img + src="@/assets/icons/friends.svg">Friends</router-link></li> + <li><router-link data-cy="settings" + class="dropdown-item text-white dropdown-username-link" :to="toSetting()"><img + src="@/assets/icons/settings.svg">Settings</router-link></li> + <li><router-link data-cy="feedback" + class="dropdown-item text-white dropdown-username-link" :to="toFeedback()"><img + src="@/assets/icons/feedback.svg">Feedback</router-link></li> + <li><router-link data-cy="admin" + class="dropdown-item text-white dropdown-username-link" :to="toSetting()"><img + src="@/assets/icons/admin.svg">Admin</router-link></li> + <li><a data-testid="logout" class="dropdown-item text-white dropdown-username-link" ref="#" @click="toLogout()"><img + src="@/assets/icons/logout.svg">Log out</a></li> </ul> </li> <li v-else class="nav-item"> @@ -63,9 +97,11 @@ </nav> </template> <script setup lang="ts"> -import { ref } from 'vue'; import { useRouter } from "vue-router"; import { useUserInfoStore } from '@/stores/UserStore'; +import {onMounted, ref} from "vue"; + + const router = useRouter(); @@ -79,6 +115,65 @@ if (useUserInfoStore().profileImage !== 0) { profileImage = 'src/assets/userprofile.png'; } +//Hashmap that contains the path to the Badges, The Friend, The dashboard etc. +//The key value pair is the message of the notification and the path of the route +let notifMap = ref (new Map<number, any[]>); + +let notifId = ref(0); + +let path = ref('#'); + +let counter = ref(0) + + +/* id: 0 -> /roadmap + id: 1 -> /profile + id: 2 -> /friend + */ + + + +function getNotification(){ + //axios call + let response: any = ref( ['1', 'You have recived a award for getting 200 points']) + let response2: any = ref( ['2', 'You have recived a friend request from Jens Aanestad']) + let response3: any = ref( ['3', 'You have lost your streak. Come back to try again']) + notifMap.value.set(notifId.value,response.value) + notifId.value++ + notifMap.value.set(notifId.value,response2.value) + notifId.value++ + notifMap.value.set(notifId.value,response3.value) + notifId.value++ + + counter.value = notifMap.value.size +} +function toBadges(){ + +} + +function getPath(id : string){ + if(id === '1'){ + return path.value = '/profile' + } + if(id === '2'){ + return path.value = '/friends' + } + if(id === '3'){ + return path.value = '/roadmap' + } + + return '#'; +} + +function updateNotification(){ + //Axios get request to the getFunction +} + +function removeNotification() { + +} + + function toHome() { return '/' } @@ -123,7 +218,9 @@ function toLogout() { userStore.clearUserInfo(); router.push('login') } - +onMounted(() => { + getNotification() +}) </script> <style scoped> @@ -144,6 +241,9 @@ function toLogout() { .nav-item:hover { background-color: #2b6ac7; } +.not-item:hover { + background-color: #2b6ac7; +} .nav-item .dropdown { display: flex; @@ -167,6 +267,16 @@ function toLogout() { right: -0.5rem; } +#notifyBtn { + background-color: #0A58CA; + border: #0A58CA; +} + +#notifyBtn:hover { + background-color: #2b6ac7; + border: #2b6ac7; +} + .dropdown-menu[data-bs-popper] { left: auto; } @@ -209,4 +319,9 @@ function toLogout() { height: auto; aspect-ratio: 1.3/1; } +.notification.hidden-arrow::after{ + display: none; +} + + </style> \ No newline at end of file diff --git a/src/views/BasePageView.vue b/src/views/BasePageView.vue index 83a5382bc788f5584b3e272e89a579975f119820..312242fa196f91ac4a2def0a9c793f2b82b65c2c 100644 --- a/src/views/BasePageView.vue +++ b/src/views/BasePageView.vue @@ -7,8 +7,7 @@ import { useUserInfoStore } from '@/stores/UserStore'; </script> <template> - - <Menu></Menu> + <Menu data-cy="menu"></Menu> <div v-if="!useUserInfoStore().isPremium && !useUserInfoStore().isNoAds" style="display: flex; flex-direction: row;"> <img v-for="item in 7" src="@/assets/coca.webp" style="width: 100%; height: 100px; margin: 5px; border-radius: 1rem;" alt="picture"> </div>