mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 11:39:16 +01:00
chore: ⬆️ Upgrade to Nuxt 4
Some checks failed
Some checks failed
This commit is contained in:
parent
8debe97f63
commit
7f7cf20311
386 changed files with 2376 additions and 2332 deletions
15
app/components/profiles/address.vue
Normal file
15
app/components/profiles/address.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<Text class="font-semibold text-sm tracking-tight">
|
||||
<span class="text-accent-foreground">@{{ username }}</span>
|
||||
<span v-if="domain" class="text-muted-foreground">@{{ domain }}</span>
|
||||
</Text>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Text from "../typography/text.vue";
|
||||
|
||||
const { username, domain } = defineProps<{
|
||||
username?: string;
|
||||
domain?: string;
|
||||
}>();
|
||||
</script>
|
||||
31
app/components/profiles/avatar.vue
Normal file
31
app/components/profiles/avatar.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<Avatar :class="['rounded-md bg-secondary']">
|
||||
<AvatarFallback v-if="name">
|
||||
{{ getInitials(name) }}
|
||||
</AvatarFallback>
|
||||
<AvatarImage v-if="src" :src="src" :alt="`${name}'s avatar`" />
|
||||
</Avatar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||
|
||||
const { name } = defineProps<{
|
||||
src?: string;
|
||||
name?: string;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Gets the initials of any string, even if it's not a name.
|
||||
* If not a name, it will return the first two characters.
|
||||
* @param name
|
||||
*/
|
||||
const getInitials = (name: string): string => {
|
||||
const initials = name.match(/\b\w/g) || [];
|
||||
|
||||
const firstLetter = initials.shift() || name[0] || "";
|
||||
const secondLetter = initials.pop() || name[1] || "";
|
||||
|
||||
return `${firstLetter}${secondLetter}`.toUpperCase();
|
||||
};
|
||||
</script>
|
||||
129
app/components/profiles/profile-actions.vue
Normal file
129
app/components/profiles/profile-actions.vue
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<slot />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-56">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.username)">
|
||||
<AtSign />
|
||||
{{ m.cool_dark_tapir_belong() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(JSON.stringify(account, null, 4))">
|
||||
<Code />
|
||||
{{ m.yummy_moving_scallop_sail() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.id)">
|
||||
<Hash />
|
||||
{{ m.sunny_zany_jellyfish_pop() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem as="button" @click="copyText(url)">
|
||||
<Link />
|
||||
{{ m.ago_new_pelican_drip() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.url)">
|
||||
<Link />
|
||||
{{ m.solid_witty_zebra_walk() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="account.url">
|
||||
<ExternalLink />
|
||||
{{ m.active_trite_lark_inspire() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="isLoggedIn && !isMe" />
|
||||
<DropdownMenuGroup v-if="isLoggedIn && !isMe">
|
||||
<DropdownMenuItem as="button" @click="muteUser(account.id)">
|
||||
<VolumeX />
|
||||
{{ m.spare_wild_mole_intend() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="blockUser(account.id)">
|
||||
<Ban />
|
||||
{{ m.misty_soft_sparrow_vent() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="isRemote" />
|
||||
<DropdownMenuGroup v-if="isRemote">
|
||||
<DropdownMenuItem as="button" @click="refresh">
|
||||
<RefreshCw />
|
||||
{{ m.slow_chunky_chipmunk_hush() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="isLoggedIn && !isMe" />
|
||||
<DropdownMenuGroup v-if="isLoggedIn && !isMe">
|
||||
<DropdownMenuItem as="button" :disabled="true">
|
||||
<Flag />
|
||||
{{ m.great_few_jaguar_rise() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import {
|
||||
AtSign,
|
||||
Ban,
|
||||
Code,
|
||||
ExternalLink,
|
||||
Flag,
|
||||
Hash,
|
||||
Link,
|
||||
RefreshCw,
|
||||
VolumeX,
|
||||
} from "lucide-vue-next";
|
||||
import { toast } from "vue-sonner";
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
}>();
|
||||
|
||||
const isMe = identity.value?.account.id === account.id;
|
||||
const isLoggedIn = !!identity.value;
|
||||
|
||||
const { copy } = useClipboard();
|
||||
const copyText = (text: string) => {
|
||||
copy(text);
|
||||
toast.success(m.flat_nice_worm_dream());
|
||||
};
|
||||
|
||||
const url = wrapUrl(`/@${account.acct}`);
|
||||
const isRemote = account.acct.includes("@");
|
||||
|
||||
const muteUser = async (userId: string) => {
|
||||
const id = toast.loading(m.ornate_tidy_coyote_grow());
|
||||
await client.value.muteAccount(userId);
|
||||
toast.dismiss(id);
|
||||
|
||||
toast.success("User muted");
|
||||
};
|
||||
|
||||
const blockUser = async (userId: string) => {
|
||||
const id = toast.loading(m.empty_smug_raven_bloom());
|
||||
await client.value.blockAccount(userId);
|
||||
toast.dismiss(id);
|
||||
|
||||
toast.success("User blocked");
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
const id = toast.loading(m.real_every_macaw_wish());
|
||||
await client.value.refetchAccount(account.id);
|
||||
toast.dismiss(id);
|
||||
|
||||
toast.success(m.many_cool_fox_love());
|
||||
};
|
||||
</script>
|
||||
32
app/components/profiles/profile-badge.vue
Normal file
32
app/components/profiles/profile-badge.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<Tooltip>
|
||||
<TooltipTrigger :as-child="true">
|
||||
<Badge variant="default" class="gap-1">
|
||||
<BadgeCheck v-if="verified" />
|
||||
<img v-else-if="icon" :src="icon" alt="" class="size-4 rounded" />
|
||||
{{ name }}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent v-if="description">
|
||||
<Text>{{ description }}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BadgeCheck } from "lucide-vue-next";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "~/components/ui/tooltip";
|
||||
import Text from "../typography/text.vue";
|
||||
|
||||
defineProps<{
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
verified?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
48
app/components/profiles/profile-badges.vue
Normal file
48
app/components/profiles/profile-badges.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Row class="gap-2" wrap
|
||||
v-if="isDeveloper || account.bot || roles.length > 0"
|
||||
>
|
||||
<ProfileBadge
|
||||
v-if="isDeveloper"
|
||||
:name="m.nice_bad_grizzly_coax()"
|
||||
:description="m.honest_jolly_shell_blend()"
|
||||
:verified="true"
|
||||
/>
|
||||
<ProfileBadge
|
||||
v-if="account.bot"
|
||||
:name="m.merry_red_shrimp_bump()"
|
||||
:description="m.sweet_mad_jannes_create()"
|
||||
/>
|
||||
<ProfileBadge
|
||||
v-for="role in roles"
|
||||
:key="role.id"
|
||||
:name="role.name"
|
||||
:description="role.description"
|
||||
:icon="role.icon"
|
||||
/>
|
||||
</Row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
import Row from "../typography/layout/row.vue";
|
||||
import ProfileBadge from "./profile-badge.vue";
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
}>();
|
||||
|
||||
const config = useConfig();
|
||||
const roles = account.roles.filter((r) => r.visible);
|
||||
// Get user handle in username@instance format
|
||||
const handle = account.acct.includes("@")
|
||||
? account.acct
|
||||
: `${account.acct}@${
|
||||
identity.value?.instance.domain ?? window.location.host
|
||||
}`;
|
||||
const isDeveloper = config.DEVELOPER_HANDLES.includes(handle);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
21
app/components/profiles/profile-fields.vue
Normal file
21
app/components/profiles/profile-fields.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<Col class="gap-y-4">
|
||||
<Col v-for="field in fields" :key="field.name" class="gap-1 break-words">
|
||||
<HeadingSmall v-render-emojis="emojis">{{ field.name }}</HeadingSmall>
|
||||
<Html v-html="field.value" v-render-emojis="emojis" />
|
||||
</Col>
|
||||
</Col>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { CustomEmoji, Field } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import HeadingSmall from "~/components/typography/headings/small.vue";
|
||||
import Html from "../typography/html.vue";
|
||||
import Col from "../typography/layout/col.vue";
|
||||
|
||||
defineProps<{
|
||||
fields: z.infer<typeof Field>[];
|
||||
emojis: z.infer<typeof CustomEmoji>[];
|
||||
}>();
|
||||
</script>
|
||||
36
app/components/profiles/profile-header.vue
Normal file
36
app/components/profiles/profile-header.vue
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<CardHeader class="relative w-full">
|
||||
<div class="bg-muted rounded overflow-hidden h-48 md:h-72 w-full">
|
||||
<img
|
||||
v-if="header"
|
||||
:src="header"
|
||||
alt=""
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
<!-- Shadow overlay at the bottom -->
|
||||
<div
|
||||
class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 translate-y-1/3 left-4 flex flex-row items-start gap-2"
|
||||
>
|
||||
<Avatar
|
||||
class="size-32 border"
|
||||
:src="avatar"
|
||||
:name="displayName"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CardHeader } from "~/components/ui/card";
|
||||
import Avatar from "./avatar.vue";
|
||||
|
||||
defineProps<{
|
||||
header: string;
|
||||
avatar: string;
|
||||
displayName: string;
|
||||
}>();
|
||||
</script>
|
||||
80
app/components/profiles/profile-relationship-actions.vue
Normal file
80
app/components/profiles/profile-relationship-actions.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<Button variant="secondary" :disabled="isLoading || relationship?.requested" v-if="!isMe && identity"
|
||||
@click="relationship?.following ? unfollow() : follow()">
|
||||
<Loader v-if="isLoading" class="animate-spin" />
|
||||
<span v-else>
|
||||
{{
|
||||
relationship?.following
|
||||
? m.brief_upper_otter_cuddle()
|
||||
: relationship?.requested
|
||||
? m.weak_bright_larva_grasp()
|
||||
: m.lazy_major_loris_grasp()
|
||||
}}
|
||||
</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import { Loader } from "lucide-vue-next";
|
||||
import { toast } from "vue-sonner";
|
||||
import type { z } from "zod";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
import { confirmModalService } from "../modals/composable";
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
}>();
|
||||
|
||||
const { relationship, isLoading } = useRelationship(client, account.id);
|
||||
const isMe = identity.value?.account.id === account.id;
|
||||
|
||||
const follow = async () => {
|
||||
if (preferences.confirm_actions.value.includes("follow")) {
|
||||
const confirmation = await confirmModalService.confirm({
|
||||
title: m.many_fair_capybara_imagine(),
|
||||
message: m.mellow_yummy_jannes_cuddle({
|
||||
acct: `@${account.acct}`,
|
||||
}),
|
||||
confirmText: m.cuddly_even_tern_loop(),
|
||||
cancelText: m.soft_bold_ant_attend(),
|
||||
});
|
||||
|
||||
if (!confirmation.confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const id = toast.loading(m.quick_basic_peacock_bubble());
|
||||
const { data } = await client.value.followAccount(account.id);
|
||||
toast.dismiss(id);
|
||||
|
||||
relationship.value = data;
|
||||
toast.success(m.awake_quick_cuckoo_smile());
|
||||
};
|
||||
|
||||
const unfollow = async () => {
|
||||
if (preferences.confirm_actions.value.includes("follow")) {
|
||||
const confirmation = await confirmModalService.confirm({
|
||||
title: m.funny_aloof_swan_loop(),
|
||||
message: m.white_best_dolphin_catch({
|
||||
acct: `@${account.acct}`,
|
||||
}),
|
||||
confirmText: m.cute_polite_oryx_blend(),
|
||||
cancelText: m.soft_bold_ant_attend(),
|
||||
});
|
||||
|
||||
if (!confirmation.confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const id = toast.loading(m.big_safe_guppy_mix());
|
||||
const { data } = await client.value.unfollowAccount(account.id);
|
||||
toast.dismiss(id);
|
||||
|
||||
relationship.value = data;
|
||||
toast.success(m.misty_level_stingray_expand());
|
||||
};
|
||||
</script>
|
||||
30
app/components/profiles/profile-stats.vue
Normal file
30
app/components/profiles/profile-stats.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<Row class="gap-2 w-full justify-around">
|
||||
<Col centered>
|
||||
<Bold>{{ noteCount }}</Bold>
|
||||
<Small muted>{{ m.real_gray_stork_seek() }}</Small>
|
||||
</Col>
|
||||
<Col centered>
|
||||
<Bold>{{ followerCount }}</Bold>
|
||||
<Small muted>{{ m.teal_helpful_parakeet_hike() }}</Small>
|
||||
</Col>
|
||||
<Col centered>
|
||||
<Bold>{{ followingCount }}</Bold>
|
||||
<Small muted>{{ m.aloof_royal_samuel_startle() }}</Small>
|
||||
</Col>
|
||||
</Row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
import Bold from "../typography/bold.vue";
|
||||
import Col from "../typography/layout/col.vue";
|
||||
import Row from "../typography/layout/row.vue";
|
||||
import Small from "../typography/small.vue";
|
||||
|
||||
const { noteCount, followerCount, followingCount } = defineProps<{
|
||||
noteCount: number;
|
||||
followerCount: number;
|
||||
followingCount: number;
|
||||
}>();
|
||||
</script>
|
||||
65
app/components/profiles/profile.vue
Normal file
65
app/components/profiles/profile.vue
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<Card class="gap-4">
|
||||
<ProfileHeader :header="account.header" :avatar="account.avatar" :display-name="account.display_name" />
|
||||
<Row class="justify-end gap-2">
|
||||
<ProfileRelationshipActions :account="account" />
|
||||
<ProfileActions :account="account">
|
||||
<Button variant="secondary" size="icon">
|
||||
<Ellipsis />
|
||||
</Button>
|
||||
</ProfileActions>
|
||||
</Row>
|
||||
<Col class="justify-center">
|
||||
<Text class="font-bold" v-render-emojis="account.emojis">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="username" :domain="domain" />
|
||||
</Col>
|
||||
<ProfileBadges :account="account" />
|
||||
<Html v-html="account.note" v-render-emojis="account.emojis" />
|
||||
<Separator />
|
||||
<ProfileFields v-if="account.fields.length > 0" :fields="account.fields" :emojis="account.emojis" />
|
||||
<Separator v-if="account.fields.length > 0" />
|
||||
<Row>
|
||||
<HeadingSmall class="flex items-center gap-1">
|
||||
<CalendarDays class="size-4" /> {{ formattedCreationDate }}
|
||||
</HeadingSmall>
|
||||
</Row>
|
||||
<Separator />
|
||||
<ProfileStats :follower-count="account.followers_count" :following-count="account.following_count"
|
||||
:note-count="account.statuses_count" />
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import { CalendarDays, Ellipsis } from "lucide-vue-next";
|
||||
import type { z } from "zod";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card } from "~/components/ui/card";
|
||||
import { Separator } from "~/components/ui/separator";
|
||||
import { getLocale } from "~~/paraglide/runtime";
|
||||
import HeadingSmall from "../typography/headings/small.vue";
|
||||
import Html from "../typography/html.vue";
|
||||
import Col from "../typography/layout/col.vue";
|
||||
import Row from "../typography/layout/row.vue";
|
||||
import Text from "../typography/text.vue";
|
||||
import Address from "./address.vue";
|
||||
import ProfileActions from "./profile-actions.vue";
|
||||
import ProfileBadges from "./profile-badges.vue";
|
||||
import ProfileFields from "./profile-fields.vue";
|
||||
import ProfileHeader from "./profile-header.vue";
|
||||
import ProfileRelationshipActions from "./profile-relationship-actions.vue";
|
||||
import ProfileStats from "./profile-stats.vue";
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
}>();
|
||||
|
||||
const [username, domain] = account.acct.split("@");
|
||||
|
||||
const formattedCreationDate = new Intl.DateTimeFormat(getLocale(), {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
}).format(new Date(account.created_at || 0));
|
||||
</script>
|
||||
60
app/components/profiles/small-card.vue
Normal file
60
app/components/profiles/small-card.vue
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="relative">
|
||||
<div class="bg-muted rounded overflow-hidden h-32 w-full">
|
||||
<img
|
||||
:src="account.header"
|
||||
alt=""
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
<!-- Shadow overlay at the bottom -->
|
||||
<div
|
||||
class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 left-1/2 translate-y-1/3 -translate-x-1/2 flex flex-row items-start gap-2"
|
||||
>
|
||||
<Avatar
|
||||
size="base"
|
||||
class="border"
|
||||
:src="account.avatar"
|
||||
:name="account.display_name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-center mt-8">
|
||||
<Text class="font-bold" v-render-emojis="account.emojis">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="username" :domain="domain" />
|
||||
</div>
|
||||
<Html
|
||||
v-html="account.note"
|
||||
v-render-emojis="account.emojis"
|
||||
class="mt-4 max-h-72 overflow-y-auto"
|
||||
/>
|
||||
<Separator v-if="account.fields.length > 0" class="mt-4" />
|
||||
<ProfileFields
|
||||
v-if="account.fields.length > 0"
|
||||
:fields="account.fields"
|
||||
:emojis="account.emojis"
|
||||
class="mt-4 max-h-48 overflow-y-auto"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { Separator } from "~/components/ui/separator";
|
||||
import Html from "../typography/html.vue";
|
||||
import Text from "../typography/text.vue";
|
||||
import Address from "./address.vue";
|
||||
import Avatar from "./avatar.vue";
|
||||
import ProfileFields from "./profile-fields.vue";
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
}>();
|
||||
|
||||
const [username, domain] = account.acct.split("@");
|
||||
</script>
|
||||
31
app/components/profiles/tiny-card.vue
Normal file
31
app/components/profiles/tiny-card.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<Card
|
||||
class="flex-row gap-2 p-2 truncate items-center"
|
||||
:class="naked ? 'p-0 bg-transparent ring-0 border-none shadow-none' : ''"
|
||||
>
|
||||
<Avatar :src="account.avatar" :name="account.display_name" class="size-10" />
|
||||
<CardContent class="leading-tight">
|
||||
<Text class="font-semibold" v-render-emojis="account.emojis">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="account.username" :domain="domain" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import Text from "../typography/text.vue";
|
||||
import Address from "./address.vue";
|
||||
import Avatar from "./avatar.vue";
|
||||
|
||||
const { account, domain, naked } = defineProps<{
|
||||
account: z.infer<typeof Account>;
|
||||
domain: string;
|
||||
naked?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue