Skip to content
Snippets Groups Projects
Commit 5c77a10e authored by Sander August Heggland Schrader's avatar Sander August Heggland Schrader
Browse files

Merge branch 'renting' of...

parents 7260b165 072d14b2
No related branches found
No related tags found
1 merge request!81Renting
Showing
with 488 additions and 94 deletions
image: node:16
stages:
- setup
# - setup
- test
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
#variables:
# npm_config_cache: "$CI_PROJECT_DIR/.npm"
# 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
#.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
#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
lint_test:
test:
stage: test
script:
- npm ci
- npm run lint
unit_test:
stage: test
script:
- npm run test:unit
#unit_test:
# stage: test
# script:
# - npm run test:unit
......@@ -8,6 +8,7 @@
"name": "frontend",
"version": "0.1.0",
"dependencies": {
"@heroicons/vue": "^1.0.6",
"@mdi/font": "5.9.55",
"@vuelidate/core": "^2.0.0-alpha.40",
"@vuelidate/validators": "^2.0.0-alpha.28",
......@@ -1707,6 +1708,14 @@
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@heroicons/vue": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.6.tgz",
"integrity": "sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==",
"peerDependencies": {
"vue": ">= 3"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.5.0",
"dev": true,
......@@ -16108,6 +16117,12 @@
"@hapi/hoek": "^9.0.0"
}
},
"@heroicons/vue": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.6.tgz",
"integrity": "sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==",
"requires": {}
},
"@humanwhocodes/config-array": {
"version": "0.5.0",
"dev": true,
......
......@@ -9,6 +9,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@heroicons/vue": "^1.0.6",
"@mdi/font": "5.9.55",
"@vuelidate/core": "^2.0.0-alpha.40",
"@vuelidate/validators": "^2.0.0-alpha.28",
......
public/favicon.ico

15 KiB

<!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>
......@@ -9,14 +9,18 @@
import { defineComponent } from "vue";
// Components
import NavBar from "./components/NavigationComponents/NavBar.vue";
import NavBar from "./components/BaseComponents/NavBar.vue";
export default defineComponent({
name: "App",
components: {
NavBar,
},
});
</script>
<style global>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
src/assets/members.png

4.38 KiB

src/assets/newCommunity.png

19 KiB

<template>
<button
class="block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
{{ text }}
</button>
</template>
<script>
export default {
name: "ColoredButton",
props: {
text: String,
},
};
</script>
<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"
>
{{ community.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>
{{ community.location }}
</div>
</div>
</div>
<div>
<span class="hidden sm:block">
<!-- Legg dette til i button: v-if="adminStatus" -->
<svg
@click="toggle"
xmlns="http://www.w3.org/2000/svg"
class="w-9 h-9 cursor-pointer"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<CommunityHamburger
v-if="hamburgerOpen"
class="origin-top-right absolute right-0"
:community-i-d="community.communityId"
/>
<!-- class="absolute" -->
</span>
</div>
</div>
</template>
<script>
import CommunityHamburger from "@/components/CommunityComponents/CommunityHamburger";
export default {
name: "CommunityHeader",
components: {
CommunityHamburger,
},
data() {
return {
hamburgerOpen: false,
};
},
props: {
adminStatus: Boolean,
community: {
communityId: Number,
name: String,
description: String,
visibility: Number,
location: String,
picture: String,
},
},
methods: {
toggle: function () {
if (this.hamburgerOpen) {
this.hamburgerOpen = false;
} else {
this.hamburgerOpen = true;
}
},
},
};
</script>
<template>
<!-- Main modal -->
<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 md:inset-0 h-modal md:h-full"
>
<div class="relative w-full h-full max-w-2xl p-4 md:h-auto">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div
class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"
>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
{{ title }}
</h3>
<button
@click="close()"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<p class="text-base leading-relaxed text-gray-500 dark:text-gray-400">
{{ message }}
</p>
</div>
<!-- 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>
</div>
</div>
</template>
<script>
export default {
name: "CustomFooterModal",
props: {
visible: Boolean,
title: String,
message: String,
},
methods: {
close() {
this.$emit("close");
},
},
};
</script>
<template>
<!-- Icon button -->
<button
class="block w-fit text-white text-base bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<div class="flex flex-row px-5 py-2.5 h-10">
<!-- Icon slot: Default content "Ban"-icon -->
<div class="h-6 w-6">
<slot>
<BanIcon />
</slot>
</div>
<p>{{ text }}</p>
</div>
</button>
</template>
<script>
import { BanIcon } from "@heroicons/vue/outline";
export default {
name: "IconButton",
props: {
text: String,
},
components: {
BanIcon,
},
};
</script>
<template>
<nav
class="flex items-center justify-between bg-white h-14 border-1 border-b border-gray-300 border-solid"
class="flex items-center justify-between bg-white h-14 border-1 border-b border-gray-300 border-solid sticky top-0 z-50"
>
<div class="logo">
<img
......
<template>
<!-- Main modal -->
<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 md:inset-0 h-modal md:h-full"
>
<div class="relative w-full h-full max-w-2xl p-4 md:h-auto">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div
class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"
>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
{{ title }}
</h3>
<button
@click="close()"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<p class="text-base leading-relaxed text-gray-500 dark:text-gray-400">
{{ message }}
</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "NotificationModal",
props: {
visible: Boolean,
title: String,
message: String,
},
methods: {
close() {
this.$emit("close");
},
},
};
</script>
......@@ -47,7 +47,7 @@ export default {
calculateTime() {
//let time = this.message.from;
// Calculate time when message was sent
let date = new Date(Date.now());
let date = new Date(this.message.timestamp);
let hours = date.getHours();
let minutes = "0" + date.getMinutes();
let formattedTime = hours + ":" + minutes.substr(-2);
......
<template>
<div
id="dropdown"
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">
<li id="newItem">
<router-link
to="/addNewItem"
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"
>Opprett Utleie</router-link
>
</li>
<li id="getMembers">
<router-link
:to="'/group/' + communityID + '/memberlist'"
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"
>Se Medlemmer
</router-link>
</li>
<li id="adminGroup">
<router-link
:to="'/group/' + communityID + '/memberlist'"
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"
>Administrer Gruppe</router-link
>
</li>
<li id="leaveGroup">
<div
class="cursor-pointer block py-2 px-4 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
>
Forlat Gruppe
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "CommunityHamburger",
props: {
communityID: Number,
},
};
</script>
<template>
<section class="relative w-full max-w-md px-5 py-4 mx-auto rounded-md">
<div class="mb-5 mt-5 border-b-2 border-blue-900">
<label class="text-xl font-bold">Tøyenhus borettslag</label>
</div>
<section class="w-full px-5 py-4 mx-auto rounded-md">
<CommunityHeader
:admin-status="false"
:community="community"
class="mb-5"
/>
<!-- Search field -->
<div class="relative" 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">
......@@ -25,9 +29,17 @@
/>
</div>
<div class="absolute inset-x-0 px-6 py-3 mt-4">
<div class="grid grid-cols-2">
<ItemCard v-for="item in searchedItems" :key="item" :item="item" />
<!-- Item cards -->
<div class="absolute inset-x-0 px-6 py-3">
<div
class="grid grid-flow-row-dense grid-cols-2 md:grid-cols-4 lg:grid-cols-5 w-full place-items-center"
>
<ItemCard
v-for="item in searchedItems"
:key="item"
:item="item"
@click="goToItemInfoPage(item.listingID)"
/>
</div>
</div>
</section>
......@@ -35,10 +47,13 @@
<script>
import ItemCard from "@/components/CommunityComponents/ItemCard";
import CommunityHeader from "@/components/BaseComponents/CommunityHeader";
import { GetCommunity, GetListingsInCommunity } from "@/utils/apiutil";
export default {
name: "SearchItemListComponent",
components: {
CommunityHeader,
ItemCard,
},
......@@ -49,37 +64,51 @@ export default {
filteredItems = this.items.filter(
(p) =>
p.title.toLowerCase().includes(this.search.toLowerCase()) ||
p.adresse.toLowerCase().includes(this.search.toLowerCase()) ||
p.price === Number(this.search)
p.address.toLowerCase().includes(this.search.toLowerCase()) ||
p.pricePerDay === Number(this.search)
);
return filteredItems;
},
},
/**
* 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.
*/
created() {
if (this.$store.state.user.token !== null) {
this.isLoggedIn = true;
}
},
data() {
return {
items: [
{ img: "", adresse: "Oslo", title: "Dyson", price: 1000 },
{ img: "", adresse: "Trondheim", title: "Gressklipper", price: 500 },
{ img: "", adresse: "Bergen", title: "Bil", price: 500 },
],
items: [],
item: {
listingID: 0,
img: "",
adresse: "",
address: "",
title: "",
price: 0,
pricePerDay: 0,
},
search: "",
communityID: -1,
community: {},
};
},
methods: {
getCommunityFromAPI: async function () {
this.communityID = await this.$router.currentRoute.value.params
.communityID;
this.community = await GetCommunity(this.communityID);
},
getListingsOfCommunityFromAPI: async function () {
this.communityID = await this.$router.currentRoute.value.params
.communityID;
this.items = await GetListingsInCommunity(this.communityID);
},
goToItemInfoPage(item) {
this.$router.push("/itempage/" + item);
},
},
beforeMount() {
this.getCommunityFromAPI(); //To get the id of the community before mounting the view
this.getListingsOfCommunityFromAPI();
},
};
</script>
<template>
<ul>
<li v-for="(group, index) in groupList" :key="index">
<group-list-item :group="group" />
<li v-for="community in communities" :key="community">
<CommunityListItem :community="community" :member="member" />
</li>
</ul>
</template>
<script>
import GroupListItem from "@/components/CommunityComponents/CommunityListItem.vue";
import CommunityListItem from "@/components/CommunityComponents/CommunityListItem.vue";
export default {
name: "GroupList",
name: "CommunityList",
props: {
groupList: Array,
communities: Array,
member: Boolean,
},
components: {
GroupListItem,
CommunityListItem,
},
};
</script>
<template>
<CustomFooterModal
@close="this.dialogOpen = false"
:visible="dialogOpen"
:title="community.name"
:message="community.description"
>
<div class="flex justify-center p-2">
<ColoredButton
v-if="!member"
:text="'Bli med'"
@click="goToJoin(community.communityId)"
/>
<ColoredButton
v-if="member"
:text="'Gå til'"
@click="goToGroup(community.communityId)"
/>
</div>
</CustomFooterModal>
<div
@click="toggleDialog()"
class="bg-white shadow dark:bg-gray-800 select-none cursor-pointer hover:bg-gray-50 flex items-center p-4"
>
<div class="h-10 w-10 flex flex-col justify-center items-center mr-4">
<img alt="groupIMG" src="../../assets/group.png" />
<UserGroupIcon v-if="!community.image" alt="Felleskapets bilde" />
<!-- TODO: USE COMMUNITY IMAGE <img alt="Felleskapets bilde" src="@/assets/group.png" /> -->
</div>
<div class="flex-1 pl-1">
<div class="font-medium dark:text-white">
{{ group.name }}
<div class="flex-1 pl-1 overflow-hidden">
<div class="font-medium dark:text-white truncate">
{{ community.name }}
</div>
</div>
<div class="flex flex-row justify-center">
<button
@click="goToJoin(group.communityId)"
v-if="!isMember"
class="px-4 py-2 w-24 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"
>
Bli med
</button>
<button
v-if="isMember"
@click="goToGroup(group.communityId)"
class="px-4 py-2 w-24 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"
>
Gå til
</button>
<div class="flex flex-row justify-center items-center">
<LockClosedIcon
v-if="community.visibility === 0"
class="max-h-6 max-w-6 shrink m-2"
/>
</div>
</div>
</template>
<script>
import { getMyGroups } from "@/utils/apiutil";
import CustomFooterModal from "@/components/BaseComponents/CustomFooterModal.vue";
import ColoredButton from "@/components/BaseComponents/ColoredButton.vue";
import { UserGroupIcon, LockClosedIcon } from "@heroicons/vue/outline";
export default {
name: "GroupListItem",
name: "CommunityListItem",
components: {
CustomFooterModal,
ColoredButton,
UserGroupIcon,
LockClosedIcon,
},
data() {
return {
myGroups: [],
dialogOpen: false,
};
},
props: {
group: Object,
community: Object,
member: Boolean,
},
methods: {
goToGroup(id) {
......@@ -49,15 +69,9 @@ export default {
goToJoin(id) {
this.$router.push("/community/" + id + "/join");
},
async getMyGroups() {
this.myGroups = await getMyGroups();
toggleDialog() {
this.dialogOpen = !this.dialogOpen;
},
isMember(group) {
return this.myGroups.includes(group);
},
},
beforeMount() {
this.getMyGroups();
},
};
</script>
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