added frontend for friendships, and fixed logic around conflicts
This commit is contained in:
24
src/api/friends.ts
Normal file
24
src/api/friends.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { apiFetch } from './client'
|
||||||
|
import type { Friend, FriendRequest } from '../types/api'
|
||||||
|
|
||||||
|
export function fetchFriends() {
|
||||||
|
return apiFetch<Friend[]>('/friends')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchFriendRequests() {
|
||||||
|
return apiFetch<FriendRequest[]>('/friends/requests')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendFriendRequest(receiverUsername: string) {
|
||||||
|
return apiFetch<void>('/friends/request', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ receiver_username: receiverUsername }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function acceptFriendRequest(senderUuid: string) {
|
||||||
|
return apiFetch<void>('/friends/accept', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ sender_uuid: senderUuid }),
|
||||||
|
})
|
||||||
|
}
|
||||||
195
src/pages/FriendListPage.vue
Normal file
195
src/pages/FriendListPage.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<div class="friends-page">
|
||||||
|
<header class="friends-header">
|
||||||
|
<h1>Your friends</h1>
|
||||||
|
|
||||||
|
<!-- Friend Request Form -->
|
||||||
|
<div class="friend-request-form">
|
||||||
|
<h3>Add Friend</h3>
|
||||||
|
<div class="input-container">
|
||||||
|
<input v-model="username" placeholder="Username" />
|
||||||
|
<button @click="send">Send Request</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="friends-requests-container">
|
||||||
|
<!-- Friends List -->
|
||||||
|
<div class="friends-list">
|
||||||
|
<h2>Your Friends</h2>
|
||||||
|
<ul>
|
||||||
|
<li v-for="friend in friends" :key="friend.uuid">
|
||||||
|
{{ friend.username }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Friend Request List -->
|
||||||
|
<div class="requests">
|
||||||
|
<h2>Friend Requests</h2>
|
||||||
|
<ul v-if="requests.length">
|
||||||
|
<li v-for="req in requests" :key="req.sender_uuid">
|
||||||
|
<span>{{ req.sender_username }}</span>
|
||||||
|
<button @click="accept(req.sender_uuid)">Accept</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p v-else>No pending requests</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { fetchFriends, fetchFriendRequests, acceptFriendRequest, sendFriendRequest } from '../api/friends'
|
||||||
|
import type { Friend, FriendRequest } from '../types/api'
|
||||||
|
|
||||||
|
const friends = ref<Friend[]>([])
|
||||||
|
const requests = ref<FriendRequest[]>([])
|
||||||
|
const username = ref('')
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
friends.value = await fetchFriends()
|
||||||
|
requests.value = await fetchFriendRequests()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function accept(senderUuid: string) {
|
||||||
|
try {
|
||||||
|
await acceptFriendRequest(senderUuid)
|
||||||
|
requests.value = requests.value.filter(r => r.sender_uuid !== senderUuid)
|
||||||
|
fetchFriends().then(f => (friends.value = f))
|
||||||
|
} catch (err) {
|
||||||
|
errorMessage.value = 'An error occurred while accepting the request.' // TODO: handle this supposedly impossible case
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function send() {
|
||||||
|
if (!username.value) {
|
||||||
|
// errorMessage.value = 'Username is required.'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendFriendRequest(username.value)
|
||||||
|
username.value = ''
|
||||||
|
errorMessage.value = ''
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-request-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-request-form input {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-requests-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-list,
|
||||||
|
.requests {
|
||||||
|
min-width: 250px;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-list ul,
|
||||||
|
.requests ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-list li,
|
||||||
|
.requests li {
|
||||||
|
background: var(--panel-light);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requests {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requests li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--panel-light);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requests li button {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requests p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-list h2,
|
||||||
|
.requests h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.friends-requests-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
<CreateRoomModal v-if="showCreate" @close="showCreate = false" @created="rooms.push($event)" />
|
<CreateRoomModal v-if="showCreate" @close="showCreate = false" @created="rooms.push($event)" />
|
||||||
|
|
||||||
|
<router-link to="/friendlist">
|
||||||
|
to friendlist
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<ul class="rooms-list">
|
<ul class="rooms-list">
|
||||||
<li v-for="room in rooms" :key="room.uuid" class="room-item">
|
<li v-for="room in rooms" :key="room.uuid" class="room-item">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { initAuth, validateToken } from '../stores/auth.ts'
|
|||||||
import LoginPage from '../pages/LoginPage.vue'
|
import LoginPage from '../pages/LoginPage.vue'
|
||||||
import RoomsPage from '../pages/RoomsPage.vue'
|
import RoomsPage from '../pages/RoomsPage.vue'
|
||||||
import ChatPage from '../pages/ChatPage.vue'
|
import ChatPage from '../pages/ChatPage.vue'
|
||||||
|
import FriendListPage from '../pages/FriendListPage.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
@@ -11,6 +12,7 @@ const router = createRouter({
|
|||||||
{ path: '/login', component: LoginPage },
|
{ path: '/login', component: LoginPage },
|
||||||
{ path: '/', component: RoomsPage },
|
{ path: '/', component: RoomsPage },
|
||||||
{ path: '/rooms/:uuid', component: ChatPage, props: true },
|
{ path: '/rooms/:uuid', component: ChatPage, props: true },
|
||||||
|
{ path: '/friendlist', component: FriendListPage }
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,3 +17,12 @@ export interface Message {
|
|||||||
sent_at: string
|
sent_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Friend {
|
||||||
|
uuid: string
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FriendRequest {
|
||||||
|
sender_uuid: string
|
||||||
|
sender_username: string
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user