Profile Component now accepts a user-object as paramter.

Started to implement the settings popup
This commit is contained in:
joschuatonn
2024-05-18 08:46:04 +02:00
parent e9b042a2ea
commit d19c441539
8 changed files with 240 additions and 60 deletions

View File

@@ -1,25 +1,23 @@
<script setup lang="ts">
const props = defineProps({
displayName: String,
username: String,
profilePictureUrl: String,
status: String,
})
import {message, ptpTestUser} from 'src/models';
const profilePicture = "url("+props.profilePictureUrl+")";
const props = defineProps({
user: Object,
selected: Boolean,
})
</script>
<template>
<div class="profileCard">
<div class="profilePicture"></div>
<div class="profileCard" :class="selected ? 'selected' : ''">
<div class="profilePicture" :style="{'background-image' : 'url('+props.user!.profilePictureUrl+')' }"></div>
<div style="margin-left:10px;">
<p style="margin-bottom: 0;line-height: 25px;vertical-align: middle;height: 25px;display: table-cell;">
<span style="font-weight: bold;font-size: 1.2rem;">{{props.displayName}}</span>
@{{props.username}}
<span style="font-weight: bold;font-size: 1.2rem;">{{props.user!.firstName}} {{ props.user!.lastName }}</span>
@{{props.user!.username}}
</p>
<p style="margin: 0;">{{ props.status }}</p>
<p style="margin: 0;">{{ props.user!.status }}</p>
</div>
</div>
</template>
@@ -31,7 +29,7 @@ const profilePicture = "url("+props.profilePictureUrl+")";
border-radius: 50%;
background-size: cover;
background-position: center;
background-image: v-bind('profilePicture');
}
.profileCard {
@@ -46,4 +44,8 @@ const profilePicture = "url("+props.profilePictureUrl+")";
transition: .3s;
background: rgba(0,0,0,0.2);
}
.selected {
background: rgba(0,0,0,0.2);
}
</style>

View File

