mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
refactor: ♻️ Rewrite authentication page
This commit is contained in:
parent
1194bc4ffb
commit
c483f35b99
26 changed files with 373 additions and 797 deletions
|
|
@ -1,172 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full ring-1 ring-inset ring-white/5 pb-10 bg-dark-800">
|
||||
<Avatar :src="account?.header" :alt="`${account?.acct}'s header image'`"
|
||||
class="w-full aspect-[8/3] border-b border-white/10 bg-dark-700 !rounded-none" />
|
||||
|
||||
<div class="flex items-start justify-between px-4 py-3">
|
||||
<Avatar :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
|
||||
class="h-32 w-32 -mt-[4.5rem] z-10 shrink-0 rounded ring-2 ring-dark-800" />
|
||||
|
||||
<div class="flex gap-x-2">
|
||||
<Button theme="secondary" v-if="account && account?.id === identity?.account?.id">Edit Profile
|
||||
</Button>
|
||||
<Button theme="secondary" :loading="isLoading" @click="follow()"
|
||||
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && !relationship.requested">
|
||||
<span>Follow</span>
|
||||
</Button>
|
||||
<Button theme="secondary" :loading="isLoading" @click="unfollow()"
|
||||
v-if="account && account?.id !== identity?.account?.id && relationship && relationship.following">
|
||||
<span>Unfollow</span>
|
||||
</Button>
|
||||
<Button theme="secondary" :loading="isLoading" :disabled="true"
|
||||
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && relationship.requested">
|
||||
<span>Requested</span>
|
||||
</Button>
|
||||
|
||||
<AccountActionsDropdown v-if="account && relationship" :account="account"
|
||||
:relationship="relationship" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 px-4">
|
||||
<h2
|
||||
class="text-xl font-bold text-gray-100 tracking-tight bg-gradient-to-r from-primary-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">
|
||||
<Skeleton :enabled="skeleton" :min-width="200" :max-width="350" class="h-6">
|
||||
<span v-html="display_name"></span>
|
||||
<iconify-icon v-if="account?.locked" icon="tabler:lock" width="1.25rem" height="1.25rem"
|
||||
class="text-gray-400 cursor-pointer align-text-top ml-1"
|
||||
title="This account manually approves its followers" />
|
||||
</Skeleton>
|
||||
</h2>
|
||||
<span class="text-gray-300 block mt-2">
|
||||
<Skeleton :enabled="skeleton" :min-width="130" :max-width="250">@{{ account?.acct }}</Skeleton>
|
||||
</span>
|
||||
<div class="flex flex-row flex-wrap gap-4 mt-4" v-if="isDeveloper || visibleRoles.length > 0">
|
||||
<Badge v-for="role of visibleRoles" :key="role.id" :name="role.name" :description="role.description"
|
||||
:img="role.icon" />
|
||||
<Badge v-if="isDeveloper" name="Versia Developer" description="This user is a Versia developer."
|
||||
:verified="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 px-4">
|
||||
<Skeleton :enabled="true" v-if="skeleton" class="!h-6" :min-width="50" :max-width="100" width-unit="%"
|
||||
shape="rect" type="content">
|
||||
</Skeleton>
|
||||
<div class="prose prose-invert" v-html="note" v-else></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||
<div class="flex items-center space-x-1">
|
||||
<Skeleton :enabled="skeleton" :min-width="150" :max-width="150" shape="rect">
|
||||
<iconify-icon icon="tabler:calendar" width="1.25rem" height="1.25rem" class="text-gray-400" />
|
||||
<span class="text-gray-400">Created {{ formattedJoin }}</span>
|
||||
</Skeleton>
|
||||
</div>
|
||||
<div v-if="account?.bot" class="flex items-center space-x-1">
|
||||
<iconify-icon icon="tabler:robot" width="1.25rem" height="1.25rem" class="text-gray-400" />
|
||||
<span class="text-gray-400">Bot</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||
<div class="cursor-pointer hover:underline space-x-1">
|
||||
<Skeleton :enabled="skeleton" :min-width="100" :max-width="150" shape="rect">
|
||||
<span class="font-bold text-gray-200">{{ account?.statuses_count }}</span>
|
||||
<span class="text-gray-400">Posts</span>
|
||||
</Skeleton>
|
||||
</div>
|
||||
<div class="cursor-pointer hover:underline space-x-1">
|
||||
<Skeleton :enabled="skeleton" :min-width="100" :max-width="150" shape="rect">
|
||||
<span class="font-bold text-gray-200">{{ account?.following_count }}</span>
|
||||
<span class="text-gray-400">Following</span>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!skeleton && fields && fields.length > 0" class="mt-4 px-4 flex-col flex space-y-3">
|
||||
<div v-for="field of fields" :key="field.name ?? ''" class="flex flex-col gap-1">
|
||||
<span class="text-primary2-500 font-semibold" v-html="field.name"></span>
|
||||
<span class="text-gray-200 prose prose-invert break-all" v-html="field.value"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="skeleton" class="mt-4 px-4 flex-col space-y-3">
|
||||
<div v-for="_ of 3" class="flex flex-col gap-1">
|
||||
<Skeleton :enabled="skeleton" :min-width="10" :max-width="100" width-unit="%" shape="rect">
|
||||
</Skeleton>
|
||||
<Skeleton :enabled="skeleton" :min-width="10" :max-width="100" width-unit="%" shape="rect">
|
||||
</Skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/types";
|
||||
import Avatar from "~/components/avatars/avatar.vue";
|
||||
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
||||
import Button from "~/packages/ui/components/buttons/button.vue";
|
||||
import AccountActionsDropdown from "./AccountActionsDropdown.vue";
|
||||
import Badge from "./Badge.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
account?: Account;
|
||||
}>();
|
||||
|
||||
const account = ref(props.account);
|
||||
|
||||
watch(props, () => {
|
||||
account.value = props.account;
|
||||
});
|
||||
|
||||
const skeleton = computed(() => !account.value);
|
||||
const config = useConfig();
|
||||
const accountId = computed(() => account.value?.id ?? null);
|
||||
const { relationship, isLoading } = useRelationship(client, accountId);
|
||||
|
||||
const follow = () => {
|
||||
if (!(identity.value && account.value && relationship.value)) {
|
||||
return;
|
||||
}
|
||||
relationship.value = {
|
||||
...relationship.value,
|
||||
following: true,
|
||||
};
|
||||
};
|
||||
|
||||
const unfollow = () => {
|
||||
if (!(identity.value && account.value && relationship.value)) {
|
||||
return;
|
||||
}
|
||||
relationship.value = {
|
||||
...relationship.value,
|
||||
following: false,
|
||||
};
|
||||
};
|
||||
|
||||
const formattedJoin = computed(() =>
|
||||
Intl.DateTimeFormat("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(new Date(account.value?.created_at ?? 0)),
|
||||
);
|
||||
|
||||
const handle = computed(() => {
|
||||
if (!account.value?.acct.includes("@")) {
|
||||
return `${account.value?.acct}@${new URL(useBaseUrl().value).host}`;
|
||||
}
|
||||
return account.value?.acct;
|
||||
});
|
||||
const isDeveloper = computed(() =>
|
||||
config.DEVELOPER_HANDLES.includes(handle.value),
|
||||
);
|
||||
const visibleRoles = computed(
|
||||
() => account.value?.roles.filter((r) => r.visible) ?? [],
|
||||
);
|
||||
|
||||
const { display_name, fields, note } = useParsedAccount(
|
||||
computed(() => account.value),
|
||||
settings,
|
||||
);
|
||||
</script>
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
<template>
|
||||
<AdaptiveDropdown>
|
||||
<template #button>
|
||||
<Button theme="secondary" class="h-full">
|
||||
<iconify-icon width="unset" icon="tabler:dots" class="size-5 text-gray-200" aria-hidden="true" />
|
||||
<span class="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template #items>
|
||||
<Menu.ItemGroup>
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="copyUsername" icon="tabler:at" class="w-full">
|
||||
Copy username
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="copyId" icon="tabler:hash" class="w-full">
|
||||
Copy ID
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="copy(JSON.stringify(account, null, 4))" icon="tabler:code" class="w-full">
|
||||
Copy API
|
||||
response
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
<hr class="border-white/10 rounded" />
|
||||
<Menu.ItemGroup>
|
||||
<Menu.Item value="" v-if="isRemoteUser">
|
||||
<ButtonDropdown @click="viewOnRemote" icon="tabler:external-link" class="w-full">
|
||||
Open remote profile
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="copyUrl" icon="tabler:link" class="w-full">
|
||||
Copy URL
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
<hr class="border-white/10 rounded" v-if="identity && !isMyAccount" />
|
||||
<Menu.ItemGroup v-if="identity && !isMyAccount">
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="mute" icon="tabler:volume-off" class="w-full" v-if="!relationship.muting">
|
||||
Mute
|
||||
</ButtonDropdown>
|
||||
<ButtonDropdown @click="unmute" icon="tabler:volume-2" class="w-full" v-else>
|
||||
Unmute
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="block" icon="tabler:shield-x" class="w-full" v-if="!relationship.blocking">
|
||||
Block
|
||||
</ButtonDropdown>
|
||||
<ButtonDropdown @click="unblock" icon="tabler:shield-check" class="w-full" v-else>
|
||||
Unblock
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
<hr class="border-white/10 rounded" v-if="identity && isRemoteUser" />
|
||||
<Menu.ItemGroup v-if="identity && isRemoteUser">
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="refetch" icon="tabler:refresh" class="w-full">
|
||||
Update remote user information
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
<hr class="border-white/10 rounded" v-if="identity" />
|
||||
<Menu.ItemGroup v-if="identity">
|
||||
<Menu.Item value="">
|
||||
<ButtonDropdown @click="useEvent('account:report', account)" icon="tabler:flag" class="w-full"
|
||||
:disabled="!permissions.includes(RolePermission.ManageOwnReports)">
|
||||
Report
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
<Menu.Item value="" v-if="permissions.includes(RolePermission.ManageAccounts)">
|
||||
<ButtonDropdown icon="tabler:shield-bolt" class="w-full">
|
||||
Open moderation panel
|
||||
</ButtonDropdown>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
</template>
|
||||
</AdaptiveDropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Menu } from "@ark-ui/vue";
|
||||
import {
|
||||
type Account,
|
||||
type Relationship,
|
||||
RolePermission,
|
||||
} from "@versia/client/types";
|
||||
import ButtonDropdown from "~/components/buttons/button-dropdown.vue";
|
||||
import AdaptiveDropdown from "~/components/dropdowns/AdaptiveDropdown.vue";
|
||||
import Button from "~/packages/ui/components/buttons/button.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
account: Account;
|
||||
relationship: Relationship;
|
||||
}>();
|
||||
|
||||
const relationship = ref(props.relationship);
|
||||
|
||||
const permissions = usePermissions();
|
||||
const isMyAccount = computed(
|
||||
() => identity.value?.account.id === props.account.id,
|
||||
);
|
||||
const isRemoteUser = computed(() => props.account.acct.includes("@"));
|
||||
const { copy } = useClipboard();
|
||||
|
||||
const copyUsername = () => copy(props.account.acct);
|
||||
const copyId = () => copy(props.account.id);
|
||||
const viewOnRemote = () => window.open(props.account.url, "_blank");
|
||||
const copyUrl = () => copy(props.account.url);
|
||||
|
||||
const mute = async () => {
|
||||
const { data } = await client.value.muteAccount(props.account.id);
|
||||
relationship.value = data;
|
||||
useEvent("notification:new", {
|
||||
type: "success",
|
||||
title: "Account muted",
|
||||
description: `You will no longer see notifications from ${props.account.acct}`,
|
||||
});
|
||||
};
|
||||
const unmute = async () => {
|
||||
const { data } = await client.value.unmuteAccount(props.account.id);
|
||||
relationship.value = data;
|
||||
useEvent("notification:new", {
|
||||
type: "success",
|
||||
title: "Account unmuted",
|
||||
description: `You will now see notifications from ${props.account.acct}`,
|
||||
});
|
||||
};
|
||||
|
||||
const block = async () => {
|
||||
const { data } = await client.value.blockAccount(props.account.id);
|
||||
relationship.value = data;
|
||||
useEvent("notification:new", {
|
||||
type: "success",
|
||||
title: "Account blocked",
|
||||
description: `You will no longer see content from ${props.account.acct}`,
|
||||
});
|
||||
};
|
||||
|
||||
const unblock = async () => {
|
||||
const { data } = await client.value.unblockAccount(props.account.id);
|
||||
relationship.value = data;
|
||||
useEvent("notification:new", {
|
||||
type: "success",
|
||||
title: "Account unblocked",
|
||||
description: `You will now see content from ${props.account.acct}`,
|
||||
});
|
||||
};
|
||||
|
||||
const refetch = async () => {
|
||||
const { data } = await client.value.refetchAccount(props.account.id);
|
||||
useEvent("account:update", data);
|
||||
useEvent("notification:new", {
|
||||
type: "success",
|
||||
title: "Remote user information updated",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<template>
|
||||
<Tooltip.Root @update:open="(o) => open = o" :open="open" :open-delay="0">
|
||||
<Tooltip.Trigger><span
|
||||
class="inline-flex items-center px-2 py-1 gap-x-2 rounded text-sm font-medium bg-dark-300 text-primary2-200 ring-white/5 ring-1">
|
||||
<svg viewBox="0 0 22 22" v-if="verified" aria-label="Verified account" role="img"
|
||||
class="size-4 fill-primary-500">
|
||||
<g>
|
||||
<path
|
||||
d="M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.902-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.144 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.567s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.907.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
<img v-else-if="img" :src="img" alt="" class="size-4 rounded-sm" />
|
||||
<iconify-icon v-else-if="icon" :icon="icon" width="none" class="text-primary2-200 size-4"
|
||||
aria-hidden="true" />
|
||||
<span>{{ name }}</span>
|
||||
</span></Tooltip.Trigger>
|
||||
<Tooltip.Positioner>
|
||||
<transition enter-active-class="transition ease-in duration-100"
|
||||
enter-from-class="transform opacity-0 translate-y-full sm:translate-y-0 scale-95"
|
||||
enter-to-class="transform translate-y-0 opacity-100 scale-100"
|
||||
leave-active-class="transition ease-out duration-75" leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95">
|
||||
<Tooltip.Content v-if="open"
|
||||
class="rounded px-4 py-2 text-sm bg-dark-400 text-gray-200 ring-1 ring-white/10 shadow-xl">
|
||||
<svg viewBox="0 0 22 22" v-if="verified" aria-label="Verified account" role="img"
|
||||
class="size-4 fill-primary-500 inline mb-0.5">
|
||||
<g>
|
||||
<path
|
||||
d="M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.902-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.144 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.567s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.907.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
{{ description }}
|
||||
</Tooltip.Content>
|
||||
</transition>
|
||||
</Tooltip.Positioner>
|
||||
</Tooltip.Root>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Tooltip } from "@ark-ui/vue";
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
description?: string | null;
|
||||
icon?: string | null;
|
||||
img?: string | null;
|
||||
verified?: boolean;
|
||||
}>();
|
||||
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
<template>
|
||||
<HoverCard.Root :positioning="{
|
||||
placement: 'bottom',
|
||||
strategy: 'fixed',
|
||||
}" v-if="isEnabled.value" v-model:open="open" :open-delay="1000">
|
||||
<HoverCard.Trigger :as-child="true">
|
||||
<slot />
|
||||
</HoverCard.Trigger>
|
||||
<Teleport to="body" v-if="account">
|
||||
<Transition enter-active-class="transition duration-300 ease-in-out" enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100" leave-active-class="duration-200 ease-in-out"
|
||||
leave-from-class="opacity-100" leave-to-class="opacity-0">
|
||||
<div class="fixed bg-black/70 inset-0 z-10 pointer-events-none" v-if="open"></div>
|
||||
</Transition>
|
||||
<HoverCard.Positioner>
|
||||
<HoverCard.Content
|
||||
class="bg-dark-700 pb-4 w-96 z-20 overflow-y-auto rounded overflow-x-hidden ring-1 ring-white/20 shadow-xl max-h-[60vh] text-sm">
|
||||
<Avatar :src="account.header" :alt="`${account.acct}'s header image'`"
|
||||
class="w-full aspect-[8/3] border-b border-white/10 bg-dark-700 !rounded-none" />
|
||||
|
||||
<div class="flex items-start justify-between px-4 py-3">
|
||||
<Avatar :src="account.avatar" :alt="`${account.acct}'s avatar'`"
|
||||
class="h-32 w-32 -mt-[4.5rem] z-10 shrink-0 rounded ring-2 ring-dark-200" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2 px-4">
|
||||
<h2
|
||||
class="text-xl font-bold text-gray-100 tracking-tight bg-gradient-to-r from-primary-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">
|
||||
<span v-html="display_name"></span>
|
||||
<iconify-icon v-if="account.locked" icon="tabler:lock" width="1.25rem" height="1.25rem"
|
||||
class="text-gray-400 cursor-pointer align-text-top ml-1"
|
||||
title="This account manually approves its followers" />
|
||||
</h2>
|
||||
<span class="text-gray-300 block mt-2">
|
||||
@{{ account.acct }}
|
||||
</span>
|
||||
<div class="flex flex-row flex-wrap gap-4 mt-4" v-if="isDeveloper || visibleRoles.length > 0">
|
||||
<Badge v-for="role of visibleRoles" :key="role.id" :name="role.name"
|
||||
:description="role.description" :img="role.icon" />
|
||||
<Badge v-if="isDeveloper" name="Versia Developer"
|
||||
description="This user is a Versia developer." :verified="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 px-4">
|
||||
<div class="prose prose-invert prose-sm" v-html="note"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||
<div class="flex items-center space-x-1">
|
||||
<iconify-icon icon="tabler:calendar" width="1.25rem" height="1.25rem"
|
||||
class="text-gray-400" />
|
||||
<span class="text-gray-400">Created {{ formattedJoin }}</span>
|
||||
</div>
|
||||
<div v-if="account.bot" class="flex items-center space-x-1">
|
||||
<iconify-icon icon="tabler:robot" width="1.25rem" height="1.25rem" class="text-gray-400" />
|
||||
<span class="text-gray-400">Bot</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||
<div class="cursor-pointer hover:underline space-x-1">
|
||||
<span class="font-bold text-gray-200">{{ account.statuses_count }}</span>
|
||||
<span class="text-gray-400">Posts</span>
|
||||
</div>
|
||||
<div class="cursor-pointer hover:underline space-x-1">
|
||||
<span class="font-bold text-gray-200">{{ account.following_count }}</span>
|
||||
<span class="text-gray-400">Following</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="fields && fields.length > 0" class="mt-4 px-4 flex-col flex space-y-3">
|
||||
<div v-for="field of fields" :key="field.name ?? ''" class="flex flex-col gap-1">
|
||||
<span class="text-primary2-500 font-semibold" v-html="field.name"></span>
|
||||
<span class="text-gray-200 prose prose-invert prose-sm break-all"
|
||||
v-html="field.value"></span>
|
||||
</div>
|
||||
</div>
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Positioner>
|
||||
</Teleport>
|
||||
</HoverCard.Root>
|
||||
<slot v-else />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoverCard } from "@ark-ui/vue";
|
||||
import type { Account } from "@versia/client/types";
|
||||
import Avatar from "~/components/avatars/avatar.vue";
|
||||
import { SettingIds } from "~/settings";
|
||||
import Badge from "./Badge.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
account?: Account;
|
||||
}>();
|
||||
|
||||
const config = useConfig();
|
||||
const isEnabled = ref({ value: false }); // useSetting(SettingIds.PopupAvatarHover);
|
||||
const open = ref(false);
|
||||
|
||||
const formattedJoin = computed(() =>
|
||||
Intl.DateTimeFormat("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(new Date(props.account?.created_at ?? 0)),
|
||||
);
|
||||
|
||||
const handle = computed(() => {
|
||||
if (!props.account?.acct.includes("@")) {
|
||||
return `${props.account?.acct}@${new URL(useBaseUrl().value).host}`;
|
||||
}
|
||||
return props.account?.acct;
|
||||
});
|
||||
const isDeveloper = computed(() =>
|
||||
config.DEVELOPER_HANDLES.includes(handle.value),
|
||||
);
|
||||
const visibleRoles = computed(
|
||||
() => props.account?.roles.filter((r) => r.visible) ?? [],
|
||||
);
|
||||
|
||||
const { display_name, fields, note } = useParsedAccount(
|
||||
computed(() => props.account),
|
||||
settings,
|
||||
);
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue