✨ Friends
This commit is contained in:
parent
cbcb007517
commit
2d3f8a8bd7
62
src/components/friends/FriendListItem.vue
Normal file
62
src/components/friends/FriendListItem.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<v-list-item :title="otherside.nick">
|
||||
<template #subtitle>@{{ otherside.name }}</template>
|
||||
<template #prepend>
|
||||
<v-avatar
|
||||
color="grey-lighten-2"
|
||||
icon="mdi-account-circle"
|
||||
class="rounded-card me-2"
|
||||
size="small"
|
||||
:image="othersidePicture"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn
|
||||
icon="mdi-check"
|
||||
size="x-small"
|
||||
color="success"
|
||||
variant="text"
|
||||
:disabled="!canApprove"
|
||||
@click="emits('decline')"
|
||||
/>
|
||||
<v-btn
|
||||
icon="mdi-close"
|
||||
size="x-small"
|
||||
color="error"
|
||||
variant="text"
|
||||
:disabled="!canDecline"
|
||||
@click="emits('approve')"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { useUserinfo } from "@/stores/userinfo"
|
||||
import { buildRequestUrl } from "@/scripts/request"
|
||||
|
||||
const id = useUserinfo()
|
||||
|
||||
const props = defineProps<{ item: any }>()
|
||||
const emits = defineEmits(["approve", "decline"])
|
||||
|
||||
const canApprove = computed(() => {
|
||||
return props.item.status !== 1 && props.item.account_id !== id.userinfo.data?.id
|
||||
})
|
||||
const canDecline = computed(() => {
|
||||
return props.item.status !== 2
|
||||
})
|
||||
|
||||
const otherside = computed(() => {
|
||||
if (props.item.account_id === id.userinfo.data?.id) {
|
||||
return props.item.related
|
||||
} else {
|
||||
return props.item.account
|
||||
}
|
||||
})
|
||||
const othersidePicture = computed(() => otherside.value?.avatar ?
|
||||
buildRequestUrl("identity", `/api/avatar/${otherside.value?.avatar}`) :
|
||||
undefined
|
||||
)
|
||||
</script>
|
@ -6,8 +6,15 @@
|
||||
|
||||
<div class="aside-nav max-md:order-first">
|
||||
<v-card prepend-icon="mdi-cog" title="Settings">
|
||||
<v-list density="comfortable">
|
||||
<v-list density="comfortable" class="overflow-auto">
|
||||
<v-list-item title="Basis" prepend-icon="mdi-network" exact :to="{ name: 'settings' }" />
|
||||
|
||||
<v-divider class="border-[#000] my-2" />
|
||||
|
||||
<v-list-item title="Friends" prepend-icon="mdi-handshake" :to="{ name: 'settings.account.friends' }" />
|
||||
|
||||
<v-divider class="border-[#000] my-2" />
|
||||
|
||||
<v-list-item title="Personalize" prepend-icon="mdi-card-bulleted-outline" :to="{ name: 'settings.account.personalize' }" />
|
||||
<v-list-item title="Personal Page" prepend-icon="mdi-sitemap" :to="{ name: 'settings.account.personal-page' }" />
|
||||
<v-list-item title="Security" prepend-icon="mdi-security" :to="{ name: 'settings.account.security' }" />
|
||||
|
@ -5,6 +5,12 @@ export const settingRouter = [
|
||||
component: () => import("@/views/settings.vue")
|
||||
},
|
||||
|
||||
{
|
||||
path: "account/friends",
|
||||
name: "settings.account.friends",
|
||||
component: () => import("@/views/users/me/friends.vue")
|
||||
},
|
||||
|
||||
{
|
||||
path: "account/personalize",
|
||||
name: "settings.account.personalize",
|
||||
|
23
src/stores/friends.ts
Normal file
23
src/stores/friends.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { reactive, ref } from "vue"
|
||||
import { defineStore } from "pinia"
|
||||
import { checkLoggedIn, getAtk } from "@/stores/userinfo"
|
||||
import { request } from "@/scripts/request"
|
||||
|
||||
export const useFriends = defineStore("friends", () => {
|
||||
const available = ref<any[]>([])
|
||||
|
||||
async function list() {
|
||||
if (!(await checkLoggedIn())) return
|
||||
|
||||
const res = await request("identity", "/api/users/me/friends?status=1", {
|
||||
headers: { Authorization: `Bearer ${await getAtk()}` }
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
throw new Error(await res.text())
|
||||
} else {
|
||||
available.value = await res.json()
|
||||
}
|
||||
}
|
||||
|
||||
return { available, list }
|
||||
})
|
@ -4,6 +4,7 @@ import { request } from "@/scripts/request"
|
||||
import { Preferences } from "@capacitor/preferences"
|
||||
import { useRealms } from "@/stores/realms"
|
||||
import { useChannels } from "@/stores/channels"
|
||||
import { useFriends } from "@/stores/friends"
|
||||
|
||||
export interface Userinfo {
|
||||
isReady: boolean
|
||||
@ -51,7 +52,7 @@ export async function signout() {
|
||||
|
||||
export const useUserinfo = defineStore("userinfo", () => {
|
||||
const userinfoHooks = {
|
||||
after: [useRealms().list, useChannels().list, useChannels().connect]
|
||||
after: [useRealms().list, useChannels().list, useChannels().connect, useFriends().list]
|
||||
}
|
||||
|
||||
const userinfo = ref(defaultUserinfo)
|
||||
|
144
src/views/users/me/friends.vue
Normal file
144
src/views/users/me/friends.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3">
|
||||
<v-card prepend-icon="mdi-account-plus" title="Add a new friend" :loading="submitting">
|
||||
<v-form @submit.prevent="sendRequest">
|
||||
<div class="pl-4.5 pr-5.5 pt-0.5 pb-4">
|
||||
<v-text-field
|
||||
label="Username or email"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hint="Username not nickname. You need wait your friend accept your request to establish friend relationship."
|
||||
append-icon="mdi-plus"
|
||||
persistent-hint
|
||||
clearable
|
||||
v-model="username"
|
||||
@click:append="sendRequest"
|
||||
/>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card>
|
||||
|
||||
<v-card prepend-icon="mdi-account-multiple" title="Friend list" :loading="loading">
|
||||
<v-list>
|
||||
<v-list-subheader>FRIENDS ({{ friends.available.length }})</v-list-subheader>
|
||||
<friend-list-item
|
||||
v-for="item in friends.available"
|
||||
:item="item"
|
||||
@decline="upgradeFriendship(item, 2)"
|
||||
/>
|
||||
|
||||
<v-list-subheader>PENDING ({{ pending.length }})</v-list-subheader>
|
||||
<friend-list-item
|
||||
v-for="item in pending"
|
||||
:item="item"
|
||||
@approve="upgradeFriendship(item, 1)"
|
||||
@decline="upgradeFriendship(item, 2)"
|
||||
/>
|
||||
|
||||
<v-list-subheader>BLOCKED ({{ blocked.length }})</v-list-subheader>
|
||||
<friend-list-item
|
||||
v-for="item in blocked"
|
||||
:item="item"
|
||||
@approve="upgradeFriendship(item, 1)"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { checkLoggedIn, getAtk, useUserinfo } from "@/stores/userinfo"
|
||||
import { request } from "@/scripts/request"
|
||||
import { useFriends } from "@/stores/friends"
|
||||
import { useUI } from "@/stores/ui"
|
||||
import FriendListItem from "@/components/friends/FriendListItem.vue"
|
||||
|
||||
const { showSnackbar, showErrorSnackbar } = useUI()
|
||||
const id = useUserinfo()
|
||||
|
||||
const submitting = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const username = ref("")
|
||||
|
||||
const friends = useFriends()
|
||||
const pending = ref<any[]>([])
|
||||
const blocked = ref<any[]>([])
|
||||
|
||||
async function sendRequest() {
|
||||
if (submitting.value) return
|
||||
|
||||
submitting.value = true
|
||||
const res = await request("identity", "/api/users/me/friends?" + new URLSearchParams({
|
||||
related: username.value
|
||||
}), {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${await getAtk()}` }
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
showErrorSnackbar(await res.text())
|
||||
} else {
|
||||
showSnackbar("You have sent the friend invitation, go reach your friend out.")
|
||||
await Promise.all([readFriendships(), friends.list()])
|
||||
}
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
async function upgradeFriendship(item: any, status: number) {
|
||||
if (submitting.value) return
|
||||
|
||||
let otherside: any;
|
||||
if (item.account_id === id.userinfo.data?.id) {
|
||||
otherside = item.related
|
||||
} else {
|
||||
otherside = item.account
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
const res = await request("identity", `/api/users/me/friends/${otherside.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${await getAtk()}` },
|
||||
body: JSON.stringify({
|
||||
status
|
||||
})
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
showErrorSnackbar(await res.text())
|
||||
} else {
|
||||
showSnackbar("Friendship status has been updated.")
|
||||
await Promise.all([readFriendships(), friends.list()])
|
||||
}
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
async function readFriendships() {
|
||||
if (!(await checkLoggedIn())) return
|
||||
|
||||
let res: Response
|
||||
|
||||
loading.value = true
|
||||
|
||||
res = await request("identity", "/api/users/me/friends?status=0", {
|
||||
headers: { Authorization: `Bearer ${await getAtk()}` }
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
showErrorSnackbar(await res.text())
|
||||
} else {
|
||||
pending.value = await res.json()
|
||||
}
|
||||
|
||||
res = await request("identity", "/api/users/me/friends?status=2", {
|
||||
headers: { Authorization: `Bearer ${await getAtk()}` }
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
showErrorSnackbar(await res.text())
|
||||
} else {
|
||||
blocked.value = await res.json()
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
readFriendships()
|
||||
</script>
|
@ -45,7 +45,7 @@
|
||||
icon="mdi-account-circle"
|
||||
class="rounded-card"
|
||||
size="large"
|
||||
:image="accountPicture ?? ''"
|
||||
:image="accountPicture"
|
||||
/>
|
||||
<v-file-input
|
||||
clearable
|
||||
@ -64,7 +64,7 @@
|
||||
class="bg-grey-lighten-2"
|
||||
max-height="280px"
|
||||
:aspect-ratio="16 / 9"
|
||||
:src="accountBanner ?? ''"
|
||||
:src="accountBanner"
|
||||
/>
|
||||
|
||||
<v-card-text>
|
||||
@ -191,11 +191,11 @@ function loadImage(event: InputEvent, type: string) {
|
||||
|
||||
const accountPicture = computed(() => id.userinfo.data?.avatar ?
|
||||
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.avatar}`) :
|
||||
null
|
||||
undefined
|
||||
)
|
||||
const accountBanner = computed(() => id.userinfo.data?.banner ?
|
||||
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.banner}`) :
|
||||
null
|
||||
undefined
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -9,7 +9,7 @@
|
||||
color="grey-lighten-2"
|
||||
icon="mdi-account-circle"
|
||||
class="rounded-card me-2"
|
||||
:image="accountPicture ?? ''"
|
||||
:image="accountPicture"
|
||||
/>
|
||||
|
||||
<div>
|
||||
@ -100,11 +100,11 @@ const posts = ref<any[]>([])
|
||||
|
||||
const accountPicture = computed(() => metadata.value?.avatar ?
|
||||
buildRequestUrl("identity", `/api/avatar/${metadata.value?.avatar}`) :
|
||||
null
|
||||
undefined
|
||||
)
|
||||
const accountBanner = computed(() => metadata.value?.banner ?
|
||||
buildRequestUrl("identity", `/api/avatar/${metadata.value?.banner}`) :
|
||||
null
|
||||
undefined
|
||||
)
|
||||
|
||||
async function readMetadata() {
|
||||
|
Reference in New Issue
Block a user