diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d5a3272aa2efef8561bb8f8b3e676da1b1fa59b8..7b1056c4b82ed6c99e62d9c41901604f74157d12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,52 +1,36 @@ -image: node:lts +image: node:16 stages: - - build + - setup - test -# - deploy -# These folders are cached between builds -# http://docs.gitlab.com/ce/ci/yaml/README.html#cache -cache: - key: ${CI_COMMIT_REF_SLUG} - paths: - # Default cache directory from https://classic.yarnpkg.com/en/docs/install-ci/#gitlab. - - node_modules/ - # Enables git-lab CI caching. Both .cache and public must be cached, otherwise builds will fail. - - .cache/ - - public/ +variables: + npm_config_cache: "$CI_PROJECT_DIR/.npm" -npm:install: - stage: build +# Define a hidden job to be used with extends +# Better than default to avoid activating cache for all jobs +.dependencies_cache: + cache: + key: + files: + - package-lock.json + paths: + - .npm + policy: pull + +setup: + stage: setup script: - npm ci + extends: .dependencies_cache + cache: + policy: pull-push + artifacts: + expire_in: 3 days #delete cache after 3 days to conserve space + paths: + - node_modules -test:unit: +test: stage: test - needs: ["npm:install"] script: - - npm ci - - npm run test:unit - -#test:coverage: -# stage: test -# needs: ["npm:install"] -# script: -# - ./node_modules/.bin/gatsby info -# rules: -# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -# - if: $CI_MERGE_REQUEST_ID - -#pages: -# stage: deploy -# needs: -# - npm:install -# - test:unit -# - test:gatsby -# script: -# - ./node_modules/.bin/gatsby build --prefix-paths -# artifacts: -# paths: -# - public -# rules: -# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - npm run test:unit \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 41235286563508e0feb4c451a34b598ef822ec21..0000000000000000000000000000000000000000 --- a/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width,initial-scale=1.0"> - <link rel="icon" href="<%= BASE_URL %>favicon.ico"> - <title><%= htmlWebpackPlugin.options.title %></title> - </head> - <body> - <noscript> - <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> - </noscript> - <div id="app"></div> - <!-- built files will be auto injected --> - </body> -</html> diff --git a/src/components/AddNewItem.vue b/src/components/AddNewItem.vue index 86e7cf484ee05781a2cac3ba36f5f7dd8aacde6d..9324b2bb76cfe0df33fde7a0cad06bd202470708 100644 --- a/src/components/AddNewItem.vue +++ b/src/components/AddNewItem.vue @@ -68,24 +68,23 @@ </div> </div> - <!-- Select Group --> <div class="mb-6"> <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" - >Gruppe</label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" + >Gruppe</label > <select - v-model="v$.item.selectGroup.$model" - class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + v-model="v$.item.selectGroup.$model" + class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" > <option class="text-gray-400" value="" disabled selected> Select a Group </option> <option - v-for="group in groups" - :key="group" - class="text-gray-900 text-sm" + v-for="group in groups" + :key="group" + class="text-gray-900 text-sm" > {{ group }} </option> @@ -93,9 +92,9 @@ <!-- error message for select box --> <div - class="text-red" - v-for="(error, index) of v$.item.selectGroup.$errors" - :key="index" + class="text-red" + v-for="(error, index) of v$.item.selectGroup.$errors" + :key="index" > <div class="text-red-600 text-sm"> {{ error.$message }} @@ -157,25 +156,25 @@ </div> </div> - <!-- Address --> <div class="mb-6" :class="{ error: v$.item.address.$errors.length }"> <label - class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" - >Adresse</label> + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" + >Adresse</label + > <input - type="text" - class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" - v-model="v$.item.address.$model" - id="adress" - required + type="text" + class="bg-gray-200 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + v-model="v$.item.address.$model" + id="adress" + required /> <!-- error message for address--> <div - class="text-red" - v-for="(error, index) of v$.item.address.$errors" - :key="index" + class="text-red" + v-for="(error, index) of v$.item.address.$errors" + :key="index" > <div class="text-red-600 text-sm"> {{ error.$message }} @@ -183,7 +182,6 @@ </div> </div> - <!-- Images --> <div> <label @@ -252,8 +250,8 @@ export default { item: { title: { required: helpers.withMessage( - () => "Tittelen kan ikke være tom", - required + () => "Tittelen kan ikke være tom", + required ), max: helpers.withMessage( () => `Tittelen kan inneholde max 50 tegn`, @@ -262,8 +260,8 @@ export default { }, description: { required: helpers.withMessage( - () => "Beskrivelsen kan ikke være tom", - required + () => "Beskrivelsen kan ikke være tom", + required ), max: helpers.withMessage( () => `Beskrivelsen kan inneholde max 200 tegn`, @@ -289,12 +287,12 @@ export default { }, address: { required: helpers.withMessage( - () => "Addressen kan ikke være tom", - required + () => "Addressen kan ikke være tom", + required ), max: helpers.withMessage( - () => `Addressen kan inneholde max 50 tegn`, - maxLength(50) + () => `Addressen kan inneholde max 50 tegn`, + maxLength(50) ), }, }, @@ -365,11 +363,10 @@ export default { const postRequest = await postNewItem(itemInfo); console.log("posted: " + postRequest); - } }, - checkUser: async function (){ + checkUser: async function () { let user = parseUserFromToken(this.$store.state.user.token); this.item.userId = parseInt(user.accountId); console.log("id: " + this.item.userId); @@ -380,6 +377,6 @@ export default { this.item.images.push(URL.createObjectURL(event.target.files[0])); console.log("antall bilder: " + this.item.images.length); }, - } + }, }; </script> diff --git a/src/components/MemberList.vue b/src/components/MemberList.vue new file mode 100644 index 0000000000000000000000000000000000000000..5f935be3e67737b6228cc4113fc0da6a8bb8a934 --- /dev/null +++ b/src/components/MemberList.vue @@ -0,0 +1,36 @@ +<template> + <ul> + <li v-for="member in memberlist" :key="member.userId"> + <user-list-item-card :admin="admin" :user="member" /> + </li> + </ul> +</template> + +<script> +import UserListItemCard from "./UserProfileComponents/UserListItemCard.vue"; + +export default { + data() { + return { + memberlist: [ + { + userId: 2, + firstName: "erik", + lastName: "hansen", + }, + { + userId: 1, + firstName: "Test", + lastName: "Testesen", + }, + ], + }; + }, + components: { + UserListItemCard, + }, + props: { + admin: Boolean, + }, +}; +</script> diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 507c2ab4f30dccb71032c8c0d94fffc34cef5b97..d06452fc2bd430f1903d2dccc467540f0251fb23 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -10,10 +10,20 @@ </div> <ul class="flex"> <li> - <img class="m-6 cursor-pointer h-8 " src="../assets/additem.png" alt="Legg til" @click="$router.push('/addNewItem')"/> + <img + class="m-6 cursor-pointer h-8" + src="../assets/additem.png" + alt="Legg til" + @click="$router.push('/addNewItem')" + /> </li> <li> - <img class="m-6 cursor-pointer h-8 " src="../assets/messages.png" alt="Meldinger" @click="$router.push('/messages')"/> + <img + class="m-6 cursor-pointer h-8" + src="../assets/messages.png" + alt="Meldinger" + @click="$router.push('/messages')" + /> </li> <li> <img diff --git a/src/components/UserProfileComponents/LargeProfileCard.vue b/src/components/UserProfileComponents/LargeProfileCard.vue index 7c5c036f8103be9c42b62d0c818dfa229af2f8cd..695ef260ac7df019805abe3f27cbeda6cc00f5b3 100644 --- a/src/components/UserProfileComponents/LargeProfileCard.vue +++ b/src/components/UserProfileComponents/LargeProfileCard.vue @@ -1,13 +1,13 @@ <template> <div - class="max-w-sm bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700" + class="min-w-full md:min-w-0 md:w-96 my-4 py-8 bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700" > - <div v-show="isCurrentUser" class="flex justify-end px-4 pt-4"> + <div v-show="isCurrentUser" class="flex absolute justify-end px-4 pt-4"> <button id="dropdownDefault" data-dropdown-toggle="dropdown" @click="dropdown = !dropdown" - class="hidden sm:inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5" + class="w-10 h-10 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5" type="button" > <svg @@ -25,7 +25,6 @@ <div id="dropdown" v-show="dropdown" - zindex="2" class="z-10 w-44 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700" > <ul class="py-1" aria-labelledby="dropdownDefault"> @@ -67,6 +66,7 @@ </ul> </div> </div> + <div class="flex flex-col items-center pb-10"> <img class="mb-3 w-24 h-24 rounded-full shadow-lg" @@ -95,8 +95,7 @@ <script> import RatingComponent from "@/components/UserProfileComponents/RatingComponent.vue"; import { parseCurrentUser } from "@/utils/token-utils"; -import { getUser, getRenterRating, getOwnerRating } from "@/utils/apiutil"; -import router from "@/router"; +import { getUser, getAverageRating } from "@/utils/apiutil"; export default { name: "LargeProfileCard", @@ -106,8 +105,8 @@ export default { currentUser: {}, id: -1, isCurrentUser: false, - renterRating: -1, //getRenterRating(this.userID), - ownerRating: -1, //getOwnerRating(this.userID), + renterRating: -1, + ownerRating: -1, dropdown: false, }; }, @@ -116,21 +115,25 @@ export default { }, methods: { async getUser() { - this.currentUser = parseCurrentUser(); - this.id = router.currentRoute.value.params.id; - if (this.id == this.currentUser.account_id) { + this.currentUser = await parseCurrentUser(); + this.id = await this.$router.currentRoute.value.params.id; + + if (this.id == this.currentUser.accountId) { this.isCurrentUser = true; this.user = this.currentUser; return; } this.user = await getUser(this.id); - this.renterRating = getRenterRating(this.id); - this.ownerRating = getOwnerRating(this.id); + let rating = await getAverageRating(this.id); + if (rating >= 0 && rating <= 5) { + this.renterRating = rating; + this.ownerRating = rating; + } }, getProfilePicture() { - /* if (this.user.picture != "") { + if (this.user.picture != "") { return this.user.picture; - } */ + } return "../assets/defaultUserProfileImage.jpg"; }, }, diff --git a/src/components/UserProfileComponents/RatingComponent.vue b/src/components/UserProfileComponents/RatingComponent.vue index 662b9607911697c89421e7c4727b44caa93dfe4c..c6e7e8252f2fb8916098f3a1ab8b6a2a7290eb62 100644 --- a/src/components/UserProfileComponents/RatingComponent.vue +++ b/src/components/UserProfileComponents/RatingComponent.vue @@ -1,5 +1,5 @@ <template> - <ul v-if="compRating != -1" class="flex justify-center"> + <ul v-if="rating != -1" class="flex justify-center"> <li> <p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400"> {{ ratingType }}: @@ -19,7 +19,7 @@ </li> <li> <p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400"> - {{ compRating }} out of 5 + {{ rating }} out of 5 </p> </li> </ul> @@ -40,11 +40,6 @@ <script> export default { name: "RatingComponent", - data() { - return { - compRating: this.rating + 0, - }; - }, props: { rating: Number, ratingType: String, diff --git a/src/components/UserProfileComponents/UserListItemCard.vue b/src/components/UserProfileComponents/UserListItemCard.vue index 4da140e21610587c8e88953ed9b17572c9bef98a..005646d51d405772521392a452dc100e3ff75375 100644 --- a/src/components/UserProfileComponents/UserListItemCard.vue +++ b/src/components/UserProfileComponents/UserListItemCard.vue @@ -1,20 +1,31 @@ <template> <div - class="select-none cursor-pointer hover:bg-gray-50 flex flex-1 items-center p-4" + class="bg-white shadow dark:bg-gray-800 select-none cursor-pointer hover:bg-gray-50 flex items-center p-4" > - <div class="flex flex-col w-10 h-10 justify-center items-center mr-4"> - <router-link to=""> - <img alt="profil" :src="getProfilePicture" /> + <div class="h-10 w-10 flex flex-col justify-center items-center mr-4"> + <router-link :to="'/profile/' + user.userId"> + <img alt="profil" src="../../assets/defaultUserProfileImage.jpg" /> </router-link> </div> <div class="flex-1 pl-1"> <div class="font-medium dark:text-white"> - {{ user.first_name }} {{ user.last_name }} + {{ user.firstName }} {{ user.lastName }} </div> </div> + <div class="hidden md:block flex-auto"> + <rating-component :rating="rating" :ratingType="'Gjennomsnitts rating'" /> + </div> <div class="flex flex-row justify-center"> - <button class="w-10 text-right flex justify-end">Åpne chat</button> - <button v-if="admin" class="w-10 text-right flex justify-end"> + <button + v-if="!admin" + class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-blue-600 rounded-md hover:bg-blue-500 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-80" + > + Åpne chat + </button> + <button + v-if="admin" + class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-blue-600 rounded-md hover:bg-blue-500 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-80" + > Fjern bruker </button> </div> @@ -22,8 +33,19 @@ </template> <script> +import { getAverageRating } from "@/utils/apiutil"; +import RatingComponent from "./RatingComponent.vue"; + export default { name: "UserListItem", + data() { + return { + rating: this.getRating(), + }; + }, + components: { + RatingComponent, + }, props: { user: Object, admin: Boolean, @@ -35,6 +57,12 @@ export default { } return "../assets/defaultUserProfileImage.jpg"; }, + async getRating() { + this.rating = await getAverageRating(this.user.userId); + }, + }, + beforeMount() { + this.getRating(); }, }; </script> diff --git a/src/router/index.js b/src/router/index.js index db05196280c868673bfa640d504272f2434c71be..83a4778b01b3ee2319244eef96176d8dab82e731 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -64,6 +64,11 @@ const routes = [ path: "/createNewGroup", name: "createNewGroup", component: () => import("../views/CreateNewGroupView.vue"), + }, + { + path: "/group/:id/memberlist", + name: "memberlist", + component: () => import("../views/MemberListView.vue"), beforeEnter: guardRoute, }, { diff --git a/src/utils/apiutil.js b/src/utils/apiutil.js index 94d14a9052f5b5e1b199a17b3acfc2d4d8803be3..0c7f3d0328366024a5ca25b75bfb1a8545aa59db 100644 --- a/src/utils/apiutil.js +++ b/src/utils/apiutil.js @@ -72,6 +72,18 @@ export function getOwnerRating(userid) { }); } +export function getAverageRating(userid) { + return axios + .get(API_URL + "rating/" + userid + "/average", { + headers: tokenHeader(), + }) + .then((response) => { + return response.data; + }) + .catch((error) => { + console.error(error); + }); +} export function doNewPassword() { //m //add newPasswordInfo to input @@ -83,15 +95,15 @@ export function doNewPassword() { return auth; //remove after axios is added } -export function postNewItem(itemInfo){ - return axios - .post(API_URL + "listing", itemInfo) - .then((response) => { - console.log("prøver: " + response.data); - return response; - }) - .catch((error) => { - console.log(error.response); - return error; - }); +export function postNewItem(itemInfo) { + return axios + .post(API_URL + "listing", itemInfo) + .then((response) => { + console.log("prøver: " + response.data); + return response; + }) + .catch((error) => { + console.log(error.response); + return error; + }); } diff --git a/src/views/MemberListView.vue b/src/views/MemberListView.vue new file mode 100644 index 0000000000000000000000000000000000000000..5e07022277233e69cbecd17ee711b194157619ad --- /dev/null +++ b/src/views/MemberListView.vue @@ -0,0 +1,83 @@ +<template> + <div class="flex items-center justify-between mx-4"> + <div class="flex-1 min-w-0"> + <h2 + class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate" + > + {{ groupe.name }} + </h2> + <div + class="mt-1 flex flex-col sm:flex-row sm:flex-wrap sm:mt-0 sm:space-x-6" + > + <div class="mt-2 flex items-center text-sm text-gray-500"> + <svg + class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fill-rule="evenodd" + d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" + clip-rule="evenodd" + /> + </svg> + {{ groupe.address }} + </div> + </div> + </div> + <div class="flex"> + <span class="hidden sm:block"> + <button + v-if="adminStatus" + @click="edit()" + type="button" + class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + > + <!-- Heroicon name: solid/pencil --> + <svg + class="-ml-1 mr-2 h-5 w-5 text-gray-500" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" + /> + </svg> + Edit + </button> + </span> + </div> + </div> + <div class="h-screen bg-gray-200 content-top grid place-items-top"> + <member-list :admin="editing" /> + </div> +</template> + +<script> +import MemberList from "@/components/MemberList.vue"; + +export default { + data() { + return { + groupe: { + name: "Solsikken borettslag", + address: "Sollia 6, 7033 Trondheim", + }, + adminStatus: false, + editing: false, + }; + }, + components: { + MemberList, + }, + methods: { + edit() { + this.editing = !this.editing; + }, + }, +}; +</script> diff --git a/src/views/ProfileView.vue b/src/views/ProfileView.vue index cfd2cefd902017ad7b867b7cfe447cce5648cf07..c4ea15c9d2d5c8f640b8180c226a64e4555535a2 100644 --- a/src/views/ProfileView.vue +++ b/src/views/ProfileView.vue @@ -1,6 +1,7 @@ -<!-- View for looking at different profile display methods --> <template> - <large-profile-card :isCurrentUser="true" /> + <div class="h-screen bg-gray-200 grid place-items-center"> + <large-profile-card :isCurrentUser="true" class="align-top" /> + </div> </template> <script>