Skip to content
Snippets Groups Projects
Commit bde4383d authored by Victor Ekholt Gunrell Kaste's avatar Victor Ekholt Gunrell Kaste
Browse files

Merge branch 'main' into 'feat/redesign-roadmap'

# Conflicts:
#   src/api/services/GoalService.ts
#   src/api/services/UserService.ts
parents e8411034 ca25ad19
No related branches found
No related tags found
1 merge request!69Feat/redesign roadmap
Pipeline #282314 failed with stages
in 2 minutes and 2 seconds
Showing
with 4588 additions and 494 deletions
# frontend
# SpareSti
This template should help get you started developing with Vue 3 in Vite.
## Description
The frontend of sparesti.app. SpareSti is designed to make saving fun. The app is integrated with your online bank, therefore it has an overview of what your money is being spent on and can provide you with personalized saving tips based on this information. The app is suitable for all saving goals and offers motivation and tips tailored to your desires. Since we know that saving money can be difficult, SpareSti automatically deposits money into your savings account when you complete challenges. Based on your saved funds, the feed will give you personalized tips on how your money can be invested, and you will be able to set up a budget that provides you with the overview you need to make informed choices.
## Links
- **Backend**: [https://gitlab.stud.idi.ntnu.no/idatt2106-2024-07/backend](https://gitlab.stud.idi.ntnu.no/idatt2106-2024-07/backend)
## Recommended IDE Setup
......@@ -14,7 +19,7 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
## Getting Started
```sh
npm install
......@@ -59,3 +64,13 @@ npm run test:e2e
```sh
npm run lint
```
## Contributors
The individuals who contributed to the project:
- Anders Høvik
- Andreas Kluge Svendsrud
- Henrik Dybdal
- Henrik Teksle Sandok
- Jens Christian Aanestad
- Victor Kaste
- Viktor Grevskott
......@@ -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,
}
})
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
This diff is collapsed.
This diff is collapsed.
......@@ -11,6 +11,7 @@ export type { Account } from './models/Account';
export type { AccountRequestDTO } from './models/AccountRequestDTO';
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 { BankProfile } from './models/BankProfile';
export type { BankProfileDTO } from './models/BankProfileDTO';
......@@ -44,7 +45,9 @@ export type { UserUpdateDTO } from './models/UserUpdateDTO';
export { AccountControllerService } from './services/AccountControllerService';
export { AuthenticationService } from './services/AuthenticationService';
export { BadgeService } from './services/BadgeService';
export { BankProfileControllerService } from './services/BankProfileControllerService';
export { BudgetService } from './services/BudgetService';
export { FriendService } from './services/FriendService';
export { GoalService } from './services/GoalService';
export { ImageService } from './services/ImageService';
......
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BadgeDTO = {
id?: number;
badgeName?: string;
criteria?: number;
imageId?: number;
};
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BadgeDTO } from '../models/BadgeDTO';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class BadgeService {
/**
* Get the list of badges
* Get all badges stored in the database
* @returns BadgeDTO Successfully got badges
* @throws ApiError
*/
public static getAllBadges(): CancelablePromise<Array<BadgeDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/badge',
});
}
/**
* Get the budget
* Get budget by its id
* @returns BadgeDTO Successfully got budget
* @throws ApiError
*/
public static getBadge({
badgeId,
}: {
badgeId: number,
}): CancelablePromise<BadgeDTO> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/badge/{badgeId}',
path: {
'badgeId': badgeId,
},
errors: {
500: `Badge is not found`,
},
});
}
/**
* Updates unlocked badges
* Checks if a user has met the criteria for unlocking badges
* @returns any Successfully updated badges
* @throws ApiError
*/
public static updateUnlockedBadges(): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/badge/update',
});
}
/**
* Get the list of badges
* Get all badges unlocked by the user
* @returns BadgeDTO Successfully got badges
* @throws ApiError
*/
public static getBadgesUnlockedByUser(): CancelablePromise<Array<BadgeDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/badge/unlocked',
});
}
/**
* Get the list of badges
* Get all badges not unlocked by the user
* @returns BadgeDTO Successfully got badges
* @throws ApiError
*/
public static getBadgesNotUnlockedByUser(): CancelablePromise<Array<BadgeDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/badge/locked',
});
}
}
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
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 { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class BudgetService {
/**
* Updates a budget
* Updates a budget based on the budget request
* @returns any Successfully updated budget
* @throws ApiError
*/
public static updateBudget({
budgetId,
requestBody,
}: {
budgetId: number,
requestBody: BudgetRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/update/{budgetId}',
path: {
'budgetId': budgetId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
500: `Budget is not found`,
},
});
}
/**
* Created/Updates an expense
* Creates/Updates a budget based on the budget request
* @returns any Successfully updated budget
* @throws ApiError
*/
public static updateExpense({
budgetId,
requestBody,
}: {
budgetId: number,
requestBody: ExpenseRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/update/expense/{budgetId}',
path: {
'budgetId': budgetId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
500: `Error updating expense`,
},
});
}
/**
* Create a new budget
* Create a new budget with based on the budget request
* @returns any Successfully created new budget
* @throws ApiError
*/
public static createBudget({
requestBody,
}: {
requestBody: BudgetRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/create',
body: requestBody,
mediaType: 'application/json',
});
}
/**
* Get the list of budgets
* Get all budgets related to the authenticated user
* @returns BudgetResponseDTO Successfully got budgets
* @throws ApiError
*/
public static getBudgetsByUser(): CancelablePromise<Array<BudgetResponseDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget',
});
}
/**
* Get the budget
* Get budget by its id
* @returns BudgetResponseDTO Successfully got budget
* @throws ApiError
*/
public static getBudget({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<BudgetResponseDTO> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/{budgetId}',
path: {
'budgetId': budgetId,
},
errors: {
500: `Budget is not found`,
},
});
}
/**
* Get the list of budgets
* Get all budgets related to the authenticated user
* @returns ExpenseResponseDTO Successfully got expenses
* @throws ApiError
*/
public static getExpenses({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<Array<ExpenseResponseDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/expenses/{budgetId}',
path: {
'budgetId': budgetId,
},
});
}
/**
* Get the expense
* Get expense by its id
* @returns ExpenseResponseDTO Successfully got expense
* @throws ApiError
*/
public static getExpense({
expenseId,
}: {
expenseId: number,
}): CancelablePromise<ExpenseResponseDTO> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/expense/{expenseId}',
path: {
'expenseId': expenseId,
},
errors: {
500: `Expense is not found`,
},
});
}
/**
* Deletes a budget
* Deletes a budget based on provided budget id
* @returns any Successfully deleted budget
* @throws ApiError
*/
public static deleteBudget({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/delete/{budgetId}',
path: {
'budgetId': budgetId,
},
errors: {
500: `Budget is not found`,
},
});
}
/**
* Deletes an expense
* Deletes an expense based on provided expense id
* @returns any Successfully deleted expense
* @throws ApiError
*/
public static deleteExpense({
expenseId,
}: {
expenseId: number,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/delete/expense/{expenseId}',
path: {
'expenseId': expenseId,
},
errors: {
500: `Expense is not found`,
},
});
}
}
......@@ -79,7 +79,9 @@ export class GoalService {
});
}
/**
* @returns GoalDTO OK
* Update a challenge
* Update a challenge day as completed
* @returns any Successfully updated the challenge
* @throws ApiError
*/
public static getGoal({
......
......@@ -95,76 +95,6 @@ export class UserService {
},
});
}
/**
* Updates a budget
* Updates a budget based on the budget request
* @returns any Successfully updated budget
* @throws ApiError
*/
public static updateBudget({
budgetId,
requestBody,
}: {
budgetId: number,
requestBody: BudgetRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/update/{budgetId}',
path: {
'budgetId': budgetId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
500: `Budget is not found`,
},
});
}
/**
* Created/Updates an expense
* Creates/Updates a budget based on the budget request
* @returns any Successfully updated budget
* @throws ApiError
*/
public static updateExpense({
budgetId,
requestBody,
}: {
budgetId: number,
requestBody: ExpenseRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/update/expense/{budgetId}',
path: {
'budgetId': budgetId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
500: `Error updating expense`,
},
});
}
/**
* Create a new budget
* Create a new budget with based on the budget request
* @returns any Successfully created new budget
* @throws ApiError
*/
public static createBudget({
requestBody,
}: {
requestBody: BudgetRequestDTO,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/budget/create',
body: requestBody,
mediaType: 'application/json',
});
}
/**
* Update a profile
* Update the profile of the authenticated user
......@@ -312,117 +242,10 @@ export class UserService {
* @returns BudgetResponseDTO Successfully got budgets
* @throws ApiError
*/
public static getBudgetsByUser(): CancelablePromise<Array<BudgetResponseDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget',
});
}
/**
* Get the budget
* Get budget by its id
* @returns BudgetResponseDTO Successfully got budget
* @throws ApiError
*/
public static getBudget({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<BudgetResponseDTO> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/{budgetId}',
path: {
'budgetId': budgetId,
},
errors: {
500: `Budget is not found`,
},
});
}
/**
* Get the list of budgets
* Get all budgets related to the authenticated user
* @returns ExpenseResponseDTO Successfully got expenses
* @throws ApiError
*/
public static getExpenses({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<Array<ExpenseResponseDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/expenses/{budgetId}',
path: {
'budgetId': budgetId,
},
});
}
/**
* Get the expense
* Get expense by its id
* @returns ExpenseResponseDTO Successfully got expense
* @throws ApiError
*/
public static getExpense({
expenseId,
}: {
expenseId: number,
}): CancelablePromise<ExpenseResponseDTO> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/expense/{expenseId}',
path: {
'expenseId': expenseId,
},
errors: {
500: `Expense is not found`,
},
});
}
/**
* Deletes a budget
* Deletes a budget based on provided budget id
* @returns any Successfully deleted budget
* @throws ApiError
*/
public static deleteBudget({
budgetId,
}: {
budgetId: number,
}): CancelablePromise<Record<string, any>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/delete/{budgetId}',
path: {
'budgetId': budgetId,
},
errors: {
500: `Budget is not found`,
},
});
}
/**
* Deletes an expense
* Deletes an expense based on provided expense id
* @returns any Successfully deleted expense
* @throws ApiError
*/
public static deleteExpense({
expenseId,
}: {
expenseId: number,
}): CancelablePromise<Record<string, any>> {
public static getFeedback(): CancelablePromise<Array<FeedbackResponseDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/budget/delete/expense/{expenseId}',
path: {
'expenseId': expenseId,
},
errors: {
500: `Expense is not found`,
},
url: '/api/users/get-feedback',
});
}
}
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="57" height="57" rx="12" fill="#39134C"/>
<path d="M19.5001 21.0004H13.5C12.674 21.0004 12 20.3264 12 19.5004C12 18.6764 12.674 18.0004 13.5 18.0004H19.5001C20.3241 18.0004 21.0001 18.6744 21.0001 19.5004C21.0001 20.3264 20.3241 21.0004 19.5001 21.0004Z" fill="white"/>
<path d="M19.5001 32.9992H13.5C12.674 32.9992 12 32.3252 12 31.4992C12 30.6752 12.674 29.9991 13.5 29.9991H19.5001C20.3241 29.9991 21.0001 30.6732 21.0001 31.4992C21.0001 32.3252 20.3241 32.9992 19.5001 32.9992Z" fill="white"/>
<path d="M19.5001 38.9998H13.5C12.674 38.9998 12 38.3258 12 37.4998C12 36.6758 12.674 35.9998 13.5 35.9998H19.5001C20.3241 35.9998 21.0001 36.6738 21.0001 37.4998C21.0001 38.3258 20.3241 38.9998 19.5001 38.9998Z" fill="white"/>
<path d="M31.5005 26.9993H25.5004C24.6744 26.9993 24.0004 26.3253 24.0004 25.4993C24.0004 24.6753 24.6744 23.9993 25.5004 23.9993H31.5005C32.3245 23.9993 33.0005 24.6733 33.0005 25.4993C33.0005 26.3253 32.3245 26.9993 31.5005 26.9993Z" fill="white"/>
<path d="M31.5005 32.9992H25.5004C24.6744 32.9992 24.0004 32.3252 24.0004 31.4992C24.0004 30.6752 24.6744 29.9991 25.5004 29.9991H31.5005C32.3245 29.9991 33.0005 30.6732 33.0005 31.4992C33.0005 32.3252 32.3245 32.9992 31.5005 32.9992Z" fill="white"/>
<path d="M43.5001 21H37.5C36.674 21 36 20.326 36 19.5C36 18.676 36.674 18 37.5 18H43.5001C44.3241 18 45.0001 18.674 45.0001 19.5C45.0001 20.326 44.3241 21 43.5001 21Z" fill="white"/>
<path d="M43.5001 26.9993H37.5C36.674 26.9993 36 26.3253 36 25.4993C36 24.6753 36.674 23.9993 37.5 23.9993H43.5001C44.3241 23.9993 45.0001 24.6733 45.0001 25.4993C45.0001 26.3253 44.3241 26.9993 43.5001 26.9993Z" fill="white"/>
<path d="M43.5001 38.9998H37.5C36.674 38.9998 36 38.3258 36 37.4998C36 36.6758 36.674 35.9998 37.5 35.9998H43.5001C44.3241 35.9998 45.0001 36.6738 45.0001 37.4998C45.0001 38.3258 44.3241 38.9998 43.5001 38.9998Z" fill="white"/>
</svg>
<!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
<?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
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#FFF" height="40px" width="40px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 460 460" xml:space="preserve">
<g id="XMLID_241_">
<g>
<path d="M369.635,0H90.365C73.595,0,60,13.595,60,30.365v399.27C60,446.405,73.595,460,90.365,460h279.27
c16.77,0,30.365-13.595,30.365-30.365V30.365C400,13.595,386.405,0,369.635,0z M108.204,343.61v-43.196
c0-3.451,2.797-6.248,6.248-6.248h43.196c3.451,0,6.248,2.797,6.248,6.248v43.196c0,3.451-2.797,6.248-6.248,6.248h-43.196
C111.001,349.858,108.204,347.06,108.204,343.61z M108.204,256.61v-43.196c0-3.451,2.797-6.248,6.248-6.248h43.196
c3.451,0,6.248,2.797,6.248,6.248v43.196c0,3.451-2.797,6.248-6.248,6.248h-43.196C111.001,262.858,108.204,260.06,108.204,256.61
z M308.891,421H151.109c-11.046,0-20-8.954-20-20c0-11.046,8.954-20,20-20h157.782c11.046,0,20,8.954,20,20
C328.891,412.046,319.937,421,308.891,421z M208.402,294.165h43.196c3.451,0,6.248,2.797,6.248,6.248v43.196
c0,3.451-2.797,6.248-6.248,6.248h-43.196c-3.451,0-6.248-2.797-6.248-6.248v-43.196
C202.154,296.963,204.951,294.165,208.402,294.165z M202.154,256.61v-43.196c0-3.451,2.797-6.248,6.248-6.248h43.196
c3.451,0,6.248,2.797,6.248,6.248v43.196c0,3.451-2.797,6.248-6.248,6.248h-43.196C204.951,262.858,202.154,260.06,202.154,256.61
z M345.548,349.858h-43.196c-3.451,0-6.248-2.797-6.248-6.248v-43.196c0-3.451,2.797-6.248,6.248-6.248h43.196
c3.451,0,6.248,2.797,6.248,6.248v43.196h0C351.796,347.061,348.999,349.858,345.548,349.858z M345.548,262.858h-43.196
c-3.451,0-6.248-2.797-6.248-6.248v-43.196c0-3.451,2.797-6.248,6.248-6.248h43.196c3.451,0,6.248,2.797,6.248,6.248v43.196h0
C351.796,260.061,348.999,262.858,345.548,262.858z M354,149.637c0,11.799-9.565,21.363-21.363,21.363H127.364
C115.565,171,106,161.435,106,149.637V62.363C106,50.565,115.565,41,127.364,41h205.273C344.435,41,354,50.565,354,62.363V149.637
z"/>
</g>
</g>
</svg>
\ No newline at end of file
src/assets/icons/medal.png

28.1 KiB

<svg id="visual" viewBox="0 0 900 600" width="900" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><rect x="0" y="0" width="900" height="600" fill="#003a58"></rect><path d="M0 314L16.7 321.3C33.3 328.7 66.7 343.3 100 341C133.3 338.7 166.7 319.3 200 308.7C233.3 298 266.7 296 300 299.8C333.3 303.7 366.7 313.3 400 323.8C433.3 334.3 466.7 345.7 500 346.7C533.3 347.7 566.7 338.3 600 336.7C633.3 335 666.7 341 700 345.7C733.3 350.3 766.7 353.7 800 344C833.3 334.3 866.7 311.7 883.3 300.3L900 289L900 601L883.3 601C866.7 601 833.3 601 800 601C766.7 601 733.3 601 700 601C666.7 601 633.3 601 600 601C566.7 601 533.3 601 500 601C466.7 601 433.3 601 400 601C366.7 601 333.3 601 300 601C266.7 601 233.3 601 200 601C166.7 601 133.3 601 100 601C66.7 601 33.3 601 16.7 601L0 601Z" fill="#ffffff" stroke-linecap="round" stroke-linejoin="miter"></path></svg>
\ No newline at end of file
......@@ -7,70 +7,180 @@
</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
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 class="nav-link text-white" :to="toSavingGoals()"><img
src="@/assets/icons/saving.svg">Saving goals</router-link>
<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 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">Nyheter</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">Butikk</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="Varslingsikon" 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="Varslingsikon" 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="Varslingsikon" 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="@/assets/icons/person.svg">{{ useUserInfoStore().firstname}}
<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><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
<li><router-link data-cy="profile"
class="dropdown-item text-white dropdown-username-link" :to="toUserProfile()"><img
src="@/assets/icons/person.svg">Brukerprofil</router-link></li>
<li v-if="useUserInfoStore().isPremium"><router-link data-cy="budget"
class="dropdown-item text-white dropdown-username-link" :to="toBudget()"><img>Budjsett</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">Venner</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">Innstillinger</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">Tilbakemelding</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>
src="@/assets/icons/logout.svg">Logg ut</a></li>
</ul>
</li>
<li v-else class="nav-item">
<a class="nav-link text-white" href="#" @click="toLogout">Login</a>
<a class="nav-link text-white" href="#" @click="toLogout">Logg inn</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
import { useUserInfoStore } from '@/stores/UserStore';
import {onMounted, ref} from "vue";
const router = useRouter();
const userStore : any = useUserInfoStore();
const userStore: any = useUserInfoStore();
let profileImage: any = ref('');
if (useUserInfoStore().profileImage !== 0) {
profileImage = 'http://localhost:8080/api/images/' + useUserInfoStore().profileImage;
} else {
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 '/'
}
function toBudget() {
return '/budget-overview'
return '/budget-overview'
}
function toSavingGoals() {
......@@ -102,14 +212,16 @@ function toFriends() {
}
function toUserProfile() {
return '/profile'
return '/profile'
}
function toLogout() {
userStore.clearUserInfo();
router.push('login')
}
onMounted(() => {
getNotification()
})
</script>
<style scoped>
......@@ -130,6 +242,9 @@ function toLogout() {
.nav-item:hover {
background-color: #2b6ac7;
}
.not-item:hover {
background-color: #2b6ac7;
}
.nav-item .dropdown {
display: flex;
......@@ -153,6 +268,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;
}
......@@ -195,4 +320,9 @@ function toLogout() {
height: auto;
aspect-ratio: 1.3/1;
}
.notification.hidden-arrow::after{
display: none;
}
</style>
\ No newline at end of file
......@@ -91,7 +91,7 @@ const onBudgetDeleted = () => {
</i>
<div class="expenses-container">
<h5>{{expenses}} kr</h5>
<p>Expenses</p>
<p>Utgifter</p>
</div>
</div>
......@@ -101,7 +101,7 @@ const onBudgetDeleted = () => {
</i>
<div class="balance-container">
<h5>{{balance}} kr</h5>
<p>Balance</p>
<p>Saldo</p>
</div>
</div>
</div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment