added user profiles
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "frangipane-client",
|
"name": "frangipane-client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"backendVersion": "1.0.3",
|
"backendVersion": "1.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "frangipane",
|
"productName": "frangipane",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"identifier": "com.strawberries.frangipane",
|
"identifier": "com.strawberries.frangipane",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
|
|||||||
@@ -29,3 +29,14 @@ export function declineFriendRequest(senderUuid: string) {
|
|||||||
body: JSON.stringify({ sender_uuid: senderUuid }),
|
body: JSON.stringify({ sender_uuid: senderUuid }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkIsFriend(targetUuid: string) {
|
||||||
|
return apiFetch<boolean>(`/friends/check/${targetUuid}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFriend(friendUuid: string) {
|
||||||
|
return apiFetch<void>('/friends/remove', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ friend_uuid: friendUuid }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,19 @@
|
|||||||
<ul :class="{ 'is-compact': isCompact }">
|
<ul :class="{ 'is-compact': isCompact }">
|
||||||
<li v-for="(m, i) in messages" :key="i" class="message" :class="{ 'is-me': m.sender_uuid === currentUserUuid }">
|
<li v-for="(m, i) in messages" :key="i" class="message" :class="{ 'is-me': m.sender_uuid === currentUserUuid }">
|
||||||
<div class="sender-info">
|
<div class="sender-info">
|
||||||
<img :src="getAvatarUrl(m.sender_uuid)" @error="handleAvatarError" class="avatar" />
|
<img :src="getAvatarUrl(m.sender_uuid)" @error="handleAvatarError" class="avatar clickable"
|
||||||
<div class="sender">{{ m.sender }}</div>
|
@click.stop="openUserProfile(m)" />
|
||||||
|
<div class="sender clickable" @click.stop="openUserProfile(m)">
|
||||||
|
{{ m.sender }}
|
||||||
|
</div>
|
||||||
<span class="timestamp">{{ m.sent_at }}</span>
|
<span class="timestamp">{{ m.sent_at }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content">{{ m.content }}</div>
|
<div class="message-content">{{ m.content }}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<UserProfileModal v-if="selectedUser" :username="selectedUser.name" :user-uuid="selectedUser.uuid"
|
||||||
|
@close="selectedUser = null" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -17,12 +23,15 @@ import type { Message } from '../types'
|
|||||||
import { getAvatarUrl, getCompactLayoutPreference } from '../store.ts'
|
import { getAvatarUrl, getCompactLayoutPreference } from '../store.ts'
|
||||||
import defaultAvatar from '../assets/default-avatar.png'
|
import defaultAvatar from '../assets/default-avatar.png'
|
||||||
import { getAuthData } from '../store.ts';
|
import { getAuthData } from '../store.ts';
|
||||||
|
import UserProfileModal from './UserProfileModal.vue';
|
||||||
|
|
||||||
defineProps<{ messages: Message[] }>()
|
defineProps<{ messages: Message[] }>()
|
||||||
|
|
||||||
const currentUserUuid = ref<string | null>(null)
|
const currentUserUuid = ref<string | null>(null)
|
||||||
const isCompact = ref(false)
|
const isCompact = ref(false)
|
||||||
|
|
||||||
|
const selectedUser = ref<{ name: string, uuid: string } | null>(null)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const auth = await getAuthData()
|
const auth = await getAuthData()
|
||||||
if (auth.user) {
|
if (auth.user) {
|
||||||
@@ -35,6 +44,13 @@ const handleAvatarError = (event: Event) => {
|
|||||||
const img = event.target as HTMLImageElement;
|
const img = event.target as HTMLImageElement;
|
||||||
img.src = defaultAvatar;
|
img.src = defaultAvatar;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openUserProfile = (message: Message) => {
|
||||||
|
selectedUser.value = {
|
||||||
|
name: message.sender,
|
||||||
|
uuid: message.sender_uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -78,9 +94,23 @@ ul {
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender.clickable:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -102,8 +132,6 @@ ul {
|
|||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Compact layout overrides --- */
|
|
||||||
|
|
||||||
ul.is-compact .message {
|
ul.is-compact .message {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
|
|||||||
256
src/components/UserProfileModal.vue
Normal file
256
src/components/UserProfileModal.vue
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
<template>
|
||||||
|
<div class="backdrop" @click.self="emit('close')">
|
||||||
|
<div class="modal">
|
||||||
|
<!-- <h2>User Profile</h2> -->
|
||||||
|
|
||||||
|
<div class="profile-content">
|
||||||
|
<img :src="getAvatarUrl(userUuid)" @error="handleAvatarError" class="avatar-large" />
|
||||||
|
|
||||||
|
<div class="info-group">
|
||||||
|
<label>{{ $t('profile-username') }}</label>
|
||||||
|
<div class="info-value">{{ username }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-group">
|
||||||
|
<label>{{ $t('profile-userid') }}</label>
|
||||||
|
<div class="info-value uuid">{{ userUuid }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<!-- Friend Button -->
|
||||||
|
<div v-if="!isMe && !isLoading" class="friend-actions">
|
||||||
|
<button v-if="isFriend" class="btn-danger" @click="requestRemoveFriend" :disabled="isActionLoading">
|
||||||
|
{{ $t('profile-remove-friend') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-else class="btn-primary" @click="handleAddFriend" :disabled="isActionLoading || requestSent">
|
||||||
|
{{ requestSent ? $t('profile-request-sent') : $t('profile-add-friend') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" @click="emit('close')" class="secondary">
|
||||||
|
{{ $t('shared-close') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConfirmModal v-if="modalState.visible" :title="modalState.title" :message="modalState.message"
|
||||||
|
:confirm-label="modalState.confirmLabel" :confirm-button-class="modalState.isDanger ? 'btn-danger' : ''"
|
||||||
|
@yes="handleConfirmRemove" @no="closeConfirmModal" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed, reactive } from 'vue'
|
||||||
|
import { getAvatarUrl, getAuthData } from '../store.ts'
|
||||||
|
import { checkIsFriend, removeFriend, sendFriendRequest } from '../api/friends'
|
||||||
|
import defaultAvatar from '../assets/default-avatar.png'
|
||||||
|
import ConfirmModal from './ConfirmModal.vue'
|
||||||
|
import { useFluent } from 'fluent-vue'
|
||||||
|
|
||||||
|
const { $t } = useFluent()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
username: string
|
||||||
|
userUuid: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const currentUserUuid = ref<string | null>(null)
|
||||||
|
const isFriend = ref(false)
|
||||||
|
const isLoading = ref(true)
|
||||||
|
const isActionLoading = ref(false)
|
||||||
|
const requestSent = ref(false)
|
||||||
|
|
||||||
|
// Confirm Modal State
|
||||||
|
const modalState = reactive({
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
confirmLabel: '',
|
||||||
|
isDanger: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMe = computed(() => currentUserUuid.value === props.userUuid)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const auth = await getAuthData()
|
||||||
|
if (auth.user) {
|
||||||
|
currentUserUuid.value = auth.user.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check if not self
|
||||||
|
if (currentUserUuid.value && currentUserUuid.value !== props.userUuid) {
|
||||||
|
isFriend.value = await checkIsFriend(props.userUuid)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to check friend status", error)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAddFriend = async () => {
|
||||||
|
isActionLoading.value = true
|
||||||
|
try {
|
||||||
|
await sendFriendRequest(props.username)
|
||||||
|
requestSent.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send friend request", error)
|
||||||
|
} finally {
|
||||||
|
isActionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestRemoveFriend = () => {
|
||||||
|
modalState.title = $t('profile-remove-friend');
|
||||||
|
modalState.message = $t('profile-remove-friend-confirm', { user: props.username });
|
||||||
|
modalState.confirmLabel = $t('shared-delete'); // or 'shared-confirm'
|
||||||
|
modalState.isDanger = true;
|
||||||
|
modalState.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeConfirmModal = () => {
|
||||||
|
modalState.visible = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmRemove = async () => {
|
||||||
|
closeConfirmModal();
|
||||||
|
isActionLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeFriend(props.userUuid)
|
||||||
|
isFriend.value = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to remove friend", error)
|
||||||
|
} finally {
|
||||||
|
isActionLoading.value = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAvatarError = (event: Event) => {
|
||||||
|
const img = event.target as HTMLImageElement;
|
||||||
|
img.src = defaultAvatar;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-large {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-group {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--panel-accent);
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value.uuid {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--accent);
|
||||||
|
color: var(--bg);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background-color: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--error);
|
||||||
|
color: var(--error);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover:not(:disabled) {
|
||||||
|
background-color: var(--error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -52,6 +52,15 @@ chat-create-submit = Create
|
|||||||
chat-connecting = Connecting to room...
|
chat-connecting = Connecting to room...
|
||||||
chat-connecting-failed = Could not connect. Check internet connection.
|
chat-connecting-failed = Could not connect. Check internet connection.
|
||||||
|
|
||||||
|
## User profile
|
||||||
|
profile-title = User profile
|
||||||
|
profile-add-friend = Add Friend
|
||||||
|
profile-remove-friend = Remove Friend
|
||||||
|
profile-remove-friend-confirm = Are you sure you want to remove this friend?
|
||||||
|
profile-request-sent = Request sent
|
||||||
|
profile-username = Username
|
||||||
|
profile-userid = User ID
|
||||||
|
|
||||||
## Friends page
|
## Friends page
|
||||||
friends-title = Your friends
|
friends-title = Your friends
|
||||||
friends-add-title = Add Friend
|
friends-add-title = Add Friend
|
||||||
@@ -102,7 +111,7 @@ settings-error-upload-avatar-failed-upload = Failed to upload image
|
|||||||
|
|
||||||
## Warning
|
## Warning
|
||||||
warning-wrongversion-title = Wrong app version
|
warning-wrongversion-title = Wrong app version
|
||||||
warning-wrongversion-message = The backend expects version {$expectedBackendVersion} while your version of the app ({$appVersion}) supports backend version {$backendVersion}. Please update to avoid potential issues.
|
warning-wrongversion-message = The backend expects version {$backendVersion} while your version of the app ({$appVersion}) supports backend version {$expectedBackendVersion}. Please update to avoid potential issues.
|
||||||
warning-wrongversion-dismiss = I know what I'm doing
|
warning-wrongversion-dismiss = I know what I'm doing
|
||||||
|
|
||||||
## Shared
|
## Shared
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ chat-create-submit = Créer
|
|||||||
chat-connecting = Connexion au salon...
|
chat-connecting = Connexion au salon...
|
||||||
chat-connecting-failed = Impossible d'établir la connexion. Vérifiez votre internet.
|
chat-connecting-failed = Impossible d'établir la connexion. Vérifiez votre internet.
|
||||||
|
|
||||||
|
## User profile
|
||||||
|
profile-title = Profil d'utilisateur
|
||||||
|
profile-add-friend = Ajouter en ami
|
||||||
|
profile-remove-friend = Retirer l'ami
|
||||||
|
profile-remove-friend-confirm = Etes-vous sûr de vouloir retirer cet ami ?
|
||||||
|
profile-request-sent = Requête envoyée
|
||||||
|
profile-username = Nom d'utilisateur
|
||||||
|
profile-userid = ID d'utilisateur
|
||||||
|
|
||||||
## Friends page
|
## Friends page
|
||||||
friends-title = Vos amis
|
friends-title = Vos amis
|
||||||
friends-add-title = Ajouter un ami
|
friends-add-title = Ajouter un ami
|
||||||
@@ -100,7 +109,7 @@ settings-error-upload-avatar-failed-upload = Erreur d'envoi de l'image
|
|||||||
|
|
||||||
## Warning
|
## Warning
|
||||||
warning-wrongversion-title = Mauvaise version de l'application
|
warning-wrongversion-title = Mauvaise version de l'application
|
||||||
warning-wrongversion-message = Le backend attend la version {$expectedBackendVersion} alors que votre version de l'application ({$appVersion}) prend en charge la version {$backendVersion} du backend. Veuillez mettre à jour pour éviter d'éventuels problèmes.
|
warning-wrongversion-message = Le backend attend la version {$backendVersion} alors que votre version de l'application ({$appVersion}) prend en charge la version {$expectedBackendVersion} du backend. Veuillez mettre à jour pour éviter d'éventuels problèmes.
|
||||||
warning-wrongversion-dismiss = Je sais ce que je fais
|
warning-wrongversion-dismiss = Je sais ce que je fais
|
||||||
|
|
||||||
## Shared
|
## Shared
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ async function init() {
|
|||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
export const API = 'http://127.0.0.1:8080'
|
// export const API = 'http://127.0.0.1:8080'
|
||||||
// export const API = 'http://192.168.1.183:8080'
|
// export const API = 'http://192.168.1.183:8080'
|
||||||
// export const API = 'https://alatreon.org/frangipane'
|
export const API = 'https://alatreon.org/frangipane'
|
||||||
export const API_WS = 'ws://127.0.0.1:8080/ws'
|
// export const API_WS = 'ws://127.0.0.1:8080/ws'
|
||||||
// export const API_WS = 'ws://192.168.1.183:8080/ws'
|
// export const API_WS = 'ws://192.168.1.183:8080/ws'
|
||||||
// export const API_WS = 'wss://alatreon.org/frangipane/ws'
|
export const API_WS = 'wss://alatreon.org/frangipane/ws'
|
||||||
|
|||||||
@@ -15,12 +15,16 @@
|
|||||||
|
|
||||||
<div class="friends-list">
|
<div class="friends-list">
|
||||||
<h2>{{ $t('friends-list-header') }}</h2>
|
<h2>{{ $t('friends-list-header') }}</h2>
|
||||||
<ul>
|
<div class="friends">
|
||||||
<li v-for="friend in friends" :key="friend.uuid">
|
<button class="friend" v-for="friend in friends" :key="friend.uuid" @click="openProfile(friend)">
|
||||||
{{ friend.username }}
|
<img :src="getAvatarUrl(friend.uuid)" @error="handleAvatarError" class="avatar" />
|
||||||
</li>
|
<p>{{ friend.username }}</p>
|
||||||
</ul>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<UserProfileModal v-if="selectedFriend" :username="selectedFriend.username" :user-uuid="selectedFriend.uuid"
|
||||||
|
@close="selectedFriend = null" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -28,15 +32,29 @@
|
|||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { fetchFriends, sendFriendRequest } from '../api/friends'
|
import { fetchFriends, sendFriendRequest } from '../api/friends'
|
||||||
import type { Friend } from '../types'
|
import type { Friend } from '../types'
|
||||||
|
import { getAvatarUrl } from '../store'
|
||||||
|
import defaultAvatar from '../assets/default-avatar.png'
|
||||||
|
import UserProfileModal from '../components/UserProfileModal.vue'
|
||||||
|
|
||||||
const friends = ref<Friend[]>([])
|
const friends = ref<Friend[]>([])
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
const selectedFriend = ref<Friend | null>(null)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
friends.value = await fetchFriends()
|
friends.value = await fetchFriends()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const openProfile = (friend: Friend) => {
|
||||||
|
selectedFriend.value = friend
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAvatarError = (event: Event) => {
|
||||||
|
const img = event.target as HTMLImageElement;
|
||||||
|
img.src = defaultAvatar;
|
||||||
|
};
|
||||||
|
|
||||||
async function send() {
|
async function send() {
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
// errorMessage.value = 'Username is required.'
|
// errorMessage.value = 'Username is required.'
|
||||||
@@ -106,21 +124,37 @@ async function send() {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friends-list ul {
|
.friend {
|
||||||
list-style: none;
|
width: 100%;
|
||||||
padding: 0;
|
box-sizing: border-box;
|
||||||
}
|
|
||||||
|
|
||||||
.friends-list li {
|
|
||||||
background: var(--panel-light);
|
background: var(--panel-light);
|
||||||
|
color: var(--text);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 0.75rem 1rem;
|
/* padding: 0.75rem 1rem; */
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.2rem;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.friend:hover p {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .friend:hover { */
|
||||||
|
/* background-color: var(--panel-hover); */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.friends-list h2 {
|
.friends-list h2 {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -42,9 +42,9 @@
|
|||||||
"panel-hover": "#414559",
|
"panel-hover": "#414559",
|
||||||
"text": "#c6d0f5",
|
"text": "#c6d0f5",
|
||||||
"muted": "#838ba7",
|
"muted": "#838ba7",
|
||||||
"accent": "#a6d189",
|
"accent": "#8caaee",
|
||||||
"accent-rgb": "166, 209, 137",
|
"accent-rgb": "140, 170, 238",
|
||||||
"accent-hover": "#81c8be",
|
"accent-hover": "#85c1dc",
|
||||||
"accent-second": "#f2d5cf",
|
"accent-second": "#f2d5cf",
|
||||||
"border": "#414559",
|
"border": "#414559",
|
||||||
"error": "#e78284"
|
"error": "#e78284"
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
"panel-hover": "#313244",
|
"panel-hover": "#313244",
|
||||||
"text": "#cdd6f4",
|
"text": "#cdd6f4",
|
||||||
"muted": "#7f849c",
|
"muted": "#7f849c",
|
||||||
"accent": "#89b4fa",
|
"accent": "#a6e3a1",
|
||||||
"accent-rgb": "137, 180, 250",
|
"accent-rgb": "166, 227, 161",
|
||||||
"accent-hover": "#89dceb",
|
"accent-hover": "#94e2d5",
|
||||||
"accent-second": "#f5c2e7",
|
"accent-second": "#f5c2e7",
|
||||||
"border": "#313244",
|
"border": "#313244",
|
||||||
"error": "#f38ba8"
|
"error": "#f38ba8"
|
||||||
|
|||||||
Reference in New Issue
Block a user