- 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.
292 lines
9.8 KiB
Vue
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>
|