@@ -3,6 +3,17 @@ export interface ptpUser {
firstName: string;
lastName: string;
username: string;
status?: string;
profilePictureUrl?: string;
}
export interface ptpTestUser {
id: string;
firstName: string;
lastName: string;
username: string;
status?: string;
profilePictureUrl?: string;
}
export interface responseModel {

View File

@@ -1,5 +1,93 @@
<template>
<div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;position: fixed;">
<!-- Hier gibt es die ganzen PopUps und so -->
<q-dialog v-model="settingsDialog" backdrop-filter="brightness(60%)" full-width>
<q-card style="margin: 3rem 20rem;padding: 30px;height: 80vh;overflow-y:scroll">
<q-card-section class="row items-center q-pb-none">
<h4 style="margin: 0;">Einstellungen</h4>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<h5>Nutzerdaten</h5>
<div style="display: flex; flex-wrap: nowrap">
<div>
<div v-if="userStore.userLoaded">
<p>{{ userStore.user?.firstName }}</p>
<p>{{ userStore.user?.lastName }}</p>
<p>{{ userStore.user?.username }}</p>
<p>{{ userStore.user?.id }}</p>
</div>
</div>
<div style="margin-left: 50px;">
<div style="border-radius:100%;height:10rem;width:10rem;background-image: url('https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0');background-size: cover; background-position: center;"></div>
</div>
</div>
</q-card-section>
<q-card-section>
<h5>Status</h5>
<p>Hier kannst du deinen Status festlegen</p>
<q-input filled v-model="newStatus" placeholder="" counter maxlength="256">
<!--<template v-slot:prepend>
<q-icon name="search"></q-icon>
</template>-->
<template v-slot:after>
<q-btn flat :disable="newStatus == newStatus">Speichern</q-btn>
</template>
</q-input>
</q-card-section>
<q-card-section>
<h5>Profilbild</h5>
<p>Du kannst eins der folgenden Porfilbilder aussuchen</p>
<div class="flex-center">
<q-btn
style="background-image: url('https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0');"
round
flat
class="profilePictureSelector"
>
</q-btn>
<q-btn
style="background-image: url('https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0');"
round
flat
class="profilePictureSelector"
>
</q-btn>
<q-btn
style="background-image: url('https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0');"
round
flat
class="profilePictureSelector"
>
</q-btn>
<q-btn
style="background-image: url('https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0');"
round
flat
class="profilePictureSelector"
>
</q-btn>
</div>
</q-card-section>
<q-card-section>
<q-btn @click="logout()" label="Abmelden" style="color:darkred;border:2px solid darkred;border-radius: 6px;"/>
</q-card-section>
</q-card>
</q-dialog>
<div style="background: gray; width: 25%;height:100%;">
<div style="display: flex; align-items: center; justify-content: space-between;" class="navigation">
<div title="Nach Nutzern suchen" @click="activeMenu = 'search'" :class="{menuItemActive : activeMenu === 'search', menuItemInactive : activeMenu !== 'search'}">
@@ -19,11 +107,16 @@
</template>
</q-input>
<div v-if="userStore.usersLoaded && searchQuery.length > 0" style="margin-top: 20px;">
<ProfileCardComponent v-for="user in userStore.users.filter(u => (u.username.includes(searchQuery) || (u.firstName + ' ' + u.lastName).includes(searchQuery) || searchQuery == '*') && u.id != userStore.id)" :displayName="user.firstName + ' ' + user.lastName" :username="user.username" profilePictureUrl="https://th.bing.com/th/id/OIP.ab5MRsPm1MXfNDVSMW6cjQHaHa?rs=1&pid=ImgDetMain" status="Hey there!"></ProfileCardComponent>
<div v-if="userStore.users.filter(u => u.username.includes(searchQuery) || (u.firstName + ' ' + u.lastName).includes(searchQuery) || searchQuery == '*').length == 0" style="text-align: center;">
<!--<q-icon name="error"></q-icon>
<p>Wir konnten keine Nutzer mit diesem Nutzernamen finden</p>-->
<div v-if="userStore.usersLoaded && searchQuery.length > 0 && searchQuery.replace(/\s/g, '').length" style="margin-top: 20px;">
<ProfileCardComponent
v-for="user in userStore.users.filter(u => (u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.trimStart().toLocaleLowerCase()) || searchQuery == '*') && u.id != userStore.id && searchQuery.replace(/\s/g, '').length)" :displayName="user.firstName + ' ' + user.lastName" :username="user.username"
:profilePictureUrl="user.profilePictureUrl"
:status="user.status"
:user="user"
:selected="selectedUser.id == user.id"
@click="selectUser(user as ptpTestUser)"
></ProfileCardComponent>
<div v-if="userStore.users.filter(u => u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.trimStart().toLocaleLowerCase()) || searchQuery == '*').length == 0 || !searchQuery.replace(/\s/g, '').length" style="text-align: center;">
<img src="https://media.tenor.com/KOZLvzU0o4kAAAAd/no-results.gif" style="border-radius: 1rem;" alt="No search results found">
</div>
</div>
@@ -32,7 +125,29 @@
</div>
<div style="margin: 20px;" v-if="activeMenu == 'chats'">
<p>Hier werden dann die Chats angezeigt</p>
<p>Chats, in denen du Mitglied bist:</p>
<div v-if="chatStore.chatsLoaded">
<div v-for="chat in chatStore.chats" :key="chat.id">
<div v-if="chat.members.length > 2">
<h5>{{ chat.displayName }}</h5>
</div>
<div v-else>
<h5>Ist ein Chat für zwei</h5>
</div>
<p v-for="member in chat.members">
{{ member.id }}
</p>
<!----<ProfileCardComponent
v-else
:user="userStore.users.find((user) => user.id === chat.members.find((member) => member.id !== userStore.user!.id)?.id)"
:selected="selectedUser.id == chat.members[0].id"
@click="selectUser(chat.members[0] as ptpTestUser)"
></ProfileCardComponent>-->
</div>
</div>
<br>
<q-input
v-model="chatName"
@@ -56,44 +171,31 @@
<q-option-group
v-model="usersForChat"
rules="[val => val.length > 0 || 'Please select at least one user']"
:options="userStore.users.filter(u => (u.username.includes(searchQuery) || (u.firstName + ' ' + u.lastName).includes(searchQuery) || searchQuery == '*') && u.id != userStore.id).map((u) => {
:options="userStore.users.filter(u => (u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || searchQuery == '*') && u.id != userStore.id).map((u) => {
return {label: u.firstName + ' ' + u.lastName, value: u.id}})"
type="checkbox"
color="primary"
inline
>
</q-option-group>
</div>
<q-btn @click="createChat(chatName)" label="Create Chat"/>
<br>
<q-btn @click="loadChats()" label="Load Chats"/>
<div v-if="chatStore.chatsLoaded">
<p>Chats:</p>
<div v-for="chat in chatStore.chats" :key="chat.id">
<h5>{{ chat.displayName }}</h5>
<div v-for="ptpUser in chat.members" :key="ptpUser.id">
<p>{{ ptpUser.id }}</p>
</div>
</div>
</div>
</div>
<q-btn @click="logout()" label="Logout"/>
<h5>Nutzerdaten</h5>
<div v-if="userStore.userLoaded">
<p>{{ userStore.user?.firstName }}</p>
<p>{{ userStore.user?.lastName }}</p>
<p>{{ userStore.user?.username }}</p>
<p>{{ userStore.user?.id }}</p>
</div>
<q-btn round flat icon="settings" title="Einstellungen" @click="settingsDialog = true">
</q-btn>
</div>
<div style="width: 75%;height: 100%;background: #DFD0BF;">
<div id="messageContainer" style="padding: 25px;overflow-y: auto;height: 90%;display: flex;flex-direction: column-reverse;">
<div v-for="(message, index) in messages" :key="index">
<div v-if="selectedUser.id != null" id="messageContainer" style="padding: 25px;overflow-y: auto;height: 90%;display: flex;flex-direction: column-reverse;">
<div v-if="!hasChatWith(selectedUser.id)" style="width: 100%; text-align: center;font-size: 1.2rem;">
<p>Das ist der Beginn deines Chats mit <strong>{{selectedUser.firstName}} {{selectedUser.lastName}}</strong></p>
</div>
<div v-else>
<p>Du hast schonmal mit dem geschrieben wie schön</p>
<div v-for="(message, index) in messages" :key="index">
<MessageComponent
v-if="message.gif"
:url="message.content"
@@ -117,6 +219,7 @@
:own-message="message.isOwn"
></MessageComponent>
</div>
</div>
</div>
<div class="footer">
@@ -165,7 +268,7 @@
import MessageComponent from 'components/MessageComponent.vue';
import { useTenorStore } from 'stores/tenor-store';
import ProfileCardComponent from 'components/ProfileCardComponent.vue';
import {message} from 'src/models';
import {message, ptpTestUser} from 'src/models';
import {useUserStore} from 'stores/user-store';
import { useChatStore } from 'stores/chat-store';
@@ -173,6 +276,10 @@
name: 'IndexPage'
});
const tenorStore = useTenorStore();
const userStore = useUserStore();
const chatStore = useChatStore();
const searchQuery = ref("")
const message = ref("");
const gifSearch = ref("");
@@ -180,6 +287,10 @@
const userSearch = ref('');
const usersForChat = ref([] as string[]);
const settingsDialog = ref(true);
const newStatus = ref("");
const activeMenu = ref("search");
@@ -236,9 +347,7 @@
changed.value = true;
}
const tenorStore = useTenorStore();
const userStore = useUserStore();
const chatStore = useChatStore();
function getGifs() {
@@ -289,12 +398,32 @@
chatName = "Chat";
}
await chatStore.createChat(chatName, usersForChat.value);
loadChats();
}
const loadChats = async() => {
await chatStore.loadChatsByUser();
};
const selectedUser = ref({} as ptpTestUser);
function selectUser(user : ptpTestUser) {
selectedUser.value = user;
}
function hasChatWith(id : string) : boolean {
// Hier wird geprüft, ob es ein Chat mit einem bestimmten anderen Nutzer existiert
const chatExists = chatStore.chats.find((chat) => chat.members.length === 2 && chat.members.find((member) => member.id === id));
if(chatExists) return true;
return false;
}
// Init methods
loadChats();
</script>
<style scoped>
@@ -349,4 +478,23 @@
.menuItemInactive {
border-bottom: 2px solid transparent;
}
.profilePictureSelector {
transition: .3s;
background-size: cover;
background-position:center;
width: 6rem;
height:6rem;
margin: 15px;
}
.profilePictureSelector:hover {
transition: .3s;
filter: brightness(0.6);
}
h4, h5 {
font-weight: bold;
margin-bottom: 0;
}
</style>

