diff --git a/src/App.vue b/src/App.vue index d80fae0b5000eaab01e06674f5084b3da957048d..7b8e141ea5d57e53886dc5b9c557d254f3a55c4f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { RouterView } from 'vue-router' -import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue'; +import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue' </script> <template> @@ -12,8 +12,8 @@ import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.v </template> <style> - main { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-weight: 600; - } -</style> \ No newline at end of file +main { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-weight: 600; +} +</style> diff --git a/src/api/core/ApiError.ts b/src/api/core/ApiError.ts index ec7b16af6f41b1323a8e3aa3d529bf2324959e66..d485a9d8911498b0a8da9c0365ff06d6f96363ac 100644 --- a/src/api/core/ApiError.ts +++ b/src/api/core/ApiError.ts @@ -2,24 +2,24 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; +import type { ApiRequestOptions } from './ApiRequestOptions' +import type { ApiResult } from './ApiResult' export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: any; - public readonly request: ApiRequestOptions; + public readonly url: string + public readonly status: number + public readonly statusText: string + public readonly body: any + public readonly request: ApiRequestOptions - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message) - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } + this.name = 'ApiError' + this.url = response.url + this.status = response.status + this.statusText = response.statusText + this.body = response.body + this.request = request + } } diff --git a/src/api/core/ApiRequestOptions.ts b/src/api/core/ApiRequestOptions.ts index 93143c3ce1ba5323894d4ac10299f62493f030f6..2cd01362fea40a00a4db04ef8ca2e5c98e815c70 100644 --- a/src/api/core/ApiRequestOptions.ts +++ b/src/api/core/ApiRequestOptions.ts @@ -3,15 +3,15 @@ /* tslint:disable */ /* eslint-disable */ export type ApiRequestOptions = { - readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; - readonly url: string; - readonly path?: Record<string, any>; - readonly cookies?: Record<string, any>; - readonly headers?: Record<string, any>; - readonly query?: Record<string, any>; - readonly formData?: Record<string, any>; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly errors?: Record<number, string>; -}; + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH' + readonly url: string + readonly path?: Record<string, any> + readonly cookies?: Record<string, any> + readonly headers?: Record<string, any> + readonly query?: Record<string, any> + readonly formData?: Record<string, any> + readonly body?: any + readonly mediaType?: string + readonly responseHeader?: string + readonly errors?: Record<number, string> +} diff --git a/src/api/core/ApiResult.ts b/src/api/core/ApiResult.ts index ee1126e2ccd1e37dba97511c38c56a282ceac4dc..6509a7d90c2677f0dd87e8c6b6ec6ddaaad3dfe4 100644 --- a/src/api/core/ApiResult.ts +++ b/src/api/core/ApiResult.ts @@ -3,9 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type ApiResult = { - readonly url: string; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly body: any; -}; + readonly url: string + readonly ok: boolean + readonly status: number + readonly statusText: string + readonly body: any +} diff --git a/src/api/core/CancelablePromise.ts b/src/api/core/CancelablePromise.ts index d70de92946d977e9da7970871375117a8b04770a..c9184a63cce193f4367fd7fc8aa30c1108b15bf9 100644 --- a/src/api/core/CancelablePromise.ts +++ b/src/api/core/CancelablePromise.ts @@ -3,129 +3,128 @@ /* tslint:disable */ /* eslint-disable */ export class CancelError extends Error { - - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } + constructor(message: string) { + super(message) + this.name = 'CancelError' + } + + public get isCancelled(): boolean { + return true + } } export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; + readonly isResolved: boolean + readonly isRejected: boolean + readonly isCancelled: boolean - (cancelHandler: () => void): void; + (cancelHandler: () => void): void } export class CancelablePromise<T> implements Promise<T> { - #isResolved: boolean; - #isRejected: boolean; - #isCancelled: boolean; - readonly #cancelHandlers: (() => void)[]; - readonly #promise: Promise<T>; - #resolve?: (value: T | PromiseLike<T>) => void; - #reject?: (reason?: any) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike<T>) => void, - reject: (reason?: any) => void, - onCancel: OnCancel - ) => void - ) { - this.#isResolved = false; - this.#isRejected = false; - this.#isCancelled = false; - this.#cancelHandlers = []; - this.#promise = new Promise<T>((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - - const onResolve = (value: T | PromiseLike<T>): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isResolved = true; - if (this.#resolve) this.#resolve(value); - }; - - const onReject = (reason?: any): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isRejected = true; - if (this.#reject) this.#reject(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this.#isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this.#isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this.#isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then<TResult1 = T, TResult2 = never>( - onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, - onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null - ): Promise<TResult1 | TResult2> { - return this.#promise.then(onFulfilled, onRejected); - } - - public catch<TResult = never>( - onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null - ): Promise<T | TResult> { - return this.#promise.catch(onRejected); - } + #isResolved: boolean + #isRejected: boolean + #isCancelled: boolean + readonly #cancelHandlers: (() => void)[] + readonly #promise: Promise<T> + #resolve?: (value: T | PromiseLike<T>) => void + #reject?: (reason?: any) => void + + constructor( + executor: ( + resolve: (value: T | PromiseLike<T>) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false + this.#isRejected = false + this.#isCancelled = false + this.#cancelHandlers = [] + this.#promise = new Promise<T>((resolve, reject) => { + this.#resolve = resolve + this.#reject = reject + + const onResolve = (value: T | PromiseLike<T>): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return + } + this.#isResolved = true + if (this.#resolve) this.#resolve(value) + } - public finally(onFinally?: (() => void) | null): Promise<T> { - return this.#promise.finally(onFinally); - } + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return + } + this.#isRejected = true + if (this.#reject) this.#reject(reason) + } - public cancel(): void { + const onCancel = (cancelHandler: () => void): void => { if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; + return } - this.#isCancelled = true; - if (this.#cancelHandlers.length) { - try { - for (const cancelHandler of this.#cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } + this.#cancelHandlers.push(cancelHandler) + } + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this.#isResolved + }) + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this.#isRejected + }) + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled + }) + + return executor(onResolve, onReject, onCancel as OnCancel) + }) + } + + get [Symbol.toStringTag]() { + return 'Cancellable Promise' + } + + public then<TResult1 = T, TResult2 = never>( + onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null + ): Promise<TResult1 | TResult2> { + return this.#promise.then(onFulfilled, onRejected) + } + + public catch<TResult = never>( + onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null + ): Promise<T | TResult> { + return this.#promise.catch(onRejected) + } + + public finally(onFinally?: (() => void) | null): Promise<T> { + return this.#promise.finally(onFinally) + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return + } + this.#isCancelled = true + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler() } - this.#cancelHandlers.length = 0; - if (this.#reject) this.#reject(new CancelError('Request aborted')); + } catch (error) { + console.warn('Cancellation threw an error', error) + return + } } + this.#cancelHandlers.length = 0 + if (this.#reject) this.#reject(new CancelError('Request aborted')) + } - public get isCancelled(): boolean { - return this.#isCancelled; - } + public get isCancelled(): boolean { + return this.#isCancelled + } } diff --git a/src/api/core/OpenAPI.ts b/src/api/core/OpenAPI.ts index b33daf25d0e3518d35dc91aac288e991b8a7c160..594630592c7ea1a30285f6caec91160c9bf0b61f 100644 --- a/src/api/core/OpenAPI.ts +++ b/src/api/core/OpenAPI.ts @@ -2,31 +2,31 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiRequestOptions } from './ApiRequestOptions' -type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; -type Headers = Record<string, string>; +type Resolver<T> = (options: ApiRequestOptions) => Promise<T> +type Headers = Record<string, string> export type OpenAPIConfig = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - TOKEN?: string | Resolver<string> | undefined; - USERNAME?: string | Resolver<string> | undefined; - PASSWORD?: string | Resolver<string> | undefined; - HEADERS?: Headers | Resolver<Headers> | undefined; - ENCODE_PATH?: ((path: string) => string) | undefined; -}; + BASE: string + VERSION: string + WITH_CREDENTIALS: boolean + CREDENTIALS: 'include' | 'omit' | 'same-origin' + TOKEN?: string | Resolver<string> | undefined + USERNAME?: string | Resolver<string> | undefined + PASSWORD?: string | Resolver<string> | undefined + HEADERS?: Headers | Resolver<Headers> | undefined + ENCODE_PATH?: ((path: string) => string) | undefined +} export const OpenAPI: OpenAPIConfig = { - BASE: import.meta.env.VITE_APP_API_URL, - VERSION: '3.0', - WITH_CREDENTIALS: false, - CREDENTIALS: 'include', - TOKEN: undefined, - USERNAME: undefined, - PASSWORD: undefined, - HEADERS: undefined, - ENCODE_PATH: undefined, -}; + BASE: import.meta.env.VITE_APP_API_URL, + VERSION: '3.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined +} diff --git a/src/api/core/request.ts b/src/api/core/request.ts index 1dc6fef4aab4086ff57b48d26b20ec26bb8fa472..e8021a7ea04b0cf1719f543162b77401e2541e5d 100644 --- a/src/api/core/request.ts +++ b/src/api/core/request.ts @@ -2,286 +2,303 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import axios from 'axios'; -import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; -import FormData from 'form-data'; - -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; - -export const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => { - return value !== undefined && value !== null; -}; +import axios from 'axios' +import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios' +import FormData from 'form-data' + +import { ApiError } from './ApiError' +import type { ApiRequestOptions } from './ApiRequestOptions' +import type { ApiResult } from './ApiResult' +import { CancelablePromise } from './CancelablePromise' +import type { OnCancel } from './CancelablePromise' +import type { OpenAPIConfig } from './OpenAPI' + +export const isDefined = <T>( + value: T | null | undefined +): value is Exclude<T, null | undefined> => { + return value !== undefined && value !== null +} export const isString = (value: any): value is string => { - return typeof value === 'string'; -}; + return typeof value === 'string' +} export const isStringWithValue = (value: any): value is string => { - return isString(value) && value !== ''; -}; + return isString(value) && value !== '' +} export const isBlob = (value: any): value is Blob => { - return ( - typeof value === 'object' && - typeof value.type === 'string' && - typeof value.stream === 'function' && - typeof value.arrayBuffer === 'function' && - typeof value.constructor === 'function' && - typeof value.constructor.name === 'string' && - /^(Blob|File)$/.test(value.constructor.name) && - /^(Blob|File)$/.test(value[Symbol.toStringTag]) - ); -}; + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ) +} export const isFormData = (value: any): value is FormData => { - return value instanceof FormData; -}; + return value instanceof FormData +} export const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; + return status >= 200 && status < 300 +} export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; + try { + return btoa(str) + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64') + } +} export const getQueryString = (params: Record<string, any>): string => { - const qs: string[] = []; - - const append = (key: string, value: any) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const process = (key: string, value: any) => { - if (isDefined(value)) { - if (Array.isArray(value)) { - value.forEach(v => { - process(key, v); - }); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => { - process(`${key}[${k}]`, v); - }); - } else { - append(key, value); - } - } - }; - - Object.entries(params).forEach(([key, value]) => { - process(key, value); - }); - - if (qs.length > 0) { - return `?${qs.join('&')}`; + const qs: string[] = [] + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + } + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach((v) => { + process(key, v) + }) + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v) + }) + } else { + append(key, value) + } } + } - return ''; -}; + Object.entries(params).forEach(([key, value]) => { + process(key, value) + }) -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = `${config.BASE}${path}`; - if (options.query) { - return `${url}${getQueryString(options.query)}`; - } - return url; -}; + if (qs.length > 0) { + return `?${qs.join('&')}` + } -export const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: any) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([_, value]) => isDefined(value)) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(v => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; + return '' +} -export const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => { - if (typeof resolver === 'function') { - return (resolver as Resolver<T>)(options); - } - return resolver; -}; - -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => { - const [token, username, password, additionalHeaders] = await Promise.all([ - resolve(options, config.TOKEN), - resolve(options, config.USERNAME), - resolve(options, config.PASSWORD), - resolve(options, config.HEADERS), - ]); - - const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - ...formHeaders, +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])) + } + return substring }) - .filter(([_, value]) => isDefined(value)) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record<string, string>); - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } + const url = `${config.BASE}${path}` + if (options.query) { + return `${url}${getQueryString(options.query)}` + } + return url +} - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData() + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value) + } else { + formData.append(key, JSON.stringify(value)) + } } - if (options.body !== undefined) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => process(key, v)) + } else { + process(key, value) } + }) + + return formData + } + return undefined +} + +type Resolver<T> = (options: ApiRequestOptions) => Promise<T> + +export const resolve = async <T>( + options: ApiRequestOptions, + resolver?: T | Resolver<T> +): Promise<T | undefined> => { + if (typeof resolver === 'function') { + return (resolver as Resolver<T>)(options) + } + return resolver +} + +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + formData?: FormData +): Promise<Record<string, string>> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS) + ]) + + const formHeaders = (typeof formData?.getHeaders === 'function' && formData?.getHeaders()) || {} + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + ...formHeaders + }) + .filter(([_, value]) => isDefined(value)) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value) + }), + {} as Record<string, string> + ) + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}` + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`) + headers['Authorization'] = `Basic ${credentials}` + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream' + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain' + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json' } + } - return headers; -}; + return headers +} export const getRequestBody = (options: ApiRequestOptions): any => { - if (options.body) { - return options.body; - } - return undefined; -}; + if (options.body) { + return options.body + } + return undefined +} export const sendRequest = async <T>( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Record<string, string>, - onCancel: OnCancel, - axiosClient: AxiosInstance + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record<string, string>, + onCancel: OnCancel, + axiosClient: AxiosInstance ): Promise<AxiosResponse<T>> => { - const source = axios.CancelToken.source(); - - const requestConfig: AxiosRequestConfig = { - url, - headers, - data: body ?? formData, - method: options.method, - withCredentials: config.WITH_CREDENTIALS, - withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false, - cancelToken: source.token, - }; - - onCancel(() => source.cancel('The user aborted a request.')); - - try { - return await axiosClient.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError<T>; - if (axiosError.response) { - return axiosError.response; - } - throw error; + const source = axios.CancelToken.source() + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false, + cancelToken: source.token + } + + onCancel(() => source.cancel('The user aborted a request.')) + + try { + return await axiosClient.request(requestConfig) + } catch (error) { + const axiosError = error as AxiosError<T> + if (axiosError.response) { + return axiosError.response } -}; - -export const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } + throw error + } +} + +export const getResponseHeader = ( + response: AxiosResponse<any>, + responseHeader?: string +): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader] + if (isString(content)) { + return content } - return undefined; -}; + } + return undefined +} export const getResponseBody = (response: AxiosResponse<any>): any => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; + if (response.status !== 204) { + return response.data + } + return undefined +} export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { - const errors: Record<number, string> = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 500: 'Internal Server Error', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - ...options.errors, - } - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? 'unknown'; - const errorStatusText = result.statusText ?? 'unknown'; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError(options, result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` - ); - } -}; + const errors: Record<number, string> = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors + } + + const error = errors[result.status] + if (error) { + throw new ApiError(options, result, error) + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown' + const errorStatusText = result.statusText ?? 'unknown' + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2) + } catch (e) { + return undefined + } + })() + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ) + } +} /** * Request method @@ -291,33 +308,46 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): * @returns CancelablePromise<T> * @throws ApiError */ -export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise<T> => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options, formData); - - if (!onCancel.isCancelled) { - const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); +export const request = <T>( + config: OpenAPIConfig, + options: ApiRequestOptions, + axiosClient: AxiosInstance = axios +): CancelablePromise<T> => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options) + const formData = getFormData(options) + const body = getRequestBody(options) + const headers = await getHeaders(config, options, formData) + + if (!onCancel.isCancelled) { + const response = await sendRequest<T>( + config, + options, + url, + body, + formData, + headers, + onCancel, + axiosClient + ) + const responseBody = getResponseBody(response) + const responseHeader = getResponseHeader(response, options.responseHeader) + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody } - }); -}; + + catchErrorCodes(options, result) + + resolve(result.body) + } + } catch (error) { + reject(error) + } + }) +} diff --git a/src/api/index.ts b/src/api/index.ts index 6eac6e3b97685b4ddc4f788b6f48f939dfa831e5..c1b56696f0cf93ee8b0f24ab243e5204150a79e3 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,62 +2,62 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; +export { ApiError } from './core/ApiError' +export { CancelablePromise, CancelError } from './core/CancelablePromise' +export { OpenAPI } from './core/OpenAPI' +export type { OpenAPIConfig } from './core/OpenAPI' -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 { BalanceDTO } from './models/BalanceDTO'; -export type { BankIDRequest } from './models/BankIDRequest'; -export type { BankProfile } from './models/BankProfile'; -export type { BankProfileDTO } from './models/BankProfileDTO'; -export type { BankProfileResponseDTO } from './models/BankProfileResponseDTO'; -export type { BudgetRequestDTO } from './models/BudgetRequestDTO'; -export type { BudgetResponseDTO } from './models/BudgetResponseDTO'; -export type { ChallengeDTO } from './models/ChallengeDTO'; -export { ChallengeTemplateDTO } from './models/ChallengeTemplateDTO'; -export type { ConfigurationDTO } from './models/ConfigurationDTO'; -export type { CreateGoalDTO } from './models/CreateGoalDTO'; -export type { ExceptionResponse } from './models/ExceptionResponse'; -export type { ExpenseRequestDTO } from './models/ExpenseRequestDTO'; -export type { ExpenseResponseDTO } from './models/ExpenseResponseDTO'; -export type { FeedbackRequestDTO } from './models/FeedbackRequestDTO'; -export type { FeedbackResponseDTO } from './models/FeedbackResponseDTO'; -export type { GoalDTO } from './models/GoalDTO'; -export type { InventoryDTO } from './models/InventoryDTO'; -export type { ItemDTO } from './models/ItemDTO'; -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 { PointDTO } from './models/PointDTO'; -export type { ProfileDTO } from './models/ProfileDTO'; -export type { ProgressDTO } from './models/ProgressDTO'; -export type { SignUpRequest } from './models/SignUpRequest'; -export type { StreakDTO } from './models/StreakDTO'; -export type { TransactionDTO } from './models/TransactionDTO'; -export type { UserDTO } from './models/UserDTO'; -export type { UserUpdateDTO } from './models/UserUpdateDTO'; +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 { BalanceDTO } from './models/BalanceDTO' +export type { BankIDRequest } from './models/BankIDRequest' +export type { BankProfile } from './models/BankProfile' +export type { BankProfileDTO } from './models/BankProfileDTO' +export type { BankProfileResponseDTO } from './models/BankProfileResponseDTO' +export type { BudgetRequestDTO } from './models/BudgetRequestDTO' +export type { BudgetResponseDTO } from './models/BudgetResponseDTO' +export type { ChallengeDTO } from './models/ChallengeDTO' +export { ChallengeTemplateDTO } from './models/ChallengeTemplateDTO' +export type { ConfigurationDTO } from './models/ConfigurationDTO' +export type { CreateGoalDTO } from './models/CreateGoalDTO' +export type { ExceptionResponse } from './models/ExceptionResponse' +export type { ExpenseRequestDTO } from './models/ExpenseRequestDTO' +export type { ExpenseResponseDTO } from './models/ExpenseResponseDTO' +export type { FeedbackRequestDTO } from './models/FeedbackRequestDTO' +export type { FeedbackResponseDTO } from './models/FeedbackResponseDTO' +export type { GoalDTO } from './models/GoalDTO' +export type { InventoryDTO } from './models/InventoryDTO' +export type { ItemDTO } from './models/ItemDTO' +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 { PointDTO } from './models/PointDTO' +export type { ProfileDTO } from './models/ProfileDTO' +export type { ProgressDTO } from './models/ProgressDTO' +export type { SignUpRequest } from './models/SignUpRequest' +export type { StreakDTO } from './models/StreakDTO' +export type { TransactionDTO } from './models/TransactionDTO' +export type { UserDTO } from './models/UserDTO' +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'; -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'; +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' +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/Account.ts b/src/api/models/Account.ts index fb3a63221139e5c22defbd906d3017d6286435ab..32f1ae9bdccc01cd30588633a18efced1748dbae 100644 --- a/src/api/models/Account.ts +++ b/src/api/models/Account.ts @@ -2,10 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { BankProfile } from './BankProfile'; +import type { BankProfile } from './BankProfile' export type Account = { - bban?: number; - balance?: number; - bankProfile?: BankProfile; -}; - + bban?: number + balance?: number + bankProfile?: BankProfile +} diff --git a/src/api/models/AccountRequestDTO.ts b/src/api/models/AccountRequestDTO.ts index c2f7ddd34fb91f531ac004c517213fcd31dd2c0d..80121b43bbfe5bf84e0a494f34816bbe03d0ad30 100644 --- a/src/api/models/AccountRequestDTO.ts +++ b/src/api/models/AccountRequestDTO.ts @@ -3,6 +3,5 @@ /* tslint:disable */ /* eslint-disable */ export type AccountRequestDTO = { - ssn?: number; -}; - + ssn?: number +} diff --git a/src/api/models/AccountResponseDTO.ts b/src/api/models/AccountResponseDTO.ts index 276a2131b75b4c6dd96beb6eae76e448f0a07bb6..0c784736652d2798644f355e0951bf70d30f0174 100644 --- a/src/api/models/AccountResponseDTO.ts +++ b/src/api/models/AccountResponseDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type AccountResponseDTO = { - bankProfileId?: number; - balance?: number; -}; - + bankProfileId?: number + balance?: number +} diff --git a/src/api/models/AuthenticationResponse.ts b/src/api/models/AuthenticationResponse.ts index 3cbef78cec18fdd088dc5349ec155fdfc9ae8b02..c806f0bddf632851607921a82e05e486a7a40ecf 100644 --- a/src/api/models/AuthenticationResponse.ts +++ b/src/api/models/AuthenticationResponse.ts @@ -3,12 +3,11 @@ /* tslint:disable */ /* eslint-disable */ export type AuthenticationResponse = { - firstName?: string; - lastName?: string; - userId?: number; - profileImage?: number; - role?: string; - subscriptionLevel?: string; - token?: string; -}; - + firstName?: string + lastName?: string + userId?: number + profileImage?: number + role?: string + subscriptionLevel?: string + token?: string +} diff --git a/src/api/models/BadgeDTO.ts b/src/api/models/BadgeDTO.ts index 9fd3d054da77c4e8adcef24d0a2853b0bba641f5..3329209c183def491f4216cab66edcd7584f3684 100644 --- a/src/api/models/BadgeDTO.ts +++ b/src/api/models/BadgeDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type BadgeDTO = { - id?: number; - badgeName?: string; - criteria?: number; - imageId?: number; -}; - + id?: number + badgeName?: string + criteria?: number + imageId?: number +} diff --git a/src/api/models/BalanceDTO.ts b/src/api/models/BalanceDTO.ts index 9d400734e966952ab4492239c3977e4d7a434edb..0aa6ec80ef4bb2291e2dea205a9450ffbb12a87a 100644 --- a/src/api/models/BalanceDTO.ts +++ b/src/api/models/BalanceDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type BalanceDTO = { - bban?: number; - balance?: number; -}; - + bban?: number + balance?: number +} diff --git a/src/api/models/BankIDRequest.ts b/src/api/models/BankIDRequest.ts index 225dc8bb202284c5438e1a0f8d01bdd9d85a2231..d65cf8b4225aee97864dc7f145a75c9cde3e645b 100644 --- a/src/api/models/BankIDRequest.ts +++ b/src/api/models/BankIDRequest.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type BankIDRequest = { - code?: string; - state?: string; -}; - + code?: string + state?: string +} diff --git a/src/api/models/BankProfile.ts b/src/api/models/BankProfile.ts index b4fe99e6921234a056658b64a4b0ea6ec5e33f69..e55f97171f94df85e662ef0502d448e61cfe18a1 100644 --- a/src/api/models/BankProfile.ts +++ b/src/api/models/BankProfile.ts @@ -2,10 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Account } from './Account'; +import type { Account } from './Account' export type BankProfile = { - id?: number; - ssn?: number; - accounts?: Array<Account>; -}; - + id?: number + ssn?: number + accounts?: Array<Account> +} diff --git a/src/api/models/BankProfileDTO.ts b/src/api/models/BankProfileDTO.ts index e6b1b89cb3c01a69bb0276e169f93d7e5787c5a7..68abd309d66f36bf8d83a7ad911d519c5b445474 100644 --- a/src/api/models/BankProfileDTO.ts +++ b/src/api/models/BankProfileDTO.ts @@ -3,6 +3,5 @@ /* tslint:disable */ /* eslint-disable */ export type BankProfileDTO = { - ssn?: number; -}; - + ssn?: number +} diff --git a/src/api/models/BankProfileResponseDTO.ts b/src/api/models/BankProfileResponseDTO.ts index 3ef2360b3463e66b704ff1b587f7598b4efba5dd..cb22614eeddcbd6a063b3be4a50bee43092a5d90 100644 --- a/src/api/models/BankProfileResponseDTO.ts +++ b/src/api/models/BankProfileResponseDTO.ts @@ -2,9 +2,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Account } from './Account'; +import type { Account } from './Account' export type BankProfileResponseDTO = { - ssn?: number; - accounts?: Array<Account>; -}; - + ssn?: number + accounts?: Array<Account> +} diff --git a/src/api/models/BudgetRequestDTO.ts b/src/api/models/BudgetRequestDTO.ts index d4800b3b9b4ad2d2f2ce04cc6ea2d2bedf1cd76e..241b78dde85e68052814355d070c6328eee399cb 100644 --- a/src/api/models/BudgetRequestDTO.ts +++ b/src/api/models/BudgetRequestDTO.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type BudgetRequestDTO = { - budgetName?: string; - budgetAmount?: number; - expenseAmount?: number; -}; - + budgetName?: string + budgetAmount?: number + expenseAmount?: number +} diff --git a/src/api/models/BudgetResponseDTO.ts b/src/api/models/BudgetResponseDTO.ts index 04a579845fbac1129b9d23bde7d31ed661ee5465..c7bc482f637679b3ba056630c843d05ac8a08bbd 100644 --- a/src/api/models/BudgetResponseDTO.ts +++ b/src/api/models/BudgetResponseDTO.ts @@ -3,10 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type BudgetResponseDTO = { - id?: number; - budgetName?: string; - budgetAmount?: number; - expenseAmount?: number; - createdAt?: string; -}; - + id?: number + budgetName?: string + budgetAmount?: number + expenseAmount?: number + createdAt?: string +} diff --git a/src/api/models/ChallengeDTO.ts b/src/api/models/ChallengeDTO.ts index 3ccf0f055c0ffb92c2f2612a02aaf5e25d3c856b..ce9106931cefbcb59de23444705d945c3d3daa47 100644 --- a/src/api/models/ChallengeDTO.ts +++ b/src/api/models/ChallengeDTO.ts @@ -2,17 +2,16 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ChallengeTemplateDTO } from './ChallengeTemplateDTO'; -import type { ProgressDTO } from './ProgressDTO'; +import type { ChallengeTemplateDTO } from './ChallengeTemplateDTO' +import type { ProgressDTO } from './ProgressDTO' export type ChallengeDTO = { - id?: number; - amount?: number; - points?: number; - checkDays?: number; - totalDays?: number; - startDate?: string; - endDate?: string; - challengeTemplate?: ChallengeTemplateDTO; - progressList?: Array<ProgressDTO>; -}; - + id?: number + amount?: number + points?: number + checkDays?: number + totalDays?: number + startDate?: string + endDate?: string + challengeTemplate?: ChallengeTemplateDTO + progressList?: Array<ProgressDTO> +} diff --git a/src/api/models/ChallengeTemplateDTO.ts b/src/api/models/ChallengeTemplateDTO.ts index 286b0fc6fd32f7a9e313deceaf38f412f0e071ef..1b8ccd4b7e0b274ea0d6b3dff0f70f56c0fd8c42 100644 --- a/src/api/models/ChallengeTemplateDTO.ts +++ b/src/api/models/ChallengeTemplateDTO.ts @@ -3,30 +3,29 @@ /* tslint:disable */ /* eslint-disable */ export type ChallengeTemplateDTO = { - id?: number; - templateName?: string; - text?: string; - amount?: number; - type?: ChallengeTemplateDTO.type; -}; + id?: number + templateName?: string + text?: string + amount?: number + type?: ChallengeTemplateDTO.type +} export namespace ChallengeTemplateDTO { - export enum type { - NO_COFFEE = 'NO_COFFEE', - NO_CAR = 'NO_CAR', - SHORTER_SHOWER = 'SHORTER_SHOWER', - SPEND_LESS_ON_FOOD = 'SPEND_LESS_ON_FOOD', - BUY_USED_CLOTHES = 'BUY_USED_CLOTHES', - LESS_SHOPPING = 'LESS_SHOPPING', - DROP_SUBSCRIPTION = 'DROP_SUBSCRIPTION', - SELL_SOMETHING = 'SELL_SOMETHING', - BUY_USED = 'BUY_USED', - EAT_PACKED_LUNCH = 'EAT_PACKED_LUNCH', - STOP_SHOPPING = 'STOP_SHOPPING', - ZERO_SPENDING = 'ZERO_SPENDING', - RENT_YOUR_STUFF = 'RENT_YOUR_STUFF', - MEATLESS = 'MEATLESS', - SCREEN_TIME_LIMIT = 'SCREEN_TIME_LIMIT', - UNPLUGGED_ENTERTAINMENT = 'UNPLUGGED_ENTERTAINMENT', - } + export enum type { + NO_COFFEE = 'NO_COFFEE', + NO_CAR = 'NO_CAR', + SHORTER_SHOWER = 'SHORTER_SHOWER', + SPEND_LESS_ON_FOOD = 'SPEND_LESS_ON_FOOD', + BUY_USED_CLOTHES = 'BUY_USED_CLOTHES', + LESS_SHOPPING = 'LESS_SHOPPING', + DROP_SUBSCRIPTION = 'DROP_SUBSCRIPTION', + SELL_SOMETHING = 'SELL_SOMETHING', + BUY_USED = 'BUY_USED', + EAT_PACKED_LUNCH = 'EAT_PACKED_LUNCH', + STOP_SHOPPING = 'STOP_SHOPPING', + ZERO_SPENDING = 'ZERO_SPENDING', + RENT_YOUR_STUFF = 'RENT_YOUR_STUFF', + MEATLESS = 'MEATLESS', + SCREEN_TIME_LIMIT = 'SCREEN_TIME_LIMIT', + UNPLUGGED_ENTERTAINMENT = 'UNPLUGGED_ENTERTAINMENT' + } } - diff --git a/src/api/models/ConfigurationDTO.ts b/src/api/models/ConfigurationDTO.ts index 4d4b45261998b7595857854df32b24269b026d4f..252fe3fc753b0b22711518d083b843347df3d60e 100644 --- a/src/api/models/ConfigurationDTO.ts +++ b/src/api/models/ConfigurationDTO.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type ConfigurationDTO = { - commitment?: string; - experience?: string; - challengeTypes?: Array<string>; -}; - + commitment?: string + experience?: string + challengeTypes?: Array<string> +} diff --git a/src/api/models/CreateGoalDTO.ts b/src/api/models/CreateGoalDTO.ts index 2e6e535f0b0a2317ef26e76c8db8f53572cbcd8d..993e437539b90c071b615f1e99ef30de30564ff8 100644 --- a/src/api/models/CreateGoalDTO.ts +++ b/src/api/models/CreateGoalDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type CreateGoalDTO = { - name?: string; - description?: string; - targetAmount?: number; - targetDate?: string; -}; - + name?: string + description?: string + targetAmount?: number + targetDate?: string +} diff --git a/src/api/models/ExceptionResponse.ts b/src/api/models/ExceptionResponse.ts index 12c0a7873d7d38f6782e970737603521971c9c81..3f5ae65f6a47e9ad657cb4a25769ee861f85df97 100644 --- a/src/api/models/ExceptionResponse.ts +++ b/src/api/models/ExceptionResponse.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type ExceptionResponse = { - status?: number; - message?: string; -}; - + status?: number + message?: string +} diff --git a/src/api/models/ExpenseRequestDTO.ts b/src/api/models/ExpenseRequestDTO.ts index c4337af1a3b5617e25cff23b80f256957d9fc22b..cd72cdbfa04c8746f6864f075a8e8d88f4c115bb 100644 --- a/src/api/models/ExpenseRequestDTO.ts +++ b/src/api/models/ExpenseRequestDTO.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type ExpenseRequestDTO = { - expenseId?: number; - description?: string; - amount?: number; -}; - + expenseId?: number + description?: string + amount?: number +} diff --git a/src/api/models/ExpenseResponseDTO.ts b/src/api/models/ExpenseResponseDTO.ts index 35bcc0e0b2bc582aa8fb5c9bcd7756d9a213d1ce..2e2ee3c2432820297cb33bc3a5775c58eb11d2af 100644 --- a/src/api/models/ExpenseResponseDTO.ts +++ b/src/api/models/ExpenseResponseDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type ExpenseResponseDTO = { - expenseId?: number; - budgetId?: number; - description?: string; - amount?: string; -}; - + expenseId?: number + budgetId?: number + description?: string + amount?: string +} diff --git a/src/api/models/FeedbackRequestDTO.ts b/src/api/models/FeedbackRequestDTO.ts index 174e9eff582bc5a724980e15fcb6341a50d4acaa..84c2e4e73bdf0d36f6bc489ac2b4c9ad6833893b 100644 --- a/src/api/models/FeedbackRequestDTO.ts +++ b/src/api/models/FeedbackRequestDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type FeedbackRequestDTO = { - email?: string; - message?: string; -}; - + email?: string + message?: string +} diff --git a/src/api/models/FeedbackResponseDTO.ts b/src/api/models/FeedbackResponseDTO.ts index 7b249002ad1e5b596541e138c7ac6e34eef093f0..13c933f59a7197e8450cf9b2b62e7b57ca251a6f 100644 --- a/src/api/models/FeedbackResponseDTO.ts +++ b/src/api/models/FeedbackResponseDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type FeedbackResponseDTO = { - id?: string; - email?: string; - message?: string; - createdAt?: string; -}; - + id?: string + email?: string + message?: string + createdAt?: string +} diff --git a/src/api/models/GoalDTO.ts b/src/api/models/GoalDTO.ts index c24e9abfb1d37acc49db64803d4da80a901960ce..b558edf4cdb34f1eef08bdf351335c49060b6a9d 100644 --- a/src/api/models/GoalDTO.ts +++ b/src/api/models/GoalDTO.ts @@ -2,16 +2,15 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ChallengeDTO } from './ChallengeDTO'; -import type { UserDTO } from './UserDTO'; +import type { ChallengeDTO } from './ChallengeDTO' +import type { UserDTO } from './UserDTO' export type GoalDTO = { - id?: number; - name?: string; - description?: string; - targetAmount?: number; - targetDate?: string; - createdAt?: string; - challenges?: Array<ChallengeDTO>; - user?: UserDTO; -}; - + id?: number + name?: string + description?: string + targetAmount?: number + targetDate?: string + createdAt?: string + challenges?: Array<ChallengeDTO> + user?: UserDTO +} diff --git a/src/api/models/InventoryDTO.ts b/src/api/models/InventoryDTO.ts index 826a6b61da10137a07982263be585d3be2571bfa..99ee1f373837399fff5b01d475337920ffac6029 100644 --- a/src/api/models/InventoryDTO.ts +++ b/src/api/models/InventoryDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type InventoryDTO = { - id?: number; - itemName?: string; - imageId?: number; - boughtAt?: string; -}; - + id?: number + itemName?: string + imageId?: number + boughtAt?: string +} diff --git a/src/api/models/ItemDTO.ts b/src/api/models/ItemDTO.ts index b2c65c3135bb7307dd11c25a5c4defe7781af209..e6cdba3179ad90b24f85d084cf843981cb820d64 100644 --- a/src/api/models/ItemDTO.ts +++ b/src/api/models/ItemDTO.ts @@ -3,10 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type ItemDTO = { - id?: number; - itemName?: string; - price?: number; - imageId?: number; - alreadyBought?: boolean; -}; - + id?: number + itemName?: string + price?: number + imageId?: number + alreadyBought?: boolean +} diff --git a/src/api/models/LeaderboardDTO.ts b/src/api/models/LeaderboardDTO.ts index 58bddb76f2fc2a4cfbf09ec19b8915ca5f7f3976..a50acf3f6c425b452da906921f8e81ac607fcc11 100644 --- a/src/api/models/LeaderboardDTO.ts +++ b/src/api/models/LeaderboardDTO.ts @@ -2,10 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { LeaderboardEntryDTO } from './LeaderboardEntryDTO'; +import type { LeaderboardEntryDTO } from './LeaderboardEntryDTO' export type LeaderboardDTO = { - type?: string; - filter?: string; - entries?: Array<LeaderboardEntryDTO>; -}; - + type?: string + filter?: string + entries?: Array<LeaderboardEntryDTO> +} diff --git a/src/api/models/LeaderboardEntryDTO.ts b/src/api/models/LeaderboardEntryDTO.ts index 6a7dc1d7c916baae5ea0b496ac69ef08792f4e98..8600a14014619f13cc18911b084dcbdb73a99a89 100644 --- a/src/api/models/LeaderboardEntryDTO.ts +++ b/src/api/models/LeaderboardEntryDTO.ts @@ -2,10 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { UserDTO } from './UserDTO'; +import type { UserDTO } from './UserDTO' export type LeaderboardEntryDTO = { - user?: UserDTO; - score?: number; - rank?: number; -}; - + user?: UserDTO + score?: number + rank?: number +} diff --git a/src/api/models/LoginRequest.ts b/src/api/models/LoginRequest.ts index 9c5def4f1a6a028911a9b4ca8a147fcc14f8015e..fadc1037d447014bcd220b0a703f742e2766a4f0 100644 --- a/src/api/models/LoginRequest.ts +++ b/src/api/models/LoginRequest.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type LoginRequest = { - email?: string; - password?: string; -}; - + email?: string + password?: string +} diff --git a/src/api/models/MarkChallengeDTO.ts b/src/api/models/MarkChallengeDTO.ts index 09c06b8a91f40448ceb2fe25867da13de7f713cb..5944200e316df0dc15c4d325632b2142aae9d6de 100644 --- a/src/api/models/MarkChallengeDTO.ts +++ b/src/api/models/MarkChallengeDTO.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type MarkChallengeDTO = { - id?: number; - day?: number; - amount?: number; -}; - + id?: number + day?: number + amount?: number +} diff --git a/src/api/models/NotificationDTO.ts b/src/api/models/NotificationDTO.ts index 2834bf8466bb776108d1054e1278c96a6ce69636..2634516a0a3cf81bfae56961984bf9cac0963874 100644 --- a/src/api/models/NotificationDTO.ts +++ b/src/api/models/NotificationDTO.ts @@ -3,17 +3,16 @@ /* tslint:disable */ /* eslint-disable */ export type NotificationDTO = { - id?: number; - message?: string; - unread?: boolean; - notificationType?: NotificationDTO.notificationType; - createdAt?: string; -}; + 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', - } + export enum notificationType { + BADGE = 'BADGE', + FRIEND_REQUEST = 'FRIEND_REQUEST', + COMPLETED_GOAL = 'COMPLETED_GOAL' + } } - diff --git a/src/api/models/PasswordResetDTO.ts b/src/api/models/PasswordResetDTO.ts index 383b7ad1f57b7f48dc891ec0f3f2a86a28d03f29..73d4aa124472f9fd581bbcdff4e714fd9fa95ac5 100644 --- a/src/api/models/PasswordResetDTO.ts +++ b/src/api/models/PasswordResetDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type PasswordResetDTO = { - token: string; - password?: string; -}; - + token: string + password?: string +} diff --git a/src/api/models/PasswordUpdateDTO.ts b/src/api/models/PasswordUpdateDTO.ts index 7a5085afa9a61c1b1494c034391cb4a758ed7e5d..3fb3201ea6cb3c257b84d2e3c683544ba82e3056 100644 --- a/src/api/models/PasswordUpdateDTO.ts +++ b/src/api/models/PasswordUpdateDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type PasswordUpdateDTO = { - oldPassword?: string; - newPassword?: string; -}; - + oldPassword?: string + newPassword?: string +} diff --git a/src/api/models/PointDTO.ts b/src/api/models/PointDTO.ts index a122a85f12c3ade9a3d68966565295ce1ddd8d38..df01702da1b658aab3e37dcc766fe1c664eb58ea 100644 --- a/src/api/models/PointDTO.ts +++ b/src/api/models/PointDTO.ts @@ -3,7 +3,6 @@ /* tslint:disable */ /* eslint-disable */ export type PointDTO = { - currentPoints?: number; - totalEarnedPoints?: number; -}; - + currentPoints?: number + totalEarnedPoints?: number +} diff --git a/src/api/models/ProfileDTO.ts b/src/api/models/ProfileDTO.ts index 7a621da3b05b3203bfb202cb74aa4ff9e95d954c..146103d0394860cd13751fcce5555fa1b6ca1785 100644 --- a/src/api/models/ProfileDTO.ts +++ b/src/api/models/ProfileDTO.ts @@ -2,16 +2,15 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { PointDTO } from './PointDTO'; -import type { StreakDTO } from './StreakDTO'; +import type { PointDTO } from './PointDTO' +import type { StreakDTO } from './StreakDTO' export type ProfileDTO = { - id?: number; - firstName?: string; - lastName?: string; - profileImage?: number; - bannerImage?: number; - createdAt?: string; - point?: PointDTO; - streak?: StreakDTO; -}; - + id?: number + firstName?: string + lastName?: string + profileImage?: number + bannerImage?: number + createdAt?: string + point?: PointDTO + streak?: StreakDTO +} diff --git a/src/api/models/ProgressDTO.ts b/src/api/models/ProgressDTO.ts index cb6f11746458912e9efef6438bb3278ff585c2d6..73652637ae4a402626086439b808bc8cefdf6d44 100644 --- a/src/api/models/ProgressDTO.ts +++ b/src/api/models/ProgressDTO.ts @@ -3,9 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type ProgressDTO = { - id?: number; - day?: number; - amount?: number; - completedAt?: string; -}; - + id?: number + day?: number + amount?: number + completedAt?: string +} diff --git a/src/api/models/SignUpRequest.ts b/src/api/models/SignUpRequest.ts index ae32d8fef4943a273dac83e3f72e48b759582779..1e6924892d8b5bbaa68d9732010db876a9870c4c 100644 --- a/src/api/models/SignUpRequest.ts +++ b/src/api/models/SignUpRequest.ts @@ -2,14 +2,13 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ConfigurationDTO } from './ConfigurationDTO'; +import type { ConfigurationDTO } from './ConfigurationDTO' export type SignUpRequest = { - firstName?: string; - lastName?: string; - email?: string; - password?: string; - checkingAccountBBAN?: number; - savingsAccountBBAN?: number; - configuration: ConfigurationDTO; -}; - + firstName?: string + lastName?: string + email?: string + password?: string + checkingAccountBBAN?: number + savingsAccountBBAN?: number + configuration: ConfigurationDTO +} diff --git a/src/api/models/StreakDTO.ts b/src/api/models/StreakDTO.ts index 07822694f7855d8d7dd631accaa57c71723ee7f9..319e7a88a8be97fa54dc2b5be49914ceef2fbe42 100644 --- a/src/api/models/StreakDTO.ts +++ b/src/api/models/StreakDTO.ts @@ -3,11 +3,10 @@ /* tslint:disable */ /* eslint-disable */ export type StreakDTO = { - currentStreak?: number; - currentStreakCreatedAt?: string; - currentStreakUpdatedAt?: string; - highestStreak?: number; - highestStreakCreatedAt?: string; - highestStreakEndedAt?: string; -}; - + currentStreak?: number + currentStreakCreatedAt?: string + currentStreakUpdatedAt?: string + highestStreak?: number + highestStreakCreatedAt?: string + highestStreakEndedAt?: string +} diff --git a/src/api/models/TransactionDTO.ts b/src/api/models/TransactionDTO.ts index d974cb53a8a01cb4c99043e2cb3a983a608d295f..5a8d81f393e14fcf640444afd9022bf75d40cb3b 100644 --- a/src/api/models/TransactionDTO.ts +++ b/src/api/models/TransactionDTO.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type TransactionDTO = { - debtorBBAN?: number; - creditorBBAN?: number; - amount?: number; -}; - + debtorBBAN?: number + creditorBBAN?: number + amount?: number +} diff --git a/src/api/models/UserDTO.ts b/src/api/models/UserDTO.ts index a94d14567640f5d9b30901361f5fd94097ce2f93..a98d104b220cb4fde9e2b71337dc3472e67bf54b 100644 --- a/src/api/models/UserDTO.ts +++ b/src/api/models/UserDTO.ts @@ -2,21 +2,20 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { PointDTO } from './PointDTO'; -import type { StreakDTO } from './StreakDTO'; +import type { PointDTO } from './PointDTO' +import type { StreakDTO } from './StreakDTO' export type UserDTO = { - id?: number; - firstName?: string; - lastName?: string; - profileImage?: number; - bannerImage?: number; - email?: string; - createdAt?: string; - role?: string; - subscriptionLevel?: string; - checkingAccountBBAN?: number; - savingsAccountBBAN?: number; - point?: PointDTO; - streak?: StreakDTO; -}; - + id?: number + firstName?: string + lastName?: string + profileImage?: number + bannerImage?: number + email?: string + createdAt?: string + role?: string + subscriptionLevel?: string + checkingAccountBBAN?: number + savingsAccountBBAN?: number + point?: PointDTO + streak?: StreakDTO +} diff --git a/src/api/models/UserUpdateDTO.ts b/src/api/models/UserUpdateDTO.ts index ea34828c404818efdad9904e2d7c6e29ae847591..fd41934867e5228d1a3dfd59c6c3750322200eb1 100644 --- a/src/api/models/UserUpdateDTO.ts +++ b/src/api/models/UserUpdateDTO.ts @@ -2,15 +2,14 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ConfigurationDTO } from './ConfigurationDTO'; +import type { ConfigurationDTO } from './ConfigurationDTO' export type UserUpdateDTO = { - firstName?: string; - lastName?: string; - email?: string; - profileImage?: number; - bannerImage?: number; - savingsAccountBBAN?: number; - checkingAccountBBAN?: number; - configuration?: ConfigurationDTO; -}; - + firstName?: string + lastName?: string + email?: string + profileImage?: number + bannerImage?: number + savingsAccountBBAN?: number + checkingAccountBBAN?: number + configuration?: ConfigurationDTO +} diff --git a/src/api/services/AccountControllerService.ts b/src/api/services/AccountControllerService.ts index 407586d5513b619bf165f827ec56ee730f42fb85..03000e9a085f388355fe120e812cef7b480aa239 100644 --- a/src/api/services/AccountControllerService.ts +++ b/src/api/services/AccountControllerService.ts @@ -2,99 +2,91 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Account } from '../models/Account'; -import type { AccountRequestDTO } from '../models/AccountRequestDTO'; -import type { AccountResponseDTO } from '../models/AccountResponseDTO'; -import type { BalanceDTO } from '../models/BalanceDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { Account } from '../models/Account' +import type { AccountRequestDTO } from '../models/AccountRequestDTO' +import type { AccountResponseDTO } from '../models/AccountResponseDTO' +import type { BalanceDTO } from '../models/BalanceDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class AccountControllerService { - /** - * Create account - * Create account with random balance - * @returns AccountResponseDTO Successfully created account - * @throws ApiError - */ - public static createAccount({ - requestBody, - }: { - requestBody: AccountRequestDTO, - }): CancelablePromise<AccountResponseDTO> { - return __request(OpenAPI, { - method: 'POST', - url: '/bank/v1/account/create-account', - body: requestBody, - mediaType: 'application/json', - errors: { - 404: `Provided bank profile id could not be found`, - }, - }); - } - /** - * Create account - * Create account with random balance - * @returns BalanceDTO Successfully created account - * @throws ApiError - */ - public static getAccountsByBban({ - bban, - }: { - bban: number, - }): CancelablePromise<BalanceDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/bank/v1/account/balance/{bban}', - path: { - 'bban': bban, - }, - errors: { - 404: `Provided bban could not be found`, - }, - }); - } - /** - * Get user accounts - * Get accounts associated with a user by providing their social security number - * @returns Account No accounts associated with a bank user - * @throws ApiError - */ - public static getAccountsBySsn({ - ssn, - }: { - ssn: number, - }): CancelablePromise<Array<Account>> { - return __request(OpenAPI, { - method: 'GET', - url: '/bank/v1/account/accounts/ssn/{ssn}', - path: { - 'ssn': ssn, - }, - errors: { - 404: `Social security number does not exist`, - }, - }); - } - /** - * Get user accounts - * Get accounts associated with a user by providing their bank profile id - * @returns Account No accounts associated with a bank user - * @throws ApiError - */ - public static getAccounts({ - bankProfileId, - }: { - bankProfileId: number, - }): CancelablePromise<Array<Account>> { - return __request(OpenAPI, { - method: 'GET', - url: '/bank/v1/account/accounts/profile/{bankProfileId}', - path: { - 'bankProfileId': bankProfileId, - }, - errors: { - 404: `Bank profile id does not exist`, - }, - }); - } + /** + * Create account + * Create account with random balance + * @returns AccountResponseDTO Successfully created account + * @throws ApiError + */ + public static createAccount({ + requestBody + }: { + requestBody: AccountRequestDTO + }): CancelablePromise<AccountResponseDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/account/create-account', + body: requestBody, + mediaType: 'application/json', + errors: { + 404: `Provided bank profile id could not be found` + } + }) + } + /** + * Create account + * Create account with random balance + * @returns BalanceDTO Successfully created account + * @throws ApiError + */ + public static getAccountsByBban({ bban }: { bban: number }): CancelablePromise<BalanceDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/bank/v1/account/balance/{bban}', + path: { + bban: bban + }, + errors: { + 404: `Provided bban could not be found` + } + }) + } + /** + * Get user accounts + * Get accounts associated with a user by providing their social security number + * @returns Account No accounts associated with a bank user + * @throws ApiError + */ + public static getAccountsBySsn({ ssn }: { ssn: number }): CancelablePromise<Array<Account>> { + return __request(OpenAPI, { + method: 'GET', + url: '/bank/v1/account/accounts/ssn/{ssn}', + path: { + ssn: ssn + }, + errors: { + 404: `Social security number does not exist` + } + }) + } + /** + * Get user accounts + * Get accounts associated with a user by providing their bank profile id + * @returns Account No accounts associated with a bank user + * @throws ApiError + */ + public static getAccounts({ + bankProfileId + }: { + bankProfileId: number + }): CancelablePromise<Array<Account>> { + return __request(OpenAPI, { + method: 'GET', + url: '/bank/v1/account/accounts/profile/{bankProfileId}', + path: { + bankProfileId: bankProfileId + }, + errors: { + 404: `Bank profile id does not exist` + } + }) + } } diff --git a/src/api/services/AuthenticationService.ts b/src/api/services/AuthenticationService.ts index ac619756c90377b2090d430cbcd47e746eb52af9..7b8a28595b3b415a58bf96547806337a3fca06b1 100644 --- a/src/api/services/AuthenticationService.ts +++ b/src/api/services/AuthenticationService.ts @@ -2,95 +2,95 @@ /* istanbul ignore file */ /* 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'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +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' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class AuthenticationService { - /** - * Validate email - * Check that the given email is valid - * @returns any Email is valid - * @throws ApiError - */ - public static validateEmail({ - email, - }: { - email: string, - }): CancelablePromise<Record<string, any>> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/auth/valid-email/{email}', - path: { - 'email': email, - }, - errors: { - 409: `Email already exists`, - }, - }); - } - /** - * User Signup - * Sign up a new user - * @returns AuthenticationResponse Successfully signed up - * @throws ApiError - */ - public static signup({ - requestBody, - }: { - requestBody: SignUpRequest, - }): CancelablePromise<AuthenticationResponse> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/auth/signup', - body: requestBody, - mediaType: 'application/json', - errors: { - 409: `Email already exists`, - }, - }); - } - /** - * User Login - * Log in with an existing user - * @returns AuthenticationResponse Successfully logged in - * @throws ApiError - */ - public static login({ - requestBody, - }: { - requestBody: LoginRequest, - }): CancelablePromise<AuthenticationResponse> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/auth/login', - body: requestBody, - mediaType: 'application/json', - errors: { - 401: `Invalid credentials`, - 404: `User not found`, - }, - }); - } - /** - * 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', - }); - } + /** + * Validate email + * Check that the given email is valid + * @returns any Email is valid + * @throws ApiError + */ + public static validateEmail({ + email + }: { + email: string + }): CancelablePromise<Record<string, any>> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/auth/valid-email/{email}', + path: { + email: email + }, + errors: { + 409: `Email already exists` + } + }) + } + /** + * User Signup + * Sign up a new user + * @returns AuthenticationResponse Successfully signed up + * @throws ApiError + */ + public static signup({ + requestBody + }: { + requestBody: SignUpRequest + }): CancelablePromise<AuthenticationResponse> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/auth/signup', + body: requestBody, + mediaType: 'application/json', + errors: { + 409: `Email already exists` + } + }) + } + /** + * User Login + * Log in with an existing user + * @returns AuthenticationResponse Successfully logged in + * @throws ApiError + */ + public static login({ + requestBody + }: { + requestBody: LoginRequest + }): CancelablePromise<AuthenticationResponse> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/auth/login', + body: requestBody, + mediaType: 'application/json', + errors: { + 401: `Invalid credentials`, + 404: `User not found` + } + }) + } + /** + * 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/BadgeService.ts b/src/api/services/BadgeService.ts index 4c4bee2bdc45ba6160400b1d111085fe557cca77..65b0125b605070694cf6f9aaa0f2876285c130c5 100644 --- a/src/api/services/BadgeService.ts +++ b/src/api/services/BadgeService.ts @@ -2,98 +2,94 @@ /* 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'; +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 getBadgesUnlockedByActiveUser(): CancelablePromise<Array<BadgeDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/badge/unlocked', - }); - } - /** - * Get the list of badges - * Get all badges unlocked by the user - * @returns BadgeDTO Successfully got badges - * @throws ApiError - */ - public static getBadgesUnlockedByUser({ - userId, - }: { - userId: number, - }): CancelablePromise<Array<BadgeDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/badge/unlocked/{userId}', - path: { - 'userId': userId, - }, - }); - } - /** - * Get the list of badges - * Get all badges not unlocked by the user - * @returns BadgeDTO Successfully got badges - * @throws ApiError - */ - public static getBadgesNotUnlockedByActiveUser(): CancelablePromise<Array<BadgeDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/badge/locked', - }); - } + /** + * 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 getBadgesUnlockedByActiveUser(): CancelablePromise<Array<BadgeDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/badge/unlocked' + }) + } + /** + * Get the list of badges + * Get all badges unlocked by the user + * @returns BadgeDTO Successfully got badges + * @throws ApiError + */ + public static getBadgesUnlockedByUser({ + userId + }: { + userId: number + }): CancelablePromise<Array<BadgeDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/badge/unlocked/{userId}', + path: { + userId: userId + } + }) + } + /** + * Get the list of badges + * Get all badges not unlocked by the user + * @returns BadgeDTO Successfully got badges + * @throws ApiError + */ + public static getBadgesNotUnlockedByActiveUser(): CancelablePromise<Array<BadgeDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/badge/locked' + }) + } } diff --git a/src/api/services/BankProfileControllerService.ts b/src/api/services/BankProfileControllerService.ts index 5ae9fa35124c975f752afa4228fccdb1d2bd68b3..080b436ae291d9564ece9e2404b6c35f791f7ec1 100644 --- a/src/api/services/BankProfileControllerService.ts +++ b/src/api/services/BankProfileControllerService.ts @@ -2,31 +2,31 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { BankProfileDTO } from '../models/BankProfileDTO'; -import type { BankProfileResponseDTO } from '../models/BankProfileResponseDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { BankProfileDTO } from '../models/BankProfileDTO' +import type { BankProfileResponseDTO } from '../models/BankProfileResponseDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class BankProfileControllerService { - /** - * Create bank profile - * Create a bank profile by providing a social security number - * @returns BankProfileResponseDTO Successfully created a bank profile - * @throws ApiError - */ - public static createBankProfile({ - requestBody, - }: { - requestBody: BankProfileDTO, - }): CancelablePromise<BankProfileResponseDTO> { - return __request(OpenAPI, { - method: 'POST', - url: '/bank/v1/profile/create-profile', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Could not create profile`, - }, - }); - } + /** + * Create bank profile + * Create a bank profile by providing a social security number + * @returns BankProfileResponseDTO Successfully created a bank profile + * @throws ApiError + */ + public static createBankProfile({ + requestBody + }: { + requestBody: BankProfileDTO + }): CancelablePromise<BankProfileResponseDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/profile/create-profile', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Could not create profile` + } + }) + } } diff --git a/src/api/services/BudgetService.ts b/src/api/services/BudgetService.ts index 219c866aa5f4ed51c86e3d10404e1e197898be1d..762384684abfb57caaec47e577c00e2faf5efd2e 100644 --- a/src/api/services/BudgetService.ts +++ b/src/api/services/BudgetService.ts @@ -2,201 +2,201 @@ /* 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'; +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`, - }, - }); - } + /** + * 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` + } + }) + } } diff --git a/src/api/services/FriendService.ts b/src/api/services/FriendService.ts index 70b531d682d20c95dc134b4f0d694df49fb548fb..b41c4dc0597e3ee1583a96e32ed5e4fab42d5d1e 100644 --- a/src/api/services/FriendService.ts +++ b/src/api/services/FriendService.ts @@ -2,96 +2,92 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { UserDTO } from '../models/UserDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { UserDTO } from '../models/UserDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class FriendService { - /** - * Accept a friend request - * Accepts a friend request from another user. - * @returns any Friend request successfully accepted - * @throws ApiError - */ - public static acceptFriendRequest({ - friendId, - }: { - friendId: number, - }): CancelablePromise<Record<string, any>> { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/friends/{friendId}', - path: { - 'friendId': friendId, - }, - errors: { - 404: `Friend request not found`, - }, - }); - } - /** - * Delete a friend or cancel a friend request - * Deletes an existing friend from your friend list or cancels a received friend request. - * @returns any Friend successfully deleted or friend request cancelled - * @throws ApiError - */ - public static deleteFriendOrFriendRequest({ - friendId, - }: { - friendId: number, - }): CancelablePromise<Record<string, any>> { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/friends/{friendId}', - path: { - 'friendId': friendId, - }, - errors: { - 404: `Friend or friend request not found`, - }, - }); - } - /** - * Send a friend request - * Sends a new friend request to another user. A notification is sent to this user - * @returns any Friend request successfully created - * @throws ApiError - */ - public static addFriendRequest({ - userId, - }: { - userId: number, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/friends/{userId}', - path: { - 'userId': userId, - }, - }); - } - /** - * Get all friends - * Returns a list of all friends. - * @returns UserDTO Successfully retrieved list of friends - * @throws ApiError - */ - public static getFriends(): CancelablePromise<Array<UserDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/friends', - }); - } - /** - * Get friend requests - * Returns a list of all users who have sent a friend request. - * @returns UserDTO Successfully retrieved friend requests - * @throws ApiError - */ - public static getFriendRequests(): CancelablePromise<Array<UserDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/friends/requests', - }); - } + /** + * Accept a friend request + * Accepts a friend request from another user. + * @returns any Friend request successfully accepted + * @throws ApiError + */ + public static acceptFriendRequest({ + friendId + }: { + friendId: number + }): CancelablePromise<Record<string, any>> { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/friends/{friendId}', + path: { + friendId: friendId + }, + errors: { + 404: `Friend request not found` + } + }) + } + /** + * Delete a friend or cancel a friend request + * Deletes an existing friend from your friend list or cancels a received friend request. + * @returns any Friend successfully deleted or friend request cancelled + * @throws ApiError + */ + public static deleteFriendOrFriendRequest({ + friendId + }: { + friendId: number + }): CancelablePromise<Record<string, any>> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/friends/{friendId}', + path: { + friendId: friendId + }, + errors: { + 404: `Friend or friend request not found` + } + }) + } + /** + * Send a friend request + * Sends a new friend request to another user. A notification is sent to this user + * @returns any Friend request successfully created + * @throws ApiError + */ + public static addFriendRequest({ userId }: { userId: number }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/friends/{userId}', + path: { + userId: userId + } + }) + } + /** + * Get all friends + * Returns a list of all friends. + * @returns UserDTO Successfully retrieved list of friends + * @throws ApiError + */ + public static getFriends(): CancelablePromise<Array<UserDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/friends' + }) + } + /** + * Get friend requests + * Returns a list of all users who have sent a friend request. + * @returns UserDTO Successfully retrieved friend requests + * @throws ApiError + */ + public static getFriendRequests(): CancelablePromise<Array<UserDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/friends/requests' + }) + } } diff --git a/src/api/services/GoalService.ts b/src/api/services/GoalService.ts index d679829c9813d1b022a7c5cca1431450d40a112c..12ec5025ecd4f4f47be7a724efe3fbd0be93a008 100644 --- a/src/api/services/GoalService.ts +++ b/src/api/services/GoalService.ts @@ -2,115 +2,107 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ChallengeDTO } from '../models/ChallengeDTO'; -import type { CreateGoalDTO } from '../models/CreateGoalDTO'; -import type { GoalDTO } from '../models/GoalDTO'; -import type { MarkChallengeDTO } from '../models/MarkChallengeDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { ChallengeDTO } from '../models/ChallengeDTO' +import type { CreateGoalDTO } from '../models/CreateGoalDTO' +import type { GoalDTO } from '../models/GoalDTO' +import type { MarkChallengeDTO } from '../models/MarkChallengeDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class GoalService { - /** - * Get goals - * Get the goals of the authenticated user - * @returns GoalDTO Successfully retrieved the goals - * @throws ApiError - */ - public static getGoals(): CancelablePromise<Array<GoalDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/goals', - }); - } - /** - * Create a goal - * Create a new goal - * @returns GoalDTO Successfully created a goal - * @throws ApiError - */ - public static createGoal({ - requestBody, - }: { - requestBody: CreateGoalDTO, - }): CancelablePromise<GoalDTO> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/goals', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Update a challenge - * Update a challenge day as completed - * @returns any Successfully updated the challenge - * @throws ApiError - */ - public static updateChallenge({ - requestBody, - }: { - requestBody: MarkChallengeDTO, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/goals/update-challenge', - body: requestBody, - mediaType: 'application/json', - errors: { - 401: `Day is already completed or day outside of range`, - }, - }); - } - /** - * Update challenge saving amount - * Update the challenge saving amount - * @returns any Successfully updated the challenge - * @throws ApiError - */ - public static updateChallengeAmount({ - requestBody, - }: { - requestBody: MarkChallengeDTO, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/goals/update-challenge-amount', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * @returns ChallengeDTO OK - * @throws ApiError - */ - public static regenerateChallenge({ - id, - }: { - id: number, - }): CancelablePromise<ChallengeDTO> { - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/goals/challenge/{id}', - path: { - 'id': id, - }, - }); - } - /** - * @returns GoalDTO OK - * @throws ApiError - */ - public static getGoal({ - id, - }: { - id: number, - }): CancelablePromise<GoalDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/goals/{id}', - path: { - 'id': id, - }, - }); - } + /** + * Get goals + * Get the goals of the authenticated user + * @returns GoalDTO Successfully retrieved the goals + * @throws ApiError + */ + public static getGoals(): CancelablePromise<Array<GoalDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/goals' + }) + } + /** + * Create a goal + * Create a new goal + * @returns GoalDTO Successfully created a goal + * @throws ApiError + */ + public static createGoal({ + requestBody + }: { + requestBody: CreateGoalDTO + }): CancelablePromise<GoalDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/goals', + body: requestBody, + mediaType: 'application/json' + }) + } + /** + * Update a challenge + * Update a challenge day as completed + * @returns any Successfully updated the challenge + * @throws ApiError + */ + public static updateChallenge({ + requestBody + }: { + requestBody: MarkChallengeDTO + }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/goals/update-challenge', + body: requestBody, + mediaType: 'application/json', + errors: { + 401: `Day is already completed or day outside of range` + } + }) + } + /** + * Update challenge saving amount + * Update the challenge saving amount + * @returns any Successfully updated the challenge + * @throws ApiError + */ + public static updateChallengeAmount({ + requestBody + }: { + requestBody: MarkChallengeDTO + }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/goals/update-challenge-amount', + body: requestBody, + mediaType: 'application/json' + }) + } + /** + * @returns ChallengeDTO OK + * @throws ApiError + */ + public static regenerateChallenge({ id }: { id: number }): CancelablePromise<ChallengeDTO> { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/goals/challenge/{id}', + path: { + id: id + } + }) + } + /** + * @returns GoalDTO OK + * @throws ApiError + */ + public static getGoal({ id }: { id: number }): CancelablePromise<GoalDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/goals/{id}', + path: { + id: id + } + }) + } } diff --git a/src/api/services/ImageService.ts b/src/api/services/ImageService.ts index a673de7d12020130232a9693c0df8a47393ee061..536e2f5d499b28fde0b084570ed98314643de019 100644 --- a/src/api/services/ImageService.ts +++ b/src/api/services/ImageService.ts @@ -2,50 +2,46 @@ /* 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'; +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class ImageService { - /** - * Upload an image - * Upload an image to the server - * @returns number Successfully uploaded the image - * @throws ApiError - */ - public static uploadImage({ - formData, - }: { - formData?: { - file: Blob; - }, - }): CancelablePromise<number> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/images', - formData: formData, - mediaType: 'multipart/form-data', - }); - } - /** - * Retrieve an image - * Retrieve an image from the server - * @returns binary Successfully retrieved the image - * @throws ApiError - */ - public static getImage({ - id, - }: { - id: number, - }): CancelablePromise<Blob> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/images/{id}', - path: { - 'id': id, - }, - errors: { - 404: `Image not found`, - }, - }); + /** + * Upload an image + * Upload an image to the server + * @returns number Successfully uploaded the image + * @throws ApiError + */ + public static uploadImage({ + formData + }: { + formData?: { + file: Blob } + }): CancelablePromise<number> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/images', + formData: formData, + mediaType: 'multipart/form-data' + }) + } + /** + * Retrieve an image + * Retrieve an image from the server + * @returns binary Successfully retrieved the image + * @throws ApiError + */ + public static getImage({ id }: { id: number }): CancelablePromise<Blob> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/images/{id}', + path: { + id: id + }, + errors: { + 404: `Image not found` + } + }) + } } diff --git a/src/api/services/ItemService.ts b/src/api/services/ItemService.ts index 6328a34f5b155f14aeb785ddc1f7e568271fcc0a..4d09c06ba52d1ab4c3a70787af817b1f8cc0d9bd 100644 --- a/src/api/services/ItemService.ts +++ b/src/api/services/ItemService.ts @@ -2,75 +2,71 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { InventoryDTO } from '../models/InventoryDTO'; -import type { ItemDTO } from '../models/ItemDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { InventoryDTO } from '../models/InventoryDTO' +import type { ItemDTO } from '../models/ItemDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class ItemService { - /** - * Purchase an item - * Performs a purchase of the item by the user. Points will be deducted from the user. - * @returns any Item purchased and added to inventory successfully - * @throws ApiError - */ - public static buyItem({ - itemId, - }: { - itemId: number, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/item/{itemId}', - path: { - 'itemId': itemId, - }, - errors: { - 403: `Insufficient points to purchase the item`, - }, - }); - } - /** - * Get available store items - * Retrieves all items available in the store and a flag indicating whether the user has purchased each item. - * @returns ItemDTO List of store items fetched successfully - * @throws ApiError - */ - public static getStore(): CancelablePromise<Array<ItemDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/item/store', - }); - } - /** - * Get the active user's inventory items - * Retrieves a list of all items currently in the inventory of the active user. - * @returns InventoryDTO List of inventory items fetched successfully - * @throws ApiError - */ - public static getInventory(): CancelablePromise<Array<InventoryDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/item/inventory', - }); - } - /** - * Get user inventory items - * Retrieves a list of all items currently in the inventory of the user. - * @returns InventoryDTO List of inventory items fetched successfully - * @throws ApiError - */ - public static getInventoryByUserId({ - userId, - }: { - userId: number, - }): CancelablePromise<Array<InventoryDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/item/inventory/{userId}', - path: { - 'userId': userId, - }, - }); - } + /** + * Purchase an item + * Performs a purchase of the item by the user. Points will be deducted from the user. + * @returns any Item purchased and added to inventory successfully + * @throws ApiError + */ + public static buyItem({ itemId }: { itemId: number }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/item/{itemId}', + path: { + itemId: itemId + }, + errors: { + 403: `Insufficient points to purchase the item` + } + }) + } + /** + * Get available store items + * Retrieves all items available in the store and a flag indicating whether the user has purchased each item. + * @returns ItemDTO List of store items fetched successfully + * @throws ApiError + */ + public static getStore(): CancelablePromise<Array<ItemDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/item/store' + }) + } + /** + * Get the active user's inventory items + * Retrieves a list of all items currently in the inventory of the active user. + * @returns InventoryDTO List of inventory items fetched successfully + * @throws ApiError + */ + public static getInventory(): CancelablePromise<Array<InventoryDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/item/inventory' + }) + } + /** + * Get user inventory items + * Retrieves a list of all items currently in the inventory of the user. + * @returns InventoryDTO List of inventory items fetched successfully + * @throws ApiError + */ + public static getInventoryByUserId({ + userId + }: { + userId: number + }): CancelablePromise<Array<InventoryDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/item/inventory/{userId}', + path: { + userId: userId + } + }) + } } diff --git a/src/api/services/LeaderboardService.ts b/src/api/services/LeaderboardService.ts index 8a38209a1808d6879f636efd4613b5f53b65f23c..ece6093e2865ffad3575048a4dd549be56c67e17 100644 --- a/src/api/services/LeaderboardService.ts +++ b/src/api/services/LeaderboardService.ts @@ -2,67 +2,67 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { LeaderboardDTO } from '../models/LeaderboardDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { LeaderboardDTO } from '../models/LeaderboardDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class LeaderboardService { - /** - * @returns LeaderboardDTO OK - * @throws ApiError - */ - public static getLeaderboard({ - type, - filter, - entryCount = 10, - }: { - type: string, - filter: string, - entryCount?: number, - }): CancelablePromise<LeaderboardDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/leaderboard', - query: { - 'type': type, - 'filter': filter, - 'entryCount': entryCount, - }, - }); - } - /** - * Get sum of total points globally - * Get the sum of the total points of all users globally - * @returns number Successfully retrieved total points - * @throws ApiError - */ - public static getTotalPoints(): CancelablePromise<number> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/leaderboard/total-points', - }); - } - /** - * @returns LeaderboardDTO OK - * @throws ApiError - */ - public static getSurrounding({ - type, - filter, - entryCount = 10, - }: { - type: string, - filter: string, - entryCount?: number, - }): CancelablePromise<LeaderboardDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/leaderboard/surrounding', - query: { - 'type': type, - 'filter': filter, - 'entryCount': entryCount, - }, - }); - } + /** + * @returns LeaderboardDTO OK + * @throws ApiError + */ + public static getLeaderboard({ + type, + filter, + entryCount = 10 + }: { + type: string + filter: string + entryCount?: number + }): CancelablePromise<LeaderboardDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/leaderboard', + query: { + type: type, + filter: filter, + entryCount: entryCount + } + }) + } + /** + * Get sum of total points globally + * Get the sum of the total points of all users globally + * @returns number Successfully retrieved total points + * @throws ApiError + */ + public static getTotalPoints(): CancelablePromise<number> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/leaderboard/total-points' + }) + } + /** + * @returns LeaderboardDTO OK + * @throws ApiError + */ + public static getSurrounding({ + type, + filter, + entryCount = 10 + }: { + type: string + filter: string + entryCount?: number + }): CancelablePromise<LeaderboardDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/leaderboard/surrounding', + query: { + type: type, + filter: filter, + entryCount: entryCount + } + }) + } } diff --git a/src/api/services/NotificationService.ts b/src/api/services/NotificationService.ts index 331fc68ba61a26c7fd8b51972a2b2e0de381ce45..44096deb1fbb25dbeace92e2f6edf7d951cf83cf 100644 --- a/src/api/services/NotificationService.ts +++ b/src/api/services/NotificationService.ts @@ -2,76 +2,76 @@ /* 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'; +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', - }); - } + /** + * 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 index 1dbf1cfac5dedad6ccd91c038991c0cb961ed24e..63bef16d37ae22396a19bac1e1d49dfd230d2f50 100644 --- a/src/api/services/RedirectService.ts +++ b/src/api/services/RedirectService.ts @@ -2,25 +2,21 @@ /* 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'; +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, - }, - }); - } + /** + * @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/TransactionControllerService.ts b/src/api/services/TransactionControllerService.ts index f584d0ae03eb7882c73c6e0940575f0c009bd327..43c0f2ea8ae9f76693ecffd0c6ee40b8e72de56a 100644 --- a/src/api/services/TransactionControllerService.ts +++ b/src/api/services/TransactionControllerService.ts @@ -2,30 +2,30 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { TransactionDTO } from '../models/TransactionDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { TransactionDTO } from '../models/TransactionDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class TransactionControllerService { - /** - * Transfer to account - * Transfer money from a users account to another account of the same user - * @returns TransactionDTO No accounts associated with a bank user - * @throws ApiError - */ - public static transferToSelf({ - requestBody, - }: { - requestBody: TransactionDTO, - }): CancelablePromise<TransactionDTO> { - return __request(OpenAPI, { - method: 'POST', - url: '/bank/v1/transaction/norwegian-domestic-payment-to-self', - body: requestBody, - mediaType: 'application/json', - errors: { - 404: `Bank profile id does not exist`, - }, - }); - } + /** + * Transfer to account + * Transfer money from a users account to another account of the same user + * @returns TransactionDTO No accounts associated with a bank user + * @throws ApiError + */ + public static transferToSelf({ + requestBody + }: { + requestBody: TransactionDTO + }): CancelablePromise<TransactionDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/transaction/norwegian-domestic-payment-to-self', + body: requestBody, + mediaType: 'application/json', + errors: { + 404: `Bank profile id does not exist` + } + }) + } } diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index 937ebd2a011227360c45a788069445e0bad0a35e..0fa78277eba7e3f20c64bbbc03a6b779ad938432 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -2,226 +2,218 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { FeedbackRequestDTO } from '../models/FeedbackRequestDTO'; -import type { FeedbackResponseDTO } from '../models/FeedbackResponseDTO'; -import type { PasswordResetDTO } from '../models/PasswordResetDTO'; -import type { PasswordUpdateDTO } from '../models/PasswordUpdateDTO'; -import type { ProfileDTO } from '../models/ProfileDTO'; -import type { UserDTO } from '../models/UserDTO'; -import type { UserUpdateDTO } from '../models/UserUpdateDTO'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { FeedbackRequestDTO } from '../models/FeedbackRequestDTO' +import type { FeedbackResponseDTO } from '../models/FeedbackResponseDTO' +import type { PasswordResetDTO } from '../models/PasswordResetDTO' +import type { PasswordUpdateDTO } from '../models/PasswordUpdateDTO' +import type { ProfileDTO } from '../models/ProfileDTO' +import type { UserDTO } from '../models/UserDTO' +import type { UserUpdateDTO } from '../models/UserUpdateDTO' +import type { CancelablePromise } from '../core/CancelablePromise' +import { OpenAPI } from '../core/OpenAPI' +import { request as __request } from '../core/request' export class UserService { - /** - * Update User Subscription Level - * Updates the subscription level of the current user - * @returns any Subscription level updated successfully - * @throws ApiError - */ - public static updateSubscriptionLevel({ - subscriptionLevel, - }: { - subscriptionLevel: string, - }): CancelablePromise<Record<string, any>> { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/users/subscription/{subscriptionLevel}', - path: { - 'subscriptionLevel': subscriptionLevel, - }, - }); - } - /** - * Send feedback - * Send feedback from an email. - * @returns any Success - * @throws ApiError - */ - public static sendFeedback({ - requestBody, - }: { - requestBody: FeedbackRequestDTO, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/users/send-feedback', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Initiate a password reset - * Send a password reset mail to the user with the specified email - * @returns any Successfully initiated a password reset - * @throws ApiError - */ - public static resetPassword({ - requestBody, - }: { - requestBody: string, - }): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/users/reset-password', - body: requestBody, - mediaType: 'text/plain', - }); - } - /** - * Confirm a password reset - * Confirms a password reset using a token and a new password - * @returns void - * @throws ApiError - */ - public static confirmPasswordReset({ - requestBody, - }: { - requestBody: PasswordResetDTO, - }): CancelablePromise<void> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/users/confirm-password', - body: requestBody, - mediaType: 'application/json', - errors: { - 403: `Invalid token`, - }, - }); - } - /** - * Update a profile - * Update the profile of the authenticated user - * @returns UserDTO Successfully updated profile - * @throws ApiError - */ - public static update({ - requestBody, - }: { - requestBody: UserUpdateDTO, - }): CancelablePromise<UserDTO> { - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/users', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Update a password - * Update the password of the authenticated user - * @returns UserDTO Successfully updated password - * @throws ApiError - */ - public static updatePassword({ - requestBody, - }: { - requestBody: PasswordUpdateDTO, - }): CancelablePromise<UserDTO> { - return __request(OpenAPI, { - method: 'PATCH', - url: '/api/users/password', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Get a profile - * Get the profile of a user - * @returns ProfileDTO Successfully got profile - * @throws ApiError - */ - public static getProfile({ - userId, - }: { - userId: number, - }): CancelablePromise<ProfileDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/users/{userId}/profile', - path: { - 'userId': userId, - }, - }); - } - /** - * Search for users by name and filter - * Returns a list of users whose names contain the specified search term and match the filter. - * @returns UserDTO Successfully retrieved list of users - * @throws ApiError - */ - public static getUsersByNameAndFilter({ - searchTerm, - filter, - }: { - searchTerm: string, - filter: string, - }): CancelablePromise<Array<UserDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/users/search/{searchTerm}/{filter}', - path: { - 'searchTerm': searchTerm, - 'filter': filter, - }, - }); - } - /** - * Get X amount of random users - * Get X amount of random users that fit the filter - * @returns UserDTO Successfully retrieved list of users - * @throws ApiError - */ - public static getRandomUsers({ - amount, - filter, - }: { - amount: number, - filter: string, - }): CancelablePromise<Array<UserDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/users/search/random/{amount}/{filter}', - path: { - 'amount': amount, - 'filter': filter, - }, - }); - } - /** - * Get the authenticated user - * Get all user information for the authenticated user - * @returns UserDTO Successfully got user - * @throws ApiError - */ - public static getUser(): CancelablePromise<UserDTO> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/users/me', - }); - } - /** - * Delete the authenticated user - * Delete the authenticated user - * @returns any Successfully deleted user - * @throws ApiError - */ - public static deleteUser(): CancelablePromise<any> { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/users/me', - }); - } - /** - * Send feedback - * Send feedback from a user. - * @returns FeedbackResponseDTO Success - * @throws ApiError - */ - public static getFeedback(): CancelablePromise<Array<FeedbackResponseDTO>> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/users/get-feedback', - }); - } + /** + * Update User Subscription Level + * Updates the subscription level of the current user + * @returns any Subscription level updated successfully + * @throws ApiError + */ + public static updateSubscriptionLevel({ + subscriptionLevel + }: { + subscriptionLevel: string + }): CancelablePromise<Record<string, any>> { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/users/subscription/{subscriptionLevel}', + path: { + subscriptionLevel: subscriptionLevel + } + }) + } + /** + * Send feedback + * Send feedback from an email. + * @returns any Success + * @throws ApiError + */ + public static sendFeedback({ + requestBody + }: { + requestBody: FeedbackRequestDTO + }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/users/send-feedback', + body: requestBody, + mediaType: 'application/json' + }) + } + /** + * Initiate a password reset + * Send a password reset mail to the user with the specified email + * @returns any Successfully initiated a password reset + * @throws ApiError + */ + public static resetPassword({ requestBody }: { requestBody: string }): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/users/reset-password', + body: requestBody, + mediaType: 'text/plain' + }) + } + /** + * Confirm a password reset + * Confirms a password reset using a token and a new password + * @returns void + * @throws ApiError + */ + public static confirmPasswordReset({ + requestBody + }: { + requestBody: PasswordResetDTO + }): CancelablePromise<void> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/users/confirm-password', + body: requestBody, + mediaType: 'application/json', + errors: { + 403: `Invalid token` + } + }) + } + /** + * Update a profile + * Update the profile of the authenticated user + * @returns UserDTO Successfully updated profile + * @throws ApiError + */ + public static update({ + requestBody + }: { + requestBody: UserUpdateDTO + }): CancelablePromise<UserDTO> { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/users', + body: requestBody, + mediaType: 'application/json' + }) + } + /** + * Update a password + * Update the password of the authenticated user + * @returns UserDTO Successfully updated password + * @throws ApiError + */ + public static updatePassword({ + requestBody + }: { + requestBody: PasswordUpdateDTO + }): CancelablePromise<UserDTO> { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/users/password', + body: requestBody, + mediaType: 'application/json' + }) + } + /** + * Get a profile + * Get the profile of a user + * @returns ProfileDTO Successfully got profile + * @throws ApiError + */ + public static getProfile({ userId }: { userId: number }): CancelablePromise<ProfileDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/{userId}/profile', + path: { + userId: userId + } + }) + } + /** + * Search for users by name and filter + * Returns a list of users whose names contain the specified search term and match the filter. + * @returns UserDTO Successfully retrieved list of users + * @throws ApiError + */ + public static getUsersByNameAndFilter({ + searchTerm, + filter + }: { + searchTerm: string + filter: string + }): CancelablePromise<Array<UserDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/search/{searchTerm}/{filter}', + path: { + searchTerm: searchTerm, + filter: filter + } + }) + } + /** + * Get X amount of random users + * Get X amount of random users that fit the filter + * @returns UserDTO Successfully retrieved list of users + * @throws ApiError + */ + public static getRandomUsers({ + amount, + filter + }: { + amount: number + filter: string + }): CancelablePromise<Array<UserDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/search/random/{amount}/{filter}', + path: { + amount: amount, + filter: filter + } + }) + } + /** + * Get the authenticated user + * Get all user information for the authenticated user + * @returns UserDTO Successfully got user + * @throws ApiError + */ + public static getUser(): CancelablePromise<UserDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/me' + }) + } + /** + * Delete the authenticated user + * Delete the authenticated user + * @returns any Successfully deleted user + * @throws ApiError + */ + public static deleteUser(): CancelablePromise<any> { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/users/me' + }) + } + /** + * Send feedback + * Send feedback from a user. + * @returns FeedbackResponseDTO Success + * @throws ApiError + */ + public static getFeedback(): CancelablePromise<Array<FeedbackResponseDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/get-feedback' + }) + } } diff --git a/src/components/Admin/AdminFeedback.vue b/src/components/Admin/AdminFeedback.vue index 677b77ae69a4d443df6d46ec1722d910284163fc..0bbfe0708e02766713e2c5bd2f23836c45541176 100644 --- a/src/components/Admin/AdminFeedback.vue +++ b/src/components/Admin/AdminFeedback.vue @@ -3,21 +3,21 @@ import { onMounted, ref } from 'vue' import { type FeedbackResponseDTO, UserService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const feedbacks = ref<FeedbackResponseDTO[]>([]); +const feedbacks = ref<FeedbackResponseDTO[]>([]) onMounted(async () => { try { - feedbacks.value = await UserService.getFeedback(); + feedbacks.value = await UserService.getFeedback() console.log(feedbacks.value) } catch (error) { - handleUnknownError(error); + handleUnknownError(error) } }) const formattedDate = (dateStr?: string): string => { - if (!dateStr) return ''; - return new Date(dateStr).toLocaleString(); -}; + if (!dateStr) return '' + return new Date(dateStr).toLocaleString() +} </script> <template> @@ -51,7 +51,9 @@ const formattedDate = (dateStr?: string): string => { border-bottom: 1px solid #ccc; } -.email, .message, .created-at { +.email, +.message, +.created-at { padding: 5px 0; } @@ -70,4 +72,4 @@ const formattedDate = (dateStr?: string): string => { font-size: 0.8rem; color: #999; } -</style> \ No newline at end of file +</style> diff --git a/src/components/BaseComponents/BaseFooter.vue b/src/components/BaseComponents/BaseFooter.vue index d9628730141561e16a323cc7bc2cb0ff66610613..c6fe66ece6e1a5a1ef7e9a3586da0848d61829b8 100644 --- a/src/components/BaseComponents/BaseFooter.vue +++ b/src/components/BaseComponents/BaseFooter.vue @@ -1,15 +1,13 @@ <template> - <div> - <footer id = "footer" class="text-center text-white" style="width: 100%"> - <div class="text-center p-3"> - © SpareSti 2024 - </div> - </footer> - </div> + <div> + <footer id="footer" class="text-center text-white" style="width: 100%"> + <div class="text-center p-3">© SpareSti 2024</div> + </footer> + </div> </template> <style scoped> #footer { - background-color: #003A58; + background-color: #003a58; } -</style> \ No newline at end of file +</style> diff --git a/src/components/BaseComponents/Buttons/BaseButton.vue b/src/components/BaseComponents/Buttons/BaseButton.vue index f57ef80ede1de7c53b8466b13ed7dab8e926b7c3..d227ad5eae2387c9955f1e7aa79f13754450603c 100644 --- a/src/components/BaseComponents/Buttons/BaseButton.vue +++ b/src/components/BaseComponents/Buttons/BaseButton.vue @@ -1,32 +1,33 @@ <template> - <button type="button" class="btn btn-primary" id="buttonStyle">{{ buttonText }}</button> + <button type="button" class="btn btn-primary" id="buttonStyle">{{ buttonText }}</button> </template> <script setup lang="ts"> -defineProps<{ buttonText: string }>(); +defineProps<{ buttonText: string }>() </script> <style scoped> - #buttonStyle { - padding: 0.5rem 4rem; - font-size: 1.5rem; - font-weight: 600; - } +#buttonStyle { + padding: 0.5rem 4rem; + font-size: 1.5rem; + font-weight: 600; +} - .btn-primary { - background-color: #003A58; - border-color: #003A58; - font-weight: 600; - } +.btn-primary { + background-color: #003a58; + border-color: #003a58; + font-weight: 600; +} - .btn-primary:hover, .btn-primary:active, .btn-primary:visited { - background-color: #004a72 !important; - border-color: #004a72 !important; - } - - .btn-primary:disabled { - background-color: rgba(0, 74, 114, 0.56) !important; - border-color: rgba(0, 74, 114, 0.56) !important; - } +.btn-primary:hover, +.btn-primary:active, +.btn-primary:visited { + background-color: #004a72 !important; + border-color: #004a72 !important; +} +.btn-primary:disabled { + background-color: rgba(0, 74, 114, 0.56) !important; + border-color: rgba(0, 74, 114, 0.56) !important; +} </style> diff --git a/src/components/BaseComponents/ConfirmationModal.vue b/src/components/BaseComponents/ConfirmationModal.vue index 438ff362e01d693aa2d6663c2176e167c5281de1..6b461f3eb882da91609e16af71f338cb296a493e 100644 --- a/src/components/BaseComponents/ConfirmationModal.vue +++ b/src/components/BaseComponents/ConfirmationModal.vue @@ -1,38 +1,53 @@ <!-- ConfirmationModal.vue --> <template> - <div class="modal" :id="modalId" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true" - data-bs-backdrop="static" data-bs-keyboard="false"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="modalLabel">{{ title }}</h5> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> - </div> - <div class="modal-body"> - {{ message }} - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ cancelButtonText }}</button> - <button type="button" class="btn btn-primary" @click="confirmAction">{{ confirmButtonText }}</button> - </div> + <div + class="modal" + :id="modalId" + tabindex="-1" + aria-labelledby="modalLabel" + aria-hidden="true" + data-bs-backdrop="static" + data-bs-keyboard="false" + > + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="modalLabel">{{ title }}</h5> + <button + type="button" + class="btn-close" + data-bs-dismiss="modal" + aria-label="Close" + ></button> + </div> + <div class="modal-body"> + {{ message }} + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + {{ cancelButtonText }} + </button> + <button type="button" class="btn btn-primary" @click="confirmAction"> + {{ confirmButtonText }} + </button> </div> </div> </div> - </template> - - <script setup> - import { ref } from 'vue'; - - const modalId = ref(null); - - const title = 'Confirm Action'; - const message = 'Are you sure you want to proceed?'; - const confirmButtonText = 'Confirm'; - const cancelButtonText = 'Cancel'; - - // Methods - function confirmAction() { - emit('confirm'); - } - </script> - \ No newline at end of file + </div> +</template> + +<script setup> +import { ref } from 'vue' + +const modalId = ref(null) + +const title = 'Confirm Action' +const message = 'Are you sure you want to proceed?' +const confirmButtonText = 'Confirm' +const cancelButtonText = 'Cancel' + +// Methods +function confirmAction() { + emit('confirm') +} +</script> diff --git a/src/components/BaseComponents/Input/BaseInput.vue b/src/components/BaseComponents/Input/BaseInput.vue index cf6bddf0886b2b5a78ee463be9cbd048fae290d6..f875d0bd50cde6dbd0fca73e1404002c1bb8c17d 100644 --- a/src/components/BaseComponents/Input/BaseInput.vue +++ b/src/components/BaseComponents/Input/BaseInput.vue @@ -1,27 +1,25 @@ <script setup lang="ts"> - import { ref } from 'vue' -const emit = defineEmits(['inputChangeEvent']); +const emit = defineEmits(['inputChangeEvent']) const props = defineProps({ label: { type: String, - default: "" + default: '' }, type: { type: String, - default: "text" + default: 'text' }, placeholder: { type: String, - default: "" + default: '' }, inputId: { type: String, required: true }, - modelValue: { - }, + modelValue: {}, min: { type: String, required: false @@ -48,13 +46,12 @@ const props = defineProps({ }, inputClass: { type: String, - default: "form-control" + default: 'form-control' } -}); +}) // Form reference in order to display validations input -const formRef = ref(); - +const formRef = ref() /** * Adds the "was-validated" class to the input element, and emits @@ -63,7 +60,7 @@ const formRef = ref(); * @param event The input event object */ const onInputEvent = (event: any) => { - formRef.value.classList.add("was-validated") + formRef.value.classList.add('was-validated') emit('inputChangeEvent', event.target.value) } </script> @@ -71,23 +68,22 @@ const onInputEvent = (event: any) => { <template> <div ref="formRef"> <label :for="inputId" data-cy="bi-label">{{ label }}</label> - <input :value="modelValue" - @input="onInputEvent" - :type="type" - :class="inputClass" - :placeholder="placeholder" - :id="inputId" - :min="min" - :max="max" - :pattern="pattern" - :required="required" - data-cy="bi-input" + <input + :value="modelValue" + @input="onInputEvent" + :type="type" + :class="inputClass" + :placeholder="placeholder" + :id="inputId" + :min="min" + :max="max" + :pattern="pattern" + :required="required" + data-cy="bi-input" /> <div data-cy="bi-valid-msg" class="valid-feedback">{{ validMessage }}</div> <div data-cy="bi-invalid-msg" class="invalid-feedback" id="invalid">{{ invalidMessage }}</div> </div> </template> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/components/BaseComponents/Input/__tests__/BaseInput.spec.ts b/src/components/BaseComponents/Input/__tests__/BaseInput.spec.ts index b795f41627a01c0d19fc748f40a5619194b4b2a4..3ea8c470fe422c01d9879a7a93a93ac8ff9b0c60 100644 --- a/src/components/BaseComponents/Input/__tests__/BaseInput.spec.ts +++ b/src/components/BaseComponents/Input/__tests__/BaseInput.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' -import { mount } from '@vue/test-utils'; -import InputField from '../BaseInput.vue'; +import { mount } from '@vue/test-utils' +import InputField from '../BaseInput.vue' describe('InputField.vue', () => { it('emits inputChangeEvent when input event is triggered', async () => { @@ -10,12 +10,12 @@ describe('InputField.vue', () => { inputId: 'testId', modelValue: '' } - }); + }) - const input = wrapper.find('input'); - await input.setValue('Test Value'); - - expect(wrapper.emitted().inputChangeEvent).toBeTruthy(); - expect(wrapper.emitted().inputChangeEvent[0]).toEqual(['Test Value']); - }); -}); + const input = wrapper.find('input') + await input.setValue('Test Value') + + expect(wrapper.emitted().inputChangeEvent).toBeTruthy() + expect(wrapper.emitted().inputChangeEvent[0]).toEqual(['Test Value']) + }) +}) diff --git a/src/components/BaseComponents/NavBar.vue b/src/components/BaseComponents/NavBar.vue index 6bb477581683e7e6969e67df1db7651f65b494fb..f87b9dad76305f83dd98423fef9b1ca79511ce38 100644 --- a/src/components/BaseComponents/NavBar.vue +++ b/src/components/BaseComponents/NavBar.vue @@ -2,148 +2,212 @@ <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"> + <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"> + <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 + data-cy="savingGoals" + class="nav-link text-white" + :to="toSavingGoals()" + exact-active-class="active-nav" + > + <img src="@/assets/icons/saving.svg" />Sparemål </router-link> </li> <li class="nav-item"> - <router-link data-cy="leaderboard" class="nav-link text-white" - :to="toLeaderboard()" exact-active-class="active-nav"> - <img src="@/assets/icons/leaderboard.svg">Ledertavle + <router-link + 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 + data-cy="news" + class="nav-link text-white" + :to="toNews()" + exact-active-class="active-nav" + > + <img src="@/assets/icons/newsletter.svg" />Nyheter </router-link> </li> <li class="nav-item" v-if="useUserInfoStore().isPremium"> - <router-link data-cy="budget" - class="nav-link text-white" - :to="toBudget()" - exact-active-class="active-nav" - @click="toggleDropdown"> - <img src="@/assets/icons/budget.svg">Budsjett + <router-link + data-cy="budget" + class="nav-link text-white" + :to="toBudget()" + exact-active-class="active-nav" + @click="toggleDropdown" + > + <img src="@/assets/icons/budget.svg" />Budsjett </router-link> </li> <li class="nav-item"> - <router-link data-cy="store" class="nav-link text-white" - :to="toStore()" exact-active-class="active-nav"> - <img src="@/assets/icons/storefront.svg">Butikk + <router-link + data-cy="store" + class="nav-link text-white" + :to="toStore()" + exact-active-class="active-nav" + > + <img src="@/assets/icons/storefront.svg" />Butikk </router-link> </li> <li class="nav-item dropdown d-flex flex-column"> - <a data-mdb-dropdown-init class=" nav-link dropdown-toggle hidden-arrow notification" href="#" id="navbarDropdownMenuLink" - role="button" data-bs-toggle="dropdown" aria-expanded="false"> - <img src="/src/assets/icons/bell-white.svg"> - <span v-if="notificationListRef.length > 0" class="badge rounded-pill badge-notification bg-danger">{{ notificationListRef.length }}</span> - </a> - <ul v-if="notificationListRef.length > 0" class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> - <li v-for="(item, index) in notificationListRef" :key="index" > - <router-link :to="notificationPathMapper[String(item.notificationType)]" - class="d-flex align-items-center" - @click="readNotification(item)"> - <div class="flex-shrink-0"> - <img :src="notificationImageMapper[String(item.notificationType)]" alt="Varslingsikon" class="notification-icon"> - </div> - <div class="flex-grow-1 ms-3"> - <div class="not-item dropdown-item" id="notificationText">{{item.message}}</div> - </div> - </router-link> - </li> - </ul> - <ul v-else-if="notificationListRef.length === 0" class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> - <li>Ingen varslinger</li> - </ul> - </li> + <a + data-mdb-dropdown-init + class="nav-link dropdown-toggle hidden-arrow notification" + href="#" + id="navbarDropdownMenuLink" + role="button" + data-bs-toggle="dropdown" + aria-expanded="false" + > + <img src="/src/assets/icons/bell-white.svg" /> + <span + v-if="notificationListRef.length > 0" + class="badge rounded-pill badge-notification bg-danger" + >{{ notificationListRef.length }}</span + > + </a> + <ul + v-if="notificationListRef.length > 0" + class="dropdown-menu" + aria-labelledby="navbarDropdownMenuLink" + > + <li v-for="(item, index) in notificationListRef" :key="index"> + <router-link + :to="notificationPathMapper[String(item.notificationType)]" + class="d-flex align-items-center" + @click="readNotification(item)" + > + <div class="flex-shrink-0"> + <img + :src="notificationImageMapper[String(item.notificationType)]" + alt="Varslingsikon" + class="notification-icon" + /> + </div> + <div class="flex-grow-1 ms-3"> + <div class="not-item dropdown-item" id="notificationText"> + {{ item.message }} + </div> + </div> + </router-link> + </li> + </ul> + <ul + v-else-if="notificationListRef.length === 0" + class="dropdown-menu" + aria-labelledby="navbarDropdownMenuLink" + > + <li>Ingen varslinger</li> + </ul> + </li> <li v-if="userStore.isLoggedIn" class="nav-item dropdown d-flex flex-column"> <a data-cy="user" - :class="['nav-link', 'dropdown-toggle', 'username-text', 'text-white', { 'underline-active': !isAnyActivePage() }]" + :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 }} + 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" + <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 + @click="toggleDropdown" + > + <img src="@/assets/icons/black_person.svg" />Min profil </router-link> </li> <li> - <router-link data-cy="friends" + <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 + @click="toggleDropdown" + > + <img src="@/assets/icons/black_friends.svg" />Venner </router-link> </li> <li> - <router-link data-cy="settings" + <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 + @click="toggleDropdown" + > + <img src="@/assets/icons/settings.svg" />Innstillinger </router-link> </li> <li> - <router-link data-cy="feedback" + <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 + @click="toggleDropdown" + > + <img src="@/assets/icons/feedback.svg" />Tilbakemelding </router-link> </li> <li v-if="useUserInfoStore().role === 'ADMIN'"> - <router-link data-cy="admin" + <router-link + data-cy="admin" class="dropdown-item dropdown-username-link" :to="toAdmin()" exact-active-class="active-link" - @click="toggleDropdown"> - <img src="@/assets/icons/admin.svg">Admin + @click="toggleDropdown" + > + <img src="@/assets/icons/admin.svg" />Admin </router-link> </li> <li style="cursor: pointer"> - <a data-testid="logout" - class="dropdown-item dropdown-username-link" - href="#" - @click="toLogout()"> - <img src="@/assets/icons/logout.svg">Logg ut + <a + data-testid="logout" + class="dropdown-item dropdown-username-link" + href="#" + @click="toLogout()" + > + <img src="@/assets/icons/logout.svg" />Logg ut </a> </li> - - </ul> </li> <li v-else class="nav-item"> - <a class="nav-link" style="cursor: pointer;" href="#" - @click="toLogout">Logg inn - </a> + <a class="nav-link" style="cursor: pointer" href="#" @click="toLogout">Logg inn </a> </li> </ul> </div> @@ -151,38 +215,37 @@ </nav> </template> - <script setup lang="ts"> -import { useRouter, useRoute } from "vue-router"; -import { useUserInfoStore } from '@/stores/UserStore'; -import {onMounted, ref} from "vue"; +import { useRouter, useRoute } from 'vue-router' +import { useUserInfoStore } from '@/stores/UserStore' +import { onMounted, ref } from 'vue' import { BadgeService, type NotificationDTO, NotificationService } from '@/api' -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' // Declaring router, route and userStore variables -const router = useRouter(); -const route = useRoute(); -const userStore: any = useUserInfoStore(); +const router = useRouter() +const route = useRoute() +const userStore: any = useUserInfoStore() // Declaring profile image -let profileImage: any = ref(''); +let profileImage: any = ref('') if (useUserInfoStore().profileImage !== 0) { - profileImage.value = 'http://localhost:80/api/images/' + useUserInfoStore().profileImage; + profileImage.value = 'http://localhost:80/api/images/' + useUserInfoStore().profileImage } else { - profileImage.value = 'src/assets/userprofile.png'; + profileImage.value = 'src/assets/userprofile.png' } // Declaring reactive notification list for displaying notification -let notificationListRef = ref<NotificationDTO[]>([]); +let notificationListRef = ref<NotificationDTO[]>([]) /** * Checks if the current route is any of the active pages. * * @returns {boolean} True if the current route is one of the active pages, otherwise false. */ - function isAnyActivePage(): boolean { - const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/budget-overview', '/shop']; - return activeRoutes.includes(route.path); +function isAnyActivePage(): boolean { + const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/budget-overview', '/shop'] + return activeRoutes.includes(route.path) } /** @@ -191,9 +254,9 @@ let notificationListRef = ref<NotificationDTO[]>([]); * @param {Event} event The event object. */ function toggleDropdown(event: any) { - const dropdownMenu = event.target.closest('.dropdown-menu'); + const dropdownMenu = event.target.closest('.dropdown-menu') if (dropdownMenu) { - dropdownMenu.classList.remove('show'); + dropdownMenu.classList.remove('show') } } @@ -201,18 +264,18 @@ function toggleDropdown(event: any) { * Maps notification types to their respective image paths. */ const notificationImageMapper: any = { - "FRIEND_REQUEST": "/src/assets/userprofile.png", - "BADGE": "/src/assets/icons/medal.png", - "COMPLETED_GOAL": "/src/assets/icons/piggybank.svg" + FRIEND_REQUEST: '/src/assets/userprofile.png', + BADGE: '/src/assets/icons/medal.png', + COMPLETED_GOAL: '/src/assets/icons/piggybank.svg' } /** * Maps notification types to their respective paths. */ const notificationPathMapper: any = { - "FRIEND_REQUEST": "/friends", - "BADGE": "/profile", - "COMPLETED_GOAL": "/roadmap" + FRIEND_REQUEST: '/friends', + BADGE: '/profile', + COMPLETED_GOAL: '/roadmap' } /** @@ -223,10 +286,10 @@ const notificationPathMapper: any = { */ const getNotifications = async () => { try { - await BadgeService.updateUnlockedBadges(); + await BadgeService.updateUnlockedBadges() notificationListRef.value = await NotificationService.getUnreadNotificationByUser() } catch (error) { - handleUnknownError(error); + handleUnknownError(error) notificationListRef.value = [] } } @@ -242,12 +305,12 @@ const getNotifications = async () => { */ const readNotification = async (notification: NotificationDTO) => { try { - notification.unread = false; - await NotificationService.updateNotification({requestBody: notification}); + notification.unread = false + await NotificationService.updateNotification({ requestBody: notification }) notificationListRef.value = await NotificationService.getUnreadNotificationByUser() } catch (error) { - handleUnknownError(error); - notificationListRef.value = []; + handleUnknownError(error) + notificationListRef.value = [] } } /** @@ -256,7 +319,7 @@ const readNotification = async (notification: NotificationDTO) => { * @returns {string} The URL for the budget overview page. */ function toBudget(): string { - return '/budget-overview'; + return '/budget-overview' } /** @@ -265,7 +328,7 @@ function toBudget(): string { * @returns {string} The URL for the saving goals page. */ function toSavingGoals(): string { - return '/roadmap'; + return '/roadmap' } /** @@ -274,7 +337,7 @@ function toSavingGoals(): string { * @returns {string} The URL for the leaderboard page. */ function toLeaderboard(): string { - return '/leaderboard'; + return '/leaderboard' } /** @@ -283,7 +346,7 @@ function toLeaderboard(): string { * @returns {string} The URL for the news page. */ function toNews(): string { - return '/news'; + return '/news' } /** @@ -292,7 +355,7 @@ function toNews(): string { * @returns {string} The URL for the store page. */ function toStore(): string { - return '/shop'; + return '/shop' } /** @@ -301,7 +364,7 @@ function toStore(): string { * @returns {string} The URL for the user settings page. */ function toSetting(): string { - return '/settings/profile'; + return '/settings/profile' } /** @@ -310,7 +373,7 @@ function toSetting(): string { * @returns {string} The URL for the feedback page. */ function toFeedback(): string { - return '/feedback'; + return '/feedback' } /** @@ -319,7 +382,7 @@ function toFeedback(): string { * @returns {string} The URL for the admin page. */ function toAdmin(): string { - return '/admin'; + return '/admin' } /** @@ -328,7 +391,7 @@ function toAdmin(): string { * @returns {string} The URL for the friends page. */ function toFriends(): string { - return '/friends'; + return '/friends' } /** @@ -337,15 +400,15 @@ function toFriends(): string { * @returns {string} The URL for the user profile page. */ function toUserProfile(): string { - return '/profile'; + return '/profile' } /** * Logs out the user by clearing user info and redirecting to the login page. */ function toLogout() { - userStore.clearUserInfo(); - router.push('login'); + userStore.clearUserInfo() + router.push('login') } /** @@ -357,20 +420,20 @@ onMounted(() => { </script> <style scoped> .navbar-brand { - display: flex; - align-items: center; + display: flex; + align-items: center; } .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba(255, 255, 255)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba(255, 255, 255)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } .nav-item { - display: flex; - justify-content: center; - align-items: center; - padding: 0.1rem 0.3rem; - font-size: 1.7rem; + display: flex; + justify-content: center; + align-items: center; + padding: 0.1rem 0.3rem; + font-size: 1.7rem; } .active-nav { @@ -394,7 +457,7 @@ onMounted(() => { } .nav-item:hover { - background-color: #01476b; + background-color: #01476b; border-radius: 1rem; } @@ -403,21 +466,20 @@ onMounted(() => { } .nav-item .dropdown { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } .nav-link { - display: flex; - align-items: center; - justify-content: center; - + display: flex; + align-items: center; + justify-content: center; } .dropdown-item { - width: 100%; - display: flex; - justify-content: left; + width: 100%; + display: flex; + justify-content: left; } .dropdown-item:hover { @@ -430,17 +492,17 @@ onMounted(() => { } .dropdown-menu[data-bs-popper] { - left: auto; + left: auto; } .dropdown-username-link { - font-size: 1.7rem; - display: flex; - justify-self: center; + font-size: 1.7rem; + display: flex; + justify-self: center; } .dropdown-username-link:hover { - background-color: #f3f3f3; + background-color: #f3f3f3; } .dropdown-item img { @@ -450,7 +512,7 @@ onMounted(() => { } #navBar { - background-color: #003A58; + background-color: #003a58; } .notification-icon { @@ -463,40 +525,40 @@ onMounted(() => { } .navbar { - display: flex; - align-items: center; + display: flex; + align-items: center; } .container-fluid { - font-size: 1.7rem; + font-size: 1.7rem; margin: 0 140px; } @media (max-width: 768px) { - .container-fluid { - margin: 0 20px; - } + .container-fluid { + margin: 0 20px; + } } #logo { - font-size: 2.5rem; - height: 100%; + font-size: 2.5rem; + height: 100%; } .nav-link img { - margin-right: 5px; + margin-right: 5px; height: 35px; width: 35px; } #logoImg { - margin-right: 0.3rem; - width: 75px; - height: auto; - aspect-ratio: 1.3/1; + margin-right: 0.3rem; + width: 75px; + height: auto; + aspect-ratio: 1.3/1; } -.notification.hidden-arrow::after{ +.notification.hidden-arrow::after { display: none; } @@ -509,5 +571,4 @@ onMounted(() => { text-wrap: wrap; } } - -</style> \ No newline at end of file +</style> diff --git a/src/components/BaseComponents/__tests__/NavBar.spec.ts b/src/components/BaseComponents/__tests__/NavBar.spec.ts index dcbc92d44f1f013375c4a5d60b5d6b0bd49cfd92..558b5b9f828e12ede82ca3396427ac935fb6dc3f 100644 --- a/src/components/BaseComponents/__tests__/NavBar.spec.ts +++ b/src/components/BaseComponents/__tests__/NavBar.spec.ts @@ -1,124 +1,122 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { mount } from '@vue/test-utils'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import { createPinia, setActivePinia } from 'pinia'; -import { useUserInfoStore } from '@/stores/UserStore'; -import MyComponent from '../NavBar.vue'; // Adjust path as needed -import router from '@/router/index'; // Adjust path as needed -import { access } from 'fs'; -import { render, screen } from '@testing-library/vue'; -import userEvent from '@testing-library/user-event'; +import { describe, it, expect, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import { createPinia, setActivePinia } from 'pinia' +import { useUserInfoStore } from '@/stores/UserStore' +import MyComponent from '../NavBar.vue' // Adjust path as needed +import router from '@/router/index' // Adjust path as needed +import { access } from 'fs' +import { render, screen } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' describe('Menu and Router Tests', () => { - let store : any - let mockRouter : any; + let store: any + let mockRouter: any beforeEach(() => { // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); + setActivePinia(createPinia()) + store = useUserInfoStore() mockRouter = createRouter({ history: createMemoryHistory(), - routes: router.getRoutes(), - }); + routes: router.getRoutes() + }) router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) } else { - next(); + next() } - }); - - }); + }) + }) describe('Component Rendering', () => { it('renders Menu correctly with data from the store', () => { - store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }); + store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }) const wrapper = mount(MyComponent, { global: { - plugins: [mockRouter], - }, - }); + plugins: [mockRouter] + } + }) - expect(wrapper.text()).toContain('Jane'); - }); - }); + expect(wrapper.text()).toContain('Jane') + }) + }) describe('Navigation Guards', () => { it('redirects an unauthenticated user to login when accessing a protected route', async () => { - store.$patch({ accessToken: '' }); - - router.push('/profile'); - await router.isReady(); - - expect(router.currentRoute.value.name).toBe('login'); - }); - + store.$patch({ accessToken: '' }) + + router.push('/profile') + await router.isReady() + + expect(router.currentRoute.value.name).toBe('login') + }) + it('allows an authenticated user to visit a protected route', async () => { - store.$patch({ accessToken: 'valid-token' }); + store.$patch({ accessToken: 'valid-token' }) - mockRouter.push('/profile'); + mockRouter.push('/profile') - await mockRouter.isReady(); + await mockRouter.isReady() - expect(mockRouter.currentRoute.value.name).toBe('profile'); - }); - }); - + expect(mockRouter.currentRoute.value.name).toBe('profile') + }) + }) describe('UserStore Actions', () => { it('updates user information correctly', () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith' }); + store.setUserInfo({ firstname: 'John', lastname: 'Smith' }) - expect(store.firstname).toBe('John'); - expect(store.lastname).toBe('Smith'); - }); + expect(store.firstname).toBe('John') + expect(store.lastname).toBe('Smith') + }) it('clears user information correctly', () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); - store.clearUserInfo(); + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken' }) + store.clearUserInfo() - expect(store.firstname).toBe(''); - expect(store.lastname).toBe(''); - expect(store.accessToken).toBe(''); - }); - }); + expect(store.firstname).toBe('') + expect(store.lastname).toBe('') + expect(store.accessToken).toBe('') + }) + }) describe('Menu Actions', () => { it('logout clears userstore', async () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken' }) render(MyComponent, { global: { - plugins: [mockRouter], - }, - }); - await userEvent.click(screen.getByTestId('logout')); + plugins: [mockRouter] + } + }) + await userEvent.click(screen.getByTestId('logout')) - expect(store.firstname).toBe(''); - expect(store.lastname).toBe(''); - expect(store.accessToken).toBe(''); - }); + expect(store.firstname).toBe('') + expect(store.lastname).toBe('') + expect(store.accessToken).toBe('') + }) it('home redirects to home', async () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken' }) const { container } = render(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); + global: { + plugins: [mockRouter] + } + }) // Assuming there's an element with id="home-link" that you want to click - const homeLink = container.querySelector('#home'); // Use the actual ID here + const homeLink = container.querySelector('#home') // Use the actual ID here if (homeLink) { - await userEvent.click(homeLink); - await mockRouter.isReady(); + await userEvent.click(homeLink) + await mockRouter.isReady() } - expect(mockRouter.currentRoute.value.name).toBe('roadmap'); // Assuming 'Home' is the route name for '/' - }); - }); -}); + expect(mockRouter.currentRoute.value.name).toBe('roadmap') // Assuming 'Home' is the route name for '/' + }) + }) +}) diff --git a/src/components/Budget/BudgetBox.vue b/src/components/Budget/BudgetBox.vue index 3cece123e089bf16cf13d733e1d3c1cc3b111ee4..b9b8b8cb72f5557196461c9d03f7d790351fb971 100644 --- a/src/components/Budget/BudgetBox.vue +++ b/src/components/Budget/BudgetBox.vue @@ -29,7 +29,7 @@ const props = defineProps({ // Calculated balance variable let balance = props.budget - props.expenses // Reactive variable for determining background color -const iRef = ref<Element | null>(null); +const iRef = ref<Element | null>(null) /** * Checks if the balance is positive, and depending on the value @@ -38,12 +38,11 @@ const iRef = ref<Element | null>(null); onMounted(() => { if (iRef.value !== null && balance >= 0) { // By default, the background is set to red - const element = iRef.value as HTMLElement; - element.style.backgroundColor = 'rgba(34, 231, 50, 0.43)'; + const element = iRef.value as HTMLElement + element.style.backgroundColor = 'rgba(34, 231, 50, 0.43)' } }) - /** * Navigates to the pressed budget with its id. */ @@ -55,51 +54,61 @@ const onBudgetContainerPressed = () => { * Emits an event to tell parent component to delete budget with its id. */ const onBudgetDeleted = () => { - emit('deletedBudgetEvent'); + emit('deletedBudgetEvent') } </script> <template> - <confirm-delete-modal :budget-id="id" - :modal-id="String(id)" - :budgetTitle="title" - @deletedEvent="onBudgetDeleted"/> + <confirm-delete-modal + :budget-id="id" + :modal-id="String(id)" + :budgetTitle="title" + @deletedEvent="onBudgetDeleted" + /> <div class="container-fluid row" @click="onBudgetContainerPressed"> <div class="col-12"> <div class="title-container"> - <h2>{{title}}</h2> - <p>Created {{createdAt.substring(0, 10).replace(/-/g, "/")}}</p> + <h2>{{ title }}</h2> + <p>Created {{ createdAt.substring(0, 10).replace(/-/g, '/') }}</p> </div> - <button id="deleteButton" class="btn btn-danger" data-bs-toggle="modal" :data-bs-target="'#' + id" @click.stop=";"><img src="../../assets/icons/trash-can.svg" height="20" width="20" alt="picture">Delete</button> + <button + id="deleteButton" + class="btn btn-danger" + data-bs-toggle="modal" + :data-bs-target="'#' + id" + @click.stop="" + > + <img src="../../assets/icons/trash-can.svg" height="20" width="20" alt="picture" />Delete + </button> </div> <div class="col-4 budget"> <i> - <img src="../../assets/icons/money2.svg" width="48px" height="48px"> + <img src="../../assets/icons/money2.svg" width="48px" height="48px" /> </i> <div class="budget-container"> - <h5>{{budget}} kr</h5> + <h5>{{ budget }} kr</h5> <p>Budget</p> </div> </div> <div class="col-4 expenses"> <i> - <img src="../../assets/icons/credit-card.svg" width="48px" height="48px"> + <img src="../../assets/icons/credit-card.svg" width="48px" height="48px" /> </i> <div class="expenses-container"> - <h5>{{expenses}} kr</h5> + <h5>{{ expenses }} kr</h5> <p>Utgifter</p> </div> </div> <div class="col-4 balance"> <i ref="iRef"> - <img src="../../assets/icons/scale.svg" width="48px" height="48px"> + <img src="../../assets/icons/scale.svg" width="48px" height="48px" /> </i> <div class="balance-container"> - <h5>{{balance}} kr</h5> + <h5>{{ balance }} kr</h5> <p>Saldo</p> </div> </div> @@ -107,17 +116,21 @@ const onBudgetDeleted = () => { </template> <style scoped> - -.title-container, .budget-container, .expenses-container, .balance-container { +.title-container, +.budget-container, +.expenses-container, +.balance-container { display: grid; align-self: center; } .container-fluid { - border: 4px solid #003A58; + border: 4px solid #003a58; min-height: 90px; border-radius: 15px; - transition: transform 150ms ease-in-out, border 200ms ease-in-out; + transition: + transform 150ms ease-in-out, + border 200ms ease-in-out; cursor: pointer; } @@ -126,7 +139,9 @@ const onBudgetDeleted = () => { transform: scale(1.03); } -h2, h5, p { +h2, +h5, +p { color: black; align-self: center; } @@ -139,7 +154,6 @@ i { border-radius: 7px; } - .budget i { background-color: rgba(78, 107, 239, 0.43); } @@ -185,4 +199,4 @@ div.container-fluid.row { margin-bottom: 10px; /* Add some spacing between columns */ } } -</style> \ No newline at end of file +</style> diff --git a/src/components/Budget/ExpenseBox.vue b/src/components/Budget/ExpenseBox.vue index 7cf7c976fd2385453b74ddde964ba596d4902c46..016167ad24245b703c9a333a95d25ec0a11911cf 100644 --- a/src/components/Budget/ExpenseBox.vue +++ b/src/components/Budget/ExpenseBox.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { ref } from 'vue' -const emit = defineEmits(['deleteEvent', 'editEvent']); +const emit = defineEmits(['deleteEvent', 'editEvent']) const props = defineProps({ id: { type: Number, @@ -45,27 +45,53 @@ const emitEditEvent = () => { <template> <div class="expense-container"> - <p>{{index + 1}}</p> - <p>{{description}}</p> - <p>{{amount}} kr</p> - <button class="btn btn-success" data-bs-toggle="collapse" :data-bs-target="'#' + index" aria-expanded="false" aria-controls="editBudgetCollapse"> - <img src="../../assets/icons/edit-button.svg" alt="Edit" height="18" width="18"> + <p>{{ index + 1 }}</p> + <p>{{ description }}</p> + <p>{{ amount }} kr</p> + <button + class="btn btn-success" + data-bs-toggle="collapse" + :data-bs-target="'#' + index" + aria-expanded="false" + aria-controls="editBudgetCollapse" + > + <img src="../../assets/icons/edit-button.svg" alt="Edit" height="18" width="18" /> Endre </button> <button class="btn btn-danger" @click="emitDeleteEvent"> - <img src="../../assets/icons/trash-can.svg" alt="Edit" height="18" width="18"> + <img src="../../assets/icons/trash-can.svg" alt="Edit" height="18" width="18" /> Slett </button> </div> - <div class="collapse" :id=String(index)> + <div class="collapse" :id="String(index)"> <div class="container collapse-container"> <form @submit.prevent="emitEditEvent"> <div class="input-group"> - <span class="input-group-text">Endre utgift {{ index+1 }} </span> - <input type="text" class="form-control" placeholder="Utgift beskrivelse" required v-model="editDescription"> - <input type="number" min="0" class="form-control" placeholder="Amount (kr)" required v-model="editAmount"> - <button type="submit" class="btn btn-primary" data-bs-toggle="collapse" :data-bs-target="'#' + index">Bekreft</button> + <span class="input-group-text">Endre utgift {{ index + 1 }} </span> + <input + type="text" + class="form-control" + placeholder="Utgift beskrivelse" + required + v-model="editDescription" + /> + <input + type="number" + min="0" + class="form-control" + placeholder="Amount (kr)" + required + v-model="editAmount" + /> + <button + type="submit" + class="btn btn-primary" + data-bs-toggle="collapse" + :data-bs-target="'#' + index" + > + Bekreft + </button> </div> </form> </div> @@ -80,7 +106,7 @@ div.collapse { .expense-container { padding: 0 10px; display: grid; - grid-template-columns: 1fr 1fr 1fr .6fr .6fr; + grid-template-columns: 1fr 1fr 1fr 0.6fr 0.6fr; border-radius: 10px; background-color: #2a2a34; align-content: center; @@ -98,4 +124,4 @@ div.collapse { margin: 5px; padding: 0; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Budget/Modal/ConfirmDeleteModal.vue b/src/components/Budget/Modal/ConfirmDeleteModal.vue index 9fe9ee282a36ae49eb7d2d215bd81f7b58ae5635..c300abdcfd29721babd364e33ce7f253e58ab418 100644 --- a/src/components/Budget/Modal/ConfirmDeleteModal.vue +++ b/src/components/Budget/Modal/ConfirmDeleteModal.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { BudgetService } from '@/api' -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' const emit = defineEmits(['errorEvent', 'deletedEvent']) const props = defineProps({ @@ -23,14 +23,13 @@ const props = defineProps({ */ const deleteBudget = async () => { try { - await BudgetService.deleteBudget({budgetId: props.budgetId}) + await BudgetService.deleteBudget({ budgetId: props.budgetId }) emit('deletedEvent') } catch (error) { - handleUnknownError(error); + handleUnknownError(error) emit('errorEvent', error) } } - </script> <template> @@ -51,14 +50,12 @@ const deleteBudget = async () => { </template> <style scoped> - .modal-header { display: flex; } .modal-body { display: grid; - gap: 10px + gap: 10px; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/Budget/Modal/ImportBudgetModal.vue b/src/components/Budget/Modal/ImportBudgetModal.vue index ad4b706885b8a1ab4a6c11409f9b411d7380010d..3fcf629a05be625c515fc0148af6452578137a6a 100644 --- a/src/components/Budget/Modal/ImportBudgetModal.vue +++ b/src/components/Budget/Modal/ImportBudgetModal.vue @@ -34,16 +34,20 @@ const emitImportBudgetEvent = (budgetId: number) => { <button class="btn btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> - <h6 v-if="listOfBudgetResponseDTO.length === 0" class="text-center">Du har ingen budsjetter du kan importere</h6> + <h6 v-if="listOfBudgetResponseDTO.length === 0" class="text-center"> + Du har ingen budsjetter du kan importere + </h6> <div v-else> - <MiniBudgetBox v-for="(item, index) in listOfBudgetResponseDTO" - :key="index" - :budget-id="Number(item.id) || 0" - :budget-title="item.budgetName" - :budget-amount="Number(item.budgetAmount)" - :expense-amount="Number(item.expenseAmount)" - @importBudgetEvent="emitImportBudgetEvent" - data-bs-dismiss="modal"> + <MiniBudgetBox + v-for="(item, index) in listOfBudgetResponseDTO" + :key="index" + :budget-id="Number(item.id) || 0" + :budget-title="item.budgetName" + :budget-amount="Number(item.budgetAmount)" + :expense-amount="Number(item.expenseAmount)" + @importBudgetEvent="emitImportBudgetEvent" + data-bs-dismiss="modal" + > </MiniBudgetBox> </div> </div> @@ -53,9 +57,7 @@ const emitImportBudgetEvent = (budgetId: number) => { </template> <style scoped> - div.modal-body { padding-left: 0; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/Budget/Modal/MiniBudgetBox.vue b/src/components/Budget/Modal/MiniBudgetBox.vue index da0b1736accb5aed41e241d9528df2661c93862c..432564283215f6010563b038384c6cf8d8498299 100644 --- a/src/components/Budget/Modal/MiniBudgetBox.vue +++ b/src/components/Budget/Modal/MiniBudgetBox.vue @@ -22,7 +22,7 @@ const props = defineProps({ }) // Calculated balance from props attribute -const balance = ref<number>(props.budgetAmount - props.expenseAmount); +const balance = ref<number>(props.budgetAmount - props.expenseAmount) /** * Emits an importBudgetEvent to the parent in order to signalize that @@ -31,40 +31,38 @@ const balance = ref<number>(props.budgetAmount - props.expenseAmount); const emitImportBudgetEvent = () => { emit('importBudgetEvent', props.budgetId) } - </script> <template> <div class="container-fluid" @click="emitImportBudgetEvent"> - <h3>{{budgetTitle}}</h3> + <h3>{{ budgetTitle }}</h3> <div class="info budget"> <i> - <img src="../../../assets/icons/money2.svg" width="30px" height="30px"> + <img src="../../../assets/icons/money2.svg" width="30px" height="30px" /> </i> <div class="amount budget-container"> - <h5>{{budgetAmount}} kr</h5> + <h5>{{ budgetAmount }} kr</h5> </div> </div> <div class="info expenses"> <i> - <img src="../../../assets/icons/credit-card.svg" width="30px" height="30px"> + <img src="../../../assets/icons/credit-card.svg" width="30px" height="30px" /> </i> <div class="amount expenses-container"> - <h5>{{expenseAmount}} kr</h5> + <h5>{{ expenseAmount }} kr</h5> </div> </div> <div class="info balance"> <i ref="iRef"> - <img src="../../../assets/icons/scale.svg" width="30px" height="30px"> + <img src="../../../assets/icons/scale.svg" width="30px" height="30px" /> </i> <div class="amount balance-container"> - <h5>{{balance}} kr</h5> + <h5>{{ balance }} kr</h5> </div> </div> - </div> </template> @@ -92,7 +90,8 @@ div.amount { align-content: center; } -h3, h5 { +h3, +h5 { color: white; margin-bottom: 0; align-self: center; @@ -120,4 +119,4 @@ i { .balance i { background-color: rgba(232, 14, 14, 0.43); } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ChallangeCheckBox.vue b/src/components/Configuration/ChallangeCheckBox.vue index 849bd3404435999b2aed98a7b77d279b1e3a7647..89a9599f33776029692d1342c4fd59eaa0119982 100644 --- a/src/components/Configuration/ChallangeCheckBox.vue +++ b/src/components/Configuration/ChallangeCheckBox.vue @@ -1,5 +1,4 @@ <script setup lang="ts"> - const emit = defineEmits(['challengeChangedEvent']) const props = defineProps({ id: { @@ -32,21 +31,30 @@ const onChallengeChanged = (event: any) => { const data = [props.enumValue, value] emit('challengeChangedEvent', data) } - </script> <template> <div class="col-auto"> - <input @change="onChallengeChanged" type="checkbox" class="btn-check" :id="props.id" autocomplete="off"> - <label class="btn btn-outline-primary align-items-center justify-content-center" :for="props.id">{{ props.text }}</label> + <input + @change="onChallengeChanged" + type="checkbox" + class="btn-check" + :id="props.id" + autocomplete="off" + /> + <label + class="btn btn-outline-primary align-items-center justify-content-center" + :for="props.id" + >{{ props.text }}</label + > </div> </template> <style scoped> label { - margin: 5px + margin: 5px; } div.col-auto { padding: 0; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationParent.vue b/src/components/Configuration/ConfigurationParent.vue index 4d01b6193fb46d9e5d684a6de71d587f9c6d6895..986808f1d16883196544f71632fa938c7c5232a2 100644 --- a/src/components/Configuration/ConfigurationParent.vue +++ b/src/components/Configuration/ConfigurationParent.vue @@ -7,15 +7,22 @@ import { useRoute } from 'vue-router' const router = useRouter() // The configuration steps with path and order value. -const configurationSteps = {'/bank-account': 1,'/commitment': 2, '/experience': 3, '/suitable-challenges': 4, '/first-saving-goal': 5, '/finished-configuration': 6} +const configurationSteps = { + '/bank-account': 1, + '/commitment': 2, + '/experience': 3, + '/suitable-challenges': 4, + '/first-saving-goal': 5, + '/finished-configuration': 6 +} const length = Object.keys(configurationSteps).length -let percentage = ref(1/length); +let percentage = ref(1 / length) // Initially pushes to the commitment-RouterView and sets current path to this path. router.push(Object.keys(configurationSteps)[0]) let currentRoute = useRoute() let currentPath = currentRoute.fullPath -type ConfigurationStepPath = keyof typeof configurationSteps; +type ConfigurationStepPath = keyof typeof configurationSteps /** * Sets the current path variable to the child component's route path. @@ -25,18 +32,17 @@ type ConfigurationStepPath = keyof typeof configurationSteps; */ const onNewRouteEvent = (path: ConfigurationStepPath) => { currentPath = path - percentage.value = (1/length) * configurationSteps[path] + percentage.value = (1 / length) * configurationSteps[path] } - </script> <template> <div class="container"> <div class="progress-bar-container"> - <ProgressBar id="progressbar" :percentage="percentage"/> + <ProgressBar id="progressbar" :percentage="percentage" /> </div> <div class="configuration-container"> - <RouterView @changeRouterEvent="onNewRouteEvent"/> + <RouterView @changeRouterEvent="onNewRouteEvent" /> </div> </div> </template> @@ -50,4 +56,4 @@ const onNewRouteEvent = (path: ConfigurationStepPath) => { #progressbar { padding-top: 2rem; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationProgressBar.vue b/src/components/Configuration/ConfigurationProgressBar.vue index 3f846982e581645e15c00c0cd4a46f4287df07d3..6f6e16958605353e64bd47bf5d667303bf581078 100644 --- a/src/components/Configuration/ConfigurationProgressBar.vue +++ b/src/components/Configuration/ConfigurationProgressBar.vue @@ -10,15 +10,19 @@ const props = defineProps({ <template> <div class="container"> <div class="progress"> - <div id="configuration-progress" - class="progress-bar progress-bar-striped bg-info progress-bar-animated" - role="progressbar" - :aria-valuenow="props.percentage" - :style="{ width: Math.round(props.percentage*100) + '%' }" - aria-valuemin="0" - aria-valuemax="100"/> + <div + id="configuration-progress" + class="progress-bar progress-bar-striped bg-info progress-bar-animated" + role="progressbar" + :aria-valuenow="props.percentage" + :style="{ width: Math.round(props.percentage * 100) + '%' }" + aria-valuemin="0" + aria-valuemax="100" + /> </div> - <label class="row text-info font-bold display-5">{{ Math.round(props.percentage*100) + '%' }} Fullført</label> + <label class="row text-info font-bold display-5" + >{{ Math.round(props.percentage * 100) + '%' }} Fullført</label + > </div> </template> @@ -31,4 +35,4 @@ const props = defineProps({ .row { justify-content: center; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationSteps/BankAccount.vue b/src/components/Configuration/ConfigurationSteps/BankAccount.vue index 9ffde7b48fd03b8dca31c1e69b29f5decb9999ac..33fa9c4c2564756c698223e73d2b0224b8ef07a9 100644 --- a/src/components/Configuration/ConfigurationSteps/BankAccount.vue +++ b/src/components/Configuration/ConfigurationSteps/BankAccount.vue @@ -6,13 +6,13 @@ import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' import { useConfigurationStore } from '@/stores/ConfigurationStore' import { AccountControllerService } from '@/api' -const router = useRouter(); +const router = useRouter() // Declaring reactive variables -const formRef = ref(); -const checkingAccount = ref<string>(''); -const savingsAccount = ref<string>(''); -let errorMsg = ref<string>(''); +const formRef = ref() +const checkingAccount = ref<string>('') +const savingsAccount = ref<string>('') +let errorMsg = ref<string>('') // Updates progress bar in the parent Configuration component. const emit = defineEmits(['changeRouterEvent']) @@ -42,25 +42,25 @@ const handleSavingInputEvent = (newValue: any) => { * and navigates the user to the "/commitment" route. */ const handleSubmit = async () => { - formRef.value.classList.add("was-validated") - const form = formRef.value; + formRef.value.classList.add('was-validated') + const form = formRef.value if (form.checkValidity()) { - errorMsg.value = ''; + errorMsg.value = '' try { - await AccountControllerService.getAccountsByBban({bban: Number(checkingAccount.value)}) + await AccountControllerService.getAccountsByBban({ bban: Number(checkingAccount.value) }) } catch (error) { - errorMsg.value = "Fant ikke forbrukskonto" + errorMsg.value = 'Fant ikke forbrukskonto' return } try { - await AccountControllerService.getAccountsByBban({bban: Number(savingsAccount.value)}) + await AccountControllerService.getAccountsByBban({ bban: Number(savingsAccount.value) }) } catch (error) { - errorMsg.value = "Fant ikke sparekonto" + errorMsg.value = 'Fant ikke sparekonto' return } useConfigurationStore().setChekingAccountBBAN(Number(checkingAccount.value)) useConfigurationStore().setSavingsAccountBBAN(Number(savingsAccount.value)) - await router.push("/commitment") + await router.push('/commitment') } } </script> @@ -71,29 +71,33 @@ const handleSubmit = async () => { Velg forburkskonto og sparekonto </h3> <form ref="formRef"> - <BaseInput data-cy="spending-account-input" - :model-value="checkingAccount" - @input-change-event="handleSpendingInputEvent" - id="spending-account-base-input" - input-id="spending-account-input" - type="number" - min="10000000000" - max="99999999999" - label="Forbrukskonto" - placeholder="Skriv inn din brukskonto" - invalid-message="Vennligst skriv inn din brukskonto (11 siffer)"/> + <BaseInput + data-cy="spending-account-input" + :model-value="checkingAccount" + @input-change-event="handleSpendingInputEvent" + id="spending-account-base-input" + input-id="spending-account-input" + type="number" + min="10000000000" + max="99999999999" + label="Forbrukskonto" + placeholder="Skriv inn din brukskonto" + invalid-message="Vennligst skriv inn din brukskonto (11 siffer)" + /> - <BaseInput data-cy="savings-account-input" - :model-value="savingsAccount" - @input-change-event="handleSavingInputEvent" - id="saving-account-base-input" - input-id="savings-account-input" - type="number" - min="10000000000" - max="99999999999" - label="Sparekonto" - placeholder="Skriv inn din sparekonto" - invalid-message="Vennligst skriv inn din sparekonto (11 siffer)"/> + <BaseInput + data-cy="savings-account-input" + :model-value="savingsAccount" + @input-change-event="handleSavingInputEvent" + id="saving-account-base-input" + input-id="savings-account-input" + type="number" + min="10000000000" + max="99999999999" + label="Sparekonto" + placeholder="Skriv inn din sparekonto" + invalid-message="Vennligst skriv inn din sparekonto (11 siffer)" + /> </form> <div style="color: red">{{ errorMsg }}</div> <div class="confirm-button-container"> @@ -104,12 +108,13 @@ const handleSubmit = async () => { <style scoped> #confirmButton { - margin: 2rem 0 ; + margin: 2rem 0; height: 38px; width: 300px; } -#spending-account-base-input, #spending-account-base-input { +#spending-account-base-input, +#spending-account-base-input { margin: 1rem 0; } @@ -117,4 +122,4 @@ const handleSubmit = async () => { display: flex; justify-content: center; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue b/src/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue index 836ec18e28f118e65a09a76e803cdac2317d5700..158b1a63edea9336257ee82adc9cc4fc902376ab 100644 --- a/src/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue +++ b/src/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { ref } from 'vue' import { useConfigurationStore } from '@/stores/ConfigurationStore' -const router = useRouter(); +const router = useRouter() // Updates progress bar in the parent Configuration component. const emit = defineEmits(['changeRouterEvent']) @@ -15,7 +15,7 @@ const formRef = ref() const lowRef = ref() const mediumRef = ref() const highRef = ref() -let errorMsg = ref(); +let errorMsg = ref() /** * Validates the commitment form radio buttons and updates the commitment choice in the store. @@ -24,20 +24,18 @@ let errorMsg = ref(); * an error message prompting the user to select an option before continuing. */ const handleSubmit = () => { - const form = formRef.value; + const form = formRef.value if (form.checkValidity()) { - let choice = ''; + let choice = '' if (lowRef.value.checked) choice = 'LITTLE' else if (mediumRef.value.checked) choice = 'SOME' else if (highRef.value.checked) choice = 'MUCH' useConfigurationStore().setCommitment(choice) router.push('/experience') - } - else { + } else { errorMsg.value = 'Please select an option before continuing' } } - </script> <template> @@ -46,16 +44,50 @@ const handleSubmit = () => { I hvilken grad er du villig til å gjøre endringer? </h3> <form class="btn-group-vertical" ref="formRef"> + <input + ref="lowRef" + type="radio" + class="btn-check" + name="commitment" + id="btn-check-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check-outlined" + >Lav</label + > - <input ref="lowRef" type="radio" class="btn-check" name="commitment" id="btn-check-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Lav</label> - - <input ref="mediumRef" type="radio" class="btn-check" name="commitment" id="btn-check2-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Middels</label> - - <input ref="highRef" type="radio" class="btn-check" name="commitment" id="btn-check3-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">Høy</label> + <input + ref="mediumRef" + type="radio" + class="btn-check" + name="commitment" + id="btn-check2-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check2-outlined" + >Middels</label + > + <input + ref="highRef" + type="radio" + class="btn-check" + name="commitment" + id="btn-check3-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check3-outlined" + >Høy</label + > </form> <p class="text-danger">{{ errorMsg }}</p> <div class="confirm-button-container"> @@ -64,7 +96,6 @@ const handleSubmit = () => { </div> </template> - <style scoped> div.container { display: flex; @@ -82,4 +113,4 @@ div.container { display: flex; justify-content: center; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue b/src/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue index a481e313db0fbd72e911e898fe8cf2338f1f382a..9d6595707c301684e9ab4ae1ce7cd11158129cd3 100644 --- a/src/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue +++ b/src/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { ref } from 'vue' import { useConfigurationStore } from '@/stores/ConfigurationStore' -const router = useRouter(); +const router = useRouter() // Updates progress bar in the parent Configuration component. const emit = defineEmits(['changeRouterEvent']) @@ -15,7 +15,7 @@ const formRef = ref() const beginnerRef = ref() const someExperienceRef = ref() const expertRef = ref() -let errorMsg = ref(); +let errorMsg = ref() /** * Validates the experience form radio buttons and updates the commitment choice in the store. @@ -24,7 +24,7 @@ let errorMsg = ref(); * an error message prompting the user to select an option before continuing. */ const handleSubmit = () => { - const form = formRef.value; + const form = formRef.value if (form.checkValidity()) { let choice = '' if (beginnerRef.value.checked) choice = 'NONE' @@ -32,12 +32,10 @@ const handleSubmit = () => { else if (expertRef.value.checked) choice = 'EXPERT' useConfigurationStore().setExperience(choice) router.push('/suitable-challenges') - } - else { + } else { errorMsg.value = 'Please select an option before continuing' } } - </script> <template> @@ -49,21 +47,56 @@ const handleSubmit = () => { </div> <form class="btn-group-vertical" ref="formRef"> + <input + ref="beginnerRef" + type="radio" + class="btn-check" + name="experience" + id="btn-check-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check-outlined" + >Lite</label + > - <input ref="beginnerRef" type="radio" class="btn-check" name="experience" id="btn-check-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Lite</label> - - <input ref="someExperienceRef" type="radio" class="btn-check" name="experience" id="btn-check2-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Noe</label> + <input + ref="someExperienceRef" + type="radio" + class="btn-check" + name="experience" + id="btn-check2-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check2-outlined" + >Noe</label + > - <input ref="expertRef" type="radio" class="btn-check" name="experience" id="btn-check3-outlined" autocomplete="off" required> - <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">Ekspert</label> + <input + ref="expertRef" + type="radio" + class="btn-check" + name="experience" + id="btn-check3-outlined" + autocomplete="off" + required + /> + <label + class="btn btn-outline-primary d-flex align-items-center justify-content-center" + for="btn-check3-outlined" + >Ekspert</label + > </form> <p class="text-danger">{{ errorMsg }}</p> <div class="confirm-button-container"> - <BaseButton id="confirmButton" @click="handleSubmit" button-text="Fortsett"/> + <BaseButton id="confirmButton" @click="handleSubmit" button-text="Fortsett" /> </div> -</div> + </div> </template> <style scoped> @@ -83,4 +116,4 @@ div.container { display: flex; justify-content: center; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue index 66e57f6eff167de937da6d2091ac47104d78b6f8..af881c4288b24a1958f71e9db21ec6814d1cfe4b 100644 --- a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue +++ b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue @@ -3,10 +3,10 @@ import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' import { ref } from 'vue' import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' import { useRouter } from 'vue-router' -import {type CreateGoalDTO, GoalService} from "@/api"; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import { type CreateGoalDTO, GoalService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const router = useRouter(); +const router = useRouter() // Updates progress bar in the parent Configuration component const emit = defineEmits(['changeRouterEvent']) @@ -18,7 +18,7 @@ const titleRef = ref<string>() let descriptionRef = ref<string>() const sumRef = ref<number>() const dateRef = ref<string>() -const errorMessage = ref("") +const errorMessage = ref('') /** * Adds the "was-validated" class to the form element, validates the form, @@ -27,10 +27,10 @@ const errorMessage = ref("") */ const handleSubmit = async () => { // Check form validation - formRef.value.classList.add("was-validated") + formRef.value.classList.add('was-validated') const form = formRef.value if (!form.checkValidity()) { - return; + return } // Declares the goal payload @@ -38,16 +38,16 @@ const handleSubmit = async () => { name: titleRef.value, description: descriptionRef.value, targetAmount: sumRef.value, - targetDate: dateRef.value + " 00:00:00.000000000", - }; + targetDate: dateRef.value + ' 00:00:00.000000000' + } try { // Creates new goal with the payload - await GoalService.createGoal({ requestBody: createGoalPayload }); - await router.push("/") + await GoalService.createGoal({ requestBody: createGoalPayload }) + await router.push('/') } catch (error: any) { - handleUnknownError(error); - errorMessage.value = error.message; + handleUnknownError(error) + errorMessage.value = error.message } } @@ -57,15 +57,15 @@ const handleSubmit = async () => { * @returns Today's date in "YYYY-MM-DD" format. */ const getTodayDate = () => { - const today = new Date(); - const year = today.getFullYear(); - let month: string | number = today.getMonth() + 1; - let day: string | number = today.getDate(); + const today = new Date() + const year = today.getFullYear() + let month: string | number = today.getMonth() + 1 + let day: string | number = today.getDate() // Ensure month and day are in double digits - month = month < 10 ? `0${month}` : month; - day = day < 10 ? `0${day}` : day; - return `${year}-${month}-${day}`; -}; + month = month < 10 ? `0${month}` : month + day = day < 10 ? `0${day}` : day + return `${year}-${month}-${day}` +} /** * Handles the input event for the goal title. @@ -73,7 +73,7 @@ const getTodayDate = () => { * @param newTitle The new title value entered by the user. */ const handleTitleInputEvent = (newTitle: string) => { - titleRef.value = newTitle; + titleRef.value = newTitle } /** @@ -82,7 +82,7 @@ const handleTitleInputEvent = (newTitle: string) => { * @param newDate The new date value entered by the user. */ const handleDateInputEvent = (newDate: string) => { - dateRef.value = newDate; + dateRef.value = newDate } /** @@ -91,54 +91,56 @@ const handleDateInputEvent = (newDate: string) => { * @param newSum The new sum value entered by the user. */ const handleSumInputEvent = (newSum: number) => { - sumRef.value = newSum; + sumRef.value = newSum } - </script> <template> - <div class="container"> <div> - <h3 class="d-flex align-items-center justify-content-center"> - Nå gjenstår det kun ett steg - </h3> - <h5 class="d-flex align-items-center justify-content-center"> - Lag ditt første sparemål - </h5> + <h3 class="d-flex align-items-center justify-content-center">Nå gjenstår det kun ett steg</h3> + <h5 class="d-flex align-items-center justify-content-center">Lag ditt første sparemål</h5> </div> <form ref="formRef" id="loginForm"> - <BaseInput :model-value="titleRef" - @input-change-event="handleTitleInputEvent" - id="titleInput" - input-id="title" - label="Navn" - placeholder="Oppgi navnet på sparemålet"/> + <BaseInput + :model-value="titleRef" + @input-change-event="handleTitleInputEvent" + id="titleInput" + input-id="title" + label="Navn" + placeholder="Oppgi navnet på sparemålet" + /> <div> <label for="description">Description</label> - <textarea v-model="descriptionRef" - type="text" - maxlength="150" - class="form-control" - placeholder="Oppgi en beskrivelse på sparemålet her (valgfritt)" - id="description"/> + <textarea + v-model="descriptionRef" + type="text" + maxlength="150" + class="form-control" + placeholder="Oppgi en beskrivelse på sparemålet her (valgfritt)" + id="description" + /> </div> - <BaseInput :model-value="dateRef" - @input-change-event="handleDateInputEvent" - id="dueDateInput" - input-id="dueDate" - type="date" - :min="getTodayDate()" - label="Utløpsdato"/> - <BaseInput :model-value="sumRef" - @input-change-event="handleSumInputEvent" - id="sumToSaveInput" - input-id="sumToSpareInput" - type="number" - label="Sum" - min="0" - placeholder="Oppgi summen du ønsker å spare (kr)"/> + <BaseInput + :model-value="dateRef" + @input-change-event="handleDateInputEvent" + id="dueDateInput" + input-id="dueDate" + type="date" + :min="getTodayDate()" + label="Utløpsdato" + /> + <BaseInput + :model-value="sumRef" + @input-change-event="handleSumInputEvent" + id="sumToSaveInput" + input-id="sumToSpareInput" + type="number" + label="Sum" + min="0" + placeholder="Oppgi summen du ønsker å spare (kr)" + /> </form> <div class="confirm-button-container"> @@ -148,12 +150,13 @@ const handleSumInputEvent = (newSum: number) => { {{ errorMessage }} </div> </div> - </template> <style scoped> - -#titleInput, #description, #dueDateInput, #sumToSaveInput { +#titleInput, +#description, +#dueDateInput, +#sumToSaveInput { margin-top: 5px; } @@ -172,5 +175,4 @@ const handleSumInputEvent = (newSum: number) => { display: flex; justify-content: center; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index e7ee258697099a7d81aeb0b2e63a9697461cd493..a8868846f57663bfc6a8d4eb8b1261c73bb8950a 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -8,7 +8,7 @@ import { useUserInfoStore } from '@/stores/UserStore' import { AuthenticationService, OpenAPI, type SignUpRequest, UserService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const router = useRouter(); +const router = useRouter() // Updates progress bar in the parent Configuration component. const emit = defineEmits(['changeRouterEvent']) @@ -19,28 +19,45 @@ let chosenChallenges = ref<string[]>([]) let errorMsg = ref('') // Represents a list of available challenges. -const challenges: string[] = ['NO_COFFEE' , 'NO_CAR' , 'SHORTER_SHOWER' , 'SPEND_LESS_ON_FOOD' , 'BUY_USED_CLOTHES' , 'LESS_SHOPPING' , 'DROP_SUBSCRIPTION' , 'SELL_SOMETHING' , 'BUY_USED' , 'EAT_PACKED_LUNCH' , 'STOP_SHOPPING' , 'ZERO_SPENDING' , 'RENT_YOUR_STUFF' , 'MEATLESS' , 'SCREEN_TIME_LIMIT' , 'UNPLUGGED_ENTERTAINMENT'] +const challenges: string[] = [ + 'NO_COFFEE', + 'NO_CAR', + 'SHORTER_SHOWER', + 'SPEND_LESS_ON_FOOD', + 'BUY_USED_CLOTHES', + 'LESS_SHOPPING', + 'DROP_SUBSCRIPTION', + 'SELL_SOMETHING', + 'BUY_USED', + 'EAT_PACKED_LUNCH', + 'STOP_SHOPPING', + 'ZERO_SPENDING', + 'RENT_YOUR_STUFF', + 'MEATLESS', + 'SCREEN_TIME_LIMIT', + 'UNPLUGGED_ENTERTAINMENT' +] /** * Mapping between challenge enum and norwegian translation. */ const challengeMapper: any = { - "NO_COFFEE": "Droppe kaffe", - "NO_CAR": "Droppe bil", - "SHORTER_SHOWER": "Ta kortere dusjer", - "SPEND_LESS_ON_FOOD": "Bruk mindre penger på mat", - "BUY_USED_CLOTHES": "Kjøp brukte klær", - "LESS_SHOPPING": "Handle mindre", - "DROP_SUBSCRIPTION": "Si opp abonnement", - "SELL_SOMETHING": "Selg noe", - "BUY_USED": "Kjøp brukt", - "EAT_PACKED_LUNCH": "Lag niste", - "STOP_SHOPPING": "Shoppestopp", - "ZERO_SPENDING": "Null-forbruk", - "RENT_YOUR_STUFF": "Lei ut ting", - "MEATLESS": "Kjøttfritt", - "SCREEN_TIME_LIMIT": "Skjerm tidsgrense", - "UNPLUGGED_ENTERTAINMENT": "Strømløs underholdning" + NO_COFFEE: 'Droppe kaffe', + NO_CAR: 'Droppe bil', + SHORTER_SHOWER: 'Ta kortere dusjer', + SPEND_LESS_ON_FOOD: 'Bruk mindre penger på mat', + BUY_USED_CLOTHES: 'Kjøp brukte klær', + LESS_SHOPPING: 'Handle mindre', + DROP_SUBSCRIPTION: 'Si opp abonnement', + SELL_SOMETHING: 'Selg noe', + BUY_USED: 'Kjøp brukt', + EAT_PACKED_LUNCH: 'Lag niste', + STOP_SHOPPING: 'Shoppestopp', + ZERO_SPENDING: 'Null-forbruk', + RENT_YOUR_STUFF: 'Lei ut ting', + MEATLESS: 'Kjøttfritt', + SCREEN_TIME_LIMIT: 'Skjerm tidsgrense', + UNPLUGGED_ENTERTAINMENT: 'Strømløs underholdning' } /** @@ -56,7 +73,7 @@ const onChangedChallengeEvent = (value: never) => { } // if challenge is unchecked then remove it from the chosenChallenges variable else { - chosenChallenges.value = chosenChallenges.value.filter(item => item !== value[0]); + chosenChallenges.value = chosenChallenges.value.filter((item) => item !== value[0]) } console.log(chosenChallenges.value) } @@ -71,7 +88,7 @@ const signUpUser = async () => { useConfigurationStore().setChallenges(chosenChallenges.value) // Declares the request payload - const signUpPayLoad: SignUpRequest = { + const signUpPayLoad: SignUpRequest = { firstName: useUserInfoStore().getFirstName, lastName: useUserInfoStore().getLastname, email: useUserInfoStore().getEmail, @@ -82,19 +99,19 @@ const signUpUser = async () => { challengeTypes: useConfigurationStore().getChallenges }, checkingAccountBBAN: useConfigurationStore().getCheckingAccountBBAN, - savingsAccountBBAN: useConfigurationStore().getSavingsAccountBBAN, - }; + savingsAccountBBAN: useConfigurationStore().getSavingsAccountBBAN + } - let response = await AuthenticationService.signup({ requestBody: signUpPayLoad }); + let response = await AuthenticationService.signup({ requestBody: signUpPayLoad }) if (response.token == null) { - errorMsg.value = 'A valid token could not be created'; - return; + errorMsg.value = 'A valid token could not be created' + return } - OpenAPI.TOKEN = response.token; + OpenAPI.TOKEN = response.token useUserInfoStore().setUserInfo({ accessToken: response.token, - role: response.role, - }); + role: response.role + }) } /** @@ -110,16 +127,14 @@ const handleSubmit = async () => { } useConfigurationStore().setChallenges(chosenChallenges.value) try { - await signUpUser(); + await signUpUser() useUserInfoStore().resetPassword() - await router.push("/first-saving-goal") - + await router.push('/first-saving-goal') } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } - </script> <template> @@ -131,17 +146,19 @@ const handleSubmit = async () => { </div> <div class="challenge-container row justify-content-center"> - <ChallangeCheckBox v-for="(item, index) in challenges" - :id="String(index)" - :text="challengeMapper[item]" - :enum-value="item" - @challengeChangedEvent="onChangedChallengeEvent"/> + <ChallangeCheckBox + v-for="(item, index) in challenges" + :id="String(index)" + :text="challengeMapper[item]" + :enum-value="item" + @challengeChangedEvent="onChangedChallengeEvent" + /> </div> <p class="text-danger">{{ errorMsg }}</p> <div class="confirm-button-container"> - <BaseButton id="confirmButton" @click="handleSubmit" button-text="Fortsett"/> + <BaseButton id="confirmButton" @click="handleSubmit" button-text="Fortsett" /> </div> </div> </template> @@ -162,5 +179,4 @@ const handleSubmit = async () => { display: flex; justify-content: center; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/Exceptions/ErrorBoundaryCatcher.vue b/src/components/Exceptions/ErrorBoundaryCatcher.vue index f22d58617a6b405ab272879295371708174bb7ca..9bd4a108c6319b6adf845b8ad2e800856506b014 100644 --- a/src/components/Exceptions/ErrorBoundaryCatcher.vue +++ b/src/components/Exceptions/ErrorBoundaryCatcher.vue @@ -1,15 +1,18 @@ <template> - <error-box :error-message="errorStore.getFirstError" @update:errorMessage="errorStore.removeCurrentError" /> + <error-box + :error-message="errorStore.getFirstError" + @update:errorMessage="errorStore.removeCurrentError" + /> <slot></slot> </template> <script setup lang="ts"> -import { onErrorCaptured } from 'vue'; -import { useErrorStore } from '@/stores/ErrorStore'; -import ErrorBox from '@/components/Exceptions/ErrorBox.vue'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import { onErrorCaptured } from 'vue' +import { useErrorStore } from '@/stores/ErrorStore' +import ErrorBox from '@/components/Exceptions/ErrorBox.vue' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const errorStore = useErrorStore(); +const errorStore = useErrorStore() /** * Handles errors captured during component lifecycle hooks or during component rendering. @@ -20,8 +23,8 @@ const errorStore = useErrorStore(); * @return {boolean} Returns false to indicate that the error has been handled. */ onErrorCaptured((err, _vm, _info): boolean => { - const message = handleUnknownError(err); - errorStore.addError(message); - return false; -}); -</script> \ No newline at end of file + const message = handleUnknownError(err) + errorStore.addError(message) + return false +}) +</script> diff --git a/src/components/Exceptions/ErrorBox.vue b/src/components/Exceptions/ErrorBox.vue index 5dc012e672ae7ef555ff45c74107da29f71838a4..2a1173e6e427d656581cbe24a33acdd7c94ff5c3 100644 --- a/src/components/Exceptions/ErrorBox.vue +++ b/src/components/Exceptions/ErrorBox.vue @@ -5,7 +5,11 @@ <button @click="wrapText = !wrapText" class="error-message-button"> <h4>{{ errorMessage }}</h4> </button> - <button class="error-remove-button" @click="$emit('update:errorMessage', '')" data-testid="hide-button"> + <button + class="error-remove-button" + @click="$emit('update:errorMessage', '')" + data-testid="hide-button" + > <v-icon scale="2" name="bi-dash" /> </button> </span> @@ -13,28 +17,28 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; -import { OhVueIcon, addIcons } from 'oh-vue-icons'; -import { BiDash, BiExclamationTriangle } from 'oh-vue-icons/icons'; +import { defineComponent } from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { BiDash, BiExclamationTriangle } from 'oh-vue-icons/icons' -addIcons(BiDash, BiExclamationTriangle); +addIcons(BiDash, BiExclamationTriangle) export default defineComponent({ components: { - 'v-icon': OhVueIcon, + 'v-icon': OhVueIcon }, props: { errorMessage: { type: String, - default: '', - }, + default: '' + } }, data() { return { - wrapText: false, - }; - }, -}); + wrapText: false + } + } +}) </script> <style scoped> @@ -44,7 +48,7 @@ export default defineComponent({ left: 50%; transform: translate(-50%, 0); width: min(100%, 700px); - background-color: red ; /*var(--red-color);*/ + background-color: red; /*var(--red-color);*/ padding: 7px; border-radius: 5px; z-index: 1000; @@ -87,4 +91,4 @@ export default defineComponent({ .error-box button:hover { color: rgb(40, 38, 38); } -</style> \ No newline at end of file +</style> diff --git a/src/components/Exceptions/NotFoundPage.vue b/src/components/Exceptions/NotFoundPage.vue index 882ba71bf3635cba55221df775a5a216aa17742e..b69a7ee5934e67252e48a18e0808d017dad4cb3e 100644 --- a/src/components/Exceptions/NotFoundPage.vue +++ b/src/components/Exceptions/NotFoundPage.vue @@ -1,50 +1,46 @@ <template> - <div class="container-fluid"> - <div class="row"> - <div class="col-md-12"> - <div class="error-template text-center"> - <h1> - Oi!</h1> - <h2 data-cy="404-error"> - 404 Ikke funnet</h2> - <div class="error-details"> - Beklager, det har oppstått en feil. Forespurt side ikke funnet! - </div> - <div class="error-actions"> - <BaseButton data-cy="to-home" button-text="Ta meg hjem" @click="home" /> - </div> - </div> - </div> + <div class="container-fluid"> + <div class="row"> + <div class="col-md-12"> + <div class="error-template text-center"> + <h1>Oi!</h1> + <h2 data-cy="404-error">404 Ikke funnet</h2> + <div class="error-details"> + Beklager, det har oppstått en feil. Forespurt side ikke funnet! + </div> + <div class="error-actions"> + <BaseButton data-cy="to-home" button-text="Ta meg hjem" @click="home" /> + </div> </div> + </div> </div> + </div> </template> - <script setup lang="ts"> -import { useRouter } from 'vue-router'; -import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; +import { useRouter } from 'vue-router' +import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' -const router = useRouter(); +const router = useRouter() /** * Navigates to home page. */ const home = () => { - router.push('/'); -}; + router.push('/') +} </script> - <style scoped> .error-template { - text-align: center; - /* Ensures all text and inline elements within are centered */ - display: flex; - flex-direction: column; - align-items: center; - /* Aligns child elements (which are block-level) centrally */ - justify-content: center; - /* Optional: if you want vertical centering */ - margin: 2rem; + text-align: center; + /* Ensures all text and inline elements within are centered */ + display: flex; + flex-direction: column; + align-items: center; + /* Aligns child elements (which are block-level) centrally */ + justify-content: center; + /* Optional: if you want vertical centering */ + margin: 2rem; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Exceptions/UnauthorizedPage.vue b/src/components/Exceptions/UnauthorizedPage.vue index 949ff41dfce485ef38237fa7edccf4ff0ed879c9..3c423ebf401c9d92350b1d345cd219bbc4f6e0f7 100644 --- a/src/components/Exceptions/UnauthorizedPage.vue +++ b/src/components/Exceptions/UnauthorizedPage.vue @@ -1,36 +1,35 @@ <template> - <body class="bg-dark text-white py-5"> - <div class="container py-5"> - <div class="row"> - <div class="col-md-2 text-center"> - <p><img src="../../assets/icons/danger.svg" alt="fare"> <br/>Statuskode: 403</p> - </div> - <div class="col-md-10"> - <h3>OOPS!!! Beklager...</h3> - <p>Beklager, din tilgang er nektet av sikkerhetsgrunner på serveren vår og også våre sensitive data.<br/>Vennligst gå tilbake til startsiden for å fortsette å surfe.</p> - <BaseButton :button-text="'Ta meg hjem'" @click="home" /> - </div> - </div> + <body class="bg-dark text-white py-5"> + <div class="container py-5"> + <div class="row"> + <div class="col-md-2 text-center"> + <p><img src="../../assets/icons/danger.svg" alt="fare" /> <br />Statuskode: 403</p> </div> - </body> + <div class="col-md-10"> + <h3>OOPS!!! Beklager...</h3> + <p> + Beklager, din tilgang er nektet av sikkerhetsgrunner på serveren vår og også våre + sensitive data.<br />Vennligst gå tilbake til startsiden for å fortsette å surfe. + </p> + <BaseButton :button-text="'Ta meg hjem'" @click="home" /> + </div> + </div> + </div> + </body> </template> - <script setup lang="ts"> -import { useRouter } from 'vue-router'; -import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; +import { useRouter } from 'vue-router' +import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' -const router = useRouter(); +const router = useRouter() /** * Navigates to home page. */ const home = () => { - router.push('/'); -}; + router.push('/') +} </script> - -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/components/Exceptions/unkownErrorHandler.ts b/src/components/Exceptions/unkownErrorHandler.ts index bcb29854fbea2b0d15c4a5cb9dc32f6b367461bf..73b8302b82ae7c9d9d03a2dee467cd8314952ecf 100644 --- a/src/components/Exceptions/unkownErrorHandler.ts +++ b/src/components/Exceptions/unkownErrorHandler.ts @@ -1,5 +1,5 @@ -import { ApiError as BackendApiError } from '@/api'; -import { AxiosError } from 'axios'; +import { ApiError as BackendApiError } from '@/api' +import { AxiosError } from 'axios' import router from '@/router' import { useUserInfoStore } from '@/stores/UserStore' @@ -11,17 +11,17 @@ import { useUserInfoStore } from '@/stores/UserStore' */ const handleUnknownError = (error: any): string => { if (error instanceof AxiosError) { - return error.code!!; + return error.code!! } else if (error instanceof BackendApiError) { if (error.body.status == 403) { - router.push("/login"); - useUserInfoStore().clearUserInfo(); + router.push('/login') + useUserInfoStore().clearUserInfo() } else if (error.body.status == 401) { - router.push("/roadmap"); + router.push('/roadmap') } - return error.body.message ?? error.body; + return error.body.message ?? error.body } - return error; -}; + return error +} -export default handleUnknownError; \ No newline at end of file +export default handleUnknownError diff --git a/src/components/Friends/UserFriends.vue b/src/components/Friends/UserFriends.vue index 9767d8eb429321ef79cc608902d28a59b0d43888..a0199fac65d6b53c89b06524a807bc61ef7a9909 100644 --- a/src/components/Friends/UserFriends.vue +++ b/src/components/Friends/UserFriends.vue @@ -1,147 +1,210 @@ <template> - <div class="container" style="margin-bottom: 3rem;"> - <h1 class="my-3">Dine venner</h1> - <div> - <button class="btn pull-right" @click="addNewFriends" id="addFriend">+ Legg til venn</button> - <div class="my-3"> - <button class="btn pages" @click="setupFriends" :class="{ 'active-tab': showFriends }"> - Dine venner - </button> - <button class="btn pages" @click="requestFriend" :class="{ 'active-tab': showRequests }"> - Venneforespørsler - </button> - </div> - </div> - <div v-if="showFriends"> - <div v-if="elementsInFriends"> - <div class="row"> - <div class="friendBox d-flex flex-wrap" v-for="friend in friends" :key="friend.id"> - <div class="card card-one"> - <div class="header"> - <div v-if="friend.profileImage" class="avatar"> - <img :src="apiUrl + '/api/images/' + friend.profileImage" alt=""> - </div> - <div v-else class="avatar"> - <img :src="'../src/assets/userprofile.png'" alt=""> - </div> - </div> - <h3><router-link to="" data-cy="navigateToFriend" href="#" class="btn stretched-link" - id="profileName" @click="navigateToFriend(friend.id)">{{ - friend.firstName }} {{ friend.lastName }}</router-link></h3> - <div class="desc">{{ friend.firstName }} {{ friend.lastName }}</div> - <div class="contacts"> - <a class="text removeFriend" data-bs-toggle="collapse" - :href="'#collapseExample' + friend.id" role="button" aria-expanded="false" - :aria-controls="'collapseExample' + friend.id"> - Se mer - </a> - <div class="collapse" :id="'collapseExample' + friend.id"> - <button class="btn btn-danger" @click="removeFriend(friend.id)"> - <h5><img src="@/assets/icons/remove-white.svg" style="width: 30px"> Fjern venn - </h5> - </button> - </div> - </div> - </div> - </div> + <div class="container" style="margin-bottom: 3rem"> + <h1 class="my-3">Dine venner</h1> + <div> + <button class="btn pull-right" @click="addNewFriends" id="addFriend">+ Legg til venn</button> + <div class="my-3"> + <button class="btn pages" @click="setupFriends" :class="{ 'active-tab': showFriends }"> + Dine venner + </button> + <button class="btn pages" @click="requestFriend" :class="{ 'active-tab': showRequests }"> + Venneforespørsler + </button> + </div> + </div> + <div v-if="showFriends"> + <div v-if="elementsInFriends"> + <div class="row"> + <div class="friendBox d-flex flex-wrap" v-for="friend in friends" :key="friend.id"> + <div class="card card-one"> + <div class="header"> + <div v-if="friend.profileImage" class="avatar"> + <img :src="apiUrl + '/api/images/' + friend.profileImage" alt="" /> </div> + <div v-else class="avatar"> + <img :src="'../src/assets/userprofile.png'" alt="" /> + </div> + </div> + <h3> + <router-link + to="" + data-cy="navigateToFriend" + href="#" + class="btn stretched-link" + id="profileName" + @click="navigateToFriend(friend.id)" + >{{ friend.firstName }} {{ friend.lastName }}</router-link + > + </h3> + <div class="desc">{{ friend.firstName }} {{ friend.lastName }}</div> + <div class="contacts"> + <a + class="text removeFriend" + data-bs-toggle="collapse" + :href="'#collapseExample' + friend.id" + role="button" + aria-expanded="false" + :aria-controls="'collapseExample' + friend.id" + > + Se mer + </a> + <div class="collapse" :id="'collapseExample' + friend.id"> + <button class="btn btn-danger" @click="removeFriend(friend.id)"> + <h5> + <img src="@/assets/icons/remove-white.svg" style="width: 30px" /> Fjern venn + </h5> + </button> + </div> + </div> </div> - <div v-else>Ingen venner</div> + </div> </div> - <div v-else-if="showRequests" class="row"> - <div class="content-body"> - <div v-if="elementsInFriendRequest" id="requests"> - <div class="request" v-for="(friend) in friendRequests" :key="friend.id"> - <div v-if="friend.profileImage !== null"><img id="profilePicture" - :src="apiUrl + '/api/images/' + friend.profileImage" alt="bruker" - class="profile-photo-lg"></div> - <div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'" alt="bruker" - class="profile-photo-lg"></div> - <h2>{{ friend.firstName }}</h2> - <button class="btn btn-success mx-2" - @click="acceptRequest(friend.id)">Godta</button> - <button class="btn btn-danger" @click="rejectRequest(friend.id)">Avslå</button> - </div> - </div> - <div v-else>Ingen venneforespørsler</div> + </div> + <div v-else>Ingen venner</div> + </div> + <div v-else-if="showRequests" class="row"> + <div class="content-body"> + <div v-if="elementsInFriendRequest" id="requests"> + <div class="request" v-for="friend in friendRequests" :key="friend.id"> + <div v-if="friend.profileImage !== null"> + <img + id="profilePicture" + :src="apiUrl + '/api/images/' + friend.profileImage" + alt="bruker" + class="profile-photo-lg" + /> </div> + <div v-else> + <img + id="profilePicture" + :src="'../src/assets/userprofile.png'" + alt="bruker" + class="profile-photo-lg" + /> + </div> + <h2>{{ friend.firstName }}</h2> + - <button class="btn btn-success mx-2" @click="acceptRequest(friend.id)">Godta</button> + <button class="btn btn-danger" @click="rejectRequest(friend.id)">Avslå</button> + </div> </div> - <div v-if="showAddFriend" class="modal" tabindex="-1" role="dialog" - style="display:block; background-color: rgba(0,0,0,0.5);"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title">Legg til venn</h5> - <button type="button" class="close btn-close" @click="showAddFriend = false" - aria-label="Close"></button> + <div v-else>Ingen venneforespørsler</div> + </div> + </div> + <div + v-if="showAddFriend" + class="modal" + tabindex="-1" + role="dialog" + style="display: block; background-color: rgba(0, 0, 0, 0.5)" + > + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">Legg til venn</h5> + <button + type="button" + class="close btn-close" + @click="showAddFriend = false" + aria-label="Close" + ></button> + </div> + <div class="modal-body d-flex justify-content-center align-items-center flex-column"> + <form + class="col-md-10 d-flex justify-content-center align-items-center flex-row my-4" + id="searchBox" + role="search" + @submit.prevent="searchProfile(searchWord)" + > + <input + class="form-control me-2 custom-border" + type="search" + placeholder="Søk" + aria-label="Søk" + v-model="searchWord" + /> + <button class="btn btn-success" type="submit">Søk</button> + </form> + <div class="col-md-12"> + <div class="people-nearby"> + <div v-for="user in searchedUsers" :key="user.id" class="nearby-user"> + <div class="row d-flex align-items-center"> + <div class="col-md-2 col-sm-2"> + <div v-if="user.profileImage !== null"> + <img + id="profilePicture" + :src="apiUrl + '/api/images/' + user.profileImage" + alt="bruker" + class="profile-photo-lg" + /> + </div> + <div v-else> + <img + id="profilePicture" + :src="'../src/assets/userprofile.png'" + alt="bruker" + class="profile-photo-lg" + /> + </div> </div> - <div class="modal-body d-flex justify-content-center align-items-center flex-column"> - <form class="col-md-10 d-flex justify-content-center align-items-center flex-row my-4" - id="searchBox" role="search" @submit.prevent="searchProfile(searchWord)"> - <input class="form-control me-2 custom-border" type="search" placeholder="Søk" - aria-label="Søk" v-model="searchWord"> - <button class="btn btn-success" type="submit">Søk</button> - </form> - <div class="col-md-12"> - <div class="people-nearby"> - <div v-for="user in searchedUsers" :key="user.id" class="nearby-user"> - <div class="row d-flex align-items-center"> - <div class="col-md-2 col-sm-2"> - <div v-if="user.profileImage !== null"><img id="profilePicture" - :src="apiUrl + '/api/images/' + user.profileImage" alt="bruker" - class="profile-photo-lg"></div> - <div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'" - alt="bruker" class="profile-photo-lg"></div> - </div> - <div class="col-md-7 col-sm-7"> - <h5><a href="#" class="profile-link" @click="toUserProfile(user.id)">{{ - user.firstName }} {{ user.lastName }}</a> - </h5> - </div> - <div class="col-md-3 col-sm-3"> - <button class="btn btn-primary pull-right" @click="addFriend(user.id)" - :disabled="friendRequestsSent[user.id]" - v-if="!friendRequestsSent[user.id]">Legg til venn</button> - <button class="btn btn-secondary pull-right" disabled - v-if="friendRequestsSent[user.id]">Forespørsel sendt</button> - </div> - </div> - </div> - </div> - </div> + <div class="col-md-7 col-sm-7"> + <h5> + <a href="#" class="profile-link" @click="toUserProfile(user.id)" + >{{ user.firstName }} {{ user.lastName }}</a + > + </h5> </div> + <div class="col-md-3 col-sm-3"> + <button + class="btn btn-primary pull-right" + @click="addFriend(user.id)" + :disabled="friendRequestsSent[user.id]" + v-if="!friendRequestsSent[user.id]" + > + Legg til venn + </button> + <button + class="btn btn-secondary pull-right" + disabled + v-if="friendRequestsSent[user.id]" + > + Forespørsel sendt + </button> + </div> + </div> </div> + </div> </div> + </div> </div> + </div> </div> + </div> </template> - - - <script setup lang="ts"> -import { type Ref, ref, onMounted } from 'vue'; -import { useRouter } from 'vue-router'; -import { FriendService, UserService } from '@/api'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import { type Ref, ref, onMounted } from 'vue' +import { useRouter } from 'vue-router' +import { FriendService, UserService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -let apiUrl = import.meta.env.VITE_APP_API_URL; +let apiUrl = import.meta.env.VITE_APP_API_URL -const router = useRouter(); +const router = useRouter() // Declaring reactive variables -const friends = ref(); -const showFriends = ref(true); -const showRequests = ref(false); -const showAddFriend = ref(false); -const friendRequests = ref([] as any); -const addFriends = ref([] as any); -const searchedUsers = ref([] as any); +const friends = ref() +const showFriends = ref(true) +const showRequests = ref(false) +const showAddFriend = ref(false) +const friendRequests = ref([] as any) +const addFriends = ref([] as any) +const searchedUsers = ref([] as any) -const friendRequestsSent: Ref<Record<number, boolean>> = ref({}); -const searchWord = ref(""); +const friendRequestsSent: Ref<Record<number, boolean>> = ref({}) +const searchWord = ref('') -const elementsInFriendRequest = ref(false); -const elementsInFriends = ref(false); +const elementsInFriendRequest = ref(false) +const elementsInFriends = ref(false) /** * Navigates to the user profile page based on the given user ID. @@ -149,9 +212,8 @@ const elementsInFriends = ref(false); * @param {number} userId The ID of the user whose profile will be navigated to. */ const toUserProfile = (userId: number) => { - router.push('/profile/' + userId); -}; - + router.push('/profile/' + userId) +} /** * Searches for user profiles based on the provided search term. @@ -159,37 +221,37 @@ const toUserProfile = (userId: number) => { * @param {string} searchTerm The term to be used for searching user profiles. */ const searchProfile = async (searchTerm: string) => { - const userPayload = { - searchTerm: searchTerm as string, - filter: 'NON_FRIENDS' as string, - }; - try { - const response = await UserService.getUsersByNameAndFilter(userPayload); - searchedUsers.value = response; - console.log(response); - } catch (error) { - handleUnknownError(error); - console.error('Failed to search for profile', error); - } -}; + const userPayload = { + searchTerm: searchTerm as string, + filter: 'NON_FRIENDS' as string + } + try { + const response = await UserService.getUsersByNameAndFilter(userPayload) + searchedUsers.value = response + console.log(response) + } catch (error) { + handleUnknownError(error) + console.error('Failed to search for profile', error) + } +} /** * Adds new friends to the user's friend list. */ const addNewFriends = async () => { - const userPayload = { - amount: 6 as number, - filter: 'NON_FRIENDS' as string, - }; - try { - const response = await UserService.getRandomUsers(userPayload); - searchedUsers.value = response; - showAddFriend.value = true; - } catch (error) { - handleUnknownError(error); - console.error('Failed to add friend', error); - } -}; + const userPayload = { + amount: 6 as number, + filter: 'NON_FRIENDS' as string + } + try { + const response = await UserService.getRandomUsers(userPayload) + searchedUsers.value = response + showAddFriend.value = true + } catch (error) { + handleUnknownError(error) + console.error('Failed to add friend', error) + } +} /** * Sends a friend request to the specified user. @@ -197,31 +259,31 @@ const addNewFriends = async () => { * @param {number} friendID The ID of the user to whom the friend request will be sent. */ async function addFriend(friendID: number) { - try { - await FriendService.addFriendRequest({ userId: friendID }); - // Use a spread to update the state and keep immutability - friendRequestsSent.value = { ...friendRequestsSent.value, [friendID]: true }; - } catch (error) { - handleUnknownError(error); - console.error('Failed to send friend request', error); - } + try { + await FriendService.addFriendRequest({ userId: friendID }) + // Use a spread to update the state and keep immutability + friendRequestsSent.value = { ...friendRequestsSent.value, [friendID]: true } + } catch (error) { + handleUnknownError(error) + console.error('Failed to send friend request', error) + } } /** * Fetches friend requests and updates the state accordingly. */ async function requestFriend() { - showRequests.value = true; - showFriends.value = false; - try { - const response = await FriendService.getFriendRequests(); - friendRequests.value = response; - elementsInFriendRequest.value = response.length > 0; - console.log("Friend requests: " + response); - } catch (error) { - handleUnknownError(error); - console.error('Failed to fetch friend requests', error); - } + showRequests.value = true + showFriends.value = false + try { + const response = await FriendService.getFriendRequests() + friendRequests.value = response + elementsInFriendRequest.value = response.length > 0 + console.log('Friend requests: ' + response) + } catch (error) { + handleUnknownError(error) + console.error('Failed to fetch friend requests', error) + } } /** @@ -230,8 +292,8 @@ async function requestFriend() { * @param friendID The ID of the friend whose profile will be navigated to. */ const navigateToFriend = (friendID: number) => { - router.push('/profile/' + friendID); -}; + router.push('/profile/' + friendID) +} /** * Removes the specified friend from the user's friend list. @@ -239,33 +301,32 @@ const navigateToFriend = (friendID: number) => { * @param friendID The ID of the friend to be removed. */ const removeFriend = async (friendID: number) => { - try { - await FriendService.deleteFriendOrFriendRequest({ friendId: friendID }); - const responseFriends = await FriendService.getFriends(); - friends.value = responseFriends; - } catch (error) { - handleUnknownError(error); - console.error('Failed to remove friend', error); - } -}; + try { + await FriendService.deleteFriendOrFriendRequest({ friendId: friendID }) + const responseFriends = await FriendService.getFriends() + friends.value = responseFriends + } catch (error) { + handleUnknownError(error) + console.error('Failed to remove friend', error) + } +} /** * Sets up the user's friends by fetching and updating the friends list. */ const setupFriends = async () => { - showFriends.value = true; - showRequests.value = false; - try { - const response = await FriendService.getFriends(); - friends.value = response; - elementsInFriends.value = response.length > 0; - console.log(response); - } catch (error) { - handleUnknownError(error); - console.error('Failed to fetch friends', error); - } -}; - + showFriends.value = true + showRequests.value = false + try { + const response = await FriendService.getFriends() + friends.value = response + elementsInFriends.value = response.length > 0 + console.log(response) + } catch (error) { + handleUnknownError(error) + console.error('Failed to fetch friends', error) + } +} /** * Accepts a friend request with the specified request ID. @@ -273,17 +334,17 @@ const setupFriends = async () => { * @param {number} requestID The ID of the friend request to be accepted. */ const acceptRequest = async (requestID: number) => { - try { - await FriendService.acceptFriendRequest({ friendId: requestID }); - const responseRequest = await FriendService.getFriendRequests(); - friendRequests.value = responseRequest; - const responseFriends = await FriendService.getFriends(); - friends.value = responseFriends; - } catch (error) { - handleUnknownError(error); - console.error('Failed to accept friend request', error); - } -}; + try { + await FriendService.acceptFriendRequest({ friendId: requestID }) + const responseRequest = await FriendService.getFriendRequests() + friendRequests.value = responseRequest + const responseFriends = await FriendService.getFriends() + friends.value = responseFriends + } catch (error) { + handleUnknownError(error) + console.error('Failed to accept friend request', error) + } +} /** * Rejects a friend request with the specified request ID. @@ -291,378 +352,375 @@ const acceptRequest = async (requestID: number) => { * @param {number} requestID The ID of the friend request to be rejected. */ const rejectRequest = async (requestID: number) => { - try { - await FriendService.deleteFriendOrFriendRequest({ friendId: requestID }); - const response = await FriendService.getFriendRequests(); - friendRequests.value = response; - } catch (error) { - handleUnknownError(error); - console.error('Failed to reject friend request', error); - } -}; + try { + await FriendService.deleteFriendOrFriendRequest({ friendId: requestID }) + const response = await FriendService.getFriendRequests() + friendRequests.value = response + } catch (error) { + handleUnknownError(error) + console.error('Failed to reject friend request', error) + } +} /** * Initializes the component by setting up the user's friends. */ onMounted(() => { - setupFriends(); -}); + setupFriends() +}) </script> - <style scoped> body { - background-color: #f0f6ff; - color: #28384d; - + background-color: #f0f6ff; + color: #28384d; } /*social */ .card-one { - position: relative; - width: 200px; - background: #fff; - box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4); + position: relative; + width: 200px; + background: #fff; + box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4); } .card { - margin-bottom: 35px; - padding-bottom: 1rem; - box-shadow: 0 10px 20px 0 rgba(26, 44, 57, 0.14); - border: none; + margin-bottom: 35px; + padding-bottom: 1rem; + box-shadow: 0 10px 20px 0 rgba(26, 44, 57, 0.14); + border: none; } .follower-wrapper li { - list-style-type: none; - color: #fff; - display: inline-block; - float: left; - margin-right: 20px; + list-style-type: none; + color: #fff; + display: inline-block; + float: left; + margin-right: 20px; } .social-profile { - color: #fff; + color: #fff; } .social-profile a { - color: #fff; + color: #fff; } .social-profile { - position: relative; - margin-bottom: 150px; + position: relative; + margin-bottom: 150px; } .social-profile .user-profile { - position: absolute; - bottom: -75px; - width: 150px; - height: 150px; - border-radius: 50%; - left: 50px; + position: absolute; + bottom: -75px; + width: 150px; + height: 150px; + border-radius: 50%; + left: 50px; } .social-nav { - position: absolute; - bottom: 0; + position: absolute; + bottom: 0; } .social-prof { - color: #333; - text-align: center; + color: #333; + text-align: center; } .social-prof .wrapper { - width: 70%; - margin: auto; - margin-top: -100px; + width: 70%; + margin: auto; + margin-top: -100px; } .social-prof img { - width: 150px; - height: 150px; - border-radius: 50%; - margin-bottom: 20px; - border: 5px solid #fff; - /*border: 10px solid #70b5e6ee;*/ + width: 150px; + height: 150px; + border-radius: 50%; + margin-bottom: 20px; + border: 5px solid #fff; + /*border: 10px solid #70b5e6ee;*/ } .social-prof h3 { - font-size: 36px; - font-weight: 700; - margin-bottom: 0; + font-size: 36px; + font-weight: 700; + margin-bottom: 0; } .social-prof p { - font-size: 18px; + font-size: 18px; } .social-prof .nav-tabs { - border: none; + border: none; } -.card .nav>li { - position: relative; - display: block; +.card .nav > li { + position: relative; + display: block; } -.card .nav>li>a { - position: relative; - display: block; - padding: 10px 15px; - font-weight: 300; - border-radius: 4px; +.card .nav > li > a { + position: relative; + display: block; + padding: 10px 15px; + font-weight: 300; + border-radius: 4px; } -.card .nav>li>a:focus, -.card .nav>li>a:hover { - text-decoration: none; - background-color: #eee; +.card .nav > li > a:focus, +.card .nav > li > a:hover { + text-decoration: none; + background-color: #eee; } -.card .s-nav>li>a.active { - text-decoration: none; - background-color: #3afe; - color: #fff; +.card .s-nav > li > a.active { + text-decoration: none; + background-color: #3afe; + color: #fff; } .text-blue { - color: #3afe; + color: #3afe; } ul.friend-list { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } ul.friend-list li { - list-style-type: none; - display: flex; - align-items: center; + list-style-type: none; + display: flex; + align-items: center; } ul.friend-list li:hover { - background: rgba(0, 0, 0, .1); - cursor: pointer; + background: rgba(0, 0, 0, 0.1); + cursor: pointer; } ul.friend-list .left img { - width: 45px; - height: 45px; - border-radius: 50%; - margin-right: 20px; + width: 45px; + height: 45px; + border-radius: 50%; + margin-right: 20px; } ul.friend-list li { - padding: 10px; + padding: 10px; } ul.friend-list .right h3 { - font-size: 16px; - font-weight: 700; - margin-bottom: 0; + font-size: 16px; + font-weight: 700; + margin-bottom: 0; } ul.friend-list .right p { - font-size: 11px; - color: #6c757d; - margin: 0; + font-size: 11px; + color: #6c757d; + margin: 0; } .social-timeline-card .dropdown-toggle::after { - display: none; + display: none; } .info-card h4 { - font-size: 15px; + font-size: 15px; } .info-card h2 { - font-size: 18px; - margin-bottom: 20px; + font-size: 18px; + margin-bottom: 20px; } .social-about .social-info { - font-size: 16px; - margin-bottom: 20px; + font-size: 16px; + margin-bottom: 20px; } .social-about p { - margin-bottom: 20px; + margin-bottom: 20px; } .info-card i { - color: #3afe; + color: #3afe; } .card-one { - position: relative; - width: 300px; - background: #fff; - box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4); + position: relative; + width: 300px; + background: #fff; + box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4); } .card-one .header { - position: relative; - width: 100%; - height: 60px; - background-color: rgba(7, 46, 74, 0.895); + position: relative; + width: 100%; + height: 60px; + background-color: rgba(7, 46, 74, 0.895); } .card-one .header::before, .card-one .header::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background: inherit; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: inherit; } .card-one .header::before { - -webkit-transform: skewY(-8deg); - transform: skewY(-8deg); - -webkit-transform-origin: 100% 100%; - transform-origin: 100% 100%; + -webkit-transform: skewY(-8deg); + transform: skewY(-8deg); + -webkit-transform-origin: 100% 100%; + transform-origin: 100% 100%; } .card-one .header::after { - -webkit-transform: skewY(8deg); - transform: skewY(8deg); - -webkit-transform-origin: 0 100%; - transform-origin: 0 100%; + -webkit-transform: skewY(8deg); + transform: skewY(8deg); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; } .card-one .header .avatar { - position: absolute; - left: 50%; - top: 30px; - margin-left: -50px; - z-index: 5; - width: 100px; - height: 100px; - border-radius: 50%; - overflow: hidden; - background: #ccc; - border: 3px solid #fff; + position: absolute; + left: 50%; + top: 30px; + margin-left: -50px; + z-index: 5; + width: 100px; + height: 100px; + border-radius: 50%; + overflow: hidden; + background: #ccc; + border: 3px solid #fff; } .card-one .header .avatar img { - position: absolute; - top: 50%; - left: 50%; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - width: 100px; - height: auto; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 100px; + height: auto; } .card-one h3 { - position: relative; - margin: 80px 0 30px; - text-align: center; + position: relative; + margin: 80px 0 30px; + text-align: center; } .card-one h3::after { - content: ''; - position: absolute; - bottom: -15px; - left: 50%; - margin-left: -15px; - width: 30px; - height: 1px; - background: #000; + content: ''; + position: absolute; + bottom: -15px; + left: 50%; + margin-left: -15px; + width: 30px; + height: 1px; + background: #000; } .card-one .desc { - padding: 0 1rem 2rem; - text-align: center; - line-height: 1.5; - color: #777; + padding: 0 1rem 2rem; + text-align: center; + line-height: 1.5; + color: #777; } #gallery li { - width: 24%; - float: left; - margin: 6px; - + width: 24%; + float: left; + margin: 6px; } .removeFriend { - text-wrap: nowrap; + text-wrap: nowrap; } .contacts { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } #profileName { - font-size: 1.5rem; - font-weight: 600; - width: 100%; + font-size: 1.5rem; + font-weight: 600; + width: 100%; } #requests { - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; } .request { - display: flex; - justify-content: center; - align-items: center; - margin: 1rem; + display: flex; + justify-content: center; + align-items: center; + margin: 1rem; } #profilePicture { - width: 70px; - height: 70px; - border-radius: 50%; - margin-right: 1rem; - border: 2px solid #000; + width: 70px; + height: 70px; + border-radius: 50%; + margin-right: 1rem; + border: 2px solid #000; } .modal-content { - padding: 1rem; + padding: 1rem; } .modal-header { - margin-bottom: 5px; + margin-bottom: 5px; } .pages { - border-bottom: 1px solid #000; - border-radius: 0px; - margin: 0px 5px; + border-bottom: 1px solid #000; + border-radius: 0px; + margin: 0px 5px; } .pages { - border-bottom: 2px solid #000; - /* default border */ - border-radius: 0px; - margin: 0px 5px; + border-bottom: 2px solid #000; + /* default border */ + border-radius: 0px; + margin: 0px 5px; } .active-tab { - border-bottom: 4px solid #000; - /* thicker border when active */ + border-bottom: 4px solid #000; + /* thicker border when active */ } #addFriend { - background-color: #084766; - color: white; + background-color: #084766; + color: white; } #addFriend:hover { - background-color: #003b58f5; + background-color: #003b58f5; } .friendBox { - width: 250px; + width: 250px; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Leaderboard/LeaderboardRank.vue b/src/components/Leaderboard/LeaderboardRank.vue index 819834e8799383de8494fc5bdaf16dee333dcf98..c6cc5632c4e3bbf7905120a20ac5df48925b03a2 100644 --- a/src/components/Leaderboard/LeaderboardRank.vue +++ b/src/components/Leaderboard/LeaderboardRank.vue @@ -1,169 +1,212 @@ <template> - <br> + <br /> + <div id="dropdownContainer"> + <h1 class="box">Poengtavle</h1> + </div> + <div id="content"> <div id="dropdownContainer"> - <h1 class="box">Poengtavle</h1> - </div> - <div id="content"> - <div id="dropdownContainer"> - <div class="box"> - <div class="btn-group-vertical" id="radioContainer" role="group" - aria-label="Vertikal radio knappgruppe"> - <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked> - <label class="btn btn-outline-primary" for="vbtn-radio1" @click="global"><img src="../../assets/globe.png" style="width: 60px" alt="globus"> Global</label> - <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off"> - <label data-cy="friends-leaderboard-btn" class="btn btn-outline-primary" - for="vbtn-radio2" - @click="friends"><img src="../../assets/friends.png" style="width: 60px" alt="venner"> Venner</label> - </div> - </div> + <div class="box"> + <div + class="btn-group-vertical" + id="radioContainer" + role="group" + aria-label="Vertikal radio knappgruppe" + > + <input + type="radio" + class="btn-check" + name="vbtn-radio" + id="vbtn-radio1" + autocomplete="off" + checked + /> + <label class="btn btn-outline-primary" for="vbtn-radio1" @click="global" + ><img src="../../assets/globe.png" style="width: 60px" alt="globus" /> Global</label + > + <input + type="radio" + class="btn-check" + name="vbtn-radio" + id="vbtn-radio2" + autocomplete="off" + /> + <label + data-cy="friends-leaderboard-btn" + class="btn btn-outline-primary" + for="vbtn-radio2" + @click="friends" + ><img src="../../assets/friends.png" style="width: 60px" alt="venner" /> Venner</label + > </div> - <main> - <div id="leaderboard"> - <h1><img src="../../assets/items/pigcoin.png" style="width: 2rem" alt="pig coin"> Totale poeng</h1> - <Leaderboard data-cy="total-points-board" :leaderboard="pointsLeaderboardData" - :leaderboardExtra="pointsLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> - </div> - <div id="leaderboard"> - <h1><img src="../../assets/icons/fire.png" style="width: 2rem" alt="ild"> Nåværende rekke</h1> - <Leaderboard data-cy="current-points-board" :leaderboard="currentLeaderboardData" - :leaderboardExtra="currentLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> - </div> - <div id="leaderboard"> - <h1><img src="../../assets/icons/fire.png" style="width: 2rem" alt="ild"> Høyeste rekke</h1> - <Leaderboard data-cy="streak-board" :leaderboard="streakLeaderboardData" - :leaderboardExtra="streakLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> - </div> - </main> - </div> - <div id="communityContainer"> - <h1>Totale poeng opptjent som et fellesskap</h1> - <h2>{{communityPoints}} <img src="../../assets/items/pigcoin.png" style="width: 2rem" alt="alt"></h2> + </div> </div> + <main> + <div id="leaderboard"> + <h1> + <img src="../../assets/items/pigcoin.png" style="width: 2rem" alt="pig coin" /> Totale + poeng + </h1> + <Leaderboard + data-cy="total-points-board" + :leaderboard="pointsLeaderboardData" + :leaderboardExtra="pointsLeaderboardDataExtra" + @navigateToUserProfile="navigateToUserProfile" + /> + </div> + <div id="leaderboard"> + <h1> + <img src="../../assets/icons/fire.png" style="width: 2rem" alt="ild" /> Nåværende rekke + </h1> + <Leaderboard + data-cy="current-points-board" + :leaderboard="currentLeaderboardData" + :leaderboardExtra="currentLeaderboardDataExtra" + @navigateToUserProfile="navigateToUserProfile" + /> + </div> + <div id="leaderboard"> + <h1> + <img src="../../assets/icons/fire.png" style="width: 2rem" alt="ild" /> Høyeste rekke + </h1> + <Leaderboard + data-cy="streak-board" + :leaderboard="streakLeaderboardData" + :leaderboardExtra="streakLeaderboardDataExtra" + @navigateToUserProfile="navigateToUserProfile" + /> + </div> + </main> + </div> + <div id="communityContainer"> + <h1>Totale poeng opptjent som et fellesskap</h1> + <h2> + {{ communityPoints }} + <img src="../../assets/items/pigcoin.png" style="width: 2rem" alt="alt" /> + </h2> + </div> </template> - <script setup lang="ts"> -import { onMounted, ref } from 'vue'; -import { useRouter } from 'vue-router'; -import Leaderboard from '@/components/Leaderboard/LeaderboardTable.vue'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; -import { LeaderboardService } from '@/api'; +import { onMounted, ref } from 'vue' +import { useRouter } from 'vue-router' +import Leaderboard from '@/components/Leaderboard/LeaderboardTable.vue' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' +import { LeaderboardService } from '@/api' -const router = useRouter(); +const router = useRouter() -let streakLeaderboardData = ref([] as any); -let currentLeaderboardData = ref([] as any); -let pointsLeaderboardData = ref([] as any); +let streakLeaderboardData = ref([] as any) +let currentLeaderboardData = ref([] as any) +let pointsLeaderboardData = ref([] as any) -let streakLeaderboardDataExtra = ref([] as any); -let currentLeaderboardDataExtra = ref([] as any); -let pointsLeaderboardDataExtra = ref([] as any); +let streakLeaderboardDataExtra = ref([] as any) +let currentLeaderboardDataExtra = ref([] as any) +let pointsLeaderboardDataExtra = ref([] as any) -let communityPoints = ref(0); +let communityPoints = ref(0) /** * Fetches quiz data including community points. */ async function fetchQuizData() { - await global(); + await global() - const response = await LeaderboardService.getTotalPoints(); - communityPoints.value = response; + const response = await LeaderboardService.getTotalPoints() + communityPoints.value = response } onMounted(() => { - fetchQuizData(); -}); - + fetchQuizData() +}) /** * Retrieves global leaderboard data. */ async function global() { - try { - let globalPoints = await LeaderboardService.getLeaderboard({ - type: "TOTAL_POINTS", - filter: "GLOBAL", - }); - let globalStreak = await LeaderboardService.getLeaderboard({ - type: "TOP_STREAK", - filter: "GLOBAL", - }); - let globalCurrentStreak = await LeaderboardService.getLeaderboard({ - type: "CURRENT_STREAK", - filter: "GLOBAL", - }); - let globalPointsYou = await LeaderboardService.getSurrounding({ - type: "TOTAL_POINTS", - filter: "GLOBAL", - entryCount: 2, - }); - let globalStreakYou = await LeaderboardService.getSurrounding({ - type: "TOP_STREAK", - filter: "GLOBAL", - entryCount: 2, - }); - let globalCurrentStreakYou = await LeaderboardService.getSurrounding({ - type: "CURRENT_STREAK", - filter: "GLOBAL", - entryCount: 2, - }); - - pointsLeaderboardData.value = globalPoints.entries; - currentLeaderboardData.value = globalCurrentStreak.entries; - streakLeaderboardData.value = globalStreak.entries; - - pointsLeaderboardDataExtra.value = globalPointsYou.entries; - currentLeaderboardDataExtra.value = globalCurrentStreakYou.entries; - streakLeaderboardDataExtra.value = globalStreakYou.entries; - } catch (error) { - handleUnknownError(error); - } + try { + let globalPoints = await LeaderboardService.getLeaderboard({ + type: 'TOTAL_POINTS', + filter: 'GLOBAL' + }) + let globalStreak = await LeaderboardService.getLeaderboard({ + type: 'TOP_STREAK', + filter: 'GLOBAL' + }) + let globalCurrentStreak = await LeaderboardService.getLeaderboard({ + type: 'CURRENT_STREAK', + filter: 'GLOBAL' + }) + let globalPointsYou = await LeaderboardService.getSurrounding({ + type: 'TOTAL_POINTS', + filter: 'GLOBAL', + entryCount: 2 + }) + let globalStreakYou = await LeaderboardService.getSurrounding({ + type: 'TOP_STREAK', + filter: 'GLOBAL', + entryCount: 2 + }) + let globalCurrentStreakYou = await LeaderboardService.getSurrounding({ + type: 'CURRENT_STREAK', + filter: 'GLOBAL', + entryCount: 2 + }) + + pointsLeaderboardData.value = globalPoints.entries + currentLeaderboardData.value = globalCurrentStreak.entries + streakLeaderboardData.value = globalStreak.entries + + pointsLeaderboardDataExtra.value = globalPointsYou.entries + currentLeaderboardDataExtra.value = globalCurrentStreakYou.entries + streakLeaderboardDataExtra.value = globalStreakYou.entries + } catch (error) { + handleUnknownError(error) + } } /** * Retrieves friends leaderboard data. */ async function friends() { - try { + try { let friendsPoints = await LeaderboardService.getLeaderboard({ - type: "TOTAL_POINTS", - filter: "FRIENDS", - }); + type: 'TOTAL_POINTS', + filter: 'FRIENDS' + }) let friendsStreak = await LeaderboardService.getLeaderboard({ - type: "TOP_STREAK", - filter: "FRIENDS", - }); + type: 'TOP_STREAK', + filter: 'FRIENDS' + }) let friendsCurrentStreak = await LeaderboardService.getLeaderboard({ - type: "CURRENT_STREAK", - filter: "FRIENDS", - }); + type: 'CURRENT_STREAK', + filter: 'FRIENDS' + }) let friendsPointsYou = await LeaderboardService.getSurrounding({ - type: "TOTAL_POINTS", - filter: "FRIENDS", - entryCount: 2, - }); + type: 'TOTAL_POINTS', + filter: 'FRIENDS', + entryCount: 2 + }) let friendsStreakYou = await LeaderboardService.getSurrounding({ - type: "TOP_STREAK", - filter: "FRIENDS", - entryCount: 2, - }); + type: 'TOP_STREAK', + filter: 'FRIENDS', + entryCount: 2 + }) let friendsCurrentStreakYou = await LeaderboardService.getSurrounding({ - type: "CURRENT_STREAK", - filter: "FRIENDS", - entryCount: 2, - }); - - pointsLeaderboardData.value = friendsPoints.entries; - currentLeaderboardData.value = friendsCurrentStreak.entries; - streakLeaderboardData.value = friendsStreak.entries; - - pointsLeaderboardDataExtra.value = friendsPointsYou.entries; - currentLeaderboardDataExtra.value = friendsStreakYou.entries; - streakLeaderboardDataExtra.value = friendsCurrentStreakYou.entries; - } catch (error) { - handleUnknownError(error); - } + type: 'CURRENT_STREAK', + filter: 'FRIENDS', + entryCount: 2 + }) + + pointsLeaderboardData.value = friendsPoints.entries + currentLeaderboardData.value = friendsCurrentStreak.entries + streakLeaderboardData.value = friendsStreak.entries + + pointsLeaderboardDataExtra.value = friendsPointsYou.entries + currentLeaderboardDataExtra.value = friendsStreakYou.entries + streakLeaderboardDataExtra.value = friendsCurrentStreakYou.entries + } catch (error) { + handleUnknownError(error) + } } /** @@ -172,66 +215,65 @@ async function friends() { * @param {number} userId The ID of the user whose profile will be navigated to. */ const navigateToUserProfile = (userId: number) => { - router.push({ name: 'user', params: { id: userId } }); -}; + router.push({ name: 'user', params: { id: userId } }) +} </script> <style scoped> main { - margin-bottom: 4rem; - width: 80%; - display: flex; - justify-content: space-around; - align-items: start; - flex-wrap: wrap; - flex-direction: row; + margin-bottom: 4rem; + width: 80%; + display: flex; + justify-content: space-around; + align-items: start; + flex-wrap: wrap; + flex-direction: row; } #leaderboard { - width: 400px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-bottom: 3rem; + width: 400px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-bottom: 3rem; } #content { - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; } .box { - width: 90%; + width: 90%; } h1 { - font-weight: 700; - margin-bottom: 1rem; + font-weight: 700; + margin-bottom: 1rem; } #dropdownContainer { - display: flex; - justify-content: center; - margin-bottom: 2rem; + display: flex; + justify-content: center; + margin-bottom: 2rem; } #radioContainer { - display: flex; - justify-content: center; - margin-bottom: 2rem; - width: 100%; - margin-top: 3.6rem; + display: flex; + justify-content: center; + margin-bottom: 2rem; + width: 100%; + margin-top: 3.6rem; } #communityContainer { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - margin-bottom: 5rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-bottom: 5rem; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/Leaderboard/LeaderboardTable.vue b/src/components/Leaderboard/LeaderboardTable.vue index 4cb62331f504e48c74ddb7cc28f6398e21b1d036..07d46da9ef6423dbabae5b4d1621d57ed974c1a9 100644 --- a/src/components/Leaderboard/LeaderboardTable.vue +++ b/src/components/Leaderboard/LeaderboardTable.vue @@ -1,26 +1,39 @@ <template> <div id="leaderboard"> - <table> <tbody data-cy="top-leaderboard-table"> - <tr data-cy="top-leaderboard-tablerow" v-for="(entry, index) in leaderboard" :key="entry.user?.id" :class="{ - 'is-user-5': - entry.user?.id === userStore.id - }"> + <tr + data-cy="top-leaderboard-tablerow" + v-for="(entry, index) in leaderboard" + :key="entry.user?.id" + :class="{ + 'is-user-5': entry.user?.id === userStore.id + }" + > <td class="number">{{ entry.rank }}</td> - <td class="name" @click="navigateToUserProfile(entry.user?.id ?? 0)">{{ entry.user?.firstName }}</td> + <td class="name" @click="navigateToUserProfile(entry.user?.id ?? 0)"> + {{ entry.user?.firstName }} + </td> <td class="points" v-if="index === 0"> {{ entry.score }} </td> <td v-else class="points">{{ entry.score }}</td> </tr> </tbody> - <tbody id="line">`</tbody> + <tbody id="line"> + ` + </tbody> <tbody data-cy="surrounding-user-leaderboard-table" v-if="!userInLeaderboard"> - <tr data-cy="surrounding-user-leaderboard-tablerow" v-for="(entry, index) in leaderboardExtra" :key="entry.user?.id" - :class="{ 'is-user-5': entry.user?.id === userStore.id }"> + <tr + data-cy="surrounding-user-leaderboard-tablerow" + v-for="(entry, index) in leaderboardExtra" + :key="entry.user?.id" + :class="{ 'is-user-5': entry.user?.id === userStore.id }" + > <td class="number">{{ entry.rank }}</td> - <td class="name" @click="navigateToUserProfile(entry.user?.id ?? 0)">{{ entry.user?.firstName }}</td> + <td class="name" @click="navigateToUserProfile(entry.user?.id ?? 0)"> + {{ entry.user?.firstName }} + </td> <td class="points">{{ entry.score }}</td> </tr> </tbody> @@ -29,16 +42,15 @@ </div> </template> - <script setup lang="ts"> -import { computed } from 'vue'; -import { useRouter } from 'vue-router'; -import { useUserInfoStore } from '@/stores/UserStore'; -import type { LeaderboardEntryDTO } from '@/api/models/LeaderboardEntryDTO'; -import type { PropType } from 'vue'; +import { computed } from 'vue' +import { useRouter } from 'vue-router' +import { useUserInfoStore } from '@/stores/UserStore' +import type { LeaderboardEntryDTO } from '@/api/models/LeaderboardEntryDTO' +import type { PropType } from 'vue' -const router = useRouter(); -const userStore = useUserInfoStore(); +const router = useRouter() +const userStore = useUserInfoStore() const props = defineProps({ leaderboard: { @@ -49,14 +61,16 @@ const props = defineProps({ type: Array as PropType<LeaderboardEntryDTO[]>, required: true } -}); +}) /** * Checks if the current user is in the leaderboard. * * @returns {boolean} Returns true if the current user is in the leaderboard, false otherwise. */ -const userInLeaderboard = computed(() => props.leaderboard.some(entry => entry.user && entry.user.email === userStore.email)); +const userInLeaderboard = computed(() => + props.leaderboard.some((entry) => entry.user && entry.user.email === userStore.email) +) /** * Navigates to the user profile page based on the given user ID. @@ -64,15 +78,17 @@ const userInLeaderboard = computed(() => props.leaderboard.some(entry => entry.u * @param {number} id The ID of the user whose profile will be navigated to. */ const navigateToUserProfile = (id: number) => { - router.push(`/profile/${id}`); -}; + router.push(`/profile/${id}`) +} </script> <style scoped> #leaderboard { max-width: 80%; position: relative; - box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; + box-shadow: + rgba(0, 0, 0, 0.16) 0px 3px 6px, + rgba(0, 0, 0, 0.23) 0px 3px 6px; } table { @@ -105,7 +121,7 @@ tr:nth-child(even) { td { height: 2rem; - font-family: "Rubik", sans-serif; + font-family: 'Rubik', sans-serif; font-size: 1.4rem; padding: 1rem 2rem; position: relative; @@ -145,7 +161,6 @@ td { } } - .points:first-child { width: 10rem; } @@ -159,32 +174,32 @@ td { width: 106%; height: 4.5rem; top: -0.5rem; - background-color: #003A58; + background-color: #003a58; position: absolute; /**left: -1rem;*/ box-shadow: 0px 15px 11px -6px #7a7a7d; } .ribbon::before { - content: ""; + content: ''; height: 1.5rem; width: 1.5rem; bottom: -0.8rem; left: 0.35rem; transform: rotate(45deg); - background-color: #003A58; + background-color: #003a58; position: absolute; z-index: -1; } .ribbon::after { - content: ""; + content: ''; height: 1.5rem; width: 1.5rem; bottom: -0.8rem; right: 0.35rem; transform: rotate(45deg); - background-color: #003A58; + background-color: #003a58; position: absolute; z-index: -1; } @@ -192,11 +207,11 @@ td { #line { width: 100%; height: 0.01rem; - border-top: 8px solid #003A58 + border-top: 8px solid #003a58; } tr.is-user-5 { background-color: #419c5c !important; color: #fff !important; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Leaderboard/__tests__/LeaderboardTable.spec.ts b/src/components/Leaderboard/__tests__/LeaderboardTable.spec.ts index 1e358426983dfe36f7089ffcca8def2100b3f16a..931681001ab2de1a52507df2e5c8cc4b4183d07b 100644 --- a/src/components/Leaderboard/__tests__/LeaderboardTable.spec.ts +++ b/src/components/Leaderboard/__tests__/LeaderboardTable.spec.ts @@ -1,42 +1,40 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { mount } from '@vue/test-utils'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import { createPinia, setActivePinia } from 'pinia'; -import Leaderboard from '../LeaderboardTable.vue'; -import { useUserInfoStore } from '../../../stores/UserStore'; -import router from '../../../router'; +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import { createPinia, setActivePinia } from 'pinia' +import Leaderboard from '../LeaderboardTable.vue' +import { useUserInfoStore } from '../../../stores/UserStore' +import router from '../../../router' describe('Leaderboard', () => { - - let wrapper: any, store: any, mockRouter; - + let wrapper: any, store: any, mockRouter const leaderboard = [ { user: { id: 1, firstName: 'Alice', email: 'alice@example.com' }, rank: 1, score: 50 }, { user: { id: 2, firstName: 'Bob', email: 'bob@example.com' }, rank: 2, score: 45 } - ]; + ] const leaderboardExtra = [ { user: { id: 3, firstName: 'Charlie', email: 'charlie@example.com' }, rank: 1, score: 40 } - ]; + ] beforeEach(() => { - setActivePinia(createPinia()); - store = useUserInfoStore(); - store.$state = { email: 'alice@example.com' }; // Setting initial state + setActivePinia(createPinia()) + store = useUserInfoStore() + store.$state = { email: 'alice@example.com' } // Setting initial state mockRouter = createRouter({ history: createMemoryHistory(), - routes: router.getRoutes(), - }); + routes: router.getRoutes() + }) router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); - } else { - next(); - } - }); + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) + } else { + next() + } + }) wrapper = mount(Leaderboard, { props: { leaderboard, leaderboardExtra }, @@ -44,20 +42,20 @@ describe('Leaderboard', () => { plugins: [mockRouter], stubs: ['router-link', 'router-view'] } - }); - }); + }) + }) it('renders all entries from the leaderboard and leaderboardExtra props', () => { - const rows = wrapper.findAll('tbody > tr'); - expect(rows.length).toBe(2); - }); + const rows = wrapper.findAll('tbody > tr') + expect(rows.length).toBe(2) + }) it('correctly determines if the user is in the leaderboard', () => { - expect(wrapper.vm.userInLeaderboard).toBe(true); - }); + expect(wrapper.vm.userInLeaderboard).toBe(true) + }) it('applies the is-user-5 class based on user firstName', () => { - store.$state.firstname = 'User'; // Change state to match the condition - expect(wrapper.find('.is-user-5').exists()).toBe(false); // Check if the class is applied - }); -}); + store.$state.firstname = 'User' // Change state to match the condition + expect(wrapper.find('.is-user-5').exists()).toBe(false) // Check if the class is applied + }) +}) diff --git a/src/components/Login/ChangePassword.vue b/src/components/Login/ChangePassword.vue index b2a129ea45d2f5d5ad9e926fa2f35a5caab108a3..41875fe8f0edb25e194285e449f9a3a808dd6c41 100644 --- a/src/components/Login/ChangePassword.vue +++ b/src/components/Login/ChangePassword.vue @@ -1,42 +1,51 @@ <template> - <div class="containers"> - <div class="box"> - <div class="container-fluid"> - <div class="container-fluid d-flex justify-content-center align-items-center flex-column mt-5"> - <h1>Opprett nytt passord</h1> - </div> - <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit" novalidate> - - <BaseInput :model-value="newPassword" - @input-change-event="handlePasswordInputEvent" - id="passwordInput" - input-id="password" - type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" - label="Passord" - placeholder="Skriv inn ditt passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" - /> - - <BaseInput :model-value="confirmPassword" - @input-change-event="handleConfirmPasswordInputEvent" - id="confirmPasswordInput" - input-id="password" - type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" - label="Bekreft Passord" - placeholder="Skriv inn ditt passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" - /> - - <p class="text-danger" data-cy="error">{{ errorMsg }}</p> - <p v-if="!samePasswords" class="text-danger">Passordene er ikke like</p> - <BaseButton id="confirmButton" type="submit" @click="handleSubmit" :disabled="isSubmitting" button-text="Oppdater passordet"></BaseButton> - - <SignUpLink/> - </form> + <div class="containers"> + <div class="box"> + <div class="container-fluid"> + <div + class="container-fluid d-flex justify-content-center align-items-center flex-column mt-5" + > + <h1>Opprett nytt passord</h1> </div> - <!--<div class="row justify-content-center"> + <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit" novalidate> + <BaseInput + :model-value="newPassword" + @input-change-event="handlePasswordInputEvent" + id="passwordInput" + input-id="password" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Passord" + placeholder="Skriv inn ditt passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" + /> + + <BaseInput + :model-value="confirmPassword" + @input-change-event="handleConfirmPasswordInputEvent" + id="confirmPasswordInput" + input-id="password" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Bekreft Passord" + placeholder="Skriv inn ditt passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" + /> + + <p class="text-danger" data-cy="error">{{ errorMsg }}</p> + <p v-if="!samePasswords" class="text-danger">Passordene er ikke like</p> + <BaseButton + id="confirmButton" + type="submit" + @click="handleSubmit" + :disabled="isSubmitting" + button-text="Oppdater passordet" + ></BaseButton> + + <SignUpLink /> + </form> + </div> + <!--<div class="row justify-content-center"> <div class="col-lg-5"> <div class="card shadow-lg border-0 rounded-lg mt-5"> <div class="card-header"> @@ -68,30 +77,30 @@ </div> </div> </div>--> - </div> </div> + </div> </template> <script setup lang="ts"> -import { ref } from 'vue'; -import { useRouter, useRoute } from 'vue-router'; -import { UserService } from '@/api'; +import { ref } from 'vue' +import { useRouter, useRoute } from 'vue-router' +import { UserService } from '@/api' import SignUpLink from '@/components/SignUp/SignUpLink.vue' import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const router = useRouter(); -const route = useRoute(); +const router = useRouter() +const route = useRoute() -const token = route.params.token; +const token = route.params.token -const newPassword = ref(''); -const confirmPassword = ref(''); +const newPassword = ref('') +const confirmPassword = ref('') const formRef = ref() let samePasswords = ref(true) -let errorMsg = ref(''); -const isSubmitting = ref(false); +let errorMsg = ref('') +const isSubmitting = ref(false) /** * Handles password input event by updating the value of the newPassword reactive variable. @@ -117,29 +126,28 @@ const handleConfirmPasswordInputEvent = (newValue: any) => { */ const handleSubmit = async () => { // Validates the form - if (isSubmitting.value) return; - isSubmitting.value = true; - samePasswords.value = (newPassword.value === confirmPassword.value) - formRef.value.classList.add("was-validated") + if (isSubmitting.value) return + isSubmitting.value = true + samePasswords.value = newPassword.value === confirmPassword.value + formRef.value.classList.add('was-validated') - const form = formRef.value; + const form = formRef.value if (form.checkValidity()) { if (samePasswords.value) { try { const resetPassword = { password: newPassword.value, - token: token as string, - }; - await UserService.confirmPasswordReset({ requestBody: resetPassword }); - router.push('/login'); + token: token as string + } + await UserService.confirmPasswordReset({ requestBody: resetPassword }) + router.push('/login') } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } } - isSubmitting.value = false; -}; - + isSubmitting.value = false +} </script> <style scoped> @@ -182,4 +190,4 @@ h1 { margin: 1rem 10rem; width: 100%; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Login/ForgottenPassword.vue b/src/components/Login/ForgottenPassword.vue index 169c13f93fb32f376d6ce0a0d838b9d36c70ce78..c2de655e470a3f02f5565f546f911094c1601f88 100644 --- a/src/components/Login/ForgottenPassword.vue +++ b/src/components/Login/ForgottenPassword.vue @@ -1,108 +1,121 @@ <template> - <div class="containers"> - <div class="box"> - <h1 class="title">Tilbakestill passord</h1> - <p>Fyll inn e-posten din, så sender vi deg instruksjoner for å tilbakestille passordet ditt.</p> - <form @submit.prevent="submitForm" id="resetForm" ref="formRef" novalidate> - <div class="form-floating inputBox"> - <input v-model="email" class="form-control" id="inputEmail" type="email" - placeholder="name@example.com" required> - <label for="emailInput">Skriv inn din e-post</label> - </div> - - <div v-if="errorMessage" class="text-danger"> - {{ errorMessage }} - </div> - <div v-else class="text-success"> - {{ confirmationMessage }} - </div> - <BaseButton id="confirmButton" type="submit" :disabled="isSubmitting" button-text="Send e-post"></BaseButton> - - <div class="login-link"> - <Router-Link to="/login" class="small">Gå tilbake</Router-Link> - </div> - </form> - </div> + <div class="containers"> + <div class="box"> + <h1 class="title">Tilbakestill passord</h1> + <p> + Fyll inn e-posten din, så sender vi deg instruksjoner for å tilbakestille passordet ditt. + </p> + <form @submit.prevent="submitForm" id="resetForm" ref="formRef" novalidate> + <div class="form-floating inputBox"> + <input + v-model="email" + class="form-control" + id="inputEmail" + type="email" + placeholder="name@example.com" + required + /> + <label for="emailInput">Skriv inn din e-post</label> + </div> + + <div v-if="errorMessage" class="text-danger"> + {{ errorMessage }} + </div> + <div v-else class="text-success"> + {{ confirmationMessage }} + </div> + <BaseButton + id="confirmButton" + type="submit" + :disabled="isSubmitting" + button-text="Send e-post" + ></BaseButton> + + <div class="login-link"> + <Router-Link to="/login" class="small">Gå tilbake</Router-Link> + </div> + </form> </div> - </template> - - <script setup lang="ts"> - import { ref } from 'vue'; - import { UserService } from '@/api'; - import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' - import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; - - const formRef = ref() - const form = formRef.value; - const email = ref(''); - const confirmationMessage = ref(''); - const errorMessage = ref(''); - const isSubmitting = ref(false); + </div> +</template> - /** - * Submits the form for resetting the password. - */ - const submitForm = async () => { - // Validates the form - if (isSubmitting.value) return; - isSubmitting.value = true; - formRef.value.classList.add("was-validated") - - try { - await UserService.resetPassword({ requestBody: email.value }); - confirmationMessage.value = 'An email has been sent to your email address with a link to reset your password.'; - errorMessage.value = ''; - } catch (error) { - handleUnknownError(error); - errorMessage.value = 'Failed to send email. Please try again.'; - confirmationMessage.value = ''; - } - isSubmitting.value = false; - }; - </script> - - <style scoped> - .containers { - background: url('@/assets/wave.svg'); - background-size: cover; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - } - - .box { - background-color: white; - border-radius: 1rem; - width: 100%; - max-width: 450px; - padding: 2rem; - box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px; - text-align: center; - } - - h1 { - font-size: 2rem; - font-weight: bold; - text-align: center; - } - - #resetForm { - display: flex; - flex-direction: column; - align-items: center; - } - - .login-link { - width: 100%; - font-size: 14px; - margin-top: 10px; - text-align: center; - } - - .inputBox { - width: 100%; - margin: 20px; +<script setup lang="ts"> +import { ref } from 'vue' +import { UserService } from '@/api' +import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' + +const formRef = ref() +const form = formRef.value +const email = ref('') +const confirmationMessage = ref('') +const errorMessage = ref('') +const isSubmitting = ref(false) + +/** + * Submits the form for resetting the password. + */ +const submitForm = async () => { + // Validates the form + if (isSubmitting.value) return + isSubmitting.value = true + formRef.value.classList.add('was-validated') + + try { + await UserService.resetPassword({ requestBody: email.value }) + confirmationMessage.value = + 'An email has been sent to your email address with a link to reset your password.' + errorMessage.value = '' + } catch (error) { + handleUnknownError(error) + errorMessage.value = 'Failed to send email. Please try again.' + confirmationMessage.value = '' } - </style> - \ No newline at end of file + isSubmitting.value = false +} +</script> + +<style scoped> +.containers { + background: url('@/assets/wave.svg'); + background-size: cover; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.box { + background-color: white; + border-radius: 1rem; + width: 100%; + max-width: 450px; + padding: 2rem; + box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px; + text-align: center; +} + +h1 { + font-size: 2rem; + font-weight: bold; + text-align: center; +} + +#resetForm { + display: flex; + flex-direction: column; + align-items: center; +} + +.login-link { + width: 100%; + font-size: 14px; + margin-top: 10px; + text-align: center; +} + +.inputBox { + width: 100%; + margin: 20px; +} +</style> diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index ae3cb5d3dead318dbb9a4af0c18fbc697b9e2479..eea8b7f491b7d0ef281ad02ee7753045a3df4b2c 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -2,22 +2,22 @@ import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' import { ref } from 'vue' -import { useUserInfoStore } from '@/stores/UserStore'; -import { AuthenticationService, OpenAPI, type LoginRequest } from '@/api'; -import { useRouter } from 'vue-router'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; -import { useErrorStore } from '@/stores/ErrorStore'; +import { useUserInfoStore } from '@/stores/UserStore' +import { AuthenticationService, OpenAPI, type LoginRequest } from '@/api' +import { useRouter } from 'vue-router' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' +import { useErrorStore } from '@/stores/ErrorStore' import SignUpLink from '@/components/SignUp/SignUpLink.vue' -const errorStore = useErrorStore(); -const router = useRouter(); -const userStore = useUserInfoStore(); +const errorStore = useErrorStore() +const router = useRouter() +const userStore = useUserInfoStore() const emailRef = ref('') const passwordRef = ref('') const formRef = ref() -let errorMsg = ref(''); -const isSubmitting = ref(false); +let errorMsg = ref('') +const isSubmitting = ref(false) /** * Handles email input event by updating the value of the emailRef reactive variable. @@ -44,31 +44,31 @@ const handlePasswordInputEvent = (newValue: any) => { const handleSubmit = async () => { console.log(emailRef.value) console.log(passwordRef.value) - if (isSubmitting.value) return; - isSubmitting.value = true; + if (isSubmitting.value) return + isSubmitting.value = true - formRef.value.classList.add("was-validated") + formRef.value.classList.add('was-validated') - const form = formRef.value; + const form = formRef.value if (!form.checkValidity()) { - isSubmitting.value = false; - return; + isSubmitting.value = false + return } const loginUserPayload: LoginRequest = { email: emailRef.value, password: passwordRef.value - }; + } try { - let response = await AuthenticationService.login({ requestBody: loginUserPayload }); + let response = await AuthenticationService.login({ requestBody: loginUserPayload }) if (response.token == null || response.token == undefined) { - errorMsg.value = 'A valid token could not be created'; - isSubmitting.value = false; - return; + errorMsg.value = 'A valid token could not be created' + isSubmitting.value = false + return } - OpenAPI.TOKEN = response.token; + OpenAPI.TOKEN = response.token userStore.setUserInfo({ id: response.userId, @@ -79,17 +79,16 @@ const handleSubmit = async () => { role: response.role, subscriptionLevel: response.subscriptionLevel, profileImage: response.profileImage - }); + }) console.log(response.token) - await router.push({ name: 'roadmap' }); + await router.push({ name: 'roadmap' }) } catch (error: any) { - errorMsg.value = handleUnknownError(error); - isSubmitting.value = false; + errorMsg.value = handleUnknownError(error) + isSubmitting.value = false } } - </script> <template> @@ -98,26 +97,27 @@ const handleSubmit = async () => { <h1>Logg inn</h1> </div> <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit" novalidate> - - <BaseInput :model-value="emailRef" - @input-change-event="handleEmailInputEvent" - id="emailInput" - input-id="email" - type="email" - label="E-post" - placeholder="Skriv inn din e-post" - invalid-message="Ugyldig e-post" + <BaseInput + :model-value="emailRef" + @input-change-event="handleEmailInputEvent" + id="emailInput" + input-id="email" + type="email" + label="E-post" + placeholder="Skriv inn din e-post" + invalid-message="Ugyldig e-post" /> - <BaseInput :model-value="passwordRef" - @input-change-event="handlePasswordInputEvent" - id="passwordInput" - input-id="password" - type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" - label="Passord" - placeholder="Skriv inn ditt passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" + <BaseInput + :model-value="passwordRef" + @input-change-event="handlePasswordInputEvent" + id="passwordInput" + input-id="password" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Passord" + placeholder="Skriv inn ditt passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" /> <div class="password-reset-link"> @@ -125,14 +125,23 @@ const handleSubmit = async () => { </div> <p class="text-danger" data-cy="error">{{ errorMsg }}</p> - <BaseButton id="confirmButton" type="submit" @click="handleSubmit" :disabled="isSubmitting" button-text="Logg inn"></BaseButton> - - <a class="btn bankid-btn" href="https://preprod.signicat.com/oidc/authorize?response_type=code&scope=openid+profile+signicat.national_id&client_id=demo-preprod&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fredirect&acr_values=urn:signicat:oidc:method:nbid&state=nbid:auth_demo_bankid:123456789"> - <img src="/src/assets/bankid.svg" width="26" height="26"> + <BaseButton + id="confirmButton" + type="submit" + @click="handleSubmit" + :disabled="isSubmitting" + button-text="Logg inn" + ></BaseButton> + + <a + class="btn bankid-btn" + href="https://preprod.signicat.com/oidc/authorize?response_type=code&scope=openid+profile+signicat.national_id&client_id=demo-preprod&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fredirect&acr_values=urn:signicat:oidc:method:nbid&state=nbid:auth_demo_bankid:123456789" + > + <img src="/src/assets/bankid.svg" width="26" height="26" /> Fortsett med BankID </a> - <SignUpLink/> + <SignUpLink /> </form> </div> </template> @@ -171,4 +180,4 @@ h1 { justify-content: flex-start; font-size: 14px; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Login/LoginLink.vue b/src/components/Login/LoginLink.vue index d52d948ef108f12394f09d1ac558fc007c19087d..885122eed08bcfbfba765731d19dde6bf52ea2d1 100644 --- a/src/components/Login/LoginLink.vue +++ b/src/components/Login/LoginLink.vue @@ -1,11 +1,7 @@ -<script setup lang="ts"> - -</script> +<script setup lang="ts"></script> <template> <p>Har du en bruker? <RouterLink to="/login" id="login">Logg inn</RouterLink></p> </template> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/components/Login/LoginParent.vue b/src/components/Login/LoginParent.vue index c1735b01d3b28aad80150f6c5e49f1b3a10fb5a1..3ff14a24a83d16ba9862ed33a5312dd835828a06 100644 --- a/src/components/Login/LoginParent.vue +++ b/src/components/Login/LoginParent.vue @@ -6,42 +6,41 @@ import LoginForm from '@/components/Login/LoginForm.vue' <div class="containers"> <h1 class="title">SpareSti</h1> <div class="box"> - <LoginForm/> + <LoginForm /> </div> </div> </template> <style scoped> - .containers { - background: url('@/assets/wave.svg'); - background-repeat: no-repeat; - background-size: cover; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - } +.containers { + background: url('@/assets/wave.svg'); + background-repeat: no-repeat; + background-size: cover; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} - .box { - background-color: white; - border-radius: 1rem; - max-width: 450px; - padding: 0 3rem 1rem 3rem; - box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px; - } +.box { + background-color: white; + border-radius: 1rem; + max-width: 450px; + padding: 0 3rem 1rem 3rem; + box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px; +} - @media (max-width: 600px){ - .box { +@media (max-width: 600px) { + .box { padding: 0; } - } +} - .title { - font-size: 60px; - color: white; - margin-bottom: 40px; - font-weight: 700; - } - -</style> \ No newline at end of file +.title { + font-size: 60px; + color: white; + margin-bottom: 40px; + font-weight: 700; +} +</style> diff --git a/src/components/Login/__tests__/LoginForm.spec.ts b/src/components/Login/__tests__/LoginForm.spec.ts index d97fadf173e8a5568b802de05fec6dcc67973a52..6276df21f1143f593d3d93317441233720e7dfff 100644 --- a/src/components/Login/__tests__/LoginForm.spec.ts +++ b/src/components/Login/__tests__/LoginForm.spec.ts @@ -1,120 +1,121 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { mount } from '@vue/test-utils'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import { createPinia, setActivePinia } from 'pinia'; -import { useUserInfoStore } from '@/stores/UserStore'; -import MyComponent from '@/components/Login/LoginForm.vue'; // Adjust path as needed -import router from '@/router/index'; // Adjust path as needed -import { access } from 'fs'; -import { render, fireEvent, cleanup, screen } from '@testing-library/vue'; -import userEvent from '@testing-library/user-event'; +import { describe, it, expect, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import { createPinia, setActivePinia } from 'pinia' +import { useUserInfoStore } from '@/stores/UserStore' +import MyComponent from '@/components/Login/LoginForm.vue' // Adjust path as needed +import router from '@/router/index' // Adjust path as needed +import { access } from 'fs' +import { render, fireEvent, cleanup, screen } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' describe('Menu and Router Tests', () => { - let store: any, mockRouter: any; - - beforeEach(() => { - // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); - mockRouter = createRouter({ - history: createMemoryHistory(), - routes: router.getRoutes(), - }); - router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); - } else { - next(); - } - }); - - }); - - describe('Component Rendering', () => { - it('renders form correctly', () => { - store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }); - - const wrapper = mount(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - expect(wrapper.text()).toContain('E-post'); - expect(wrapper.text()).toContain('Passord'); - }); - }); - - describe('Navigation Guards', () => { - it('redirects an unauthenticated user to login when accessing a protected route', async () => { - store.$patch({ accessToken: '' }); - - router.push('/'); - await router.isReady(); - - expect(router.currentRoute.value.name).toBe('login'); - }); - - it('allows an unauthenticated user to visit signup', async () => { - store.$patch({ accessToken: 'valid-token' }); - - mockRouter.push('/sign-up'); - - await mockRouter.isReady(); - - expect(mockRouter.currentRoute.value.name).toBe('sign up'); - }); - }); - - - describe('Input fields', () => { - it('updates user credetials correctly', async () => { - const { getByPlaceholderText } = render(MyComponent); - - const emailInput = getByPlaceholderText('Skriv inn din e-post') as HTMLInputElement; - const passwordInput = getByPlaceholderText('Skriv inn ditt passord') as HTMLInputElement; - await fireEvent.update(emailInput, 'user@example.com'); - await fireEvent.update(passwordInput, 'Password1'); - - expect(emailInput.value).toBe('user@example.com'); - expect(passwordInput.value).toBe('Password1'); - }); - - it('Password error msg', async () => { - const { container } = render(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - const errorMsg = container.querySelector('#invalid'); // Use the actual ID here - expect(errorMsg?.textContent === "Password must be between 4 and 16 characters and contain one capital letter, small letter and a number") - }); - - it('logout should have empty store at application start', () => { - expect(store.firstname).toBe(''); - expect(store.lastname).toBe(''); - expect(store.accessToken).toBe(''); - }); - }); - - describe('Menu Actions', () => { - it('signup redirects to signup', async () => { - const { container } = render(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - // Assuming there's an element with id="home-link" that you want to click - const signupLink = container.querySelector('#signup'); // Use the actual ID here - if (signupLink) { - await userEvent.click(signupLink); - await mockRouter.isReady(); - } - - expect(mockRouter.currentRoute.value.name).toBe('sign up'); // Assuming 'Home' is the route name for '/' - }); - }); -}); + let store: any, mockRouter: any + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()) + store = useUserInfoStore() + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes() + }) + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) + } else { + next() + } + }) + }) + + describe('Component Rendering', () => { + it('renders form correctly', () => { + store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }) + + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + expect(wrapper.text()).toContain('E-post') + expect(wrapper.text()).toContain('Passord') + }) + }) + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + store.$patch({ accessToken: '' }) + + router.push('/') + await router.isReady() + + expect(router.currentRoute.value.name).toBe('login') + }) + + it('allows an unauthenticated user to visit signup', async () => { + store.$patch({ accessToken: 'valid-token' }) + + mockRouter.push('/sign-up') + + await mockRouter.isReady() + + expect(mockRouter.currentRoute.value.name).toBe('sign up') + }) + }) + + describe('Input fields', () => { + it('updates user credetials correctly', async () => { + const { getByPlaceholderText } = render(MyComponent) + + const emailInput = getByPlaceholderText('Skriv inn din e-post') as HTMLInputElement + const passwordInput = getByPlaceholderText('Skriv inn ditt passord') as HTMLInputElement + await fireEvent.update(emailInput, 'user@example.com') + await fireEvent.update(passwordInput, 'Password1') + + expect(emailInput.value).toBe('user@example.com') + expect(passwordInput.value).toBe('Password1') + }) + + it('Password error msg', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + const errorMsg = container.querySelector('#invalid') // Use the actual ID here + expect( + errorMsg?.textContent === + 'Password must be between 4 and 16 characters and contain one capital letter, small letter and a number' + ) + }) + + it('logout should have empty store at application start', () => { + expect(store.firstname).toBe('') + expect(store.lastname).toBe('') + expect(store.accessToken).toBe('') + }) + }) + + describe('Menu Actions', () => { + it('signup redirects to signup', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + // Assuming there's an element with id="home-link" that you want to click + const signupLink = container.querySelector('#signup') // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink) + await mockRouter.isReady() + } + + expect(mockRouter.currentRoute.value.name).toBe('sign up') // Assuming 'Home' is the route name for '/' + }) + }) +}) diff --git a/src/components/Login/__tests__/LoginLink.spec.ts b/src/components/Login/__tests__/LoginLink.spec.ts index 79b1a00b43514585a5175386ea7e346e35cfb440..d7dfe57dbc4ab1b40e449ed62965f3d4d22a0406 100644 --- a/src/components/Login/__tests__/LoginLink.spec.ts +++ b/src/components/Login/__tests__/LoginLink.spec.ts @@ -1,72 +1,71 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { createPinia, setActivePinia } from 'pinia'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import LoginPrompt from '@/components/Login/LoginLink.vue'; -import { useUserInfoStore } from '@/stores/UserStore'; -import router from '@/router/index'; -import { render, screen } from '@testing-library/vue'; -import userEvent from '@testing-library/user-event'; +import { describe, it, expect, beforeEach } from 'vitest' +import { createPinia, setActivePinia } from 'pinia' +import { createRouter, createMemoryHistory } from 'vue-router' +import LoginPrompt from '@/components/Login/LoginLink.vue' +import { useUserInfoStore } from '@/stores/UserStore' +import router from '@/router/index' +import { render, screen } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' describe('LoginPrompt', () => { - let store: any, mockRouter: any; + let store: any, mockRouter: any - beforeEach(() => { - // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); - mockRouter = createRouter({ - history: createMemoryHistory(), - routes: router.getRoutes(), - }); - router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); - } else { - next(); - } - }); - }); + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()) + store = useUserInfoStore() + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes() + }) + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) + } else { + next() + } + }) + }) + it('renders login link correctly', async () => { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', component: { template: 'Login Page' } }] + }) - it('renders login link correctly', async () => { - const router = createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/login', component: { template: 'Login Page' } }], - }); + const { getByText } = render(LoginPrompt, { + global: { + plugins: [router] + } + }) - const { getByText } = render(LoginPrompt, { - global: { - plugins: [router], - }, - }); + await router.isReady() // Ensure the router is ready before asserting - await router.isReady(); // Ensure the router is ready before asserting + const loginLink = getByText('Logg inn') + expect(loginLink).toBeDefined() // Check if the 'Login' link is rendered + }) - const loginLink = getByText('Logg inn'); - expect(loginLink).toBeDefined(); // Check if the 'Login' link is rendered - }); + it('navigates to the login page when the login link is clicked', async () => { + const mockRouter = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }] + }) - it('navigates to the login page when the login link is clicked', async () => { - const mockRouter = createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }], - }); - - const { container } = render(LoginPrompt, { - global: { - plugins: [mockRouter], - }, - }); - - await mockRouter.isReady(); // Ensure the router is ready before asserting - - const loginLink = container.querySelector('#login'); // Use the actual ID here - if (loginLink) { - await userEvent.click(loginLink); - await mockRouter.isReady(); - } - - expect(mockRouter.currentRoute.value.path).toBe('/login'); // Check if the router navigated to the login page - }, 10000); -}); + const { container } = render(LoginPrompt, { + global: { + plugins: [mockRouter] + } + }) + + await mockRouter.isReady() // Ensure the router is ready before asserting + + const loginLink = container.querySelector('#login') // Use the actual ID here + if (loginLink) { + await userEvent.click(loginLink) + await mockRouter.isReady() + } + + expect(mockRouter.currentRoute.value.path).toBe('/login') // Check if the router navigated to the login page + }, 10000) +}) diff --git a/src/components/News/NewsFeed.vue b/src/components/News/NewsFeed.vue index 99abc7c31974222468ecd2b9c7da81bccb33e6c6..79db9757546cc417636742e7ed720c9ff1de9b44 100644 --- a/src/components/News/NewsFeed.vue +++ b/src/components/News/NewsFeed.vue @@ -1,13 +1,12 @@ <script lang="ts"> - /** * Interface representing a news article. */ interface news { - urlToImage: string; - title: string; - description: string; - url: string; + urlToImage: string + title: string + description: string + url: string } /** @@ -17,14 +16,14 @@ export default { data() { return { articles: [] as news[] - }; + } }, mounted() { - this.fetchFinanceNews(); + this.fetchFinanceNews() // Call fetchFinanceNews() every 5 minutes (300,000 milliseconds) // Done so the user does not need to refresh for news to be updated // Might remove for consistent reading - setInterval(this.fetchFinanceNews, 300000); + setInterval(this.fetchFinanceNews, 300000) }, methods: { /** @@ -33,28 +32,26 @@ export default { async fetchFinanceNews() { try { const response = await fetch( - 'https://newsapi.org/v2/everything?q=saving%20money&pageSize=10&apiKey=f092756b3b6b41369b047cb7ae980db5' - ); - const data = await response.json(); + 'https://newsapi.org/v2/everything?q=saving%20money&pageSize=10&apiKey=f092756b3b6b41369b047cb7ae980db5' + ) + const data = await response.json() //English articles, might want to translate to norwegian - this.articles = data.articles; - + this.articles = data.articles } catch (error) { - console.error('Error fetching saving money news:', error); + console.error('Error fetching saving money news:', error) } } } -}; +} </script> - <template> <div class="center-box"> <div class="box"> - <br> + <br /> <h1>Nyheter</h1> - <br> + <br /> <div v-for="(article, index) in articles" :key="index" class="article-container"> <div class="content"> <h3>{{ article.title }}</h3> @@ -62,7 +59,7 @@ export default { <a :href="article.url" target="_blank">Les mer</a> </div> <div class="image"> - <img :src="article.urlToImage" alt="Article Image"/> + <img :src="article.urlToImage" alt="Article Image" /> </div> </div> </div> @@ -77,7 +74,7 @@ export default { } .box { - width:90%; + width: 90%; } .article-container { @@ -118,5 +115,4 @@ export default { .content a:hover { background-color: #0056b3; } - -</style> \ No newline at end of file +</style> diff --git a/src/components/News/__tests__/NewsFeed.spec.ts b/src/components/News/__tests__/NewsFeed.spec.ts index 0c2a4e092954b6a74218fa0d4998cdb8910574bb..0674a2cfca74fc77cbbcea66f12e5434b4da3725 100644 --- a/src/components/News/__tests__/NewsFeed.spec.ts +++ b/src/components/News/__tests__/NewsFeed.spec.ts @@ -1,44 +1,44 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { mount } from '@vue/test-utils'; -import MyComponent from '../NewsFeed.vue'; // Adjust the import path according to your setup +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { mount } from '@vue/test-utils' +import MyComponent from '../NewsFeed.vue' // Adjust the import path according to your setup global.fetch = vi.fn(() => Promise.resolve( - new Response(JSON.stringify({ - articles: [ - { - urlToImage: 'example-image.jpg', - title: 'Test Title', - description: 'Test Description', - url: 'http://example.com' - } - ] - })) + new Response( + JSON.stringify({ + articles: [ + { + urlToImage: 'example-image.jpg', + title: 'Test Title', + description: 'Test Description', + url: 'http://example.com' + } + ] + }) + ) ) -); - +) describe('MyComponent', () => { - let wrapper :any; + let wrapper: any beforeEach(() => { - vi.useFakeTimers(); // Set up fake timers - vi.spyOn(global, 'setInterval'); // Spy on setInterval + vi.useFakeTimers() // Set up fake timers + vi.spyOn(global, 'setInterval') // Spy on setInterval // Setting up the wrapper before each test - wrapper = mount(MyComponent); - }); + wrapper = mount(MyComponent) + }) afterEach(() => { // Clearing all mocks and timers after each test - vi.clearAllMocks(); - vi.restoreAllMocks(); // Restore original implementations - vi.runOnlyPendingTimers(); - vi.useRealTimers(); // Use real timers again - }); - + vi.clearAllMocks() + vi.restoreAllMocks() // Restore original implementations + vi.runOnlyPendingTimers() + vi.useRealTimers() // Use real timers again + }) it('sets up an interval to fetch news every 5 minutes', () => { - expect(setInterval).toHaveBeenCalledWith(expect.any(Function), 300000); - }); -}); + expect(setInterval).toHaveBeenCalledWith(expect.any(Function), 300000) + }) +}) diff --git a/src/components/SavingGoal/SavingGoal.vue b/src/components/SavingGoal/SavingGoal.vue index 0ae8f52b788036ee227a3446f0d9050561e938f6..28281a8ca2eea51e382792036cf168dcc4810f35 100644 --- a/src/components/SavingGoal/SavingGoal.vue +++ b/src/components/SavingGoal/SavingGoal.vue @@ -1,14 +1,14 @@ <script lang="ts"> -import SavingGoalList from "@/components/SavingGoal/SavingGoalList.vue"; -import SavingGoalRoadmap from "@/components/SavingGoal/SavingGoalRoadmap.vue"; -import SavingGoalCreate from "@/components/SavingGoal/SavingGoalCreate.vue"; -import SavingGoalDefault from "@/components/SavingGoal/SavingGoalDefault.vue"; -import type {GoalDTO} from "@/api"; -import {GoalService} from "@/api"; -import {useUserInfoStore} from "@/stores/UserStore"; +import SavingGoalList from '@/components/SavingGoal/SavingGoalList.vue' +import SavingGoalRoadmap from '@/components/SavingGoal/SavingGoalRoadmap.vue' +import SavingGoalCreate from '@/components/SavingGoal/SavingGoalCreate.vue' +import SavingGoalDefault from '@/components/SavingGoal/SavingGoalDefault.vue' +import type { GoalDTO } from '@/api' +import { GoalService } from '@/api' +import { useUserInfoStore } from '@/stores/UserStore' export default { - components: {SavingGoalDefault, SavingGoalCreate, SavingGoalRoadmap, SavingGoalList}, + components: { SavingGoalDefault, SavingGoalCreate, SavingGoalRoadmap, SavingGoalList }, data() { return { bluePanelMaxHeight: 'auto' as string, @@ -17,81 +17,108 @@ export default { selectedGoal: [] as any, createdGoal: [] as any, key: 0 as number, - keyForList: 0 as number, - }; + keyForList: 0 as number + } }, mounted() { - this.calculateBluePanelMaxHeight(); + this.calculateBluePanelMaxHeight() }, methods: { useUserInfoStore, calculateBluePanelMaxHeight() { // Query the timeline element - const timelineElement = document.querySelector('.timeline'); + const timelineElement = document.querySelector('.timeline') if (timelineElement instanceof HTMLElement) { // Calculate the max-height based on the height of the timeline - const timelineHeight = timelineElement.offsetHeight; + const timelineHeight = timelineElement.offsetHeight console.log(timelineHeight) - this.bluePanelMaxHeight = (timelineHeight * 1.5)+'px'; + this.bluePanelMaxHeight = timelineHeight * 1.5 + 'px' } else { - this.bluePanelMaxHeight = '700px'; + this.bluePanelMaxHeight = '700px' } }, createGoal() { - this.createClicked = true; + this.createClicked = true }, async goToSavingGoal(savingGoal: GoalDTO) { - this.$emit('goToSavingGoal', savingGoal); - this.selectedGoal = await GoalService.getGoal({id: savingGoal.id as number}) - this.createClicked = false; - this.savingGoalClicked = true; + this.$emit('goToSavingGoal', savingGoal) + this.selectedGoal = await GoalService.getGoal({ id: savingGoal.id as number }) + this.createClicked = false + this.savingGoalClicked = true this.key++ setTimeout(() => { this.calculateBluePanelMaxHeight() - }, 500); + }, 500) }, async handleCreateGoalClicked(savingGoal: GoalDTO) { - this.$emit('goToSavingGoal', savingGoal); - let response = await GoalService.getGoal({id: savingGoal.id as number}); + this.$emit('goToSavingGoal', savingGoal) + let response = await GoalService.getGoal({ id: savingGoal.id as number }) setTimeout(() => { this.selectedGoal = response - this.createClicked = false; + this.createClicked = false this.key++ - this.savingGoalClicked = true; + this.savingGoalClicked = true this.keyForList++ - }, 100); + }, 100) }, async refreshSpareSti() { try { - this.selectedGoal = await GoalService.getGoal({id: this.selectedGoal.id as number}) - console.log("yessir") - this.key++; + this.selectedGoal = await GoalService.getGoal({ id: this.selectedGoal.id as number }) + console.log('yessir') + this.key++ } catch (error) { console.log(error) } } - }, -}; + } +} </script> <template> <div class="cont"> <div class="row"> - <div class="col-lg-4 blue-background overflow-scroll" :style="{ 'max-height': bluePanelMaxHeight }"> + <div + class="col-lg-4 blue-background overflow-scroll" + :style="{ 'max-height': bluePanelMaxHeight }" + > <h2>Dine sparemål</h2> <div> - <button class="btn btn-success btn-lg" style="font-weight: 600; margin-bottom: 20px" @click="createGoal">+ Lag et nytt sparemål</button> + <button + class="btn btn-success btn-lg" + style="font-weight: 600; margin-bottom: 20px" + @click="createGoal" + > + + Lag et nytt sparemål + </button> </div> <saving-goal-list :key="keyForList" @goToSavingGoal="goToSavingGoal"></saving-goal-list> </div> <div class="spacer"> - <div v-if="!useUserInfoStore().isPremium && !useUserInfoStore().isNoAds" v-for="(challenge, index) in 5" :key="index"> - <img v-if="index % 2 === 0" src="https://www.codefuel.com/wp-content/uploads/2022/10/image1-1.png"> - <img v-else src="https://www.vaultnetworks.com/wp-content/uploads/2012/11/PROMO-BLOG-AD-YELLOW-VERTICAL.png"> + <div + v-if="!useUserInfoStore().isPremium && !useUserInfoStore().isNoAds" + v-for="(challenge, index) in 5" + :key="index" + > + <img + v-if="index % 2 === 0" + src="https://www.codefuel.com/wp-content/uploads/2022/10/image1-1.png" + /> + <img + v-else + src="https://www.vaultnetworks.com/wp-content/uploads/2012/11/PROMO-BLOG-AD-YELLOW-VERTICAL.png" + /> </div> </div> - <saving-goal-create @createGoalClicked="handleCreateGoalClicked" v-if="createClicked"></saving-goal-create> - <saving-goal-roadmap @refreshSavingGoal="refreshSpareSti" :key="key" :selected-goal="selectedGoal" v-else-if="savingGoalClicked"></saving-goal-roadmap> + <saving-goal-create + @createGoalClicked="handleCreateGoalClicked" + v-if="createClicked" + ></saving-goal-create> + <saving-goal-roadmap + @refreshSavingGoal="refreshSpareSti" + :key="key" + :selected-goal="selectedGoal" + v-else-if="savingGoalClicked" + ></saving-goal-roadmap> <saving-goal-default v-else></saving-goal-default> </div> </div> @@ -109,7 +136,7 @@ export default { margin-top: 20px; margin-bottom: 20px; padding: 12px; - background-color: #003A58; + background-color: #003a58; width: 27%; border-radius: 0 1em 1em 0; } @@ -129,6 +156,5 @@ h2 { color: white; margin-bottom: 16px; font-weight: 600; - } -</style> \ No newline at end of file +</style> diff --git a/src/components/SavingGoal/SavingGoalCreate.vue b/src/components/SavingGoal/SavingGoalCreate.vue index 2fe1e2cdd1914d0792453c465ea2b8789076a673..cd13897b48652edf2e536daf5d58ce3cca3c4580 100644 --- a/src/components/SavingGoal/SavingGoalCreate.vue +++ b/src/components/SavingGoal/SavingGoalCreate.vue @@ -1,52 +1,57 @@ <script setup lang="ts"> -import {GoalService, type CreateGoalDTO, type GoalDTO} from "@/api" -import {ref, defineProps, defineEmits} from "vue"; +import { GoalService, type CreateGoalDTO, type GoalDTO } from '@/api' +import { ref, defineProps, defineEmits } from 'vue' -const emits = defineEmits(['createGoalClicked']); +const emits = defineEmits(['createGoalClicked']) -const name = ref("") -const desc = ref("") -const date = ref("") +const name = ref('') +const desc = ref('') +const date = ref('') const amount = ref(0) -const createdConfirm = ref(""); -const errorMessage = ref("") +const createdConfirm = ref('') +const errorMessage = ref('') const createGoalClick = async () => { const createGoalPayload: CreateGoalDTO = { name: name.value, description: desc.value, targetAmount: amount.value, - targetDate: date.value + " 00:00:00.000000000", - }; - + targetDate: date.value + ' 00:00:00.000000000' + } try { - let response = await GoalService.createGoal({ requestBody: createGoalPayload }); - if(response.name != "") { - createdConfirm.value = "Your goal has been created!" - errorMessage.value = "" + let response = await GoalService.createGoal({ requestBody: createGoalPayload }) + if (response.name != '') { + createdConfirm.value = 'Your goal has been created!' + errorMessage.value = '' emits('createGoalClicked', response) } } catch (error: any) { - console.log(error.message); - errorMessage.value = error.message; + console.log(error.message) + errorMessage.value = error.message } } - </script> <template> <div class="col-lg-8"> <h1>Lag et nytt sparemål!</h1> - <br> - <p>Gi sparemålet et navn </p> + <br /> + <p>Gi sparemålet et navn</p> <div class="input-group mb-3"> <span class="input-group-text" id="basic-addon1">Navn</span> - <input v-model="name" type="text" class="form-control" placeholder="Navn på sparemålet" - aria-label="Username" aria-describedby="basic-addon1" required> + <input + v-model="name" + type="text" + class="form-control" + placeholder="Navn på sparemålet" + aria-label="Username" + aria-describedby="basic-addon1" + required + /> </div> - <p>Legg til en beskrivelse for sparemålet </p> + <p>Legg til en beskrivelse for sparemålet</p> <div class="input-group mb-3"> <span class="input-group-text" id="basic-addon2">Beskrivelse</span> <textarea v-model="desc" class="form-control" aria-label="With textarea"></textarea> @@ -55,16 +60,16 @@ const createGoalClick = async () => { <!--Change this to date picker?--> <p>Når skal pengene være spart?</p> <div class="input-group mb-3"> - <input v-model="date" type="date" class="form-control" aria-label="Amount of days" required> + <input v-model="date" type="date" class="form-control" aria-label="Amount of days" required /> </div> <p>Hvor mye vil du spare?</p> <div class="input-group"> - <input v-model="amount" type="number" class="form-control" aria-label="NOK" required> + <input v-model="amount" type="number" class="form-control" aria-label="NOK" required /> <span class="input-group-text">NOK</span> </div> - <br> + <br /> <button class="btn btn-primary btn-lg" @click="createGoalClick">Create goal!</button> <div class="confirmMessage"> @@ -90,4 +95,4 @@ const createGoalClick = async () => { font-size: 32px; min-height: 100px; } -</style> \ No newline at end of file +</style> diff --git a/src/components/SavingGoal/SavingGoalDefault.vue b/src/components/SavingGoal/SavingGoalDefault.vue index 683d5647eae83ab34e4ce366f31e97adc6b15c12..d38481920c55ae587b06130ad95c310e0446ccd3 100644 --- a/src/components/SavingGoal/SavingGoalDefault.vue +++ b/src/components/SavingGoal/SavingGoalDefault.vue @@ -1,6 +1,4 @@ -<script setup lang="ts"> - -</script> +<script setup lang="ts"></script> <template> <section id="hero" class="hero section"> @@ -8,8 +6,10 @@ <div class="container text-center"> <div class="d-flex flex-column justify-content-center align-items-center"> <h1>Velkommen til <span>SpareSti</span></h1> - <p style="margin-top: 32px">Kom i økonomisk form: Ta på deg våre spareutfordringer!<br></p> - <img src="../../assets/savingPigRun.png" alt="SpareSti-logo"> + <p style="margin-top: 32px"> + Kom i økonomisk form: Ta på deg våre spareutfordringer!<br /> + </p> + <img src="../../assets/savingPigRun.png" alt="SpareSti-logo" /> </div> </div> </div> @@ -53,21 +53,20 @@ .hero h1 span { color: white; - background-color: #003A58; + background-color: #003a58; padding: 4px 24px 14px 24px; border-radius: 6px; } - .hero p { - color: #003A58; + color: #003a58; margin: 5px 0 30px 0; font-size: 28px; font-weight: 400; } .hero .btn-watch-video i { - color: #003A58; + color: #003a58; font-size: 32px; transition: 0.3s; line-height: 0; @@ -75,7 +74,7 @@ } .hero .btn-watch-video:hover i { - color: #003A58; + color: #003a58; } @media (max-width: 640px) { @@ -99,4 +98,4 @@ z-index: 1; } } -</style> \ No newline at end of file +</style> diff --git a/src/components/SavingGoal/SavingGoalList.vue b/src/components/SavingGoal/SavingGoalList.vue index 975aa69196b6faca35be84f99823b1e4d3068fc8..5fec4b8c73e6fcd67bd54adc53e3cd5f5f2382c7 100644 --- a/src/components/SavingGoal/SavingGoalList.vue +++ b/src/components/SavingGoal/SavingGoalList.vue @@ -1,66 +1,64 @@ <script setup lang="ts"> -import {ref, onMounted} from "vue"; -import {GoalService, type GoalDTO, type ChallengeDTO} from "@/api" +import { ref, onMounted } from 'vue' +import { GoalService, type GoalDTO, type ChallengeDTO } from '@/api' const savingGoalList = ref(null as any) const oldSavingGoalList = ref(null as any) const getGoals = async () => { try { - let response = await GoalService.getGoals(); + let response = await GoalService.getGoals() savingGoalList.value = [] oldSavingGoalList.value = [] const date = new Date() response.forEach((goal: GoalDTO) => { - if(goal.targetDate) { + if (goal.targetDate) { const targetDate = new Date(goal.targetDate) - if(targetDate < date) { - console.log("old") + if (targetDate < date) { + console.log('old') oldSavingGoalList.value.push(goal) } else { - console.log("current") + console.log('current') savingGoalList.value.push(goal) } } }) } catch (error: any) { - console.log(error.message); + console.log(error.message) } } onMounted(getGoals) -const emits = defineEmits(['goToSavingGoal']); +const emits = defineEmits(['goToSavingGoal']) const goToSavingGoal = (savingGoal: GoalDTO) => { - emits('goToSavingGoal', savingGoal); -}; - -const deleteSavingGoal = () => { + emits('goToSavingGoal', savingGoal) +} -}; +const deleteSavingGoal = () => {} -function calculateSavedSoFar (goal: GoalDTO) { - console.log("hehe") - let savedSoFar = 0; // Reset savedSoFar before calculating again - if(goal.challenges){ - console.log("hehehe") +function calculateSavedSoFar(goal: GoalDTO) { + console.log('hehe') + let savedSoFar = 0 // Reset savedSoFar before calculating again + if (goal.challenges) { + console.log('hehehe') goal.challenges.forEach((challenge: ChallengeDTO) => { // Check if progressList exists before accessing its elements if (challenge.progressList) { challenge.progressList.forEach((progress: any) => { // Assuming 'amount' is the property you want to add from progressList - savedSoFar += progress.amount; - console.log("he") - }); + savedSoFar += progress.amount + console.log('he') + }) } - }); + }) } return savedSoFar } function formatDate(date: string) { - const date1 = new Date(date); + const date1 = new Date(date) return date1.toISOString().split('T')[0] } </script> @@ -71,29 +69,30 @@ function formatDate(date: string) { <h3>Current:</h3> </div> <div v-for="(savingGoal, index) in savingGoalList" :key="index" class="card bg-body"> - <div class="card-header"> - Sparemål {{ index + 1 }} - </div> + <div class="card-header">Sparemål {{ index + 1 }}</div> <div class="card-body"> <h5 class="card-title">{{ savingGoal.name }}</h5> <p class="card-text">{{ savingGoal.description }}</p> - <p class="card-text">Spart: {{calculateSavedSoFar(savingGoal)}}/{{savingGoal.targetAmount}} Kr</p> - <p class="card-text">Avsluttes: {{formatDate(savingGoal.targetDate)}} </p> + <p class="card-text"> + Spart: {{ calculateSavedSoFar(savingGoal) }}/{{ savingGoal.targetAmount }} Kr + </p> + <p class="card-text">Avsluttes: {{ formatDate(savingGoal.targetDate) }}</p> <a class="btn btn-primary" @click="goToSavingGoal(savingGoal)">Gå til sparemål</a> <a class="btn btn-danger" @click="deleteSavingGoal" style="margin-left: 8px"> - <img src="../../assets/icons/trash-can.svg"> Slett</a> + <img src="../../assets/icons/trash-can.svg" /> Slett</a + > </div> </div> <div v-if="oldSavingGoalList.lenght > 0"> <h3>Old:</h3> <div v-for="(savingGoal, index) in oldSavingGoalList" :key="index" class="card bg-body"> - <div class="card-header"> - Sparemål {{ index + 1 }} - </div> + <div class="card-header">Sparemål {{ index + 1 }}</div> <div class="card-body"> <h5 class="card-title">{{ savingGoal.name }}</h5> <p class="card-text">{{ savingGoal.description }}</p> - <p class="card-text">{{calculateSavedSoFar(savingGoal)}}/{{savingGoal.targetAmount}}</p> + <p class="card-text"> + {{ calculateSavedSoFar(savingGoal) }}/{{ savingGoal.targetAmount }} + </p> <a class="btn btn-primary" @click="goToSavingGoal(savingGoal)">Gå til sparemål</a> <a class="btn btn-danger" @click="deleteSavingGoal" style="margin-left: 8px">Slett</a> </div> @@ -102,17 +101,19 @@ function formatDate(date: string) { </div> <div class="loading" v-else> Laster... - <img src="../../assets/loadingPig.png" alt="loadingPig"> + <img src="../../assets/loadingPig.png" alt="loadingPig" /> </div> </template> <style scoped> -.card-header, .card-text, .card-title { +.card-header, +.card-text, +.card-title { color: white; } .card-header { - background-color: #40698A; + background-color: #40698a; } .card-body { @@ -142,4 +143,4 @@ function formatDate(date: string) { h3 { color: white; } -</style> \ No newline at end of file +</style> diff --git a/src/components/SavingGoal/SavingGoalRoadmap.vue b/src/components/SavingGoal/SavingGoalRoadmap.vue index e9b447889bc0ced5921228c9556ab6e20418c864..fca00e58deb4eefdb7f78dbdd5b890e91d8680b7 100644 --- a/src/components/SavingGoal/SavingGoalRoadmap.vue +++ b/src/components/SavingGoal/SavingGoalRoadmap.vue @@ -1,27 +1,28 @@ <script lang="ts"> -import {CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, Title, Tooltip} from 'chart.js' -import {Line} from 'vue-chartjs' +import { + CategoryScale, + Chart as ChartJS, + Legend, + LinearScale, + LineElement, + PointElement, + Title, + Tooltip +} from 'chart.js' +import { Line } from 'vue-chartjs' import { type ChallengeDTO, type CreateGoalDTO, type GoalDTO, type MarkChallengeDTO, - TransactionControllerService, type TransactionDTO, + TransactionControllerService, + type TransactionDTO, UserService -} from "@/api"; -import {GoalService} from '@/api' +} from '@/api' +import { GoalService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend -) - +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend) export default { components: { @@ -29,16 +30,19 @@ export default { }, data() { return { - image: 'https://th.bing.com/th/id/OIG3.NMbdxmKYKVnxYGLOa0Z0?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, - altImage: 'https://th.bing.com/th/id/OIG4.gVWUC.rwCb8faTNx31yU?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, + image: + 'https://th.bing.com/th/id/OIG3.NMbdxmKYKVnxYGLOa0Z0?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, + altImage: + 'https://th.bing.com/th/id/OIG4.gVWUC.rwCb8faTNx31yU?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, failedImage: 'https://cdn-icons-png.flaticon.com/512/6659/6659895.png' as string, - successImage: 'https://static-00.iconduck.com/assets.00/checkmark-running-icon-1024x1024-aakqv1qi.png' as string, + successImage: + 'https://static-00.iconduck.com/assets.00/checkmark-running-icon-1024x1024-aakqv1qi.png' as string, title: 'Spain trip' as string, bluePanelMaxHeight: 'auto' as string, roadmapSelected: true as boolean, statsSelected: false as boolean, chartData: { - labels: ["start"], + labels: ['start'], datasets: [ { label: this.selectedGoal.name, @@ -60,8 +64,8 @@ export default { newPrice: 0, savedSoFar: 0 as number, currentChallengeIndex: 0, - feedback: "" as string, - }; + feedback: '' as string + } }, async mounted() { setTimeout(() => { @@ -73,7 +77,7 @@ export default { this.onLoadDisableChecks(this.selectedGoal) this.onLoadAddDataToGraph(this.selectedGoal) console.log() - }, 500); + }, 500) }, computed: { computeImageFilter() { @@ -87,36 +91,38 @@ export default { return 'grayscale(100%)' } else { // Challenge is currently active, no grayscale - return 'none'; + return 'none' } - }; + } } }, props: { selectedGoal: { type: Object, - default: null, - }, + default: null + } }, methods: { togglePanel(step: any) { if (step.showPanel) { - step.showPanel = false; + step.showPanel = false } else { - this.selectedGoal.challenges.forEach((s: any) => (s.showPanel = false)); - step.showPanel = true; - this.scrollToPanel(step); + this.selectedGoal.challenges.forEach((s: any) => (s.showPanel = false)) + step.showPanel = true + this.scrollToPanel(step) } }, scrollToPanel(step: any) { if (step.showPanel) { this.$nextTick(() => { - const panel = document.getElementById(`panel-${this.selectedGoal.challenges.indexOf(step)}`); + const panel = document.getElementById( + `panel-${this.selectedGoal.challenges.indexOf(step)}` + ) if (panel) { - panel.scrollIntoView({ behavior: 'smooth', block: 'center' }); + panel.scrollIntoView({ behavior: 'smooth', block: 'center' }) } - }); + }) } }, @@ -130,7 +136,7 @@ export default { setTimeout(() => { this.onLoadDisableChecks(this.selectedGoal) this.disableAllChecksThatNotCurrent() - }, 100); + }, 100) } }, @@ -142,7 +148,7 @@ export default { challengeText = challengeText.replace('{totalDays}', challenge.totalDays?.toString()) let totalAmount: any if (challenge.checkDays !== undefined && challenge.amount !== undefined) { - totalAmount = challenge.checkDays * challenge.amount; + totalAmount = challenge.checkDays * challenge.amount } else { // Handle the case when challenge.checkDays or challenge.amount is undefined } @@ -160,106 +166,105 @@ export default { }, async handleCheckboxClick(challenge: ChallengeDTO, index: number, amount: number) { - this.lockCheckBox(challenge, index) const markChallengePayload: MarkChallengeDTO = { id: challenge.id, day: index, - amount: amount, - }; + amount: amount + } try { - await GoalService.updateChallenge({ requestBody: markChallengePayload }); - const today: Date = new Date(); - const dateString: string = today.toISOString().split('T')[0]; // Extract YYYY-MM-DD part + await GoalService.updateChallenge({ requestBody: markChallengePayload }) + const today: Date = new Date() + const dateString: string = today.toISOString().split('T')[0] // Extract YYYY-MM-DD part if (challenge.progressList) { challenge.progressList.push({ day: index, amount: amount, completedAt: dateString }) } - this.addDataToChart(amount, dateString); + this.addDataToChart(amount, dateString) await this.transferMoney(amount) - this.calculateSavedSoFar(); + this.calculateSavedSoFar() } catch (error: any) { handleUnknownError(error) - console.log(error.message); + console.log(error.message) } }, lockCheckBox(challenge: ChallengeDTO, index: number) { const checkboxId = challenge.id + 'inlineCheckbox' + index - const checkbox = document.getElementById(checkboxId) as HTMLInputElement | null; + const checkbox = document.getElementById(checkboxId) as HTMLInputElement | null if (checkbox) { // Disable the checkbox - checkbox.disabled = true; + checkbox.disabled = true } }, onLoadDisableChecks(goal: GoalDTO) { - (goal.challenges || []).forEach((challenge: any) => { + ;(goal.challenges || []).forEach((challenge: any) => { challenge.progressList.forEach((progress: any) => { // Assuming 'amount' is the property you want to add from progressList const checkBoxId = challenge.id + 'inlineCheckbox' + progress.day - const checkbox = document.getElementById(checkBoxId) as HTMLInputElement | null; + const checkbox = document.getElementById(checkBoxId) as HTMLInputElement | null if (checkbox) { // Disable the checkbox - checkbox.checked = true; - checkbox.disabled = true; + checkbox.checked = true + checkbox.disabled = true } - }); - }); + }) + }) }, onLoadAddDataToGraph(goal: GoalDTO) { - (goal.challenges || []).forEach((challenge: any) => { + ;(goal.challenges || []).forEach((challenge: any) => { challenge.progressList.forEach((progress: any) => { - this.addDataToChart(progress.amount, progress.completedAt); - }); - }); + this.addDataToChart(progress.amount, progress.completedAt) + }) + }) }, addDataToChart(data: number, date: string) { // Find the last dataset - const lastDataset = this.chartData.datasets[this.chartData.datasets.length - 1]; + const lastDataset = this.chartData.datasets[this.chartData.datasets.length - 1] // Calculate the new label based on the last label - const newLabel = date.split('T')[0]; + const newLabel = date.split('T')[0] // Calculate the new data point based on the last data point - const lastDataPoint = lastDataset.data[lastDataset.data.length - 1]; - const newDataPoint = lastDataPoint + data; + const lastDataPoint = lastDataset.data[lastDataset.data.length - 1] + const newDataPoint = lastDataPoint + data // Add the new label and data point to the chart data - this.chartData.labels.push(newLabel); - lastDataset.data.push(newDataPoint); + this.chartData.labels.push(newLabel) + lastDataset.data.push(newDataPoint) }, calculateSavedSoFar() { - this.savedSoFar = 0; // Reset savedSoFar before calculating again + this.savedSoFar = 0 // Reset savedSoFar before calculating again this.selectedGoal.challenges.forEach((challenge: ChallengeDTO) => { // Check if progressList exists before accessing its elements if (challenge.progressList) { challenge.progressList.forEach((progress: any) => { // Assuming 'amount' is the property you want to add from progressList - this.savedSoFar += progress.amount; - }); + this.savedSoFar += progress.amount + }) } - }); + }) }, findCurrentChallenge() { - const today: Date = new Date(); + const today: Date = new Date() this.selectedGoal.challenges.forEach((challenge: ChallengeDTO, index: number) => { - const startDate: Date = new Date(challenge.startDate as string); - const endDate: Date = new Date(challenge.endDate as string); + const startDate: Date = new Date(challenge.startDate as string) + const endDate: Date = new Date(challenge.endDate as string) if (today >= startDate && today <= endDate) { this.currentChallengeIndex = index } else { if (today >= endDate) { - console.log("In the past") + console.log('In the past') } else { - console.log("In the future") + console.log('In the future') } } }) @@ -276,14 +281,14 @@ export default { }, formatDate(date: string) { - const date1 = new Date(date); + const date1 = new Date(date) return date1.toISOString().split('T')[0] }, calculateSavedSoFarPerChallengeInPercent(challenge: ChallengeDTO) { let savedSoFarOnChallenge = this.calculateSavedSoFarPerChallenge(challenge) let targetAmount = 1 - if(challenge.amount && challenge.checkDays) { + if (challenge.amount && challenge.checkDays) { targetAmount = challenge.amount * challenge.checkDays } @@ -292,22 +297,22 @@ export default { calculateSavedSoFarPerChallenge(challenge: ChallengeDTO) { let savedSoFar = 0 - challenge.progressList?.forEach(progress => { - if(progress.amount) { + challenge.progressList?.forEach((progress) => { + if (progress.amount) { savedSoFar += progress.amount } }) return savedSoFar }, - async updateUnitPrice (challenge: ChallengeDTO) { + async updateUnitPrice(challenge: ChallengeDTO) { const createGoalPayload: MarkChallengeDTO = { id: challenge.id, amount: this.newPrice - }; + } try { - await GoalService.updateChallengeAmount({requestBody: createGoalPayload}) - this.$emit('refreshSavingGoal'); + await GoalService.updateChallengeAmount({ requestBody: createGoalPayload }) + this.$emit('refreshSavingGoal') } catch (e: any) { handleUnknownError(e) console.log(e.message) @@ -318,30 +323,31 @@ export default { let possibleSaving = this.calculateTotalAmountFromChallenges() let wantedSaving = this.selectedGoal.targetAmount - console.log(possibleSaving + "," + wantedSaving) - if(wantedSaving > possibleSaving) { - this.feedback = "Vi beundrer din ambisjon, men å oppnå den ettertraktede" + - " summen er ikke lett. Men disse utfordringene tar deg på god vei!" + console.log(possibleSaving + ',' + wantedSaving) + if (wantedSaving > possibleSaving) { + this.feedback = + 'Vi beundrer din ambisjon, men å oppnå den ettertraktede' + + ' summen er ikke lett. Men disse utfordringene tar deg på god vei!' } }, getImageSource(challenge: ChallengeDTO) { - const today = new Date(); - const endDate = new Date(challenge.endDate as any); + const today = new Date() + const endDate = new Date(challenge.endDate as any) // Check if the challenge is in the past if (today > endDate) { // Challenge is in the past, return alternative image source - if(challenge.progressList) { - if(challenge.checkDays == challenge.progressList.length) { + if (challenge.progressList) { + if (challenge.checkDays == challenge.progressList.length) { return this.successImage } else { - return this.failedImage; + return this.failedImage } } } else { // Challenge is currently active or in the future, return default image source - return this.image; + return this.image } }, @@ -353,88 +359,157 @@ export default { const transactionPayload: TransactionDTO = { debtorBBAN: spendingAccount, creditorBBAN: savingAccount, - amount: amount, + amount: amount } - await TransactionControllerService.transferToSelf({requestBody: transactionPayload}) + await TransactionControllerService.transferToSelf({ requestBody: transactionPayload }) }, async regenerateChallenge(challenge: ChallengeDTO) { let challengeId = challenge.id as number try { - let response = await GoalService.regenerateChallenge({id: challengeId}) + let response = await GoalService.regenerateChallenge({ id: challengeId }) console.log(response) - this.$emit('refreshSavingGoal'); + this.$emit('refreshSavingGoal') } catch (e) { handleUnknownError(e) } - }, - }, -}; + } + } +} </script> <template> <div class="col-lg-8"> <div class="SavingGoalTitle text-center"> - {{selectedGoal.name}} - <br> + {{ selectedGoal.name }} + <br /> <p class="d-inline-flex gap-1"> - <button @click="changeDisplay" class="btn btn-primary" type="button" style="font-size: 25px;"> - <div v-if="roadmapSelected"> - Se statistikk - </div> - <div v-else> - Se sparesti - </div> + <button + @click="changeDisplay" + class="btn btn-primary" + type="button" + style="font-size: 25px" + > + <div v-if="roadmapSelected">Se statistikk</div> + <div v-else>Se sparesti</div> </button> </p> </div> <div v-if="roadmapSelected"> <ul class="timeline"> - <li v-for="(challenge, index) in selectedGoal.challenges" :key="index" :class="{ 'timeline-inverted': index % 2 !== 0 }"> + <li + v-for="(challenge, index) in selectedGoal.challenges" + :key="index" + :class="{ 'timeline-inverted': index % 2 !== 0 }" + > <div class="timeline-image z-1" @click="togglePanel(challenge)"> - <img class="circular-image" :src="challenge.showPanel ? altImage : getImageSource(challenge)" :style="{ filter: computeImageFilter(challenge) }" alt=""> + <img + class="circular-image" + :src="challenge.showPanel ? altImage : getImageSource(challenge)" + :style="{ filter: computeImageFilter(challenge) }" + alt="" + /> </div> <div class="timeline-panel z-3" :id="'panel-' + index" v-show="challenge.showPanel"> <div class="timeline-heading"> <div class="coinAndRegen"> <div class="coinCoin"> - <h5 style="margin-top: 12px">{{challenge.points}}<img src="../../assets/items/pigcoin.png" alt="pig coin" style="width: 2rem"></h5> + <h5 style="margin-top: 12px"> + {{ challenge.points + }}<img + src="../../assets/items/pigcoin.png" + alt="pig coin" + style="width: 2rem" + /> + </h5> </div> <div class="coinButton"> - <a @click="regenerateChallenge(challenge)" style="cursor: pointer"><img src="../../assets/icons/refresh.svg"/> Regenerer</a> + <a @click="regenerateChallenge(challenge)" style="cursor: pointer" + ><img src="../../assets/icons/refresh.svg" /> Regenerer</a + > </div> </div> - <h4>{{ challenge.challengeTemplate.templateName}}</h4> - <p style="font-size: 12px">{{formatDate(challenge.startDate)}} til {{formatDate(challenge.endDate)}}</p> - <h4 class="subheading">{{convertTemplateTextToChallengeText(challenge)}}</h4> + <h4>{{ challenge.challengeTemplate.templateName }}</h4> + <p style="font-size: 12px"> + {{ formatDate(challenge.startDate) }} til {{ formatDate(challenge.endDate) }} + </p> + <h4 class="subheading">{{ convertTemplateTextToChallengeText(challenge) }}</h4> </div> <div class="timeline-body"> - <br> + <br /> <p> - Pris per enhet: {{challenge.amount}} kr <img src="../../assets/icons/edit-button.svg" alt="editIcon" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="exampleModal"> + Pris per enhet: {{ challenge.amount }} kr + <img + src="../../assets/icons/edit-button.svg" + alt="editIcon" + data-bs-toggle="collapse" + href="#collapseExample" + role="button" + aria-expanded="false" + aria-controls="exampleModal" + /> </p> - <br> - <div class="collapse" id="collapseExample" style="background-color: white; padding: 12px; border-radius: 5px"> + <br /> + <div + class="collapse" + id="collapseExample" + style="background-color: white; padding: 12px; border-radius: 5px" + > <div class="card card-body"> <div class="input-group mb-3"> <span class="input-group-text" id="basic-addon1">Ny pris</span> - <input v-model="newPrice" type="number" class="form-control" placeholder="Pris i kr" aria-label="newPrice" aria-describedby="basic-addon1" required> + <input + v-model="newPrice" + type="number" + class="form-control" + placeholder="Pris i kr" + aria-label="newPrice" + aria-describedby="basic-addon1" + required + /> </div> </div> - <br> - <button @click="updateUnitPrice(challenge)" class="btn btn-success">Bekreft endring</button> + <br /> + <button @click="updateUnitPrice(challenge)" class="btn btn-success"> + Bekreft endring + </button> </div> - <br> - <p>Spart: {{ calculateSavedSoFarPerChallenge(challenge)}} Kr</p> + <br /> + <p>Spart: {{ calculateSavedSoFarPerChallenge(challenge) }} Kr</p> <div class="progress"> - <div class="progress-bar" role="progressbar" :style="{ width: calculateSavedSoFarPerChallengeInPercent(challenge) + '%' }" :aria-valuenow="calculateSavedSoFarPerChallengeInPercent(challenge)" aria-valuemin="0" aria-valuemax="100"></div> + <div + class="progress-bar" + role="progressbar" + :style="{ width: calculateSavedSoFarPerChallengeInPercent(challenge) + '%' }" + :aria-valuenow="calculateSavedSoFarPerChallengeInPercent(challenge)" + aria-valuemin="0" + aria-valuemax="100" + ></div> </div> - <br> + <br /> <div class="checkbox-row"> - <div class="form-check form-check-inline" v-for="(day, index) in challenge.checkDays" :key="index"> - <input class="form-check-input" @click="handleCheckboxClick(challenge, index+1, challenge.challengeTemplate.amount)" type="checkbox" :id="challenge.id + 'inlineCheckbox' + (index + 1)" :value="day" :disabled="day.checked"> - <label class="form-check-label" style="color:white;" :for="'inlineCheckbox' + (index + 1)">{{ day }}</label> + <div + class="form-check form-check-inline" + v-for="(day, index) in challenge.checkDays" + :key="index" + > + <input + class="form-check-input" + @click=" + handleCheckboxClick(challenge, index + 1, challenge.challengeTemplate.amount) + " + type="checkbox" + :id="challenge.id + 'inlineCheckbox' + (index + 1)" + :value="day" + :disabled="day.checked" + /> + <label + class="form-check-label" + style="color: white" + :for="'inlineCheckbox' + (index + 1)" + >{{ day }}</label + > </div> </div> </div> @@ -444,37 +519,46 @@ export default { </ul> </div> <div v-else> - <div class="row"> <div v-if="feedback != ''" class="feedbackBox"> <h3>Oops!</h3> <h5 class="">{{ feedback }}</h5> </div> <div class="col-sm-3"> - <div class="card-box tilebox-one"><i class="icon-layers float-right text-muted"></i> + <div class="card-box tilebox-one"> + <i class="icon-layers float-right text-muted"></i> <h6 class="text-muted text-uppercase mt-0">Du ønsker å spare</h6> - <h2 class="" data-plugin="counterup">Kr {{ selectedGoal.targetAmount }}</h2></div> + <h2 class="" data-plugin="counterup">Kr {{ selectedGoal.targetAmount }}</h2> + </div> </div> <div class="col-sm-3"> - <div class="card-box tilebox-one"><i class="icon-rocket float-right text-muted"></i> + <div class="card-box tilebox-one"> + <i class="icon-rocket float-right text-muted"></i> <h6 class="text-muted text-uppercase mt-0">Du kan spare</h6> - <h2 class="" data-plugin="counterup">Kr {{ calculateTotalAmountFromChallenges() }}</h2></div> + <h2 class="" data-plugin="counterup">Kr {{ calculateTotalAmountFromChallenges() }}</h2> + </div> </div> <div class="col-sm-3"> - <div class="card-box tilebox-one"><i class="icon-paypal float-right text-muted"></i> + <div class="card-box tilebox-one"> + <i class="icon-paypal float-right text-muted"></i> <h6 class="text-muted text-uppercase mt-0">Du har spart</h6> - <h2 class="">Kr <span data-plugin="counterup">{{ savedSoFar }}</span></h2></div> + <h2 class=""> + Kr <span data-plugin="counterup">{{ savedSoFar }}</span> + </h2> + </div> </div> <div class="col-sm-3"> - <div class="card-box tilebox-one"><i class="icon-rocket float-right text-muted"></i> - <h6 class="text-muted text-uppercase mt-0">Sparemålet ender </h6> - <h2 class="" data-plugin="counterup">{{ formatDate(selectedGoal.targetDate) }}</h2></div> + <div class="card-box tilebox-one"> + <i class="icon-rocket float-right text-muted"></i> + <h6 class="text-muted text-uppercase mt-0">Sparemålet ender</h6> + <h2 class="" data-plugin="counterup">{{ formatDate(selectedGoal.targetDate) }}</h2> + </div> </div> </div> - <br> + <br /> <Line ref="chart" :data="chartData" :options="chartOptions" /> </div> </div> @@ -490,42 +574,42 @@ export default { font-weight: 600; font-size: 45px; margin-top: 20px; - margin-bottom:40px; + margin-bottom: 40px; padding-bottom: 10px; color: white; border-radius: 20px; - background-color: #003A58; + background-color: #003a58; } .timeline { position: relative; - padding:4px 0 0 0; - margin-top:22px; + padding: 4px 0 0 0; + margin-top: 22px; list-style: none; margin-bottom: 300px; } -.timeline>li:nth-child(even) { +.timeline > li:nth-child(even) { position: relative; margin-bottom: 50px; height: 100px; - right:-100px; + right: -100px; } -.timeline>li:nth-child(odd) { +.timeline > li:nth-child(odd) { position: relative; margin-bottom: 50px; height: 100px; - left:-100px; + left: -100px; } -.timeline>li:before, -.timeline>li:after { - content: " "; +.timeline > li:before, +.timeline > li:after { + content: ' '; display: table; } -.timeline>li:after { +.timeline > li:after { clear: both; min-height: 170px; } @@ -536,124 +620,124 @@ export default { width: 31%; padding: 0 20px 20px 30px; text-align: right; - background-color: #003A58; + background-color: #003a58; border-radius: 1em; margin-left: 100px; color: white; } -.timeline>li .timeline-panel:before { +.timeline > li .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } -.timeline>li .timeline-panel:after { +.timeline > li .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } -.timeline>li .timeline-image { +.timeline > li .timeline-image { z-index: 100; position: absolute; left: 50%; - border: 7px solid #003A58; + border: 7px solid #003a58; border-radius: 100%; box-shadow: 0 0 5px #00e1ff; width: 100px; height: 100px; margin-left: -50px; - cursor:pointer; + cursor: pointer; } -.timeline>li .timeline-image h4 { +.timeline > li .timeline-image h4 { margin-top: 12px; font-size: 10px; line-height: 14px; } -.timeline>li.timeline-inverted>.timeline-panel { +.timeline > li.timeline-inverted > .timeline-panel { float: right; padding: 0 30px 20px 20px; text-align: left; margin-right: 100px; } -.timeline>li.timeline-inverted>.timeline-panel:before { +.timeline > li.timeline-inverted > .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } -.timeline>li.timeline-inverted>.timeline-panel:after { +.timeline > li.timeline-inverted > .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } -.timeline>li:last-child { +.timeline > li:last-child { margin-bottom: 0; } .timeline .timeline-heading h4 { - margin-top:0px; + margin-top: 0px; margin-bottom: 4px; - padding:0; + padding: 0; color: white; - font-weight:600; + font-weight: 600; } .timeline .timeline-heading h4.subheading { - margin:0; - padding:0; + margin: 0; + padding: 0; text-transform: none; - font-size:18px; - color:white; + font-size: 18px; + color: white; } -.timeline .timeline-body>p, -.timeline .timeline-body>ul { +.timeline .timeline-body > p, +.timeline .timeline-body > ul { margin-bottom: 0; - color:white; + color: white; } /*Style for even div.line*/ -.timeline>li:nth-child(odd) .line:before { - content: ""; +.timeline > li:nth-child(odd) .line:before { + content: ''; position: absolute; top: 30px; bottom: 0; left: 700px; width: 30px; - height:320px; + height: 320px; background-color: grey; -ms-transform: rotate(-44deg); /* IE 9 */ -webkit-transform: rotate(-44deg); /* Safari */ transform: rotate(-44deg); border: dotted white 3px; /**box-shadow: 0 0 5px #00FF00;**/ - display:none; + display: none; } /*Style for odd div.line*/ -.timeline>li:nth-child(even) .line:before { - content: ""; +.timeline > li:nth-child(even) .line:before { + content: ''; position: absolute; top: 30px; bottom: 0; left: 480px; width: 30px; - height:320px; + height: 320px; background-color: grey; -ms-transform: rotate(44deg); /* IE 9 */ -webkit-transform: rotate(44deg); /* Safari */ transform: rotate(44deg); border: dotted white 3px; /*box-shadow: 0 0 5px #00FF00;*/ - display:none; + display: none; } /* Medium Devices, .visible-md-* */ @media (min-width: 992px) and (max-width: 1199px) { @@ -671,11 +755,11 @@ export default { min-height: 0; left: 0; } - .timeline>li:nth-child(even) .timeline-image { + .timeline > li:nth-child(even) .timeline-image { left: 0; margin-left: 0; } - .timeline>li:nth-child(odd) .timeline-image { + .timeline > li:nth-child(odd) .timeline-image { left: 690px; margin-left: 0; } @@ -705,11 +789,11 @@ export default { min-height: 0; left: 0; } - .timeline>li:nth-child(even) .timeline-image { + .timeline > li:nth-child(even) .timeline-image { left: 0; margin-left: 0; } - .timeline>li:nth-child(odd) .timeline-image { + .timeline > li:nth-child(odd) .timeline-image { left: 520px; margin-left: 0; } @@ -739,20 +823,20 @@ export default { min-height: 0; left: 0; } - .timeline>li .timeline-image { + .timeline > li .timeline-image { position: static; width: 150px; height: 150px; - margin-bottom:0; + margin-bottom: 0; } - .timeline>li:nth-child(even) .timeline-image { + .timeline > li:nth-child(even) .timeline-image { left: 0; margin-left: 0; } - .timeline>li:nth-child(odd) .timeline-image { - float:right; + .timeline > li:nth-child(odd) .timeline-image { + float: right; left: 0; - margin-left:0; + margin-left: 0; } .timeline > li:nth-child(even) .timeline-panel { width: 100%; @@ -845,4 +929,4 @@ export default { align-items: center; text-align: center; } -</style> \ No newline at end of file +</style> diff --git a/src/components/Settings/SettingsAccount.vue b/src/components/Settings/SettingsAccount.vue index 68f2123af8a3d6ff930ac5efe9a97ba2d79748ed..417a008d07fdadc77e2f2101e23e0ce7db76fa0f 100644 --- a/src/components/Settings/SettingsAccount.vue +++ b/src/components/Settings/SettingsAccount.vue @@ -1,10 +1,10 @@ <script setup lang="ts"> -import { ref, onMounted } from 'vue'; -import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; -import { useUserInfoStore } from "@/stores/UserStore"; -import { UserService } from '@/api'; -import type { UserUpdateDTO } from '@/api'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import { ref, onMounted } from 'vue' +import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' +import { useUserInfoStore } from '@/stores/UserStore' +import { UserService } from '@/api' +import type { UserUpdateDTO } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import router from '@/router' const emailRef = ref('') @@ -28,14 +28,14 @@ const handleEmailInputEvent = (newValue: any) => { */ async function setupForm() { try { - let response = await UserService.getUser(); + let response = await UserService.getUser() if (response.email != null) { emailRef.value = response.email } - confirmationMsg.value = ''; - errorMsg.value = ''; + confirmationMsg.value = '' + errorMsg.value = '' } catch (err) { - errorMsg.value = handleUnknownError(err); + errorMsg.value = handleUnknownError(err) confirmationMsg.value = '' } } @@ -48,33 +48,33 @@ async function setupForm() { const handleSubmit = async () => { // Construct payload for updating user email const updateUserPayload: UserUpdateDTO = { - email: emailRef.value, - }; + email: emailRef.value + } try { // Send request to update user email UserService.update({ requestBody: updateUserPayload }) // Update user info in the store useUserInfoStore().setUserInfo({ - email: emailRef.value, + email: emailRef.value }) confirmationMsg.value = 'Email updated successfully!' - errorMsg.value = ''; + errorMsg.value = '' } catch (err) { - handleUnknownError(err); - errorMsg.value = "Error updating email, try again!"; + handleUnknownError(err) + errorMsg.value = 'Error updating email, try again!' confirmationMsg.value = '' } } const handleSubmit2 = async () => { try { - console.log("test") - UserService.deleteUser(); - console.log("test") - useUserInfoStore().clearUserInfo(); - await router.push("/login"); + console.log('test') + UserService.deleteUser() + console.log('test') + useUserInfoStore().clearUserInfo() + await router.push('/login') } catch (err) { - errorMsg2.value = handleUnknownError(err); + errorMsg2.value = handleUnknownError(err) } } onMounted(() => { @@ -84,45 +84,55 @@ onMounted(() => { <template> <div class="tab-pane active" id="account"> - <h6>KONTO</h6> - <hr> - <form @submit.prevent="handleSubmit"> - <div class="form-group"> - <BaseInput data-cy="email-input" :model-value="emailRef" - @input-change-event="handleEmailInputEvent" id="emailInput-change" - input-id="email-new" type="email" label="E-post" placeholder="Skriv inn din e-post" - invalid-message="Ugyldig e-post"/> - </div> - <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> - <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> - <br> - <button data-cy="change-email-btn" type="submit" class="btn btn-primary classyButton">Endre - Informasjon</button> - </form> - <form @submit.prevent="handleSubmit2" style="margin-top: 20px;"> - <div class="form-group"> - <label class="d-block text-danger">Slett Bruker</label> - <p class="text-muted font-size-sm">Obs: Når du først har slettet kontoen din, er det ingen vei tilbake.</p> - </div> - <p data-cy="delete-user-msg-error" class="text-danger">{{ errorMsg2 }}</p> - <button class="btn btn-danger" type="submit">Slett Bruker</button> - </form> + <h6>KONTO</h6> + <hr /> + <form @submit.prevent="handleSubmit"> + <div class="form-group"> + <BaseInput + data-cy="email-input" + :model-value="emailRef" + @input-change-event="handleEmailInputEvent" + id="emailInput-change" + input-id="email-new" + type="email" + label="E-post" + placeholder="Skriv inn din e-post" + invalid-message="Ugyldig e-post" + /> + </div> + <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> + <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> + <br /> + <button data-cy="change-email-btn" type="submit" class="btn btn-primary classyButton"> + Endre Informasjon + </button> + </form> + <form @submit.prevent="handleSubmit2" style="margin-top: 20px"> + <div class="form-group"> + <label class="d-block text-danger">Slett Bruker</label> + <p class="text-muted font-size-sm"> + Obs: Når du først har slettet kontoen din, er det ingen vei tilbake. + </p> + </div> + <p data-cy="delete-user-msg-error" class="text-danger">{{ errorMsg2 }}</p> + <button class="btn btn-danger" type="submit">Slett Bruker</button> + </form> </div> </template> <style scoped> - .classyButton { - background-color: #003A58; - border: #003A58; - } +.classyButton { + background-color: #003a58; + border: #003a58; +} - .classyButton:hover { - background-color: #003b58ec; - border: #003A58; - } +.classyButton:hover { + background-color: #003b58ec; + border: #003a58; +} - .classyButton:active { - background-color: #003b58d6; - border: #003A58; - } +.classyButton:active { + background-color: #003b58d6; + border: #003a58; +} </style> diff --git a/src/components/Settings/SettingsBank.vue b/src/components/Settings/SettingsBank.vue index ba2f237634f03bd85f77aeae8342d0a4971d9c77..34b7dcee30e74d0ac1e1a5464b3a20bb93590940 100644 --- a/src/components/Settings/SettingsBank.vue +++ b/src/components/Settings/SettingsBank.vue @@ -1,61 +1,84 @@ <template> - <div class="tab-pane active" id="billing"> - <h6>BANK</h6> - <hr> - <form @submit.prevent="handleSpendingSubmit" novalidate> - <div class="form-group"> - <BaseInput data-cy="spending-account-input" :model-value="spendingAccount" - @input-change-event="handleSpendingInputEvent" id="firstNameInputChange" input-id="first-name-new" - type="Number" label="Brukskonto" placeholder="Skriv inn din brukskonto" - invalid-message="Vennligst skriv inn din brukskonto" /> + <div class="tab-pane active" id="billing"> + <h6>BANK</h6> + <hr /> + <form @submit.prevent="handleSpendingSubmit" novalidate> + <div class="form-group"> + <BaseInput + data-cy="spending-account-input" + :model-value="spendingAccount" + @input-change-event="handleSpendingInputEvent" + id="firstNameInputChange" + input-id="first-name-new" + type="Number" + label="Brukskonto" + placeholder="Skriv inn din brukskonto" + invalid-message="Vennligst skriv inn din brukskonto" + /> + </div> + <br /> + <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> + <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> + <button data-cy="update-spending-btn" type="submit" class="btn btn-primary classyButton"> + Oppdater brukskonto + </button> + </form> + <br /> + <form @submit.prevent="handleSavingSubmit"> + <div class="form-group"> + <BaseInput + data-cy="savings-account-input" + :model-value="savingsAccount" + @input-change-event="handleSavingInputEvent" + id="firstNameInputChange" + input-id="first-name-new" + type="Number" + label="Sparekonto" + placeholder="Skriv inn din sparekonto" + invalid-message="Vennligst skriv inn din sparekonto" + /> + </div> + <br /> + <button data-cy="update-savings-btn" type="submit" class="btn btn-primary classyButton"> + Oppdater sparekonto + </button> + </form> + <hr /> + <div class="form-group mb-0"> + <label class="d-block">Saldooversikt</label> + <div class="border border-gray-500 bg-gray-200 p-3 text-center font-size-sm"> + <div class="row"> + <div class="col-sm-6"> + <div class="card-box tilebox-one"> + <i class="icon-rocket float-right text-muted"></i> + <h6 class="text-muted text-uppercase mt-0">Brukskonto</h6> + <h2 class="">{{ spendingAccountBalance }} Kr</h2> </div> - <br> - <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> - <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> - <button data-cy="update-spending-btn" type="submit" class="btn btn-primary classyButton">Oppdater - brukskonto</button> - </form> - <br> - <form @submit.prevent="handleSavingSubmit"> - <div class="form-group"> - <BaseInput data-cy="savings-account-input" :model-value="savingsAccount" - @input-change-event="handleSavingInputEvent" id="firstNameInputChange" input-id="first-name-new" type="Number" - label="Sparekonto" placeholder="Skriv inn din sparekonto" - invalid-message="Vennligst skriv inn din sparekonto" /> - </div> - <br> - <button data-cy="update-savings-btn" type="submit" class="btn btn-primary classyButton">Oppdater - sparekonto</button> - </form> - <hr> - <div class="form-group mb-0"> - <label class="d-block">Saldooversikt</label> - <div class="border border-gray-500 bg-gray-200 p-3 text-center font-size-sm"> - <div class="row"> - <div class="col-sm-6"> - <div class="card-box tilebox-one"><i class="icon-rocket float-right text-muted"></i> - <h6 class="text-muted text-uppercase mt-0">Brukskonto</h6> - <h2 class="">{{spendingAccountBalance}} Kr</h2></div> - </div> - <div class="col-sm-6"> - <div class="card-box tilebox-one"><i class="icon-rocket float-right text-muted"></i> - <h6 class="text-muted text-uppercase mt-0">Sparekonto</h6> - <h2 class="">{{savingsAccountBalance}} Kr</h2></div> - </div> - </div> + </div> + <div class="col-sm-6"> + <div class="card-box tilebox-one"> + <i class="icon-rocket float-right text-muted"></i> + <h6 class="text-muted text-uppercase mt-0">Sparekonto</h6> + <h2 class="">{{ savingsAccountBalance }} Kr</h2> </div> + </div> </div> + </div> </div> + </div> </template> - <script setup lang="ts"> -import { ref, onMounted } from 'vue'; -import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; -import { AccountControllerService, type BalanceDTO, BankProfileControllerService, type UserUpdateDTO } from '@/api' -import { UserService } from '@/api'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' - +import { ref, onMounted } from 'vue' +import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' +import { + AccountControllerService, + type BalanceDTO, + BankProfileControllerService, + type UserUpdateDTO +} from '@/api' +import { UserService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' const spendingAccount = ref() const savingsAccount = ref() @@ -87,17 +110,17 @@ const handleSavingInputEvent = (newValue: any) => { * Handles errors by calling the handleUnknownError function. */ const handleSavingSubmit = async () => { - try { - const updateUserPayload: UserUpdateDTO = { - savingsAccountBBAN: savingsAccount.value - }; - UserService.update({ requestBody: updateUserPayload }) - errorMsg.value = '' - confirmationMsg.value = 'Kontonummer ble oppdatert' - } catch (err) { - errorMsg.value = handleUnknownError(err); - confirmationMsg.value = '' + try { + const updateUserPayload: UserUpdateDTO = { + savingsAccountBBAN: savingsAccount.value } + UserService.update({ requestBody: updateUserPayload }) + errorMsg.value = '' + confirmationMsg.value = 'Kontonummer ble oppdatert' + } catch (err) { + errorMsg.value = handleUnknownError(err) + confirmationMsg.value = '' + } } /** @@ -105,17 +128,17 @@ const handleSavingSubmit = async () => { * Handles errors by calling the handleUnknownError function. */ const handleSpendingSubmit = async () => { - try { - const updateUserPayload: UserUpdateDTO = { - checkingAccountBBAN: spendingAccount.value - }; - UserService.update({ requestBody: updateUserPayload }) - errorMsg.value = '' - confirmationMsg.value = 'Kontonummer ble oppdatert' - } catch (err) { - errorMsg.value = handleUnknownError(err); - confirmationMsg.value = '' + try { + const updateUserPayload: UserUpdateDTO = { + checkingAccountBBAN: spendingAccount.value } + UserService.update({ requestBody: updateUserPayload }) + errorMsg.value = '' + confirmationMsg.value = 'Kontonummer ble oppdatert' + } catch (err) { + errorMsg.value = handleUnknownError(err) + confirmationMsg.value = '' + } } onMounted(getAccountInfo) @@ -126,17 +149,17 @@ onMounted(getAccountInfo) */ async function getAccountInfo() { try { - let response = await UserService.getUser(); - savingsAccount.value = response.savingsAccountBBAN; - let bban1: any = response.savingsAccountBBAN; - AccountControllerService.getAccountsByBban({bban: bban1}).then((balance: BalanceDTO) => { - savingsAccountBalance.value = balance.balance; + let response = await UserService.getUser() + savingsAccount.value = response.savingsAccountBBAN + let bban1: any = response.savingsAccountBBAN + AccountControllerService.getAccountsByBban({ bban: bban1 }).then((balance: BalanceDTO) => { + savingsAccountBalance.value = balance.balance }) - spendingAccount.value = response.checkingAccountBBAN; - let bban2: any = response.checkingAccountBBAN; - AccountControllerService.getAccountsByBban({bban: bban2}).then((balance: BalanceDTO) => { - spendingAccountBalance.value = balance.balance; + spendingAccount.value = response.checkingAccountBBAN + let bban2: any = response.checkingAccountBBAN + AccountControllerService.getAccountsByBban({ bban: bban2 }).then((balance: BalanceDTO) => { + spendingAccountBalance.value = balance.balance }) } catch (err) { handleUnknownError(err) @@ -145,18 +168,18 @@ async function getAccountInfo() { </script> <style scoped> - .classyButton { - background-color: #003A58; - border: #003A58; - } +.classyButton { + background-color: #003a58; + border: #003a58; +} - .classyButton:hover { - background-color: #003b58ec; - border: #003A58; - } +.classyButton:hover { + background-color: #003b58ec; + border: #003a58; +} - .classyButton:active { - background-color: #003b58d6; - border: #003A58; - } -</style> \ No newline at end of file +.classyButton:active { + background-color: #003b58d6; + border: #003a58; +} +</style> diff --git a/src/components/Settings/SettingsProfile.vue b/src/components/Settings/SettingsProfile.vue index 8080512c78edb0b955407fa50a79f25e988821c7..e8da1fde2ebec4684a7e2f7ae270c007092ca1d1 100644 --- a/src/components/Settings/SettingsProfile.vue +++ b/src/components/Settings/SettingsProfile.vue @@ -1,12 +1,12 @@ <script setup lang="ts"> -import { ref, onMounted } from 'vue'; -import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; -import { useUserInfoStore } from "@/stores/UserStore"; -import { UserService, ImageService, ItemService } from '@/api'; -import type { UserUpdateDTO } from '@/api'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import { ref, onMounted } from 'vue' +import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' +import { useUserInfoStore } from '@/stores/UserStore' +import { UserService, ImageService, ItemService } from '@/api' +import type { UserUpdateDTO } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -let apiUrl = import.meta.env.VITE_APP_API_URL; +let apiUrl = import.meta.env.VITE_APP_API_URL const firstNameRef = ref() const surnameRef = ref('') @@ -16,14 +16,14 @@ const formRef = ref() let samePasswords = ref(true) let banners = ref([] as any) -let hasBanners = ref(false); -let selectedBannerId = ref(0); +let hasBanners = ref(false) +let selectedBannerId = ref(0) const selectedBanner = ref() -const errorMsg = ref(''); -const successMsg = ref(''); +const errorMsg = ref('') +const successMsg = ref('') -const iconSrc = ref('../src/assets/userprofile.png'); -const fileInputRef = ref(); +const iconSrc = ref('../src/assets/userprofile.png') +const fileInputRef = ref() /** * Handles the event when the first name input changes. @@ -49,8 +49,8 @@ const handleSurnameInputEvent = (newValue: any) => { * Triggers the file upload dialog. */ const triggerFileUpload = () => { - fileInputRef.value.click(); -}; + fileInputRef.value.click() +} /** * Handles the file change event. @@ -59,11 +59,11 @@ const triggerFileUpload = () => { * @param {any} event - The file change event. */ const handleFileChange = (event: any) => { - const file = event.target.files[0]; + const file = event.target.files[0] if (file) { - uploadImage(file); + uploadImage(file) } -}; +} /** * Uploads the image file to the server. @@ -76,39 +76,39 @@ const uploadImage = async (file: any) => { const formData = { file: new Blob([file]) } try { - const response = await ImageService.uploadImage({ formData }); - iconSrc.value = apiUrl + "/api/images/" + response; + const response = await ImageService.uploadImage({ formData }) + iconSrc.value = apiUrl + '/api/images/' + response const updateUserPayload: UserUpdateDTO = { - profileImage: response, - }; + profileImage: response + } UserService.update({ requestBody: updateUserPayload }) useUserInfoStore().setUserInfo({ - profileImage: response, + profileImage: response }) } catch (error) { - handleUnknownError(error); - console.error('Failed to upload image:', error); + handleUnknownError(error) + console.error('Failed to upload image:', error) } -}; +} const getInventory = async () => { try { - const response = await ItemService.getInventory(); + const response = await ItemService.getInventory() console.log(response) - banners.value = response; - hasBanners.value = response.length > 0; + banners.value = response + hasBanners.value = response.length > 0 } catch (error) { - handleUnknownError(error); - console.error('Failed to get inventory:', error); + handleUnknownError(error) + console.error('Failed to get inventory:', error) } -}; +} const selectItem = async (bannerId: any) => { try { const bannerImagePayload: UserUpdateDTO = { - bannerImage: bannerId, - }; + bannerImage: bannerId + } await UserService.update({ requestBody: bannerImagePayload }) setupForm() } catch (error) { @@ -123,27 +123,26 @@ const selectItem = async (bannerId: any) => { */ async function setupForm() { try { - const response = await UserService.getUser(); + const response = await UserService.getUser() console.log(response.firstName) - firstNameRef.value = response.firstName; + firstNameRef.value = response.firstName if (response.lastName != null) { - surnameRef.value = response.lastName; + surnameRef.value = response.lastName } if (response.profileImage != null) { - iconSrc.value = apiUrl + "/api/images/" + response.profileImage; + iconSrc.value = apiUrl + '/api/images/' + response.profileImage } else { - iconSrc.value = "../src/assets/userprofile.png"; + iconSrc.value = '../src/assets/userprofile.png' } if (response.bannerImage != null) { - selectedBanner.value = response.bannerImage; + selectedBanner.value = response.bannerImage } } catch (err) { - handleUnknownError(err); + handleUnknownError(err) console.error(err) } } - /** * Handles form submission. * Updates user profile information with the provided first name and surname. @@ -151,19 +150,19 @@ async function setupForm() { const handleSubmit = async () => { const updateUserPayload: UserUpdateDTO = { firstName: firstNameRef.value, - lastName: surnameRef.value, - }; + lastName: surnameRef.value + } try { await UserService.update({ requestBody: updateUserPayload }) useUserInfoStore().setUserInfo({ firstname: firstNameRef.value, - lastname: surnameRef.value, + lastname: surnameRef.value }) - errorMsg.value = ''; - successMsg.value = 'Profilen ble oppdatert!'; + errorMsg.value = '' + successMsg.value = 'Profilen ble oppdatert!' } catch (err) { - errorMsg.value = handleUnknownError(err); - successMsg.value = ''; + errorMsg.value = handleUnknownError(err) + successMsg.value = '' console.error(err) } } @@ -171,69 +170,100 @@ onMounted(() => { setupForm() getInventory() }) - </script> - <template> <div class="tab-pane active" id="profile"> <h6>DIN PROFILINFORMASJON</h6> - <hr> + <hr /> <form @submit.prevent="handleSubmit" novalidate class="d-flex infoHolder"> <div> <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: 200px; height: 200px;"> + <input + type="file" + ref="fileInputRef" + @change="handleFileChange" + accept=".jpg, .jpeg, .png" + style="display: none" + /> + <img :src="iconSrc" alt="Brukeravatar" style="width: 200px; height: 200px" /> <div class="mt-2"> - <button type="button" class="btn btn-primary classyButton" @click="triggerFileUpload"><img - src="../../assets/icons/download.svg"> Last opp bilde</button> + <button type="button" class="btn btn-primary classyButton" @click="triggerFileUpload"> + <img src="../../assets/icons/download.svg" /> Last opp bilde + </button> </div> </div> </div> <div class="mx-5"> <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" class="inputDynamic" /> + <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" + class="inputDynamic" + /> </div> - <br> + <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" - class="inputDynamic"/> + <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" + class="inputDynamic" + /> </div> - <br> + <br /> <div class="d-flex"> - <p class="text-danger"> {{ errorMsg }}</p> - <p class="text-success"> {{ successMsg }}</p> + <p class="text-danger">{{ errorMsg }}</p> + <p class="text-success">{{ successMsg }}</p> </div> - <button data-cy="profile-submit-btn" type="submit" class="btn btn-primary classyButton">Oppdater profil</button> + <button data-cy="profile-submit-btn" type="submit" class="btn btn-primary classyButton"> + Oppdater profil + </button> </div> </form> - <hr> + <hr /> <div> <h6>Banners</h6> <div v-if="hasBanners" class="scrolling-wrapper-badges row flex-row flex-wrap mt-2 pb-2 pt-2"> - <div v-for="banner in banners" :key="banner.id" + <div + v-for="banner in banners" + :key="banner.id" class="card text-center banner justify-content-center d-flex align-items-center" - @click="selectItem(banner.id)" :class="{ 'selected-banner': banner.id === selectedBannerId }" - data-bs-toggle="tooltip" data-bs-placement="top" data-bs-custom-class="custom-tooltip" - :data-bs-title="banner.criteria"> - <img :src="apiUrl + `/api/images/${banner.imageId}`" class="card-img-top" - :class="{ 'selected-banner': banner.id === selectedBanner }" alt="Banner" - style="width: 200px; height: 100px" @click="selectItem(banner.imageId)" /> + @click="selectItem(banner.id)" + :class="{ 'selected-banner': banner.id === selectedBannerId }" + data-bs-toggle="tooltip" + data-bs-placement="top" + data-bs-custom-class="custom-tooltip" + :data-bs-title="banner.criteria" + > + <img + :src="apiUrl + `/api/images/${banner.imageId}`" + class="card-img-top" + :class="{ 'selected-banner': banner.id === selectedBanner }" + alt="Banner" + style="width: 200px; height: 100px" + @click="selectItem(banner.imageId)" + /> </div> </div> - <div v-else> - Ingen banners - </div> + <div v-else>Ingen banners</div> </div> </div> </template> - <style scoped> #icon { width: 90px; @@ -251,18 +281,18 @@ onMounted(() => { } .classyButton { - background-color: #003A58; - border: #003A58; + background-color: #003a58; + border: #003a58; } .classyButton:hover { background-color: #003b58ec; - border: #003A58; + border: #003a58; } .classyButton:active { background-color: #003b58d6; - border: #003A58; + border: #003a58; } .selected-banner { @@ -297,4 +327,4 @@ onMounted(() => { width: 200px; } } -</style> \ No newline at end of file +</style> diff --git a/src/components/Settings/SettingsSecurity.vue b/src/components/Settings/SettingsSecurity.vue index e71ee3db5e0d57084b2a09ef8e6a5a5648085ef8..59b7fbccf7bf6682275364ccd2cc776905f7d496 100644 --- a/src/components/Settings/SettingsSecurity.vue +++ b/src/components/Settings/SettingsSecurity.vue @@ -1,48 +1,68 @@ <template> - <div class="tab-pane active" id="security"> - <h6>SIKKERHET</h6> - <hr> - <form @submit.prevent="handleSubmit" > - <div class="form-group"> - <h5 class="d-block">Endre passord</h5> - <BaseInput data-cy="old-password-input" :model-value="oldPasswordRef" - @input-change-event="handleOldPasswordInputEvent" - id="passwordInput-change" input-id="password-old" type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Gammelt passord" placeholder="Skriv inn passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" /> + <div class="tab-pane active" id="security"> + <h6>SIKKERHET</h6> + <hr /> + <form @submit.prevent="handleSubmit"> + <div class="form-group"> + <h5 class="d-block">Endre passord</h5> + <BaseInput + data-cy="old-password-input" + :model-value="oldPasswordRef" + @input-change-event="handleOldPasswordInputEvent" + id="passwordInput-change" + input-id="password-old" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Gammelt passord" + placeholder="Skriv inn passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" + /> - <BaseInput data-cy="new-password-input" :model-value="newPasswordRef" - @input-change-event="handleNewPasswordInputEvent" - id="passwordInput-change" input-id="password-new" type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Nytt passord" placeholder="Skriv inn passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" /> + <BaseInput + data-cy="new-password-input" + :model-value="newPasswordRef" + @input-change-event="handleNewPasswordInputEvent" + id="passwordInput-change" + input-id="password-new" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Nytt passord" + placeholder="Skriv inn passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" + /> - <BaseInput data-cy="confirm-password-input" :model-value="confirmPasswordRef" - @input-change-event="handleConfirmPasswordInputEvent" - id="passwordInput-change" input-id="password-confirm" type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Bekreft nytt passord" placeholder="Skriv inn passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" /> - </div> - <p class="text-danger" data-cy="error">{{ errorMsg }}</p> - <button data-cy="update-password-btn" type="submit" class="btn btn-primary classyButton">Oppdater - passord</button> - </form> - <hr> - </div> + <BaseInput + data-cy="confirm-password-input" + :model-value="confirmPasswordRef" + @input-change-event="handleConfirmPasswordInputEvent" + id="passwordInput-change" + input-id="password-confirm" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Bekreft nytt passord" + placeholder="Skriv inn passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde en stor bokstav, en liten bokstav og et tall" + /> + </div> + <p class="text-danger" data-cy="error">{{ errorMsg }}</p> + <button data-cy="update-password-btn" type="submit" class="btn btn-primary classyButton"> + Oppdater passord + </button> + </form> + <hr /> + </div> </template> - <script setup lang="ts"> import { ref } from 'vue' import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' import { type PasswordUpdateDTO, UserService } from '@/api' -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; - -const oldPasswordRef = ref(''); -const newPasswordRef = ref(''); -const confirmPasswordRef = ref(''); -let errorMsg = ref(''); +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' +const oldPasswordRef = ref('') +const newPasswordRef = ref('') +const confirmPasswordRef = ref('') +let errorMsg = ref('') /** * Handles the event when the old password input changes. @@ -51,7 +71,7 @@ let errorMsg = ref(''); * @param {any} newValue - The new value of the old password input. */ const handleOldPasswordInputEvent = (newValue: any) => { - oldPasswordRef.value = newValue + oldPasswordRef.value = newValue } /** @@ -61,7 +81,7 @@ const handleOldPasswordInputEvent = (newValue: any) => { * @param {any} newValue - The new value of the new password input. */ const handleNewPasswordInputEvent = (newValue: any) => { - newPasswordRef.value = newValue + newPasswordRef.value = newValue } /** @@ -71,7 +91,7 @@ const handleNewPasswordInputEvent = (newValue: any) => { * @param {any} newValue - The new value of the confirm-password input. */ const handleConfirmPasswordInputEvent = (newValue: any) => { - confirmPasswordRef.value = newValue + confirmPasswordRef.value = newValue } /** @@ -79,41 +99,41 @@ const handleConfirmPasswordInputEvent = (newValue: any) => { * Validates if the new password matches the confirm-password. */ const handleSubmit = async () => { - if (newPasswordRef.value.length === 0 || newPasswordRef.value !== confirmPasswordRef.value) { - errorMsg.value = "Passordene er ikke identiske"; - return - } - errorMsg.value = ''; - try { - const updateUserPayload: PasswordUpdateDTO = { - oldPassword: oldPasswordRef.value, - newPassword: newPasswordRef.value, - }; - await UserService.updatePassword({ requestBody: updateUserPayload }); - errorMsg.value = ''; - } catch (err: any) { - errorMsg.value = err.body.message; + if (newPasswordRef.value.length === 0 || newPasswordRef.value !== confirmPasswordRef.value) { + errorMsg.value = 'Passordene er ikke identiske' + return + } + errorMsg.value = '' + try { + const updateUserPayload: PasswordUpdateDTO = { + oldPassword: oldPasswordRef.value, + newPassword: newPasswordRef.value } + await UserService.updatePassword({ requestBody: updateUserPayload }) + errorMsg.value = '' + } catch (err: any) { + errorMsg.value = err.body.message + } } </script> <style scoped> - .classyButton { - background-color: #003A58; - border: #003A58; - } +.classyButton { + background-color: #003a58; + border: #003a58; +} - .classyButton:hover { - background-color: #003b58ec; - border: #003A58; - } +.classyButton:hover { + background-color: #003b58ec; + border: #003a58; +} - .classyButton:active { - background-color: #003b58d6; - border: #003A58; - } +.classyButton:active { + background-color: #003b58d6; + border: #003a58; +} - #passwordInput-change { - margin-bottom: 15px; - } -</style> \ No newline at end of file +#passwordInput-change { + margin-bottom: 15px; +} +</style> diff --git a/src/components/Shop/ItemShop.vue b/src/components/Shop/ItemShop.vue index 60f544d0c4897cdc076e852ecc38f2a5a629b36b..38a61462b55cd927acc16ebdf6a3c33fee6ce93b 100644 --- a/src/components/Shop/ItemShop.vue +++ b/src/components/Shop/ItemShop.vue @@ -4,7 +4,9 @@ <div id="dropdownContainer"> <h1 class="box">Butikk</h1> <div> - <p class="mb-1 h2" data-cy="points">{{ points }}<img src="@/assets/items/pigcoin.png" style="width: 4rem" /></p> + <p class="mb-1 h2" data-cy="points"> + {{ points }}<img src="@/assets/items/pigcoin.png" style="width: 4rem" /> + </p> </div> </div> <div class="container d-flex justify-content-center"> @@ -12,24 +14,48 @@ <div class="col-md-12"> <h1>Stash</h1> <div class="category row mb-2 m-2"> - <div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none"> - <img src="../../assets/items/adfree.png" class="card-img-top" alt="..." - style="width: 100px; height: 100px;" /> + <div + class="card text-center justify-content-center align-items-center" + style="width: 8rem; border: none" + > + <img + src="../../assets/items/adfree.png" + class="card-img-top" + alt="..." + style="width: 100px; height: 100px" + /> <div class="card-body"> <h5 class="card-title">Adfree</h5> - <button type="button" class="btn btn-primary" id="buttonStyle" data-toggle="modal" - data-target="#adfreeModal"> + <button + type="button" + class="btn btn-primary" + id="buttonStyle" + data-toggle="modal" + data-target="#adfreeModal" + > +35kr </button> </div> </div> - <div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none"> - <img src="../../assets/items/piggybank.webp" class="card-img-top" alt="..." - style="width: 100px; height: 100px;" /> + <div + class="card text-center justify-content-center align-items-center" + style="width: 8rem; border: none" + > + <img + src="../../assets/items/piggybank.webp" + class="card-img-top" + alt="..." + style="width: 100px; height: 100px" + /> <div class="card-body"> <h5 class="card-title">Premium</h5> - <button type="button" class="btn btn-primary" id="buttonStyle" data-toggle="modal" - data-target="#premiumModal"> + <button + type="button" + class="btn btn-primary" + id="buttonStyle" + data-toggle="modal" + data-target="#premiumModal" + > +50kr </button> </div> @@ -39,16 +65,30 @@ <div class="col-md-12"> <h1>Items</h1> <div class="category row mb-2 m-2"> - <div v-for="product in products" :key="product.id" + <div + v-for="product in products" + :key="product.id" class="card text-center d-flex justify-content-center align-items-center" - style="width: 16rem; border: none"> - <img :src="apiUrl + `/api/images/${product.imageId}`" style="width: 200px; height: 100px;" - class="card-img-top" alt="..." /> + style="width: 16rem; border: none" + > + <img + :src="apiUrl + `/api/images/${product.imageId}`" + style="width: 200px; height: 100px" + class="card-img-top" + alt="..." + /> <div class="card-body"> <h5 class="card-title">{{ product.itemName }}</h5> - <h6>{{ product.price }}<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6> - <ShopButton v-if="!product.alreadyBought" button-text="Buy item" :disabled="product.price > points" - @click="buyItem(product.id)" /> + <h6> + {{ product.price + }}<img src="../../assets/items/pigcoin.png" style="width: 2rem" /> + </h6> + <ShopButton + v-if="!product.alreadyBought" + button-text="Buy item" + :disabled="product.price > points" + @click="buyItem(product.id)" + /> <p v-else>Owned</p> </div> </div> @@ -57,34 +97,64 @@ <div class="col-md-12"> <h1>Cool items</h1> <div class="category row mb-2 m-2"> - <div class="card text-center d-flex justify-content-center align-items-center" - style="width: 8rem; border: none"> - <img src="../../assets/items/coffee.jpg" class="card-img-top" alt="..." - style="width: 100px; height: 100px;"> + <div + class="card text-center d-flex justify-content-center align-items-center" + style="width: 8rem; border: none" + > + <img + src="../../assets/items/coffee.jpg" + class="card-img-top" + alt="..." + style="width: 100px; height: 100px" + /> <div class="card-body"> <h5 class="card-title">Free Coffee</h5> - <h6>500<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> - <ShopButton button-text="Buy item" :disabled="500 > points" @click="buySomething()" /> + <h6>500<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6> + <ShopButton + button-text="Buy item" + :disabled="500 > points" + @click="buySomething()" + /> </div> </div> - <div class="card text-center d-flex justify-content-center align-items-center" - style="width: 8rem; border: none"> - <img src="../../assets/items/viaplay.jpg" class="card-img-top" alt="..." - style="width: 100px; height: 100px;"> + <div + class="card text-center d-flex justify-content-center align-items-center" + style="width: 8rem; border: none" + > + <img + src="../../assets/items/viaplay.jpg" + class="card-img-top" + alt="..." + style="width: 100px; height: 100px" + /> <div class="card-body"> <h5 class="card-title">1 Month</h5> - <h6>10 000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> - <ShopButton button-text="Buy item" :disabled="10000 > points" @click="buySomething()" /> + <h6>10 000<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6> + <ShopButton + button-text="Buy item" + :disabled="10000 > points" + @click="buySomething()" + /> </div> </div> - <div class="card text-center d-flex justify-content-center align-items-center" - style="width: 8rem; border: none"> - <img src="../../assets/items/pirbad.png" class="card-img-top" alt="..." - style="width: 100px; height: 100px;"> + <div + class="card text-center d-flex justify-content-center align-items-center" + style="width: 8rem; border: none" + > + <img + src="../../assets/items/pirbad.png" + class="card-img-top" + alt="..." + style="width: 100px; height: 100px" + /> <div class="card-body"> <h5 class="card-title">-10% rabatt</h5> - <h6>1000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> - <ShopButton button-text="Buy item" :disabled="1000 > points" @click="buySomething()" /> + <h6>1000<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6> + <ShopButton + button-text="Buy item" + :disabled="1000 > points" + @click="buySomething()" + /> </div> </div> </div> @@ -92,8 +162,14 @@ </div> </div> - <div class="modal fade" id="premiumModal" tabindex="-1" role="dialog" aria-labelledby="premiumModalLabel" - aria-hidden="true"> + <div + class="modal fade" + id="premiumModal" + tabindex="-1" + role="dialog" + aria-labelledby="premiumModalLabel" + aria-hidden="true" + > <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> @@ -119,8 +195,14 @@ </div> <!-- Modal for Ad-free Package --> - <div class="modal fade" id="adfreeModal" tabindex="-1" role="dialog" aria-labelledby="adfreeModalLabel" - aria-hidden="true"> + <div + class="modal fade" + id="adfreeModal" + tabindex="-1" + role="dialog" + aria-labelledby="adfreeModalLabel" + aria-hidden="true" + > <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> @@ -148,27 +230,27 @@ </template> <script setup lang="ts"> -import ShopButton from '@/components/Shop/ShopButton.vue'; -import { ref, onMounted } from 'vue'; -import { UserService } from '@/api'; -import { useUserInfoStore } from '@/stores/UserStore'; -import { ItemService } from '@/api'; -import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +import ShopButton from '@/components/Shop/ShopButton.vue' +import { ref, onMounted } from 'vue' +import { UserService } from '@/api' +import { useUserInfoStore } from '@/stores/UserStore' +import { ItemService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -let apiUrl = import.meta.env.VITE_APP_API_URL; -const products = ref([] as any); -const points = ref(); +let apiUrl = import.meta.env.VITE_APP_API_URL +const products = ref([] as any) +const points = ref() /** * Retrieves the store's products and updates the products list. */ const getStore = async () => { try { - const response = await ItemService.getStore(); - products.value = response; + const response = await ItemService.getStore() + products.value = response } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } @@ -177,11 +259,11 @@ const getStore = async () => { */ const getPoints = async () => { try { - const response = await UserService.getUser(); - points.value = response.point?.currentPoints; + const response = await UserService.getUser() + points.value = response.point?.currentPoints } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } @@ -193,12 +275,12 @@ const getPoints = async () => { */ const buyItem = async (itemId: number) => { try { - await ItemService.buyItem({ itemId: itemId }); - await getStore(); - await getPoints(); + await ItemService.buyItem({ itemId: itemId }) + await getStore() + await getPoints() } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } @@ -209,13 +291,13 @@ const buyItem = async (itemId: number) => { */ const buyPremium = async () => { try { - await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }); + await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }) useUserInfoStore().setUserInfo({ - subscriptionLevel: 'PREMIUM', + subscriptionLevel: 'PREMIUM' }) } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } @@ -226,13 +308,13 @@ const buyPremium = async () => { */ const buyNoAds = async () => { try { - await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }); + await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }) useUserInfoStore().setUserInfo({ - subscriptionLevel: 'NO_ADS', + subscriptionLevel: 'NO_ADS' }) } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } @@ -243,12 +325,12 @@ const buyNoAds = async () => { * @returns A randomly generated code. */ function generateRandomCode(length = 8) { - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); + result += characters.charAt(Math.floor(Math.random() * characters.length)) } - return result; + return result } /** @@ -257,17 +339,17 @@ function generateRandomCode(length = 8) { */ const buySomething = async () => { try { - const randomCode = generateRandomCode(); - alert(`Thank you for your purchase! Your code is: ${randomCode}`); + const randomCode = generateRandomCode() + alert(`Thank you for your purchase! Your code is: ${randomCode}`) } catch (error) { - handleUnknownError(error); - console.log(error); + handleUnknownError(error) + console.log(error) } } onMounted(() => { - getStore(); - getPoints(); + getStore() + getPoints() }) </script> @@ -310,5 +392,6 @@ onMounted(() => { flex-direction: column; } -#background {} -</style> \ No newline at end of file +#background { +} +</style> diff --git a/src/components/Shop/ShopButton.vue b/src/components/Shop/ShopButton.vue index a7bf5fcf9e5bf96a2a2cd27d9f56a477e2c082be..efe163409fbc56ea11ac654cad3a46f6825e1203 100644 --- a/src/components/Shop/ShopButton.vue +++ b/src/components/Shop/ShopButton.vue @@ -11,14 +11,14 @@ </template> <script setup lang="ts"> -import { defineProps, defineEmits } from 'vue'; +import { defineProps, defineEmits } from 'vue' const props = defineProps({ buttonText: String, - disabled: Boolean, -}); + disabled: Boolean +}) -const emit = defineEmits(['click']); +const emit = defineEmits(['click']) /** * Handles the click event for a button component. @@ -27,9 +27,9 @@ const emit = defineEmits(['click']); const handleClick = () => { if (!props.disabled) { - emit('click'); + emit('click') } -}; +} </script> <style scoped> diff --git a/src/components/Shop/__tests__/ShopButton.spec.ts b/src/components/Shop/__tests__/ShopButton.spec.ts index bc9cbea5ba146921c2e480d56e8257e9c1e021b0..ad38a5ba2938e56b4a55c8f650c54f7d42022f61 100644 --- a/src/components/Shop/__tests__/ShopButton.spec.ts +++ b/src/components/Shop/__tests__/ShopButton.spec.ts @@ -12,8 +12,8 @@ describe('ImageButtonComponent', () => { global: { stubs: { // This stubs out all <router-link> and <router-view> components used in the app. - 'RouterLink': true, - 'RouterView': true + RouterLink: true, + RouterView: true } } }) diff --git a/src/components/SignUp/SignUp.vue b/src/components/SignUp/SignUp.vue index b0c7d67db691ba0be33be137210b6f580786e9c5..31353b6e513eacb77850472077d70df9dd7a5a10 100644 --- a/src/components/SignUp/SignUp.vue +++ b/src/components/SignUp/SignUp.vue @@ -37,4 +37,4 @@ import SignUpForm from '@/components/SignUp/SignUpForm.vue' margin-bottom: 40px; font-weight: 700; } -</style> \ No newline at end of file +</style> diff --git a/src/components/SignUp/SignUpForm.vue b/src/components/SignUp/SignUpForm.vue index ca6e633671e04ce97a8927792c8f9b238097854b..16f07dba7b11661af8c2d29bc466268793551440 100644 --- a/src/components/SignUp/SignUpForm.vue +++ b/src/components/SignUp/SignUpForm.vue @@ -8,8 +8,8 @@ import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import { useUserInfoStore } from '@/stores/UserStore' import LoginLink from '@/components/Login/LoginLink.vue' -const router = useRouter(); -const userStore = useUserInfoStore(); +const router = useRouter() +const userStore = useUserInfoStore() // Reactive variables for the form const firstNameRef = ref('') @@ -19,8 +19,8 @@ const passwordRef = ref('') const confirmPasswordRef = ref('') const formRef = ref() let samePasswords = ref(true) -let errorMsg = ref(''); -const isSubmitting = ref(false); +let errorMsg = ref('') +const isSubmitting = ref(false) /** * Handles the event when the first name input changes. @@ -79,36 +79,35 @@ const handleConfirmPasswordInputEvent = (newValue: any) => { * Redirects the user to the configuration page after registration. */ const handleSubmit = async () => { - if (isSubmitting.value) return; - isSubmitting.value = true; + if (isSubmitting.value) return + isSubmitting.value = true // Validates form and displays validation - samePasswords.value = (passwordRef.value === confirmPasswordRef.value) - formRef.value.classList.add("was-validated") + samePasswords.value = passwordRef.value === confirmPasswordRef.value + formRef.value.classList.add('was-validated') - const form = formRef.value; + const form = formRef.value if (form.checkValidity()) { if (samePasswords.value) { try { // Validates email - await AuthenticationService.validateEmail({email: emailRef.value}); + await AuthenticationService.validateEmail({ email: emailRef.value }) // Set userInfo details in user store userStore.setUserInfo({ firstname: firstNameRef.value, lastname: surnameRef.value, - email: emailRef.value, - }); + email: emailRef.value + }) userStore.setPassword(passwordRef.value) await router.push('/configuration') } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) console.log(error) } } } - isSubmitting.value = false; + isSubmitting.value = false } - </script> <template> @@ -119,66 +118,78 @@ const handleSubmit = async () => { <form ref="formRef" id="signUpForm" @submit.prevent="handleSubmit" novalidate> <div class="row"> <div class="col-sm"> - <BaseInput :model-value=firstNameRef - @input-change-event="handleFirstNameInputEvent" - id="firstNameInput" - input-id="first-name" - type="text" - pattern="^(?=.{4,16}$)[^\d]+$" - label="Fornavn" - placeholder="Skriv inn ditt fornavn" - invalid-message="Ugyldig fornavn, husk ingen tall, må være mellom 4 og 16 bokstaver"/> - <BaseInput :model-value="surnameRef" - @input-change-event="handleSurnameInputEvent" - id="surnameInput" - input-id="surname" - type="text" - pattern="^(?=.{4,16}$)[^\d]+$" - label="Etternavn" - placeholder="Skriv inn ditt etternavn" - invalid-message="Ugyldig etternavn, må være mellom 4 og 16 bokstaver"/> - <BaseInput :model-value="emailRef" - @input-change-event="handleEmailInputEvent" - id="emailInput" - input-id="email" - type="email" - label="E-post" - placeholder="Skriv inn din e-post" - invalid-message="Ugyldig e-post"/> + <BaseInput + :model-value="firstNameRef" + @input-change-event="handleFirstNameInputEvent" + id="firstNameInput" + input-id="first-name" + type="text" + pattern="^(?=.{4,16}$)[^\d]+$" + label="Fornavn" + placeholder="Skriv inn ditt fornavn" + invalid-message="Ugyldig fornavn, husk ingen tall, må være mellom 4 og 16 bokstaver" + /> + <BaseInput + :model-value="surnameRef" + @input-change-event="handleSurnameInputEvent" + id="surnameInput" + input-id="surname" + type="text" + pattern="^(?=.{4,16}$)[^\d]+$" + label="Etternavn" + placeholder="Skriv inn ditt etternavn" + invalid-message="Ugyldig etternavn, må være mellom 4 og 16 bokstaver" + /> + <BaseInput + :model-value="emailRef" + @input-change-event="handleEmailInputEvent" + id="emailInput" + input-id="email" + type="email" + label="E-post" + placeholder="Skriv inn din e-post" + invalid-message="Ugyldig e-post" + /> </div> <div class="col-sm"> - <BaseInput :model-value="passwordRef" - @input-change-event="handlePasswordInputEvent" - id="passwordInput" - input-id="password" - type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" - label="Passord" - placeholder="Skriv inn passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall"/> - <BaseInput :modelValue="confirmPasswordRef" - @input-change-event="handleConfirmPasswordInputEvent" - id="confirmPasswordInput" - input-id="confirmPassword" - type="password" - pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" - label="Bekreft Passord" - placeholder="Bekreft passord" - invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" + <BaseInput + :model-value="passwordRef" + @input-change-event="handlePasswordInputEvent" + id="passwordInput" + input-id="password" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Passord" + placeholder="Skriv inn passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" + /> + <BaseInput + :modelValue="confirmPasswordRef" + @input-change-event="handleConfirmPasswordInputEvent" + id="confirmPasswordInput" + input-id="confirmPassword" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Bekreft Passord" + placeholder="Bekreft passord" + invalid-message="Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall" /> </div> </div> <p class="text-danger">{{ errorMsg }}</p> <p v-if="!samePasswords" class="text-danger">Passordene er ikke like</p> - <BaseButton id="confirmButton" @click="handleSubmit" :disabled="isSubmitting" button-text="Registrer deg"></BaseButton> - <LoginLink/> + <BaseButton + id="confirmButton" + @click="handleSubmit" + :disabled="isSubmitting" + button-text="Registrer deg" + ></BaseButton> + <LoginLink /> </form> </div> - </template> <style scoped> - .container-fluid { max-width: 950px; } @@ -190,7 +201,12 @@ const handleSubmit = async () => { width: 100%; } -#firstNameInput, #surnameInput, #emailInput, #passwordInput, #confirmButton, #confirmPasswordInput { +#firstNameInput, +#surnameInput, +#emailInput, +#passwordInput, +#confirmButton, +#confirmPasswordInput { margin: 1rem 0; } @@ -198,4 +214,4 @@ h1 { font-size: 2rem; font-weight: bold; } -</style> \ No newline at end of file +</style> diff --git a/src/components/SignUp/SignUpLink.vue b/src/components/SignUp/SignUpLink.vue index c53f6914dca776f7784c420472a4126328dbb2c2..dcee4660e944cf06ed07a32e8813e9e442440e6c 100644 --- a/src/components/SignUp/SignUpLink.vue +++ b/src/components/SignUp/SignUpLink.vue @@ -1,9 +1,9 @@ -<script setup lang="ts"> - -</script> +<script setup lang="ts"></script> <template> - <p id="signupText">Ingen bruker? <RouterLink to="/sign-up" id="signup">Registrer deg</RouterLink></p> + <p id="signupText"> + Ingen bruker? <RouterLink to="/sign-up" id="signup">Registrer deg</RouterLink> + </p> </template> <style scoped> @@ -11,4 +11,4 @@ font-size: 14px; padding: 5px; } -</style> \ No newline at end of file +</style> diff --git a/src/components/SignUp/__tests__/SignUpForm.spec.ts b/src/components/SignUp/__tests__/SignUpForm.spec.ts index 1d66d74cc37f9f922d88123a76052b2bed2337c6..4e57653a4377f3a2aa433096d1273eef0e39b1c8 100644 --- a/src/components/SignUp/__tests__/SignUpForm.spec.ts +++ b/src/components/SignUp/__tests__/SignUpForm.spec.ts @@ -1,127 +1,127 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { mount } from '@vue/test-utils'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import { createPinia, setActivePinia } from 'pinia'; -import { useUserInfoStore } from '@/stores/UserStore'; -import MyComponent from '@/components/SignUp/SignUpForm.vue'; // Adjust path as needed -import router from '@/router/index'; // Adjust path as needed -import { access } from 'fs'; -import { render, fireEvent, cleanup, screen } from '@testing-library/vue'; -import userEvent from '@testing-library/user-event'; +import { describe, it, expect, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import { createPinia, setActivePinia } from 'pinia' +import { useUserInfoStore } from '@/stores/UserStore' +import MyComponent from '@/components/SignUp/SignUpForm.vue' // Adjust path as needed +import router from '@/router/index' // Adjust path as needed +import { access } from 'fs' +import { render, fireEvent, cleanup, screen } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' describe('Menu and Router Tests', () => { - let store: any, mockRouter: any; - - beforeEach(() => { - // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); - mockRouter = createRouter({ - history: createMemoryHistory(), - routes: router.getRoutes(), - }); - router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); - } else { - next(); - } - }); - - }); - - describe('Component Rendering', () => { - it('renders form correctly', () => { - const wrapper = mount(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - expect(wrapper.text()).toContain('Fornavn'); - expect(wrapper.text()).toContain('Etternavn'); - expect(wrapper.text()).toContain('E-post'); - }); - }); - - describe('Navigation Guards', () => { - it('redirects an unauthenticated user to login when accessing a protected route', async () => { - store.$patch({ accessToken: '' }); - - router.push('/'); - await router.isReady(); - - expect(router.currentRoute.value.name).toBe('login'); - }); - - it('allows an unauthenticated user to visit login', async () => { - store.$patch({ accessToken: 'valid-token' }); - - mockRouter.push('/login'); - - await mockRouter.isReady(); - - expect(mockRouter.currentRoute.value.name).toBe('login'); - }); - }); - - - describe('Input fields', () => { - it('updates user credentials correctly', async () => { - const { getByPlaceholderText } = render(MyComponent); - - const firstInput = getByPlaceholderText('Skriv inn ditt fornavn') as HTMLInputElement; - const lastInput = getByPlaceholderText('Skriv inn ditt etternavn') as HTMLInputElement; - const emailInput = getByPlaceholderText('Skriv inn din e-post') as HTMLInputElement; - const passwordInput = getByPlaceholderText('Skriv inn passord') as HTMLInputElement; - - await fireEvent.update(firstInput, 'Alice'); - await fireEvent.update(lastInput, 'Alicon'); - await fireEvent.update(emailInput, 'user@example.com'); - await fireEvent.update(passwordInput, 'Password1'); - - expect(firstInput.value).toBe('Alice'); - expect(lastInput.value).toBe('Alicon'); - expect(emailInput.value).toBe('user@example.com'); - expect(passwordInput.value).toBe('Password1'); - }); - - - it('Password error msg', async () => { - const { container } = render(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - const errorMsg = container.querySelector('#invalid'); // Use the actual ID here - expect(errorMsg?.textContent === "Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall") - }); - - it('logout should have empty store at application start', () => { - expect(store.firstname).toBe(''); - expect(store.lastname).toBe(''); - expect(store.accessToken).toBe(''); - }); - }); - - describe('Menu Actions', () => { - it('signup redirects to signup', async () => { - const { container } = render(MyComponent, { - global: { - plugins: [mockRouter], - }, - }); - - // Assuming there's an element with id="home-link" that you want to click - const signupLink = container.querySelector('#login'); // Use the actual ID here - if (signupLink) { - await userEvent.click(signupLink); - await mockRouter.isReady(); - } - - expect(mockRouter.currentRoute.value.name).toBe('login'); // Assuming 'Home' is the route name for '/' - }); - }); -}); + let store: any, mockRouter: any + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()) + store = useUserInfoStore() + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes() + }) + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) + } else { + next() + } + }) + }) + + describe('Component Rendering', () => { + it('renders form correctly', () => { + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + expect(wrapper.text()).toContain('Fornavn') + expect(wrapper.text()).toContain('Etternavn') + expect(wrapper.text()).toContain('E-post') + }) + }) + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + store.$patch({ accessToken: '' }) + + router.push('/') + await router.isReady() + + expect(router.currentRoute.value.name).toBe('login') + }) + + it('allows an unauthenticated user to visit login', async () => { + store.$patch({ accessToken: 'valid-token' }) + + mockRouter.push('/login') + + await mockRouter.isReady() + + expect(mockRouter.currentRoute.value.name).toBe('login') + }) + }) + + describe('Input fields', () => { + it('updates user credentials correctly', async () => { + const { getByPlaceholderText } = render(MyComponent) + + const firstInput = getByPlaceholderText('Skriv inn ditt fornavn') as HTMLInputElement + const lastInput = getByPlaceholderText('Skriv inn ditt etternavn') as HTMLInputElement + const emailInput = getByPlaceholderText('Skriv inn din e-post') as HTMLInputElement + const passwordInput = getByPlaceholderText('Skriv inn passord') as HTMLInputElement + + await fireEvent.update(firstInput, 'Alice') + await fireEvent.update(lastInput, 'Alicon') + await fireEvent.update(emailInput, 'user@example.com') + await fireEvent.update(passwordInput, 'Password1') + + expect(firstInput.value).toBe('Alice') + expect(lastInput.value).toBe('Alicon') + expect(emailInput.value).toBe('user@example.com') + expect(passwordInput.value).toBe('Password1') + }) + + it('Password error msg', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + const errorMsg = container.querySelector('#invalid') // Use the actual ID here + expect( + errorMsg?.textContent === + 'Passordet må være mellom 4 og 16 tegn og inneholde én stor bokstav, liten bokstav og et tall' + ) + }) + + it('logout should have empty store at application start', () => { + expect(store.firstname).toBe('') + expect(store.lastname).toBe('') + expect(store.accessToken).toBe('') + }) + }) + + describe('Menu Actions', () => { + it('signup redirects to signup', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter] + } + }) + + // Assuming there's an element with id="home-link" that you want to click + const signupLink = container.querySelector('#login') // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink) + await mockRouter.isReady() + } + + expect(mockRouter.currentRoute.value.name).toBe('login') // Assuming 'Home' is the route name for '/' + }) + }) +}) diff --git a/src/components/SignUp/__tests__/SignUpLink.spec.ts b/src/components/SignUp/__tests__/SignUpLink.spec.ts index 50e0aa0e4017db44b2113380dcdceaa9c5ed5bca..83e9d55079962e9df2034b9f6217a581c7c5a468 100644 --- a/src/components/SignUp/__tests__/SignUpLink.spec.ts +++ b/src/components/SignUp/__tests__/SignUpLink.spec.ts @@ -1,70 +1,69 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { createPinia, setActivePinia } from 'pinia'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import LoginPrompt from '@/components/SignUp/SignUpLink.vue'; -import { useUserInfoStore } from '@/stores/UserStore'; -import router from '@/router/index'; -import { render, screen } from '@testing-library/vue'; -import userEvent from '@testing-library/user-event'; +import { describe, it, expect, beforeEach } from 'vitest' +import { createPinia, setActivePinia } from 'pinia' +import { createRouter, createMemoryHistory } from 'vue-router' +import LoginPrompt from '@/components/SignUp/SignUpLink.vue' +import { useUserInfoStore } from '@/stores/UserStore' +import router from '@/router/index' +import { render, screen } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' describe('LoginPrompt', () => { - let store: any, mockRouter: any; + let store: any, mockRouter: any - beforeEach(() => { - // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); - mockRouter = createRouter({ - history: createMemoryHistory(), - routes: router.getRoutes(), - }); - router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); - } else { - next(); - } - }); - }); + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()) + store = useUserInfoStore() + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes() + }) + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) + } else { + next() + } + }) + }) + it('renders login link correctly', async () => { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/signup', component: { template: 'Signup Page' } }] + }) - it('renders login link correctly', async () => { - const router = createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/signup', component: { template: 'Signup Page' } }], - }); + const { getByText } = render(LoginPrompt, { + global: { + plugins: [router] + } + }) - const { getByText } = render(LoginPrompt, { - global: { - plugins: [router], - }, - }); + const loginLink = getByText('Registrer deg') + expect(loginLink).toBeDefined() // Check if the 'Login' link is rendered + }) - const loginLink = getByText('Registrer deg'); - expect(loginLink).toBeDefined(); // Check if the 'Login' link is rendered - }); + it('navigates to the login page when the login link is clicked', async () => { + const mockRouter = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }] + }) - it('navigates to the login page when the login link is clicked', async () => { - const mockRouter = createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }], - }); - - const { container } = render(LoginPrompt, { - global: { - plugins: [mockRouter], - }, - }); - - await mockRouter.isReady(); // Ensure the router is ready before asserting - - const signupLink = container.querySelector('#signup'); // Use the actual ID here - if (signupLink) { - await userEvent.click(signupLink); - await mockRouter.isReady(); - } - - expect(mockRouter.currentRoute.value.path).toBe('/sign-up'); // Check if the router navigated to the login page - }, 10000); -}); + const { container } = render(LoginPrompt, { + global: { + plugins: [mockRouter] + } + }) + + await mockRouter.isReady() // Ensure the router is ready before asserting + + const signupLink = container.querySelector('#signup') // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink) + await mockRouter.isReady() + } + + expect(mockRouter.currentRoute.value.path).toBe('/sign-up') // Check if the router navigated to the login page + }, 10000) +}) diff --git a/src/components/UserProfile/ExternalProfile.vue b/src/components/UserProfile/ExternalProfile.vue index f95f18854632bb6294f5a90dde7e8bee5ee47eb1..c5b6bbd8f8dde1a43eecd980962e8b3777cb9b91 100644 --- a/src/components/UserProfile/ExternalProfile.vue +++ b/src/components/UserProfile/ExternalProfile.vue @@ -1,35 +1,42 @@ <script setup lang="ts"> -import {ref, onMounted} from "vue"; -import { useRoute, useRouter } from "vue-router"; -import { useUserInfoStore } from "@/stores/UserStore"; -import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO, FriendService} from "@/api"; -import { ItemService } from "@/api"; +import { ref, onMounted } from 'vue' +import { useRoute, useRouter } from 'vue-router' +import { useUserInfoStore } from '@/stores/UserStore' +import { + UserService, + BadgeService, + GoalService, + type GoalDTO, + type BadgeDTO, + FriendService +} from '@/api' +import { ItemService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import bannerImage from '@/assets/banners/stacked.svg' -let apiUrl = import.meta.env.VITE_APP_API_URL; +let apiUrl = import.meta.env.VITE_APP_API_URL -let firstname = ref(); -let lastname = ref(); -const imageUrl = ref(`../src/assets/userprofile.png`); +let firstname = ref() +let lastname = ref() +const imageUrl = ref(`../src/assets/userprofile.png`) -const bannerImageUrl = ref(bannerImage); +const bannerImageUrl = ref(bannerImage) let hasBadges = ref(false) let hasInventory = ref(false) -const router = useRouter(); -const route = useRoute(); -const inventory = ref([] as any); -const badges = ref<BadgeDTO[]>([]); -const backgroundName = ref(""); -const points = ref(0 as any); -const streak = ref(0 as any); +const router = useRouter() +const route = useRoute() +const inventory = ref([] as any) +const badges = ref<BadgeDTO[]>([]) +const backgroundName = ref('') +const points = ref(0 as any) +const streak = ref(0 as any) -const isFriend = ref(false); -const isRequestSent = ref(false); +const isFriend = ref(false) +const isRequestSent = ref(false) -const isMe = ref(false); +const isMe = ref(false) /** * Sets up the form for displaying user profile information. @@ -39,29 +46,29 @@ const isMe = ref(false); */ async function setupForm() { try { - let id = route.params.id as any; + let id = route.params.id as any let response = await UserService.getProfile({ userId: id }) - firstname.value = response.firstName; - lastname.value = response.lastName; + firstname.value = response.firstName + lastname.value = response.lastName if (response.point?.currentPoints) { - points.value = response.point?.currentPoints; + points.value = response.point?.currentPoints } if (response.streak?.currentStreak) { - streak.value = response.streak?.currentStreak; + streak.value = response.streak?.currentStreak } if (response.profileImage) { - imageUrl.value = apiUrl + "/api/images/" + response.profileImage; + imageUrl.value = apiUrl + '/api/images/' + response.profileImage } if (response.bannerImage != 0 && response.bannerImage !== null) { console.log(response.bannerImage) - bannerImageUrl.value = apiUrl + "/api/images/" + response.bannerImage; + bannerImageUrl.value = apiUrl + '/api/images/' + response.bannerImage } - let userId = route.params.id; - isMe.value = String(userId) !== String(useUserInfoStore().id); - getBadges(); + let userId = route.params.id + isMe.value = String(userId) !== String(useUserInfoStore().id) + getBadges() } catch (err) { handleUnknownError(err) console.error(err) @@ -73,14 +80,14 @@ async function setupForm() { * Fetches the user's friends list and sets the isFriend value accordingly. */ const checkIfFriend = async () => { - let id = route.params.id as any; - const response = await FriendService.getFriends(); + let id = route.params.id as any + const response = await FriendService.getFriends() response.forEach((friend) => { if (friend.id == id) { - isFriend.value = true; + isFriend.value = true } - }); -}; + }) +} /** * Retrieves the user's inventory by user ID. @@ -89,8 +96,8 @@ const checkIfFriend = async () => { const getInventory = async () => { try { let id = route.params.id as any - const response = await ItemService.getInventoryByUserId({ userId: id }); - inventory.value = response; + const response = await ItemService.getInventoryByUserId({ userId: id }) + inventory.value = response if (inventory.value.length > 0) { hasInventory.value = true } else { @@ -99,7 +106,7 @@ const getInventory = async () => { } } catch (error) { handleUnknownError(error) - console.log(error); + console.log(error) } } @@ -110,8 +117,8 @@ const getInventory = async () => { const getBadges = async () => { try { let id = route.params.id as any - const responseBadge = await BadgeService.getBadgesUnlockedByUser({ userId: id }); - badges.value = responseBadge; + const responseBadge = await BadgeService.getBadgesUnlockedByUser({ userId: id }) + badges.value = responseBadge if (badges.value.length > 0) { hasBadges.value = true } else { @@ -120,7 +127,7 @@ const getBadges = async () => { } } catch (error) { handleUnknownError(error) - console.log(error); + console.log(error) } } @@ -130,22 +137,19 @@ onMounted(() => { }) const toRoadmap = () => { - router.push('/'); -}; + router.push('/') +} const addFriend = () => { - let id = route.params.id as any; - const response = FriendService.addFriendRequest({ userId: id }); - isRequestSent.value = true; -}; + let id = route.params.id as any + const response = FriendService.addFriendRequest({ userId: id }) + isRequestSent.value = true +} const removeFriend = () => { - let id = route.params.id as any; - const response = FriendService.deleteFriendOrFriendRequest({ friendId: id }); -}; - - - + let id = route.params.id as any + const response = FriendService.deleteFriendOrFriendRequest({ friendId: id }) +} </script> <template> @@ -153,48 +157,68 @@ const removeFriend = () => { <div class="row d-flex justify-content-center align-items-center h-100"> <div class="col 12"> <div class="card"> - <div class="rounded-top text-white d-flex flex-row bg-primary justify-content-between" :style="{ - height: '200px', - backgroundImage: `url(${bannerImageUrl})`, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat' - }"> - <div class=" text-white d-flex flex-row"> - <div class=" d-flex flex-column align-items-center justify-content-center"> - <img :src="imageUrl" alt="Generisk plassholderbilde" class="img-fluid img-thumbnail" - style="width: 150px; height:150px; margin-left: 25px; margin-right: 15px;"> + <div + class="rounded-top text-white d-flex flex-row bg-primary justify-content-between" + :style="{ + height: '200px', + backgroundImage: `url(${bannerImageUrl})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat' + }" + > + <div class="text-white d-flex flex-row"> + <div class="d-flex flex-column align-items-center justify-content-center"> + <img + :src="imageUrl" + alt="Generisk plassholderbilde" + class="img-fluid img-thumbnail" + style="width: 150px; height: 150px; margin-left: 25px; margin-right: 15px" + /> </div> - <h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px;">{{ firstname }} {{ - lastname }}</h1> + <h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px"> + {{ firstname }} {{ lastname }} + </h1> </div> <div class="d-flex align-items-end text-white my-3 mx-5"> <div class="d-flex align-items-center flex-column"> - <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="points"><img - src="@/assets/items/pigcoin.png" style="width: 80px; height: 80px" data-toggle="tooltip" - title="Points"> {{ points }}</p> + <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="points"> + <img + src="@/assets/items/pigcoin.png" + style="width: 80px; height: 80px" + data-toggle="tooltip" + title="Points" + /> + {{ points }} + </p> </div> <div class="d-flex align-items-center flex-column px-3"> - <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="streak"><img - src="@/assets/icons/fire.png" style="width: 80px; height: 80px" data-toggle="tooltip" - title="Points"> {{ streak }}</p> + <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="streak"> + <img + src="@/assets/icons/fire.png" + style="width: 80px; height: 80px" + data-toggle="tooltip" + title="Points" + /> + {{ streak }} + </p> </div> </div> </div> - <div v-if="isMe" class="p-3 text-black" style="background-color: #f8f9fa;"> + <div v-if="isMe" class="p-3 text-black" style="background-color: #f8f9fa"> <div class="d-flex justify-content-end text-center py-1"> <div style="width: 100%; display: flex; justify-content: start"> <button v-if="!isFriend && !isRequestSent" @click="addFriend" class="btn btn-success mx-3" - style="height: 40px;" + style="height: 40px" > Legg til venn </button> <button v-else-if="isRequestSent" class="btn btn-secondary mx-2" - style="height: 40px;" + style="height: 40px" disabled > Forespørsel sendt @@ -203,36 +227,50 @@ const removeFriend = () => { v-else @click="removeFriend" class="btn btn-danger mx-3" - style="height: 40px;" + style="height: 40px" > Fjern venn </button> </div> </div> </div> - <hr> + <hr /> <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> <div class="container-fluid"> <h1 class="mt-1 text-start badges-text">Merker</h1> - <div v-if="hasBadges" 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="apiUrl + `/api/images/${badge.imageId}`" class="card-img-top" - alt="..." /> - <div class="card-body"> - <h5 class="card-title">{{ badge.badgeName }}</h5> - </div> + <div + v-if="hasBadges" + 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="apiUrl + `/api/images/${badge.imageId}`" + class="card-img-top" + alt="..." + /> + <div class="card-body"> + <h5 class="card-title">{{ badge.badgeName }}</h5> + </div> </div> - - </div> - <div v-else> - Ingen merker </div> + <div v-else>Ingen merker</div> </div> </div> </div> @@ -243,7 +281,6 @@ const removeFriend = () => { </div> </template> - <style scoped> .scrolling-wrapper-badges { overflow-x: auto; @@ -256,12 +293,12 @@ const removeFriend = () => { .badges-text { font-weight: 500; - font-size: 2.0em; + font-size: 2em; } .history-text { font-weight: 500; - font-size: 2.0em; + font-size: 2em; } .badges-block { @@ -303,59 +340,57 @@ const removeFriend = () => { } .card-1 { - background-color: #4158D0; - background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%); + background-color: #4158d0; + background-image: linear-gradient(43deg, #4158d0 0%, #c850c0 46%, #ffcc70 100%); } .card-2 { - background-color: #0093E9; - background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%); + background-color: #0093e9; + background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%); } .card-3 { - background-color: #00DBDE; - background-image: linear-gradient(90deg, #00DBDE 0%, #FC00FF 100%); + background-color: #00dbde; + background-image: linear-gradient(90deg, #00dbde 0%, #fc00ff 100%); } .card-4 { - background-color: #FBAB7E; - background-image: linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%); + background-color: #fbab7e; + background-image: linear-gradient(62deg, #fbab7e 0%, #f7ce68 100%); } .card-5 { - background-color: #85FFBD; - background-image: linear-gradient(45deg, #85FFBD 0%, #FFFB7D 100%); + background-color: #85ffbd; + background-image: linear-gradient(45deg, #85ffbd 0%, #fffb7d 100%); } .card-6 { - background-color: #FA8BFF; - background-image: linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BFF88 90%); + background-color: #fa8bff; + background-image: linear-gradient(45deg, #fa8bff 0%, #2bd2ff 52%, #2bff88 90%); } .card-7 { - background-color: #FA8BFF; - background-image: linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BFF88 90%); + background-color: #fa8bff; + background-image: linear-gradient(45deg, #fa8bff 0%, #2bd2ff 52%, #2bff88 90%); } .card-8 { - background-color: #FBDA61; - background-image: linear-gradient(45deg, #FBDA61 0%, #FF5ACD 100%); + background-color: #fbda61; + background-image: linear-gradient(45deg, #fbda61 0%, #ff5acd 100%); } .card-9 { - background-color: #4158D0; - background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%); + background-color: #4158d0; + background-image: linear-gradient(43deg, #4158d0 0%, #c850c0 46%, #ffcc70 100%); } .card-10 { - background-color: #FF3CAC; - background-image: linear-gradient(225deg, #FF3CAC 0%, #784BA0 50%, #2B86C5 100%); - + background-color: #ff3cac; + background-image: linear-gradient(225deg, #ff3cac 0%, #784ba0 50%, #2b86c5 100%); } - /*-------*/ .rounded-top { - background-color: #00DBDE; + background-color: #00dbde; } -</style> \ No newline at end of file +</style> diff --git a/src/components/UserProfile/MyProfile.vue b/src/components/UserProfile/MyProfile.vue index e6a4871922b77d5dd24d5903c11ce18e9645f492..2a01a3a0299253543a9d85b6dcdc3cc053cd0cea 100644 --- a/src/components/UserProfile/MyProfile.vue +++ b/src/components/UserProfile/MyProfile.vue @@ -13,25 +13,24 @@ import { import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import bannerImage from '@/assets/banners/stacked.svg' -let apiUrl = import.meta.env.VITE_APP_API_URL; -let numberOfHistory = 6; -let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", "Pretty clothes"] -let firstname = ref(); -let lastname = ref(); -const imageUrl = ref(`../src/assets/userprofile.png`); -const bannerImageUrl = ref(bannerImage); - +let apiUrl = import.meta.env.VITE_APP_API_URL +let numberOfHistory = 6 +let cardTitles = ['Spain tour', 'Food waste', 'Coffee', 'Concert', 'New book', 'Pretty clothes'] +let firstname = ref() +let lastname = ref() +const imageUrl = ref(`../src/assets/userprofile.png`) +const bannerImageUrl = ref(bannerImage) let hasHistory = ref(true) let hasBadges = ref(false) let hasInventory = ref(false) -const router = useRouter(); -const inventory = ref([] as any); -const badges = ref<BadgeDTO[]>([]); -const backgroundName = ref(""); -const points = ref(0 as any); -const streak = ref(0 as any); +const router = useRouter() +const inventory = ref([] as any) +const badges = ref<BadgeDTO[]>([]) +const backgroundName = ref('') +const points = ref(0 as any) +const streak = ref(0 as any) let goals = ref<GoalDTO[]>([]) @@ -42,11 +41,11 @@ let goals = ref<GoalDTO[]>([]) */ async function getGoals() { try { - goals.value = await GoalService.getGoals(); - hasHistory.value = goals.value.length > 0; + goals.value = await GoalService.getGoals() + hasHistory.value = goals.value.length > 0 } catch (error) { handleUnknownError(error) - console.error("Something went wrong", error) + console.error('Something went wrong', error) } } @@ -58,26 +57,26 @@ async function getGoals() { */ async function setupForm() { try { - const response = await UserService.getUser(); + const response = await UserService.getUser() console.log(response.firstName) - firstname.value = response.firstName; - lastname.value = response.lastName; + firstname.value = response.firstName + lastname.value = response.lastName if (response.point?.currentPoints) { - points.value = response.point?.currentPoints; + points.value = response.point?.currentPoints } if (response.streak?.currentStreak) { - streak.value = response.streak?.currentStreak; + streak.value = response.streak?.currentStreak } if (response.profileImage) { - imageUrl.value = apiUrl + "/api/images/" + response.profileImage; + imageUrl.value = apiUrl + '/api/images/' + response.profileImage } if (response.bannerImage != 0 && response.bannerImage !== null) { console.log(response.bannerImage) - bannerImageUrl.value = apiUrl + "/api/images/" + response.bannerImage; + bannerImageUrl.value = apiUrl + '/api/images/' + response.bannerImage } - getInventory(); - getBadges(); + getInventory() + getBadges() } catch (err) { handleUnknownError(err) console.error(err) @@ -91,15 +90,14 @@ async function setupForm() { */ const getInventory = async () => { try { - inventory.value = await ItemService.getInventory(); - hasInventory.value = inventory.value.length > 0; + inventory.value = await ItemService.getInventory() + hasInventory.value = inventory.value.length > 0 } catch (error) { handleUnknownError(error) - console.log(error); + console.log(error) } } - /** * Retrieves the badges unlocked by the active user. * Updates the badges value with the retrieved data. @@ -107,11 +105,11 @@ const getInventory = async () => { */ const getBadges = async () => { try { - badges.value = await BadgeService.getBadgesUnlockedByActiveUser(); - hasBadges.value = badges.value.length > 0; + badges.value = await BadgeService.getBadgesUnlockedByActiveUser() + hasBadges.value = badges.value.length > 0 } catch (error) { handleUnknownError(error) - console.log(error); + console.log(error) } } @@ -123,14 +121,14 @@ const getBadges = async () => { */ const selectItem = (item: any) => { try { - backgroundName.value = item.itemName; - let imageId = item.imageId; + backgroundName.value = item.itemName + let imageId = item.imageId const bannerImagePayload: UserUpdateDTO = { - bannerImage: imageId as any, - }; + bannerImage: imageId as any + } UserService.update({ requestBody: bannerImagePayload }) if (imageId != 0) { - bannerImageUrl.value = `${apiUrl}/api/images/${imageId}`; + bannerImageUrl.value = `${apiUrl}/api/images/${imageId}` } } catch (error) { handleUnknownError(error) @@ -150,17 +148,15 @@ onMounted(() => { * Redirects the user to the roadmap page. */ const toRoadmap = () => { - router.push('/'); -}; - - + router.push('/') +} /** * Redirects the user to the update user settings page. */ const toUpdateUserSettings = () => { - router.push('/settings/profile'); -}; + router.push('/settings/profile') +} </script> <template> @@ -168,92 +164,145 @@ const toUpdateUserSettings = () => { <div class="row d-flex justify-content-center align-items-center h-100"> <div class="col 12"> <div class="card"> - <div class="rounded-top text-white d-flex flex-row bg-primary justify-content-between flex-wrap" id="banner" :style="{ - - backgroundImage: `url(${bannerImageUrl})`, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat' - }"> - <div class=" text-white d-flex flex-row"> - <div class=" d-flex flex-column align-items-center justify-content-center"> - <img :src="imageUrl" alt="Generisk plassholderbilde" class="img-fluid img-thumbnail" - style="width: 150px; height:150px; margin-left: 25px; margin-right: 15px;"> + <div + class="rounded-top text-white d-flex flex-row bg-primary justify-content-between flex-wrap" + id="banner" + :style="{ + backgroundImage: `url(${bannerImageUrl})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat' + }" + > + <div class="text-white d-flex flex-row"> + <div class="d-flex flex-column align-items-center justify-content-center"> + <img + :src="imageUrl" + alt="Generisk plassholderbilde" + class="img-fluid img-thumbnail" + style="width: 150px; height: 150px; margin-left: 25px; margin-right: 15px" + /> </div> - <h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px;">{{ firstname }} {{ - lastname }}</h1> + <h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px"> + {{ firstname }} {{ lastname }} + </h1> </div> <div class="d-flex align-items-end text-white my-3 mx-5"> <div class="d-flex align-items-center flex-column"> - <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="points"><img - src="@/assets/items/pigcoin.png" style="width: 80px; height: 80px" data-toggle="tooltip" - title="Points"> {{ points }}</p> + <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="points"> + <img + src="@/assets/items/pigcoin.png" + style="width: 80px; height: 80px" + data-toggle="tooltip" + title="Points" + /> + {{ points }} + </p> </div> <div class="d-flex align-items-center flex-column px-3"> - <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="streak"><img - src="@/assets/icons/fire.png" style="width: 80px; height: 80px" data-toggle="tooltip" - title="Points"> {{ streak }}</p> + <p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="streak"> + <img + src="@/assets/icons/fire.png" + style="width: 80px; height: 80px" + data-toggle="tooltip" + title="Points" + /> + {{ streak }} + </p> </div> </div> </div> - <div class="p-3 text-black" style="background-color: #f8f9fa;"> + <div class="p-3 text-black" style="background-color: #f8f9fa"> <div class="d-flex justify-content-end text-center py-1"> <div style="width: 100%; display: flex; justify-content: start"> - <button data-cy="toUpdate" type="button" data-mdb-button-init data-mdb-ripple-init - class="btn btn-outline-primary classyButton" data-mdb-ripple-color="dark" - style="z-index: 1; height: 40px; margin-left: 17px" id="toUpdate" @click="toUpdateUserSettings"> + <button + data-cy="toUpdate" + type="button" + data-mdb-button-init + data-mdb-ripple-init + class="btn btn-outline-primary classyButton" + data-mdb-ripple-color="dark" + style="z-index: 1; height: 40px; margin-left: 17px" + id="toUpdate" + @click="toUpdateUserSettings" + > Rediger profil </button> </div> </div> </div> - <hr> + <hr /> <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> <div class="container-fluid"> <h1 class="mt-1 text-start badges-text">Merker</h1> - <div v-if="hasBadges" 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 d-flex align-items-center justify-content-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="apiUrl + `/api/images/${badge.imageId}`" class="card-img-top mt-2" alt="..." - style="width: 150px; height: 150px;" /> + <div + v-if="hasBadges" + 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 d-flex align-items-center justify-content-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="apiUrl + `/api/images/${badge.imageId}`" + class="card-img-top mt-2" + alt="..." + style="width: 150px; height: 150px" + /> <div class="card-body"> <h5 class="card-title">{{ badge.badgeName }}</h5> </div> </div> </div> - <div v-else> - Ingen merker - </div> + <div v-else>Ingen merker</div> </div> </div> </div> - <hr> + <hr /> <div class="row"> <div class="col"> <!-- Her er historikken over lagrede mål --> <div class="container-fluid mb-5"> <h1 class="mt-1 text-start history-text">Historie</h1> <div v-if="hasHistory" class="row scrolling-wrapper-history"> - <div v-for="(item, index) in goals" :key="index" - class="col-md-4 col-sm-4 col-lg-4 col-xs-4 col-xl-4 control-label"> + <div + v-for="(item, index) in goals" + :key="index" + class="col-md-4 col-sm-4 col-lg-4 col-xs-4 col-xl-4 control-label" + > <div class="card history-block"> - <div class="card mb-3" style="max-width: 540px;"> + <div class="card mb-3" style="max-width: 540px"> <div class="row g-0"> <div class="col-md-4"> - <img src="/src/assets/icons/piggybank.svg" - class="img-fluid rounded-start h-40 mx-auto d-none d-md-block" alt="..."> + <img + src="/src/assets/icons/piggybank.svg" + class="img-fluid rounded-start h-40 mx-auto d-none d-md-block" + alt="..." + /> </div> <div class="col-md-8"> <div class="card-body"> <h5 class="card-title">{{ goals[index]['name'] }}</h5> <p class="card-text">{{ goals[index]['description'] }}</p> - <p class="card-text"><small class="text-muted">{{ goals[index]['targetAmount'] - }}</small> + <p class="card-text"> + <small class="text-muted">{{ + goals[index]['targetAmount'] + }}</small> </p> - <a href="#" class="btn stretched-link" @click="toRoadmap"></a> + <a href="#" class="btn stretched-link" @click="toRoadmap"></a> </div> </div> </div> @@ -261,10 +310,7 @@ const toUpdateUserSettings = () => { </div> </div> </div> - <div v-if="!hasHistory"> - Ingen sparemål - </div> - + <div v-if="!hasHistory">Ingen sparemål</div> </div> </div> </div> @@ -275,7 +321,6 @@ const toUpdateUserSettings = () => { </div> </template> - <style scoped> .scrolling-wrapper-badges { overflow-x: auto; @@ -288,12 +333,12 @@ const toUpdateUserSettings = () => { .badges-text { font-weight: 500; - font-size: 2.0em; + font-size: 2em; } .history-text { font-weight: 500; - font-size: 2.0em; + font-size: 2em; } .badges-block { @@ -336,28 +381,28 @@ const toUpdateUserSettings = () => { @media (max-width: 940px) { #banner { - height: 320px; -} + height: 320px; + } } /*-------*/ .rounded-top { - background-color: #00DBDE; + background-color: #00dbde; } .classyButton { - background-color: #003A58; - border: #003A58; + background-color: #003a58; + border: #003a58; color: white; } .classyButton:hover { background-color: #003b58ec; - border: #003A58; + border: #003a58; } .classyButton:active { background-color: #003b58d6; - border: #003A58; + border: #003a58; } -</style> \ No newline at end of file +</style> diff --git a/src/components/UserProfile/__tests__/MyProfile.spec.ts b/src/components/UserProfile/__tests__/MyProfile.spec.ts index 306f0f13d39a6864b6947b57e86362399e1c1003..54dc1fcab35f12b685fa642e69116e2fd103b59d 100644 --- a/src/components/UserProfile/__tests__/MyProfile.spec.ts +++ b/src/components/UserProfile/__tests__/MyProfile.spec.ts @@ -1,80 +1,77 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { mount } from '@vue/test-utils'; -import { createRouter, createMemoryHistory } from 'vue-router'; -import { createPinia, setActivePinia } from 'pinia'; -import { useUserInfoStore } from '@/stores/UserStore'; -import MyComponent from '../MyProfile.vue'; // Adjust path as needed -import router from '@/router/index'; // Adjust path as needed +import { describe, it, expect, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import { createPinia, setActivePinia } from 'pinia' +import { useUserInfoStore } from '@/stores/UserStore' +import MyComponent from '../MyProfile.vue' // Adjust path as needed +import router from '@/router/index' // Adjust path as needed describe('MyComponent and Router Tests', () => { - let store: any, mockRouter: any; + let store: any, mockRouter: any beforeEach(() => { // Create a fresh Pinia and Router instance before each test - setActivePinia(createPinia()); - store = useUserInfoStore(); + setActivePinia(createPinia()) + store = useUserInfoStore() mockRouter = createRouter({ history: createMemoryHistory(), - routes: router.getRoutes(), - }); + routes: router.getRoutes() + }) router.beforeEach((to, from, next) => { - const isAuthenticated = store.accessToken; - if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { - next({ name: 'login' }); + const isAuthenticated = store.accessToken + if (to.matched.some((record) => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }) } else { - next(); + next() } - }); - - }); + }) + }) describe('Component Rendering', () => { it('renders MyComponent correctly', () => { - const wrapper = mount(MyComponent, { global: { - plugins: [mockRouter], - }, - }); - expect(wrapper.text()).toContain('Rediger profil'); - }); - }); + plugins: [mockRouter] + } + }) + expect(wrapper.text()).toContain('Rediger profil') + }) + }) describe('Navigation Guards', () => { it('redirects an unauthenticated user to login when accessing a protected route', async () => { // Simulate the user being unauthenticated - store.$patch({ accessToken: '' }); - - router.push('/profile'); - await router.isReady(); - - expect(router.currentRoute.value.name).toBe('login'); - }); - + store.$patch({ accessToken: '' }) + + router.push('/profile') + await router.isReady() + + expect(router.currentRoute.value.name).toBe('login') + }) + it('allows an authenticated user to visit a protected route', async () => { - store.$patch({ accessToken: 'valid-token' }); // Token is present - mockRouter.push('/profile'); - await mockRouter.isReady(); - expect(mockRouter.currentRoute.value.name).toBe('profile'); - }); - }); - + store.$patch({ accessToken: 'valid-token' }) // Token is present + mockRouter.push('/profile') + await mockRouter.isReady() + expect(mockRouter.currentRoute.value.name).toBe('profile') + }) + }) describe('UserStore Actions', () => { it('updates user information correctly', () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith' }); + store.setUserInfo({ firstname: 'John', lastname: 'Smith' }) - expect(store.firstname).toBe('John'); - expect(store.lastname).toBe('Smith'); - }); + expect(store.firstname).toBe('John') + expect(store.lastname).toBe('Smith') + }) it('clears user information correctly', () => { - store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); - store.clearUserInfo(); + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken' }) + store.clearUserInfo() - expect(store.firstname).toBe(''); - expect(store.lastname).toBe(''); - expect(store.accessToken).toBe(''); - }); - }); -}); + expect(store.firstname).toBe('') + expect(store.lastname).toBe('') + expect(store.accessToken).toBe('') + }) + }) +}) diff --git a/src/main.ts b/src/main.ts index 6aa433a9ccecfdd9d9a9498cfd68ef2817967524..8b3ed1a10c8173f7203ac08d34ecad1f3b4bb068 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,16 @@ -import { createApp } from 'vue'; -import { createPinia } from 'pinia'; -import piniaPluginPersistedState from 'pinia-plugin-persistedstate'; +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import piniaPluginPersistedState from 'pinia-plugin-persistedstate' -import App from './App.vue'; -import router from './router'; -import 'bootstrap/dist/css/bootstrap.min.css'; -import 'bootstrap'; +import App from './App.vue' +import router from './router' +import 'bootstrap/dist/css/bootstrap.min.css' +import 'bootstrap' -const app = createApp(App); -const pinia = createPinia(); -pinia.use(piniaPluginPersistedState); +const app = createApp(App) +const pinia = createPinia() +pinia.use(piniaPluginPersistedState) -app.use(pinia); -app.use(router); -app.mount('#app'); +app.use(pinia) +app.use(router) +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts index 5d22daa14e0a814cca8b96f4ab79038b31bb128c..21000daf056991c252618cf673380bd9312a9c56 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,6 @@ // Import necessary dependencies from Vue Router and your views -import { createRouter, createWebHistory } from 'vue-router'; -import { useUserInfoStore } from '@/stores/UserStore'; +import { createRouter, createWebHistory } from 'vue-router' +import { useUserInfoStore } from '@/stores/UserStore' const routes = [ { path: '/', @@ -11,22 +11,22 @@ const routes = [ { path: '', name: 'home', - component: () => import('@/views/SavingGoal/RoadmapView.vue'), + component: () => import('@/views/SavingGoal/RoadmapView.vue') }, { path: 'news', name: 'news', - component: () => import('@/views/News/NewsView.vue'), + component: () => import('@/views/News/NewsView.vue') }, { path: 'leaderboard', name: 'leaderboard', - component: () => import('@/views/Leaderboard/LeaderboardView.vue'), + component: () => import('@/views/Leaderboard/LeaderboardView.vue') }, { path: 'profile', name: 'profile', - component: () => import('@/views/User/MyProfileView.vue'), + component: () => import('@/views/User/MyProfileView.vue') }, { path: 'admin', @@ -42,98 +42,98 @@ const routes = [ { path: '/settings/account', name: 'account', - component: () => import('@/components/Settings/SettingsAccount.vue'), + component: () => import('@/components/Settings/SettingsAccount.vue') }, { path: '/settings/profile', name: 'profilesettings', - component: () => import('@/components/Settings/SettingsProfile.vue'), + component: () => import('@/components/Settings/SettingsProfile.vue') }, { path: '/settings/security', name: 'security', - component: () => import('@/components/Settings/SettingsSecurity.vue'), + component: () => import('@/components/Settings/SettingsSecurity.vue') }, { path: '/settings/bank', name: 'bank', - component: () => import('@/components/Settings/SettingsBank.vue'), - }, + component: () => import('@/components/Settings/SettingsBank.vue') + } ] }, { path: 'roadmap', name: 'roadmap', - component: () => import('@/views/SavingGoal/RoadmapView.vue'), + component: () => import('@/views/SavingGoal/RoadmapView.vue') }, { path: 'feedback', name: 'feedback', - component: () => import('@/views/User/UserFeedbackView.vue'), + component: () => import('@/views/User/UserFeedbackView.vue') }, { path: 'shop', name: 'shop', - component: () => import('@/views/Shop/ShopView.vue'), + component: () => import('@/views/Shop/ShopView.vue') }, { path: '/budget-overview', name: 'budget overview', component: () => import('@/views/Budget/BudgetOverview.vue'), - meta: { requiresPremium: true }, + meta: { requiresPremium: true } }, { path: '/budget', name: 'budget', component: () => import('@/views/Budget/BudgetView.vue'), - meta: { requiresPremium: true }, + meta: { requiresPremium: true } }, { path: '/profile/:id', name: 'friend-profile', - component: () => import('@/views/User/ExternalProfileView.vue'), + component: () => import('@/views/User/ExternalProfileView.vue') }, { path: 'friends', name: 'friends', - component: () => import('@/views/User/UserFriendsView.vue'), + component: () => import('@/views/User/UserFriendsView.vue') }, { path: 'unauthorized', name: 'unauthorized', - component: () => import('@/views/Exception/UnauthorizedView.vue'), + component: () => import('@/views/Exception/UnauthorizedView.vue') }, { path: '/:pathMatch(.*)*', name: 'not-found', - component: () => import('@/views/Exception/NotFoundView.vue'), - }, + component: () => import('@/views/Exception/NotFoundView.vue') + } ] }, { path: '/login', name: 'login', - component: () => import('@/views/Authentication/LoginView.vue'), + component: () => import('@/views/Authentication/LoginView.vue') }, { path: '/forgotten-password', name: 'forgotten-password', - component: () => import('@/views/Authentication/ForgottenPasswordView.vue'), + component: () => import('@/views/Authentication/ForgottenPasswordView.vue') }, { path: '/change-password/:token', name: 'change-password', - component: () => import('@/views/Authentication/ChangePasswordView.vue'), + component: () => import('@/views/Authentication/ChangePasswordView.vue') }, { path: '/sign-up', name: 'sign up', - component: () => import('@/views/Authentication/SignUpView.vue'), + component: () => import('@/views/Authentication/SignUpView.vue') }, { path: '/redirect', name: 'redirect', - component: () => import('@/views/BankID/RedirectView.vue'), + component: () => import('@/views/BankID/RedirectView.vue') }, { path: '/configuration', @@ -143,62 +143,66 @@ const routes = [ { path: '/bank-account', name: 'bank account', - component: () => import('@/components/Configuration/ConfigurationSteps/BankAccount.vue'), + component: () => import('@/components/Configuration/ConfigurationSteps/BankAccount.vue') }, { path: '/commitment', name: 'commitment', - component: () => import('@/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue'), + component: () => + import('@/components/Configuration/ConfigurationSteps/ConfigurationCommitment.vue') }, { path: '/experience', name: 'experience', - component: () => import('@/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue'), + component: () => + import('@/components/Configuration/ConfigurationSteps/ConfigurationExperience.vue') }, { path: '/suitable-challenges', name: 'suitable challenges', - component: () => import('@/components/Configuration/ConfigurationSteps/SuitableChallenges.vue'), + component: () => + import('@/components/Configuration/ConfigurationSteps/SuitableChallenges.vue') }, { path: '/first-saving-goal', name: 'first saving goal', - component: () => import('@/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue'), + component: () => + import('@/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue') } ] }, { path: '/:pathMatch(.*)*', - redirect: { name: 'not-found' }, - }, -]; + redirect: { name: 'not-found' } + } +] const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL || '/'), routes, scrollBehavior() { - return { top: 0 }; - }, -}); + return { top: 0 } + } +}) router.beforeEach((to, from, next) => { - const requiresAuth = to.matched.some(record => record.meta.requiresAuth); - const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin); - const requiresPremium = to.matched.some(record => record.meta.requiresPremium); - const user = useUserInfoStore(); - const userRole = user.role; - const userSubscription = user.subscriptionLevel; - const isAuthenticated = user.isLoggedIn; + const requiresAuth = to.matched.some((record) => record.meta.requiresAuth) + const requiresAdmin = to.matched.some((record) => record.meta.requiresAdmin) + const requiresPremium = to.matched.some((record) => record.meta.requiresPremium) + const user = useUserInfoStore() + const userRole = user.role + const userSubscription = user.subscriptionLevel + const isAuthenticated = user.isLoggedIn if (requiresAuth && !isAuthenticated) { - next({ name: 'login', query: { redirect: to.fullPath } }); + next({ name: 'login', query: { redirect: to.fullPath } }) } else if (requiresAdmin && userRole !== 'ADMIN') { - next({ name: 'unauthorized' }); + next({ name: 'unauthorized' }) } else if (requiresPremium && userSubscription !== 'PREMIUM') { - next({ name: 'home' }); + next({ name: 'home' }) } else { - next(); + next() } -}); +}) -export default router; \ No newline at end of file +export default router diff --git a/src/stores/BudgetStore.ts b/src/stores/BudgetStore.ts index 60fce67358d1bfdcaf9cac337e59bd8f32296f12..bd8ed549bee00fc32607db5156ded02ed38bb829 100644 --- a/src/stores/BudgetStore.ts +++ b/src/stores/BudgetStore.ts @@ -6,7 +6,7 @@ import { defineStore } from 'pinia' export const useBudgetStore = defineStore('BudgetStore', { state: () => ({ /** The ID of the active budget. */ - activeBudgetId: 0, + activeBudgetId: 0 }), actions: { /** @@ -30,4 +30,4 @@ export const useBudgetStore = defineStore('BudgetStore', { persist: { storage: sessionStorage } -}); +}) diff --git a/src/stores/ConfigurationStore.ts b/src/stores/ConfigurationStore.ts index e0fd57fe25b56583f75c895407209fcf6dd0417d..f5fb3304ee589db98b4e12d9784c3e6e3ef530f2 100644 --- a/src/stores/ConfigurationStore.ts +++ b/src/stores/ConfigurationStore.ts @@ -14,20 +14,20 @@ export const useConfigurationStore = defineStore('ConfigurationStore', { /** The user's experience. */ experience: '', /** The challenges the user is facing. */ - challenges: [] as Array<string>, + challenges: [] as Array<string> }), actions: { /** * Sets the Basic Bank Account Number of the cheking account. - * + * * @param {number} newValue - The new Basic Bank Account Number of the cheking account. */ setChekingAccountBBAN(newValue: number) { - this.chekingAccountBBAN = newValue; + this.chekingAccountBBAN = newValue }, /** * Sets the Basic Bank Account Number of the savings account. - * + * * @param {number} newValue - The new Basic Bank Account Number of the savings account. */ setSavingsAccountBBAN(newValue: number) { @@ -69,7 +69,7 @@ export const useConfigurationStore = defineStore('ConfigurationStore', { getters: { /** * Retrieves the Basic Bank Account Number of the cheking account. - * + * * @returns {number} The amount in the cheking account. */ getCheckingAccountBBAN(): number { @@ -77,7 +77,7 @@ export const useConfigurationStore = defineStore('ConfigurationStore', { }, /** * Retrieves the Basic Bank Account Number of the savings account. - * + * * @returns {number} The amount in the savings account. */ getSavingsAccountBBAN(): number { @@ -107,6 +107,5 @@ export const useConfigurationStore = defineStore('ConfigurationStore', { getChallenges(): Array<string> { return this.challenges } - }, - -}); + } +}) diff --git a/src/stores/ErrorStore.ts b/src/stores/ErrorStore.ts index f67ec9e9d978ec89ba53eab3750227e82307c91f..5ea3b01f2faeacd15e65896ee343adb6b731b261 100644 --- a/src/stores/ErrorStore.ts +++ b/src/stores/ErrorStore.ts @@ -1,4 +1,4 @@ -import { defineStore } from 'pinia'; +import { defineStore } from 'pinia' /** * Representing the store for managing error-related state. @@ -6,7 +6,7 @@ import { defineStore } from 'pinia'; export const useErrorStore = defineStore('ErrorStore', { state: () => ({ /** Array containing multiple error messages. */ - errors: [] as string[], + errors: [] as string[] }), actions: { /** @@ -16,17 +16,17 @@ export const useErrorStore = defineStore('ErrorStore', { * @param {string} error - The error message to add. */ addError(error: string) { - console.log(error); - this.errors = [error]; + console.log(error) + this.errors = [error] }, /** * Removes the first error from the error array. */ removeCurrentError() { if (this.errors.length > 0) { - this.errors.shift(); + this.errors.shift() } - }, + } }, getters: { /** @@ -36,9 +36,9 @@ export const useErrorStore = defineStore('ErrorStore', { */ getFirstError(): string { if (this.errors.length > 0) { - return `Exceptions.${this.errors[0]}`; + return `Exceptions.${this.errors[0]}` } - return ''; + return '' }, /** * Retrieves the last error message in the error array. @@ -47,9 +47,9 @@ export const useErrorStore = defineStore('ErrorStore', { */ getLastError(): string { if (this.errors.length > 0) { - return `Exceptions.${this.errors[this.errors.length - 1]}`; + return `Exceptions.${this.errors[this.errors.length - 1]}` } - return ''; - }, - }, -}); \ No newline at end of file + return '' + } + } +}) diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index 9b0c51eaf2fa0ab30f87b6f495f0a14434582ca8..cf0b7cfbed1f7b5f47f267d540df991481532c49 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -1,51 +1,51 @@ -import { OpenAPI } from '@/api'; -import Cookies from 'js-cookie'; -import { defineStore } from 'pinia'; +import { OpenAPI } from '@/api' +import Cookies from 'js-cookie' +import { defineStore } from 'pinia' /** * Custom storage implementation for storing state in cookies. */ const cookiesStorage: Storage = { setItem(key, state) { - return Cookies.set(key, state, { expires: 3 }); + return Cookies.set(key, state, { expires: 3 }) }, getItem(key) { - const store = Cookies.get(key); + const store = Cookies.get(key) if (store === undefined) { - OpenAPI.TOKEN = ''; - return ''; + OpenAPI.TOKEN = '' + return '' } - OpenAPI.TOKEN = JSON.parse(Cookies.get(key) || '').accessToken; - return Cookies.get(key) || ''; + OpenAPI.TOKEN = JSON.parse(Cookies.get(key) || '').accessToken + return Cookies.get(key) || '' }, length: 0, clear: function (): void { - Cookies.remove('userInfo'); + Cookies.remove('userInfo') }, key: function (index: number): string | null { - throw new Error('Function not implemented.'); + throw new Error('Function not implemented.') }, removeItem: function (key: string): void { - throw new Error('Function not implemented.'); - }, -}; + throw new Error('Function not implemented.') + } +} /** * Interface representing user store information. */ export type UserStoreInfo = { - id?: number; - email?: string; - firstname?: string; - lastname?: string; - password?: string; - accessToken?: string; - role?: string; - subscriptionLevel?: string; - roadBackground?: number; - profileImage?: number; -}; + id?: number + email?: string + firstname?: string + lastname?: string + password?: string + accessToken?: string + role?: string + subscriptionLevel?: string + roadBackground?: number + profileImage?: number +} export const useUserInfoStore = defineStore('UserInfoStore', { state: () => ({ @@ -68,10 +68,10 @@ export const useUserInfoStore = defineStore('UserInfoStore', { /** User road background. */ roadBackground: 0, /** User profile image. */ - profileImage: 0, + profileImage: 0 }), persist: { - storage: cookiesStorage, + storage: cookiesStorage }, actions: { /** @@ -94,32 +94,32 @@ export const useUserInfoStore = defineStore('UserInfoStore', { * @param {UserStoreInfo} userinfo - The user information to set. */ setUserInfo(userinfo: UserStoreInfo) { - userinfo.id && (this.$state.id = userinfo.id); - userinfo.email && (this.$state.email = userinfo.email); - userinfo.firstname && (this.$state.firstname = userinfo.firstname); - userinfo.lastname && (this.$state.lastname = userinfo.lastname); - userinfo.accessToken && (this.$state.accessToken = userinfo.accessToken); - userinfo.accessToken && (OpenAPI.TOKEN = this.$state.accessToken); - userinfo.role && (this.$state.role = userinfo.role); - userinfo.subscriptionLevel && (this.$state.subscriptionLevel = userinfo.subscriptionLevel); - userinfo.roadBackground && (this.$state.roadBackground = userinfo.roadBackground); - userinfo.profileImage && (this.$state.profileImage = userinfo.profileImage); + userinfo.id && (this.$state.id = userinfo.id) + userinfo.email && (this.$state.email = userinfo.email) + userinfo.firstname && (this.$state.firstname = userinfo.firstname) + userinfo.lastname && (this.$state.lastname = userinfo.lastname) + userinfo.accessToken && (this.$state.accessToken = userinfo.accessToken) + userinfo.accessToken && (OpenAPI.TOKEN = this.$state.accessToken) + userinfo.role && (this.$state.role = userinfo.role) + userinfo.subscriptionLevel && (this.$state.subscriptionLevel = userinfo.subscriptionLevel) + userinfo.roadBackground && (this.$state.roadBackground = userinfo.roadBackground) + userinfo.profileImage && (this.$state.profileImage = userinfo.profileImage) }, /** * Clears the user information. */ clearUserInfo() { - this.$state.id = 0; - this.$state.email = ''; - this.$state.firstname = ''; - this.$state.lastname = ''; - this.$state.accessToken = ''; - this.$state.role = ''; - this.$state.subscriptionLevel = ''; - this.$state.roadBackground = 0; - this.$state.profileImage = 0; - OpenAPI.TOKEN = undefined; - }, + this.$state.id = 0 + this.$state.email = '' + this.$state.firstname = '' + this.$state.lastname = '' + this.$state.accessToken = '' + this.$state.role = '' + this.$state.subscriptionLevel = '' + this.$state.roadBackground = 0 + this.$state.profileImage = 0 + OpenAPI.TOKEN = undefined + } }, getters: { /** @@ -160,7 +160,7 @@ export const useUserInfoStore = defineStore('UserInfoStore', { * @returns {boolean} A boolean indicating if the user is logged in. */ isLoggedIn(): boolean { - return this.accessToken !== ''; + return this.accessToken !== '' }, /** * Checks if the user has a premium subscription. @@ -168,7 +168,7 @@ export const useUserInfoStore = defineStore('UserInfoStore', { * @returns {boolean} A boolean indicating if the user has a premium subscription. */ isPremium(): boolean { - return this.subscriptionLevel === 'PREMIUM'; + return this.subscriptionLevel === 'PREMIUM' }, /** * Checks if the user has an ad-free subscription. @@ -176,7 +176,7 @@ export const useUserInfoStore = defineStore('UserInfoStore', { * @returns {boolean} A boolean indicating if the user has an ad-free subscription. */ isNoAds(): boolean { - return this.subscriptionLevel === 'NO_ADS'; + return this.subscriptionLevel === 'NO_ADS' } - }, -}); \ No newline at end of file + } +}) diff --git a/src/views/Admin/AdminDashboardView.vue b/src/views/Admin/AdminDashboardView.vue index 32398130be99dcff38e8c13d264b1cec6aa8c8e1..6b1f2cae212c4d437eca2e09b7dc7abd47c538cb 100644 --- a/src/views/Admin/AdminDashboardView.vue +++ b/src/views/Admin/AdminDashboardView.vue @@ -1,11 +1,9 @@ <script setup lang="ts"> -import AddminFeedback from "@/components/Admin/AdminFeedback.vue"; +import AddminFeedback from '@/components/Admin/AdminFeedback.vue' </script> <template> <AddminFeedback></AddminFeedback> </template> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/views/Authentication/ChangePasswordView.vue b/src/views/Authentication/ChangePasswordView.vue index 53414e404c66a69ff7662b9fa187492ee613f151..a191d2c6b2559df989178d49a42b0c7edbf43b78 100644 --- a/src/views/Authentication/ChangePasswordView.vue +++ b/src/views/Authentication/ChangePasswordView.vue @@ -1,7 +1,7 @@ <template> - <ChangePassword/> + <ChangePassword /> </template> <script setup lang="ts"> import ChangePassword from '@/components/Login/ChangePassword.vue' -</script> \ No newline at end of file +</script> diff --git a/src/views/Authentication/ForgottenPasswordView.vue b/src/views/Authentication/ForgottenPasswordView.vue index ef2cd9f18081b53d8943d238cb8ad6a827f27d1d..c5b73592939b8037cbfe12e3e9419992b31ed6a0 100644 --- a/src/views/Authentication/ForgottenPasswordView.vue +++ b/src/views/Authentication/ForgottenPasswordView.vue @@ -1,7 +1,7 @@ <template> - <ForgottenPassword/> + <ForgottenPassword /> </template> <script setup lang="ts"> import ForgottenPassword from '@/components/Login/ForgottenPassword.vue' -</script> \ No newline at end of file +</script> diff --git a/src/views/Authentication/LoginView.vue b/src/views/Authentication/LoginView.vue index 8e98a1cec4e1285528b46c7b5aa96604d45ef030..cd53bd7da329b532e402490230f2f4c138576101 100644 --- a/src/views/Authentication/LoginView.vue +++ b/src/views/Authentication/LoginView.vue @@ -1,7 +1,7 @@ <template> - <Login/> + <Login /> </template> <script setup lang="ts"> import Login from '@/components/Login/LoginParent.vue' -</script> \ No newline at end of file +</script> diff --git a/src/views/Authentication/SignUpView.vue b/src/views/Authentication/SignUpView.vue index 7fdad6614a10dccd5b45a80c1786cfa928cd89f0..cd9b08a63c8abe30aaaf29a588b05d17213cd6b7 100644 --- a/src/views/Authentication/SignUpView.vue +++ b/src/views/Authentication/SignUpView.vue @@ -1,7 +1,7 @@ <template> - <SignUp/> + <SignUp /> </template> <script setup lang="ts"> import SignUp from '@/components/SignUp/SignUp.vue' -</script> \ No newline at end of file +</script> diff --git a/src/views/BankID/RedirectView.vue b/src/views/BankID/RedirectView.vue index fc3691d6d6edf4e43e19a5d32a6b3ee4ade6683d..59003e9de8131754a033006c3ac96201e000653c 100644 --- a/src/views/BankID/RedirectView.vue +++ b/src/views/BankID/RedirectView.vue @@ -5,7 +5,7 @@ import { useUserInfoStore } from '@/stores/UserStore' import axios from 'axios' import router from '@/router' -let apiUrl = import.meta.env.VITE_APP_API_URL; +let apiUrl = import.meta.env.VITE_APP_API_URL /** * Retrieves the authorization code and state from the URL parameters, @@ -14,16 +14,16 @@ let apiUrl = import.meta.env.VITE_APP_API_URL; */ onMounted(() => { // Extract query parameters from the URL - const query = new URLSearchParams(window.location.search); - const code = query.get('code'); - const state = query.get('state'); + const query = new URLSearchParams(window.location.search) + const code = query.get('code') + const state = query.get('state') if (code && state) { - exchangeCodeForToken(code, state); + exchangeCodeForToken(code, state) } else { - console.error("Authorization code or state missing."); + console.error('Authorization code or state missing.') } -}); +}) /** * Exchanges an authorization code and state for an authentication token. @@ -33,20 +33,21 @@ onMounted(() => { * @param {string} state - The state parameter received from the OAuth2 authorization server. */ async function exchangeCodeForToken(code: string, state: string) { - axios.post<AuthenticationResponse>(apiUrl + '/api/auth/bank-id', { code: code, state: state }) - .then(response => { - OpenAPI.TOKEN = response.data.token; + axios + .post<AuthenticationResponse>(apiUrl + '/api/auth/bank-id', { code: code, state: state }) + .then((response) => { + OpenAPI.TOKEN = response.data.token useUserInfoStore().setUserInfo({ accessToken: response.data.token, role: response.data.role, firstname: response.data.firstName, - lastname: response.data.lastName, - }); - router.push({ name: 'home' }); + lastname: response.data.lastName + }) + router.push({ name: 'home' }) + }) + .catch((error) => { + console.error('Authentication error:', error) + router.push({ name: 'login' }) }) - .catch(error => { - console.error("Authentication error:", error); - router.push({ name: 'login' }); - }); } -</script> \ No newline at end of file +</script> diff --git a/src/views/BasePageView.vue b/src/views/BasePageView.vue index 09d24858a937e2d469bc8c6204bb4a5973bb1d5e..a988ed7171deb89f6a2ae205235639b80f886e50 100644 --- a/src/views/BasePageView.vue +++ b/src/views/BasePageView.vue @@ -2,14 +2,14 @@ import { RouterView } from 'vue-router' import Footer from '@/components/BaseComponents/BaseFooter.vue' import Menu from '@/components/BaseComponents/NavBar.vue' -import { useUserInfoStore } from '@/stores/UserStore'; +import { useUserInfoStore } from '@/stores/UserStore' </script> <template> - <Menu data-cy="menu"></Menu> - <div id="minHeight"> - <RouterView /> - </div> + <Menu data-cy="menu"></Menu> + <div id="minHeight"> + <RouterView /> + </div> <Footer></Footer> </template> @@ -24,4 +24,4 @@ import { useUserInfoStore } from '@/stores/UserStore'; margin: 0 20px; } } -</style> \ No newline at end of file +</style> diff --git a/src/views/Budget/BudgetOverview.vue b/src/views/Budget/BudgetOverview.vue index f4e058c745a0bb1de9697a0cb7b0c952a1d409aa..5ea88ee06fa7568b5edcd58fe51b6e519afcabc2 100644 --- a/src/views/Budget/BudgetOverview.vue +++ b/src/views/Budget/BudgetOverview.vue @@ -7,14 +7,14 @@ import { useBudgetStore } from '@/stores/BudgetStore' import { type BudgetRequestDTO, type BudgetResponseDTO, BudgetService } from '@/api' import { useRouter } from 'vue-router' -const router = useRouter(); +const router = useRouter() // Reactive list of budget responses -const budgetList = ref<BudgetResponseDTO[]>([]); +const budgetList = ref<BudgetResponseDTO[]>([]) // Reactive variables for input value, error message, modal, and budgetList key let budgetNameInput = ref('') -let errorMsg = ref(''); -let budgetListKey = ref(0); +let errorMsg = ref('') +let budgetListKey = ref(0) /** * Attempts to retrieve budgets for the user asynchronously and updates @@ -26,14 +26,14 @@ onMounted(async () => { budgetList.value = await BudgetService.getBudgetsByUser() console.log(budgetList.value) } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } }) /** * Creates a new budget to the database and updates the displayed budget list. */ -const createNewBudget = async() => { +const createNewBudget = async () => { try { // Prepare request body for creating budget const request: BudgetRequestDTO = { @@ -42,11 +42,11 @@ const createNewBudget = async() => { expenseAmount: 0 } // Creates new budget with the budget request body - await BudgetService.createBudget({requestBody: request}) + await BudgetService.createBudget({ requestBody: request }) // Updates displayed budget list after creation await updateBudgetList() } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } @@ -64,25 +64,45 @@ const updateBudgetList = async () => { * @param {number} id The ID of the budget to navigate to. */ const goToBudget = (id: number) => { - useBudgetStore().setActiveBudgetId(id); - router.push("/budget") + useBudgetStore().setActiveBudgetId(id) + router.push('/budget') } </script> <template> <div class="container"> <h1 class="text-center">Dine Budsjetter</h1> - <BaseButton id="createBudgetButton" button-text="Opprett nytt budsjett" class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample"/> + <BaseButton + id="createBudgetButton" + button-text="Opprett nytt budsjett" + class="btn btn-primary" + type="button" + data-bs-toggle="collapse" + data-bs-target="#collapseExample" + aria-expanded="false" + aria-controls="collapseExample" + /> <div class="collapse" id="collapseExample"> <div class="container collapse-container"> <div class="input-group"> - <input id="collapseInput" class="form-control" type="text" placeholder="Skriv inn navn på budsjettet" v-model="budgetNameInput"> - <BaseButton id="collapseButton" button-text="Opprett" data-bs-dismiss="modal" @click="createNewBudget"/> + <input + id="collapseInput" + class="form-control" + type="text" + placeholder="Skriv inn navn på budsjettet" + v-model="budgetNameInput" + /> + <BaseButton + id="collapseButton" + button-text="Opprett" + data-bs-dismiss="modal" + @click="createNewBudget" + /> </div> </div> </div> <p class="text-danger">{{ errorMsg }}</p> - <hr> + <hr /> <h5 v-if="budgetList.length === 0" class="text-center">Du har ingen budsjetter</h5> <ul v-else class="budgetContainer" :key="budgetListKey"> <li v-for="(item, index) in budgetList"> @@ -98,11 +118,9 @@ const goToBudget = (id: number) => { ></budget-box> </li> </ul> - </div> </template> - <style scoped> .collapse-container { align-content: center; @@ -122,4 +140,4 @@ const goToBudget = (id: number) => { ul > li { margin: 10px 0; } -</style> \ No newline at end of file +</style> diff --git a/src/views/Budget/BudgetView.vue b/src/views/Budget/BudgetView.vue index cdf5440dad6c225ef69a85d6cf130b74ee99c30b..f4492457c15661cae9f9070a291475c519da9543 100644 --- a/src/views/Budget/BudgetView.vue +++ b/src/views/Budget/BudgetView.vue @@ -4,20 +4,25 @@ import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' import ExpenseBox from '@/components/Budget/ExpenseBox.vue' import { useRouter } from 'vue-router' import { useBudgetStore } from '@/stores/BudgetStore' -import { type BudgetResponseDTO, BudgetService, type ExpenseRequestDTO, type ExpenseResponseDTO } from '@/api' +import { + type BudgetResponseDTO, + BudgetService, + type ExpenseRequestDTO, + type ExpenseResponseDTO +} from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import ConfirmDeleteModal from '@/components/Budget/Modal/ConfirmDeleteModal.vue' import ImportBudgetModal from '@/components/Budget/Modal/ImportBudgetModal.vue' -const router = useRouter(); +const router = useRouter() // Reactive header values -let title = ref(''); -let budget = ref(0); -let expenses = ref(0); -let balance = ref(0); +let title = ref('') +let budget = ref(0) +let expenses = ref(0) +let balance = ref(0) // Reactive error message and form value -let errorMsg = ref(''); +let errorMsg = ref('') let renameFormRef = ref(null) // Reactive expense list let expenseDTOList = ref<ExpenseResponseDTO[]>([]) @@ -38,14 +43,16 @@ let expenseAmount = ref<any>() */ onMounted(async () => { try { - await updateHeader(); - await updateExpenses(); - await updateBalance(); + await updateHeader() + await updateExpenses() + await updateBalance() // Gets budgets which can be imported - budgetDTOList.value = await BudgetService.getBudgetsByUser(); - budgetDTOList.value = budgetDTOList.value.filter(item => item.id !== useBudgetStore().getActiveBudgetId); + budgetDTOList.value = await BudgetService.getBudgetsByUser() + budgetDTOList.value = budgetDTOList.value.filter( + (item) => item.id !== useBudgetStore().getActiveBudgetId + ) } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } }) @@ -55,15 +62,17 @@ onMounted(async () => { * budget amount, and expense amount accordingly. */ const updateHeader = async () => { - const budgetResponse: BudgetResponseDTO = await BudgetService.getBudget({budgetId: useBudgetStore().getActiveBudgetId}); + const budgetResponse: BudgetResponseDTO = await BudgetService.getBudget({ + budgetId: useBudgetStore().getActiveBudgetId + }) if (budgetResponse.budgetName != null) { - title.value = budgetResponse.budgetName; + title.value = budgetResponse.budgetName } if (budgetResponse.budgetAmount != null) { - budget.value = budgetResponse.budgetAmount; + budget.value = budgetResponse.budgetAmount } if (budgetResponse.expenseAmount != null) { - expenses.value = budgetResponse.expenseAmount; + expenses.value = budgetResponse.expenseAmount } } @@ -72,11 +81,13 @@ const updateHeader = async () => { * Fetches the expenses associated with the active budget using the UserService. */ const updateExpenses = async () => { - expenseDTOList.value = await BudgetService.getExpenses({budgetId: useBudgetStore().getActiveBudgetId}); + expenseDTOList.value = await BudgetService.getExpenses({ + budgetId: useBudgetStore().getActiveBudgetId + }) // Resets expenses and then re-calculates it - expenses.value = 0; + expenses.value = 0 for (let expenseDTO of expenseDTOList.value) { - expenses.value += Number(expenseDTO.amount); + expenses.value += Number(expenseDTO.amount) } } @@ -87,9 +98,9 @@ const updateBalance = async () => { // Updates balance value and background balance.value = budget.value - expenses.value if (balance.value >= 0) { - iRef.value.style.backgroundColor = 'rgba(34, 231, 50, 0.43)'; - } else { - iRef.value.style.backgroundColor= 'rgba(232, 14, 14, 0.43)'; + iRef.value.style.backgroundColor = 'rgba(34, 231, 50, 0.43)' + } else { + iRef.value.style.backgroundColor = 'rgba(232, 14, 14, 0.43)' } } @@ -103,8 +114,8 @@ const updateBalance = async () => { */ const updateBudget = async (newBudget: number, newBudgetName: string) => { try { - budget.value = newBudget; - title.value = newBudgetName; + budget.value = newBudget + title.value = newBudgetName // Prepare request body for updating budget const request: BudgetResponseDTO = { budgetName: title.value, @@ -112,7 +123,10 @@ const updateBudget = async (newBudget: number, newBudgetName: string) => { expenseAmount: expenses.value } // Send request to update budget information - await BudgetService.updateBudget({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request}) + await BudgetService.updateBudget({ + budgetId: useBudgetStore().getActiveBudgetId, + requestBody: request + }) } catch (error) { errorMsg.value = handleUnknownError(error) } @@ -134,13 +148,16 @@ const addNewExpense = async (expenseDescription: string, expenseValue: number) = amount: expenseValue } // Send request to update expense information - await BudgetService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request}); + await BudgetService.updateExpense({ + budgetId: useBudgetStore().getActiveBudgetId, + requestBody: request + }) // Trigger updates of expenses and balance and budget - await updateExpenses(); + await updateExpenses() await updateBudget(budget.value, title.value) - await updateBalance(); + await updateBalance() } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } @@ -153,12 +170,12 @@ const addNewExpense = async (expenseDescription: string, expenseValue: number) = */ const deleteExpense = async (id: number) => { try { - await BudgetService.deleteExpense({expenseId: id}); - await updateExpenses(); + await BudgetService.deleteExpense({ expenseId: id }) + await updateExpenses() await updateBudget(budget.value, title.value) - await updateBalance(); + await updateBalance() } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } @@ -180,12 +197,15 @@ const editExpense = async (id: number, newDescription: string, newAmount: number amount: newAmount } // Send request to update the expense using the UserService - await BudgetService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request}); - await updateExpenses(); + await BudgetService.updateExpense({ + budgetId: useBudgetStore().getActiveBudgetId, + requestBody: request + }) + await updateExpenses() await updateBudget(budget.value, title.value) - await updateBalance(); + await updateBalance() } catch (error) { - errorMsg.value = handleUnknownError(error); + errorMsg.value = handleUnknownError(error) } } @@ -197,23 +217,26 @@ const editExpense = async (id: number, newDescription: string, newAmount: number const importBudget = async (budgetId: number) => { try { // Update current budget value from the imported budget - const budgetResponse: BudgetResponseDTO = await BudgetService.getBudget({budgetId: budgetId}); + const budgetResponse: BudgetResponseDTO = await BudgetService.getBudget({ budgetId: budgetId }) if (budgetResponse.budgetAmount != null) { - budget.value += budgetResponse.budgetAmount; + budget.value += budgetResponse.budgetAmount } // Get all the expenses from imported budget, and copy them to current budget - const expenses: ExpenseResponseDTO[] = await BudgetService.getExpenses({budgetId: budgetId}) + const expenses: ExpenseResponseDTO[] = await BudgetService.getExpenses({ budgetId: budgetId }) for (let expense of expenses) { const expenseRequest: ExpenseRequestDTO = { description: expense.description, amount: Number(expense.amount) || 0 } - await BudgetService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: expenseRequest}); + await BudgetService.updateExpense({ + budgetId: useBudgetStore().getActiveBudgetId, + requestBody: expenseRequest + }) } // Update display and budget - await updateExpenses(); + await updateExpenses() await updateBudget(budget.value, title.value) - await updateBalance(); + await updateBalance() } catch (error) { errorMsg.value = handleUnknownError(error) } @@ -225,8 +248,13 @@ const importBudget = async (budgetId: number) => { <h1 class="text-center">{{ title }}</h1> <div class="button-container"> - <BaseButton id="goBack" @click="router.push('/budget-overview')" button-text="Gå tilbake"/> - <BaseButton id="optionButton" button-text="Alternativer" data-bs-toggle="modal" data-bs-target="#modal"/> + <BaseButton id="goBack" @click="router.push('/budget-overview')" button-text="Gå tilbake" /> + <BaseButton + id="optionButton" + button-text="Alternativer" + data-bs-toggle="modal" + data-bs-target="#modal" + /> </div> <p class="text-danger">{{ errorMsg }}</p> @@ -239,75 +267,153 @@ const importBudget = async (budgetId: number) => { <button class="btn btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> - <button id="importButton" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#import-modal"><img src="../../assets/icons/import.svg" height="20" width="20" alt="bilde">Importer budsjett</button> - <button id="editBudget" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#editBudgetCollapse" aria-expanded="false" aria-controls="editBudgetCollapse"><img src="../../assets/icons/edit-button.svg" alt="redigerKnapp">Endre navn på budsjett</button> + <button + id="importButton" + class="btn btn-primary" + data-bs-toggle="modal" + data-bs-target="#import-modal" + > + <img src="../../assets/icons/import.svg" height="20" width="20" alt="bilde" />Importer + budsjett + </button> + <button + id="editBudget" + class="btn btn-primary" + data-bs-toggle="collapse" + data-bs-target="#editBudgetCollapse" + aria-expanded="false" + aria-controls="editBudgetCollapse" + > + <img src="../../assets/icons/edit-button.svg" alt="redigerKnapp" />Endre navn på + budsjett + </button> <div class="collapse" id="editBudgetCollapse"> <div class="container collapse-container"> <form ref="renameFormRef" @submit.prevent="updateBudget(budget, budgetTitle)"> <div class="input-group"> - <input id="collapseInput" class="col-5 form-control" type="text" required minlength="1" placeholder="Skriv inn nytt navn på budsjettet" v-model="budgetTitle"> - <BaseButton id="collapseButton" type="submit" button-text="Bekreft" data-bs-dismiss="modal"/> + <input + id="collapseInput" + class="col-5 form-control" + type="text" + required + minlength="1" + placeholder="Skriv inn nytt navn på budsjettet" + v-model="budgetTitle" + /> + <BaseButton + id="collapseButton" + type="submit" + button-text="Bekreft" + data-bs-dismiss="modal" + /> </div> </form> </div> </div> - <button id="deleteButton" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirm-modal"><img src="../../assets/icons/trash-can.svg" height="20" width="20" alt="bilde">Slett budsjett</button> + <button + id="deleteButton" + class="btn btn-danger" + data-bs-toggle="modal" + data-bs-target="#confirm-modal" + > + <img src="../../assets/icons/trash-can.svg" height="20" width="20" alt="bilde" />Slett + budsjett + </button> </div> </div> </div> </div> - <confirm-delete-modal :budget-id="useBudgetStore().getActiveBudgetId" - modal-id="confirm-modal" - :budgetTitle="title" - @deletedEvent="router.push('/budget-overview')"/> + <confirm-delete-modal + :budget-id="useBudgetStore().getActiveBudgetId" + modal-id="confirm-modal" + :budgetTitle="title" + @deletedEvent="router.push('/budget-overview')" + /> - <import-budget-modal modal-id="import-modal" - :listOfBudgetResponseDTO="budgetDTOList" - @importBudgetEvent="importBudget"/> + <import-budget-modal + modal-id="import-modal" + :listOfBudgetResponseDTO="budgetDTOList" + @importBudgetEvent="importBudget" + /> <div class="budget-info-container"> <div class="info budget-container"> - <i><img src="../../assets/icons/money2.svg" width="48px" height="48px" alt="bilde"></i> + <i><img src="../../assets/icons/money2.svg" width="48px" height="48px" alt="bilde" /></i> <div class="budget-text-container"> - <h5>{{budget}} kr</h5> + <h5>{{ budget }} kr</h5> <p>Budsjett</p> </div> </div> <div class="info expenses-container"> - <i><img src="../../assets/icons/credit-card.svg" width="48px" height="48px" alt="bilde"></i> + <i + ><img src="../../assets/icons/credit-card.svg" width="48px" height="48px" alt="bilde" + /></i> <div class="expenses-text-container"> - <h5>{{expenses}} kr</h5> + <h5>{{ expenses }} kr</h5> <p>Utgifter</p> </div> </div> <div class="info balance-container"> - <i ref="iRef"><img src="../../assets/icons/scale.svg" width="48px" height="48px" alt="bilde"></i> + <i ref="iRef" + ><img src="../../assets/icons/scale.svg" width="48px" height="48px" alt="bilde" + /></i> <div class="balance-text-container"> - <h5>{{balance}} kr</h5> + <h5>{{ balance }} kr</h5> <p>Balanse</p> </div> </div> </div> - <div class="budget-content-container"> <form class="budget-from" @submit.prevent="updateBudget(budgetValue, title)"> <div class="input-group"> <span class="input-group-text">Ditt budsjett </span> - <input type="text" class="form-control" placeholder="Skriv inn ditt budsjett" required v-model="budgetValue"> - <BaseButton id="calculate-budget" type="submit" class="btn" button-text="Beregn"></BaseButton> + <input + type="text" + class="form-control" + placeholder="Skriv inn ditt budsjett" + required + v-model="budgetValue" + /> + <BaseButton + id="calculate-budget" + type="submit" + class="btn" + button-text="Beregn" + ></BaseButton> </div> </form> - <form class="expenses-form" @submit.prevent="addNewExpense(expenseDescription, expenseAmount)"> + <form + class="expenses-form" + @submit.prevent="addNewExpense(expenseDescription, expenseAmount)" + > <div class="input-group"> <span class="input-group-text">Legg til ny utgift </span> - <input type="text" class="form-control" placeholder="Navn på utgift" required v-model="expenseDescription"> - <input type="number" min="0" class="form-control" placeholder="Beløp (kr)" required v-model="expenseAmount"> - <BaseButton id="calculate-expense" type="submit" class="btn" button-text="Beregn"></BaseButton> + <input + type="text" + class="form-control" + placeholder="Navn på utgift" + required + v-model="expenseDescription" + /> + <input + type="number" + min="0" + class="form-control" + placeholder="Beløp (kr)" + required + v-model="expenseAmount" + /> + <BaseButton + id="calculate-expense" + type="submit" + class="btn" + button-text="Beregn" + ></BaseButton> </div> </form> </div> @@ -315,24 +421,23 @@ const importBudget = async (budgetId: number) => { <div v-if="expenseDTOList.length != 0" class="expenses-details-container"> <h3>Utgiftsdetaljer</h3> <div class="expense-box-container"> - <expense-box v-for="(expenseDTO, index) in expenseDTOList" - :id="Number(expenseDTO.expenseId) || 0" - :key="index" - :index="index" - :description="expenseDTO.description" - :amount="Number(expenseDTO.amount) || 0" - @deleteEvent="deleteExpense" - @editEvent="editExpense"/> + <expense-box + v-for="(expenseDTO, index) in expenseDTOList" + :id="Number(expenseDTO.expenseId) || 0" + :key="index" + :index="index" + :description="expenseDTO.description" + :amount="Number(expenseDTO.amount) || 0" + @deleteEvent="deleteExpense" + @editEvent="editExpense" + /> </div> </div> <h5 v-else class="text-center">Du har ingen utgifter</h5> - </div> </template> - <style scoped> - .button-container { display: flex; gap: 10px; @@ -349,7 +454,7 @@ const importBudget = async (budgetId: number) => { .modal-body { display: grid; - gap: 10px + gap: 10px; } div.budget-info-container { @@ -404,7 +509,6 @@ div.info:hover { align-items: center; } - .expenses-details-container { margin: 1rem 0; min-height: 80px; @@ -446,4 +550,4 @@ div.info:hover { min-width: 100%; } } -</style> \ No newline at end of file +</style> diff --git a/src/views/Configuration/ConfigurationView.vue b/src/views/Configuration/ConfigurationView.vue index 426224bfc413d2c59ac8443f701771ed77889022..e6481d04636b5cef7125213654b09d39fd5073ee 100644 --- a/src/views/Configuration/ConfigurationView.vue +++ b/src/views/Configuration/ConfigurationView.vue @@ -3,5 +3,5 @@ import Configuration from '@/components/Configuration/ConfigurationParent.vue' </script> <template> - <Configuration/> -</template> \ No newline at end of file + <Configuration /> +</template> diff --git a/src/views/Exception/NotFoundView.vue b/src/views/Exception/NotFoundView.vue index d86aab5d9cc9a72056091020e461f416b11639f6..06461f32513838c082643931427e80e5afb4d2f7 100644 --- a/src/views/Exception/NotFoundView.vue +++ b/src/views/Exception/NotFoundView.vue @@ -1,7 +1,7 @@ <template> - <NotFoundPage/> + <NotFoundPage /> </template> <script setup lang="ts"> - import NotFoundPage from '@/components/Exceptions/NotFoundPage.vue'; -</script> \ No newline at end of file +import NotFoundPage from '@/components/Exceptions/NotFoundPage.vue' +</script> diff --git a/src/views/Exception/UnauthorizedView.vue b/src/views/Exception/UnauthorizedView.vue index 412c8d2319480ed0c12661441cd85db78f8e8f75..a8c2248adda1f8a2b1facb5d08b9e7e9400dfccd 100644 --- a/src/views/Exception/UnauthorizedView.vue +++ b/src/views/Exception/UnauthorizedView.vue @@ -1,7 +1,7 @@ <template> - <UnauthorizedPage/> + <UnauthorizedPage /> </template> <script setup lang="ts"> - import UnauthorizedPage from '@/components/Exceptions/UnauthorizedPage.vue'; -</script> \ No newline at end of file +import UnauthorizedPage from '@/components/Exceptions/UnauthorizedPage.vue' +</script> diff --git a/src/views/Leaderboard/LeaderboardView.vue b/src/views/Leaderboard/LeaderboardView.vue index a57d059cbe3584e5192bc907c0cf2ed614331782..90ee5a5f68bc5508e442e5b378600d818d1631b3 100644 --- a/src/views/Leaderboard/LeaderboardView.vue +++ b/src/views/Leaderboard/LeaderboardView.vue @@ -1,7 +1,7 @@ <template> - <ItemShop/> + <ItemShop /> </template> <script setup lang="ts"> - import ItemShop from '@/components/Leaderboard/LeaderboardRank.vue'; -</script> \ No newline at end of file +import ItemShop from '@/components/Leaderboard/LeaderboardRank.vue' +</script> diff --git a/src/views/News/NewsView.vue b/src/views/News/NewsView.vue index 2c309f722ba054b6b188401b997631ab6673bb94..15667c448a13cfc78562b992078c2175ff58ff12 100644 --- a/src/views/News/NewsView.vue +++ b/src/views/News/NewsView.vue @@ -1,8 +1,7 @@ <script setup lang="ts"> -import NewsComponent from "@/components/News/NewsFeed.vue"; +import NewsComponent from '@/components/News/NewsFeed.vue' </script> - <template> <NewsComponent></NewsComponent> -</template> \ No newline at end of file +</template> diff --git a/src/views/SavingGoal/RoadmapView.vue b/src/views/SavingGoal/RoadmapView.vue index 67921dc5416487704834d0f84c5cbe07cbf0eebd..68e933d891e6a2f8dd986bb2153c75dbe7439c7f 100644 --- a/src/views/SavingGoal/RoadmapView.vue +++ b/src/views/SavingGoal/RoadmapView.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> -import SavingGoal from "@/components/SavingGoal/SavingGoal.vue"; +import SavingGoal from '@/components/SavingGoal/SavingGoal.vue' </script> <template> -<saving-goal></saving-goal> -</template> \ No newline at end of file + <saving-goal></saving-goal> +</template> diff --git a/src/views/Shop/ShopView.vue b/src/views/Shop/ShopView.vue index 667f6802ec2e019af1144e975e9924c542dd3c61..9d8f3e29447772d0d4d02290b23336a956257fa1 100644 --- a/src/views/Shop/ShopView.vue +++ b/src/views/Shop/ShopView.vue @@ -1,7 +1,7 @@ <template> - <ItemShop/> + <ItemShop /> </template> <script setup lang="ts"> - import ItemShop from '@/components/Shop/ItemShop.vue'; -</script> \ No newline at end of file +import ItemShop from '@/components/Shop/ItemShop.vue' +</script> diff --git a/src/views/User/ExternalProfileView.vue b/src/views/User/ExternalProfileView.vue index 67951edd79a50af254b995e802c041708a7083b5..edece694ea3f94dee786e614c97c7dcdeabca00d 100644 --- a/src/views/User/ExternalProfileView.vue +++ b/src/views/User/ExternalProfileView.vue @@ -1,12 +1,9 @@ <script setup lang="ts"> - -import UserProfileForeignLayout from "@/components/UserProfile/ExternalProfile.vue"; +import UserProfileForeignLayout from '@/components/UserProfile/ExternalProfile.vue' </script> <!-- The path to a foreign user is /{userId} || /profile/{userId}--> <template> -<UserProfileForeignLayout></UserProfileForeignLayout> + <UserProfileForeignLayout></UserProfileForeignLayout> </template> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/views/User/MyProfileView.vue b/src/views/User/MyProfileView.vue index f8ec753f18e393bf30db04d063c0b7026906dd02..f00313b7171d305c47173c75db718ae7cfbe0ab0 100644 --- a/src/views/User/MyProfileView.vue +++ b/src/views/User/MyProfileView.vue @@ -1,12 +1,9 @@ <script setup lang="ts"> - -import UserProfileLayout from "@/components/UserProfile/MyProfile.vue"; +import UserProfileLayout from '@/components/UserProfile/MyProfile.vue' </script> <template> -<UserProfileLayout></UserProfileLayout> + <UserProfileLayout></UserProfileLayout> </template> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/src/views/User/UserFeedbackView.vue b/src/views/User/UserFeedbackView.vue index 28a19b184a94fdede4f48198142ccf53da06e92d..75471cdc7c1b582cd77b4bcda69fbcd11796941f 100644 --- a/src/views/User/UserFeedbackView.vue +++ b/src/views/User/UserFeedbackView.vue @@ -1,41 +1,54 @@ <template> - <main> - <div class="wrapper"> + <main> + <div class="wrapper"> <div id="formFrame"> - <h1>Tilbakemelding</h1> - <form ref="formRef" id="loginForm" @submit.prevent="submitForm" novalidate> - <BaseInput :model-value="emailRef" - @input-change-event="handleEmailInputEvent" - id="emailInput" - input-id="email" - type="email" - label="E-post" - placeholder="Skriv inn din e-post" - invalid-message="Ugyldig e-post" - /> + <h1>Tilbakemelding</h1> + <form ref="formRef" id="loginForm" @submit.prevent="submitForm" novalidate> + <BaseInput + :model-value="emailRef" + @input-change-event="handleEmailInputEvent" + id="emailInput" + input-id="email" + type="email" + label="E-post" + placeholder="Skriv inn din e-post" + invalid-message="Ugyldig e-post" + /> - <br> - <label for="feedback">Din tilbakemelding:</label> - <textarea v-model="messageRef" placeholder="Skriv meldingen din her" rows="5" name="comment[text]" id="comment_text" cols="33" - required></textarea> - <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> - <BaseButton button-text="Send" @click="submitForm" style="padding: 10px 30px; font-size: 18px; font-weight: normal;">Send inn</BaseButton> - <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> - </form> + <br /> + <label for="feedback">Din tilbakemelding:</label> + <textarea + v-model="messageRef" + placeholder="Skriv meldingen din her" + rows="5" + name="comment[text]" + id="comment_text" + cols="33" + required + ></textarea> + <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p> + <BaseButton + button-text="Send" + @click="submitForm" + style="padding: 10px 30px; font-size: 18px; font-weight: normal" + >Send inn</BaseButton + > + <p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p> + </form> + </div> </div> - </div> - </main> - </template> + </main> +</template> <script setup lang="ts"> -import { ref } from 'vue'; -import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; -import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; +import { ref } from 'vue' +import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue' +import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue' import { type FeedbackRequestDTO, UserService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const emailRef = ref(""); -const messageRef = ref(""); +const emailRef = ref('') +const messageRef = ref('') const errorMsg = ref('') const confirmationMsg = ref('') @@ -48,25 +61,25 @@ const submitForm = async () => { const feedbackRequest: FeedbackRequestDTO = { email: emailRef.value, message: messageRef.value - }; - console.log("feedbackRequest", feedbackRequest); - UserService.sendFeedback({ requestBody: feedbackRequest }); + } + console.log('feedbackRequest', feedbackRequest) + UserService.sendFeedback({ requestBody: feedbackRequest }) messageRef.value = '' errorMsg.value = '' confirmationMsg.value = 'Tilbakemeldingen ble sendt!' } catch (err) { - errorMsg.value = handleUnknownError(err); + errorMsg.value = handleUnknownError(err) confirmationMsg.value = '' } -}; +} </script> <style scoped> main { - display: flex; - flex-direction: column; - align-items: center; - font-family: 'Poppins', sans-serif; + display: flex; + flex-direction: column; + align-items: center; + font-family: 'Poppins', sans-serif; } .wrapper { @@ -108,7 +121,7 @@ textarea { } textarea:focus { - background: none repeat scroll 0 0 #FFFFFF; + background: none repeat scroll 0 0 #ffffff; outline-width: 0; } -</style> \ No newline at end of file +</style> diff --git a/src/views/User/UserFriendsView.vue b/src/views/User/UserFriendsView.vue index 075b06aeae3091f5fa45091845882a2c6cd59225..754ab6604b45dcf45c7173431befcf133e875b90 100644 --- a/src/views/User/UserFriendsView.vue +++ b/src/views/User/UserFriendsView.vue @@ -1,7 +1,7 @@ <template> - <UserFriends/> + <UserFriends /> </template> <script setup lang="ts"> - import UserFriends from '@/components/Friends/UserFriends.vue'; -</script> \ No newline at end of file +import UserFriends from '@/components/Friends/UserFriends.vue' +</script> diff --git a/src/views/User/UserSettingsView.vue b/src/views/User/UserSettingsView.vue index 431cc2148d255946ef13f062283376d825f1f0ba..1c0afeaa5651a31e6b3e544e6a8fc0a95c247f4c 100644 --- a/src/views/User/UserSettingsView.vue +++ b/src/views/User/UserSettingsView.vue @@ -1,202 +1,332 @@ <script setup lang="ts"> import { ref } from 'vue' import { useRouter } from 'vue-router' -import {useRoute} from 'vue-router' +import { useRoute } from 'vue-router' -const router = useRouter(); +const router = useRouter() -const url = useRoute().path; +const url = useRoute().path -const activeLink = ref('/settings/profile'); // Default active link +const activeLink = ref('/settings/profile') // Default active link function setActive(link: string) { - activeLink.value = link; + activeLink.value = link } function toProfile() { - router.push('/settings/profile') + router.push('/settings/profile') } function toAccount() { - router.push('/settings/account') + router.push('/settings/account') } function toSecurity() { - router.push('/settings/security') + router.push('/settings/security') } function toNotification() { - router.push('/settings/notification') + router.push('/settings/notification') } function toBilling() { - router.push('/settings/bank') + router.push('/settings/bank') } - </script> <template> - <div class="container"> - - <div class="row gutters-sm"> - <div class="col-md-3 d-none d-md-block"> - <div class="card"> - <div class="card-body"> - <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': 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"> - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> - <circle cx="12" cy="7" r="4"></circle> - </svg> - Profil - </a> - - - <a @click.prevent="setActive('/settings/account')" @click="toAccount" - :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"> - <circle cx="12" cy="12" r="3"></circle> - <path - d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"> - </path> - </svg> - Konto - </a> - - - <a @click.prevent="setActive('/settings/security')" @click="toSecurity" - :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"> - <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> - </svg> - Sikkerhet - </a> - - <a> - <a @click.prevent="setActive('/settings/bank')" @click="toBilling" - :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"> - <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect> - <line x1="1" y1="10" x2="23" y2="10"></line> - </svg> - Bank - </a> - </a> - - </nav> - </div> - </div> - </div> - - - <div class="col-md-8"> - <div class="card"> - - - <!-- Tab panes --> - <div class="card-header border-bottom mb-3 d-flex d-md-none"> - <ul class="nav nav-tabs card-header-tabs nav-gap-x-1" role="tablist"> - <li class="nav-item"> - <a @click.prevent="setActive('/settings/profile')" @click="toProfile" - :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"> - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> - <circle cx="12" cy="7" r="4"></circle> - </svg></a> - </li> - <li class="nav-item"> - <a @click.prevent="setActive('/settings/account')" @click="toAccount" - :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"> - <circle cx="12" cy="12" r="3"></circle> - <path - d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"> - </path> - </svg></a> - </li> - <li class="nav-item"> - <a @click.prevent="setActive('/settings/security')" @click="toSecurity" - :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"> - <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> - </svg></a> - </li> - <li class="nav-item"> - <a @click.prevent="setActive('/settings/bank')" @click="toBilling" - :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"> - <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect> - <line x1="1" y1="10" x2="23" y2="10"></line> - </svg></a> - </li> - </ul> - </div> - - <div class="card-body tab-content"> - <RouterView></RouterView> - </div> - </div> - </div> + <div class="container"> + <div class="row gutters-sm"> + <div class="col-md-3 d-none d-md-block"> + <div class="card"> + <div class="card-body"> + <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': 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" + > + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> + <circle cx="12" cy="7" r="4"></circle> + </svg> + Profil + </a> + + <a + @click.prevent="setActive('/settings/account')" + @click="toAccount" + :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" + > + <circle cx="12" cy="12" r="3"></circle> + <path + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" + ></path> + </svg> + Konto + </a> + + <a + @click.prevent="setActive('/settings/security')" + @click="toSecurity" + :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" + > + <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> + </svg> + Sikkerhet + </a> + + <a> + <a + @click.prevent="setActive('/settings/bank')" + @click="toBilling" + :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" + > + <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect> + <line x1="1" y1="10" x2="23" y2="10"></line> + </svg> + Bank + </a> + </a> + </nav> + </div> + </div> + </div> + + <div class="col-md-8"> + <div class="card"> + <!-- Tab panes --> + <div class="card-header border-bottom mb-3 d-flex d-md-none"> + <ul class="nav nav-tabs card-header-tabs nav-gap-x-1" role="tablist"> + <li class="nav-item"> + <a + @click.prevent="setActive('/settings/profile')" + @click="toProfile" + :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" + > + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> + <circle cx="12" cy="7" r="4"></circle></svg + ></a> + </li> + <li class="nav-item"> + <a + @click.prevent="setActive('/settings/account')" + @click="toAccount" + :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" + > + <circle cx="12" cy="12" r="3"></circle> + <path + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" + ></path></svg + ></a> + </li> + <li class="nav-item"> + <a + @click.prevent="setActive('/settings/security')" + @click="toSecurity" + :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" + > + <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg + ></a> + </li> + <li class="nav-item"> + <a + @click.prevent="setActive('/settings/bank')" + @click="toBilling" + :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" + > + <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect> + <line x1="1" y1="10" x2="23" y2="10"></line></svg + ></a> + </li> + </ul> + </div> + + <div class="card-body tab-content"> + <RouterView></RouterView> + </div> </div> + </div> </div> - + </div> </template> - <style scoped> .container { - margin-top: 2rem; - margin-bottom: 4rem; + margin-top: 2rem; + margin-bottom: 4rem; } .nav-link { - color: #4a5568; + color: #4a5568; } .nav-link.active { - color: white; - background-color: #003A58; + color: white; + background-color: #003a58; } .card { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06); + box-shadow: + 0 1px 3px 0 rgba(0, 0, 0, 0.1), + 0 1px 2px 0 rgba(0, 0, 0, 0.06); } .card { - position: relative; - display: flex; - flex-direction: column; - min-width: 0; - word-wrap: break-word; - background-color: #fff; - background-clip: border-box; - border: 0 solid rgba(0, 0, 0, .125); - border-radius: .25rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 0 solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; } - .card-body { - flex: 1 1 auto; - min-height: 1px; - padding: 1rem; + flex: 1 1 auto; + min-height: 1px; + padding: 1rem; } .nav-pills { @@ -204,17 +334,17 @@ function toBilling() { } .gutters-sm { - margin-right: -8px; - margin-left: -8px; + margin-right: -8px; + margin-left: -8px; } -.gutters-sm>, -.gutters-sm> { - padding-right: 8px; - padding-left: 8px; +.gutters-sm >, +.gutters-sm > { + padding-right: 8px; + padding-left: 8px; } .mb-3 { - margin-bottom: 1rem !important; + margin-bottom: 1rem !important; } -</style> \ No newline at end of file +</style>