websockets implemented
This commit is contained in:
22
frontend/src/api/friendshipApi.js
Normal file
22
frontend/src/api/friendshipApi.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import apiClient from "@/utils/axios.js";
|
||||
|
||||
export const getFriendships = async (acceptedOnly) => {
|
||||
const response = await apiClient.get(`/api/friendships?acceptedOnly=${acceptedOnly}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const endFriendship = async (friendUserId) => {
|
||||
await apiClient.post("/api/friendships/end", { friendUserId });
|
||||
};
|
||||
|
||||
export const acceptFriendship = async (friendUserId) => {
|
||||
await apiClient.post("/api/friendships/accept", { friendUserId });
|
||||
};
|
||||
|
||||
export const rejectFriendship = async (friendUserId) => {
|
||||
await apiClient.post("/api/friendships/reject", { friendUserId });
|
||||
};
|
||||
|
||||
export const withdrawRequest = async (friendUserId) => {
|
||||
await apiClient.post("/api/friendships/withdraw", { friendUserId });
|
||||
};
|
||||
@@ -92,4 +92,24 @@ span.button:hover {
|
||||
background: #fdf1db;
|
||||
color: #7E471B;
|
||||
border: 1px solid #7E471B;
|
||||
}
|
||||
|
||||
.font-color-gender-male {
|
||||
color: #1E90FF;
|
||||
}
|
||||
|
||||
.font-color-gender-female {
|
||||
color: #FF69B4;
|
||||
}
|
||||
|
||||
.font-color-gender-transmale {
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
.font-color-gender-transfemale {
|
||||
color: #FFB6C1;
|
||||
}
|
||||
|
||||
.font-color-gender-nonbinary {
|
||||
color: #DAA520;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<nav>
|
||||
<ul>
|
||||
<li v-for="(item, key) in menu" :key="key" class="mainmenuitem" @click="openPage(item.path ?? null, !!item.children)">
|
||||
<li v-for="(item, key) in menu" :key="key" class="mainmenuitem"
|
||||
@click="openPage(item.path ?? null, !!item.children)">
|
||||
<span v-if="item.icon" :style="`background-image:url('/images/icons/${item.icon}')`"
|
||||
class="menu-icon"> </span>
|
||||
<span>{{ $t(`navigation.${key}`) }}</span>
|
||||
@@ -18,6 +19,15 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-if="item.showLoggedinFriends === 1 && friendsList.length > 0" v-for="friend in friendsList" :key="friend.id">
|
||||
{{ friend.username }}
|
||||
<ul class="submenu2">
|
||||
<li @click="openChat(friend.id)">{{ $t('navigation.m-friends.chat') }}</li>
|
||||
<li @click="openProfile(friend.id)">{{ $t('navigation.m-friends.profile') }}</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="submenu1">
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -39,28 +49,44 @@ export default {
|
||||
name: 'AppNavigation',
|
||||
data() {
|
||||
return {
|
||||
forumList: []
|
||||
forumList: [],
|
||||
friendsList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['menu', 'user']),
|
||||
...mapGetters(['menu', 'user', 'menuNeedsUpdate']),
|
||||
},
|
||||
watch: {
|
||||
menuNeedsUpdate(newValue) {
|
||||
if (newValue) {
|
||||
console.log('Menu needs update, loading menu...');
|
||||
this.loadMenu();
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.user && this.user.id) {
|
||||
this.loadMenu();
|
||||
this.fetchForums();
|
||||
this.fetchFriends();
|
||||
} else {
|
||||
console.log(this.user);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.forumInterval = setInterval(() => {
|
||||
this.fetchForums();
|
||||
}, 60000);
|
||||
if (this.$store.getters.socket) {
|
||||
this.$store.getters.socket.on('forumschanged', (data) => {
|
||||
this.fetchForums();
|
||||
});
|
||||
this.$store.getters.socket.on('friendloginchanged', () => {
|
||||
this.fetchFriends();
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.forumInterval) {
|
||||
clearInterval(this.forumInterval);
|
||||
beforeUnmount() {
|
||||
if (this.$store.getters.socket) {
|
||||
this.$store.getters.socket.off('forumschanged');
|
||||
this.$store.getters.socket.off('friendloginchanged');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -70,7 +96,6 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (url) {
|
||||
console.log('openPage', url);
|
||||
this.$router.push(url);
|
||||
}
|
||||
},
|
||||
@@ -87,6 +112,29 @@ export default {
|
||||
} catch (error) {
|
||||
console.error('Error fetching forums:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchFriends() {
|
||||
try {
|
||||
const response = await apiClient.get('/api/socialnetwork/friends/loggedin');
|
||||
this.friendsList = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching friends:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async openProfile(hashedId) {
|
||||
console.log(hashedId);
|
||||
this.$root.$refs.userProfileDialog.userId = hashedId;
|
||||
this.$root.$refs.userProfileDialog.open();
|
||||
},
|
||||
|
||||
async openChat(hashedId) {
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<span @click="$emit('delete-folder', folder)" class="icon delete-icon" title="Delete folder">✖</span>
|
||||
</span>
|
||||
<template v-for="i in depth">
|
||||
<span v-if="showPipe(i)" class="marker filler">|</span>
|
||||
<span v-if="showPipe(i)" class="marker filler tee">|</span>
|
||||
<span v-else class="marker filler"> </span>
|
||||
</template>
|
||||
<span v-if="isLastItem" class="end-marker marker">⌞</span>
|
||||
<span v-else class="marker">├</span>
|
||||
<span v-else class="marker tee">├</span>
|
||||
<span class="folder-name-text"> {{ folder.name }}</span>
|
||||
</div>
|
||||
|
||||
@@ -115,4 +115,8 @@ export default {
|
||||
.folder-name-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tee {
|
||||
margin: 0 1px 0 -1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<label>{{ $t("socialnetwork.gallery.create_folder_dialog.parent_folder") }}</label>
|
||||
<!-- Hier wird der übergeordnete Ordner angezeigt, aber nicht bearbeitbar -->
|
||||
<input type="text" :value="parentFolder.name" disabled />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="folderTitle">{{ $t("socialnetwork.gallery.create_folder_dialog.folder_title") }}</label>
|
||||
<!-- Setze den Titel des Ordners für Bearbeiten -->
|
||||
<input type="text" v-model="folderTitle"
|
||||
:placeholder="$t('socialnetwork.gallery.create_folder_dialog.folder_title')" required />
|
||||
</div>
|
||||
@@ -42,6 +40,7 @@ import Multiselect from 'vue-multiselect';
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { EventBus } from '@/utils/eventBus.js';
|
||||
|
||||
export default {
|
||||
name: 'CreateFolderDialog',
|
||||
@@ -90,7 +89,6 @@ export default {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading visibility options:', error);
|
||||
}
|
||||
},
|
||||
async createFolder() {
|
||||
@@ -105,11 +103,11 @@ export default {
|
||||
};
|
||||
try {
|
||||
if (this.parentFolder.id) {
|
||||
await apiClient.put(`/api/socialnetwork/folders/${this.parentFolder.id}`, payload);
|
||||
await apiClient.post(`/api/socialnetwork/folders/${this.parentFolder.id}`, payload);
|
||||
} else {
|
||||
await apiClient.post(`/api/socialnetwork/folders/${this.folderId}`, payload);
|
||||
}
|
||||
this.$emit('created', payload);
|
||||
EventBus.emit('folderCreated');
|
||||
this.closeDialog();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen/Bearbeiten des Ordners:', error);
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
name="UserProfileDialog" display="flex">
|
||||
<div class="activities">
|
||||
<span>{{ $t(`socialnetwork.friendship.state.${friendshipState}`) }}</span>
|
||||
<img v-if="['none', 'denied', 'withdrawn'].includes(friendshipState)" src="/images/icons/request-friendship.png"
|
||||
@click="handleFriendship()" />
|
||||
<img v-else-if="['accepted', 'open']" src="/images/icons/cancel-friendship.png"
|
||||
<img :src="'/images/icons/' +
|
||||
(['none', 'denied', 'withdrawn'].includes(friendshipState) ? 'request-friendship.png' : 'cancel-friendship.png')"
|
||||
@click="handleFriendship()" />
|
||||
</div>
|
||||
<div class="profile-content">
|
||||
@@ -320,7 +319,7 @@ export default {
|
||||
},
|
||||
async handleFriendship() {
|
||||
console.log(this.friendshipState);
|
||||
if (this.friendshipState === 'none') {
|
||||
if (['none', 'withdrawn'].includes(this.friendshipState)) {
|
||||
this.requestFriendship();
|
||||
} else if (this.friendshipState === 'waiting') {
|
||||
this.cancelFriendship();
|
||||
|
||||
@@ -12,6 +12,7 @@ import enActivate from './locales/en/activate.json';
|
||||
import enSettings from './locales/en/settings.json';
|
||||
import enAdmin from './locales/en/admin.json';
|
||||
import enSocialNetwork from './locales/en/socialnetwork.json';
|
||||
import enFriends from './locales/en/friends.json';
|
||||
|
||||
import deGeneral from './locales/de/general.json';
|
||||
import deHeader from './locales/de/header.json';
|
||||
@@ -24,6 +25,7 @@ import deActivate from './locales/de/activate.json';
|
||||
import deSettings from './locales/de/settings.json';
|
||||
import deAdmin from './locales/de/admin.json';
|
||||
import deSocialNetwork from './locales/de/socialnetwork.json';
|
||||
import deFriends from './locales/de/friends.json';
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
@@ -38,6 +40,7 @@ const messages = {
|
||||
...enSettings,
|
||||
...enAdmin,
|
||||
...enSocialNetwork,
|
||||
...enFriends,
|
||||
},
|
||||
de: {
|
||||
'Ok': 'Ok',
|
||||
@@ -52,6 +55,7 @@ const messages = {
|
||||
...deSettings,
|
||||
...deAdmin,
|
||||
...deSocialNetwork,
|
||||
...deFriends,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
23
frontend/src/i18n/locales/de/friends.json
Normal file
23
frontend/src/i18n/locales/de/friends.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"friends": {
|
||||
"title": "Freunde",
|
||||
"tabs": {
|
||||
"existing": "Bestehende",
|
||||
"rejected": "Abgelehnte",
|
||||
"pending": "Ausstehende",
|
||||
"requested": "Angefragte"
|
||||
},
|
||||
"actions": {
|
||||
"end": "Beenden",
|
||||
"accept": "Annehmen",
|
||||
"reject": "Ablehnen",
|
||||
"withdraw": "Zurückziehen"
|
||||
},
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"age": "Alter",
|
||||
"gender": "Geschlecht",
|
||||
"actions": "Aktionen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,15 @@
|
||||
"OK": "Ok",
|
||||
"Cancel": "Abbrechen",
|
||||
"yes": "Ja",
|
||||
"no": "Nein"
|
||||
"no": "Nein",
|
||||
"message": {
|
||||
"close": "Schließen"
|
||||
},
|
||||
"gender": {
|
||||
"male": "Männlich",
|
||||
"female": "Weiblich",
|
||||
"transmale": "Trans-Mann",
|
||||
"transfemale": "Trans-Frau",
|
||||
"nonbinary": "Nichtbinär"
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,9 @@
|
||||
"falukant": "Falukant"
|
||||
},
|
||||
"m-friends": {
|
||||
"manageFriends": "Freunde verwalten"
|
||||
"manageFriends": "Freunde verwalten",
|
||||
"chat": "Chatten",
|
||||
"profile": "Profil"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
frontend/src/i18n/locales/en/friends.json
Normal file
3
frontend/src/i18n/locales/en/friends.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import store from './store';
|
||||
import router from './router';
|
||||
import './assets/styles.scss';
|
||||
import i18n from './i18n';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import * as components from 'vuetify/components';
|
||||
import * as directives from 'vuetify/directives';
|
||||
|
||||
function getBrowserLanguage() {
|
||||
const browserLanguage = navigator.language || navigator.languages[0];
|
||||
@@ -14,6 +17,11 @@ function getBrowserLanguage() {
|
||||
}
|
||||
}
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
});
|
||||
|
||||
store.dispatch('setLanguage', getBrowserLanguage());
|
||||
|
||||
const app = createApp(App);
|
||||
@@ -21,6 +29,7 @@ const app = createApp(App);
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(vuetify);
|
||||
|
||||
store.dispatch('loadLoginState');
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import DiaryView from '../views/social/DiaryView.vue';
|
||||
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
||||
import ForumView from '../views/social/ForumView.vue';
|
||||
import ForumTopicView from '../views/social/ForumTopicView.vue';
|
||||
import FriendsView from '../views/social/FriendsView.vue';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -29,6 +30,12 @@ const routes = [
|
||||
name: 'Activate page',
|
||||
component: ActivateView
|
||||
},
|
||||
{
|
||||
path: '/friends',
|
||||
name: 'Friends',
|
||||
component: FriendsView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/socialnetwork/guestbook',
|
||||
name: 'Guestbook',
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createStore } from 'vuex';
|
||||
import dialogs from './modules/dialogs';
|
||||
import loadMenu from '../utils/menuLoader.js';
|
||||
import router from '../router';
|
||||
import apiClient from '../utils/axios.js';
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
const store = createStore({
|
||||
state: {
|
||||
@@ -9,6 +11,8 @@ const store = createStore({
|
||||
user: null,
|
||||
language: navigator.language.startsWith('de') ? 'de' : 'en',
|
||||
menu: [],
|
||||
socket: null,
|
||||
menuNeedsUpdate: false,
|
||||
},
|
||||
mutations: {
|
||||
async dologin(state, user) {
|
||||
@@ -16,48 +20,74 @@ const store = createStore({
|
||||
state.user = user;
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
state.menuNeedsUpdate = true;
|
||||
if (user.param.filter(param => ['birthdate', 'gender'].includes(param.name)).length < 2) {
|
||||
router.push({ path: '/settings/personal' });
|
||||
}
|
||||
},
|
||||
dologout(state) {
|
||||
async dologout(state) {
|
||||
state.isLoggedIn = false;
|
||||
state.user = null;
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('menu');
|
||||
},
|
||||
loadLoginState(state) {
|
||||
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
|
||||
let userData = {};
|
||||
try {
|
||||
userData = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : {};
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
const menu = localStorage.getItem('menu');
|
||||
const user = userData;
|
||||
state.isLoggedIn = isLoggedIn;
|
||||
state.user = user;
|
||||
state.menu = menu;
|
||||
state.menuNeedsUpdate = false;
|
||||
// await apiClient.get('/api/auth/logout');
|
||||
},
|
||||
setLanguage(state, language) {
|
||||
state.language = language;
|
||||
},
|
||||
setMenu(state, menu) {
|
||||
state.menu = menu;
|
||||
}
|
||||
state.menuNeedsUpdate = false;
|
||||
},
|
||||
setSocket(state, socket) {
|
||||
state.socket = socket;
|
||||
},
|
||||
clearSocket(state) {
|
||||
state.socket = null;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async login({ commit, dispatch }, user) {
|
||||
async login({ commit, dispatch }, user) {
|
||||
console.log('login', user);
|
||||
await commit('dologin', user);
|
||||
await dispatch('loadMenu');
|
||||
await dispatch('initializeSocket');
|
||||
const socket = this.getters.socket;
|
||||
if (socket) {
|
||||
console.log('Emitting setUserId:', user.id);
|
||||
socket.emit('setUserId', user.id);
|
||||
console.log('setUserId emitted successfully');
|
||||
} else {
|
||||
console.error('Socket not initialized');
|
||||
}
|
||||
await dispatch('loadMenu');
|
||||
dispatch('startMenuReload');
|
||||
},
|
||||
logout({ commit }) {
|
||||
logout({ commit, state }) {
|
||||
if (state.socket) {
|
||||
state.socket.disconnect();
|
||||
commit('clearSocket');
|
||||
}
|
||||
commit('dologout');
|
||||
router.push('/');
|
||||
},
|
||||
initializeSocket({ commit, state }) {
|
||||
if (state.isLoggedIn && state.user) {
|
||||
const socket = io(import.meta.env.VITE_API_BASE_URL); // oder Ihre URL
|
||||
console.log('Socket initialized:', socket);
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Socket connected:', socket.id);
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log('Socket disconnected:', reason);
|
||||
});
|
||||
|
||||
commit('setSocket', socket);
|
||||
}
|
||||
},
|
||||
loadLoginState({ commit }) {
|
||||
commit('loadLoginState');
|
||||
},
|
||||
@@ -73,17 +103,13 @@ const store = createStore({
|
||||
commit('setMenu', []);
|
||||
}
|
||||
},
|
||||
startMenuReload({ dispatch }) {
|
||||
setInterval(() => {
|
||||
dispatch('loadMenu');
|
||||
}, 10000);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
isLoggedIn: state => state.isLoggedIn,
|
||||
user: state => state.user,
|
||||
language: state => state.language,
|
||||
menu: state => state.menu,
|
||||
socket: state => state.socket,
|
||||
},
|
||||
modules: {
|
||||
dialogs,
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from 'axios';
|
||||
import store from '../store';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: import.meta.env.VUE_APP_API_BASE_URL || 'http://localhost:3001',
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
@@ -19,4 +19,13 @@ apiClient.interceptors.request.use(config => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
apiClient.interceptors.response.use(response => {
|
||||
return response;
|
||||
}, error => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
store.dispatch('logout');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
export default apiClient;
|
||||
|
||||
2
frontend/src/utils/eventBus.js
Normal file
2
frontend/src/utils/eventBus.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import mitt from 'mitt';
|
||||
export const EventBus = mitt();
|
||||
@@ -10,7 +10,6 @@ const loadMenu = async () => {
|
||||
const response = await apiClient.get('/api/navigation/' + userId);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
208
frontend/src/views/social/FriendsView.vue
Normal file
208
frontend/src/views/social/FriendsView.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ $t('friends.title') }}</h2>
|
||||
<div class="tabs-container">
|
||||
<div class="tab" v-for="(tab, index) in tabs" :key="tab.name" :class="{ active: activeTab === index }"
|
||||
@click="selectTab(index)">
|
||||
{{ $t(tab.label) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(tab, index) in tabs" v-show="activeTab === index" :key="tab.name">
|
||||
<v-data-table :items="paginatedData(tab.data, tab.pagination.page)" :headers="headers"
|
||||
:items-per-page="tab.pagination.itemsPerPage" class="elevation-1">
|
||||
<template v-slot:body="{ items }">
|
||||
<tr v-for="(item, index) in items" :key="index">
|
||||
<td :class="'font-color-gender-' + item.user.gender.toLowerCase().replace(' ', '')">
|
||||
{{ item.user.username }}
|
||||
</td>
|
||||
<td>{{ item.user.age }}</td>
|
||||
<td>{{ $t(`gender.${item.user.gender}`) }}</td>
|
||||
<td>
|
||||
<template v-if="tab.name === 'existingFriends'">
|
||||
<v-btn color="error" @click="endFriendship(item.user.hashedId)">{{ $t('friends.actions.end')
|
||||
}}</v-btn>
|
||||
</template>
|
||||
<template v-if="tab.name === 'pendingFriends'">
|
||||
<v-btn color="success" @click="acceptFriendship(item.user.hashedId)">{{
|
||||
$t('friends.actions.accept') }}</v-btn>
|
||||
<v-btn color="error" @click="rejectFriendship(item.user.hashedId)">{{
|
||||
$t('friends.actions.reject') }}</v-btn>
|
||||
</template>
|
||||
<template v-if="tab.name === 'requestedFriends'">
|
||||
<v-btn color="warning" @click="withdrawRequest(item.user.hashedId)">{{
|
||||
$t('friends.actions.withdraw') }}</v-btn>
|
||||
</template>
|
||||
<template v-if="tab.name === 'rejectedFriends'">
|
||||
<v-btn color="success" @click="acceptFriendship(item.user.hashedId)">{{
|
||||
$t('friends.actions.accept') }}</v-btn>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-pagination v-model="tab.pagination.page"
|
||||
:length="Math.ceil(tab.data.length / tab.pagination.itemsPerPage)" :total-visible="5"
|
||||
class="mt-4"></v-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFriendships, endFriendship, acceptFriendship, rejectFriendship, withdrawRequest } from "@/api/friendshipApi";
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "FriendsView",
|
||||
data() {
|
||||
return {
|
||||
activeTab: 0,
|
||||
headers: [
|
||||
{ text: this.$t("friends.headers.name"), value: "name" },
|
||||
{ text: this.$t("friends.headers.age"), value: "age" },
|
||||
{ text: this.$t("friends.headers.gender"), value: "gender" },
|
||||
{ text: this.$t("friends.headers.actions"), value: "actions", sortable: false },
|
||||
],
|
||||
tabs: [
|
||||
{ name: "existingFriends", label: "friends.tabs.existing", data: [], pagination: { page: 1, itemsPerPage: 10 } },
|
||||
{ name: "pendingFriends", label: "friends.tabs.pending", data: [], pagination: { page: 1, itemsPerPage: 10 } },
|
||||
{ name: "requestedFriends", label: "friends.tabs.requested", data: [], pagination: { page: 1, itemsPerPage: 10 } },
|
||||
{ name: "rejectedFriends", label: "friends.tabs.rejected", data: [], pagination: { page: 1, itemsPerPage: 10 } },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['socket', 'user']),
|
||||
},
|
||||
methods: {
|
||||
async fetchFriendships() {
|
||||
try {
|
||||
const friendships = await getFriendships(false);
|
||||
this.distributeFriendships(friendships);
|
||||
} catch (error) {
|
||||
console.error("Error fetching friendships:", error);
|
||||
}
|
||||
},
|
||||
distributeFriendships(friendships) {
|
||||
this.tabs.forEach(tab => (tab.data = []));
|
||||
friendships.forEach(friendship => {
|
||||
if (friendship.accepted) {
|
||||
this.tabs.find(tab => tab.name === "existingFriends").data.push(friendship);
|
||||
} else if (friendship.denied) {
|
||||
const tabName = friendship.initiatorId === this.$store.state.user.hashedId
|
||||
? "requestedFriends"
|
||||
: "rejectedFriends";
|
||||
this.tabs.find(tab => tab.name === tabName).data.push(friendship);
|
||||
} else if (friendship.withdrawn) {
|
||||
this.tabs.find(tab => tab.name === "requestedFriends").data.push(friendship);
|
||||
} else {
|
||||
const tabName = friendship.isInitiator
|
||||
? "requestedFriends"
|
||||
: "pendingFriends";
|
||||
this.tabs.find(tab => tab.name === tabName).data.push(friendship);
|
||||
}
|
||||
});
|
||||
},
|
||||
selectTab(index) {
|
||||
this.activeTab = index;
|
||||
},
|
||||
paginatedData(data, page) {
|
||||
const start = (page - 1) * 10;
|
||||
const end = start + 10;
|
||||
return data.slice(start, end);
|
||||
},
|
||||
async endFriendship(friendUserId) {
|
||||
try {
|
||||
await endFriendship(friendUserId);
|
||||
this.fetchFriendships();
|
||||
} catch (error) {
|
||||
console.error("Error ending friendship:", error);
|
||||
}
|
||||
},
|
||||
async acceptFriendship(friendUserId) {
|
||||
try {
|
||||
await acceptFriendship(friendUserId);
|
||||
this.fetchFriendships();
|
||||
} catch (error) {
|
||||
console.error("Error accepting friendship:", error);
|
||||
}
|
||||
},
|
||||
async rejectFriendship(friendUserId) {
|
||||
try {
|
||||
await rejectFriendship(friendUserId);
|
||||
this.fetchFriendships();
|
||||
} catch (error) {
|
||||
console.error("Error rejecting friendship:", error);
|
||||
}
|
||||
},
|
||||
async withdrawRequest(friendUserId) {
|
||||
try {
|
||||
await withdrawRequest(friendUserId);
|
||||
this.fetchFriendships();
|
||||
} catch (error) {
|
||||
console.error("Error withdrawing request:", error);
|
||||
}
|
||||
},
|
||||
setupSocketListener() {
|
||||
if (this.socket) {
|
||||
console.log("Setting up friendshipChanged listener");
|
||||
this.socket.on("friendshipChanged", (data) => {
|
||||
console.log("Friendship changed:", data);
|
||||
this.fetchFriendships();
|
||||
});
|
||||
} else {
|
||||
console.error("Socket not initialized");
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchFriendships();
|
||||
this.setupSocketListener();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.socket) {
|
||||
this.socket.off("friendshipChanged");
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #999;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 2px 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
border: 1px solid #999;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.tab:not(.active):hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: #F9A22C;
|
||||
color: #7E471B;
|
||||
border-color: #F9A22C;
|
||||
}
|
||||
|
||||
.font-color-gender-male {
|
||||
color: #1E90FF;
|
||||
}
|
||||
|
||||
.font-color-gender-female {
|
||||
color: #FF69B4;
|
||||
}
|
||||
|
||||
.font-color-gender-nonbinary {
|
||||
color: #DAA520;
|
||||
}
|
||||
</style>
|
||||
@@ -82,6 +82,7 @@ import apiClient from '@/utils/axios.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import FolderItem from '../../components/FolderItem.vue';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
import { EventBus } from '@/utils/eventBus.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -107,6 +108,10 @@ export default {
|
||||
if (this.folders) {
|
||||
this.selectFolder(this.folders);
|
||||
}
|
||||
EventBus.on('folderCreated', this.loadFolders);
|
||||
},
|
||||
beforeUnmount() {
|
||||
EventBus.off('folderCreated', this.loadFolders);
|
||||
},
|
||||
methods: {
|
||||
async loadFolders() {
|
||||
|
||||
Reference in New Issue
Block a user