diff --git a/README.md b/README.md index fb1f6eb8b91e5edb9de574056e10fdd8856ebce7..d75ae0c4ca45ebdfd33b67d37211cb0f45aa10cb 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,50 @@ -# frontend +# BoCo - Frontend + + +<img src="public/favicon.ico" height="100" title="hover text"> + +## Table of contents +1. [Installation](#project-setup) +1. [Running the project](#running-the-project) +1. [Running tests](#running-tests) ## Project setup + +To run the frontend node.js is required. +- Installation links + - [node.js](https://nodejs.org/en/download/) + +Install required dependencies by running ``` npm install ``` -### Compiles and hot-reloads for development -``` -npm run serve -``` +The frontend cannot run by itself and requires a backend service. The URL to this backend service needs to be provided in the environmental variables. This can be done in these steps -### Compiles and minifies for production -``` -npm run build -``` +- Create a file with the name ``.env`` in the root folder +- If the backend service uses the default configuration insert this into the file + ``` + VUE_APP_BASEURL=http://localhost:3000/api/ + ``` -### Run your unit tests -``` -npm run test:unit -``` -### Lints and fixes files +## Running the project +- To compile and run with hot-reloads for development + ``` + npm run serve + ``` + +- To compile and minify project for prodoction + ``` + npm run build + ``` + +## Running tests +To run the tests run the command: ``` -npm run lint +npm run test:unit ``` +This will run all the tests and provide a coverage report. Due to a bug with jest and vue there are tests and files missing in the coverage report. This is due to jest skipping files with ``ES6`` import. ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/src/components/ChatComponents/RentalMessage.vue b/src/components/ChatComponents/RentalMessage.vue index ba86c9f14a778c7767390a9c5c54e4aa7dba7226..c864d36bff56d845b8352e2664afcb4dd4f50ae6 100644 --- a/src/components/ChatComponents/RentalMessage.vue +++ b/src/components/ChatComponents/RentalMessage.vue @@ -1,6 +1,6 @@ <template> <div class="message-container"> - <div class="message"> + <div class="message bg-gray-100 ring-1 ring-gray-300"> <div class="info"> <div class="text"> <h2 class="header">Ny utleie forespørsel</h2> @@ -100,12 +100,14 @@ export default { null, { headers: tokenHeader() } ); + this.$router.go(0); }, async reject() { await axios.delete( process.env.VUE_APP_BASEURL + `renting/${this.rent.rentId}/delete`, { headers: tokenHeader() } ); + this.$router.go(0); }, async getImage() { let images = await getItemPictures(this.rent.listingId); @@ -135,7 +137,6 @@ export default { display: block; flex-direction: column; width: 100%; - background: #d1d5db; border-radius: 10px; padding: 10px; max-width: 50%; diff --git a/src/components/CommunityComponents/CommunityHome.vue b/src/components/CommunityComponents/CommunityHome.vue index 2e09b5104951dba94cbb936cb63c1a5f9e4d70ff..57ba73f12d40228d70bfc16a3effe1dcb28524fe 100644 --- a/src/components/CommunityComponents/CommunityHome.vue +++ b/src/components/CommunityComponents/CommunityHome.vue @@ -164,7 +164,7 @@ export default { } }, goToItemInfoPage(item) { - this.$router.push("/itempage/" + item); + this.$router.push("/item/" + item); }, getItemPictures: async function (itemid) { let res = await getItemPictures(itemid); diff --git a/src/components/CommunityComponents/CommunityList.vue b/src/components/CommunityComponents/CommunityList.vue index 8354bd25ce4cd34d3644522768e19a6298e89ad6..136e50e6b7e9e26fd999f4575e84124185dd0bf8 100644 --- a/src/components/CommunityComponents/CommunityList.vue +++ b/src/components/CommunityComponents/CommunityList.vue @@ -1,6 +1,9 @@ <template> <!-- A list conatining all the communities --> - <p v-if="!communities.length" class="flex place-content-center text-gray-400"> + <p + v-if="!communities.length" + class="flex place-content-center text-gray-400 mt-8" + > Ingen grupper </p> <ul v-else> diff --git a/src/components/CommunityComponents/CommunityListItem.vue b/src/components/CommunityComponents/CommunityListItem.vue index 8001e01a3aaba188a781c75428ecfa28a02bbd72..bdbc170a9eb0dd0de0fb8a6ce1a1c6737de88325 100644 --- a/src/components/CommunityComponents/CommunityListItem.vue +++ b/src/components/CommunityComponents/CommunityListItem.vue @@ -54,10 +54,7 @@ v-if="!community.picture" class="h-10 w-10 flex flex-col justify-center items-center ml-2 mr-2" > - <UserGroupIcon - alt="Felleskapets bilde" - class="h-10 w-10 text-primary-dark" - /> + <UserGroupIcon alt="Bilde" class="h-10 w-10 text-primary-dark" /> </div> <div v-else @@ -65,8 +62,8 @@ > <img :src="community.picture" - alt="Fellsekaps bilde" - class="rounded-md" + alt="Bilde" + class="rounded-md h-14 w-14 object-contain" /> </div> <div class="flex-1 pl-1 overflow-hidden"> diff --git a/src/components/ItemComponents/EditItemForm.vue b/src/components/ItemComponents/EditItemForm.vue index 0283428444580fbf70c6aa9fb6ee525cffbedbf9..23f9bdc9fb01f6654f94f808567961ed0e278b71 100644 --- a/src/components/ItemComponents/EditItemForm.vue +++ b/src/components/ItemComponents/EditItemForm.vue @@ -1,4 +1,5 @@ <template> + <!-- Form for editing an item --> <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > @@ -355,6 +356,10 @@ export default { return true; }, + /** + * Validation gets checked, and if it returns true + * the item and the images gets updated. + */ async saveClicked() { if (this.checkValidation()) { let itemInfo = { @@ -372,10 +377,16 @@ export default { this.initialItem.listingID, this.updatedItem.images ); - this.$router.push("/itempage/" + this.initialItem.listingID); + this.$router.push("/item/" + this.initialItem.listingID); } }, + /** + * Adds image when an image is selected from file explorer. + * Posts it to the db and gets the id of the image posted in return. + * Adds that id to an image URL and saves it in an array. + * That array containing image URLs gets posted to the db when save is clicked. + */ async addImage(event) { var that = this; let image = event.target.files[0]; @@ -390,6 +401,11 @@ export default { fileReader.readAsArrayBuffer(image); }, + /** + * Runs every time a chech box under 'grupper' is changed(checked/unchecked). + * Finds out if it was checked or unchecked and adds/removes the community from + * the array based on that. + */ onChangeCommunity(e) { this.updatedItem.selectedCommunityId = e.target.value; let alreadyInGroupList = false; @@ -417,11 +433,19 @@ export default { } }, + /** + * Updates the selected category when it gets changed changes. + */ onChangeCategory(e) { this.updatedItem.selectedCategory = e.target.value; this.updatedItem.selectedCategories = [e.target.value]; }, + /** + * pre-selects (check marks) the community/communities the item + * is posted in so the user can see where the item already is posted and + * then change the community/communities + */ isInSelectedCommunity(id) { for (let i in this.updatedItem.selectedCommunities) { if (this.updatedItem.selectedCommunities[i] == id) { @@ -430,6 +454,10 @@ export default { } return false; }, + + /** + * Removes image from item + */ async removeImage(image) { let newImages = []; for (let i in this.updatedItem.images) { @@ -441,6 +469,10 @@ export default { }, }, + /** + * Gets the item before the page gets mounted so the item info + * is filled in and ready to be displayed to user. + */ async beforeCreate() { let itemID = await this.$router.currentRoute.value.params.id; let item = await ListingService.getItem(itemID); diff --git a/src/components/ItemComponents/ItemCard.vue b/src/components/ItemComponents/ItemCard.vue index b8125f4f70bcdfd289c1c5437ee9a31764911793..154468723a48de6ecb13ded2a9e06ae6ccf16dce 100644 --- a/src/components/ItemComponents/ItemCard.vue +++ b/src/components/ItemComponents/ItemCard.vue @@ -1,11 +1,12 @@ <template> + <!-- Item card, displays title, address, price per day and a picture --> <div class="mt-5 px-5"> <div - class="w-full h-full rounded bg-gray-200 overflow-hidden display:inline-block correct-size" + class="w-full h-full rounded bg-gray-100 ring-1 ring-gray-200 overflow-hidden display:inline-block correct-size cursor-pointer" > <div class="relative h-0 pb-[66.7%]"> <img - class="w-full h-full absolute inset-0" + class="w-full h-full object-contain absolute" :src="item.img || require('../../assets/default-product.png')" alt="Item image" /> diff --git a/src/components/ItemComponents/NewItemForm.vue b/src/components/ItemComponents/NewItemForm.vue index 36c84f664967cbb685a593cf64b237d49f30e9af..3ef622f2dfe0e9fa3cbb8dcbca343f5bf9c3bfac 100644 --- a/src/components/ItemComponents/NewItemForm.vue +++ b/src/components/ItemComponents/NewItemForm.vue @@ -1,4 +1,5 @@ <template> + <!-- Form for adding a new item --> <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > @@ -58,7 +59,7 @@ </option> </select> - <!-- error message for select box --> + <!-- error message for select category --> <div class="text-error-medium" v-for="(error, index) of v$.item.select.$errors" @@ -70,7 +71,7 @@ </div> </div> - <!-- Grupper --> + <!-- Community --> <div class="mb-6"> <label class="block text-sm font-medium text-gray-900 dark:text-gray-400" >Grupper</label @@ -95,9 +96,9 @@ </ul> </div> <!-- Error message for community --> - <label class="text-error-medium text-sm block">{{ - groupErrorMessage - }}</label> + <label class="text-error-medium text-sm block"> + {{ groupErrorMessage }} + </label> </div> <!-- price --> @@ -105,7 +106,7 @@ <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" id="priceLabel" - >Pris</label + >Pris per dag</label > <input type="number" @@ -198,8 +199,10 @@ accept="image/png" /> + <!-- Opens file explorer --> <colored-button :text="'Velg bilde'" @click="$refs.file.click()" /> + <!-- Shows chosen images --> <div v-for="image in item.images" :key="image" class="m-2"> <form-image-display :image="image" @remove="removeImage(image)" /> </div> @@ -307,7 +310,6 @@ export default { selectedGroupId: -1, selectedGroups: [], }, - //Kategorier skal legges inn ved api/hente fra db, her må det endres etterhvert categories: [ "Antikviteter og kunst", "Dyr og utstyr", @@ -326,6 +328,11 @@ export default { }; }, methods: { + /** + * Checks validation. Checks also if any community is selected. + * If no community is selected or any other field isn't valid + * false is returned, otherwise true is returned. + */ checkValidation: function () { this.v$.item.$touch(); if (this.v$.item.$invalid || this.item.selectedGroups.length === 0) { @@ -337,6 +344,11 @@ export default { return true; }, + /** + * When save is clicked, the validation gets checked. If validated, + * the user is parsed from the token to get the userId. Then the item + * and the pictures are posted to db. + */ async saveClicked() { if (this.checkValidation()) { this.checkUser(); @@ -358,11 +370,20 @@ export default { } }, + /** + * Parses user from token and uses it when posting item in saveClciked method + */ checkUser: async function () { let user = parseUserFromToken(this.$store.state.user.token); this.item.userId = parseInt(user.accountId); }, + /** + * Adds image when an image is selected from file explorer. + * Posts it to the db and gets the id of the image posted in return. + * Adds that id to an image URL and saves it in an array. + * That array containing image URLs gets posted to the db when save is clicked. + */ addImage: async function (event) { var that = this; let image = event.target.files[0]; @@ -377,6 +398,11 @@ export default { fileReader.readAsArrayBuffer(image); }, + /** + * Runs every time a chech box under 'grupper' is changed(checked/unchecked). + * Finds out if it was checked or unchecked and adds/removes the community from + * the array based on that. + */ onChangeGroup: function (e) { this.selectedGroupId = e.target.value; let alreadyInGroupList = false; diff --git a/src/components/ItemComponents/SearchItemList.vue b/src/components/ItemComponents/SearchItemList.vue index 00ed0583f51be18a36e249fb9e04a599a840d862..7ba86f608346b526487791f2cdaf8c32733e2fec 100644 --- a/src/components/ItemComponents/SearchItemList.vue +++ b/src/components/ItemComponents/SearchItemList.vue @@ -1,4 +1,5 @@ <template> + <!-- A template for searching in item list --> <section class="relative w-full max-w-md px-5 py-4 mx-auto rounded-md"> <div class="relative" id="searchComponent"> <span class="absolute inset-y-0 left-0 flex items-center pl-3"> @@ -17,7 +18,7 @@ type="text" id="searchInput" class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" - placeholder="Search" + placeholder="Søk" v-model="search" /> </div> @@ -54,12 +55,6 @@ export default { }, }, - /** - * Her må det lages en metode som henter alle items (i en gruppe) fra databasen. - * De kan deretter bli pusha inn i items array, og da burde de bli displayet i lista. - * Når denne metoden er på plass kan items[] i data tømmes. Da vil alt dataen komme fra db. - */ - data() { return { items: [ diff --git a/src/components/RentingComponents/ItemInfo.vue b/src/components/RentingComponents/ItemInfo.vue index beee721738d6feb6896c2c15cbb65ab6f9dda62f..c8637741f83349dccec7ccb99ab25a4142fe537e 100644 --- a/src/components/RentingComponents/ItemInfo.vue +++ b/src/components/RentingComponents/ItemInfo.vue @@ -51,10 +51,11 @@ </p> </div> </div> - <div class="mt-2"> + <div class="mt-2" v-if="userForId"> <UserListItemCard :buttons="['chat']" :user="userForId" + :rating="rating" ></UserListItemCard> </div> <div class="mt-4"> @@ -75,6 +76,7 @@ <p class="text-xl font-semibold text-gray-900"> Total pris: {{ totPrice }} kr </p> + <p v-if="error" class="text-error-medium">Dato er påkrevd</p> <button class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-gray-500 rounded-md focus:outline-none focus:ring focus:ring-opacity-80" v-bind:class="{ colorChange: allowForRent }" @@ -107,6 +109,7 @@ export default { data() { return { confirm: false, + error: false, item: { listingID: 0, title: "", @@ -140,6 +143,7 @@ export default { dateMessage: "Venligst velg dato for leieperioden", allowForRent: false, nonAvailableTimes: [], + rating: 0, }; }, components: { @@ -153,6 +157,8 @@ export default { if (this.allowForRent) { this.confirm = true; this.createPushItem(); + } else { + this.error = true; } }, createPushItem() { @@ -212,18 +218,32 @@ export default { this.rentingEndDate = dateOfsomthing.endDate; this.calculateTotPrice(); this.allowForRent = true; + this.error = false; } }, calculateTotPrice() { let amountOfDays = this.rentingEndDate - this.rentingStartDate; - amountOfDays = amountOfDays / 86400000; + amountOfDays = Math.ceil(amountOfDays / 86400000); this.totPrice = this.item.pricePerDay * amountOfDays; }, + async getUserRating() { + let maybeRating = await UserService.getUserRatingAverage( + this.userForId.userId + ); + if (isNaN(maybeRating)) { + this.rating = NaN; + } else { + this.rating = maybeRating; + if (this.rating > 5) this.rating = 5; + else if (this.rating < 1) this.rating = 1; + } + }, }, - async beforeMount() { + async created() { await this.getItemPictures(); await this.getItem(); await this.getUser(this.item.userID); + await this.getUserRating(); await this.getAvailableTimesForListing(); }, }; diff --git a/src/components/RentingComponents/NewRent.vue b/src/components/RentingComponents/NewRent.vue index f0609f0ccaa7515c53d4839578ae77dca6bd510b..5b2647e4dbe76e592887f7464862293cc07792d3 100644 --- a/src/components/RentingComponents/NewRent.vue +++ b/src/components/RentingComponents/NewRent.vue @@ -42,6 +42,7 @@ </div> <div> <notification-modal + id="notification-modal" @click="routeToChat" :visible="confirmed" :title="'Vellykket'" diff --git a/src/components/UserAuthComponents/DeleteUserModal.vue b/src/components/UserAuthComponents/DeleteUserModal.vue index df77c6e66c069e6315707f4c648f4386dc2abcab..9fc855e945f9938320d16e340651446fa2c7f3cc 100644 --- a/src/components/UserAuthComponents/DeleteUserModal.vue +++ b/src/components/UserAuthComponents/DeleteUserModal.vue @@ -1,5 +1,5 @@ <template> - <!-- Main modal --> + <!-- Main modal for deleting a user --> <div v-if="visible" class="fixed grid place-items-center bg-gray-600 bg-opacity-50 top-0 left-0 right-0 z-50 w-full overflow-x-hidden overflow-y-auto inset-0 h-full" diff --git a/src/components/UserAuthComponents/LoginForm.vue b/src/components/UserAuthComponents/LoginForm.vue index ac4ab429ff491b04f3671995e853f4a70c6d2372..05607b1554f62930d1d7b7aed3e05b1eacf4f9e7 100644 --- a/src/components/UserAuthComponents/LoginForm.vue +++ b/src/components/UserAuthComponents/LoginForm.vue @@ -1,7 +1,9 @@ <template> + <!-- Login form --> <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full" > + <!-- Header --> <div class="px-6 py-4 mt-4"> <div class="text-xl md:text-2xl font-medium text-center text-primary-light mt-4 mb-8" @@ -9,6 +11,7 @@ Logg på </div> + <!-- Email --> <div> <div class="w-full mt-6" @@ -21,7 +24,7 @@ v-model="v$.user.email.$model" required /> - <!-- error message --> + <!-- error message for email --> <div v-for="(error, index) of v$.user.email.$errors" :key="index"> <div class="text-error-medium text-sm" @@ -33,6 +36,7 @@ </div> </div> + <!-- Password --> <div class="w-full mt-6" :class="{ error: v$.user.password.$errors.length }" @@ -45,7 +49,7 @@ @keyup.enter="loginClicked" required /> - <!-- error message --> + <!-- error message for password --> <div class="text-error-medium text-sm" v-for="(error, index) of v$.user.password.$errors" @@ -61,11 +65,13 @@ </div> </div> + <!-- Router link for forgetting password. Redirects to reset password page. --> <div class="flex items-center justify-between mt-8"> <router-link to="/resetPassword" class="text-primary-medium" >Glemt passord?</router-link > + <!-- Button for logging in --> <Button class="login" @click="loginClicked" @@ -78,6 +84,7 @@ <div class="flex items-center justify-center py-4 text-center bg-gray-50 dark:bg-gray-700" > + <!-- Router link to redirect to registering a new user page --> <router-link to="/register" class="mx-2 text-sm font-bold text-primary-medium dark:text-primary-light hover:underline" @@ -85,6 +92,7 @@ > </div> <div class="flex items-center justify-center text-center bg-gray-50"> + <!-- Shows a message to user if login fails --> <label class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" >{{ message }}</label @@ -122,7 +130,6 @@ export default { }, }; }, - data() { return { message: "", @@ -136,6 +143,12 @@ export default { }, methods: { + /** + * When login clicked, the inputs gets validated. If validation goes + * through loginRequest gets posted to api. If it returns false, + * user gets a message saying the email or password is wrong. If it + * returns true, the token returned gets saved to the state. + */ async loginClicked() { this.showError = true; diff --git a/src/components/UserAuthComponents/NewPasswordForm.vue b/src/components/UserAuthComponents/NewPasswordForm.vue index 7dfd1958ee78aca0bd7e1720b939d4c340ba6e9c..4adb53500baaabb5030968f6e2d1cb0916d5c0ea 100644 --- a/src/components/UserAuthComponents/NewPasswordForm.vue +++ b/src/components/UserAuthComponents/NewPasswordForm.vue @@ -1,19 +1,21 @@ <template> + <!-- A form for changing password --> <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > + <!-- Header --> <h3 class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" > Endre passord </h3> + <!-- Input field for old password --> <div id="oldPasswordField" :class="{ error: v$.user.oldPassword.$errors.length }" > <label - for="oldPassword" class="block text-sm text-gray-800 dark:text-gray-200" >Gammelt passord</label > @@ -22,7 +24,7 @@ v-model="v$.user.oldPassword.$model" class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" /> - <!-- error message --> + <!-- error message for password --> <div v-for="(error, index) of v$.user.oldPassword.$errors" :key="index"> <div class="text-error-medium text-sm" @@ -34,13 +36,13 @@ </div> </div> + <!-- New password --> <div id="firstPasswordField" class="mt-4" :class="{ error: v$.user.password.$errors.length }" > <label - for="password" class="block text-sm text-gray-800 dark:text-gray-200" >Nytt passord</label > @@ -49,7 +51,7 @@ v-model="v$.user.password.$model" class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" /> - <!-- error message --> + <!-- error message for password --> <div v-for="(error, index) of v$.user.password.$errors" :key="index"> <div class="text-error-medium text-sm" @@ -61,6 +63,7 @@ </div> </div> + <!-- Repeating new password --> <div id="secondPasswordField" class="mt-4" @@ -68,7 +71,6 @@ > <div class="flex items-center justify-between"> <label - for="rePassword" class="block text-sm text-gray-800 dark:text-gray-200" >Gjenta nytt passord</label > @@ -79,7 +81,7 @@ v-model="v$.user.rePassword.$model" class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" /> - <!-- error message --> + <!-- error message for password --> <div v-for="(error, index) of v$.user.rePassword.$errors" :key="index"> <div class="text-error-medium text-sm" @@ -91,6 +93,7 @@ </div> </div> + <!-- Button --> <div id="buttonsField" class="mt-6"> <Button class="float-right" @@ -98,6 +101,8 @@ :text="'Sett ny passord'" /> </div> + + <!-- Message for user --> <div class="flex items-center justify-center text-center bg-gray-50"> <label class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" @@ -162,6 +167,11 @@ export default { token: (state) => state.user.token, }), methods: { + /** + * Validates the form. If it goes through, sends a password change + * request to api and gives user a message if it does not get changed. + * If changed, saves new token to state. + */ async setNewPassword() { this.showError = true; diff --git a/src/components/UserAuthComponents/RegisterForm.vue b/src/components/UserAuthComponents/RegisterForm.vue index da8011bdce8f479730d0e8e16b082a09842243ba..b47d6ad769397bfda1e294f0b1cc697c2b584533 100644 --- a/src/components/UserAuthComponents/RegisterForm.vue +++ b/src/components/UserAuthComponents/RegisterForm.vue @@ -1,4 +1,5 @@ <template> + <!-- Register form for creating a new user account --> <div class="w-full max-w-md mx-auto mb-auto md:ring-1 ring-gray-300 overflow-hidden rounded-xl p-4" > @@ -12,6 +13,7 @@ <form @submit.prevent> <div class="grid grid-cols-1 gap-6 mt-4"> <div> + <!-- Email --> <input class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" v-model="email" @@ -19,23 +21,23 @@ type="email" placeholder="E-post adresse" /> - <!-- error message --> + <!-- error message for email --> <div class="text-error-medium text-sm" v-for="(error, index) of v$.email.$errors" :key="index" > - <div - class="text-error-medium text-sm" - v-show="showError" - id="emailErrorId" - > + <div class="text-error-medium text-sm" v-show="showError"> {{ error.$message }} </div> </div> + <div class="text-error-medium text-sm" v-show="errorMessage"> + {{ errorMessage }} + </div> </div> <div> + <!-- Password --> <input class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" v-model="password" @@ -84,6 +86,7 @@ </div> <div> + <!-- First name --> <input class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" data-test="firstNameTest" @@ -92,7 +95,7 @@ type="text" placeholder="Fornavn" /> - <!-- error message --> + <!-- error message for first name--> <div class="text-error-medium text-sm" v-for="(error, index) of v$.firstName.$errors" @@ -109,6 +112,7 @@ </div> <div> + <!-- Last name --> <input class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" v-model="lastName" @@ -116,7 +120,7 @@ type="text" placeholder="Etternavn" /> - <!-- error message --> + <!-- error message for last name --> <div class="text-error-medium text-sm" v-for="(error, index) of v$.lastName.$errors" @@ -133,6 +137,7 @@ </div> <div> + <!-- Address --> <input class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" v-model="address" @@ -140,7 +145,7 @@ type="text" placeholder="Adresse" /> - <!-- error message --> + <!-- error message for address --> <div class="text-error-medium text-sm" v-for="(error, index) of v$.address.$errors" @@ -166,7 +171,7 @@ <script> import useVuelidate from "@vuelidate/core"; -import { doLogin, registerUser } from "@/utils/apiutil"; +import { doLogin } from "@/utils/apiutil"; import { required, email, @@ -177,8 +182,6 @@ import { import Button from "@/components/BaseComponents/ColoredButton"; import UserService from "@/services/user.service"; -// const isEmailTaken = (value) => -// fetch(`/api/unique/${value}`).then((r) => r.json()); // check the email in the server export default { components: { @@ -202,7 +205,6 @@ export default { email: { required: helpers.withMessage(`Feltet må være utfylt`, required), email: helpers.withMessage("E-posten er ugyldig", email), - // isUnique: helpers.withAsync(isEmailTaken), }, password: { required: helpers.withMessage(`Feltet må være utfylt`, required), @@ -246,10 +248,7 @@ export default { //If a user is created succsessfully, try to login //If we get this far, we will be pushed anyway so there is no point updating "loading" - if (!userCreated) { - this.errorMessage = "Could not create user."; - return; - } + if (!userCreated) return; const loginRequest = { email: this.email, @@ -270,17 +269,16 @@ export default { await this.$router.push("/"); }, async sendRegisterRequest() { - const registerInfo = { + const userInfo = { email: this.email, firstName: this.firstName, - lastname: this.lastName, + lastName: this.lastName, password: this.password, address: this.address, }; - - const response = await registerUser(registerInfo); - - return response.status === 200; + const res = await UserService.registerUser(userInfo); + this.errorMessage = res; + return res.status === 200; }, }, }; diff --git a/src/components/UserProfileComponents/RatingComponents/Rating.vue b/src/components/UserProfileComponents/RatingComponents/Rating.vue index 6efa3b014f4c175b7af950ddb36119a7029f60f9..dc8eee3eefd29f3f2014d8babf87ea3a08cd47d5 100644 --- a/src/components/UserProfileComponents/RatingComponents/Rating.vue +++ b/src/components/UserProfileComponents/RatingComponents/Rating.vue @@ -1,4 +1,5 @@ <template> + <!-- Shows rating, if no rating found, tells the user that no rating is registered --> <ul v-if="isNaN(rating)" class="flex justify-center"> <li> <p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400"> @@ -29,12 +30,6 @@ ></path> </svg> </li> - <li> - <!-- Trenger vi å vise i tekst når vi har stjerner? --> - <!-- <p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400"> - {{ rating }} out of 5 - </p> --> - </li> </ul> </template> @@ -46,8 +41,9 @@ export default { ratingType: String, }, methods: { + //Fills the rating stars getFill(i) { - if (i <= this.rating) { + if (i <= Math.round(this.rating)) { return "w-5 h-5 text-warn-medium"; } return "w-5 h-5 text-gray-300 dark:text-gray-500"; diff --git a/src/components/UserProfileComponents/RatingComponents/RatingModal.vue b/src/components/UserProfileComponents/RatingComponents/RatingModal.vue index 5d028c3cea6b84c151a7e96cc6a3ac46778a96be..c73f67973335cde876b10b3dc9e269f405f6b528 100644 --- a/src/components/UserProfileComponents/RatingComponents/RatingModal.vue +++ b/src/components/UserProfileComponents/RatingComponents/RatingModal.vue @@ -1,5 +1,5 @@ <template> - <!-- Main modal --> + <!-- Main modal for rating --> <div v-if="visible" class="fixed grid place-items-center bg-gray-600 bg-opacity-50 top-0 left-0 right-0 z-50 w-full overflow-x-hidden overflow-y-auto inset-0 h-full" @@ -47,6 +47,7 @@ /> </div> + <!-- 5 rating stars --> <div class="flex items-center justify-center mb-8"> <StarIcon :class="getStarType(0)" @click="setRating(1)"> </StarIcon> <StarIcon :class="getStarType(1)" @click="setRating(2)"> </StarIcon> @@ -55,6 +56,7 @@ <StarIcon :class="getStarType(4)" @click="setRating(5)"> </StarIcon> </div> + <!-- Button for submitting the rating --> <div class="flex justify-center mb-4"> <ColoredButton :text="'Send en vurdering'" @@ -64,7 +66,6 @@ <!-- Modal footer --> <div class="rounded-b border-t border-gray-200 dark:border-gray-600"> - <!-- Slot: Add any html you want here --> <slot /> </div> </div> @@ -108,6 +109,7 @@ export default { StarIcon, }, methods: { + //A method for setting the rating setRating(ratingNumber) { this.score = ratingNumber; for (let i = 0; i < 5; i++) { @@ -124,6 +126,10 @@ export default { close() { this.$emit("close"); }, + /** + * A method for sending the rating when button is clicked. + * It posts the rating to the db. + */ async sendRating() { const ratingInfo = { score: this.score, @@ -134,6 +140,7 @@ export default { await postNewRating(ratingInfo); this.$emit("reload"); + this.$router.go(0); }, }, }; diff --git a/src/components/UserProfileComponents/RentHistoryComponents/RentHistoryItem.vue b/src/components/UserProfileComponents/RentHistoryComponents/RentHistoryItem.vue index c05823dcee3cd9a5bdcaf4661ec727d755346432..c3745f5821a630462d7f553456931eca1e3ba742 100644 --- a/src/components/UserProfileComponents/RentHistoryComponents/RentHistoryItem.vue +++ b/src/components/UserProfileComponents/RentHistoryComponents/RentHistoryItem.vue @@ -1,16 +1,24 @@ <template> + <!-- A card for a rent history containing the item's title, + name of the person who rented it, dates/renting period, and a button + to rate if not already rated it. --> <div class="bg-white shadow dark:bg-gray-800 select-none cursor-pointer hover:bg-gray-50" > + <!-- Title --> <p class="font-bold mx-4" id="title"> {{ historyItem.listing.title }} </p> + + <!-- Name of the user who rented --> <div class="flex flex-row items-center"> <div class="flex flex-col px-4 flex-1"> <router-link :to="{ path: '/profile/' + user.userId }"> Leid til: {{ user.firstName }} {{ user.lastName }} </router-link> </div> + + <!-- Period it was rented for --> <div class="flex flex-col flex-1"> <div> Fra: @@ -20,6 +28,8 @@ Til: {{ this.getDateString(historyItem.toTime, isMultipleDays) }} </div> </div> + + <!-- Button to rate --> <colored-button v-if="!isRated" :text="'Vurder'" @@ -74,12 +84,16 @@ export default { currentUser() { return parseCurrentUser(); }, + //Checks if the rent period was multiple days or not isMultipleDays() { if (this.historyItem.toTime - this.historyItem.fromTime < 86400000) { return false; } return true; }, + /** + * computed the total price based on how long the renting period was. + */ price() { if (this.isMultipleDays) { let numDays = Math.round( @@ -89,11 +103,17 @@ export default { } return this.historyItem.listing.pricePerDay; }, + /** + * Checks if the user rented its own item + */ currentUserIsRenter() { return this.isCurrentUser(this.historyItem.renterId); }, }, methods: { + /** + * returns a date as a string + */ getDateString(milliseconds) { let today = new Date(); let date = new Date(milliseconds); @@ -111,6 +131,9 @@ export default { return id == this.currentUser.accountId; }, }, + /** + * Gets the user and checks if a rating is saved, before mounting the page. + */ async beforeCreate() { if (this.currentUserIsRenter) { this.user = await userService.getUserFromId( diff --git a/src/components/UserProfileComponents/UserItems.vue b/src/components/UserProfileComponents/UserItems.vue index 418a4f22fa9e684959fe6cfa2d5033c7ce5671c3..9a988f48e44e66bfc65d3f786e5c97ffa56cc91c 100644 --- a/src/components/UserProfileComponents/UserItems.vue +++ b/src/components/UserProfileComponents/UserItems.vue @@ -1,9 +1,11 @@ <template> + <!-- Shows all the items a user has posted with search and pagination. + Includes a dropdown menu for editing or deleting an item. --> <div id="headline" class="text-xl md:text-2xl text-primary-light font-medium"> Mine gjenstander </div> <!-- Search field --> - <div class="relative" id="searchComponent"> + <div class="relative mx-4" id="searchComponent"> <span class="absolute inset-y-0 left-0 flex items-center pl-3"> <svg class="w-5 h-5 text-gray-400" viewBox="0 0 24 24" fill="none"> <path @@ -20,12 +22,12 @@ type="text" id="searchInput" class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" - placeholder="Search" + placeholder="Søk" v-model="search" @change="searchWritten" /> </div> - <div class="absolute inset-x-0 px-5 py-3"> + <div class="absolute inset-x-0"> <!-- ItemCards --> <div class="flex items-center justify-center w-screen"> <!-- Shows items based on pagination --> @@ -39,18 +41,20 @@ v-for="item in visibleItems" :key="item" > - <ItemCard - id="ItemCardPage" - class="ItemCard w-fit h-fit" - :item="item" - /> + <div class="w-full"> + <ItemCard + id="ItemCardPage" + class="ItemCard w-full h-full" + :item="item" + /> + </div> - <TripleDotButton class="DotButton" @click="openDotMenu(item)"> - </TripleDotButton> + <!-- Dropdown menu with options for editing an item and deleting an item --> + <TripleDotButton class="DotButton" @click="openDotMenu(item)" /> <div v-show="item.toggle" - class="options z-10 w-44 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700" + class="options z-10 w-44 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 pt-4 pl-12" > <ul class="py-1 absolute bg-white ring-1 ring-gray-300 rounded-xl" @@ -78,6 +82,8 @@ </div> </div> + <!-- A waring asking the user if it is sure it wants to delete the item + with options to go ahead with the deleting or to cancel the delete. --> <CustomFooterModal @close="this.readyToDelete = false" :visible="readyToDelete" @@ -86,14 +92,14 @@ > <div class="flex justify-center p-2"> <ColoredButton - id="#cancelDeleteButton" + id="#cancelDeleteButton1" :text="'Avbryt'" @click="cancelDelete" class="bg-gray-500 m-2" ></ColoredButton> <ColoredButton - id="confirmDeleteButton" + id="confirmDeleteButton1" @click="deleteItem" :text="'Slett'" class="m-2 bg-error-medium" @@ -109,10 +115,16 @@ v-if="showSearchedItems" > <div class="cardContainer" v-for="item in searchedItems" :key="item"> - <ItemCard id="ItemCardSearch" class="ItemCard" :item="item" /> - <TripleDotButton class="DotButton" @click="openDotMenu(item)"> - </TripleDotButton> + <div class="w-full"> + <ItemCard + id="ItemCardSearch" + class="ItemCard w-full h-full" + :item="item" + /> + </div> + <!-- Dropdown menu with options for editing an item and deleting an item --> + <TripleDotButton class="DotButton" @click="openDotMenu(item)" /> <div v-show="item.toggle" class="options z-10 w-44 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700" @@ -142,6 +154,9 @@ </ul> </div> </div> + + <!-- A waring asking the user if it is sure it wants to delete the item + with options to go ahead with the deleting or to cancel the delete. --> <CustomFooterModal @close="this.readyToDelete = false" :visible="readyToDelete" @@ -167,6 +182,7 @@ </CustomFooterModal> </div> </div> + <!-- pagination --> <div class="flex justify-center" v-if="showItems"> <PaginationTemplate @@ -215,6 +231,7 @@ export default { search: "", readyToDelete: false, dropdown: false, + //Variables connected to pagination currentPage: 0, pageSize: 12, @@ -222,6 +239,9 @@ export default { }; }, computed: { + /** + * Searchs items based on their title, address and price per day. + */ searchedItems() { let filteredItems = []; @@ -259,7 +279,10 @@ export default { cancelDelete() { this.readyToDelete = false; }, - //Pagination + /** + * 2 methods related to pagination. Updates page and updates + * visible items on page. + */ updatePage(pageNumber) { this.currentPage = pageNumber; this.updateVisibleTodos(); @@ -275,6 +298,7 @@ export default { this.updatePage(this.currentPage - 1); } }, + goToDeleteItem(item) { this.chosenItem = item; this.readyToDelete = true; @@ -283,8 +307,11 @@ export default { await UserService.setListingToDeleted(this.chosenItem); this.$router.go(0); }, + + /** + * This method triggers when search input field is changed + */ searchWritten: function () { - //This method triggers when search input field is changed if (this.search.length > 0) { this.showItems = false; this.showSearchedItems = true; @@ -294,6 +321,11 @@ export default { } }, }, + + /** + * Gets userlistings and prepares the pagination by + * updating the items to be visible. + */ async beforeMount() { await this.getUserListingsFromAPI(); this.updateVisibleTodos(); @@ -311,6 +343,7 @@ export default { .cardContainer { position: relative; } + .DotButton { position: absolute; right: 40px; diff --git a/src/components/UserProfileComponents/UserListItemCard.vue b/src/components/UserProfileComponents/UserListItemCard.vue index a39ae002e6b52a5547a9c0c198b57481d64faf72..0a6bd91d528dee33666d47cd8498829c6a3bb466 100644 --- a/src/components/UserProfileComponents/UserListItemCard.vue +++ b/src/components/UserProfileComponents/UserListItemCard.vue @@ -1,4 +1,7 @@ <template> + <!-- A card for displaying a user in a list. + Displays a user's profile picture, name, rating and some + buttons based on where the list is being shown. --> <div class="bg-white shadow dark:bg-gray-800 select-none cursor-pointer hover:bg-gray-50 flex items-center p-4" > @@ -62,7 +65,6 @@ <script> import RatingComponent from "@/components/UserProfileComponents/RatingComponents/Rating.vue"; import IconButton from "@/components/BaseComponents/IconButton.vue"; -import UserService from "@/services/user.service"; import CommunityAdminService from "@/services/community-admin.service"; import { @@ -76,7 +78,6 @@ export default { name: "UserListItem", data() { return { - rating: -1.0, communityID: -1, profileImage: { src: require("../../assets/defaultUserProfileImage.jpg"), @@ -94,8 +95,13 @@ export default { props: { user: Object, buttons: Array, + rating: Number, }, computed: { + /** + * returns the user's profile picture. If the user does not have any + * profile picture the default profile picture is returned. + */ getProfilePicture() { if (this.user.picture !== "" && this.user.picture != null) { return this.user.picture; @@ -104,18 +110,25 @@ export default { }, }, methods: { + /** + * If chat button clicked, the user's gets redirected to chat page. + */ openChatWithUser() { this.$router.push({ name: "messages", query: { userID: this.user.userId }, }); }, + + /** + * Admin related methods for kicking a user from a community, + * accepting and rejecting a user's join community request + */ kickUserFromCommunity() { CommunityAdminService.removeUserFromCommunity( this.communityID, this.user.userId ); - //Find a better way to do this window.location.reload(); }, acceptMemberRequest() { @@ -131,14 +144,7 @@ export default { ); }, }, - async beforeMount() { - const maybeRating = await UserService.getUserRatingAverage( - this.user.userId - ); - if (isNaN(maybeRating)) this.rating = NaN; - else this.rating = maybeRating; - if (this.rating > 5) this.rating = 5; - if (this.rating < 1) this.rating = 1; + async beforeMounted() { this.communityID = this.$route.params.communityID; }, }; diff --git a/src/components/UserProfileComponents/UserProfile.vue b/src/components/UserProfileComponents/UserProfile.vue index c2d2a05e395e40893ab49a5693e028dbec97e1e1..3ad810dc8a65986670aac5e217a1fe3f91eafee6 100644 --- a/src/components/UserProfileComponents/UserProfile.vue +++ b/src/components/UserProfileComponents/UserProfile.vue @@ -1,12 +1,17 @@ <template> + <!-- User profile with the logged in user's info and a dropdown menu--> <div class="w-full max-w-xl m-auto md:ring-1 ring-gray-300 overflow-hidden rounded-xl p-4" > + <!-- A warning when deleting a user account to ask the user + if it is sure --> <DeleteUserModal :visible="show" @close="this.show = false" @deleteUser="deleteUser" /> + + <!-- dropdown icon button to toggle(open/close) the dropdown menu --> <div v-show="isCurrentUser" class="float-right px-4 pt-4"> <button id="dropdownDefault" @@ -27,6 +32,8 @@ </svg> </button> + <!-- Dropdown menu containing options for seeing user's items, user's communities, + renting history, logging out, changing password and deleting account --> <div id="dropdown" v-show="dropdown" @@ -38,7 +45,7 @@ > <li> <router-link - to="/user/userItems" + to="/profile/items" class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" >Mine gjenstander</router-link > @@ -84,6 +91,7 @@ </div> </div> + <!-- User info (name, rating and profile picture) --> <div class="flex flex-col items-center pb-10 mt-16 z-5"> <img class="mb-3 w-24 h-24 rounded-full shadow-lg" @@ -95,7 +103,10 @@ </h5> <div> <rating-component :rating="renterRating" :ratingType="'Leietaker'" /> - <rating-component :rating="ownerRating" :ratingType="'Utleier'" /> + <RatingComponent + :rating="ownerRating" + :ratingType="'Utleier'" + ></RatingComponent> </div> <div @@ -153,6 +164,9 @@ export default { }, }, methods: { + /** + * Gets the user and it's ratings + */ async getUser() { this.currentUser = await parseCurrentUser(); this.id = await this.$router.currentRoute.value.params.id; @@ -161,11 +175,13 @@ export default { this.isCurrentUser = true; this.user = this.currentUser; this.user = await UserService.getUserFromId(this.user.accountId); - return; + } else { + this.user = await getUser(this.id); } - this.user = await getUser(this.id); - let ratingAsOwner = await UserService.getUserRatingAsOwner(this.id); - let ratingAsRenter = await UserService.getUserRatingAsRenter(this.id); + let ratingAsOwner = await UserService.getUserRatingAverageOwner(this.id); + let ratingAsRenter = await UserService.getUserRatingAverageRenter( + this.id + ); if (ratingAsOwner >= 0 && ratingAsOwner <= 5) { this.ownerRating = ratingAsOwner; @@ -174,6 +190,10 @@ export default { this.renterRating = ratingAsRenter; } }, + + /** + * Logs out the user and sets token in state to be null. + */ logout() { this.$store.commit("logout"); this.$router.push("/"); @@ -186,8 +206,8 @@ export default { this.logout(); }, }, - beforeMount() { - this.getUser(); + async beforeMount() { + await this.getUser(); }, }; </script> diff --git a/src/router/index.js b/src/router/index.js index 9f7228cc993ef163a3a40f9ea84fe3ce57f3bf2d..7804eca4ed872f85fa920b0ce83cffbdd5daa974 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -3,22 +3,28 @@ import { createRouter, createWebHistory } from "vue-router"; import NotFound from "@/views/NotFound.vue"; /** - * Guards routes. If token is null, no user is logged in and only the - * login page and the register page will be accessible. + * Guard route for logged-in users. + * If token is null, no user is logged in and only the + * login, register, reset password, help and listing of public groups and items pages will be accessible. + * Without a token the user gets redirected to the login page when trying to access guard routed pages. */ -function guardRoute(to, from, next) { +function authGuardRoute(to, from, next) { let isAuthenticated = store.state.user.token != null; if (isAuthenticated) { next(); // allow to enter route } else { - next("/login"); // go to '/login'; + next("/login"); // go to the login page } } -function isAdmin(to, from, next) { +/** + * Guards route for administartors. If token is null or + * the user is not the administrator of the group, the user gets sent to the home page. + */ +function adminGuardRoute(to, from, next) { if (store.state.user.adminList.includes(parseInt(from.params.communityID))) - next(); - else next("/"); + next(); // allow to enter route + else next("/"); // go to the home page } const routes = [ @@ -28,38 +34,44 @@ const routes = [ component: () => import("../views/CommunityViews/CommunityView.vue"), }, { - path: "/help", - name: "help", - component: () => import("../views/HelpView.vue"), + path: "/community/:communityID", + name: "communityHome", + component: () => import("../views/CommunityViews/CommunityHomeView.vue"), }, { - path: "/profile/:id", - name: "profile", - component: () => import("../views/UserProfileViews/ProfileView.vue"), - beforeEnter: guardRoute, + path: "/community/:communityID/admin", + name: "communityAdminPage", + component: () => import("@/views/CommunityViews/AdminView.vue"), + beforeEnter: [authGuardRoute, adminGuardRoute], }, { - path: "/profile/history", - name: "history", - component: () => import("../views/UserProfileViews/RentHistoryView.vue"), - beforeEnter: guardRoute, + path: "/community/:communityID/memberlist", + name: "memberlist", + component: () => import("../views/CommunityViews/MemberListView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/profile/communities", - name: "myCommunities", - component: () => import("../views/UserProfileViews/MyCommunitiesView.vue"), - beforeEnter: guardRoute, + path: "/community/:communityID/private/join", + name: "communityRequest", + component: () => import("../views/CommunityViews/CommunityRequestView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/register", - name: "register", - component: () => import("../views/UserAuthViews/RegisterView.vue"), + path: "/help", + name: "help", + component: () => import("../views/HelpView.vue"), }, { - path: "/messages", - name: "messages", - component: () => import("../views/ChatViews/ChatView.vue"), - beforeEnter: guardRoute, + path: "/item/:id", + name: "itemInfo", + component: () => import("../views/RentingViews/ItemInfoPageView.vue"), + beforeEnter: authGuardRoute, + }, + { + path: "/item/:id/edit", + name: "editItem", + component: () => import("../views/ItemViews/EditItemView.vue"), + beforeEnter: authGuardRoute, }, { path: "/login", @@ -67,81 +79,83 @@ const routes = [ component: () => import("../views/UserAuthViews/LoginView.vue"), }, { - path: "/newPassword", - name: "newPassword", - component: () => import("../views/UserAuthViews/NewPasswordView"), - beforeEnter: guardRoute, - }, - { - path: "/searchItemList", - name: "searchItemList", - component: () => import("../views/ItemViews/SearchItemListView.vue"), - }, - { - path: "/resetPassword", - name: "resetPassword", - component: () => import("../views/UserAuthViews/ResetPasswordView.vue"), + path: "/messages", + name: "messages", + component: () => import("../views/ChatViews/ChatView.vue"), + beforeEnter: authGuardRoute, }, { path: "/newCommunity", name: "newCommunity", component: () => import("../views/CommunityViews/NewCommunityView.vue"), - beforeEnter: guardRoute, - }, - { - path: "/community/:communityID/memberlist", - name: "memberlist", - component: () => import("../views/CommunityViews/MemberListView.vue"), - beforeEnter: guardRoute, + beforeEnter: authGuardRoute, }, { path: "/newItem", name: "newItem", component: () => import("../views/ItemViews/NewItemView.vue"), - beforeEnter: guardRoute, + beforeEnter: authGuardRoute, }, { - path: "/community/:communityID", - name: "communityHome", - component: () => import("../views/CommunityViews/CommunityHomeView.vue"), + path: "/newPassword", + name: "newPassword", + component: () => import("../views/UserAuthViews/NewPasswordView"), + beforeEnter: authGuardRoute, }, { - path: "/community/:communityID/private/join", - name: "communityRequest", - component: () => import("../views/CommunityViews/CommunityRequestView.vue"), - beforeEnter: guardRoute, + path: "/profile/:id", + name: "profile", + component: () => import("../views/UserProfileViews/ProfileView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/test", - name: "test", - component: () => import("../views/TestView.vue"), + path: "/profile/communities", + name: "myCommunities", + component: () => import("../views/UserProfileViews/MyCommunitiesView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/community/:communityID/admin", - name: "CommunityAdminView", - component: () => import("@/views/CommunityViews/AdminView.vue"), - beforeEnter: isAdmin, + path: "/profile/history", + name: "history", + component: () => import("../views/UserProfileViews/RentHistoryView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/itempage/:id", - name: "ItemInfo", - component: () => import("../views/RentingViews/ItemInfoPageView.vue"), - beforeEnter: guardRoute, + path: "/profile/items", + name: "userItems", + component: () => import("../views/UserProfileViews/UserItemsView.vue"), + beforeEnter: authGuardRoute, }, { - path: "/item/:id/edit", - name: "editItem", - component: () => import("../views/ItemViews/EditItemView.vue"), - beforeEnter: guardRoute, + path: "/register", + name: "register", + component: () => import("../views/UserAuthViews/RegisterView.vue"), }, { - path: "/user/userItems", - name: "UserItems", - component: () => import("../views/UserProfileViews/UserItemsView.vue"), - beforeEnter: guardRoute, + path: "/resetPassword", + name: "resetPassword", + component: () => import("../views/UserAuthViews/ResetPasswordView.vue"), + }, + { + path: "/searchItemList", + name: "searchItemList", + component: () => import("../views/ItemViews/SearchItemListView.vue"), + }, + { + path: "/test", + name: "test", + component: () => import("../views/TestView.vue"), + beforeEnter: authGuardRoute, + }, + + /** + * Catch all for wrong/non-existing routes + * Must be last to catch all + */ + { path: "/:pathMatch(.*)*", + name: "not-found", + component: NotFound }, - // Make sure it's your last route definition - { path: "/:pathMatch(.*)*", name: "not-found", component: NotFound }, ]; const router = createRouter({ diff --git a/src/services/user.service.js b/src/services/user.service.js index b6d908ba08d23bbfc32c6229ff1c093229c57a39..5aeb0201f8a3c32d04b6eae18808d0ec2f763fb3 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -39,6 +39,28 @@ class UserService { .catch((err) => console.error(err)); } + async getUserRatingAverageOwner(userId) { + return await axios + .get(API_URL + "rating/" + userId + "/average/owner", { + headers: tokenHeader(), + }) + .then((res) => { + return res.data; + }) + .catch((err) => console.error(err)); + } + + async getUserRatingAverageRenter(userId) { + return await axios + .get(API_URL + "rating/" + userId + "/average/renter", { + headers: tokenHeader(), + }) + .then((res) => { + return res.data; + }) + .catch((err) => console.error(err)); + } + async setListingToDeleted(listingId) { return await axios .delete(API_URL + "listing/" + listingId, { @@ -124,5 +146,19 @@ class UserService { }) .catch((err) => console.log(err)); } + + async registerUser(userInfo) { + return await axios + .post(API_URL + "register", userInfo) + .then((res) => { + return res; + }) + .catch((err) => { + if (err.response) { + return err.response.data; + } + console.error(err); + }); + } } export default new UserService(); diff --git a/src/utils/apiutil.js b/src/utils/apiutil.js index 7b42b8219865c3b58a02716b8326923f1946e09d..74e9b481338da2f22eda07ebb48bdc0137b5c23c 100644 --- a/src/utils/apiutil.js +++ b/src/utils/apiutil.js @@ -18,21 +18,6 @@ export function doLogin(loginRequest) { }); } -export function registerUser(registerInfo) { - return axios - .post(API_URL + "register", { - email: registerInfo.email, - firstName: registerInfo.firstName, - lastName: registerInfo.lastname, - password: registerInfo.password, - address: registerInfo.address, - }) - .then((response) => { - return response; - }) - .catch((err) => console.error(err)); -} - export async function getUser(userid) { return axios .get(API_URL + "users/" + userid + "/profile", { diff --git a/src/views/CommunityViews/CommunityView.vue b/src/views/CommunityViews/CommunityView.vue index 329917065b0679060da6503891b05c932ebaf18a..11767faa7299f22ceb232eb8045c4441b096faa0 100644 --- a/src/views/CommunityViews/CommunityView.vue +++ b/src/views/CommunityViews/CommunityView.vue @@ -48,7 +48,7 @@ type="text" id="searchInput" class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" - placeholder="Search" + placeholder="Søk" v-model="search" @change="searchWritten" /> diff --git a/src/views/ItemViews/EditItemView.vue b/src/views/ItemViews/EditItemView.vue index 763ecdafeb6145a16e7518c4630cf1ebdb79e5d0..6ae71767e398bd1f4d768bdb4682817dcde913d3 100644 --- a/src/views/ItemViews/EditItemView.vue +++ b/src/views/ItemViews/EditItemView.vue @@ -1,6 +1,7 @@ <template> + <!-- A view for editing the item --> <div class="h-screen grid md:mt-8"> - <edit-item-form :initialItem="initialItem" :communities="communities" /> + <edit-item-form /> </div> </template> diff --git a/src/views/ItemViews/NewItemView.vue b/src/views/ItemViews/NewItemView.vue index fc9db3697ba1fe00f740bb7457f233be22e6cee5..a6213c5e7c284b2980f49a5d53e6b135ed614c34 100644 --- a/src/views/ItemViews/NewItemView.vue +++ b/src/views/ItemViews/NewItemView.vue @@ -14,5 +14,3 @@ export default { }, }; </script> - -<style scoped></style> diff --git a/src/views/ItemViews/SearchItemListView.vue b/src/views/ItemViews/SearchItemListView.vue index 7da50f9397c5dee29f471267b8cb08866edcccfb..816e04e2b43a0673bb142277e79e73cc5685347d 100644 --- a/src/views/ItemViews/SearchItemListView.vue +++ b/src/views/ItemViews/SearchItemListView.vue @@ -1,4 +1,5 @@ <template> + <!-- View for search item component --> <SearchItemListComponent></SearchItemListComponent> </template> @@ -11,5 +12,3 @@ export default { }, }; </script> - -<style scoped></style> diff --git a/src/views/UserProfileViews/MyCommunitiesView.vue b/src/views/UserProfileViews/MyCommunitiesView.vue index e474528640a45dc3254256ee8ef9afef3871f7ea..bee55d4487fbdcfdcdec30435f0ba00f70ca02bb 100644 --- a/src/views/UserProfileViews/MyCommunitiesView.vue +++ b/src/views/UserProfileViews/MyCommunitiesView.vue @@ -1,4 +1,5 @@ <template> + <!-- A view for showing all the communities a user is part of --> <div> <div v-if="loading" class="flex place-content-center p-8"> <LoaderSpinner /> @@ -24,6 +25,7 @@ import CommunityList from "@/components/CommunityComponents/CommunityList.vue"; import CommunityService from "@/services/community.service"; import { UserAddIcon } from "@heroicons/vue/outline"; +import LoaderSpinner from "@/components/BaseComponents/LoaderSpinner"; export default { data() { @@ -35,6 +37,7 @@ export default { components: { CommunityList, UserAddIcon, + LoaderSpinner, }, async beforeCreate() { this.loading = true; diff --git a/src/views/UserProfileViews/ProfileView.vue b/src/views/UserProfileViews/ProfileView.vue index a490eb09dfd233803ffd624b428fa206ec767f63..cbd3a8294c0fb345ddebd3f24fbfc9c87c8fe77a 100644 --- a/src/views/UserProfileViews/ProfileView.vue +++ b/src/views/UserProfileViews/ProfileView.vue @@ -1,4 +1,5 @@ <template> + <!-- A view for the user profile card --> <div> <LargeProfileCard :isCurrentUser="true" class="md:mt-8" /> </div> diff --git a/src/views/UserProfileViews/RentHistoryView.vue b/src/views/UserProfileViews/RentHistoryView.vue index 45e7859306a8e3ede90ca30d039cb1f7840bb777..6046627d069e7410e2c23a64d5181a5bc74399b5 100644 --- a/src/views/UserProfileViews/RentHistoryView.vue +++ b/src/views/UserProfileViews/RentHistoryView.vue @@ -1,4 +1,5 @@ <template> + <!-- A view for showing rating and rent history --> <rating-modal :visible="modal.show" :name="modal.name" @@ -58,9 +59,6 @@ export default { fullHistory.sort(compareHistoryItems); return fullHistory; }, - hasNoHistory() { - return this.renterHistory.length == 0 && this.ownerHistory.length == 0; - }, }, methods: { openModal(modal) { diff --git a/src/views/UserProfileViews/UserItemsView.vue b/src/views/UserProfileViews/UserItemsView.vue index 8ac137d470d2dd6ed213d18d9c778c1da872996b..6cf4fc43868be784e6e3cccf9cb02b2059d793d4 100644 --- a/src/views/UserProfileViews/UserItemsView.vue +++ b/src/views/UserProfileViews/UserItemsView.vue @@ -1,4 +1,5 @@ <template> + <!-- A view for showing all the items the user has posted/added --> <user-items> </user-items> </template> @@ -11,5 +12,3 @@ export default { }, }; </script> - -<style></style> diff --git a/tests/unit/component-tests/ChatComponentsTest/RentalMessage.spec.js b/tests/unit/component-tests/ChatComponentsTest/RentalMessage.spec.js index c08130180924969b4b57b9c0a70eb2a50e3e35c2..9c520a8c89f7a1157b68f29f9c2ed77ba2fbf9a1 100644 --- a/tests/unit/component-tests/ChatComponentsTest/RentalMessage.spec.js +++ b/tests/unit/component-tests/ChatComponentsTest/RentalMessage.spec.js @@ -27,6 +27,9 @@ jest.mock("axios"); describe("RentalMessage.vue", () => { let wrapper; + const mockRouter = { + go: jest.fn(), + }; beforeEach(() => { wrapper = shallowMount(RentalMessage, { propsData: { @@ -47,6 +50,11 @@ describe("RentalMessage.vue", () => { deleted: false, }, }, + global: { + mocks: { + $router: mockRouter, + }, + }, }); }); diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/community-list-item.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/community-list-item.spec.js.snap index 4edde89c2c743d9c2411aa4a1a5c1403b7077c70..d52b806eba9a5cd9dcc8828d4bb79948c834e6ee 100644 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/community-list-item.spec.js.snap +++ b/tests/unit/component-tests/community-component-tests/__snapshots__/community-list-item.spec.js.snap @@ -13,8 +13,8 @@ exports[`CommunityListItem component renders correctly 1`] = ` class="h-3 w-14 flex flex-col justify-center items-center ml-2 mt-4 mb-4 mr-2" > <img - alt="Fellsekaps bilde" - class="rounded-md" + alt="Bilde" + class="rounded-md h-14 w-14 object-contain" src="string" /> </div> diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/community-list.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/community-list.spec.js.snap index fc8d4f6d97bfaa6bc5e459e517c0a2cd8b490e1e..c1b47cc4abaff7cddcd9ae8b01245615c9e5b528 100644 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/community-list.spec.js.snap +++ b/tests/unit/component-tests/community-component-tests/__snapshots__/community-list.spec.js.snap @@ -23,8 +23,8 @@ exports[`CommunityList component renders correctly 1`] = ` class="h-3 w-14 flex flex-col justify-center items-center ml-2 mt-4 mb-4 mr-2" > <img - alt="Fellsekaps bilde" - class="rounded-md" + alt="Bilde" + class="rounded-md h-14 w-14 object-contain" src="string" /> </div> @@ -74,8 +74,8 @@ exports[`CommunityList component renders correctly 1`] = ` class="h-3 w-14 flex flex-col justify-center items-center ml-2 mt-4 mb-4 mr-2" > <img - alt="Fellsekaps bilde" - class="rounded-md" + alt="Bilde" + class="rounded-md h-14 w-14 object-contain" src="string" /> </div> diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap index 9167369f515802cca42dd7be7026189a2fa8c4be..d77de4b0af5129621991ac5931424977ad9af958 100644 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap +++ b/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap @@ -2,42 +2,49 @@ exports[`ItemCard component renders correctly 1`] = ` <div - class="mt-5 px-5" + data-v-app="" > + + <!-- Item card, displays title, address, price per day and a picture --> <div - class="w-full h-full rounded bg-gray-200 overflow-hidden display:inline-block correct-size" + class="mt-5 px-5" > <div - class="relative h-0 pb-[66.7%]" + class="w-full h-full rounded bg-gray-100 ring-1 ring-gray-200 overflow-hidden display:inline-block correct-size cursor-pointer" > - <img - alt="Item image" - class="w-full h-full absolute inset-0" - src="String" - /> - </div> - <div - class="p-1 m-1 bottom-0" - > - <p - class="font-bold text-sm" - id="title" - > - String - </p> - <p - class="text-gray-700 text-xs" - id="price" + <div + class="relative h-0 pb-[66.7%]" > - 0 kr - </p> - <p - class="text-gray-700 text-xs font-bold" - id="adress" + <img + alt="Item image" + class="w-full h-full object-contain absolute" + src="String" + /> + </div> + <div + class="p-1 m-1 bottom-0" > - String - </p> + <p + class="font-bold text-sm" + id="title" + > + String + </p> + <p + class="text-gray-700 text-xs" + id="price" + > + 0 kr + </p> + <p + class="text-gray-700 text-xs font-bold" + id="adress" + > + String + </p> + </div> </div> </div> + </div> `; diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/new-item-form.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/new-item-form.spec.js.snap index d302a72c8efaf512cedea636c34b06d59abc050e..1788f9305c5a342b282e03657a5b7351548a3c4c 100644 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/new-item-form.spec.js.snap +++ b/tests/unit/component-tests/community-component-tests/__snapshots__/new-item-form.spec.js.snap @@ -2,235 +2,244 @@ exports[`NewItemForm component renders correctly 1`] = ` <div - class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" + data-v-app="" > - <!-- Component heading --> - <h3 - class="text-xl font-medium text-center text-primary-light mt-4 mb-8" - > - Opprett ny utleie - </h3> - <!-- Title --> - <div - class="mb-6" - > - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" - id="titleLabel" - > - Tittel - </label> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="title" - required="" - type="text" - /> - <!-- error message for title--> - - - </div> - <!-- Select category --> + + <!-- Form for adding a new item --> <div - class="mb-6" + class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" - id="selectCategoryLabel" + <!-- Component heading --> + <h3 + class="text-xl font-medium text-center text-primary-light mt-4 mb-8" > - Kategori - </label> - <select - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="categories" + Opprett ny utleie + </h3> + <!-- Title --> + <div + class="mb-6" > - <option - class="text-gray-400" - disabled="" - value="" + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" + id="titleLabel" > - Velg en kategori - </option> + Tittel + </label> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="title" + required="" + type="text" + /> + <!-- error message for title--> - <option - class="text-gray-900 text-sm" - > - Antikviteter og kunst - </option> - <option - class="text-gray-900 text-sm" - > - Dyr og utstyr - </option> - <option - class="text-gray-900 text-sm" + + </div> + <!-- Select category --> + <div + class="mb-6" + > + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" + id="selectCategoryLabel" > - Elektronikk og hvitevarer - </option> - <option - class="text-gray-900 text-sm" + Kategori + </label> + <select + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="categories" > - Foreldre og barn - </option> - <option - class="text-gray-900 text-sm" + <option + class="text-gray-400" + disabled="" + value="" + > + Velg en kategori + </option> + + <option + class="text-gray-900 text-sm" + > + Antikviteter og kunst + </option> + <option + class="text-gray-900 text-sm" + > + Dyr og utstyr + </option> + <option + class="text-gray-900 text-sm" + > + Elektronikk og hvitevarer + </option> + <option + class="text-gray-900 text-sm" + > + Foreldre og barn + </option> + <option + class="text-gray-900 text-sm" + > + Fritid, hobby og underholdning + </option> + <option + class="text-gray-900 text-sm" + > + Hage, oppussing og hus + </option> + <option + class="text-gray-900 text-sm" + > + Klær, kosmetikk og tilbehør + </option> + <option + class="text-gray-900 text-sm" + > + Møbler og interiør + </option> + <option + class="text-gray-900 text-sm" + > + Næringsvirksomhet + </option> + <option + class="text-gray-900 text-sm" + > + Sport og friluftsliv + </option> + <option + class="text-gray-900 text-sm" + > + Utstyr til bil, båt og MC + </option> + + </select> + <!-- error message for select category --> + + + </div> + <!-- Community --> + <div + class="mb-6" + > + <label + class="block text-sm font-medium text-gray-900 dark:text-gray-400" > - Fritid, hobby og underholdning - </option> - <option - class="text-gray-900 text-sm" + Grupper + </label> + <div + class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700" > - Hage, oppussing og hus - </option> - <option - class="text-gray-900 text-sm" + <ul + aria-labelledby="dropdownDefault" + class="py-1" + > + <li> + + + </li> + </ul> + </div> + <!-- Error message for community --> + <label + class="text-error-medium text-sm block" + /> + </div> + <!-- price --> + <div + class="mb-6 mt-4" + > + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" + id="priceLabel" > - Klær, kosmetikk og tilbehør - </option> - <option - class="text-gray-900 text-sm" + Pris per dag + </label> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="price" + required="" + type="number" + /> + <!-- error message for price --> + + + </div> + <!-- Description --> + <div + class="mb-6" + > + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" + id="descriptionLabel" > - Møbler og interiør - </option> - <option - class="text-gray-900 text-sm" + Beskrivelse + </label> + <textarea + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="description" + required="" + rows="4" + /> + <!-- error message for description --> + + + </div> + <!-- Address --> + <div + class="mb-6" + > + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" + id="addressLabel" > - Næringsvirksomhet - </option> - <option - class="text-gray-900 text-sm" + Adresse + </label> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="adress" + required="" + type="text" + /> + <!-- error message for address--> + + + </div> + <!-- Images --> + <div> + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" + id="imageLabel" > - Sport og friluftsliv - </option> - <option - class="text-gray-900 text-sm" + Bilder (bildene må være .png) + </label> + <input + accept="image/png" + style="display: none;" + type="file" + /> + <!-- Opens file explorer --> + <button + class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" > - Utstyr til bil, båt og MC - </option> + Velg bilde + </button> + <!-- Shows chosen images --> - </select> - <!-- error message for select box --> - - - </div> - <!-- Grupper --> - <div - class="mb-6" - > - <label - class="block text-sm font-medium text-gray-900 dark:text-gray-400" - > - Grupper - </label> + + </div> + <!-- Save item button --> <div - class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700" + class="float-right" > - <ul - aria-labelledby="dropdownDefault" - class="py-1" + <button + class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" + id="saveButton" > - <li> - - - </li> - </ul> + Lagre + </button> </div> - <!-- Error message for community --> - <label - class="text-error-medium text-sm block" - /> - </div> - <!-- price --> - <div - class="mb-6 mt-4" - > - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" - id="priceLabel" - > - Pris - </label> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="price" - required="" - type="number" - /> - <!-- error message for price --> - - - </div> - <!-- Description --> - <div - class="mb-6" - > - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" - id="descriptionLabel" - > - Beskrivelse - </label> - <textarea - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="description" - required="" - rows="4" - /> - <!-- error message for description --> - - - </div> - <!-- Address --> - <div - class="mb-6" - > - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" - id="addressLabel" - > - Adresse - </label> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="adress" - required="" - type="text" - /> - <!-- error message for address--> - - - </div> - <!-- Images --> - <div> - <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" - id="imageLabel" - > - Bilder (bildene må være .png) - </label> - <input - accept="image/png" - style="display: none;" - type="file" - /> - <button - class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" - > - Velg bilde - </button> - - - </div> - <!-- Save item button --> - <div - class="float-right" - > - <button - class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" - id="saveButton" - > - Lagre - </button> </div> + </div> `; diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/search-item-list.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/search-item-list.spec.js.snap index 59eecbb9003356b0debaa8228f3785d489e3ec98..9ccc19c0abc78e6bb844dc550974c7bb02cecb3d 100644 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/search-item-list.spec.js.snap +++ b/tests/unit/component-tests/community-component-tests/__snapshots__/search-item-list.spec.js.snap @@ -1,55 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SearchItemListComponent elements rendering renders correctly 1`] = ` -<section - class="relative w-full max-w-md px-5 py-4 mx-auto rounded-md" +<div + data-v-app="" > - <div - class="relative" - id="searchComponent" + + <!-- A template for searching in item list --> + <section + class="relative w-full max-w-md px-5 py-4 mx-auto rounded-md" > - <span - class="absolute inset-y-0 left-0 flex items-center pl-3" + <div + class="relative" + id="searchComponent" > - <svg - class="w-5 h-5 text-gray-400" - fill="none" - viewBox="0 0 24 24" + <span + class="absolute inset-y-0 left-0 flex items-center pl-3" > - <path - d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" - stroke="currentColor" - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - /> - </svg> - </span> - <input - class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" - id="searchInput" - placeholder="Search" - type="text" - /> - </div> - <div - class="absolute inset-x-0 px-6 py-3 mt-4 border-2 border-slate-500" - > + <svg + class="w-5 h-5 text-gray-400" + fill="none" + viewBox="0 0 24 24" + > + <path + d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + /> + </svg> + </span> + <input + class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" + id="searchInput" + placeholder="Søk" + type="text" + /> + </div> <div - class="grid grid-cols-2" + class="absolute inset-x-0 px-6 py-3 mt-4 border-2 border-slate-500" > - - <item-card-stub - item="[object Object]" - /> - <item-card-stub - item="[object Object]" - /> - <item-card-stub - item="[object Object]" - /> - + <div + class="grid grid-cols-2" + > + + <item-card-stub + item="[object Object]" + /> + <item-card-stub + item="[object Object]" + /> + <item-card-stub + item="[object Object]" + /> + + </div> </div> - </div> -</section> + </section> + +</div> `; diff --git a/tests/unit/component-tests/renting-compnents-tests/item-info.spec.js b/tests/unit/component-tests/renting-compnents-tests/item-info.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..33b2c610f69ba97491214d3fab01476b32a0e270 --- /dev/null +++ b/tests/unit/component-tests/renting-compnents-tests/item-info.spec.js @@ -0,0 +1,110 @@ +import { shallowMount } from "@vue/test-utils"; +import ItemInfo from "@/components/RentingComponents/ItemInfo.vue"; + +const mockMethod = jest.spyOn(ItemInfo.methods, "sendToConfirm"); +const mockCreatePush = jest.spyOn(ItemInfo.methods, "createPushItem"); + +jest.mock("@/utils/apiutil", () => { + return { + getItem: () => { + return new Promise((resolve) => { + resolve({ + listingID: 0, + title: "Title", + description: "Description", + pricePerDay: 100, + address: "ABC ROAD 3", + userID: 1, + categoryNames: [], + communityIDs: [], + }); + }); + }, + getItemPictures: () => { + return new Promise((resolve) => { + resolve([]); + }); + }, + }; +}); + +describe("ItemInfo component", () => { + let wrapper; + const mockRouter = { + push: jest.fn(), + currentRoute: { + value: { + params: { + id: "1", + }, + }, + }, + }; + const mockStore = { + commit: jest.fn(), + }; + + beforeEach(() => { + wrapper = shallowMount(ItemInfo, { + global: { + mocks: { + $router: mockRouter, + $store: mockStore, + }, + }, + }); + }); + + it("is instantiated", () => { + expect(wrapper.exists()).toBeTruthy(); + }); + + it("Check that title is displayed", () => { + expect(wrapper.findAll("h1")[0].text()).toBe("Title"); + }); + + it("Check that button cannot be clicked if date is not selected", async () => { + const jestCreatePushItemMock = jest.fn(); + wrapper.vm.createPushItem = jestCreatePushItemMock; + + // Click rent button + wrapper.find("button").trigger("click"); + + // wait a tick + await wrapper.vm.$nextTick(); + + // Check that jestMock was not clicked + expect(mockMethod).toHaveBeenCalledTimes(1); + expect(mockCreatePush).toHaveBeenCalledTimes(0); + // Check that the last p contains "Dato er påkrevd" + expect(wrapper.findAll("p")[wrapper.findAll("p").length - 1].text()).toBe( + "Dato er påkrevd" + ); + }); + + it("Check that send to confirm works", async () => { + // Set valid days + wrapper.vm.setDate({ + startDate: "2020-01-01", + endDate: "2020-02-01", + }); + wrapper.find("button").trigger("click"); + // wait a tick + await wrapper.vm.$nextTick(); + + // Check that method to change component was called + expect(mockCreatePush).toHaveBeenCalledTimes(1); + }); + + it("Test that price calculation works", async () => { + wrapper.vm.setDate({ + startDate: new Date("2022-01-01"), + endDate: new Date("2022-01-03"), + }); + // wait a tick + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.totPrice).toBe(200); + }); +}); diff --git a/tests/unit/component-tests/renting-compnents-tests/new-rent.spec.js b/tests/unit/component-tests/renting-compnents-tests/new-rent.spec.js index 3f8ffafa68bf5dd606afdeda9214b57871ff174f..458147edeb2d729b82043ebecec97a9158d1d876 100644 --- a/tests/unit/component-tests/renting-compnents-tests/new-rent.spec.js +++ b/tests/unit/component-tests/renting-compnents-tests/new-rent.spec.js @@ -1,16 +1,16 @@ import { mount } from "@vue/test-utils"; import NewRent from "@/components/RentingComponents/NewRent.vue"; +import axios from "axios"; + +jest.mock("axios"); +let mockRouter; describe("Confirm and send a rent request", () => { - let wrapper; - const route = { - params: { - id: 1, - }, - }; - const router = { - push: jest.fn(), + mockRouter = { + go: jest.fn(), }; + + let wrapper; beforeEach(() => { wrapper = mount(NewRent, { props: { @@ -26,8 +26,7 @@ describe("Confirm and send a rent request", () => { }, global: { mocks: { - $route: route, - $router: router, + $router: mockRouter, }, }, }); @@ -37,10 +36,26 @@ describe("Confirm and send a rent request", () => { expect(wrapper.exists()).toBeTruthy(); }); - it("Check if fields show correct informations", () => { + it("Check that fields show correct informations", () => { expect(wrapper.find("#rentTitle").text()).toEqual("Telt"); expect(wrapper.find("#fromTime").text()).toMatch("19. September 2022"); expect(wrapper.find("#toTime").text()).toMatch("23. September 2022"); expect(wrapper.find("#price").text()).toEqual("Totaltpris: 200 kr"); }); + + it("Check that clicking rent sends post request", async () => { + const button = wrapper.find("#confirmButton"); + axios.post.mockResolvedValueOnce(); + button.trigger("click"); + await wrapper.vm.$nextTick(); + expect(axios.post).toHaveBeenCalledTimes(1); + }); + + it("Checks that page is reloaded when cancelButton is press", async () => { + const button = wrapper.find("#cancelButton"); + button.trigger("click"); + await wrapper.vm.$nextTick(); + expect(mockRouter.go).toHaveBeenCalledTimes(1); + expect(mockRouter.go).toHaveBeenCalledWith(0); + }); }); diff --git a/tests/unit/component-tests/user-component-tests/__snapshots__/login-form.spec.js.snap b/tests/unit/component-tests/user-component-tests/__snapshots__/login-form.spec.js.snap index f6bd34180d6771f7dd2f7982bdf4989f1e9928db..da0a65acb1aaebb2d840fba0a32e54ed26c72800 100644 --- a/tests/unit/component-tests/user-component-tests/__snapshots__/login-form.spec.js.snap +++ b/tests/unit/component-tests/user-component-tests/__snapshots__/login-form.spec.js.snap @@ -2,76 +2,90 @@ exports[`LoginForm component renders correctly 1`] = ` <div - class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full" + data-v-app="" > + + <!-- Login form --> <div - class="px-6 py-4 mt-4" + class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full" > + <!-- Header --> <div - class="text-xl md:text-2xl font-medium text-center text-primary-light mt-4 mb-8" + class="px-6 py-4 mt-4" > - Logg på - </div> - <div> <div - class="w-full mt-6" + class="text-xl md:text-2xl font-medium text-center text-primary-light mt-4 mb-8" > - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - placeholder="Skriv inn din e-postadresse *" - required="" - type="email" - /> - <!-- error message --> - - + Logg på </div> - <div - class="w-full mt-6" - > - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - placeholder="Vennligst oppgi passordet ditt *" - required="" - type="password" - /> - <!-- error message --> - - - </div> - <div - class="flex items-center justify-between mt-8" - > - <router-link - class="text-primary-medium" - to="/resetPassword" + <!-- Email --> + <div> + <div + class="w-full mt-6" + > + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + placeholder="Skriv inn din e-postadresse *" + required="" + type="email" + /> + <!-- error message for email --> + + + </div> + <!-- Password --> + <div + class="w-full mt-6" > - Glemt passord? - </router-link> - <button - class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light login" + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + placeholder="Vennligst oppgi passordet ditt *" + required="" + type="password" + /> + <!-- error message for password --> + + + </div> + <!-- Router link for forgetting password. Redirects to reset password page. --> + <div + class="flex items-center justify-between mt-8" > - Logg på - </button> + <router-link + class="text-primary-medium" + to="/resetPassword" + > + Glemt passord? + </router-link> + <!-- Button for logging in --> + <button + class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light login" + > + Logg på + </button> + </div> </div> </div> - </div> - <div - class="flex items-center justify-center py-4 text-center bg-gray-50 dark:bg-gray-700" - > - <router-link - class="mx-2 text-sm font-bold text-primary-medium dark:text-primary-light hover:underline" - to="/register" + <div + class="flex items-center justify-center py-4 text-center bg-gray-50 dark:bg-gray-700" > - Opprette ny konto - </router-link> - </div> - <div - class="flex items-center justify-center text-center bg-gray-50" - > - <label - class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" - /> + <!-- Router link to redirect to registering a new user page --> + <router-link + class="mx-2 text-sm font-bold text-primary-medium dark:text-primary-light hover:underline" + to="/register" + > + Opprette ny konto + </router-link> + </div> + <div + class="flex items-center justify-center text-center bg-gray-50" + > + <!-- Shows a message to user if login fails --> + <label + class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" + /> + </div> </div> + </div> `; diff --git a/tests/unit/component-tests/user-component-tests/__snapshots__/new-password-form.spec.js.snap b/tests/unit/component-tests/user-component-tests/__snapshots__/new-password-form.spec.js.snap index 941fc2aaf4c6fd0b024cde59276a29a4af11813f..a62255e09948f531f521385f62d61d04c7899cef 100644 --- a/tests/unit/component-tests/user-component-tests/__snapshots__/new-password-form.spec.js.snap +++ b/tests/unit/component-tests/user-component-tests/__snapshots__/new-password-form.spec.js.snap @@ -2,87 +2,97 @@ exports[`NewPasswordForm component renders correctly 1`] = ` <div - class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" + data-v-app="" > - <h3 - class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" - > - Endre passord - </h3> + + <!-- A form for changing password --> <div - class="" - id="oldPasswordField" + class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > - <label - class="block text-sm text-gray-800 dark:text-gray-200" - for="oldPassword" + <!-- Header --> + <h3 + class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" > - Gammelt passord - </label> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" - type="password" - /> - <!-- error message --> - - - </div> - <div - class="mt-4" - id="firstPasswordField" - > - <label - class="block text-sm text-gray-800 dark:text-gray-200" - for="password" + Endre passord + </h3> + <!-- Input field for old password --> + <div + class="" + id="oldPasswordField" > - Nytt passord - </label> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" - type="password" - /> - <!-- error message --> - - - </div> - <div - class="mt-4" - id="secondPasswordField" - > + <label + class="block text-sm text-gray-800 dark:text-gray-200" + > + Gammelt passord + </label> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" + type="password" + /> + <!-- error message for password --> + + + </div> + <!-- New password --> <div - class="flex items-center justify-between" + class="mt-4" + id="firstPasswordField" > <label class="block text-sm text-gray-800 dark:text-gray-200" - for="rePassword" > - Gjenta nytt passord + Nytt passord </label> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" + type="password" + /> + <!-- error message for password --> + + </div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" - type="password" - /> - <!-- error message --> - - - </div> - <div - class="mt-6" - id="buttonsField" - > - <button - class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light float-right" + <!-- Repeating new password --> + <div + class="mt-4" + id="secondPasswordField" > - Sett ny passord - </button> - </div> - <div - class="flex items-center justify-center text-center bg-gray-50" - > - <label - class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" - /> + <div + class="flex items-center justify-between" + > + <label + class="block text-sm text-gray-800 dark:text-gray-200" + > + Gjenta nytt passord + </label> + </div> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-blue-300" + type="password" + /> + <!-- error message for password --> + + + </div> + <!-- Button --> + <div + class="mt-6" + id="buttonsField" + > + <button + class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light float-right" + > + Sett ny passord + </button> + </div> + <!-- Message for user --> + <div + class="flex items-center justify-center text-center bg-gray-50" + > + <label + class="mx-2 text-sm font-bold text-error-medium dark:text-primary-light hover:underline" + /> + </div> </div> + </div> `; diff --git a/tests/unit/component-tests/user-component-tests/__snapshots__/rating.spec.js.snap b/tests/unit/component-tests/user-component-tests/__snapshots__/rating.spec.js.snap index f22076edecd3620957e0bbf9141873adfd3a6322..5d0cf24e39eb3292cb0d2eda697982c9c214fbda 100644 --- a/tests/unit/component-tests/user-component-tests/__snapshots__/rating.spec.js.snap +++ b/tests/unit/component-tests/user-component-tests/__snapshots__/rating.spec.js.snap @@ -1,22 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Rating component renders correctly 1`] = ` -<ul - class="flex justify-center" +<div + data-v-app="" > - <li> - <p - class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400" - > - : - </p> - </li> - <li> - <p - class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400" - > - Ingen vurderinger - </p> - </li> -</ul> + + <!-- Shows rating, if no rating found, tells the user that no rating is registered --> + <ul + class="flex justify-center" + > + <li> + <p + class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400" + > + : + </p> + </li> + <li> + <p + class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400" + > + Ingen vurderinger + </p> + </li> + </ul> + +</div> `; diff --git a/tests/unit/component-tests/user-component-tests/__snapshots__/register-user-component.spec.js.snap b/tests/unit/component-tests/user-component-tests/__snapshots__/register-user-component.spec.js.snap index 193cbfa3c50d17fb2f50c439c23fe6b0031c3d33..af2bbf8783faa8af62a497730fa7c81f24b960ed 100644 --- a/tests/unit/component-tests/user-component-tests/__snapshots__/register-user-component.spec.js.snap +++ b/tests/unit/component-tests/user-component-tests/__snapshots__/register-user-component.spec.js.snap @@ -2,95 +2,111 @@ exports[`RegisterFormComponent renders correctly 1`] = ` <div - class="w-full max-w-md mx-auto mb-auto md:ring-1 ring-gray-300 overflow-hidden rounded-xl p-4" + data-v-app="" > + + <!-- Register form for creating a new user account --> <div - class="text-xl md:text-2xl font-medium text-center text-primary-light mt-4 mb-8" - id="registerLabel" + class="w-full max-w-md mx-auto mb-auto md:ring-1 ring-gray-300 overflow-hidden rounded-xl p-4" > - Opprett ny konto - </div> - <form> <div - class="grid grid-cols-1 gap-6 mt-4" + class="text-xl md:text-2xl font-medium text-center text-primary-light mt-4 mb-8" + id="registerLabel" > - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="email" - placeholder="E-post adresse" - type="email" - /> - <!-- error message --> - - - </div> - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="password" - placeholder="Passord" - type="password" - /> - <!-- error message --> - - - </div> - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="confirmPassword" - placeholder="Bekreft passord" - type="password" - /> - <!-- error message --> - - - </div> - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - data-test="firstNameTest" - id="firstName" - placeholder="Fornavn" - type="text" - /> - <!-- error message --> - - - </div> - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="lastName" - placeholder="Etternavn" - type="text" - /> - <!-- error message --> - - - </div> - <div> - <input - class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" - id="address" - placeholder="Adresse" - type="text" - /> - <!-- error message --> - - - </div> + Opprett ny konto </div> - <div - class="flex justify-end mt-6" - > - <button - class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" + <form> + <div + class="grid grid-cols-1 gap-6 mt-4" > - Opprett - </button> - </div> - </form> + <div> + <!-- Email --> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="email" + placeholder="E-post adresse" + type="email" + /> + <!-- error message for email --> + + + <div + class="text-error-medium text-sm" + style="display: none;" + /> + </div> + <div> + <!-- Password --> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="password" + placeholder="Passord" + type="password" + /> + <!-- error message --> + + + </div> + <div> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="confirmPassword" + placeholder="Bekreft passord" + type="password" + /> + <!-- error message --> + + + </div> + <div> + <!-- First name --> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + data-test="firstNameTest" + id="firstName" + placeholder="Fornavn" + type="text" + /> + <!-- error message for first name--> + + + </div> + <div> + <!-- Last name --> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="lastName" + placeholder="Etternavn" + type="text" + /> + <!-- error message for last name --> + + + </div> + <div> + <!-- Address --> + <input + class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light" + id="address" + placeholder="Adresse" + type="text" + /> + <!-- error message for address --> + + + </div> + </div> + <div + class="flex justify-end mt-6" + > + <button + class="flex items-center px-2 py-2 font-medium tracking-wide capitalize text-white transition-colors duration-200 transform rounded-md focus:outline-none focus:ring focus:ring-opacity-80 min-w-{20px} bg-primary-medium hover:bg-primary-dark focus:ring-primary-light" + > + Opprett + </button> + </div> + </form> + </div> + </div> `; diff --git a/tests/unit/component-tests/user-component-tests/__snapshots__/user-items.spec.js.snap b/tests/unit/component-tests/user-component-tests/__snapshots__/user-items.spec.js.snap index db5d7bf26553750dd6c9f3789f9541ffc2d2f071..6a73973168534f9f4153a75d0448d4e301d0ba97 100644 --- a/tests/unit/component-tests/user-component-tests/__snapshots__/user-items.spec.js.snap +++ b/tests/unit/component-tests/user-component-tests/__snapshots__/user-items.spec.js.snap @@ -5,6 +5,8 @@ exports[`UserItems component renders correctly 1`] = ` data-v-app="" > + <!-- Shows all the items a user has posted with search and pagination. + Includes a dropdown menu for editing or deleting an item. --> <div class="text-xl md:text-2xl text-primary-light font-medium" id="headline" @@ -13,7 +15,7 @@ exports[`UserItems component renders correctly 1`] = ` </div> <!-- Search field --> <div - class="relative" + class="relative mx-4" id="searchComponent" > <span @@ -36,12 +38,12 @@ exports[`UserItems component renders correctly 1`] = ` <input class="w-full py-3 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-primary-medium dark:focus:border-primary-medium focus:outline-none focus:ring" id="searchInput" - placeholder="Search" + placeholder="Søk" type="text" /> </div> <div - class="absolute inset-x-0 px-5 py-3" + class="absolute inset-x-0" > <!-- ItemCards --> <div @@ -53,6 +55,8 @@ exports[`UserItems component renders correctly 1`] = ` > + <!-- A waring asking the user if it is sure it wants to delete the item + with options to go ahead with the deleting or to cancel the delete. --> <!-- Main modal --> <!--v-if-->