feat: finish entire friends menu
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
ci/woodpecker/pr/build-and-publish Pipeline was successful

This commit is contained in:
Twig 2025-07-11 01:45:24 +02:00
parent 34976b4f50
commit 0562127e4a
No known key found for this signature in database
7 changed files with 243 additions and 5 deletions

View file

@ -0,0 +1,63 @@
<template>
<div style="text-align: left;">
<h3>Add a Friend</h3>
Enter a friend's Gorb ID to send them a friend request.
</div>
<div id="add-friend-search-bar">
<input id="add-friend-search-input" ref="inputField"
placeholder="blahaj.enjoyer?" maxlength="32" @keypress.enter="sendRequest"/> <!-- REMEMBER TO CHANGE THIS WHEN WE ADD FEDERATION-->
<Button id="friend-request-button" :callback="sendRequest" text="Send Friend Request"></Button>
</div>
</template>
<script lang="ts" setup>
import Button from '../UserInterface/Button.vue';
const inputField = ref<HTMLInputElement>();
const { addFriend } = useApi();
async function sendRequest() {
if (inputField.value) {
try {
await addFriend(inputField.value.value)
alert("Friend request sent!")
} catch {
alert("Request failed :(")
}
}
}
</script>
<style>
#add-friend-search-bar {
display: flex;
text-align: left;
margin-top: .8em;
padding: .3em .3em;
border-radius: 1em;
border: 1px solid var(--accent-color);
}
#add-friend-search-input {
border: none;
box-sizing: border-box;
margin: 0 .2em;
flex-grow: 1;
color: inherit;
background-color: unset;
font-weight: medium;
letter-spacing: .04em;
}
#add-friend-search-input:empty:before {
content: attr(placeholder);
color: gray;
}
</style>

View file

@ -5,24 +5,24 @@
<h3>Direct Messages</h3>
</div>
<VerticalSpacer />
<NuxtLink class="user-item" :href="`/me/friends`" tabindex="0">
<Icon class="user-avatar" name="lucide:user" />
<span class="user-display-name">Friends</span>
</NuxtLink>
<VerticalSpacer />
<div id="direct-message-list">
<UserEntry v-for="user of friends" :user="user" :name="user.display_name || user.username"
:href="`/me/${user.uuid}`"/>
<!-- <Channel v-for="channel of channels" :name="channel.name"
:uuid="channel.uuid" :current-uuid="(route.params.channelId as string)"
:href="`/servers/${route.params.serverId}/channels/${channel.uuid}`" /> -->
:href="`/me/${user.uuid}`"/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import VerticalSpacer from './UserInterface/VerticalSpacer.vue';
import VerticalSpacer from '~/components/UserInterface/VerticalSpacer.vue';
const { fetchFriends } = useApi();

View file

@ -0,0 +1,61 @@
<template>
<input id="search-friend-bar" placeholder="search"/>
<!-- we aren't checking for the #all hash, since this is the default and fallback one -->
<p v-if="props.variant === '#online'" style="text-align: left;">Online {{ "N/A" }}</p>
<p v-else-if="props.variant === '#pending'" style="text-align: left;">Friend Requests {{ "N/A" }}</p>
<p v-else style="text-align: left;">Friends {{ friends?.length || "N/A" }}</p>
<div id="friends-list">
<div v-if="props.variant === '#online'">
Not Implemented
</div>
<div v-else-if="props.variant === '#pending'">
Not Implemented
</div>
<div v-else>
<UserEntry v-for="user of friends" :user="user" :name="user.display_name || user.username"
:href="`/me/${user.uuid}`"/>
</div>
</div>
</template>
<script lang="ts" setup>
const { fetchFriends } = useApi();
const friends = await fetchFriends()
const props = defineProps<{
variant: string
}>();
console.log("props", props.variant)
</script>
<style>
#search-friend-bar {
text-align: left;
margin-top: .8em;
padding: .3em .5em;
width: 100%;
border-radius: 1em;
border: 1px solid var(--accent-color);
box-sizing: border-box;
color: inherit;
background-color: unset;
font-weight: medium;
letter-spacing: .04em;
}
#search-friend-bar:empty:before {
content: attr(placeholder);
color: gray;
}
</style>

