diff --git a/README.md b/README.md index 3d0d9717bfe338b96bec22f8fce822b6e760df6a..78a07bc472ae4358bfec024b00a6497fc1e052ba 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ npm run test:e2e npm run lint ``` -## Using test data +## Test users For easy use of the web application we have provided a set of test users that can be used. #### Regular user @@ -100,6 +100,26 @@ The BankID service uses a set of pre-generated test users provided by Signicat. ```sh qwer1234 ``` +## Test bank accounts +The current application uses mocked bank data to transfer money between savings and checking accounts. We provide a set of Basic Bank Account Numbers (bban) that can be used: + +- **Account 1** + ```sh + 12073650567 + ``` +- **Account 2** + ```sh + 12097256355 + ``` +- **Account 3** + ```sh + 12032202452 + ``` +- **Account 4** + ```sh + 12041281683 + ``` + ## Contributors The individuals who contributed to the project: - Anders Høvik diff --git a/spec.json b/spec.json index aaf7188fa567a4aabafc50d37f247be3de855e4c..6dc6c6b15a1e598c69d554aa2b7cd9f12e07c8c3 100644 --- a/spec.json +++ b/spec.json @@ -411,21 +411,21 @@ } ], "responses": { - "403": { - "description": "Insufficient points to purchase the item", + "201": { + "description": "Item purchased and added to inventory successfully", "content": { "application/json": { - "schema": { - "type": "string" - } + } } }, - "201": { - "description": "Item purchased and added to inventory successfully", + "403": { + "description": "Insufficient points to purchase the item", "content": { "application/json": { - + "schema": { + "type": "string" + } } } } @@ -867,8 +867,8 @@ } } }, - "401": { - "description": "Invalid credentials", + "404": { + "description": "User not found", "content": { "application/json": { "schema": { @@ -877,8 +877,8 @@ } } }, - "404": { - "description": "User not found", + "401": { + "description": "Invalid credentials", "content": { "application/json": { "schema": { @@ -1590,23 +1590,23 @@ } ], "responses": { - "200": { - "description": "Successfully retrieved the image", + "404": { + "description": "Image not found", "content": { "*/*": { "schema": { - "type": "string", - "format": "binary" + "$ref": "#/components/schemas/ExceptionResponse" } } } }, - "404": { - "description": "Image not found", + "200": { + "description": "Successfully retrieved the image", "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ExceptionResponse" + "type": "string", + "format": "binary" } } } @@ -2456,10 +2456,10 @@ "subscriptionLevel": { "type": "string" }, - "checkingAccountBBAN": { + "checkingAccount": { "$ref": "#/components/schemas/BankAccountResponseDTO" }, - "savingsAccountBBAN": { + "savingsAccount": { "$ref": "#/components/schemas/BankAccountResponseDTO" }, "point": { diff --git a/src/api/models/UserDTO.ts b/src/api/models/UserDTO.ts index f3426ff96a2e26940a922007b6ddfea0e5b9042c..5d036f873750a6e1dc8ec600620eb6c54ff937a2 100644 --- a/src/api/models/UserDTO.ts +++ b/src/api/models/UserDTO.ts @@ -15,8 +15,8 @@ export type UserDTO = { createdAt?: string; role?: string; subscriptionLevel?: string; - checkingAccountBBAN?: BankAccountResponseDTO; - savingsAccountBBAN?: BankAccountResponseDTO; + checkingAccount?: BankAccountResponseDTO; + savingsAccount?: BankAccountResponseDTO; point?: PointDTO; streak?: StreakDTO; }; diff --git a/src/assets/icons/refresh.svg b/src/assets/icons/refresh.svg new file mode 100644 index 0000000000000000000000000000000000000000..87a7e9a69186a7418807a7f29f7ef5bb88330128 --- /dev/null +++ b/src/assets/icons/refresh.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> \ No newline at end of file diff --git a/src/components/BaseComponents/NavBar.vue b/src/components/BaseComponents/NavBar.vue index f99af99d8db075c5862baf6fc57d55f86bcd4fcd..6b54c7187cf5ff808eb928e2413d7c0f62df6932 100644 --- a/src/components/BaseComponents/NavBar.vue +++ b/src/components/BaseComponents/NavBar.vue @@ -157,6 +157,7 @@ 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'; const router = useRouter(); const route = useRoute(); @@ -207,6 +208,7 @@ const getNotifications = async () => { await BadgeService.updateUnlockedBadges(); notificationListRef.value = await NotificationService.getUnreadNotificationByUser() } catch (error) { + handleUnknownError(error); notificationListRef.value = [] } } @@ -217,6 +219,7 @@ const readNotification = async (notification: NotificationDTO) => { await NotificationService.updateNotification({requestBody: notification}); notificationListRef.value = await NotificationService.getUnreadNotificationByUser() } catch (error) { + handleUnknownError(error); notificationListRef.value = []; } } diff --git a/src/components/Budget/Modal/ConfirmDeleteModal.vue b/src/components/Budget/Modal/ConfirmDeleteModal.vue index 0644da13bdc34b26a4a04955c404c566085972ae..5d8b19f87074ee47704de0c480c65186bf92a3e8 100644 --- a/src/components/Budget/Modal/ConfirmDeleteModal.vue +++ b/src/components/Budget/Modal/ConfirmDeleteModal.vue @@ -1,5 +1,6 @@ <script setup lang="ts"> import { BudgetService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; const emit = defineEmits(['errorEvent', 'deletedEvent']) const props = defineProps({ @@ -25,6 +26,7 @@ const deleteBudget = async () => { await BudgetService.deleteBudget({budgetId: props.budgetId}) emit('deletedEvent') } catch (error) { + handleUnknownError(error); emit('errorEvent', error) } } diff --git a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue index f6bf338f7ff5455a9777ed3c00aa51cf6c39747f..f94692165826660f651401c41f5ad6830ae81497 100644 --- a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue +++ b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue @@ -4,6 +4,7 @@ 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'; const router = useRouter(); const emit = defineEmits(['changeRouterEvent']) @@ -35,6 +36,7 @@ const handleSubmit = async () => { await GoalService.createGoal({ requestBody: createGoalPayload }); await router.push("/") } catch (error: any) { + handleUnknownError(error); console.log(error.message); errorMessage.value = error.message; } diff --git a/src/components/Friends/UserFriends.vue b/src/components/Friends/UserFriends.vue index d97189bedc05e1d1f590bce0233249732dc824bd..d6adc455830f7c3f22e892b395fb1bf44dd4d5c3 100644 --- a/src/components/Friends/UserFriends.vue +++ b/src/components/Friends/UserFriends.vue @@ -71,9 +71,7 @@ <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">Legg til venn</h5> - <button type="button" class="close" @click="showAddFriend = false"> - <span aria-hidden="true">×</span> - </button> + <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" @@ -124,6 +122,7 @@ import { type Ref, ref, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { FriendService, UserService } from '@/api'; import type { UserDTO } from '@/api'; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; const router = useRouter(); const friends = ref(); @@ -155,6 +154,7 @@ const searchProfile = async (searchTerm: string) => { searchedUsers.value = response; console.log(response); } catch (error) { + handleUnknownError(error); console.error('Failed to search for profile', error); } }; @@ -169,6 +169,7 @@ const addNewFriends = async () => { searchedUsers.value = response; showAddFriend.value = true; } catch (error) { + handleUnknownError(error); console.error('Failed to add friend', error); } }; @@ -179,6 +180,7 @@ async function addFriend(friendID: number) { // 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); } } @@ -192,6 +194,7 @@ async function requestFriend() { elementsInFriendRequest.value = response.length > 0; console.log("Friend requests: " + response); } catch (error) { + handleUnknownError(error); console.error('Failed to fetch friend requests', error); } } @@ -206,6 +209,7 @@ const removeFriend = async (friendID: number) => { const responseFriends = await FriendService.getFriends(); friends.value = responseFriends; } catch (error) { + handleUnknownError(error); console.error('Failed to remove friend', error); } }; @@ -219,6 +223,7 @@ const setupFriends = async () => { elementsInFriends.value = response.length > 0; console.log(response); } catch (error) { + handleUnknownError(error); console.error('Failed to fetch friends', error); } }; @@ -231,6 +236,7 @@ const acceptRequest = async (requestID: number) => { const responseFriends = await FriendService.getFriends(); friends.value = responseFriends; } catch (error) { + handleUnknownError(error); console.error('Failed to accept friend request', error); } }; @@ -241,6 +247,7 @@ const rejectRequest = async (requestID: number) => { const response = await FriendService.getFriendRequests(); friendRequests.value = response; } catch (error) { + handleUnknownError(error); console.error('Failed to reject friend request', error); } }; diff --git a/src/components/Leaderboard/LeaderboardRank.vue b/src/components/Leaderboard/LeaderboardRank.vue index b3432f49675eefd694e027b6d55c738eddc47b93..5fe859052b04ce6a4c6f412b5be0edf1517dcfbe 100644 --- a/src/components/Leaderboard/LeaderboardRank.vue +++ b/src/components/Leaderboard/LeaderboardRank.vue @@ -46,6 +46,7 @@ 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'; let streakLeaderboardData = ref([] as any); @@ -72,44 +73,49 @@ onMounted(() => { }); async function global() { - 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; + 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); + } } async function friends() { + try { let friendsPoints = await LeaderboardService.getLeaderboard({ type: "TOTAL_POINTS", filter: "FRIENDS", @@ -138,7 +144,6 @@ async function friends() { entryCount: 2, }); - pointsLeaderboardData.value = friendsPoints.entries; currentLeaderboardData.value = friendsCurrentStreak.entries; streakLeaderboardData.value = friendsStreak.entries; @@ -146,6 +151,9 @@ async function friends() { pointsLeaderboardDataExtra.value = friendsPointsYou.entries; currentLeaderboardDataExtra.value = friendsStreakYou.entries; streakLeaderboardDataExtra.value = friendsCurrentStreakYou.entries; + } catch (error) { + handleUnknownError(error); + } } const navigateToUserProfile = (userId: number) => { diff --git a/src/components/Login/ForgottenPassword.vue b/src/components/Login/ForgottenPassword.vue index 6d5e7fc808c4eb43be34b7550cf0b4e78d8fa82d..5066cef695a3c0f6cf31688edf4f0e63a931ea85 100644 --- a/src/components/Login/ForgottenPassword.vue +++ b/src/components/Login/ForgottenPassword.vue @@ -30,6 +30,7 @@ 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; @@ -49,6 +50,7 @@ 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 = ''; } diff --git a/src/components/SavingGoal/SavingGoal.vue b/src/components/SavingGoal/SavingGoal.vue index 73b63403e45151d2dce1b24f6da6b60340f1d81e..54cba56fcae6f3c3b0068aaa03a78fd9e06914a6 100644 --- a/src/components/SavingGoal/SavingGoal.vue +++ b/src/components/SavingGoal/SavingGoal.vue @@ -5,6 +5,7 @@ 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}, @@ -23,6 +24,7 @@ export default { this.calculateBluePanelMaxHeight(); }, methods: { + useUserInfoStore, calculateBluePanelMaxHeight() { // Query the timeline element const timelineElement = document.querySelector('.timeline'); @@ -73,7 +75,12 @@ export default { </div> <saving-goal-list :key="keyForList" @goToSavingGoal="goToSavingGoal"></saving-goal-list> </div> - <div class="spacer"/> + <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> + </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-default v-else></saving-goal-default> @@ -99,8 +106,14 @@ export default { } .spacer { + padding-top: 16px; width: 10%; background-color: transparent; + margin-bottom: 12px; +} + +.spacer img { + width: 100%; } h2 { diff --git a/src/components/SavingGoal/SavingGoalDefault.vue b/src/components/SavingGoal/SavingGoalDefault.vue index 788e7e44ad896fdc1d6c9a0ee879cebdbe0136e3..683d5647eae83ab34e4ce366f31e97adc6b15c12 100644 --- a/src/components/SavingGoal/SavingGoalDefault.vue +++ b/src/components/SavingGoal/SavingGoalDefault.vue @@ -7,9 +7,8 @@ <div class="col-lg-8"> <div class="container text-center"> <div class="d-flex flex-column justify-content-center align-items-center"> - <h1 class="">Velkommen til <span>SpareSti</span></h1> - <br> - <p class="">Kom i økonomisk form: Ta pÃ¥ deg vÃ¥re spareutfordringer!<br></p> + <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"> </div> </div> diff --git a/src/components/SavingGoal/SavingGoalRoadmap.vue b/src/components/SavingGoal/SavingGoalRoadmap.vue index 54a8ffd8fdf1e8652fbe69791511e41a52441ae4..fe1a241977253da06dda6547cf78a0b8d0b27077 100644 --- a/src/components/SavingGoal/SavingGoalRoadmap.vue +++ b/src/components/SavingGoal/SavingGoalRoadmap.vue @@ -1,7 +1,14 @@ <script lang="ts"> import {CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, Title, Tooltip} from 'chart.js' import {Line} from 'vue-chartjs' -import type {ChallengeDTO, CreateGoalDTO, GoalDTO, MarkChallengeDTO} from "@/api"; +import { + type ChallengeDTO, + type CreateGoalDTO, + type GoalDTO, + type MarkChallengeDTO, + TransactionControllerService, type TransactionDTO, + UserService +} from "@/api"; import {GoalService} from '@/api' import {useUserInfoStore} from "@/stores/UserStore"; @@ -171,6 +178,7 @@ export default { } this.addDataToChart(amount, dateString); + await this.transferMoney(amount) this.calculateSavedSoFar(); } catch (error: any) { console.log(error.message); @@ -334,8 +342,18 @@ export default { } }, - transferMoney(amount: number) { - //need users bank accounts + async transferMoney(amount: number) { + let response = await UserService.getUser() + let spendingAccount = response.checkingAccount?.bban + let savingAccount = response.savingsAccount?.bban + + const transactionPayload: TransactionDTO = { + debtorBBAN: spendingAccount, + creditorBBAN: savingAccount, + amount: amount, + } + + await TransactionControllerService.transferToSelf({requestBody: transactionPayload}) }, }, }; diff --git a/src/components/Settings/SettingsAccount.vue b/src/components/Settings/SettingsAccount.vue index 9e01429f4c3d343d1277fcaa29b35d0e8bdae044..90ed8d699a10f4e1f4e0e1d99ff8eee09f2b4ecc 100644 --- a/src/components/Settings/SettingsAccount.vue +++ b/src/components/Settings/SettingsAccount.vue @@ -4,6 +4,7 @@ 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'; const emailRef = ref('') const errorMsg = ref('') @@ -22,6 +23,7 @@ async function setupForm() { confirmationMsg.value = ''; errorMsg.value = ''; } catch (err) { + handleUnknownError(err); errorMsg.value = 'Error fetching email, try again!' confirmationMsg.value = '' } @@ -40,6 +42,7 @@ const handleSubmit = async () => { confirmationMsg.value = 'Email updated successfully!' errorMsg.value = ''; } catch (err) { + handleUnknownError(err); errorMsg.value = "Error updating email, try again!"; confirmationMsg.value = '' } diff --git a/src/components/Settings/SettingsBank.vue b/src/components/Settings/SettingsBank.vue index d5142d2130fd5ae83927cd8d2da5fce6bca311bb..dfcc04ff941a34ce32c3d2c0271b0c11580343e8 100644 --- a/src/components/Settings/SettingsBank.vue +++ b/src/components/Settings/SettingsBank.vue @@ -27,22 +27,38 @@ </form> <hr> <div class="form-group mb-0"> - <label class="d-block">Betalingshistorikk</label> - <div class="border border-gray-500 bg-gray-200 p-3 text-center font-size-sm">Du har ikke foretatt noen betaling.</div> + <label class="d-block">Saldo oversikt</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> </div> </template> <script setup lang="ts"> -import { ref } from 'vue'; +import { ref, onMounted } from 'vue'; import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; import type { BankAccountDTO } from '@/api'; import { UserService } from '@/api'; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' const spendingAccount = ref() const savingsAccount = ref() +const spendingAccountBalance = ref() +const savingsAccountBalance = ref() const handleSpendingInputEvent = (newValue: any) => { @@ -66,7 +82,7 @@ const handleSavingSubmit = async () => { try { UserService.selectBankAccount({ requestBody: updateSaving }) } catch (err) { - console.error(err) + handleUnknownError(err) } } @@ -80,7 +96,18 @@ const handleSpendingSubmit = async () => { try { UserService.selectBankAccount({ requestBody: updateSaving }) } catch (err) { - console.error(err) + handleUnknownError(err) } } + +onMounted(getAccountInfo) +async function getAccountInfo() { + try { + let response = await UserService.getUser() + savingsAccountBalance.value = response.savingsAccount?.balance + spendingAccountBalance.value = response.checkingAccount?.balance + } catch (err) { + handleUnknownError(err) + } +} </script> \ No newline at end of file diff --git a/src/components/Settings/SettingsSecurity.vue b/src/components/Settings/SettingsSecurity.vue index 8eab51063b9684504beea4d6e375a1c4b536e3a4..db446c7f0f94f648ddb9013e2bebc538f948a87d 100644 --- a/src/components/Settings/SettingsSecurity.vue +++ b/src/components/Settings/SettingsSecurity.vue @@ -37,6 +37,7 @@ 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(''); @@ -71,13 +72,8 @@ const handleSubmit = async () => { const response = UserService.updatePassword({ requestBody: updateUserPayload }) console.log(response) } catch (err) { + handleUnknownError(err); console.error(err) } } - - - - -</script> - -<style scoped></style> \ No newline at end of file +</script> \ No newline at end of file diff --git a/src/components/Shop/ItemShop.vue b/src/components/Shop/ItemShop.vue index da198f45b152a166fe6c70d2c1457d39eb1f1869..d6f14fdca2bad7a27b7df6daef35e0db166199f7 100644 --- a/src/components/Shop/ItemShop.vue +++ b/src/components/Shop/ItemShop.vue @@ -1,149 +1,195 @@ <template> <div id="background"> - <br> - <div id="dropdownContainer"> + <br /> + <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"> + </div> + <div class="container d-flex justify-content-center"> <div class="row col-md-10"> - <div class="col-md-12"> - <h1>Stash</h1> - <div class="category row justify-content-between mb-5 m-2"> - <div class="card text-center" style="width: 16rem; border: none"> - <img src="../../assets/items/adfree.png" class="card-img-top" alt="..."> - <div class="card-body"> - <h5 class="card-title">Adfree</h5> - <button type="button" class="btn btn-primary" id="buttonStyle" @click="buyNoAds"> - +35kr</button> - </div> - </div> - <div class="card text-center" style="width: 16rem; border: none"> - <img src="../../assets/items/piggybank.webp" class="card-img-top" alt="..."> - <div class="card-body"> - <h5 class="card-title">Premium</h5> - <button type="button" class="btn btn-primary" id="buttonStyle" - @click="buyPremium">+50kr</button> - </div> - </div> + <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-body"> + <h5 class="card-title">Adfree</h5> + <button type="button" class="btn btn-primary" id="buttonStyle" @click="buyNoAds"> + +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-body"> + <h5 class="card-title">Premium</h5> + <button type="button" class="btn btn-primary" id="buttonStyle" @click="buyPremium"> + +50kr + </button> + </div> + </div> </div> - <div class="col-md-12"> - <h1>Items</h1> - <div class="category row justify-content-between mb-5 m-2"> - <div v-for="product in products" :key="product.id" class="card text-center" - style="width: 16rem; border: none"> - <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> - <ShopButton v-if="!product.alreadyBought" :button-text="product.price" - @click="buyItem(product.id)"></ShopButton> - <p v-else>Owned</p> - </div> - </div> + </div> + <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" class="card text-center d-flex justify-content-center align-items-center" + style="width: 8rem; border: none"> + <img :src="`http://localhost:8080/api/images/${product.imageId}`" style="width: 100px; 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)" + /> + <p v-else>Owned</p> </div> + </div> </div> - <div class="col-md-12"> - <h1>Stash</h1> - <div class="category row justify-content-between mb-5 m-2"> - <div class="card text-center" style="width: 16rem; border: none"> - <img src="../../assets/items/coffee.jpg" class="card-img-top" alt="..."> + </div> + <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-body"> <h5 class="card-title">Free Coffee</h5> - <ShopButton button-text="500"></ShopButton> + <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" style="width: 16rem; border: none"> - <img src="../../assets/items/viaplay.jpg" class="card-img-top" alt="..."> + <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 Viaplay</h5> - <ShopButton button-text="10000"></ShopButton> + <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()" + /> </div> </div> - <div class="card text-center" style="width: 16rem; border: none"> - <img src="../../assets/items/pirbad.png" class="card-img-top" alt="..."> + <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> - <ShopButton button-text="1000"></ShopButton> + <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> </div> - </div> + </div> </div> - </div> -</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'; - -const products = ref([] as any); -const points = ref(); - -const getStore = async () => { + </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'; + + const products = ref([] as any); + const points = ref(); + + const getStore = async () => { try { - const response = await ItemService.getStore(); - products.value = response; + const response = await ItemService.getStore(); + products.value = response; } catch (error) { - console.log(error); + handleUnknownError(error); + console.log(error); } -} - -const getPoints = 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) { - console.log(error); + handleUnknownError(error); + console.log(error); } -} - -const buyItem = async (itemId: number) => { + } + + const buyItem = async (itemId: number) => { try { - const response = await ItemService.buyItem({ itemId: itemId }); - console.log(response); - getStore(); - getPoints(); + const response = await ItemService.buyItem({ itemId: itemId }); + console.log(response); + getStore(); + getPoints(); } catch (error) { - console.log(error); + handleUnknownError(error); + console.log(error); } -} - -const buyPremium = async () => { + } + + const buyPremium = async () => { try { - const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }); - useUserInfoStore().setUserInfo({ - subscriptionLevel: 'PREMIUM', - }) + const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }); + useUserInfoStore().setUserInfo({ + subscriptionLevel: 'PREMIUM', + }) } catch (error) { - console.log(error); + handleUnknownError(error); + console.log(error); } -} - -const buyNoAds = async () => { + } + + const buyNoAds = async () => { try { - const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }); - useUserInfoStore().setUserInfo({ - subscriptionLevel: 'NO_ADS', - }) + const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }); + useUserInfoStore().setUserInfo({ + subscriptionLevel: 'NO_ADS', + }) } catch (error) { - console.log(error); + handleUnknownError(error); + console.log(error); } + } + + //Just a random code generator for the feature's sake + function generateRandomCode(length = 8) { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; } -onMounted(() => { + const buySomething = async () => { + try { + const randomCode = generateRandomCode(); + alert(`Thank you for your purchase! Your code is: ${randomCode}`); + } catch (error) { + handleUnknownError(error); + console.log(error); + } + } + + onMounted(() => { getStore(); getPoints(); -}) -</script> + }) + </script> <style scoped> .card { @@ -152,7 +198,7 @@ onMounted(() => { border-radius: 8px; padding-left: 5px; padding-right: 5px; - /* Rounded corners */ + height: 225px; } .box { diff --git a/src/components/Shop/ShopButton.vue b/src/components/Shop/ShopButton.vue index 843eeb1530c696b3ae8aca05ab310aae54aaf057..67b6a41c3b918fb896239640b763982831feb1a7 100644 --- a/src/components/Shop/ShopButton.vue +++ b/src/components/Shop/ShopButton.vue @@ -1,13 +1,39 @@ +<!-- ShopButton.vue --> <template> - <button type="button" class="btn btn-primary" id="buttonStyle"><img src="../../assets/items/pigcoin.png" style="width: 2rem"> +{{ buttonText }}</button> + <button + :disabled="disabled" + :class="['btn', { 'btn-primary': !disabled, 'btn-secondary': disabled }]" + id="buttonStyle" + @click="handleClick" + > + {{ buttonText }} + </button> </template> <script setup lang="ts"> -defineProps<{ buttonText: string }>(); +import { defineProps, defineEmits } from 'vue'; + +const props = defineProps({ + buttonText: String, + disabled: Boolean, +}); + +const emit = defineEmits(['click']); + +const handleClick = () => { + if (!props.disabled) { + emit('click'); + } +}; </script> <style scoped> - #buttonStyle { - border-radius: 3rem; - } -</style> \ No newline at end of file +#buttonStyle { + border-radius: 1rem; + cursor: pointer; +} + +#buttonStyle[disabled] { + cursor: not-allowed; +} +</style> diff --git a/src/components/Shop/__tests__/ShopButton.spec.ts b/src/components/Shop/__tests__/ShopButton.spec.ts index 277e3521fe84175524d94d0f10bfdf5b7da851c3..bc9cbea5ba146921c2e480d56e8257e9c1e021b0 100644 --- a/src/components/Shop/__tests__/ShopButton.spec.ts +++ b/src/components/Shop/__tests__/ShopButton.spec.ts @@ -20,9 +20,6 @@ describe('ImageButtonComponent', () => { const button = wrapper.find('#buttonStyle') expect(button.exists()).toBe(true) - expect(button.text()).toContain('+Add Coin') - const image = button.find('img') - expect(image.exists()).toBe(true) - expect(image.attributes('src')).toBe('/src/assets/items/pigcoin.png') + expect(button.text()).toContain('Add Coin') }) }) diff --git a/src/components/UserProfile/ExternalProfile.vue b/src/components/UserProfile/ExternalProfile.vue index fbfb68c77bcc940033d6087d5195807e798b4808..3852752e2e3d7889edfec0f7f4862375072fc5e5 100644 --- a/src/components/UserProfile/ExternalProfile.vue +++ b/src/components/UserProfile/ExternalProfile.vue @@ -1,180 +1,218 @@ <script setup lang="ts"> -import {useRoute, useRouter} from "vue-router"; - -import {onMounted, ref} from "vue"; -import {UserService, type ProfileDTO} 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' let numberOfHistory = 6; - let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", "Pretty clothes"] - -let username = ref() - -let friend = ref(false) - -let profile: ProfileDTO;; - +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) -let id = ref() - -//Get the id from the url. viktor har eksempel - -let user = useRoute() -id.value = user.params -console.log(id.value.id) +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); -let route = useRouter() +let goalName = ref(''); +let goalDescription = ref(''); +let targetAmount = ref(''); +let targetDate = ref(''); +let createdAt = ref(''); +let goals = ref<GoalDTO[]>([]) -function toRoadmap(){ - route.push('/roadmap') +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) + } } -//todo Make a store of a friend-instance -onMounted(async () => { + +async function setupForm() { try { + let id = route.params.id as any; let response = await UserService.getProfile({ - userId: id.value.id + userId: id }) - profile = response; - console.log(profile) - username.value = profile.firstName - if (profile.profileImage){ - imageUrl.value = `http://localhost:8080/api/images/${profile.profileImage}` + + firstname.value = response.firstName; + lastname.value = response.lastName; + if (response.point?.currentPoints) { + points.value = response.point?.currentPoints; + } + if (response.streak?.currentStreak) { + streak.value = response.streak?.currentStreak; + } + if (response.profileImage) { + imageUrl.value = "http://localhost:8080/api/images/" + response.profileImage; + } + getInventory(); + getBadges(); + } catch (err) { + handleUnknownError(err) + console.error(err) + } +} + +const getInventory = async () => { + try { + const response = await ItemService.getInventory(); + inventory.value = response; + if (inventory.value.length > 0) { + hasInventory.value = true + } else { + hasInventory.value = false + console.log('No history') } - console.log(username) } catch (error) { - console.error("Something went wrong getting the profile: ", error) + handleUnknownError(error) + console.log(error); } -}) +} -function addFriend(){ - friend.value = true - console.log("Added friend") +const getBadges = async () => { + try { + const responseBadge = await BadgeService.getBadgesUnlockedByUser(); + badges.value = responseBadge; + if (badges.value.length > 0) { + hasBadges.value = true + } else { + hasBadges.value = false + console.log('No history') + } + } catch (error) { + handleUnknownError(error) + console.log(error); + } +} - //todo Send POST to backend when endpoints is made and add friend +const selectItem = (item: any) => { + backgroundName.value = item.itemName; + useUserInfoStore().setUserInfo({ + roadBackground: item.imageId, + }) } -function removeFriend(){ - friend.value = false - console.log("Removed friend") +onMounted(() => { + setupForm() + getGoals() +}) - //todo Send POST to backend when endpoints is made and remove friend +const toRoadmap = () => { + router.push('/'); +}; -} +const addFriend = () => { + let id = route.params.id as any; + const response = FriendService.addFriendRequest({ userId: id }); +}; + +const removeFriend = () => { + let id = route.params.id as any; + const response = FriendService.deleteFriendOrFriendRequest({ friendId: id }); +}; -function toUpdateUserSettings(){ - route.push('/update-user') -} -</script> +</script> <template> <div class="container py-5 h-100"> <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;"> - <div class="ms-4 mt-5 d-flex flex-column" style="width: 150px;"> - <img :src="imageUrl" alt="Generisk plassholderbilde" - class="img-fluid img-thumbnail mt-4 mb-2" style="width: 150px; z-index: 1"> - <button v-if="!friend" type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-primary" - data-mdb-ripple-color="dark" style="z-index: 1;" @click="addFriend"> - Legg til venn - </button> - <button v-if="friend" type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-danger" - data-mdb-ripple-color="dark" style="z-index: 1;" @click="removeFriend"> - Fjern venn - </button> - </div> - <div class="ms-3" style="margin-top: 130px;"> - <h1>{{username}}</h1> + <div class="rounded-top text-white d-flex flex-row bg-primary" style="height:200px;" id="banner"> + <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> </div> - <div class="p-4 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" + data-mdb-ripple-color="dark" style="z-index: 1; height: 40px; margin-left: 17px" id="toUpdate" @click="addFriend"> + Rediger profil + </button> + + </div> <div> - <p class="mb-1 h2">253 <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> <p class="small text-muted mb-0">Poeng</p> </div> <div class="px-3"> - <p class="mb-1 h2">1026 <img src="@/assets/icons/fire.png" style="width: 4rem"></p> + <p class="mb-1 h2" data-cy="streak">{{ streak }} <img src="@/assets/icons/fire.png" style="width: 4rem"></p> <p class="small text-muted mb-0">Streak</p> </div> </div> </div> + <hr> <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> <div class="container-fluid"> - <h1 class="mt-5 text-start badges-text">Merker</h1> - <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-4 pb-4 pt-2"> - - <div class="col-5"> - <div class="card badges-block card-1"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-2"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-3"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-4"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-5"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-6"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-7"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-8"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-9"></div> - </div> - <div class="col-5"> - <div class="card badges-block card-10"></div> + <h1 class="mt-1 text-start badges-text">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="`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> </div> </div> + </div> + <hr> + <div class="card-body p-1 text-black"> <div class="row"> <div class="col"> - <!-- Her er historikken over lagrede mÃ¥l --> - <div class="container-fluid mb-5"> - <h1 class="mt-5 text-start history-text">Historie</h1> - <div class="row scrolling-wrapper-history"> - <div v-for="index in numberOfHistory" :key="index" - class="col-md-4 col-sm-4 col-lg-4 col-xs-4 col-xl-4 control-label"> - <div class="card history-block"> - <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="..."> - </div> - <div class="col-md-8"> - <div class="card-body"> - <h5 class="card-title">{{ cardTitles[index - 1] }}</h5> - <p class="card-text">Penger spart: 200 <br />Du har fullført en utfordring: 21</p> - <p class="card-text"><small class="text-muted">Sist oppdatert for 3 minutter siden</small></p> - <a href="#" class="btn stretched-link" @click="toRoadmap"></a> - </div> - </div> - </div> + <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="`http://localhost:8080/api/images/${badge.imageId}`" class="card-img-top" + alt="..." /> + <div class="card-body"> + <h5 class="card-title">{{ badge.badgeName }}</h5> </div> - </div> </div> - </div> + </div> + <div v-else> + Ingen merker + </div> </div> </div> </div> @@ -196,8 +234,6 @@ function toUpdateUserSettings(){ overflow: auto; } - - .badges-text { font-weight: 500; font-size: 2.0em; @@ -242,6 +278,10 @@ function toUpdateUserSettings(){ } } +#banner { + background-image: url('/src/assets/banners/stacked.svg'); +} + .card-1 { background-color: #4158D0; background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%); diff --git a/src/components/UserProfile/MyProfile.vue b/src/components/UserProfile/MyProfile.vue index 7a10b3eab7904de7f0e12eb5b5fcaab5113f2929..6f4e2ab6c80dd4dda4b2f471bcf64e9c8e1c76a2 100644 --- a/src/components/UserProfile/MyProfile.vue +++ b/src/components/UserProfile/MyProfile.vue @@ -1,9 +1,10 @@ <script setup lang="ts"> -import { ref, onMounted } from "vue"; +import {ref, onMounted} from "vue"; import { useRouter } from "vue-router"; -import { useUserInfoStore } from "../../stores/UserStore"; -import { UserService, BadgeService } from "@/api"; +import { useUserInfoStore } from "@/stores/UserStore"; +import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO} from "@/api"; import { ItemService } from "@/api"; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' let numberOfHistory = 6; let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", "Pretty clothes"] @@ -11,10 +12,41 @@ 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) + const router = useRouter(); const inventory = ref([] as any); -const badges = ref([] as any); +const badges = ref<BadgeDTO[]>([]); 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) + } +} async function setupForm() { try { @@ -23,12 +55,19 @@ async function setupForm() { firstname.value = response.firstName; lastname.value = response.lastName; + if (response.point?.currentPoints) { + points.value = response.point?.currentPoints; + } + if (response.streak?.currentStreak) { + streak.value = response.streak?.currentStreak; + } if (response.profileImage) { imageUrl.value = "http://localhost:8080/api/images/" + response.profileImage; } getInventory(); getBadges(); } catch (err) { + handleUnknownError(err) console.error(err) } } @@ -37,7 +76,14 @@ const getInventory = async () => { try { const response = await ItemService.getInventory(); inventory.value = response; + if (inventory.value.length > 0) { + hasInventory.value = true + } else { + hasInventory.value = false + console.log('No history') + } } catch (error) { + handleUnknownError(error) console.log(error); } } @@ -46,7 +92,14 @@ const getBadges = async () => { try { const responseBadge = await BadgeService.getBadgesUnlockedByUser(); badges.value = responseBadge; + if (badges.value.length > 0) { + hasBadges.value = true + } else { + hasBadges.value = false + console.log('No history') + } } catch (error) { + handleUnknownError(error) console.log(error); } } @@ -60,6 +113,7 @@ const selectItem = (item: any) => { onMounted(() => { setupForm() + getGoals() }) const toRoadmap = () => { @@ -72,6 +126,9 @@ const toRoadmap = () => { const toUpdateUserSettings = () => { router.push('/settings/profile'); }; + + + </script> <template> @@ -96,11 +153,11 @@ const toUpdateUserSettings = () => { </div> <div> - <p class="mb-1 h2" data-cy="points">253 <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> <p class="small text-muted mb-0">Poeng</p> </div> <div class="px-3"> - <p class="mb-1 h2" data-cy="streak">1026 <img src="@/assets/icons/fire.png" style="width: 4rem"></p> + <p class="mb-1 h2" data-cy="streak">{{ streak }} <img src="@/assets/icons/fire.png" style="width: 4rem"></p> <p class="small text-muted mb-0">Streak</p> </div> </div> @@ -111,7 +168,7 @@ const toUpdateUserSettings = () => { <div class="col"> <div class="container-fluid"> <h1 class="mt-1 text-start badges-text">Lageret ditt</h1> - <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2"> + <div v-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="`http://localhost:8080/api/images/${product.imageId}`" class="card-img-top" @@ -121,6 +178,7 @@ const toUpdateUserSettings = () => { </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> </div> @@ -132,7 +190,7 @@ const toUpdateUserSettings = () => { <div class="col"> <div class="container-fluid"> <h1 class="mt-1 text-start badges-text">Merker</h1> - <div class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2"> + <div v-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; @@ -146,6 +204,9 @@ const toUpdateUserSettings = () => { </div> </div> + <div v-else> + Ingen merker + </div> </div> </div> </div> @@ -155,9 +216,9 @@ const toUpdateUserSettings = () => { <!-- Her er historikken over lagrede mÃ¥l --> <div class="container-fluid mb-5"> <h1 class="mt-1 text-start history-text">Historie</h1> - <div class="row scrolling-wrapper-history"> - <div v-for="index in numberOfHistory" :key="index" - class="col-md-4 col-sm-4 col-lg-4 col-xs-4 col-xl-4 control-label"> + <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 class="card history-block"> <div class="card mb-3" style="max-width: 540px;"> <div class="row g-0"> @@ -167,10 +228,9 @@ const toUpdateUserSettings = () => { </div> <div class="col-md-8"> <div class="card-body"> - <h5 class="card-title">{{ cardTitles[index - 1] }}</h5> - <p class="card-text">Penger spart: 200 <br />Du har fullført en utfordring: 21</p> - <p class="card-text"><small class="text-muted">Sist oppdatert for 3 minutter - siden</small></p> + <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> <a href="#" class="btn stretched-link" @click="toRoadmap"></a> </div> </div> @@ -179,6 +239,9 @@ const toUpdateUserSettings = () => { </div> </div> </div> + <div v-if="!hasHistory"> + Ingen sparemÃ¥l + </div> </div> </div> @@ -246,7 +309,7 @@ const toUpdateUserSettings = () => { } #banner { - background-image: url('../src/assets/banners/stacked.svg'); + background-image: url('/src/assets/banners/stacked.svg'); } .card-1 { diff --git a/src/views/BasePageView.vue b/src/views/BasePageView.vue index 4aebb55972d017af7c1469446acd9a9cb4ef8d61..f7907b39ced3e3c370f50f2d91faeba4f78afe75 100644 --- a/src/views/BasePageView.vue +++ b/src/views/BasePageView.vue @@ -7,15 +7,9 @@ import { useUserInfoStore } from '@/stores/UserStore'; <template> <Menu data-cy="menu"></Menu> - <div v-if="!useUserInfoStore().isPremium && !useUserInfoStore().isNoAds" style="display: flex; flex-direction: row;"> - <img v-for="item in 7" src="@/assets/coca.webp" style="width: 100%; height: 100px; margin: 5px; border-radius: 1rem;" alt="picture"> - </div> <div id="minHeight"> <RouterView /> </div> - <div v-if="!useUserInfoStore().isPremium && !useUserInfoStore().isNoAds" style="display: flex; flex-direction: row;"> - <img v-for="item in 7" src="@/assets/coca.webp" style="width: 100%; height: 100px; margin: 5px; border-radius: 1rem;" alt="picture"> - </div> <Footer></Footer> </template>