added decline action for friend requests and room invites
This commit is contained in:
@@ -25,10 +25,22 @@ export async function apiFetch<T>(
|
|||||||
throw new Error("Session expired")
|
throw new Error("Session expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle error responses
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const text = await res.text()
|
const text = await res.text()
|
||||||
throw new Error(text || res.statusText)
|
throw new Error(text || res.statusText)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json() as Promise<T>
|
// Get the response as text first
|
||||||
|
const responseText = await res.text()
|
||||||
|
|
||||||
|
if (!responseText) {
|
||||||
|
return {} as T
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(responseText) as T
|
||||||
|
} catch (e) {
|
||||||
|
return responseText as unknown as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,10 @@ export function acceptFriendRequest(senderUuid: string) {
|
|||||||
body: JSON.stringify({ sender_uuid: senderUuid }),
|
body: JSON.stringify({ sender_uuid: senderUuid }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function declineFriendRequest(senderUuid: string) {
|
||||||
|
return apiFetch<void>('/friends/decline', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ sender_uuid: senderUuid }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,3 +33,10 @@ export function acceptRoomInvite(senderUuid: string, roomUuid: string) {
|
|||||||
body: JSON.stringify({ sender_uuid: senderUuid, room_uuid: roomUuid }),
|
body: JSON.stringify({ sender_uuid: senderUuid, room_uuid: roomUuid }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function declineRoomInvite(senderUuid: string, roomUuid: string) {
|
||||||
|
return apiFetch<void>('/rooms/decline', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ sender_uuid: senderUuid, room_uuid: roomUuid }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 496 B |
@@ -9,6 +9,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="chat-container">
|
<div v-else class="chat-container">
|
||||||
|
<!-- <h2 class="room-name">{{ currentRoom?.name }}</h2> -->
|
||||||
|
|
||||||
<div class="messages-container" ref="messageListRef" @scroll="handleScroll">
|
<div class="messages-container" ref="messageListRef" @scroll="handleScroll">
|
||||||
<MessageList :messages="messages" />
|
<MessageList :messages="messages" />
|
||||||
</div>
|
</div>
|
||||||
@@ -218,6 +220,11 @@ onUnmounted(async () => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* .room-name { */
|
||||||
|
/* margin: 15px 0; */
|
||||||
|
/* text-align: center; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.loading-more {
|
.loading-more {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@@ -53,10 +53,13 @@ notifications-room-invites = Room Invites
|
|||||||
notifications-no-requests = No pending requests
|
notifications-no-requests = No pending requests
|
||||||
notifications-no-invites = No pending invites
|
notifications-no-invites = No pending invites
|
||||||
notifications-accept = Accept
|
notifications-accept = Accept
|
||||||
|
notifications-decline = Decline
|
||||||
notifications-join = Join
|
notifications-join = Join
|
||||||
notifications-invite-from = from: {$user}
|
notifications-invite-from = from: {$user}
|
||||||
notifications-error-friend = An error occurred while accepting the request.
|
notifications-error-friend-accept = An error occurred while accepting the request.
|
||||||
notifications-error-room = An error occurred while accepting the invite.
|
notifications-error-friend-decline = An error occurred while declining the request.
|
||||||
|
notifications-error-room-accept = An error occurred while accepting the invite.
|
||||||
|
notifications-error-room-decline = An error occurred while declining the invite.
|
||||||
|
|
||||||
## Settings page
|
## Settings page
|
||||||
settings-title = Settings
|
settings-title = Settings
|
||||||
|
|||||||
@@ -53,10 +53,13 @@ notifications-room-invites = Invitations
|
|||||||
notifications-no-requests = Aucune demande en attente
|
notifications-no-requests = Aucune demande en attente
|
||||||
notifications-no-invites = Aucune invitation en attente
|
notifications-no-invites = Aucune invitation en attente
|
||||||
notifications-accept = Accepter
|
notifications-accept = Accepter
|
||||||
|
notifications-decline = Refuser
|
||||||
notifications-join = Rejoindre
|
notifications-join = Rejoindre
|
||||||
notifications-invite-from = de : {$user}
|
notifications-invite-from = de : {$user}
|
||||||
notifications-error-friend = Erreur lors de l'acceptation de la demande.
|
notifications-error-friend-accept = Erreur lors de l'acceptation de la demande.
|
||||||
notifications-error-room = Erreur lors de l'acceptation de l'invitation.
|
notifications-error-friend-decline = Erreur lors du refus de la demande.
|
||||||
|
notifications-error-room-accept = Erreur lors de l'acceptation de l'invitation.
|
||||||
|
notifications-error-room-decline = Erreur lors du refus de l'invitation.
|
||||||
|
|
||||||
## Settings page
|
## Settings page
|
||||||
settings-title = Paramètres
|
settings-title = Paramètres
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
<ul v-if="requests.length">
|
<ul v-if="requests.length">
|
||||||
<li v-for="req in requests" :key="req.sender_uuid">
|
<li v-for="req in requests" :key="req.sender_uuid">
|
||||||
<span>{{ req.sender_username }}</span>
|
<span>{{ req.sender_username }}</span>
|
||||||
<button @click="acceptFriend(req.sender_uuid)">{{ $t('notifications-accept') }}</button>
|
<div class="actions">
|
||||||
|
<button @click="acceptFriend(req.sender_uuid)">{{ $t('notifications-accept') }}</button>
|
||||||
|
<button class="decline-btn" @click="declineFriend(req.sender_uuid)">{{ $t('notifications-decline')
|
||||||
|
}}</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else>{{ $t('notifications-no-requests') }}</p>
|
<p v-else>{{ $t('notifications-no-requests') }}</p>
|
||||||
@@ -22,7 +26,11 @@
|
|||||||
<li v-for="inv in invites" :key="inv.sender_uuid">
|
<li v-for="inv in invites" :key="inv.sender_uuid">
|
||||||
<span>{{ inv.room_name }}</span>
|
<span>{{ inv.room_name }}</span>
|
||||||
<span>{{ $t('notifications-invite-from', { user: inv.sender_username }) }}</span>
|
<span>{{ $t('notifications-invite-from', { user: inv.sender_username }) }}</span>
|
||||||
<button @click="acceptRoom(inv.sender_uuid, inv.room_uuid)">{{ $t('notifications-join') }}</button>
|
<div class="actions">
|
||||||
|
<button @click="acceptRoom(inv.sender_uuid, inv.room_uuid)">{{ $t('notifications-join') }}</button>
|
||||||
|
<button class="decline-btn" @click="declineRoom(inv.sender_uuid, inv.room_uuid)">{{
|
||||||
|
$t('notifications-decline') }}</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else>{{ $t('notifications-no-invites') }}</p>
|
<p v-else>{{ $t('notifications-no-invites') }}</p>
|
||||||
@@ -32,8 +40,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { fetchFriendRequests, acceptFriendRequest } from '../api/friends'
|
import { fetchFriendRequests, acceptFriendRequest, declineFriendRequest } from '../api/friends'
|
||||||
import { fetchRoomInvites, acceptRoomInvite } from '../api/rooms.ts'
|
import { fetchRoomInvites, acceptRoomInvite, declineRoomInvite } from '../api/rooms.ts'
|
||||||
import { useNotifications } from '../store'
|
import { useNotifications } from '../store'
|
||||||
import { useFluent } from 'fluent-vue';
|
import { useFluent } from 'fluent-vue';
|
||||||
|
|
||||||
@@ -54,7 +62,17 @@ async function acceptFriend(senderUuid: string) {
|
|||||||
requests.value = requests.value.filter(r => r.sender_uuid !== senderUuid)
|
requests.value = requests.value.filter(r => r.sender_uuid !== senderUuid)
|
||||||
// fetchFriends().then(f => (friends.value = f))
|
// fetchFriends().then(f => (friends.value = f))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMessage.value = $t('notifications-error-friend') // TODO: handle this case
|
errorMessage.value = $t('notifications-error-friend-accept')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function declineFriend(senderUuid: string) {
|
||||||
|
try {
|
||||||
|
await declineFriendRequest(senderUuid)
|
||||||
|
requests.value = requests.value.filter(r => r.sender_uuid !== senderUuid)
|
||||||
|
// fetchFriends().then(f => (friends.value = f))
|
||||||
|
} catch (err) {
|
||||||
|
errorMessage.value = $t('notifications-error-friend-decline')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +80,18 @@ async function acceptRoom(senderUuid: string, roomUuid: string) {
|
|||||||
try {
|
try {
|
||||||
await acceptRoomInvite(senderUuid, roomUuid)
|
await acceptRoomInvite(senderUuid, roomUuid)
|
||||||
invites.value = invites.value.filter(r => r.room_uuid !== roomUuid)
|
invites.value = invites.value.filter(r => r.room_uuid !== roomUuid)
|
||||||
// fetchFriends().then(f => (friends.value = f))
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMessage.value = $t('notifications-error-room') // TODO: handle this case
|
errorMessage.value = $t('notifications-error-room-accept')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function declineRoom(senderUuid: string, roomUuid: string) {
|
||||||
|
try {
|
||||||
|
await declineRoomInvite(senderUuid, roomUuid)
|
||||||
|
invites.value = invites.value.filter(r => r.room_uuid !== roomUuid)
|
||||||
|
} catch (err) {
|
||||||
|
errorMessage.value = $t('notifications-error-room-decline')
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +171,17 @@ async function acceptRoom(senderUuid: string, roomUuid: string) {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.decline-btn {
|
||||||
|
color: var(--text);
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.decline-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.friend-requests-container {
|
.friend-requests-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-page">
|
<div class="settings-page">
|
||||||
<h1>{{ $t('settings-title') }}</h1>
|
<h1>{{ $t('settings-title') }}</h1>
|
||||||
|
|
||||||
<UpdateAccountModal v-if="showUpdateModal" :user="user" @close="showUpdateModal = false" @updated="fetchUserData" />
|
<UpdateAccountModal v-if="showUpdateModal" :user="user" @close="showUpdateModal = false"
|
||||||
|
@updated="fetchUserData" />
|
||||||
|
|
||||||
<h2>{{ $t('settings-account') }}</h2>
|
<h2>{{ $t('settings-account') }}</h2>
|
||||||
<div v-if="user" class="info-card">
|
<div v-if="user" class="info-card">
|
||||||
<button class="update-btn" @click="showUpdateModal = true">{{ $t('settings-update-btn') }}</button>
|
<button class="update-btn" @click="showUpdateModal = true">{{ $t('settings-update-btn') }}</button>
|
||||||
<p><strong>{{ $t('settings-label-username') }}</strong> {{ user.username }}</p>
|
<p><strong>{{ $t('settings-label-username') }}</strong> {{ user.username }}</p>
|
||||||
<p><strong>{{ $t('settings-label-email') }}</strong> {{ user.email }}</p>
|
<p><strong>{{ $t('settings-label-email') }}</strong> {{ user.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="loading-state">
|
<div v-else class="loading-state">
|
||||||
<p>{{ $t('settings-loading') }}</p>
|
<p>{{ $t('settings-loading') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>{{ $t('settings-language') }}</h2>
|
<h2>{{ $t('settings-language') }}</h2>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="lang-grid">
|
<div class="lang-grid">
|
||||||
<button v-for="lang in languages" :key="lang.code" class="lang-btn"
|
<button v-for="lang in languages" :key="lang.code" class="lang-btn"
|
||||||
:class="{ active: currentLang === lang.code }" @click="changeLanguage(lang.code)">
|
:class="{ active: currentLang === lang.code }" @click="changeLanguage(lang.code)">
|
||||||
{{ lang.name }}
|
{{ lang.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="logout-btn" @click="logout">
|
||||||
|
<i class="fa-solid fa-right-from-bracket"></i>
|
||||||
|
<span>{{ $t('settings-logout-btn') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="logout-btn" @click="logout">
|
|
||||||
<i class="fa-solid fa-right-from-bracket"></i>
|
|
||||||
<span>{{ $t('settings-logout-btn') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -51,112 +52,112 @@ const currentLang = ref('')
|
|||||||
const languages = computed(() => getSupportedLanguagesMetadata())
|
const languages = computed(() => getSupportedLanguagesMetadata())
|
||||||
|
|
||||||
async function fetchUserData() {
|
async function fetchUserData() {
|
||||||
try {
|
try {
|
||||||
const auth = await getAuthData()
|
const auth = await getAuthData()
|
||||||
user.value = auth.user
|
user.value = auth.user
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load user data:", err)
|
console.error("Failed to load user data:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const pref = await getLocalePreference()
|
const pref = await getLocalePreference()
|
||||||
// Synchronize the UI state with the actual active language
|
// Synchronize the UI state with the actual active language
|
||||||
currentLang.value = pref || (navigator.language.split('-')[0])
|
currentLang.value = pref || (navigator.language.split('-')[0])
|
||||||
|
|
||||||
fetchUserData()
|
fetchUserData()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function changeLanguage(code: string) {
|
async function changeLanguage(code: string) {
|
||||||
const actual = setLanguage(code)
|
const actual = setLanguage(code)
|
||||||
currentLang.value = actual
|
currentLang.value = actual
|
||||||
await saveLocalePreference(actual)
|
await saveLocalePreference(actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
authLogout()
|
authLogout()
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.settings-page {
|
.settings-page {
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1.5rem;
|
padding: 2rem 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card {
|
.info-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-btn {
|
.update-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-grid {
|
.lang-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn {
|
.lang-btn {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn.active {
|
.lang-btn.active {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #000;
|
color: #000;
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid #ff5050;
|
border: 1px solid var(--error);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover {
|
.logout-btn:hover {
|
||||||
color: rgba(255, 80, 80, 0.8);
|
color: rgba(255, 80, 80, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover i {
|
.logout-btn:hover i {
|
||||||
color: rgba(255, 80, 80, 0.8);
|
color: rgba(255, 80, 80, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn i {
|
.logout-btn i {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-state {
|
.loading-state {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user