Files
yourpart3/frontend/src/views/social/FriendsView.vue
Torsten Schulz (local) 6ad66da836 feat(localization): expand language support and enhance UI for user settings
- Added support for additional UI locales including Cebuano and Spanish, improving accessibility for a broader user base.
- Updated language selection components in the AppHeader and SettingsWidget to reflect new language options, enhancing user experience.
- Enhanced localization of various UI elements across components, ensuring consistent language representation and improved user engagement.
- Implemented logic to synchronize user language preferences with backend settings, providing a seamless experience when changing languages.
2026-04-02 07:54:44 +02:00

292 lines
9.8 KiB
Vue

<template>
<div class="friends-view">
<section class="friends-hero surface-card">
<div>
<span class="friends-kicker">{{ $t('friends.kicker') }}</span>
<h2>{{ $t('friends.title') }}</h2>
<p>{{ $t('friends.intro') }}</p>
</div>
<div class="friends-stats">
<div class="friends-stat surface-card">
<strong>{{ tabs[0].data.length }}</strong>
<span>{{ $t('friends.stats.existing') }}</span>
</div>
<div class="friends-stat surface-card">
<strong>{{ tabs[1].data.length + tabs[2].data.length }}</strong>
<span>{{ $t('friends.stats.open') }}</span>
</div>
</div>
</section>
<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" class="friends-panel surface-card">
<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) {
this.socket.on("friendshipChanged", () => {
this.fetchFriendships();
});
}
},
},
mounted() {
this.fetchFriendships();
this.setupSocketListener();
},
beforeUnmount() {
if (this.socket) {
this.socket.off("friendshipChanged");
}
},
};
</script>
<style scoped>
.friends-view {
max-width: var(--content-max-width);
margin: 0 auto;
padding-bottom: 24px;
}
.friends-hero {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 18px;
padding: 24px 26px;
margin-bottom: 16px;
}
.friends-kicker {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(120, 195, 138, 0.14);
color: #42634e;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.friends-hero p {
margin: 0;
color: var(--color-text-secondary);
}
.friends-stats {
display: flex;
gap: 12px;
}
.friends-stat {
min-width: 120px;
padding: 14px 16px;
text-align: center;
}
.friends-stat strong {
display: block;
font-size: 1.5rem;
line-height: 1;
margin-bottom: 6px;
}
.friends-stat span {
color: var(--color-text-secondary);
font-size: 0.82rem;
font-weight: 600;
}
.tabs-container {
display: flex;
gap: 8px;
padding: 0 0 12px;
border-bottom: 0;
flex-wrap: wrap;
}
.tab {
padding: 8px 14px;
text-align: center;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.7);
color: var(--color-text-secondary);
font-weight: bold;
border: 1px solid var(--color-border);
border-radius: 999px;
transition: background-color 0.3s ease, color 0.3s ease;
}
.tab:not(.active):hover {
background-color: rgba(248, 162, 43, 0.12);
}
.tab.active {
background-color: #F9A22C;
color: #7E471B;
border-color: #F9A22C;
}
.friends-panel {
padding: 16px;
}
.font-color-gender-male {
color: #1E90FF;
}
.font-color-gender-female {
color: #FF69B4;
}
.font-color-gender-nonbinary {
color: #DAA520;
}
@media (max-width: 960px) {
.friends-hero {
flex-direction: column;
align-items: flex-start;
}
}
</style>