diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9545d594d76df2d8590c4404490263348b0993fd..15f8cb88f85dce50bd57b63701defaf3edcbc4d6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,44 +24,47 @@ install_dependencies: build_project: stage: build - script: - - npm ci - - npm run build - dependencies: - - install_dependencies cache: key: "${CI_COMMIT_REF_SLUG}" paths: - node_modules/ policy: pull - -vitest_unit-tests: - stage: test script: - npm ci - - npm run test:unit + - npm run build dependencies: - install_dependencies + +vitest_unit-tests: + stage: test cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ policy: pull - -eslint_run-lint: - stage: lint script: - npm ci - - npm run lint + - npm run test:unit dependencies: - install_dependencies + +eslint_run-lint: + stage: lint cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ policy: pull + script: + - npm ci + - npm run lint + dependencies: + - install_dependencies allow_failure: true + + + diff --git a/spec.json b/spec.json index 0aab7ca89217c5048202a87de63d24a829a02be4..d22821d7fd6ecd38fdd014cc2859ec7fcbe472f1 100644 --- a/spec.json +++ b/spec.json @@ -79,8 +79,8 @@ } ], "responses": { - "404": { - "description": "Friend request not found", + "200": { + "description": "Friend request successfully accepted", "content": { "*/*": { "schema": { @@ -89,8 +89,8 @@ } } }, - "200": { - "description": "Friend request successfully accepted", + "404": { + "description": "Friend request not found", "content": { "*/*": { "schema": { @@ -120,8 +120,8 @@ } ], "responses": { - "404": { - "description": "Friend or friend request not found", + "200": { + "description": "Friend successfully deleted or friend request cancelled", "content": { "*/*": { "schema": { @@ -130,8 +130,8 @@ } } }, - "200": { - "description": "Friend successfully deleted or friend request cancelled", + "404": { + "description": "Friend or friend request not found", "content": { "*/*": { "schema": { @@ -162,8 +162,8 @@ "required": true }, "responses": { - "404": { - "description": "Bank profile id does not exist", + "200": { + "description": "No accounts associated with a bank user", "content": { "*/*": { "schema": { @@ -172,8 +172,8 @@ } } }, - "200": { - "description": "No accounts associated with a bank user", + "404": { + "description": "Bank profile id does not exist", "content": { "*/*": { "schema": { @@ -204,8 +204,8 @@ "required": true }, "responses": { - "400": { - "description": "Could not create profile", + "200": { + "description": "Successfully created a bank profile", "content": { "*/*": { "schema": { @@ -214,8 +214,8 @@ } } }, - "200": { - "description": "Successfully created a bank profile", + "400": { + "description": "Could not create profile", "content": { "*/*": { "schema": { @@ -246,8 +246,8 @@ "required": true }, "responses": { - "404": { - "description": "Provided bank profile id could not be found", + "200": { + "description": "Successfully created account", "content": { "*/*": { "schema": { @@ -256,8 +256,8 @@ } } }, - "200": { - "description": "Successfully created account", + "404": { + "description": "Provided bank profile id could not be found", "content": { "*/*": { "schema": { @@ -339,11 +339,11 @@ "required": true }, "responses": { - "403": { - "description": "Invalid token" - }, "204": { "description": "Password was reset successfully" + }, + "403": { + "description": "Invalid token" } }, "security": [] @@ -368,8 +368,8 @@ "required": true }, "responses": { - "500": { - "description": "User is not found", + "200": { + "description": "Successfully updated notification", "content": { "*/*": { "schema": { @@ -378,8 +378,8 @@ } } }, - "200": { - "description": "Successfully updated notification", + "500": { + "description": "User is not found", "content": { "*/*": { "schema": { @@ -411,6 +411,12 @@ } ], "responses": { + "201": { + "description": "Item purchased and added to inventory successfully", + "content": { + "application/json": {} + } + }, "403": { "description": "Insufficient points to purchase the item", "content": { @@ -420,14 +426,6 @@ } } } - }, - "201": { - "description": "Item purchased and added to inventory successfully", - "content": { - "application/json": { - - } - } } } } @@ -547,6 +545,9 @@ "required": true }, "responses": { + "200": { + "description": "Successfully updated the challenge" + }, "401": { "description": "Day is already completed or day outside of range", "content": { @@ -556,9 +557,6 @@ } } } - }, - "200": { - "description": "Successfully updated the challenge" } } } @@ -594,7 +592,7 @@ "Friend" ], "summary": "Send a friend request", - "description": "Sends a new friend request to another user.", + "description": "Sends a new friend request to another user. A notification is sent to this user", "operationId": "addFriendRequest", "parameters": [ { @@ -644,8 +642,8 @@ "required": true }, "responses": { - "500": { - "description": "Budget is not found", + "200": { + "description": "Successfully updated budget", "content": { "application/json": { "schema": { @@ -654,8 +652,8 @@ } } }, - "200": { - "description": "Successfully updated budget", + "500": { + "description": "Budget is not found", "content": { "application/json": { "schema": { @@ -697,8 +695,8 @@ "required": true }, "responses": { - "500": { - "description": "Error updating expense", + "200": { + "description": "Successfully updated budget", "content": { "application/json": { "schema": { @@ -707,8 +705,8 @@ } } }, - "200": { - "description": "Successfully updated budget", + "500": { + "description": "Error updating expense", "content": { "application/json": { "schema": { @@ -771,22 +769,22 @@ } ], "responses": { - "409": { - "description": "Email already exists", + "200": { + "description": "Email is valid", "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ExceptionResponse" + "type": "object" } } } }, - "200": { - "description": "Email is valid", + "409": { + "description": "Email already exists", "content": { "*/*": { "schema": { - "type": "object" + "$ref": "#/components/schemas/ExceptionResponse" } } } @@ -814,22 +812,22 @@ "required": true }, "responses": { - "409": { - "description": "Email already exists", + "201": { + "description": "Successfully signed up", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ExceptionResponse" + "$ref": "#/components/schemas/AuthenticationResponse" } } } }, - "201": { - "description": "Successfully signed up", + "409": { + "description": "Email already exists", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AuthenticationResponse" + "$ref": "#/components/schemas/ExceptionResponse" } } } @@ -1112,8 +1110,8 @@ } ], "responses": { - "404": { - "description": "Bank profile id does not exist", + "200": { + "description": "No accounts associated with a bank user", "content": { "*/*": { "schema": { @@ -1125,8 +1123,8 @@ } } }, - "200": { - "description": "No accounts associated with a bank user", + "404": { + "description": "Bank profile id does not exist", "content": { "*/*": { "schema": { @@ -1741,8 +1739,8 @@ } ], "responses": { - "500": { - "description": "Budget is not found", + "200": { + "description": "Successfully got budget", "content": { "application/json": { "schema": { @@ -1751,8 +1749,8 @@ } } }, - "200": { - "description": "Successfully got budget", + "500": { + "description": "Budget is not found", "content": { "application/json": { "schema": { @@ -1863,8 +1861,8 @@ } ], "responses": { - "500": { - "description": "Budget is not found", + "200": { + "description": "Successfully deleted budget", "content": { "application/json": { "schema": { @@ -1873,8 +1871,8 @@ } } }, - "200": { - "description": "Successfully deleted budget", + "500": { + "description": "Budget is not found", "content": { "application/json": { "schema": { @@ -1974,8 +1972,8 @@ } ], "responses": { - "500": { - "description": "Badge is not found", + "200": { + "description": "Successfully got budget", "content": { "application/json": { "schema": { @@ -1984,8 +1982,8 @@ } } }, - "200": { - "description": "Successfully got budget", + "500": { + "description": "Badge is not found", "content": { "application/json": { "schema": { diff --git a/src/api/index.ts b/src/api/index.ts index 56778d8e88d3d473dfe5a0813b503b22a59f44ac..4ab22c2112459939cb385110bf7011d567d948a2 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -13,6 +13,7 @@ export type { AccountResponseDTO } from './models/AccountResponseDTO'; export type { AuthenticationResponse } from './models/AuthenticationResponse'; export type { BadgeDTO } from './models/BadgeDTO'; export type { BankAccountDTO } from './models/BankAccountDTO'; +export type { BankIDRequest } from './models/BankIDRequest'; export type { BankProfile } from './models/BankProfile'; export type { BankProfileDTO } from './models/BankProfileDTO'; export type { BankProfileResponseDTO } from './models/BankProfileResponseDTO'; @@ -34,6 +35,7 @@ export type { LeaderboardDTO } from './models/LeaderboardDTO'; export type { LeaderboardEntryDTO } from './models/LeaderboardEntryDTO'; export type { LoginRequest } from './models/LoginRequest'; export type { MarkChallengeDTO } from './models/MarkChallengeDTO'; +export { NotificationDTO } from './models/NotificationDTO'; export type { PasswordResetDTO } from './models/PasswordResetDTO'; export type { PasswordUpdateDTO } from './models/PasswordUpdateDTO'; export type { ProfileDTO } from './models/ProfileDTO'; @@ -53,5 +55,7 @@ export { GoalService } from './services/GoalService'; export { ImageService } from './services/ImageService'; export { ItemService } from './services/ItemService'; export { LeaderboardService } from './services/LeaderboardService'; +export { NotificationService } from './services/NotificationService'; +export { RedirectService } from './services/RedirectService'; export { TransactionControllerService } from './services/TransactionControllerService'; export { UserService } from './services/UserService'; diff --git a/src/api/models/BankIDRequest.ts b/src/api/models/BankIDRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..225dc8bb202284c5438e1a0f8d01bdd9d85a2231 --- /dev/null +++ b/src/api/models/BankIDRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type BankIDRequest = { + code?: string; + state?: string; +}; + diff --git a/src/api/models/NotificationDTO.ts b/src/api/models/NotificationDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..2834bf8466bb776108d1054e1278c96a6ce69636 --- /dev/null +++ b/src/api/models/NotificationDTO.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type NotificationDTO = { + id?: number; + message?: string; + unread?: boolean; + notificationType?: NotificationDTO.notificationType; + createdAt?: string; +}; +export namespace NotificationDTO { + export enum notificationType { + BADGE = 'BADGE', + FRIEND_REQUEST = 'FRIEND_REQUEST', + COMPLETED_GOAL = 'COMPLETED_GOAL', + } +} + diff --git a/src/api/services/AuthenticationService.ts b/src/api/services/AuthenticationService.ts index 48c819ab0a448c84d87fce77baf7d9b54b49dc51..ac619756c90377b2090d430cbcd47e746eb52af9 100644 --- a/src/api/services/AuthenticationService.ts +++ b/src/api/services/AuthenticationService.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { AuthenticationResponse } from '../models/AuthenticationResponse'; +import type { BankIDRequest } from '../models/BankIDRequest'; import type { LoginRequest } from '../models/LoginRequest'; import type { SignUpRequest } from '../models/SignUpRequest'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -74,4 +75,22 @@ export class AuthenticationService { }, }); } + /** + * Authenticate a BankID request + * Authenticate a BankID request + * @returns AuthenticationResponse If the authentication is successful + * @throws ApiError + */ + public static bankIdAuthentication({ + requestBody, + }: { + requestBody: BankIDRequest, + }): CancelablePromise<AuthenticationResponse> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/auth/bank-id', + body: requestBody, + mediaType: 'application/json', + }); + } } diff --git a/src/api/services/FriendService.ts b/src/api/services/FriendService.ts index 027c6a7e55949b0ab3663cea767d451121593f3d..70b531d682d20c95dc134b4f0d694df49fb548fb 100644 --- a/src/api/services/FriendService.ts +++ b/src/api/services/FriendService.ts @@ -53,7 +53,7 @@ export class FriendService { } /** * Send a friend request - * Sends a new friend request to another user. + * Sends a new friend request to another user. A notification is sent to this user * @returns any Friend request successfully created * @throws ApiError */ diff --git a/src/api/services/GoalService.ts b/src/api/services/GoalService.ts index f5301e2dbec1081e914aab9479cce1c18fdff5d7..b3bfa72d953cab2252919fc7ab21566db845e51f 100644 --- a/src/api/services/GoalService.ts +++ b/src/api/services/GoalService.ts @@ -79,9 +79,7 @@ export class GoalService { }); } /** - * Update a challenge - * Update a challenge day as completed - * @returns any Successfully updated the challenge + * @returns GoalDTO OK * @throws ApiError */ public static getGoal({ diff --git a/src/api/services/NotificationService.ts b/src/api/services/NotificationService.ts new file mode 100644 index 0000000000000000000000000000000000000000..331fc68ba61a26c7fd8b51972a2b2e0de381ce45 --- /dev/null +++ b/src/api/services/NotificationService.ts @@ -0,0 +1,77 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { NotificationDTO } from '../models/NotificationDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class NotificationService { + /** + * Updates a notification + * Updates a notification based on the request + * @returns any Successfully updated notification + * @throws ApiError + */ + public static updateNotification({ + requestBody, + }: { + requestBody: NotificationDTO, + }): CancelablePromise<Record<string, any>> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/notification/update', + body: requestBody, + mediaType: 'application/json', + errors: { + 500: `User is not found`, + }, + }); + } + /** + * Get the list of notifications + * Get all notifications to a user + * @returns NotificationDTO Successfully got notifications + * @throws ApiError + */ + public static getNotificationByUser(): CancelablePromise<Array<NotificationDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/notification', + }); + } + /** + * Get the notification + * Get notification by its id + * @returns NotificationDTO Successfully got notification + * @throws ApiError + */ + public static getNotification({ + notificationId, + }: { + notificationId: number, + }): CancelablePromise<NotificationDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/notification/{notificationId}', + path: { + 'notificationId': notificationId, + }, + errors: { + 500: `Notification is not found`, + }, + }); + } + /** + * Get the list of unread notifications + * Get all unread notifications to a user + * @returns NotificationDTO Successfully got notifications + * @throws ApiError + */ + public static getUnreadNotificationByUser(): CancelablePromise<Array<NotificationDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/notification/unread', + }); + } +} diff --git a/src/api/services/RedirectService.ts b/src/api/services/RedirectService.ts new file mode 100644 index 0000000000000000000000000000000000000000..1dbf1cfac5dedad6ccd91c038991c0cb961ed24e --- /dev/null +++ b/src/api/services/RedirectService.ts @@ -0,0 +1,26 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class RedirectService { + /** + * @returns any OK + * @throws ApiError + */ + public static consumeCallback({ + state, + }: { + state: string, + }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'GET', + url: '/redirect', + query: { + 'state': state, + }, + }); + } +} diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index 1be7d5b0c9164b5efc7e4442c1cd84dd678b2898..51df6e46b965ecbb438f7dcfb68258422e13d22e 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -4,10 +4,6 @@ /* eslint-disable */ import type { Account } from '../models/Account'; import type { BankAccountDTO } from '../models/BankAccountDTO'; -import type { BudgetRequestDTO } from '../models/BudgetRequestDTO'; -import type { BudgetResponseDTO } from '../models/BudgetResponseDTO'; -import type { ExpenseRequestDTO } from '../models/ExpenseRequestDTO'; -import type { ExpenseResponseDTO } from '../models/ExpenseResponseDTO'; import type { FeedbackRequestDTO } from '../models/FeedbackRequestDTO'; import type { FeedbackResponseDTO } from '../models/FeedbackResponseDTO'; import type { PasswordResetDTO } from '../models/PasswordResetDTO'; diff --git a/src/components/BaseComponents/NavBar.vue b/src/components/BaseComponents/NavBar.vue index 5fd22ab4773e4278c39205f6ec9cf260b2bb7b9a..62cb230905705022153d26ddd20449d532782f4f 100644 --- a/src/components/BaseComponents/NavBar.vue +++ b/src/components/BaseComponents/NavBar.vue @@ -36,6 +36,30 @@ <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="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">{{item.message}}</div> + </div> + </router-link> + </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"> @@ -134,6 +158,8 @@ import { useRouter, useRoute } from "vue-router"; import { useUserInfoStore } from '@/stores/UserStore'; import {onMounted, ref} from "vue"; +import { type NotificationDTO, NotificationService } from '@/api' +import { afterWrite } from '@popperjs/core' @@ -150,21 +176,11 @@ 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) - +let notificationListRef = ref<NotificationDTO[]>([]); -/* id: 0 -> /roadmap - id: 1 -> /profile - id: 2 -> /friend - */ function isAnyActivePage() { const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/shop']; // Add other pages here @@ -178,20 +194,36 @@ function toggleDropdown(event: any) { } } -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 +const notificationImageMapper: any = { + "FRIEND_REQUEST": "/src/assets/userprofile.png", + "BADGE": "/src/assets/icons/medal.png", + "COMPLETED_GOAL": "/src/assets/icons/piggybank.svg" +} + +const notificationPathMapper: any = { + "FRIEND_REQUEST": "/friends", + "BADGE": "/profile", + "COMPLETED_GOAL": "/roadmap" +} +const getNotifications = async () => { + try { + notificationListRef.value = await NotificationService.getUnreadNotificationByUser() + } catch (error) { + notificationListRef.value = [] + } +} + +const readNotification = async (notification: NotificationDTO) => { + try { + notification.unread = false; + await NotificationService.updateNotification({requestBody: notification}); + notificationListRef.value = await NotificationService.getUnreadNotificationByUser() + } catch (error) { + notificationListRef.value = []; + } } + function toBadges(){ } @@ -264,7 +296,7 @@ function toLogout() { router.push('login') } onMounted(() => { - getNotification() + getNotifications() }) </script>