Skip to content
Snippets Groups Projects
Commit ff2edf3d authored by Gilgard's avatar Gilgard
Browse files

Merge branch 'main' into help-page

parents de2867b2 6b921b82
No related branches found
No related tags found
1 merge request!100Help page
Pipeline #180544 passed
Showing
with 380 additions and 92 deletions
...@@ -8,8 +8,35 @@ ...@@ -8,8 +8,35 @@
Endre passord Endre passord
</h3> </h3>
<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
>
<input
type="password"
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 -->
<div v-for="(error, index) of v$.user.oldPassword.$errors" :key="index">
<div
class="text-red-600 text-sm"
v-show="showError"
id="oldPasswordErrorId"
>
{{ error.$message }}
</div>
</div>
</div>
<div <div
id="firstPasswordField" id="firstPasswordField"
class="mt-4"
:class="{ error: v$.user.password.$errors.length }" :class="{ error: v$.user.password.$errors.length }"
> >
<label <label
...@@ -71,6 +98,12 @@ ...@@ -71,6 +98,12 @@
:text="'Sett ny passord'" :text="'Sett ny passord'"
/> />
</div> </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"
>{{ message }}</label
>
</div>
</div> </div>
</template> </template>
...@@ -93,6 +126,9 @@ export default { ...@@ -93,6 +126,9 @@ export default {
validations() { validations() {
return { return {
user: { user: {
oldPassword: {
required: helpers.withMessage(`Feltet må være utfylt`, required),
},
password: { password: {
required: helpers.withMessage(`Feltet må være utfylt`, required), required: helpers.withMessage(`Feltet må være utfylt`, required),
minLength: helpers.withMessage( minLength: helpers.withMessage(
...@@ -112,7 +148,9 @@ export default { ...@@ -112,7 +148,9 @@ export default {
}, },
data() { data() {
return { return {
message: "",
user: { user: {
oldPassword: "",
password: "", password: "",
rePassword: "", rePassword: "",
}, },
...@@ -133,13 +171,24 @@ export default { ...@@ -133,13 +171,24 @@ export default {
return; return;
} }
const newPassword = this.user.password; const newPassword = {
newPassword: this.user.password,
oldPassword: this.user.oldPassword,
};
const newPasswordResponse = await doNewPassword(newPassword); const newPasswordResponse = await doNewPassword(newPassword);
if (newPasswordResponse != null) { if (newPasswordResponse.correctPassword) {
this.$store.commit("saveToken", newPasswordResponse); //New password was set
this.message = "";
this.$store.commit("saveToken", newPasswordResponse.token);
await this.$router.push("/"); await this.$router.push("/");
} else if (!newPasswordResponse.correctPassword) {
//The old password was not correct
this.message = "Det gamle passordet stemmer ikke for denne brukeren";
} else {
//Ops something went wrong
this.message = "Ops noe gikk galt";
} }
}, },
validate() { validate() {
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
</li> </li>
<li> <li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400"> <p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
Rating ikke tilgjengelig Ingen vurderinger
</p> </p>
</li> </li>
</ul> </ul>
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
methods: { methods: {
getFill(i) { getFill(i) {
if (i <= this.rating) { if (i <= this.rating) {
return "w-5 h-5 text-warn"; return "w-5 h-5 text-warn-medium";
} }
return "w-5 h-5 text-gray-300 dark:text-gray-500"; return "w-5 h-5 text-gray-300 dark:text-gray-500";
}, },
......
...@@ -32,9 +32,7 @@ ...@@ -32,9 +32,7 @@
</div> </div>
<!-- Modal body --> <!-- Modal body -->
<div class="p-6 space-y-6"> <div class="p-6 space-y-6">
<p <p class="text-lg leading-relaxed text-gray-500 dark:text-gray-400">
class="text-lg text-base leading-relaxed text-gray-500 dark:text-gray-400"
>
{{ title }} {{ title }}
</p> </p>
</div> </div>
...@@ -190,7 +188,7 @@ export default { ...@@ -190,7 +188,7 @@ export default {
}; };
await postNewRating(ratingInfo); await postNewRating(ratingInfo);
this.$router.push("/"); this.$emit("reload");
}, },
}, },
}; };
......
<template>
<div
class="bg-white shadow dark:bg-gray-800 select-none cursor-pointer hover:bg-gray-50"
>
<p class="font-bold mx-4" id="title">
{{ historyItem.listing.title }}
</p>
<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>
<div class="flex flex-col flex-1">
<div>
Fra:
{{ this.getDateString(historyItem.fromTime, isMultipleDays) }}
</div>
<div>
Til: {{ this.getDateString(historyItem.toTime, isMultipleDays) }}
</div>
</div>
<colored-button
v-if="!isRated"
:text="'Vurder'"
class="px-4 flex-1"
@click="
$emit('rate', {
show: true,
name: user.firstName + ' ' + user.lastName,
title: historyItem.listing.title,
rentID: historyItem.rentId,
renterIsReceiverOfRating: !currentUserIsRenter,
})
"
/>
</div>
</div>
</template>
<script>
import ColoredButton from "@/components/BaseComponents/ColoredButton.vue";
import { parseCurrentUser } from "@/utils/token-utils";
import userService from "@/services/user.service";
export default {
name: "RentHistoryItem",
components: {
ColoredButton,
},
data() {
return {
user: {},
isRated: true,
};
},
props: {
historyItem: {
rentId: Number,
fromTime: Number,
toTime: Number,
isAccepted: Boolean,
listing: {
listingID: Number,
title: String,
pricePerDay: Number,
address: String,
userID: Number,
},
renterId: Number,
},
},
computed: {
currentUser() {
return parseCurrentUser();
},
isMultipleDays() {
if (this.historyItem.toTime - this.historyItem.fromTime < 86400000) {
return false;
}
return true;
},
price() {
if (this.isMultipleDays) {
let numDays = Math.round(
(this.historyItem.toTime - this.historyItem.fromTime) / 86400000
);
return this.historyItem.listing.pricePerDay * numDays;
}
return this.historyItem.listing.pricePerDay;
},
currentUserIsRenter() {
return this.isCurrentUser(this.historyItem.renterId);
},
},
methods: {
getDateString(milliseconds) {
let today = new Date();
let date = new Date(milliseconds);
let dateString = date.getDate() + "." + (date.getMonth()+1);
if (date.getFullYear() != today.getFullYear()) {
dateString += "." + date.getFullYear();
}
return dateString;
},
isCurrentUser(id) {
return id == this.currentUser.accountId;
},
},
async beforeCreate() {
if (this.currentUserIsRenter) {
this.user = await userService.getUserFromId(
this.historyItem.listing.userID
);
} else {
this.user = await userService.getUserFromId(this.historyItem.renterId);
}
this.isRated = await userService.isRated(this.historyItem.rentId);
},
};
</script>
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
</template> </template>
<script> <script>
import RatingComponent from "@/components/UserProfileComponents/Rating.vue"; import RatingComponent from "@/components/UserProfileComponents/RatingComponents/Rating.vue";
import IconButton from "@/components/BaseComponents/IconButton.vue"; import IconButton from "@/components/BaseComponents/IconButton.vue";
import UserService from "@/services/user.service"; import UserService from "@/services/user.service";
import CommunityAdminService from "@/services/community-admin.service"; import CommunityAdminService from "@/services/community-admin.service";
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
</li> </li>
<li> <li>
<router-link <router-link
to="" to="/profile/history"
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" 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"
>Leiehistorikk</router-link >Leiehistorikk</router-link
> >
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
<li> <li>
<router-link <router-link
to="" to=""
class="block py-2 px-4 text-sm text-error hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" class="block py-2 px-4 text-sm text-error-dark hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>Slett bruker</router-link >Slett bruker</router-link
> >
</li> </li>
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
{{ user.firstName }} {{ user.lastName }} {{ user.firstName }} {{ user.lastName }}
</h5> </h5>
<div> <div>
<rating-component :rating="renterRating" :ratingType="'Leietaker'" /> <rating-component :rating="renterRating" :ratingType="'Leietaker'"/>
<rating-component :rating="ownerRating" :ratingType="'Utleier'" /> <rating-component :rating="ownerRating" :ratingType="'Utleier'" />
</div> </div>
...@@ -104,9 +104,10 @@ ...@@ -104,9 +104,10 @@
</template> </template>
<script> <script>
import RatingComponent from "@/components/UserProfileComponents/Rating.vue"; import RatingComponent from "@/components/UserProfileComponents/RatingComponents/Rating.vue";
import { parseCurrentUser } from "@/utils/token-utils"; import { parseCurrentUser } from "@/utils/token-utils";
import { getUser, getAverageRating } from "@/utils/apiutil"; import { getUser} from "@/utils/apiutil";
import UserService from "@/services/user.service"
export default { export default {
name: "LargeProfileCard", name: "LargeProfileCard",
...@@ -135,10 +136,14 @@ export default { ...@@ -135,10 +136,14 @@ export default {
return; return;
} }
this.user = await getUser(this.id); this.user = await getUser(this.id);
let rating = await getAverageRating(this.id); let ratingAsOwner = await UserService.getUserRatingAsOwner(this.id);
if (rating >= 0 && rating <= 5) { let ratingAsRenter = await UserService.getUserRatingAsRenter(this.id)
this.renterRating = rating;
this.ownerRating = rating; if (ratingAsOwner >= 0 && ratingAsOwner <= 5) {
this.ownerRating = ratingAsOwner;
}
if (ratingAsRenter >= 0 && ratingAsRenter <= 5){
this.renterRating = ratingAsRenter;
} }
}, },
getProfilePicture() { getProfilePicture() {
......
...@@ -31,6 +31,12 @@ const routes = [ ...@@ -31,6 +31,12 @@ const routes = [
component: () => import("../views/UserProfileViews/ProfileView.vue"), component: () => import("../views/UserProfileViews/ProfileView.vue"),
beforeEnter: guardRoute, beforeEnter: guardRoute,
}, },
{
path: "/profile/history",
name: "history",
component: () => import("../views/UserProfileViews/RentHistoryView.vue"),
beforeEnter: guardRoute,
},
{ {
path: "/register", path: "/register",
name: "register", name: "register",
......
...@@ -5,33 +5,91 @@ import axios from "axios"; ...@@ -5,33 +5,91 @@ import axios from "axios";
const API_URL = process.env.VUE_APP_BASEURL; const API_URL = process.env.VUE_APP_BASEURL;
class UserService { class UserService {
async getUserFromId(userId) { async getUserFromId(userId) {
return await axios
.get(API_URL + "users/" + userId + "/profile", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err));
}
async getUserRatingAverage(userId) {
return await axios
.get(API_URL + "rating/" + userId + "/average", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err));
}
async getRenterHistory() {
return await axios return await axios
.get(API_URL + "users/" + userId + "/profile", { .get(API_URL + "user/profile/rent/history", {
headers: tokenHeader(), headers: tokenHeader(),
}) })
.then((res) => { .then((res) => {
return res.data; return res.data;
}) })
.catch((err) => console.error(err)); .catch((err) => {
console.error(err);
return [];
});
} }
async getUserRatingAverage(userId) { async getOwnerHistory() {
return await axios return await axios
.get(API_URL + "rating/" + userId + "/average", { .get(API_URL + "user/profile/rent/history/owner", {
headers: tokenHeader(), headers: tokenHeader(),
}) })
.then((res) => { .then((res) => {
return res.data; return res.data;
}) })
.catch((err) => console.error(err)); .catch((err) => {
console.error(err);
});
} }
//TODO async isRated(rentID) {
async getUserRatingAsOwner() {} return await axios
.get(API_URL + "rating/" + rentID + "/israted", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => {
console.error(err);
});
}
//TODO async getUserRatingAsRenter(userId) {
async getUserRatingAsRenter() {} return await axios
} .get(API_URL + "rating/" + userId + "/average/renter", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err))
}
export default new UserService(); async getUserRatingAsOwner(userId) {
return await axios
.get(API_URL + "rating/" + userId + "/average/owner", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err))
}
}
export
default
new
UserService();
\ No newline at end of file
...@@ -85,21 +85,25 @@ export function getAverageRating(userid) { ...@@ -85,21 +85,25 @@ export function getAverageRating(userid) {
}); });
} }
export async function doNewPassword(password) { export async function doNewPassword(password) {
const auth = { correctPassword: false, token: "" };
let res = await axios({ let res = await axios({
method: "put", method: "put",
url: API_URL + "user/profile/password", url: API_URL + "user/password/change",
headers: tokenHeader(), headers: tokenHeader(),
data: { data: {
password: password, password: password,
}, },
}) })
.then((response) => { .then((response) => {
return response; auth.correctPassword = true;
auth.token = response.data;
return auth;
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.log(error);
return auth;
}); });
return res.data; return res;
} }
export function postNewItem(itemInfo) { export function postNewItem(itemInfo) {
......
<template>
<rating-modal
:visible="modal.show"
:name="modal.name"
:title="modal.title"
:rentID="modal.rentID"
:renterIsReceiverOfRating="modal.renterIsReceiverOfRating"
@close="modal.show = false"
@reload="this.$forceUpdate()"
/>
<ul>
<li v-for="historyItem in fullHistory" :key="historyItem.rentId">
<rent-history-item
:historyItem="historyItem"
@rate="(modal) => openModal(modal)"
/>
</li>
</ul>
</template>
<script>
import RentHistoryItem from "@/components/UserProfileComponents/RentHistoryComponents/RentHistoryItem.vue";
import RatingModal from "@/components/UserProfileComponents/RatingComponents/RatingModal.vue";
import UserService from "@/services/user.service";
export default {
components: {
RentHistoryItem,
RatingModal,
},
data() {
return {
renterHistory: [],
ownerHistory: [],
modal: {
show: false,
name: "",
title: "",
rentID: -1,
renterIsReceiverOfRating: false,
},
};
},
computed: {
fullHistory() {
function compareHistoryItems(itemA, itemB) {
if (itemA.fromTime > itemB.fromTime) {
return -1;
}
if (itemA.fromTime < itemB.fromTime) {
return 1;
}
return 0;
}
let fullHistory = this.renterHistory.concat(this.ownerHistory);
fullHistory.filter((item) => item.isAccepted);
fullHistory.sort(compareHistoryItems);
return fullHistory;
},
hasNoHistory() {
return this.renterHistory.length == 0 && this.ownerHistory.length == 0;
},
},
methods: {
openModal(modal) {
this.modal = modal;
},
},
async beforeCreate() {
this.renterHistory = await UserService.getRenterHistory();
this.ownerHistory = await UserService.getOwnerHistory();
},
};
</script>
import { GetCommunity, GetListingsInCommunity } from "@/utils/apiutil";
import axios from "axios";
jest.mock("axios");
describe("testing mocking of apiutil.js", () => {
it("check that existing group returns correctly", async () => {
const expectedResponse = {
communityId: 4040,
name: "Fisken i vannet",
description: "For vi som liker fjell fisk",
visibility: 1,
location: "Bergen brygge",
picture: "fish blub blub",
};
axios.get.mockImplementation(() =>
Promise.resolve({ data: expectedResponse })
);
const communityResponse = await GetCommunity(4040);
expect(communityResponse.name).toBe(expectedResponse.name);
});
it("check that existing group returns correct listings", async () => {
const expectedResponse = {
item1: {
title: "Fiskekurs",
description: "Fisking og sånn",
pricePerDay: 200,
address: "Vannet",
userID: 6,
categoryNames: null,
communityIDs: null,
},
item2: {
title: "TestFraFrontend",
description: "oslo",
pricePerDay: 500,
address: "oslo",
userID: 1,
categoryNames: null,
communityIDs: null,
},
};
axios.get.mockImplementation(() =>
Promise.resolve({ data: expectedResponse })
);
const communityItemResponse = await GetListingsInCommunity(4040);
expect(communityItemResponse).toBe(expectedResponse);
});
});
...@@ -11,6 +11,24 @@ exports[`NewPasswordForm component renders correctly 1`] = ` ...@@ -11,6 +11,24 @@ exports[`NewPasswordForm component renders correctly 1`] = `
</h3> </h3>
<div <div
class="" class=""
id="oldPasswordField"
>
<label
class="block text-sm text-gray-800 dark:text-gray-200"
for="oldPassword"
>
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" id="firstPasswordField"
> >
<label <label
...@@ -59,5 +77,12 @@ exports[`NewPasswordForm component renders correctly 1`] = ` ...@@ -59,5 +77,12 @@ exports[`NewPasswordForm component renders correctly 1`] = `
Sett ny passord Sett ny passord
</button> </button>
</div> </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>
</div> </div>
`; `;
import { mount } from "@vue/test-utils"; import { mount } from "@vue/test-utils";
import Rating from "@/components/UserProfileComponents/Rating.vue"; import Rating from "@/components/UserProfileComponents/RatingComponents/Rating.vue";
describe("Rating component", () => { describe("Rating component", () => {
let wrapper; let wrapper;
......
...@@ -4,7 +4,7 @@ module.exports = defineConfig({ ...@@ -4,7 +4,7 @@ module.exports = defineConfig({
transpileDependencies: true, transpileDependencies: true,
chainWebpack: (config) => { chainWebpack: (config) => {
config.plugin("html").tap((args) => { config.plugin("html").tap((args) => {
args[0].title = "Borrow Community"; args[0].title = "BoCo - Borrow Community";
return args; return args;
}); });
}, },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment