added errors and loading states to friends page

This commit is contained in:
2026-01-22 09:48:54 +01:00
parent 6c66b1cd7d
commit b0676d3834
4 changed files with 148 additions and 90 deletions

View File

@@ -335,7 +335,7 @@ async function onSend(content: string) {
color: var(--muted);
font-size: 1.1rem;
z-index: 10;
pointer-events: none;
/* pointer-events: none; */
text-align: center;
}
@@ -424,7 +424,7 @@ async function onSend(content: string) {
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
pointer-events: none;
/* pointer-events: none; */
text-align: center;
width: 100%;
}

View File

@@ -67,6 +67,8 @@ friends-add-title = Add Friend
friends-send-request = Send Request
friends-list-header = Your Friends
friends-error-required = Username is required.
friends-connectiong = Loading friends...
friends-list-empty = No friends found
## Notifications page
notifications-title = Notifications
@@ -82,6 +84,8 @@ notifications-error-friend-accept = An error occurred while accepting the reques
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.
notifications-connectiong = Loading notifications...
notifications-empty = No notifications found
## Settings page
settings-loading = Loading settings...

View File

@@ -67,6 +67,8 @@ friends-add-title = Ajouter un ami
friends-send-request = Envoyer
friends-list-header = Vos Amis
friends-error-required = Le nom d'utilisateur est requis.
friends-connectiong = Chargement des amis...
friends-list-empty = Pas d'amis trouvés
## Notifications page
notifications-title = Notifications
@@ -82,6 +84,8 @@ notifications-error-friend-accept = Erreur lors de l'acceptation de la demande.
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.
notifications-connectiong = Chargement des notifications...
notifications-empty = Pas de notifications trouvées
## Settings page
settings-loading = Chargement des paramètres...

View File

