diff --git a/spec.json b/spec.json index 6dc6c6b15a1e598c69d554aa2b7cd9f12e07c8c3..6528f8560d283391d0035b987bd98a6f00dd5dd3 100644 --- a/spec.json +++ b/spec.json @@ -411,14 +411,6 @@ } ], "responses": { - "201": { - "description": "Item purchased and added to inventory successfully", - "content": { - "application/json": { - - } - } - }, "403": { "description": "Insufficient points to purchase the item", "content": { @@ -428,6 +420,14 @@ } } } + }, + "201": { + "description": "Item purchased and added to inventory successfully", + "content": { + "application/json": { + + } + } } } } @@ -814,22 +814,22 @@ "required": true }, "responses": { - "409": { - "description": "Email already exists", + "201": { + "description": "Successfully signed up", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ExceptionResponse" + "$ref": "#/components/schemas/AuthenticationResponse" } } } }, - "201": { - "description": "Successfully signed up", + "409": { + "description": "Email already exists", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AuthenticationResponse" + "$ref": "#/components/schemas/ExceptionResponse" } } } @@ -867,8 +867,8 @@ } } }, - "404": { - "description": "User not found", + "401": { + "description": "Invalid credentials", "content": { "application/json": { "schema": { @@ -877,8 +877,8 @@ } } }, - "401": { - "description": "Invalid credentials", + "404": { + "description": "User not found", "content": { "application/json": { "schema": { @@ -1020,6 +1020,37 @@ } } }, + "/api/goals/challenge/{id}": { + "patch": { + "tags": [ + "Goal" + ], + "operationId": "regenerateChallenge", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChallengeDTO" + } + } + } + } + } + } + }, "/redirect": { "get": { "tags": [ @@ -1546,13 +1577,49 @@ } }, "/api/item/inventory": { + "get": { + "tags": [ + "Item" + ], + "summary": "Get the active user's inventory items", + "description": "Retrieves a list of all items currently in the inventory of the active user.", + "operationId": "getInventory", + "responses": { + "200": { + "description": "List of inventory items fetched successfully", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InventoryDTO" + } + } + } + } + } + } + } + }, + "/api/item/inventory/{userId}": { "get": { "tags": [ "Item" ], "summary": "Get user inventory items", "description": "Retrieves a list of all items currently in the inventory of the user.", - "operationId": "getInventory", + "operationId": "getInventoryByUserId", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], "responses": { "200": { "description": "List of inventory items fetched successfully", @@ -1624,7 +1691,7 @@ "parameters": [ { "name": "id", - "in": "query", + "in": "path", "required": true, "schema": { "type": "integer", @@ -2020,6 +2087,31 @@ } }, "/api/badge/unlocked": { + "get": { + "tags": [ + "Badge" + ], + "summary": "Get the list of badges", + "description": "Get all badges unlocked by the user", + "operationId": "getBadgesUnlockedByActiveUser", + "responses": { + "200": { + "description": "Successfully got badges", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BadgeDTO" + } + } + } + } + } + } + } + }, + "/api/badge/unlocked/{userId}": { "get": { "tags": [ "Badge" @@ -2027,6 +2119,17 @@ "summary": "Get the list of badges", "description": "Get all badges unlocked by the user", "operationId": "getBadgesUnlockedByUser", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], "responses": { "200": { "description": "Successfully got badges", @@ -2051,7 +2154,7 @@ ], "summary": "Get the list of badges", "description": "Get all badges not unlocked by the user", - "operationId": "getBadgesNotUnlockedByUser", + "operationId": "getBadgesNotUnlockedByActiveUser", "responses": { "200": { "description": "Successfully got badges", @@ -2296,6 +2399,9 @@ "type": "integer", "format": "int64" }, + "templateName": { + "type": "string" + }, "text": { "type": "string" }, diff --git a/src/api/core/OpenAPI.ts b/src/api/core/OpenAPI.ts index b33daf25d0e3518d35dc91aac288e991b8a7c160..213c15d4a7b840e8d13a35f74c2618c13b7c9398 100644 --- a/src/api/core/OpenAPI.ts +++ b/src/api/core/OpenAPI.ts @@ -20,7 +20,7 @@ export type OpenAPIConfig = { }; export const OpenAPI: OpenAPIConfig = { - BASE: import.meta.env.VITE_APP_API_URL, + BASE: 'http://localhost:8080', VERSION: '3.0', WITH_CREDENTIALS: false, CREDENTIALS: 'include', diff --git a/src/api/models/ChallengeTemplateDTO.ts b/src/api/models/ChallengeTemplateDTO.ts index 323e64de48b8a201d3c4da78e5de83a904b0b0b1..286b0fc6fd32f7a9e313deceaf38f412f0e071ef 100644 --- a/src/api/models/ChallengeTemplateDTO.ts +++ b/src/api/models/ChallengeTemplateDTO.ts @@ -4,6 +4,7 @@ /* eslint-disable */ export type ChallengeTemplateDTO = { id?: number; + templateName?: string; text?: string; amount?: number; type?: ChallengeTemplateDTO.type; diff --git a/src/api/services/BadgeService.ts b/src/api/services/BadgeService.ts index f014b589ece802008c9e9da2669308001665c046..4c4bee2bdc45ba6160400b1d111085fe557cca77 100644 --- a/src/api/services/BadgeService.ts +++ b/src/api/services/BadgeService.ts @@ -59,19 +59,38 @@ export class BadgeService { * @returns BadgeDTO Successfully got badges * @throws ApiError */ - public static getBadgesUnlockedByUser(): CancelablePromise<Array<BadgeDTO>> { + 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 getBadgesNotUnlockedByUser(): CancelablePromise<Array<BadgeDTO>> { + public static getBadgesNotUnlockedByActiveUser(): CancelablePromise<Array<BadgeDTO>> { return __request(OpenAPI, { method: 'GET', url: '/api/badge/locked', diff --git a/src/api/services/GoalService.ts b/src/api/services/GoalService.ts index b3bfa72d953cab2252919fc7ab21566db845e51f..d679829c9813d1b022a7c5cca1431450d40a112c 100644 --- a/src/api/services/GoalService.ts +++ b/src/api/services/GoalService.ts @@ -2,6 +2,7 @@ /* 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'; @@ -78,6 +79,23 @@ export class GoalService { 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 @@ -90,7 +108,7 @@ export class GoalService { return __request(OpenAPI, { method: 'GET', url: '/api/goals/{id}', - query: { + path: { 'id': id, }, }); diff --git a/src/api/services/ItemService.ts b/src/api/services/ItemService.ts index ae2a3a8997ee79ac0fd22e289c153666a79b7396..6328a34f5b155f14aeb785ddc1f7e568271fcc0a 100644 --- a/src/api/services/ItemService.ts +++ b/src/api/services/ItemService.ts @@ -43,8 +43,8 @@ export class ItemService { }); } /** - * Get user inventory items - * Retrieves a list of all items currently in the inventory of the user. + * 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 */ @@ -54,4 +54,23 @@ export class ItemService { 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/components/SavingGoal/SavingGoal.vue b/src/components/SavingGoal/SavingGoal.vue index 54cba56fcae6f3c3b0068aaa03a78fd9e06914a6..0ae8f52b788036ee227a3446f0d9050561e938f6 100644 --- a/src/components/SavingGoal/SavingGoal.vue +++ b/src/components/SavingGoal/SavingGoal.vue @@ -60,6 +60,15 @@ export default { this.savingGoalClicked = true; this.keyForList++ }, 100); + }, + async refreshSpareSti() { + try { + this.selectedGoal = await GoalService.getGoal({id: this.selectedGoal.id as number}) + console.log("yessir") + this.key++; + } catch (error) { + console.log(error) + } } }, }; @@ -82,7 +91,7 @@ export default { </div> </div> <saving-goal-create @createGoalClicked="handleCreateGoalClicked" v-if="createClicked"></saving-goal-create> - <saving-goal-roadmap :key="key" :selected-goal="selectedGoal" v-else-if="savingGoalClicked"></saving-goal-roadmap> + <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> diff --git a/src/components/SavingGoal/SavingGoalRoadmap.vue b/src/components/SavingGoal/SavingGoalRoadmap.vue index fe1a241977253da06dda6547cf78a0b8d0b27077..c88ba1a6c69f41b0da02c87f59e3cc62ab9e9331 100644 --- a/src/components/SavingGoal/SavingGoalRoadmap.vue +++ b/src/components/SavingGoal/SavingGoalRoadmap.vue @@ -10,7 +10,7 @@ import { UserService } from "@/api"; import {GoalService} from '@/api' -import {useUserInfoStore} from "@/stores/UserStore"; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' ChartJS.register( CategoryScale, @@ -181,6 +181,7 @@ export default { await this.transferMoney(amount) this.calculateSavedSoFar(); } catch (error: any) { + handleUnknownError(error) console.log(error.message); } }, @@ -306,7 +307,9 @@ export default { }; try { await GoalService.updateChallengeAmount({requestBody: createGoalPayload}) + this.$emit('refreshSavingGoal'); } catch (e: any) { + handleUnknownError(e) console.log(e.message) } }, @@ -355,6 +358,17 @@ export default { await TransactionControllerService.transferToSelf({requestBody: transactionPayload}) }, + + async regenerateChallenge(challenge: ChallengeDTO) { + let challengeId = challenge.id as number + try { + let response = await GoalService.regenerateChallenge({id: challengeId}) + console.log(response) + this.$emit('refreshSavingGoal'); + } catch (e) { + handleUnknownError(e) + } + }, }, }; </script> @@ -383,8 +397,15 @@ export default { </div> <div class="timeline-panel z-3" :id="'panel-' + index" v-show="challenge.showPanel"> <div class="timeline-heading"> - <h5 style="margin-top: 12px">{{challenge.points}}<img src="../../assets/items/pigcoin.png" alt="pig coint" style="width: 2rem"></h5> - <h4>Utfordring {{ index +1 }}</h4> + <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> + </div> + <div class="coinButton"> + <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> </div> @@ -800,4 +821,28 @@ export default { margin-top: 10px; padding: 12px; } + +.timeline .coinAndRegen { + margin-top: 8px; + display: flex; + flex-direction: row-reverse; +} + +.timeline-inverted .coinAndRegen { + margin-top: 8px; + display: flex; + flex-direction: row; +} + +.coinCoin { + width: 40%; +} + +.coinButton { + width: 60%; + color: white; + align-content: center; + align-items: center; + text-align: center; +} </style> \ No newline at end of file diff --git a/src/components/UserProfile/ExternalProfile.vue b/src/components/UserProfile/ExternalProfile.vue index 1d654b491ef3c07d675efbfa1d4f659a84bf8778..2737d695b9955782811eeb0dfe958f2772591f85 100644 --- a/src/components/UserProfile/ExternalProfile.vue +++ b/src/components/UserProfile/ExternalProfile.vue @@ -6,14 +6,10 @@ import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO, Fri import { ItemService } from "@/api"; import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -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`); -let hasHistory = ref(true) let hasBadges = ref(false) let hasInventory = ref(false) @@ -25,30 +21,8 @@ const backgroundName = ref(""); const points = ref(0 as any); const streak = ref(0 as any); - -let goalName = ref(''); -let goalDescription = ref(''); -let targetAmount = ref(''); -let targetDate = ref(''); -let createdAt = ref(''); -let goals = ref<GoalDTO[]>([]) - -async function getGoals() { - try { - goals.value = await GoalService.getGoals(); - console.log("number of goals: ", goals.value.length) - console.log('The id of a goal: ', goals.value[0]) - if (goals.value.length > 0) { - hasHistory.value = true - } else { - hasHistory.value = false - console.log('No history') - } - }catch (error){ - handleUnknownError(error) - console.error("Something went wrong", error) - } -} +const isFriend = ref(false); +const isRequestSent = ref(false); async function setupForm() { try { @@ -66,7 +40,7 @@ async function setupForm() { streak.value = response.streak?.currentStreak; } if (response.profileImage) { - imageUrl.value = apiUrl + "/api/images/" + response.profileImage; + imageUrl.value = "http://localhost:8080/api/images/" + response.profileImage; } getInventory(); getBadges(); @@ -76,9 +50,20 @@ async function setupForm() { } } +const checkIfFriend = async () => { + let id = route.params.id as any; + const response = await FriendService.getFriends(); + response.forEach((friend) => { + if (friend.id == id) { + isFriend.value = true; + } + }); +}; + const getInventory = async () => { try { - const response = await ItemService.getInventory(); + let id = route.params.id as any + const response = await ItemService.getInventoryByUserId({ userId: id }); inventory.value = response; if (inventory.value.length > 0) { hasInventory.value = true @@ -94,7 +79,8 @@ const getInventory = async () => { const getBadges = async () => { try { - const responseBadge = await BadgeService.getBadgesUnlockedByUser(); + let id = route.params.id as any + const responseBadge = await BadgeService.getBadgesUnlockedByUser({ userId: id }); badges.value = responseBadge; if (badges.value.length > 0) { hasBadges.value = true @@ -108,16 +94,9 @@ const getBadges = async () => { } } -const selectItem = (item: any) => { - backgroundName.value = item.itemName; - useUserInfoStore().setUserInfo({ - roadBackground: item.imageId, - }) -} - onMounted(() => { setupForm() - getGoals() + checkIfFriend() }) const toRoadmap = () => { @@ -127,6 +106,7 @@ const toRoadmap = () => { const addFriend = () => { let id = route.params.id as any; const response = FriendService.addFriendRequest({ userId: id }); + isRequestSent.value = true; }; const removeFriend = () => { @@ -153,11 +133,30 @@ const removeFriend = () => { <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" - data-mdb-ripple-color="dark" style="z-index: 1; height: 40px; margin-left: 17px" id="toUpdate" @click="addFriend"> - Rediger profil - </button> - + <button + v-if="!isFriend && !isRequestSent" + @click="addFriend" + class="btn btn-success mx-3" + style="height: 40px;" + > + Legg til venn + </button> + <button + v-else-if="isRequestSent" + class="btn btn-secondary mx-2" + style="height: 40px;" + disabled + > + Forespørsel sendt + </button> + <button + v-else + @click="removeFriend" + class="btn btn-danger mx-3" + style="height: 40px;" + > + Fjern venn + </button> </div> <div> <p class="mb-1 h2" data-cy="points">{{ points }} <img src="@/assets/items/pigcoin.png" style="width: 4rem"></p> @@ -177,16 +176,15 @@ const removeFriend = () => { <h1 class="mt-1 text-start badges-text">Lageret ditt</h1> <div v-if="hasInventory" class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2"> <div v-for="product in inventory" :key="product.id" class="card text-center" - style="width: 12rem; border: none; cursor: pointer; margin: 1rem; border: 2px solid black" @click="selectItem(product)"> - <img :src="apiUrl + `/api/images/${product.imageId}`" class="card-img-top" + style="width: 12rem; border: none; cursor: pointer; margin: 1rem; border: 2px solid black"> + <img :src="`http://localhost:8080/api/images/${product.imageId}`" class="card-img-top" alt="..." /> <div class="card-body"> <h5 class="card-title">{{ product.itemName }}</h5> </div> </div> </div> - <div v-else>Du har ingen ting på lageret ditt, gå til butikken for å kjøpe!</div> - <div v-if="backgroundName" class="text-success">You selected the background: <strong>{{ backgroundName }}!</strong></div> + <div v-else>Ingen gjenstander</div> </div> </div> </div> @@ -203,7 +201,7 @@ const removeFriend = () => { 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" + <img :src="`http://localhost:8080/api/images/${badge.imageId}`" class="card-img-top" alt="..." /> <div class="card-body"> <h5 class="card-title">{{ badge.badgeName }}</h5> diff --git a/src/components/UserProfile/MyProfile.vue b/src/components/UserProfile/MyProfile.vue index e6473bda0ef9671e6f7f56702177771707aa514a..1074b1596a1ca8deb768af0312ce43c350d1466f 100644 --- a/src/components/UserProfile/MyProfile.vue +++ b/src/components/UserProfile/MyProfile.vue @@ -3,8 +3,9 @@ import {ref, onMounted} from "vue"; import { useRouter } from "vue-router"; import { useUserInfoStore } from "@/stores/UserStore"; import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO} from "@/api"; -import { ItemService } from "@/api"; +import { ItemService, type UserUpdateDTO } 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 numberOfHistory = 6; @@ -12,6 +13,8 @@ let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", " 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) @@ -24,12 +27,6 @@ const backgroundName = ref(""); const points = ref(0 as any); const streak = ref(0 as any); - -let goalName = ref(''); -let goalDescription = ref(''); -let targetAmount = ref(''); -let targetDate = ref(''); -let createdAt = ref(''); let goals = ref<GoalDTO[]>([]) async function getGoals() { @@ -91,7 +88,7 @@ const getInventory = async () => { const getBadges = async () => { try { - const responseBadge = await BadgeService.getBadgesUnlockedByUser(); + const responseBadge = await BadgeService.getBadgesUnlockedByActiveUser(); badges.value = responseBadge; if (badges.value.length > 0) { hasBadges.value = true @@ -107,9 +104,13 @@ const getBadges = async () => { const selectItem = (item: any) => { backgroundName.value = item.itemName; - useUserInfoStore().setUserInfo({ - roadBackground: item.imageId, - }) + let imageId = item.imageId; + //const bannerImagePayload: UserUpdateDTO = { + // bannerImage: imageId as any, + // }; + //UserService.update({ requestBody: bannerImagePayload }) + //bannerImageUrl.value = `http://localhost:8080/api/images/${imageId}`; + } onMounted(() => { @@ -137,7 +138,7 @@ 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" style="height:200px;" id="banner"> + <div class="rounded-top text-white d-flex flex-row bg-primary" :style="{ height: '200px', backgroundImage: `url(${bannerImageUrl})` }"> <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;">