View file

@ -37,6 +37,14 @@ export const useApi = () => {
return await fetchWithApi('/me/friends')
}
async function addFriend(username: string): Promise<void> {
await fetchWithApi('/me/friends', { method: "POST", body: { username } });
}
async function removeFriend(userId: string): Promise<void> {
await fetchWithApi(`/me/friends/${userId}`, { method: "DELETE" });
}
async function fetchMessages(channelId: string, options?: { amount?: number, offset?: number }): Promise<MessageResponse[] | undefined> {
return await fetchWithApi(`/channels/${channelId}/messages`, { query: { amount: options?.amount ?? 100, offset: options?.offset ?? 0 } });
}
@ -64,6 +72,8 @@ export const useApi = () => {
fetchUsers,
fetchUser,
fetchFriends,
addFriend,
removeFriend,
fetchMessages,
fetchMessage,
fetchInstanceStats,

20
pages/me/[userId].vue Normal file
View file

@ -0,0 +1,20 @@
<template>
<NuxtLayout name="client">
<DirectMessagesSidebar />
<MessageArea channel-url="channels/01970e8c-a09c-76a0-9c98-80a43364bea7"/>
</NuxtLayout>
</template>
<script lang="ts" setup>
import DirectMessagesSidebar from '~/components/Me/DirectMessagesSidebar.vue';
definePageMeta({
layout: "client"
});
</script>
<style>
</style>

82
pages/me/friends.vue Normal file
View file

@ -0,0 +1,82 @@
<template>
<NuxtLayout name="client">
<DirectMessagesSidebar />
<div id="friends-page-content">
<div id="navigation-bar">
<NuxtLink class="friends-sub-page-button" @click.prevent="updateHash('all')">All Friends</NuxtLink>
<NuxtLink class="friends-sub-page-button" @click.prevent="updateHash('online')">Online</NuxtLink>
<NuxtLink class="friends-sub-page-button" @click.prevent="updateHash('pending')">Pending</NuxtLink>
<NuxtLink class="friends-sub-page-button friend-primary-button" @click.prevent="updateHash('addfriend')">Add Friend</NuxtLink>
</div>
<div>
<AddFriend v-if="windowHash == '#addfriend'"></AddFriend>
<FriendsList v-else :variant="windowHash"></FriendsList>
</div>
</div>
</NuxtLayout>
</template>
<script lang="ts" setup>
import AddFriend from '~/components/Me/AddFriend.vue';
import DirectMessagesSidebar from '~/components/Me/DirectMessagesSidebar.vue';
import FriendsList from '~/components/Me/FriendsList.vue';
let windowHash = ref(window.location.hash)
function updateHash(newHash: string) {
window.location.hash = newHash
windowHash.value = `#${newHash}`;
}
</script>
<style>
#friends-page-content {
display: flex;
flex-direction: column;
flex-grow: 1;
margin: .75em;
}
#navigation-bar {
display: flex;
align-items: left;
text-align: left;
flex-direction: row;
gap: .5em;
}
.friends-sub-page-button {
text-decoration: none;
color: inherit;
font-weight: medium;
padding: 0.2em .5em;
flex-shrink: 0;
border-radius: 1em;
background-color: var(--accent-color);
display: inline-block;
text-align: center;
align-content: center;
cursor: pointer;
transition: background-color 300ms;
}
.friends-sub-page-button:hover {
background-color: var(--accent-highlighted-color);
}
.friend-primary-button {
background-color: var(--primary-color);
}
.friend-primary-button:hover {
background-color: var(--primary-highlighted-color);
}
</style>

View file

@ -5,6 +5,8 @@
</template>
<script lang="ts" setup>
import DirectMessagesSidebar from '~/components/Me/DirectMessagesSidebar.vue';
definePageMeta({
layout: "client"