@@ -1,31 +1,46 @@
<template>
<div class="friends-page">
<header class="friends-header">
<h1>{{ $t('friends-title') }}</h1>
<div class="friends-page">
<header class="friends-header">
<h1>{{ $t('friends-title') }}</h1>
<form class="friend-request-form" @submit.prevent="send">
<h3>{{ $t('friends-add-title') }}</h3>
<div class="input-container">
<input v-model="username" :placeholder="$t('auth-username')" autofocus />
<button type="submit">{{ $t('friends-send-request') }}</button>
<form class="friend-request-form" @submit.prevent="send">
<h3>{{ $t('friends-add-title') }}</h3>
<div class="input-container">
<input v-model="username" :placeholder="$t('auth-username')" autofocus />
<button type="submit">{{ $t('friends-send-request') }}</button>
</div>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</form>
</header>
<div class="friends-list">
<h2>{{ $t('friends-list-header') }}</h2>
<!-- Loading State -->
<div class="wait-container" v-if="isLoading">
<p class="wait-msg">{{ $t('friends-connecting') }}</p>
</div>
<!-- Empty / Error / Retry State -->
<div class="wait-container" v-else-if="!friends || friends.length === 0">
<p class="wait-msg">{{ $t('friends-list-empty') || 'No friends yet' }}</p>
<button class="retry-btn" @click="loadFriends()">
<i class="fa-solid fa-rotate-right"></i>
</button>
</div>
<!-- Content State -->
<div class="friends" v-else>
<button class="friend" v-for="friend in friends" :key="friend.uuid" @click="openProfile(friend)">
<img :src="getAvatarUrl(friend.uuid)" @error="handleAvatarError" class="avatar" />
<p>{{ friend.username }}</p>
</button>
</div>
</div>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</form>
</header>
<div class="friends-list">
<h2>{{ $t('friends-list-header') }}</h2>
<div class="friends">
<button class="friend" v-for="friend in friends" :key="friend.uuid" @click="openProfile(friend)">
<img :src="getAvatarUrl(friend.uuid)" @error="handleAvatarError" class="avatar" />
<p>{{ friend.username }}</p>
</button>
</div>
<UserProfileModal v-if="selectedFriend" :username="selectedFriend.username" :user-uuid="selectedFriend.uuid"
@close="selectedFriend = null" />
</div>
<UserProfileModal v-if="selectedFriend" :username="selectedFriend.username" :user-uuid="selectedFriend.uuid"
@close="selectedFriend = null" />
</div>
</template>
<script setup lang="ts">
@@ -39,122 +54,157 @@ import UserProfileModal from '../components/UserProfileModal.vue'
const friends = ref<Friend[]>([])
const username = ref('')
const errorMessage = ref('')
const isLoading = ref(true)
const selectedFriend = ref<Friend | null>(null)
async function loadFriends() {
isLoading.value = true
try {
friends.value = await fetchFriends()
} catch (err) {
console.error("Failed to fetch friends", err)
} finally {
isLoading.value = false
}
}
onMounted(async () => {
friends.value = await fetchFriends()
await loadFriends()
})
const openProfile = (friend: Friend) => {
selectedFriend.value = friend
selectedFriend.value = friend
}
const handleAvatarError = (event: Event) => {
const img = event.target as HTMLImageElement;
img.src = defaultAvatar;
const img = event.target as HTMLImageElement;
img.src = defaultAvatar;
};
async function send() {
if (!username.value) {
// errorMessage.value = 'Username is required.'
return
}
if (!username.value) return
try {
await sendFriendRequest(username.value)
username.value = ''
errorMessage.value = ''
friends.value = await fetchFriends()
} catch (err: any) {
errorMessage.value = err
}
try {
await sendFriendRequest(username.value)
username.value = ''
errorMessage.value = ''
await loadFriends()
} catch (err: any) {
errorMessage.value = err
}
}
</script>
<style scoped>
.friends-page {
max-width: 720px;
margin: 0 auto;
padding: 2rem 1.5rem;
display: flex;
flex-direction: column;
gap: 2rem;
max-width: 720px;
margin: 0 auto;
padding: 2rem 1.5rem;
display: flex;
flex-direction: column;
gap: 2rem;
}
.friends-header {
display: flex;
flex-direction: column;
gap: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.friend-request-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.input-container {
display: flex;
gap: 0.5rem;
display: flex;
gap: 0.5rem;
}
.friend-request-form input {
padding: 0.5rem;
border-radius: var(--radius);
border: 1px solid var(--border);
flex-grow: 1;
padding: 0.5rem;
border-radius: var(--radius);
border: 1px solid var(--border);
flex-grow: 1;
}
.friend-request-form button {
padding: 0.5rem 1rem;
border-radius: var(--radius);
padding: 0.5rem 1rem;
border-radius: var(--radius);
}
.error-message {
color: var(--error);
font-size: 0.9rem;
color: var(--error);
font-size: 0.9rem;
}
.friends-list {
min-width: 250px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
position: relative;
min-width: 250px;
min-height: 200px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
}
.wait-container {
display: flex;
flex-direction: column;
gap: 1.2rem;
align-items: center;
justify-content: center;
padding: 2rem 0;
width: 100%;
}
.wait-msg {
color: var(--muted);
font-size: 1.1rem;
text-align: center;
}
.retry-btn {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
padding: 0.5rem 1rem;
border-radius: var(--radius);
cursor: pointer;
}
.retry-btn:hover {
background: var(--panel-hover);
}
.friend {
width: 100%;
box-sizing: border-box;
background: var(--panel-light);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius);
/* padding: 0.75rem 1rem; */
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
align-items: center;
gap: 1.2rem;
margin: 0 0 0.5rem 0;
width: 100%;
box-sizing: border-box;
background: var(--panel-light);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
align-items: center;
gap: 1.2rem;
cursor: pointer;
}
.friend:hover p {
text-decoration: underline;
text-decoration: underline;
}
/* .friend:hover { */
/* background-color: var(--panel-hover); */
/* } */
.friends-list h2 {
font-size: 1.25rem;
margin-bottom: 1rem;
font-size: 1.25rem;
margin-bottom: 1rem;
}
.avatar {
height: 42px;
width: 42px;
height: 42px;
width: 42px;
}
</style>