From 0b3e9b26ea2911a769a43e2e754629397251bb6a Mon Sep 17 00:00:00 2001 From: VIktorGrev <viktog2210@gmail.com> Date: Thu, 2 May 2024 15:22:48 +0200 Subject: [PATCH] feat: Adding badges and menu selected visual --- spec.json | 465 +++++++++++++++++- src/components/BaseComponents/NavBar.vue | 256 ++++++---- src/components/Friends/UserFriends.vue | 2 +- .../Leaderboard/LeaderboardRank.vue | 7 +- src/components/Login/LoginForm.vue | 2 +- src/components/Settings/SettingsProfile.vue | 40 +- src/components/UserProfile/MyProfile.vue | 73 ++- src/views/User/UserSettingsView.vue | 13 +- 8 files changed, 690 insertions(+), 168 deletions(-) diff --git a/spec.json b/spec.json index cf41850..0aab7ca 100644 --- a/spec.json +++ b/spec.json @@ -1,8 +1,8 @@ { "openapi": "3.0.1", "info": { - "title": "SpareSti API", - "description": "The SpareSti API", + "title": "Sparesti API", + "description": "The Sparesti API", "version": "3.0" }, "servers": [ @@ -349,6 +349,48 @@ "security": [] } }, + "/api/notification/update": { + "post": { + "tags": [ + "Notification" + ], + "summary": "Updates a notification", + "description": "Updates a notification based on the request", + "operationId": "updateNotification", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationDTO" + } + } + }, + "required": true + }, + "responses": { + "500": { + "description": "User is not found", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + }, + "200": { + "description": "Successfully updated notification", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, "/api/item/{itemId}": { "post": { "tags": [ @@ -575,7 +617,7 @@ "/api/budget/update/{budgetId}": { "post": { "tags": [ - "User" + "Budget" ], "summary": "Updates a budget", "description": "Updates a budget based on the budget request", @@ -628,7 +670,7 @@ "/api/budget/update/expense/{budgetId}": { "post": { "tags": [ - "User" + "Budget" ], "summary": "Created/Updates an expense", "description": "Creates/Updates a budget based on the budget request", @@ -681,7 +723,7 @@ "/api/budget/create": { "post": { "tags": [ - "User" + "Budget" ], "summary": "Create a new budget", "description": "Create a new budget with based on the budget request", @@ -849,6 +891,39 @@ "security": [] } }, + "/api/auth/bank-id": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Authenticate a BankID request", + "description": "Authenticate a BankID request", + "operationId": "bankIdAuthentication", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankIDRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "If the authentication is successful", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResponse" + } + } + } + } + }, + "security": [] + } + }, "/api/users": { "patch": { "tags": [ @@ -945,6 +1020,29 @@ } } }, + "/redirect": { + "get": { + "tags": [ + "Redirect" + ], + "operationId": "consumeCallback", + "parameters": [ + { + "name": "state", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/bank/v1/account/accounts/ssn/{ssn}": { "get": { "tags": [ @@ -1210,6 +1308,99 @@ } } }, + "/api/notification": { + "get": { + "tags": [ + "Notification" + ], + "summary": "Get the list of notifications", + "description": "Get all notifications to a user", + "operationId": "getNotificationByUser", + "responses": { + "200": { + "description": "Successfully got notifications", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationDTO" + } + } + } + } + } + } + } + }, + "/api/notification/{notificationId}": { + "get": { + "tags": [ + "Notification" + ], + "summary": "Get the notification", + "description": "Get notification by its id ", + "operationId": "getNotification", + "parameters": [ + { + "name": "notificationId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Successfully got notification", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationDTO" + } + } + } + }, + "500": { + "description": "Notification is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationDTO" + } + } + } + } + } + } + }, + "/api/notification/unread": { + "get": { + "tags": [ + "Notification" + ], + "summary": "Get the list of unread notifications", + "description": "Get all unread notifications to a user", + "operationId": "getUnreadNotificationByUser", + "responses": { + "200": { + "description": "Successfully got notifications", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationDTO" + } + } + } + } + } + } + } + }, "/api/leaderboard": { "get": { "tags": [ @@ -1258,6 +1449,29 @@ } } }, + "/api/leaderboard/total-points": { + "get": { + "tags": [ + "Leaderboard" + ], + "summary": "Get sum of total points globally", + "description": "Get the sum of the total points of all users globally", + "operationId": "getTotalPoints", + "responses": { + "200": { + "description": "Successfully retrieved total points", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, "/api/leaderboard/surrounding": { "get": { "tags": [ @@ -1401,6 +1615,37 @@ "security": [] } }, + "/api/goals/{id}": { + "get": { + "tags": [ + "Goal" + ], + "operationId": "getGoal", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GoalDTO" + } + } + } + } + } + } + }, "/api/friends": { "get": { "tags": [ @@ -1454,7 +1699,7 @@ "/api/budget": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Get the list of budgets", "description": "Get all budgets related to the authenticated user", @@ -1479,7 +1724,7 @@ "/api/budget/{budgetId}": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Get the budget", "description": "Get budget by its id ", @@ -1522,7 +1767,7 @@ "/api/budget/expenses/{budgetId}": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Get the list of budgets", "description": "Get all budgets related to the authenticated user", @@ -1558,7 +1803,7 @@ "/api/budget/expense/{expenseId}": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Get the expense", "description": "Get expense by its id ", @@ -1601,7 +1846,7 @@ "/api/budget/delete/{budgetId}": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Deletes a budget", "description": "Deletes a budget based on provided budget id", @@ -1644,7 +1889,7 @@ "/api/budget/delete/expense/{expenseId}": { "get": { "tags": [ - "User" + "Budget" ], "summary": "Deletes an expense", "description": "Deletes an expense based on provided expense id", @@ -1683,6 +1928,146 @@ } } } + }, + "/api/badge": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Get the list of badges", + "description": "Get all badges stored in the database", + "operationId": "getAllBadges", + "responses": { + "200": { + "description": "Successfully got badges", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + } + } + } + } + }, + "/api/badge/{badgeId}": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Get the budget", + "description": "Get budget by its id ", + "operationId": "getBadge", + "parameters": [ + { + "name": "badgeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "500": { + "description": "Badge is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + }, + "200": { + "description": "Successfully got budget", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + } + } + } + }, + "/api/badge/update": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Updates unlocked badges", + "description": "Checks if a user has met the criteria for unlocking badges", + "operationId": "updateUnlockedBadges", + "responses": { + "200": { + "description": "Successfully updated badges", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/badge/unlocked": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Get the list of badges", + "description": "Get all badges unlocked by the user", + "operationId": "getBadgesUnlockedByUser", + "responses": { + "200": { + "description": "Successfully got badges", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + } + } + } + } + }, + "/api/badge/locked": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Get the list of badges", + "description": "Get all badges not unlocked by the user", + "operationId": "getBadgesNotUnlockedByUser", + "responses": { + "200": { + "description": "Successfully got badges", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + } + } + } + } } }, "components": { @@ -1807,6 +2192,33 @@ } } }, + "NotificationDTO": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "unread": { + "type": "boolean" + }, + "notificationType": { + "type": "string", + "enum": [ + "BADGE", + "FRIEND_REQUEST", + "COMPLETED_GOAL" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + }, "CreateGoalDTO": { "type": "object", "properties": { @@ -2124,6 +2536,17 @@ } } }, + "BankIDRequest": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "state": { + "type": "string" + } + } + }, "UserUpdateDTO": { "type": "object", "properties": { @@ -2325,6 +2748,26 @@ "type": "string" } } + }, + "BadgeDTO": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "badgeName": { + "type": "string" + }, + "criteria": { + "type": "integer", + "format": "int32" + }, + "imageId": { + "type": "integer", + "format": "int64" + } + } } }, "securitySchemes": { diff --git a/src/components/BaseComponents/NavBar.vue b/src/components/BaseComponents/NavBar.vue index a97684e..11a11f2 100644 --- a/src/components/BaseComponents/NavBar.vue +++ b/src/components/BaseComponents/NavBar.vue @@ -1,110 +1,144 @@ <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> + <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> - <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()"><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()"><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()"><img - src="@/assets/icons/newsletter.svg">Nyheter</router-link> - </li> - <li class="nav-item"> - <router-link data-cy="store" class="nav-link text-white" :to="toStore()"><img - src="@/assets/icons/storefront.svg">Butikk</router-link> - </li> - <li class="nav-item dropdown"> - <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="counter > 0" class="badge rounded-pill badge-notification bg-danger">{{counter}}</span> - </a> - <ul v-if="counter > 0" 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="Varslingsikon" class="notification-icon"> - </div> - <div v-if="array[1][0] === '2'" class="flex-shrink-0"> - <img src="/src/assets/userprofile.png" alt="Varslingsikon" class="notification-icon"> - </div> - <div v-if="array[1][0] === '3'" class="flex-shrink-0"> - <img src="/src/assets/icons/piggybank.svg" alt="Varslingsikon" class="notification-icon"> - </div> - <div class="flex-grow-1 ms-3"> - <router-link class="not-item dropdown-item" :to="getPath(array[1][0])">{{array[1][1]}}</router-link> - </div> - </div> - </li> - </ul> - <ul v-else class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> - <li>Ingen varslinger</li> - </ul> - </li> - <li v-if="userStore.isLoggedIn" class="nav-item dropdown"> - <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="@/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()"><img - src="@/assets/icons/black_person.svg">Min profil</router-link></li> - <li v-if="useUserInfoStore().isPremium"><router-link data-cy="budget" - class="dropdown-item dropdown-username-link" :to="toBudget()"><img - src="@/assets/icons/budget.svg">Budjsett</router-link></li> - <li><router-link data-cy="friends" - class="dropdown-item dropdown-username-link" :to="toFriends()"><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()"><img - src="@/assets/icons/settings.svg">Innstillinger</router-link></li> - <li><router-link data-cy="feedback" - class="dropdown-item dropdown-username-link" :to="toFeedback()"><img - src="@/assets/icons/feedback.svg">Tilbakemelding</router-link></li> - <li><router-link data-cy="admin" - class="dropdown-item dropdown-username-link" :to="toSetting()"><img - src="@/assets/icons/admin.svg">Admin</router-link></li> - <li style="cursor: pointer"><a data-testid="logout" class="dropdown-item dropdown-username-link" ref="#" @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> + </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"> + <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 v-if="userStore.isLoggedIn" class="nav-item dropdown"> + + + <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 v-if="useUserInfoStore().isPremium"> + <router-link data-cy="budget" + class="dropdown-item dropdown-username-link" + :to="toBudget()" + exact-active-class="active-link" + @click="toggleDropdown"> + <img src="@/assets/icons/budget.svg">Budjsett + </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> + <router-link data-cy="admin" + class="dropdown-item dropdown-username-link" + :to="toSetting()" + 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="() => { toggleDropdown(); 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 } from "vue-router"; +import { useRouter, useRoute } from "vue-router"; import { useUserInfoStore } from '@/stores/UserStore'; import {onMounted, ref} from "vue"; const router = useRouter(); +const route = useRoute(); const userStore: any = useUserInfoStore(); @@ -132,7 +166,17 @@ let counter = ref(0) id: 2 -> /friend */ + function isAnyActivePage() { + const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/shop']; // Add other pages here + return activeRoutes.includes(route.path); +} +function toggleDropdown(event) { + const dropdownMenu = event.target.closest('.dropdown-menu'); + if (dropdownMenu) { + dropdownMenu.classList.remove('show'); + } +} function getNotification(){ //axios call @@ -242,6 +286,26 @@ onMounted(() => { 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; diff --git a/src/components/Friends/UserFriends.vue b/src/components/Friends/UserFriends.vue index 1858195..d97189b 100644 --- a/src/components/Friends/UserFriends.vue +++ b/src/components/Friends/UserFriends.vue @@ -95,7 +95,7 @@ </div> <div class="col-md-7 col-sm-7"> <h5><a href="#" class="profile-link" @click="toUserProfile(user.id)">{{ - user.firstName }}</a> + user.firstName }} {{ user.lastName }}</a> </h5> </div> <div class="col-md-3 col-sm-3"> diff --git a/src/components/Leaderboard/LeaderboardRank.vue b/src/components/Leaderboard/LeaderboardRank.vue index 07f88f3..b3432f4 100644 --- a/src/components/Leaderboard/LeaderboardRank.vue +++ b/src/components/Leaderboard/LeaderboardRank.vue @@ -37,7 +37,7 @@ </div> <div id="communityContainer"> <h1>Totale poeng opptjent som et fellesskap</h1> - <h2>1000000 <img src="../../assets/items/v-buck.png" style="width: 2rem" alt="alt"></h2> + <h2>{{communityPoints}} <img src="../../assets/items/pigcoin.png" style="width: 2rem" alt="alt"></h2> </div> </template> @@ -56,10 +56,15 @@ let streakLeaderboardDataExtra = ref([] as any); let currentLeaderboardDataExtra = ref([] as any); let pointsLeaderboardDataExtra = ref([] as any); +let communityPoints = ref(0); + const router = useRouter(); async function fetchQuizData() { await global(); + + const response = await LeaderboardService.getTotalPoints(); + communityPoints.value = response; } onMounted(() => { diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index 3b0518e..f0ca531 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -69,7 +69,7 @@ const handleSubmit = async () => { console.log(response.token) - await router.push({ name: 'home' }); + await router.push({ name: 'roadmap' }); } catch (error: any) { errorMsg.value = handleUnknownError(error); isSubmitting.value = false; diff --git a/src/components/Settings/SettingsProfile.vue b/src/components/Settings/SettingsProfile.vue index c45371b..6d083ff 100644 --- a/src/components/Settings/SettingsProfile.vue +++ b/src/components/Settings/SettingsProfile.vue @@ -12,6 +12,8 @@ const passwordRef = ref('') const formRef = ref() let samePasswords = ref(true) +const imageRange = ref([10, 11, 12, 13, 14, 15]); + const iconSrc = ref('../src/assets/userprofile.png'); const fileInputRef = ref(); @@ -58,13 +60,11 @@ async function setupForm() { try { const response = await UserService.getUser(); console.log(response.firstName) - firstNameRef.value = response.firstName; if (response.lastName != null) { surnameRef.value = response.lastName; } - console.log(response.profileImage) - if(response.profileImage != null){ + if (response.profileImage != null) { iconSrc.value = "http://localhost:8080/api/images/" + response.profileImage; } else { iconSrc.value = "../src/assets/userprofile.png"; @@ -107,31 +107,36 @@ onMounted(() => { <div class="user-avatar"> <input type="file" ref="fileInputRef" @change="handleFileChange" accept=".jpg, .jpeg, .png" style="display: none;" /> - <img :src="iconSrc" alt="Brukeravatar" style="width: 300px"> + <img :src="iconSrc" alt="Brukeravatar" style="width: 200px; height: 200px;"> <div class="mt-2"> <button type="button" class="btn btn-primary" @click="triggerFileUpload"><img src="../../assets/icons/download.svg"> Last opp bilde</button> </div> </div> <div class="form-group"> - <BaseInput data-cy="first-name" :model-value="firstNameRef" - @input-change-event="handleFirstNameInputEvent" id="firstNameInputChange" - input-id="first-name-new" type="text" label="Fornavn" placeholder="Skriv inn ditt fornavn" - invalid-message="Vennligst skriv inn ditt fornavn" /> + <BaseInput data-cy="first-name" :model-value="firstNameRef" @input-change-event="handleFirstNameInputEvent" + id="firstNameInputChange" input-id="first-name-new" type="text" label="Fornavn" + placeholder="Skriv inn ditt fornavn" invalid-message="Vennligst skriv inn ditt fornavn" /> </div> <br> <div class="form-group"> - <BaseInput data-cy="last-name" :model-value="surnameRef" - @input-change-event="handleSurnameInputEvent" - id="surnameInput-change" - input-id="surname-new" type="text" label="Etternavn" - placeholder="Skriv inn ditt etternavn" - invalid-message="Vennligst skriv inn ditt etternavn" /> + <BaseInput data-cy="last-name" :model-value="surnameRef" @input-change-event="handleSurnameInputEvent" + id="surnameInput-change" input-id="surname-new" type="text" label="Etternavn" + placeholder="Skriv inn ditt etternavn" invalid-message="Vennligst skriv inn ditt etternavn" /> </div> <br> <button data-cy="profile-submit-btn" type="submit" class="btn btn-primary">Oppdater profil</button> </form> + <hr> + <div> + <h6>Stilsett din profil banner</h6> + <div class="bannerHolder"> + <div v-for="x in imageRange" :key="x"> + <img :src="'http://localhost:8080/api/images/' + x" style="width: 400px; height: 40px; margin: 10px"> + </div> + </div> + </div> </div> </template> @@ -144,4 +149,11 @@ onMounted(() => { -moz-border-radius: 100px; border-radius: 100px; } + +.bannerHolder { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-top: 20px; +} </style> \ No newline at end of file diff --git a/src/components/UserProfile/MyProfile.vue b/src/components/UserProfile/MyProfile.vue index a1d386d..7a10b3e 100644 --- a/src/components/UserProfile/MyProfile.vue +++ b/src/components/UserProfile/MyProfile.vue @@ -2,7 +2,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { useUserInfoStore } from "../../stores/UserStore"; -import { UserService } from "@/api"; +import { UserService, BadgeService } from "@/api"; import { ItemService } from "@/api"; let numberOfHistory = 6; @@ -13,6 +13,7 @@ const imageUrl = ref(`../src/assets/userprofile.png`); const router = useRouter(); const inventory = ref([] as any); +const badges = ref([] as any); const backgroundName = ref(""); async function setupForm() { @@ -26,6 +27,7 @@ async function setupForm() { imageUrl.value = "http://localhost:8080/api/images/" + response.profileImage; } getInventory(); + getBadges(); } catch (err) { console.error(err) } @@ -40,6 +42,15 @@ const getInventory = async () => { } } +const getBadges = async () => { + try { + const responseBadge = await BadgeService.getBadgesUnlockedByUser(); + badges.value = responseBadge; + } catch (error) { + console.log(error); + } +} + const selectItem = (item: any) => { backgroundName.value = item.itemName; useUserInfoStore().setUserInfo({ @@ -55,6 +66,8 @@ const toRoadmap = () => { router.push('/'); }; + + // Function to navigate to update user settings const toUpdateUserSettings = () => { router.push('/settings/profile'); @@ -80,7 +93,7 @@ const toUpdateUserSettings = () => { data-mdb-ripple-color="dark" style="z-index: 1; height: 40px; margin-left: 17px" id="toUpdate" @click="toUpdateUserSettings"> Rediger profil </button> - + </div> <div> <p class="mb-1 h2" data-cy="points">253 <img src="@/assets/items/pigcoin.png" style="width: 4rem"></p> @@ -92,12 +105,13 @@ const toUpdateUserSettings = () => { </div> </div> </div> + <hr> <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> <div class="container-fluid"> - <h1 class="mt-5 text-start badges-text">Lageret ditt</h1> - <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-4 pb-4 pt-2"> + <h1 class="mt-1 text-start badges-text">Lageret ditt</h1> + <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2"> <div v-for="product in inventory" :key="product.id" class="card text-center" style="width: 12rem; border: none; cursor: pointer; margin: 1rem; border: 2px solid black" @click="selectItem(product)"> <img :src="`http://localhost:8080/api/images/${product.imageId}`" class="card-img-top" @@ -112,52 +126,35 @@ const toUpdateUserSettings = () => { </div> </div> </div> + <hr> <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> <div class="container-fluid"> - <h1 class="mt-5 text-start badges-text">Merker</h1> - <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-4 pb-4 pt-2"> - - <div class="col-5"> - <div class="card badges-block card-1"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-2"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-3"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-4"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-5"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-6"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-7"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-8"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-9"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-10"></div> + <h1 class="mt-1 text-start badges-text">Merker</h1> + <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2"> + + <div v-for="badge in badges" :key="badge.id" class="card text-center" + style="width: 12rem; border: none; cursor: pointer; margin: 1rem; + border: 2px solid black" data-bs-toggle="tooltip" data-bs-placement="top" + data-bs-custom-class="custom-tooltip" :data-bs-title="badge.criteria"> + <img :src="`http://localhost:8080/api/images/${badge.imageId}`" class="card-img-top" + alt="..." /> + <div class="card-body"> + <h5 class="card-title">{{ badge.badgeName }}</h5> + </div> </div> + </div> </div> </div> </div> + <hr> <div class="row"> <div class="col"> <!-- Her er historikken over lagrede mål --> <div class="container-fluid mb-5"> - <h1 class="mt-5 text-start history-text">Historie</h1> + <h1 class="mt-1 text-start history-text">Historie</h1> <div class="row scrolling-wrapper-history"> <div v-for="index in numberOfHistory" :key="index" class="col-md-4 col-sm-4 col-lg-4 col-xs-4 col-xl-4 control-label"> @@ -204,8 +201,6 @@ const toUpdateUserSettings = () => { overflow: auto; } - - .badges-text { font-weight: 500; font-size: 2.0em; diff --git a/src/views/User/UserSettingsView.vue b/src/views/User/UserSettingsView.vue index a190d10..485d8dd 100644 --- a/src/views/User/UserSettingsView.vue +++ b/src/views/User/UserSettingsView.vue @@ -1,9 +1,12 @@ <script setup lang="ts"> import { ref } from 'vue' import { useRouter } from 'vue-router' +import {useRoute} from 'vue-router' const router = useRouter(); +const url = useRoute().path; + const activeLink = ref('/settings/profile'); // Default active link function setActive(link: string) { @@ -42,7 +45,7 @@ function toBilling() { <nav class="nav flex-column nav-pills nav-gap-y-1"> <a @click.prevent="setActive('/settings/profile')" @click="toProfile" - :class="['nav-item nav-link has-icon', { 'nav-link-faded': activeLink !== '/settings/profile', 'active': activeLink === '/settings/profile' }]"> + :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/profile', 'active': useRoute().path === '/settings/profile' }]"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user mr-2"> @@ -54,7 +57,7 @@ function toBilling() { <a @click.prevent="setActive('/settings/account')" @click="toAccount" - :class="['nav-item nav-link has-icon', { 'nav-link-faded': activeLink !== '/settings/account', 'active': activeLink === '/settings/account' }]"> + :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/account', 'active': useRoute().path === '/settings/account' }]"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings mr-2"> @@ -68,7 +71,7 @@ function toBilling() { <a @click.prevent="setActive('/settings/security')" @click="toSecurity" - :class="['nav-item nav-link has-icon', { 'nav-link-faded': activeLink !== '/settings/security', 'active': activeLink === '/settings/security' }]"> + :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/security', 'active': useRoute().path === '/settings/security' }]"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shield mr-2"> @@ -79,7 +82,7 @@ function toBilling() { <a @click.prevent="setActive('/settings/notification')" @click="toNotification" - :class="['nav-item nav-link has-icon', { 'nav-link-faded': activeLink !== '/settings/notification', 'active': activeLink === '/settings/notification' }]"> + :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/notification', 'active': useRoute().path === '/settings/notification' }]"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell mr-2"> @@ -89,7 +92,7 @@ function toBilling() { </a> <a> <a @click.prevent="setActive('/settings/bank')" @click="toBilling" - :class="['nav-item nav-link has-icon', { 'nav-link-faded': activeLink !== '/settings/bank', 'active': activeLink === '/settings/bank' }]"> + :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/bank', 'active': useRoute().path === '/settings/bank' }]"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-credit-card mr-2"> -- GitLab