View File

@@ -1,12 +1,13 @@
import { defineStore } from 'pinia';
import { chat } from 'src/models';
import {chat, responseModel} from 'src/models';
import { api } from 'src/boot/axios';
import { useUserStore } from 'stores/user-store';
export const useChatStore = defineStore('chatStore',{
state:() => ({
chats: [] as chat[],
chatsLoaded: false
chatsLoaded: false,
chatExistsReponse: false,
}),
actions: {
async getChatsByUser(): Promise<chat[]> {
@@ -33,6 +34,13 @@ export const useChatStore = defineStore('chatStore',{
}
console.log(members);
return (await api.post("/chats/create/" + chatName, members)).data;
},
async loadDoesChatExist(keycloakID : string, otherKeycloakID : string) : Promise<responseModel> {
return (await api.get("/chats/"+keycloakID+"/"+otherKeycloakID)).data;
},
doesChatExist(keycloakID : string, otherKeycloakID : string) {
this.loadDoesChatExist(keycloakID, otherKeycloakID)
.then(m => this.chatExistsReponse = m.success);
}
}
});

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import {api} from 'boot/axios';
import { ptpUser, responseModel, tenorResponseElement } from 'src/models';
import { tenorResponseElement } from 'src/models';
import Keycloak from 'keycloak-js';
export const useTenorStore = defineStore('tenorStore', {

View File

@@ -49,6 +49,17 @@ export const useUserStore = defineStore('userStore', {
getAllPtpUsers() {
this.loadAllPtpUsers().then((response : ptpUser[]) => {
this.users = response;
this.users.forEach((user : ptpUser) => {
if(user.profilePictureUrl == null) {
user.profilePictureUrl = "https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0";
}
if(user.status == null) {
user.status = "Hey there, I am using ptpChat!";
}
})
this.usersLoaded = true;
})
},