diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 90070224797236dfe126967705c3ab18fd9c8edb..894f4e0f1a7bdc7dd1c37095792a858c356f87df 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,5 +80,5 @@ deploy_docker: - apt-get -yqq install ssh - apt-get install sshpass script: - - sshpass -p sParest1en ssh -v -o StrictHostKeyChecking=no root@128.199.53.153 "docker rm -f frontend || true; docker pull registry.gitlab.com/$DOCKER_USER/sparesti-registry:frontend && docker run -d -p 81:80 --name frontend registry.gitlab.com/$DOCKER_USER/sparesti-registry:frontend" + - sshpass -p $ssh_password ssh -v -o StrictHostKeyChecking=no $ssh_user@$ssh_ip "docker rm -f frontend || true; docker pull registry.gitlab.com/$DOCKER_USER/sparesti-registry:frontend && docker run -d -p 81:80 --name frontend registry.gitlab.com/$DOCKER_USER/sparesti-registry:frontend" when: manual \ No newline at end of file diff --git a/README.md b/README.md index 78a07bc472ae4358bfec024b00a6497fc1e052ba..d6f2ace929cdd86a1f312ef1912355da5662d09b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The frontend of sparesti.app. SpareSti is designed to make saving fun. The app i ## Links +- **Website**: [https://sparesti.org/](https://sparesti.org/login) - **Backend**: [https://gitlab.stud.idi.ntnu.no/idatt2106-2024-07/backend](https://gitlab.stud.idi.ntnu.no/idatt2106-2024-07/backend) ## Recommended IDE Setup diff --git a/src/components/BaseComponents/Input/BaseInput.vue b/src/components/BaseComponents/Input/BaseInput.vue index 80901c51577e8b562b68e7c4d8e5f2fef27cbeb1..cf6bddf0886b2b5a78ee463be9cbd048fae290d6 100644 --- a/src/components/BaseComponents/Input/BaseInput.vue +++ b/src/components/BaseComponents/Input/BaseInput.vue @@ -52,8 +52,16 @@ const props = defineProps({ } }); +// Form reference in order to display validations input const formRef = ref(); + +/** + * Adds the "was-validated" class to the input element, and emits + * an 'inputChangeEvent' to parent component. + * + * @param event The input event object + */ const onInputEvent = (event: any) => { formRef.value.classList.add("was-validated") emit('inputChangeEvent', event.target.value) diff --git a/src/components/BaseComponents/NavBar.vue b/src/components/BaseComponents/NavBar.vue index d03b9bd8818a5661d6bd0b0053d286d280df7f43..9af55cb30ee505ad64ae4acf3fa3b8b27df5d888 100644 --- a/src/components/BaseComponents/NavBar.vue +++ b/src/components/BaseComponents/NavBar.vue @@ -159,21 +159,37 @@ import {onMounted, ref} from "vue"; import { BadgeService, type NotificationDTO, NotificationService } from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; +// Declaring router, route and userStore variables const router = useRouter(); const route = useRoute(); - const userStore: any = useUserInfoStore(); -let path = ref('#'); +// Declaring profile image +let profileImage: any = ref(''); +if (useUserInfoStore().profileImage !== 0) { + profileImage.value = 'http://localhost:80/api/images/' + useUserInfoStore().profileImage; +} else { + profileImage.value = 'src/assets/userprofile.png'; +} +// Declaring reactive notification list for displaying notification let notificationListRef = ref<NotificationDTO[]>([]); - - function isAnyActivePage() { - const activeRoutes = ['/roadmap', '/leaderboard', '/news', '/shop']; // Add other pages here +/** + * 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', '/shop']; return activeRoutes.includes(route.path); } +/** + * Toggles the visibility of the dropdown menu based on the event target. + * + * @param {Event} event The event object. + */ function toggleDropdown(event: any) { const dropdownMenu = event.target.closest('.dropdown-menu'); if (dropdownMenu) { @@ -181,19 +197,30 @@ 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" } +/** + * Maps notification types to their respective paths. + */ const notificationPathMapper: any = { "FRIEND_REQUEST": "/friends", "BADGE": "/profile", "COMPLETED_GOAL": "/roadmap" } +/** + * Retrieves the list of notifications for the current user. + * This function updates the list of unread notifications by fetching them from the NotificationService. + * If successful, it updates the notificationListRef.value with the retrieved notifications. + * If an error occurs during the process, it catches the error, it sets the notificationListRef.value to an empty array. + */ const getNotifications = async () => { try { await BadgeService.updateUnlockedBadges(); @@ -204,6 +231,15 @@ const getNotifications = async () => { } } +/** + * Marks a notification as read. + * This function updates the unread status of the provided notification to false, + * then sends a request to the NotificationService to update the notification in the database. + * If successful, it updates the notificationListRef.value with the updated list of unread notifications. + * If an error occurs during the process, it catches the error, it sets the notificationListRef.value to an empty array. + * + * @param {NotificationDTO} notification The notification to mark as read. + */ const readNotification = async (notification: NotificationDTO) => { try { notification.unread = false; @@ -214,82 +250,101 @@ const readNotification = async (notification: NotificationDTO) => { notificationListRef.value = []; } } - -function toBadges(){ - -} - -function getPath(id : string){ - if(id === '1'){ - return path.value = '/profile' - } - if(id === '2'){ - return path.value = '/friends' - } - if(id === '3'){ - return path.value = '/roadmap' - } - - return '#'; -} - -function updateNotification(){ - //Axios get request to the getFunction -} - -function removeNotification() { - -} - - -function toHome() { - return '/' -} - -function toBudget() { - return '/budget-overview' -} - -function toSavingGoals() { - return '/roadmap' -} - -function toLeaderboard() { - return '/leaderboard' -} - -function toNews() { - return '/news' -} - -function toStore() { - return '/shop' -} - -function toSetting() { - return '/settings/profile' -} - -function toFeedback() { - return '/feedback' -} - -function toFriends() { - return '/friends' -} - -function toUserProfile() { - return '/profile' -} - +/** + * Redirects to the budget overview page. + * + * @returns {string} The URL for the budget overview page. + */ +function toBudget(): string { + return '/budget-overview'; +} + +/** + * Redirects to the saving goals page. + * + * @returns {string} The URL for the saving goals page. + */ +function toSavingGoals(): string { + return '/roadmap'; +} + +/** + * Redirects to the leaderboard page. + * + * @returns {string} The URL for the leaderboard page. + */ +function toLeaderboard(): string { + return '/leaderboard'; +} + +/** + * Redirects to the news page. + * + * @returns {string} The URL for the news page. + */ +function toNews(): string { + return '/news'; +} + +/** + * Redirects to the store page. + * + * @returns {string} The URL for the store page. + */ +function toStore(): string { + return '/shop'; +} + +/** + * Redirects to the user settings page. + * + * @returns {string} The URL for the user settings page. + */ +function toSetting(): string { + return '/settings/profile'; +} + +/** + * Redirects to the feedback page. + * + * @returns {string} The URL for the feedback page. + */ +function toFeedback(): string { + return '/feedback'; +} + +/** + * Redirects to the friends page. + * + * @returns {string} The URL for the friends page. + */ +function toFriends(): string { + return '/friends'; +} + +/** + * Redirects to the user profile page. + * + * @returns {string} The URL for the user profile page. + */ +function toUserProfile(): string { + 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'); } + +/** + * Calls the getNotifications function when the component is mounted. + */ onMounted(() => { getNotifications() }) - </script> <style scoped> .navbar-brand { diff --git a/src/components/Budget/Modal/MiniBudgetBox.vue b/src/components/Budget/Modal/MiniBudgetBox.vue index ef36d7c092890128e1bf041dd88a554fc6556b67..da0b1736accb5aed41e241d9528df2661c93862c 100644 --- a/src/components/Budget/Modal/MiniBudgetBox.vue +++ b/src/components/Budget/Modal/MiniBudgetBox.vue @@ -20,6 +20,8 @@ const props = defineProps({ default: 0 } }) + +// Calculated balance from props attribute const balance = ref<number>(props.budgetAmount - props.expenseAmount); /** diff --git a/src/components/Configuration/ChallangeCheckBox.vue b/src/components/Configuration/ChallangeCheckBox.vue index 58720ba2308be0bb592db6118c09e9939fba82fe..849bd3404435999b2aed98a7b77d279b1e3a7647 100644 --- a/src/components/Configuration/ChallangeCheckBox.vue +++ b/src/components/Configuration/ChallangeCheckBox.vue @@ -21,10 +21,11 @@ const props = defineProps({ }) /** - * This method is run whenever a change has occurred in the checkbox. It retrieves + * This method executes whenever a change has occurred in the checkbox. It retrieves * the current value of the checkbox (true/false) and emits a data object containing * the challenge's description and the checked value. - * @param event The event object containing information about the input's checked value. + * + * @param {any} event The event object containing information about the input's checked value. */ const onChallengeChanged = (event: any) => { const value = event.target.checked diff --git a/src/components/Configuration/ConfigurationParent.vue b/src/components/Configuration/ConfigurationParent.vue index 06a6872cc8663d40d5d4a8fd6828a14e329c8ea9..4d01b6193fb46d9e5d684a6de71d587f9c6d6895 100644 --- a/src/components/Configuration/ConfigurationParent.vue +++ b/src/components/Configuration/ConfigurationParent.vue @@ -17,7 +17,12 @@ let currentRoute = useRoute() let currentPath = currentRoute.fullPath type ConfigurationStepPath = keyof typeof configurationSteps; -// Sets the current path to a new path and updates progressbar +/** + * Sets the current path variable to the child component's route path. + * Maps the path with its value and updates the percentage value for the progressbar. + * + * @param path The path of the newly navigated route. + */ const onNewRouteEvent = (path: ConfigurationStepPath) => { currentPath = path percentage.value = (1/length) * configurationSteps[path] diff --git a/src/components/Configuration/ConfigurationSteps/BankAccount.vue b/src/components/Configuration/ConfigurationSteps/BankAccount.vue index 8e2256bbb945a77a8730a2905f26efa7728cb2fd..ed454804e18f437a9d0f8be2bcc51797088e9bff 100644 --- a/src/components/Configuration/ConfigurationSteps/BankAccount.vue +++ b/src/components/Configuration/ConfigurationSteps/BankAccount.vue @@ -9,22 +9,39 @@ import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' const router = useRouter(); +// Declaring reactive variables const formRef = ref(); const checkingAccount = ref<string>(''); const savingsAccount = ref<string>(''); -let errorMsg = ref(''); +let errorMsg = ref<string>(''); // Updates progress bar in the parent Configuration component. const emit = defineEmits(['changeRouterEvent']) emit('changeRouterEvent', '/bank-account') +/** + * Handles the input event for spending account. + * + * @param {any} newValue - The new value of the spending account. + */ const handleSpendingInputEvent = (newValue: any) => { checkingAccount.value = newValue } +/** + * Handles the input event for saving account. + * + * @param {any} newValue - The new value of the saving account. + */ const handleSavingInputEvent = (newValue: any) => { savingsAccount.value = newValue } + +/** + * Adds the "was-validated" class to the form element and then checks if the form is valid. + * If the form is valid, it updates the spending and savings account values in the configuration store + * and navigates the user to the "/commitment" route. + */ const handleSubmit = async () => { formRef.value.classList.add("was-validated") const form = formRef.value; diff --git a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue index f94692165826660f651401c41f5ad6830ae81497..57abfe4a6e5729ebee3361f767098daaf13070bc 100644 --- a/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue +++ b/src/components/Configuration/ConfigurationSteps/ConfigurationSavingGoal.vue @@ -7,9 +7,12 @@ import {type CreateGoalDTO, GoalService} from "@/api"; import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; const router = useRouter(); + +// Updates progress bar in the parent Configuration component const emit = defineEmits(['changeRouterEvent']) emit('changeRouterEvent', '/first-saving-goal') +// Declaration of reactive variables for the form const formRef = ref<any>() const titleRef = ref<string>() let descriptionRef = ref<string>() @@ -17,14 +20,20 @@ const sumRef = ref<number>() const dateRef = ref<string>() const errorMessage = ref("") +/** + * Adds the "was-validated" class to the form element, validates the form, + * creates a payload for creating a goal, and then attempts to create the goal using GoalService. + * If successful, it navigates the user to the home page ("/"), otherwise it handles any errors. + */ const handleSubmit = async () => { + // Check form validation formRef.value.classList.add("was-validated") const form = formRef.value if (!form.checkValidity()) { return; } - // TODO integrate user creation and goal creation with backend. + // Declares the goal payload const createGoalPayload: CreateGoalDTO = { name: titleRef.value, description: descriptionRef.value, @@ -33,15 +42,20 @@ const handleSubmit = async () => { }; try { + // Creates new goal with the payload await GoalService.createGoal({ requestBody: createGoalPayload }); await router.push("/") } catch (error: any) { handleUnknownError(error); - console.log(error.message); errorMessage.value = error.message; } } +/** + * Gets today's date in the format "YYYY-MM-DD". + * + * @returns Today's date in "YYYY-MM-DD" format. + */ const getTodayDate = () => { const today = new Date(); const year = today.getFullYear(); @@ -53,14 +67,29 @@ const getTodayDate = () => { return `${year}-${month}-${day}`; }; +/** + * Handles the input event for the goal title. + * + * @param newTitle The new title value entered by the user. + */ const handleTitleInputEvent = (newTitle: string) => { titleRef.value = newTitle; } +/** + * Handles the input event for the goal date. + * + * @param newDate The new date value entered by the user. + */ const handleDateInputEvent = (newDate: string) => { dateRef.value = newDate; } +/** + * Handles the input event for the goal sum. + * + * @param newSum The new sum value entered by the user. + */ const handleSumInputEvent = (newSum: number) => { sumRef.value = newSum; } diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index 8d56476be08e5a2112c9e7d804d33acac0c1d988..c852366d7968ba196afc7a7d8cbdf6b94a033bda 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -39,7 +39,13 @@ const onChangedChallengeEvent = (value: never) => { console.log(chosenChallenges.value) } -const convertEnumToText = (enumValue: String) => { +/** + * Converts the given enum value to a formatted text representation. + * + * @param {string} enumValue the enum value to be converted + * @return {string} The formatted text representation of the enum value + */ +const convertEnumToText = (enumValue: String): string => { return enumValue.charAt(0).toUpperCase() + enumValue.slice(1).replace(/_/g, ' ').toLowerCase(); } @@ -52,6 +58,7 @@ const signUpUser = async () => { // Saves the chosen challenges to the configuration store useConfigurationStore().setChallenges(chosenChallenges.value) + // Declares the request payload const signUpPayLoad: SignUpRequest = { firstName: useUserInfoStore().getFirstName, lastName: useUserInfoStore().getLastname, @@ -66,8 +73,6 @@ const signUpUser = async () => { savingsAccountBBAN: useConfigurationStore().getSavingsAccountBBAN, }; - console.log(signUpPayLoad) - let response = await AuthenticationService.signup({ requestBody: signUpPayLoad }); if (response.token == null) { errorMsg.value = 'A valid token could not be created'; @@ -80,12 +85,18 @@ const signUpUser = async () => { }); } +/** + * Handles form submission by signing up the user, + * updating bank accounts, and navigating to the next configuration step. + * If an error occur, an error message will be displayed. + */ const handleSubmit = async () => { + // Check if there are no chosen challenges if (chosenChallenges.value.length === 0) { + // if so sets chosen challenges to all, so a goal can be created. chosenChallenges.value = challenges } useConfigurationStore().setChallenges(chosenChallenges.value) - try { await signUpUser(); diff --git a/src/components/Exceptions/ErrorBoundaryCatcher.vue b/src/components/Exceptions/ErrorBoundaryCatcher.vue index 29500a2b3080583b012e271c2e8b30bbdeb988df..f22d58617a6b405ab272879295371708174bb7ca 100644 --- a/src/components/Exceptions/ErrorBoundaryCatcher.vue +++ b/src/components/Exceptions/ErrorBoundaryCatcher.vue @@ -11,6 +11,14 @@ import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; const errorStore = useErrorStore(); +/** + * Handles errors captured during component lifecycle hooks or during component rendering. + * + * @param err The error object captured. + * @param _vm The Vue instance where the error was captured. + * @param _info Additional information about the error. + * @return {boolean} Returns false to indicate that the error has been handled. + */ onErrorCaptured((err, _vm, _info): boolean => { const message = handleUnknownError(err); errorStore.addError(message); diff --git a/src/components/Exceptions/NotFoundPage.vue b/src/components/Exceptions/NotFoundPage.vue index fceb97ac6adf0017e00025fa1703f5509117497c..882ba71bf3635cba55221df775a5a216aa17742e 100644 --- a/src/components/Exceptions/NotFoundPage.vue +++ b/src/components/Exceptions/NotFoundPage.vue @@ -26,8 +26,11 @@ import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; const router = useRouter(); +/** + * Navigates to home page. + */ const home = () => { - router.push('/'); // Assuming the root URL '/' is your home route + router.push('/'); }; </script> diff --git a/src/components/Exceptions/UnauthorizedPage.vue b/src/components/Exceptions/UnauthorizedPage.vue index 99bfe00e211b79514f6fbb1b4ac8a2983b40018b..949ff41dfce485ef38237fa7edccf4ff0ed879c9 100644 --- a/src/components/Exceptions/UnauthorizedPage.vue +++ b/src/components/Exceptions/UnauthorizedPage.vue @@ -22,6 +22,9 @@ import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; const router = useRouter(); +/** + * Navigates to home page. + */ const home = () => { router.push('/'); }; diff --git a/src/components/Friends/UserFriends.vue b/src/components/Friends/UserFriends.vue index f9abe5c0484048e7d4c88ec34af2840aba01d1f3..d83a13ef9e201ef83adafbc15810d385abae0b1c 100644 --- a/src/components/Friends/UserFriends.vue +++ b/src/components/Friends/UserFriends.vue @@ -121,12 +121,13 @@ 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'; let apiUrl = import.meta.env.VITE_APP_API_URL; const router = useRouter(); + +// Declaring reactive variables const friends = ref(); const showFriends = ref(true); const showRequests = ref(false); @@ -136,16 +137,26 @@ const addFriends = ref([] as any); const searchedUsers = ref([] as any); const friendRequestsSent: Ref<Record<number, boolean>> = ref({}); - const searchWord = ref(""); const elementsInFriendRequest = ref(false); const elementsInFriends = ref(false); +/** + * Navigates to the user profile page based on the given user ID. + * + * @param {number} userId The ID of the user whose profile will be navigated to. + */ const toUserProfile = (userId: number) => { router.push('/profile/' + userId); }; + +/** + * Searches for user profiles based on the provided search term. + * + * @param {string} searchTerm The term to be used for searching user profiles. + */ const searchProfile = async (searchTerm: string) => { const userPayload = { searchTerm: searchTerm as string, @@ -161,6 +172,9 @@ const searchProfile = async (searchTerm: string) => { } }; +/** + * Adds new friends to the user's friend list. + */ const addNewFriends = async () => { const userPayload = { amount: 6 as number, @@ -176,6 +190,11 @@ const addNewFriends = async () => { } }; +/** + * Sends a friend request to the specified user. + * + * @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 }); @@ -187,6 +206,9 @@ async function addFriend(friendID: number) { } } +/** + * Fetches friend requests and updates the state accordingly. + */ async function requestFriend() { showRequests.value = true; showFriends.value = false; @@ -201,10 +223,20 @@ async function requestFriend() { } } +/** + * Navigates to the profile page of the specified friend. + * + * @param friendID The ID of the friend whose profile will be navigated to. + */ const navigateToFriend = (friendID: number) => { router.push('/profile/' + friendID); }; +/** + * Removes the specified friend from the user's friend list. + * + * @param friendID The ID of the friend to be removed. + */ const removeFriend = async (friendID: number) => { try { await FriendService.deleteFriendOrFriendRequest({ friendId: friendID }); @@ -216,6 +248,9 @@ const removeFriend = async (friendID: number) => { } }; +/** + * Sets up the user's friends by fetching and updating the friends list. + */ const setupFriends = async () => { showFriends.value = true; showRequests.value = false; @@ -230,6 +265,12 @@ const setupFriends = async () => { } }; + +/** + * Accepts a friend request with the specified request ID. + * + * @param {number} requestID The ID of the friend request to be accepted. + */ const acceptRequest = async (requestID: number) => { try { await FriendService.acceptFriendRequest({ friendId: requestID }); @@ -243,6 +284,11 @@ const acceptRequest = async (requestID: number) => { } }; +/** + * Rejects a friend request with the specified request ID. + * + * @param {number} requestID The ID of the friend request to be rejected. + */ const rejectRequest = async (requestID: number) => { try { await FriendService.deleteFriendOrFriendRequest({ friendId: requestID }); @@ -254,6 +300,9 @@ const rejectRequest = async (requestID: number) => { } }; +/** + * Initializes the component by setting up the user's friends. + */ onMounted(() => { setupFriends(); }); diff --git a/src/components/Leaderboard/LeaderboardRank.vue b/src/components/Leaderboard/LeaderboardRank.vue index 5fe859052b04ce6a4c6f412b5be0edf1517dcfbe..819834e8799383de8494fc5bdaf16dee333dcf98 100644 --- a/src/components/Leaderboard/LeaderboardRank.vue +++ b/src/components/Leaderboard/LeaderboardRank.vue @@ -49,6 +49,8 @@ import Leaderboard from '@/components/Leaderboard/LeaderboardTable.vue'; import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; import { LeaderboardService } from '@/api'; +const router = useRouter(); + let streakLeaderboardData = ref([] as any); let currentLeaderboardData = ref([] as any); let pointsLeaderboardData = ref([] as any); @@ -59,8 +61,9 @@ let pointsLeaderboardDataExtra = ref([] as any); let communityPoints = ref(0); -const router = useRouter(); - +/** + * Fetches quiz data including community points. + */ async function fetchQuizData() { await global(); @@ -72,6 +75,10 @@ onMounted(() => { fetchQuizData(); }); + +/** + * Retrieves global leaderboard data. + */ async function global() { try { let globalPoints = await LeaderboardService.getLeaderboard({ @@ -114,6 +121,9 @@ async function global() { } } +/** + * Retrieves friends leaderboard data. + */ async function friends() { try { let friendsPoints = await LeaderboardService.getLeaderboard({ @@ -156,6 +166,11 @@ async function friends() { } } +/** + * Navigates to the user profile page based on the given user ID. + * + * @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 } }); }; diff --git a/src/components/Leaderboard/LeaderboardTable.vue b/src/components/Leaderboard/LeaderboardTable.vue index 71b11995c1e5c8d94fedc8855802506ed90696bb..4cb62331f504e48c74ddb7cc28f6398e21b1d036 100644 --- a/src/components/Leaderboard/LeaderboardTable.vue +++ b/src/components/Leaderboard/LeaderboardTable.vue @@ -51,9 +51,18 @@ const props = defineProps({ } }); -console.log(props.leaderboardExtra); - +/** + * 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)); + +/** + * Navigates to the user profile page based on the given user ID. + * + * @param {number} id The ID of the user whose profile will be navigated to. + */ const navigateToUserProfile = (id: number) => { router.push(`/profile/${id}`); }; diff --git a/src/components/Login/ChangePassword.vue b/src/components/Login/ChangePassword.vue index 2e15299f39c4aca4bc6d87248ed6c72a13d49a8d..b2a129ea45d2f5d5ad9e926fa2f35a5caab108a3 100644 --- a/src/components/Login/ChangePassword.vue +++ b/src/components/Login/ChangePassword.vue @@ -93,18 +93,32 @@ let samePasswords = ref(true) let errorMsg = ref(''); const isSubmitting = ref(false); +/** + * Handles password input event by updating the value of the newPassword reactive variable. + * + * @param {any} newValue The new value of the password input. + */ const handlePasswordInputEvent = (newValue: any) => { newPassword.value = newValue } +/** + * Handles confirm password input event by updating the value of the confirmPassword reactive variable. + * + * @param {any} newValue The new value of the confirm password input. + */ const handleConfirmPasswordInputEvent = (newValue: any) => { confirmPassword.value = newValue } +/** + * Handles form submission by validating the form, checking password equality, + * and submitting the password reset request if validation passes. + */ 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") diff --git a/src/components/Login/ForgottenPassword.vue b/src/components/Login/ForgottenPassword.vue index 5066cef695a3c0f6cf31688edf4f0e63a931ea85..169c13f93fb32f376d6ce0a0d838b9d36c70ce78 100644 --- a/src/components/Login/ForgottenPassword.vue +++ b/src/components/Login/ForgottenPassword.vue @@ -38,11 +38,14 @@ 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 { diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index f0ca531608a6ec0bc966fffe6223f51a5c7629b8..ae3cb5d3dead318dbb9a4af0c18fbc697b9e2479 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -4,29 +4,43 @@ 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, useRoute } from 'vue-router'; +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 emailRef = ref('') const passwordRef = ref('') const formRef = ref() let errorMsg = ref(''); const isSubmitting = ref(false); -const errorStore = useErrorStore(); -const router = useRouter(); -const userStore = useUserInfoStore(); - +/** + * Handles email input event by updating the value of the emailRef reactive variable. + * + * @param {any} newValue The new value of the email input. + */ const handleEmailInputEvent = (newValue: any) => { emailRef.value = newValue } +/** + * Handles password input event by updating the value of the passwordRef reactive variable. + * + * @param {any} newValue The new value of the password input. + */ const handlePasswordInputEvent = (newValue: any) => { passwordRef.value = newValue } +/** + * Handles form submission by validating the form, logging in the user, + * and navigating to the roadmap page upon successful login. + */ const handleSubmit = async () => { console.log(emailRef.value) console.log(passwordRef.value) diff --git a/src/components/News/NewsFeed.vue b/src/components/News/NewsFeed.vue index 047b7800de20425e8dbf7544a41dcdcb994ca622..99abc7c31974222468ecd2b9c7da81bccb33e6c6 100644 --- a/src/components/News/NewsFeed.vue +++ b/src/components/News/NewsFeed.vue @@ -1,10 +1,18 @@ <script lang="ts"> + +/** + * Interface representing a news article. + */ interface news { urlToImage: string; title: string; description: string; url: string; } + +/** + * Component to fetch and display finance news. + */ export default { data() { return { @@ -19,6 +27,9 @@ export default { setInterval(this.fetchFinanceNews, 300000); }, methods: { + /** + * Fetches finance news articles from the NewsAPI. + */ async fetchFinanceNews() { try { const response = await fetch( @@ -29,7 +40,6 @@ export default { //English articles, might want to translate to norwegian this.articles = data.articles; - } catch (error) { console.error('Error fetching saving money news:', error); } diff --git a/src/components/Settings/SettingsAccount.vue b/src/components/Settings/SettingsAccount.vue index f29d2d5b8e8f6c6df65eee2241a48c95565ac1c7..68f2123af8a3d6ff930ac5efe9a97ba2d79748ed 100644 --- a/src/components/Settings/SettingsAccount.vue +++ b/src/components/Settings/SettingsAccount.vue @@ -12,10 +12,20 @@ const errorMsg = ref('') const confirmationMsg = ref('') const errorMsg2 = ref('') +/** + * Handles the email input event by updating the email reference value. + * + * @param {any} newValue - The new value of the email input. + */ const handleEmailInputEvent = (newValue: any) => { emailRef.value = newValue } +/** + * Sets up the form by fetching user data and populating the email field if available. + * Clears confirmation and error messages. + * Handles errors by displaying a generic error message and updating the error message field. + */ async function setupForm() { try { let response = await UserService.getUser(); @@ -30,12 +40,20 @@ async function setupForm() { } } +/** + * Handles form submission by updating the user's email. + * Updates the confirmation message upon successful email update. + * Handles errors and updates the error message accordingly. + */ const handleSubmit = async () => { + // Construct payload for updating user email const updateUserPayload: UserUpdateDTO = { 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, }) diff --git a/src/components/Settings/SettingsBank.vue b/src/components/Settings/SettingsBank.vue index 285ca9b9c511c2cbe1e813af2f622615dc6ae545..c923b7005efd5b4286edd9a4b26fe11954cb8f30 100644 --- a/src/components/Settings/SettingsBank.vue +++ b/src/components/Settings/SettingsBank.vue @@ -64,17 +64,28 @@ const savingsAccountBalance = ref(0 as any) const errorMsg = ref('') const confirmationMsg = ref('') - +/** + * Handles the event when spending input changes by updating the spending account value. + * + * @param {any} newValue - The new value of the spending input. + */ const handleSpendingInputEvent = (newValue: any) => { spendingAccount.value = newValue } - +/** + * Handles the event when saving input changes by updating the saving account value. + * + * @param {any} newValue - The new value of the saving input. + */ const handleSavingInputEvent = (newValue: any) => { savingsAccount.value = newValue } - +/** + * Submits the updated saving account information to the UserService. + * Handles errors by calling the handleUnknownError function. + */ const handleSavingSubmit = async () => { try { const updateUserPayload: UserUpdateDTO = { @@ -89,6 +100,10 @@ const handleSavingSubmit = async () => { } } +/** + * Submits the updated spending account information to the UserService. + * Handles errors by calling the handleUnknownError function. + */ const handleSpendingSubmit = async () => { try { const updateUserPayload: UserUpdateDTO = { @@ -104,6 +119,11 @@ const handleSpendingSubmit = async () => { } onMounted(getAccountInfo) + +/** + * Retrieves account information for the user upon component mounting. + * Handles errors by calling the handleUnknownError function. + */ async function getAccountInfo() { try { let response = await UserService.getUser(); diff --git a/src/components/Settings/SettingsProfile.vue b/src/components/Settings/SettingsProfile.vue index 1b1ce4d7ec303975a302f6bd45e14df3d2036172..f42d54b27f54535db5adba73a4279d69bb36a3d4 100644 --- a/src/components/Settings/SettingsProfile.vue +++ b/src/components/Settings/SettingsProfile.vue @@ -23,19 +23,39 @@ const selectedBanner = ref() const iconSrc = ref('../src/assets/userprofile.png'); const fileInputRef = ref(); +/** + * Handles the event when the first name input changes. + * Updates the first name reference value. + * + * @param {any} newValue - The new value of the first name input. + */ const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue } - +/** + * Handles the event when the surname input changes. + * Updates the surname reference value. + * + * @param {any} newValue - The new value of the surname input. + */ const handleSurnameInputEvent = (newValue: any) => { surnameRef.value = newValue } +/** + * Triggers the file upload dialog. + */ const triggerFileUpload = () => { fileInputRef.value.click(); }; +/** + * Handles the file change event. + * Uploads the selected image file. + * + * @param {any} event - The file change event. + */ const handleFileChange = (event: any) => { const file = event.target.files[0]; if (file) { @@ -43,6 +63,13 @@ const handleFileChange = (event: any) => { } }; +/** + * Uploads the image file to the server. + * Updates user profile information upon successful image upload. + * + * @param {any} file - The image file to upload. + */ + const uploadImage = async (file: any) => { const formData = { file: new Blob([file]) } @@ -76,7 +103,6 @@ const getInventory = async () => { }; const selectItem = async (bannerId: any) => { - console.log(bannerId) try { const bannerImagePayload: UserUpdateDTO = { bannerImage: bannerId, @@ -89,6 +115,10 @@ const selectItem = async (bannerId: any) => { } } +/** + * Sets up the user profile form. + * Fetches user data and populates the form fields. + */ async function setupForm() { try { const response = await UserService.getUser(); @@ -102,21 +132,27 @@ async function setupForm() { } else { iconSrc.value = "../src/assets/userprofile.png"; } - selectedBanner.value = response.bannerImage; - console.log(response.bannerImage) + if (response.bannerImage != null) { + selectedBanner.value = response.bannerImage; + } } catch (err) { handleUnknownError(err); console.error(err) } } + +/** + * Handles form submission. + * Updates user profile information with the provided first name and surname. + */ const handleSubmit = async () => { const updateUserPayload: UserUpdateDTO = { firstName: firstNameRef.value, lastName: surnameRef.value, }; try { - await UserService.update({ requestBody: updateUserPayload }) + UserService.update({ requestBody: updateUserPayload }) useUserInfoStore().setUserInfo({ firstname: firstNameRef.value, lastname: surnameRef.value, @@ -168,10 +204,11 @@ onMounted(() => { <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" class="card text-center banner justify-content-center d-flex align-items-center" - data-bs-toggle="tooltip" + <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)" /> + <img :src="apiUrl + `/api/images/${banner.imageId}`" class="card-img-top" alt="Banner" style="width: 100px; height: 100px" /> + <h5 class="card-title">{{ selectedBanner }}</h5> </div> </div> <div v-else> @@ -214,7 +251,7 @@ onMounted(() => { } .selected-banner { - border: 5px solid #1cd516; + border: 2px solid #1a81b5; display: flex; } diff --git a/src/components/Settings/SettingsSecurity.vue b/src/components/Settings/SettingsSecurity.vue index 22519a0078f42c7e99062ea3428ba90d4c70ac73..e71ee3db5e0d57084b2a09ef8e6a5a5648085ef8 100644 --- a/src/components/Settings/SettingsSecurity.vue +++ b/src/components/Settings/SettingsSecurity.vue @@ -33,36 +33,57 @@ <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'; +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(''); +const oldPasswordRef = ref(''); +const newPasswordRef = ref(''); +const confirmPasswordRef = ref(''); +let errorMsg = ref(''); - const handleOldPasswordInputEvent = (newValue: any) => { - oldPasswordRef.value = newValue + +/** + * Handles the event when the old password input changes. + * Updates the old password reference value. + * + * @param {any} newValue - The new value of the old password input. + */ +const handleOldPasswordInputEvent = (newValue: any) => { + oldPasswordRef.value = newValue } - const handleNewPasswordInputEvent = (newValue: any) => { - newPasswordRef.value = newValue +/** + * Handles the event when the new password input changes. + * Updates the new password reference value. + * + * @param {any} newValue - The new value of the new password input. + */ +const handleNewPasswordInputEvent = (newValue: any) => { + newPasswordRef.value = newValue } - const handleConfirmPasswordInputEvent = (newValue: any) => { - confirmPasswordRef.value = newValue +/** + * Handles the event when the confirm-password input changes. + * Updates the confirm-password reference value. + * + * @param {any} newValue - The new value of the confirm-password input. + */ +const handleConfirmPasswordInputEvent = (newValue: any) => { + confirmPasswordRef.value = newValue } +/** + * Handles form submission for password update. + * 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, diff --git a/src/components/Shop/ItemShop.vue b/src/components/Shop/ItemShop.vue index 2cec70f76a1fce6ed78374bc822cd623277f4041..6a9c787ee254caf667008977ff1221140a015a03 100644 --- a/src/components/Shop/ItemShop.vue +++ b/src/components/Shop/ItemShop.vue @@ -109,7 +109,10 @@ 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(); @@ -119,7 +122,10 @@ console.log(error); } } - + + /** + * Retrieves the user's current points and updates the points reference. + */ const getPoints = async () => { try { const response = await UserService.getUser(); @@ -129,22 +135,32 @@ console.log(error); } } - + + /** + * Buys an item with the specified item ID. + * Sends a request to buy the item, then refreshes the store and points information. + * + * @param {number} itemId - The ID of the item to buy. + */ const buyItem = async (itemId: number) => { try { - const response = await ItemService.buyItem({ itemId: itemId }); - console.log(response); - getStore(); - getPoints(); + await ItemService.buyItem({ itemId: itemId }); + await getStore(); + await getPoints(); } catch (error) { handleUnknownError(error); console.log(error); } } - + + /** + * Buys a premium subscription for the user. + * Sends a request to update the user's subscription level to 'PREMIUM'. + * Updates the user's subscription level in the store. + */ const buyPremium = async () => { try { - const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }); + await UserService.updateSubscriptionLevel({ subscriptionLevel: 'PREMIUM' }); useUserInfoStore().setUserInfo({ subscriptionLevel: 'PREMIUM', }) @@ -153,10 +169,15 @@ console.log(error); } } - + + /** + * Buys a subscription to remove ads for the user. + * Sends a request to update the user's subscription level to 'NO_ADS'. + * Updates the user's subscription level in the store. + */ const buyNoAds = async () => { try { - const response = await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }); + await UserService.updateSubscriptionLevel({ subscriptionLevel: 'NO_ADS' }); useUserInfoStore().setUserInfo({ subscriptionLevel: 'NO_ADS', }) @@ -166,8 +187,13 @@ } } - //Just a random code generator for the feature's sake - function generateRandomCode(length = 8) { +/** + * Generates a random code of the specified length. + * + * @param length - The length of the random code. Default is 8. + * @returns A randomly generated code. + */ +function generateRandomCode(length = 8) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { @@ -176,21 +202,25 @@ return result; } - const buySomething = async () => { - try { - const randomCode = generateRandomCode(); - alert(`Thank you for your purchase! Your code is: ${randomCode}`); - } catch (error) { - handleUnknownError(error); - console.log(error); - } +/** + * Buys something (dummy functionality for demonstration purposes). + * Generates a random code and alerts the user with the code as a confirmation message. + */ +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> +} + +onMounted(() => { + getStore(); + getPoints(); +}) +</script> <style scoped> .card { diff --git a/src/components/Shop/ShopButton.vue b/src/components/Shop/ShopButton.vue index 67b6a41c3b918fb896239640b763982831feb1a7..a7bf5fcf9e5bf96a2a2cd27d9f56a477e2c082be 100644 --- a/src/components/Shop/ShopButton.vue +++ b/src/components/Shop/ShopButton.vue @@ -20,6 +20,11 @@ const props = defineProps({ const emit = defineEmits(['click']); +/** + * Handles the click event for a button component. + * Emits a 'click' event if the button is not disabled. + */ + const handleClick = () => { if (!props.disabled) { emit('click'); diff --git a/src/components/SignUp/SignUpForm.vue b/src/components/SignUp/SignUpForm.vue index 6cf987d5b0e174520dad61c1205ad76cdc6a51cd..ca6e633671e04ce97a8927792c8f9b238097854b 100644 --- a/src/components/SignUp/SignUpForm.vue +++ b/src/components/SignUp/SignUpForm.vue @@ -11,6 +11,7 @@ import LoginLink from '@/components/Login/LoginLink.vue' const router = useRouter(); const userStore = useUserInfoStore(); +// Reactive variables for the form const firstNameRef = ref('') const surnameRef = ref('') const emailRef = ref('') @@ -21,30 +22,67 @@ let samePasswords = ref(true) let errorMsg = ref(''); const isSubmitting = ref(false); +/** + * Handles the event when the first name input changes. + * Updates the reference value of the first name input. + * + * @param {any} newValue - The new value of the first name input. + */ const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue } +/** + * Handles the event when the surname input changes. + * Updates the reference value of the surname input. + * + * @param {any} newValue - The new value of the surname input. + */ const handleSurnameInputEvent = (newValue: any) => { surnameRef.value = newValue } +/** + * Handles the event when the email input changes. + * Updates the reference value of the email input. + * + * @param {any} newValue - The new value of the email input. + */ const handleEmailInputEvent = (newValue: any) => { emailRef.value = newValue } +/** + * Handles the event when the password input changes. + * Updates the reference value of the password input. + * + * @param {any} newValue - The new value of the password input. + */ const handlePasswordInputEvent = (newValue: any) => { passwordRef.value = newValue } +/** + * Handles the event when the confirm-password input changes. + * Updates the reference value of the confirm-password input. + * + * @param {any} newValue - The new value of the confirm-password input. + */ const handleConfirmPasswordInputEvent = (newValue: any) => { confirmPasswordRef.value = newValue } +/** + * Handles form submission for user registration. + * Validates form inputs, checks password matching, and submits user information for registration. + * Updates user information in the store upon successful registration. + * Redirects the user to the configuration page after registration. + */ const handleSubmit = async () => { if (isSubmitting.value) return; isSubmitting.value = true; + // Validates form and displays validation samePasswords.value = (passwordRef.value === confirmPasswordRef.value) formRef.value.classList.add("was-validated") @@ -52,7 +90,9 @@ const handleSubmit = async () => { if (form.checkValidity()) { if (samePasswords.value) { try { - let response = await AuthenticationService.validateEmail({email: emailRef.value}); + // Validates email + await AuthenticationService.validateEmail({email: emailRef.value}); + // Set userInfo details in user store userStore.setUserInfo({ firstname: firstNameRef.value, lastname: surnameRef.value, @@ -62,6 +102,7 @@ const handleSubmit = async () => { await router.push('/configuration') } catch (error) { errorMsg.value = handleUnknownError(error); + console.log(error) } } } @@ -83,19 +124,19 @@ const handleSubmit = async () => { id="firstNameInput" input-id="first-name" type="text" - pattern="^[^\d]+$" + pattern="^(?=.{4,16}$)[^\d]+$" label="Fornavn" placeholder="Skriv inn ditt fornavn" - invalid-message="Ugyldig fornavn, husk ingen tall"/> + 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="^[^\d]+$" + pattern="^(?=.{4,16}$)[^\d]+$" label="Etternavn" placeholder="Skriv inn ditt etternavn" - invalid-message="Ugyldig etternavn, husk ingen tall"/> + invalid-message="Ugyldig etternavn, må være mellom 4 og 16 bokstaver"/> <BaseInput :model-value="emailRef" @input-change-event="handleEmailInputEvent" id="emailInput" diff --git a/src/components/UserProfile/ExternalProfile.vue b/src/components/UserProfile/ExternalProfile.vue index 2ab97adf0a3cab7d14e6145db480246453727d5c..0baf368becd7ce4f3bd4129afd498bccabfa6d21 100644 --- a/src/components/UserProfile/ExternalProfile.vue +++ b/src/components/UserProfile/ExternalProfile.vue @@ -26,6 +26,12 @@ const streak = ref(0 as any); const isFriend = ref(false); const isRequestSent = ref(false); +/** + * Sets up the form for displaying user profile information. + * Retrieves user profile data including first name, last name, points, streak, profile image, inventory, and badges. + * Populates the form fields with the retrieved data. + * Fetches the user's inventory and badges. + */ async function setupForm() { try { let id = route.params.id as any; @@ -52,6 +58,10 @@ async function setupForm() { } } +/** + * Checks if the current user is a friend of the user whose profile is being viewed. + * 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(); @@ -62,6 +72,10 @@ const checkIfFriend = async () => { }); }; +/** + * Retrieves the user's inventory by user ID. + * Updates the inventory value and sets the hasInventory value based on the retrieved inventory data. + */ const getInventory = async () => { try { let id = route.params.id as any @@ -79,6 +93,10 @@ const getInventory = async () => { } } +/** + * Retrieves the badges unlocked by the user. + * Updates the badges value and sets the hasBadges value based on the retrieved badge data. + */ const getBadges = async () => { try { let id = route.params.id as any diff --git a/src/components/UserProfile/MyProfile.vue b/src/components/UserProfile/MyProfile.vue index 20c1aa5dc09fad57a39f19bcd6e44d3d002b3fa0..20ad0b5f520a020500cdee72a6ed1fe71b9ff073 100644 --- a/src/components/UserProfile/MyProfile.vue +++ b/src/components/UserProfile/MyProfile.vue @@ -1,11 +1,17 @@ <script setup lang="ts"> -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, type UserUpdateDTO } from "@/api"; +import { onMounted, ref } from 'vue' +import { useRouter } from 'vue-router' +import { + type BadgeDTO, + BadgeService, + type GoalDTO, + GoalService, + ItemService, + UserService, + type UserUpdateDTO +} from '@/api' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -import bannerImage from '@/assets/banners/stacked.svg'; +import bannerImage from '@/assets/banners/stacked.svg' let apiUrl = import.meta.env.VITE_APP_API_URL; let numberOfHistory = 6; @@ -29,23 +35,27 @@ const streak = ref(0 as any); let goals = ref<GoalDTO[]>([]) +/** + * Retrieves the user's goals from the server. + * Updates the goals value with the retrieved data. + * Sets the hasHistory value based on whether goals are present or not. + */ 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') - } + hasHistory.value = goals.value.length > 0; }catch (error){ handleUnknownError(error) console.error("Something went wrong", error) } } +/** + * Sets up the form for displaying user profile information. + * Retrieves user profile data including first name, last name, points, streak, profile image, inventory, and badges. + * Populates the form fields with the retrieved data. + * Fetches the user's inventory and badges. + */ async function setupForm() { try { const response = await UserService.getUser(); @@ -74,38 +84,43 @@ async function setupForm() { } } +/** + * Retrieves the user's inventory from the server. + * Updates the inventory value with the retrieved data. + * Sets the hasInventory value based on whether inventory items are present or not. + */ 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') - } + inventory.value = await ItemService.getInventory(); + hasInventory.value = inventory.value.length > 0; } catch (error) { handleUnknownError(error) console.log(error); } } + +/** + * Retrieves the badges unlocked by the active user. + * Updates the badges value with the retrieved data. + * Sets the hasBadges value based on whether badges are present or not. + */ const getBadges = async () => { try { - const responseBadge = await BadgeService.getBadgesUnlockedByActiveUser(); - badges.value = responseBadge; - if (badges.value.length > 0) { - hasBadges.value = true - } else { - hasBadges.value = false - console.log('No history') - } + badges.value = await BadgeService.getBadgesUnlockedByActiveUser(); + hasBadges.value = badges.value.length > 0; } catch (error) { handleUnknownError(error) console.log(error); } } +/** + * Updates the selected item in the UI. + * Sets the backgroundName value with the item's name. + * + * @param {any} item - The selected item object. + */ const selectItem = (item: any) => { try { backgroundName.value = item.itemName; @@ -123,24 +138,29 @@ const selectItem = (item: any) => { } } +/** + * Sets up the profile form and retrieves user goals upon component mounting. + */ onMounted(() => { setupForm() getGoals() }) +/** + * Redirects the user to the roadmap page. + */ const toRoadmap = () => { router.push('/'); }; -// Function to navigate to update user settings +/** + * Redirects the user to the update user settings page. + */ const toUpdateUserSettings = () => { router.push('/settings/profile'); }; - - - </script> <template> diff --git a/src/stores/BudgetStore.ts b/src/stores/BudgetStore.ts index 0e6fce496f67816f6697d5cac49cd1b7750ec0a3..60fce67358d1bfdcaf9cac337e59bd8f32296f12 100644 --- a/src/stores/BudgetStore.ts +++ b/src/stores/BudgetStore.ts @@ -1,15 +1,28 @@ import { defineStore } from 'pinia' +/** + * Represents the store for managing budget-related state. + */ export const useBudgetStore = defineStore('BudgetStore', { state: () => ({ + /** The ID of the active budget. */ activeBudgetId: 0, }), actions: { + /** + * Sets the active budget ID. + * @param {number} id - The ID of the active budget. + */ setActiveBudgetId(id: number) { this.activeBudgetId = id } }, getters: { + /** + * Retrieves the active budget ID. + * + * @returns {number} The ID of the active budget. + */ getActiveBudgetId(): number { return this.activeBudgetId } diff --git a/src/stores/ConfigurationStore.ts b/src/stores/ConfigurationStore.ts index c1923a7d0fe703b0b787407589605ae42ff6a4ba..e0fd57fe25b56583f75c895407209fcf6dd0417d 100644 --- a/src/stores/ConfigurationStore.ts +++ b/src/stores/ConfigurationStore.ts @@ -1,28 +1,65 @@ import { defineStore } from 'pinia' + +/** + * Represents the store for managing configuration-related state. + */ export const useConfigurationStore = defineStore('ConfigurationStore', { state: () => ({ + /** The Basic Bank Account Number in the checking account. */ chekingAccountBBAN: 0, + /** The Basic Bank Account Number in the savings account. */ savingsAccountBBAN: 0, + /** The user's commitment. */ commitment: '', + /** The user's experience. */ experience: '', + /** The challenges the user is facing. */ 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; }, + /** + * 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) { this.savingsAccountBBAN = newValue }, + /** + * Sets the user's commitment. + * + * @param {string} commitment - The user's commitment. + */ setCommitment(commitment: string) { this.commitment = commitment }, + /** + * Sets the user's experience. + * + * @param {string} experience - The user's experience. + */ setExperience(experience: string) { this.experience = experience }, + /** + * Sets the challenges the user is facing. + * + * @param {Array<string>} challenges - An array of challenges. + */ setChallenges(challenges: Array<string>) { this.challenges = challenges }, + /** + * Resets the configuration state. + */ resetConfiguration() { this.commitment = '' this.experience = '' @@ -30,18 +67,43 @@ 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 { return this.chekingAccountBBAN }, + /** + * Retrieves the Basic Bank Account Number of the savings account. + * + * @returns {number} The amount in the savings account. + */ getSavingsAccountBBAN(): number { return this.savingsAccountBBAN }, + /** + * Retrieves the user's commitment. + * + * @returns {string} The user's commitment. + */ getCommitment(): string { return this.commitment }, + /** + * Retrieves the user's experience. + * + * @returns {string} The user's experience. + */ getExperience(): string { return this.experience }, + /** + * Retrieves the challenges the user is facing. + * + * @returns {Array<string>} An array of challenges. + */ getChallenges(): Array<string> { return this.challenges } diff --git a/src/stores/ErrorStore.ts b/src/stores/ErrorStore.ts index 32ffb91af9d857c3ad747853265724d3843f4ea4..f67ec9e9d978ec89ba53eab3750227e82307c91f 100644 --- a/src/stores/ErrorStore.ts +++ b/src/stores/ErrorStore.ts @@ -1,15 +1,27 @@ import { defineStore } from 'pinia'; -// A userstore which can be used to store several errors at the same time +/** + * Representing the store for managing error-related state. + */ export const useErrorStore = defineStore('ErrorStore', { state: () => ({ + /** Array containing multiple error messages. */ errors: [] as string[], }), actions: { + /** + * Adds an error to the error array. + * Logs the error message. + * + * @param {string} error - The error message to add. + */ addError(error: string) { console.log(error); this.errors = [error]; }, + /** + * Removes the first error from the error array. + */ removeCurrentError() { if (this.errors.length > 0) { this.errors.shift(); @@ -17,12 +29,22 @@ export const useErrorStore = defineStore('ErrorStore', { }, }, getters: { + /** + * Retrieves the first error message in the error array. + * + * @returns {string} The first error message. + */ getFirstError(): string { if (this.errors.length > 0) { return `Exceptions.${this.errors[0]}`; } return ''; }, + /** + * Retrieves the last error message in the error array. + * + * @returns The last error message. + */ getLastError(): string { if (this.errors.length > 0) { return `Exceptions.${this.errors[this.errors.length - 1]}`; diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index a3fd5a3d7f772fd3133e3e7b1f98332708a44ee9..9b0c51eaf2fa0ab30f87b6f495f0a14434582ca8 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -2,6 +2,9 @@ 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 }); @@ -28,6 +31,9 @@ const cookiesStorage: Storage = { }, }; +/** + * Interface representing user store information. + */ export type UserStoreInfo = { id?: number; email?: string; @@ -43,27 +49,50 @@ export type UserStoreInfo = { export const useUserInfoStore = defineStore('UserInfoStore', { state: () => ({ + /** User ID. */ id: 0, + /** User email. */ email: '', + /** User first name. */ firstname: '', + /** User last name. */ lastname: '', + /** User password. */ password: '', + /** User access token. */ accessToken: '', + /** User role. */ role: '', + /** User subscription level. */ subscriptionLevel: '', + /** User road background. */ roadBackground: 0, + /** User profile image. */ profileImage: 0, }), persist: { storage: cookiesStorage, }, actions: { + /** + * Sets the user password. + * + * @param {string} password - The user password. + */ setPassword(password: string) { this.password = password }, + /** + * Resets the user password. + */ resetPassword() { this.password = '' }, + /** + * Sets the user information. + * + * @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); @@ -76,6 +105,9 @@ export const useUserInfoStore = defineStore('UserInfoStore', { 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 = ''; @@ -90,24 +122,59 @@ export const useUserInfoStore = defineStore('UserInfoStore', { }, }, getters: { + /** + * Retrieves the user password. + * + * @returns {string} The user password. + */ getPassword(): string { return this.password }, + /** + * Retrieves the user's first name. + * + * @returns {string} The user's first name. + */ getFirstName(): string { return this.firstname }, + /** + * Retrieves the user's last name. + * + * @returns {string} The user's last name. + */ getLastname(): string { return this.lastname }, + /** + * Retrieves the user's email. + * + * @returns {string} The user's email. + */ getEmail(): string { return this.email }, + /** + * Checks if the user is logged in. + * + * @returns {boolean} A boolean indicating if the user is logged in. + */ isLoggedIn(): boolean { return this.accessToken !== ''; }, + /** + * Checks if the user has a premium subscription. + * + * @returns {boolean} A boolean indicating if the user has a premium subscription. + */ isPremium(): boolean { return this.subscriptionLevel === 'PREMIUM'; }, + /** + * Checks if the user has an ad-free subscription. + * + * @returns {boolean} A boolean indicating if the user has an ad-free subscription. + */ isNoAds(): boolean { return this.subscriptionLevel === 'NO_ADS'; } diff --git a/src/views/BankID/RedirectView.vue b/src/views/BankID/RedirectView.vue index 56b8d8b888bc4d4b0d3f7c411adfd5396aa308f4..fc3691d6d6edf4e43e19a5d32a6b3ee4ade6683d 100644 --- a/src/views/BankID/RedirectView.vue +++ b/src/views/BankID/RedirectView.vue @@ -6,7 +6,14 @@ import axios from 'axios' import router from '@/router' let apiUrl = import.meta.env.VITE_APP_API_URL; + +/** + * Retrieves the authorization code and state from the URL parameters, + * then calls the 'exchangeCodeForToken' function with the code and state if they are present. + * If the code or state is missing, it logs an error. + */ onMounted(() => { + // Extract query parameters from the URL const query = new URLSearchParams(window.location.search); const code = query.get('code'); const state = query.get('state'); @@ -18,6 +25,13 @@ onMounted(() => { } }); +/** + * Exchanges an authorization code and state for an authentication token. + * Upon successful authentication, updates the authentication token and user information in the store, and navigates to the home page. + * + * @param {string} code - The authorization code received from the OAuth2 authorization server. + * @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 => {