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 @@
Endre passord
</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
id="firstPasswordField"
class="mt-4"
:class="{ error: v$.user.password.$errors.length }"
>
<label
......@@ -71,6 +98,12 @@
:text="'Sett ny passord'"
/>
</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>
</template>
......@@ -93,6 +126,9 @@ export default {
validations() {
return {
user: {
oldPassword: {
required: helpers.withMessage(`Feltet må være utfylt`, required),
},
password: {
required: helpers.withMessage(`Feltet må være utfylt`, required),
minLength: helpers.withMessage(
......@@ -112,7 +148,9 @@ export default {
},
data() {
return {
message: "",
user: {
oldPassword: "",
password: "",
rePassword: "",
},
......@@ -133,13 +171,24 @@ export default {
return;
}
const newPassword = this.user.password;
const newPassword = {
newPassword: this.user.password,
oldPassword: this.user.oldPassword,
};
const newPasswordResponse = await doNewPassword(newPassword);
if (newPasswordResponse != null) {
this.$store.commit("saveToken", newPasswordResponse);
if (newPasswordResponse.correctPassword) {
//New password was set
this.message = "";
this.$store.commit("saveToken", newPasswordResponse.token);
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() {
......
......@@ -31,7 +31,7 @@
</li>
<li>
<p class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
Rating ikke tilgjengelig
Ingen vurderinger
</p>
</li>
</ul>
......@@ -47,7 +47,7 @@ export default {
methods: {
getFill(i) {
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";
},
......
......@@ -32,9 +32,7 @@
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<p
class="text-lg text-base leading-relaxed text-gray-500 dark:text-gray-400"
>
<p class="text-lg leading-relaxed text-gray-500 dark:text-gray-400">
{{ title }}
</p>
</div>
......@@ -190,7 +188,7 @@ export default {
};
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 @@
</template>
<script>
import RatingComponent from "@/components/UserProfileComponents/Rating.vue";
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";
......
......@@ -47,7 +47,7 @@
</li>
<li>
<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"
>Leiehistorikk</router-link
>
......@@ -70,7 +70,7 @@
<li>
<router-link
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
>
</li>
......@@ -88,7 +88,7 @@
{{ user.firstName }} {{ user.lastName }}
</h5>
<div>
<rating-component :rating="renterRating" :ratingType="'Leietaker'" />
<rating-component :rating="renterRating" :ratingType="'Leietaker'"/>
<rating-component :rating="ownerRating" :ratingType="'Utleier'" />
</div>
......@@ -104,9 +104,10 @@
</template>
<script>
import RatingComponent from "@/components/UserProfileComponents/Rating.vue";
import RatingComponent from "@/components/UserProfileComponents/RatingComponents/Rating.vue";
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 {
name: "LargeProfileCard",
......@@ -135,10 +136,14 @@ export default {
return;
}
this.user = await getUser(this.id);
let rating = await getAverageRating(this.id);
if (rating >= 0 && rating <= 5) {
this.renterRating = rating;
this.ownerRating = rating;
let ratingAsOwner = await UserService.getUserRatingAsOwner(this.id);
let ratingAsRenter = await UserService.getUserRatingAsRenter(this.id)
if (ratingAsOwner >= 0 && ratingAsOwner <= 5) {
this.ownerRating = ratingAsOwner;
}
if (ratingAsRenter >= 0 && ratingAsRenter <= 5){
this.renterRating = ratingAsRenter;
}
},
getProfilePicture() {
......
......@@ -31,6 +31,12 @@ const routes = [
component: () => import("../views/UserProfileViews/ProfileView.vue"),
beforeEnter: guardRoute,
},
{
path: "/profile/history",
name: "history",
component: () => import("../views/UserProfileViews/RentHistoryView.vue"),
beforeEnter: guardRoute,
},
{
path: "/register",
name: "register",
......
......@@ -5,33 +5,91 @@ import axios from "axios";
const API_URL = process.env.VUE_APP_BASEURL;
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
.get(API_URL + "users/" + userId + "/profile", {
.get(API_URL + "user/profile/rent/history", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err));
.catch((err) => {
console.error(err);
return [];
});
}
async getUserRatingAverage(userId) {
async getOwnerHistory() {
return await axios
.get(API_URL + "rating/" + userId + "/average", {
.get(API_URL + "user/profile/rent/history/owner", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err));
.catch((err) => {
console.error(err);
});
}
//TODO
async getUserRatingAsOwner() {}
async isRated(rentID) {
return await axios
.get(API_URL + "rating/" + rentID + "/israted", {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => {
console.error(err);
});
}
//TODO
async getUserRatingAsRenter() {}
}
async getUserRatingAsRenter(userId) {
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) {
});
}
export async function doNewPassword(password) {
const auth = { correctPassword: false, token: "" };
let res = await axios({
method: "put",
url: API_URL + "user/profile/password",
url: API_URL + "user/password/change",
headers: tokenHeader(),
data: {
password: password,
},
})
.then((response) => {
return response;
auth.correctPassword = true;
auth.token = response.data;
return auth;
})
.catch((error) => {
console.error(error);
console.log(error);
return auth;
});
return res.data;
return res;
}
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`] = `
</h3>
<div
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"
>
<label
......@@ -59,5 +77,12 @@ exports[`NewPasswordForm component renders correctly 1`] = `
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>
</div>
`;
import { mount } from "@vue/test-utils";
import Rating from "@/components/UserProfileComponents/Rating.vue";
import Rating from "@/components/UserProfileComponents/RatingComponents/Rating.vue";
describe("Rating component", () => {
let wrapper;
......
......@@ -4,7 +4,7 @@ module.exports = defineConfig({
transpileDependencies: true,
chainWebpack: (config) => {
config.plugin("html").tap((args) => {
args[0].title = "Borrow Community";
args[0].title = "BoCo - Borrow Community";
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