✨ 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">
|
<div class="aside-nav max-md:order-first">
|
||||||
<v-card prepend-icon="mdi-cog" title="Settings">
|
<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-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="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="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' }" />
|
<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")
|
component: () => import("@/views/settings.vue")
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "account/friends",
|
||||||
|
name: "settings.account.friends",
|
||||||
|
component: () => import("@/views/users/me/friends.vue")
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "account/personalize",
|
path: "account/personalize",
|
||||||
name: "settings.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 { Preferences } from "@capacitor/preferences"
|
||||||
import { useRealms } from "@/stores/realms"
|
import { useRealms } from "@/stores/realms"
|
||||||
import { useChannels } from "@/stores/channels"
|
import { useChannels } from "@/stores/channels"
|
||||||
|
import { useFriends } from "@/stores/friends"
|
||||||
|
|
||||||
export interface Userinfo {
|
export interface Userinfo {
|
||||||
isReady: boolean
|
isReady: boolean
|
||||||
@ -51,7 +52,7 @@ export async function signout() {
|
|||||||
|
|
||||||
export const useUserinfo = defineStore("userinfo", () => {
|
export const useUserinfo = defineStore("userinfo", () => {
|
||||||
const userinfoHooks = {
|
const userinfoHooks = {
|
||||||
after: [useRealms().list, useChannels().list, useChannels().connect]
|
after: [useRealms().list, useChannels().list, useChannels().connect, useFriends().list]
|
||||||
}
|
}
|
||||||
|
|
||||||
const userinfo = ref(defaultUserinfo)
|
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"
|
icon="mdi-account-circle"
|
||||||
class="rounded-card"
|
class="rounded-card"
|
||||||
size="large"
|
size="large"
|
||||||
:image="accountPicture ?? ''"
|
:image="accountPicture"
|
||||||
/>
|
/>
|
||||||
<v-file-input
|
<v-file-input
|
||||||
clearable
|
clearable
|
||||||
@ -64,7 +64,7 @@
|
|||||||
class="bg-grey-lighten-2"
|
class="bg-grey-lighten-2"
|
||||||
max-height="280px"
|
max-height="280px"
|
||||||
:aspect-ratio="16 / 9"
|
:aspect-ratio="16 / 9"
|
||||||
:src="accountBanner ?? ''"
|
:src="accountBanner"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
@ -191,11 +191,11 @@ function loadImage(event: InputEvent, type: string) {
|
|||||||
|
|
||||||
const accountPicture = computed(() => id.userinfo.data?.avatar ?
|
const accountPicture = computed(() => id.userinfo.data?.avatar ?
|
||||||
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.avatar}`) :
|
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.avatar}`) :
|
||||||
null
|
undefined
|
||||||
)
|
)
|
||||||
const accountBanner = computed(() => id.userinfo.data?.banner ?
|
const accountBanner = computed(() => id.userinfo.data?.banner ?
|
||||||
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.banner}`) :
|
buildRequestUrl("identity", `/api/avatar/${id.userinfo.data?.banner}`) :
|
||||||
null
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
color="grey-lighten-2"
|
color="grey-lighten-2"
|
||||||
icon="mdi-account-circle"
|
icon="mdi-account-circle"
|
||||||
class="rounded-card me-2"
|
class="rounded-card me-2"
|
||||||
:image="accountPicture ?? ''"
|
:image="accountPicture"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -100,11 +100,11 @@ const posts = ref<any[]>([])
|
|||||||
|
|
||||||
const accountPicture = computed(() => metadata.value?.avatar ?
|
const accountPicture = computed(() => metadata.value?.avatar ?
|
||||||
buildRequestUrl("identity", `/api/avatar/${metadata.value?.avatar}`) :
|
buildRequestUrl("identity", `/api/avatar/${metadata.value?.avatar}`) :
|
||||||
null
|
undefined
|
||||||
)
|
)
|
||||||
const accountBanner = computed(() => metadata.value?.banner ?
|
const accountBanner = computed(() => metadata.value?.banner ?
|
||||||
buildRequestUrl("identity", `/api/avatar/${metadata.value?.banner}`) :
|
buildRequestUrl("identity", `/api/avatar/${metadata.value?.banner}`) :
|
||||||
null
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
async function readMetadata() {
|
async function readMetadata() {
|
||||||
|
Reference in New Issue
Block a user