Skip to content
Snippets Groups Projects
Commit 98cfb0aa authored by Titus Netland's avatar Titus Netland
Browse files

Merge branch 'main' into 'group-home-view'

# Conflicts:
#   src/router/index.js
parents 494cfd20 73f80e9d
No related branches found
No related tags found
1 merge request!45Group home view
Pipeline #177420 passed
Showing
with 2589 additions and 5740 deletions
......@@ -12,6 +12,7 @@ module.exports = {
parser: "@babel/eslint-parser",
},
rules: {
"prettier/prettier": "warn",
"linebreak-style": 0,
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
......
This diff is collapsed.
......@@ -16,7 +16,10 @@
"core-js": "^3.8.3",
"cssom": "^0.5.0",
"jwt-decode": "^3.1.2",
"net": "^1.0.2",
"roboto-fontface": "*",
"sockjs-client": "^1.6.0",
"stompjs": "^2.3.3",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuelidate": "^0.7.7",
......
src/assets/additem.png

29.2 KiB | W: | H:

src/assets/additem.png

17.6 KiB | W: | H:

src/assets/additem.png
src/assets/additem.png
src/assets/additem.png
src/assets/additem.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/group.png

2.64 KiB

<template>
<ul>
<li v-for="(group, index) in groupList" :key="index">
<group-list-item :group="group" />
</li>
</ul>
</template>
<script>
import GroupListItem from "@/components/GroupeComponents/GroupListItem.vue";
export default {
name: "GroupList",
props: {
groupList: Array,
},
components: {
GroupListItem,
},
};
</script>
<template>
<div
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" />
</div>
<div class="flex-1 pl-1">
<div class="font-medium dark:text-white">
{{ group.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>
</div>
</template>
<script>
import { getMyGroups } from "@/utils/apiutil";
export default {
name: "GroupListItem",
data() {
return {
myGroups: [],
};
},
props: {
group: Object,
},
methods: {
goToGroup(id) {
this.$router.push("/community/" + id);
},
goToJoin(id) {
this.$router.push("/community/" + id + "/join");
},
async getMyGroups() {
this.myGroups = await getMyGroups();
},
isMember(group) {
return this.myGroups.includes(group);
},
},
beforeMount() {
this.getMyGroups();
},
};
</script>
......@@ -7,7 +7,7 @@
</template>
<script>
import UserListItemCard from "./UserProfileComponents/UserListItemCard.vue";
import UserListItemCard from "../UserProfileComponents/UserListItemCard.vue";
export default {
data() {
......
<template>
<div class="flex justify-center">
<router-link to="/login" class="m-6">Logg inn</router-link>
<router-link to="/register" class="m-6">Registrer deg</router-link>
<router-link to="/about" class="m-6">Om BoCo</router-link>
<div @click="logout" class="m-6 cursor-pointer"><p>Logout</p></div>
</div>
</template>
<script>
export default {
name: "HomeComponent",
data: () => ({}),
methods: {
logout() {
this.$store.commit("logout");
},
},
};
</script>
<template>
<nav class="flex items-center justify-between bg-white h-20 border-1 border-b border-gray-300 border-solid">
<nav
class="flex items-center justify-between bg-white h-14 border-1 border-b border-gray-300 border-solid"
>
<div class="logo">
<img
class="m-2 cursor-pointer h-16"
src="../assets/logo3.svg"
alt="BoCo logo"
@click="$router.push('/')"
class="m-1 ml-4 cursor-pointer h-12"
src="../assets/logo3.svg"
alt="BoCo logo"
@click="$router.push('/')"
/>
</div>
<ul class="flex">
<li>
<img
class="m-6 cursor-pointer h-8"
src="../assets/additem.png"
alt="Legg til"
@click="$router.push('/addNewItem')"
class="m-6 cursor-pointer h-7"
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')"
class="m-6 cursor-pointer h-7"
src="../assets/messages.png"
alt="Meldinger"
@click="$router.push('/messages')"
/>
</li>
<li>
<img
class="m-6 cursor-pointer h-8"
src="../assets/profile.png"
alt="Profil"
@click="loadProfile"
class="m-6 cursor-pointer h-7"
src="../assets/profile.png"
alt="Profil"
@click="loadProfile"
/>
</li>
</ul>
......
<template>
<div v-bind:class="'w-full break-words flex ' + side()">
<div style="max-width: 70%" v-bind:class="this.color() + ' rounded px-5 py-2 my-2 relative ' + this.textColor() ">
<span class="block">{{this.message.content}} {{this.message.from}}</span>
<span class="block text-xs text-right">{{this.calculateTime()}}</span>
</div>
</div>
</template>
<script>
import {parseCurrentUser} from "@/utils/token-utils";
export default {
props: {
message: Object
},
data() {
return {
abc: "abcde"
}
},
computed: {
userID() {
return parseCurrentUser().accountId;
}
},
methods: {
color() {
console.log(this.userID);
return this?.message.from == this.userID ? "bg-gray-300" : "bg-blue-600";
},
textColor() {
return this?.message.from == this.userID ? "text-gray-900" : "text-white";
},
side () {
return this?.message.from == this.userID ? "justify-start" : "justify-end"
},
calculateTime() {
//let time = this.message.from;
// Calculate time when message was sent
let date = new Date(Date.now());
let hours = date.getHours();
let minutes = "0" + date.getMinutes();
let formattedTime = hours + ':' + minutes.substr(-2);
return formattedTime;
}
}
}
</script>
\ No newline at end of file
<template>
<a
v-on:click="selectUser" class="hover:bg-gray-100 border-b border-gray-300 px-3 py-2 cursor-pointer flex items-center text-sm focus:outline-none focus:border-gray-300 transition duration-150 ease-in-out"
>
<img
class="h-10 w-10 rounded-full object-cover"
src="https://www.mintface.xyz/content/images/2021/08/QmTndiF423kjdXsNzsip1QQkBQqDuzDhJnGuJAXtv4XXiZ-1.png"
:alt="{name}"
/>
<div class="w-full pb-2">
<div class="flex justify-between">
<span class="block ml-2 font-semibold text-base text-gray-600"
>{{name}}</span
>
<span class="block ml-2 text-sm text-gray-600">{{lastMessageTime}}</span>
</div>
<span class="block ml-2 text-sm text-gray-600">{{lastMessage}}</span>
</div>
</a>
</template>
<script>
//TODO fix avatar
export default {
props: {
conversation: {
type: Object,
required: true
},
recipient: Function
},
data: () => {
return {
};
},
computed: {
lastMessage () {
return this.conversation.lastMessage.content;
},
name() {
return this.conversation.recipient.firstName + " " + this.conversation.recipient.lastName;
},
lastMessageTime() {
return "5 min";
}
},
methods: {
selectUser() {
console.log(this.conversation.recipient.userId)
this.$emit("recipient",this.conversation.recipient.userId);
}
}
};
</script>
<template>
<div class="col-span-3 sm:col-span-2 block">
<div class="w-full">
<div class="flex justify-between border-b border-gray-300">
<div class="grid sm:hidden space-y-2 content-center m-3">
<div class="w-8 h-0.5 bg-gray-600"></div>
<div class="w-8 h-0.5 bg-gray-600"></div>
<div class="w-8 h-0.5 bg-gray-600"></div>
</div>
<div class="relative flex items-center p-3">
<img class="object-cover w-10 h-10 rounded-full"
src="https://www.mintface.xyz/content/images/2021/08/QmTndiF423kjdXsNzsip1QQkBQqDuzDhJnGuJAXtv4XXiZ-1.png"
alt="{{name}}" />
<span class="block ml-2 font-bold text-gray-600">{{name}}</span>
</div>
<div></div>
</div>
<div class="relative w-full p-6 overflow-y-auto" style="max-height: 400px">
<ul class="space-y-2">
<div >
<ChatMessage v-for="(message, i) in messages" v-bind:key="i" :message="message"></ChatMessage>
</div>
</ul>
</div>
<div class="flex items-center justify-between w-full p-3 border-t border-gray-300">
<input type="text" placeholder="Message"
class="block w-full py-2 pl-4 mx-3 bg-gray-100 rounded-full outline-none focus:text-gray-700"
name="message" v-model="message" />
<button v-on:click="sendMessage" style="padding: 10px; color: red;">
<svg class="w-5 h-5 text-gray-500 origin-center transform rotate-90"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
</svg>
</button>
</div>
</div>
</div>
</template>
<script>
import ChatMessage from "./ChatMessage.vue"
import axios from 'axios';
import { parseCurrentUser } from "@/utils/token-utils";
import ws from '@/services/ws';
export default {
props: {
recipient: Object,
},
data: () => {
return {
messages: [],
message: ""
};
},
components: { ChatMessage },
computed: {
userid() {
return parseCurrentUser().accountId;
},
recipientID() {
return this.recipient.userId;
},
name() {
return this.recipient.firstName + " " + this.recipient.lastName;
}
},
methods: {
calculateSide(from) {
return from == this.userid ? 'end' : 'start'
},
async sendMessage() {
const token = this.$store.state.user.token;
await axios.post(process.env.VUE_APP_BASEURL + `chats/users/${this.recipientID}/messages`, {
message: this.message
}, {
headers: {
Authorization: `Bearer ${token}`
}
})
this.message = "";
ws.sendMessage({ sender: parseInt(this.userid), recipient: this.recipientID });
this.reloadMessages();
},
async reloadMessages() {
const token = this.$store.state.user.token;
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users/${this.recipientID}/messages`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
})
this.messages = await response.json()
}
},
async created() {
const token = this.$store.state.user.token;
// Fetch /chats/users/{userId}/messages from api
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users/${this.recipientID}/messages`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
});
ws.on("NEW_MESSAGE", () => {
this.reloadMessages();
})
this.messages = await response.json()
}
}
</script>
\ No newline at end of file
......@@ -3,5 +3,10 @@ import App from "./App.vue";
import router from "./router";
import store from "./store";
import "./index.css";
import ws from './services/ws';
createApp(App).use(router).use(store).mount("#app");
createApp(App)
.use(router)
.use(store)
.mount("#app");
console.log("WS", ws.test);
\ No newline at end of file
......@@ -19,7 +19,7 @@ function guardRoute(to, from, next) {
const routes = [
{
path: "/", //Endre før push
path: "/",
name: "home",
component: HomeView,
},
......@@ -39,6 +39,13 @@ const routes = [
name: "register",
component: () => import("../views/RegisterView.vue"),
},
{
path: "/messages",
name: "messages",
component: () =>
import(/* webpackChunkName: "register" */ "../views/ChatView.vue"),
beforeEnter: guardRoute,
},
{
path: "/login",
name: "login",
......
const Stomp = require("stompjs");
const SockJS = new require("sockjs-client")(process.env.VUE_APP_BASEURL + "ws");
import {parseCurrentUser} from "@/utils/token-utils";
// Create a Singleton function
// Events fired by websocket, MESSAGE
const ws = (function () {
// Object of all injected functions that the client may want
const handlers = {};
const fire = function (event, data) {
if (handlers[event]) {
handlers[event](data)
}
};
const onMessageReceived = (payload) => {
const data = JSON.parse(payload.body);
console.log("New message!")
// Fire message event
fire("MESSAGE", JSON.parse(payload.body));
if(data.status == "NEW_MESSAGE") fire("NEW_MESSAGE", JSON.parse(payload.body));
console.log("Received message: " + payload);
}
const onConnected = () => {
console.log("Websocket Connected");
stompClient.subscribe(
"/user/" + parseCurrentUser().accountId + "/queue/messages",
onMessageReceived
);
};
const onError = () => {
};
let stompClient = Stomp.over(SockJS);
stompClient.connect({}, onConnected, onError);
return {
on: function (event, callback) {
handlers[event] = callback;
},
fire: fire,
isActive: function (event) {
return !!handlers[event];
},
end: function (event) {
if (handlers[event]) {
delete handlers[event];
} else {
throw new Error("No handler for event: " + event);
}
},
sendMessage: ({sender, recipient, status }) => {
if(status) console.log(status)
stompClient.send("/app/chat", {}, JSON.stringify({
from: sender,
to: recipient,
id: null,
status: "NEW_MESSAGE"
}));
},
test: true
}
})();
export default ws;
\ No newline at end of file
......@@ -121,3 +121,29 @@ export function postNewgroup(groupInfo) {
return error;
});
}
export function getMyGroups() {
return axios
.get(API_URL + "user/communities", {
headers: tokenHeader(),
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error);
});
}
export function getVisibleGroups() {
return axios
.get(API_URL + "communities", {
headers: tokenHeader(),
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error);
});
}
<template>
<div class="min-h-full">
<div class=" border rounded grid grid-cols-3 w-full">
<div class="border-r border-gray-300 col-span-1">
<ul class="hidden sm:block overflow-auto h-full">
<h2 class="my-2 mb-2 ml-2 text-lg text-gray-600">Chats</h2>
<li>
<ChatProfile v-for="(conversation, i) in conversations" :conversation="conversation" :key="i" @recipient="selectUser"></ChatProfile>
</li>
</ul>
</div>
<CurrentChat v-if="selected" :recipient="selected" :key="key"></CurrentChat>
</div>
</div>
</template>
<script>
import ChatProfile from "@/components/chat/ChatProfile.vue";
import CurrentChat from "@/components/chat/CurrentChat.vue";
import {parseCurrentUser} from "@/utils/token-utils";
export default {
components: {
ChatProfile,
CurrentChat
},
data() {
return {
conversations: null,
selected: null
};
},
computed: {
userID() {
return parseCurrentUser().accountId;
},
key() {
return this.selected.userId || "ERROR" ;
}
},
methods: {
selectUser(value) {
const userid = value
this.conversations.find(conversation => {
return conversation.recipient.userId == userid;
})
this.selected = this.conversations.find(conversation => conversation.recipient.userId == userid).recipient;
console.log(this.selected)
}
},
async created() {
const token = this.$store.state.user.token;
// Get all conversations from api with /chats/users
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
}) // TODO add error handling
const res = await response.json();
this.conversations = res;
}
}
</script>
<template>
<Home />
<div>
<div id="myGroups">
<div>Mine grupper:</div>
<group-list :groupList="myGroups" />
</div>
<div id="localGroups">
<div>Offentlige grupper:</div>
<group-list :groupList="localGroups" />
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import GroupList from "@/components/GroupeComponents/GroupList.vue";
import { getMyGroups, getVisibleGroups } from "@/utils/apiutil";
// Components
import Home from "../components/HomeComponent.vue";
export default defineComponent({
export default {
name: "HomeView",
data() {
return {
myGroups: [],
localGroups: [],
};
},
components: {
Home,
GroupList,
},
methods: {
async getMyGroups() {
this.myGroups = await getMyGroups();
},
async getPotentialGroups() {
this.localGroups = await getVisibleGroups();
},
},
beforeMount() {
this.getMyGroups();
this.getPotentialGroups();
},
});
};
</script>
......@@ -58,7 +58,7 @@
</template>
<script>
import MemberList from "@/components/MemberList.vue";
import MemberList from "@/components/GroupeComponents/MemberList.vue";
export default {
data() {
......
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