This repository has no description
semble
/
src
/
webapp
/
features
/
platforms
/
bluesky
/
components
/
blueskySemblePost
/
BlueskySemblePost.tsx
4.5 kB
148 lines
1import { AppBskyFeedDefs, AppBskyFeedPost } from '@atproto/api';
2import { getBlueskyPost } from '../../lib/dal';
3import SembleHeader from '@/features/semble/components/SembleHeader/SembleHeader';
4import { getPostUriFromUrl } from '@/lib/utils/atproto';
5import RichTextRenderer from '@/components/contentDisplay/richTextRenderer/RichTextRenderer';
6import {
7 detectUrlPlatform,
8 getDomain,
9 SupportedPlatform,
10} from '@/lib/utils/link';
11import { getFormattedDate } from '@/lib/utils/time';
12import {
13 Stack,
14 Tooltip,
15 Anchor,
16 Card,
17 Group,
18 Avatar,
19 Box,
20 Image,
21 Text,
22 Badge,
23} from '@mantine/core';
24import Link from 'next/link';
25import { FaBluesky } from 'react-icons/fa6';
26import PostEmbed from '../postEmbed/PostEmbed';
27import BlackskyLogo from '@/assets/icons/blacksky-logo.svg';
28import BlackskyLogoWhite from '@/assets/icons/blacksky-logo-white.svg';
29import { RiArrowRightUpLine } from 'react-icons/ri';
30import { UrlType } from '@semble/types';
31import { getUrlTypeIcon } from '@/lib/utils/icon';
32import { IconType } from 'react-icons/lib';
33import { getUrlMetadata } from '@/features/cards/lib/dal';
34
35interface Props {
36 url: string;
37}
38
39export default async function BlueskySemblePost(props: Props) {
40 const postUri = getPostUriFromUrl(props.url);
41 const data = await getBlueskyPost(postUri);
42 const { metadata } = await getUrlMetadata(props.url);
43 const urlTypeIcon = getUrlTypeIcon(metadata.type as UrlType);
44 const IconComponent = urlTypeIcon as IconType;
45
46 const platform = detectUrlPlatform(props.url);
47 const platformIcon =
48 platform.type === SupportedPlatform.BLUESKY_POST ? (
49 <FaBluesky fill="#0085ff" size={18} />
50 ) : (
51 <>
52 <Image
53 src={BlackskyLogo.src}
54 alt="Blacksky logo"
55 w={18}
56 h={'auto'}
57 darkHidden
58 />
59 <Image
60 src={BlackskyLogoWhite.src}
61 alt="Blacksky logo"
62 w={18}
63 h={'auto'}
64 lightHidden
65 />
66 </>
67 );
68
69 if (
70 !data.thread ||
71 !AppBskyFeedDefs.isThreadViewPost(data.thread) ||
72 AppBskyFeedDefs.isNotFoundPost(data.thread.post) ||
73 AppBskyFeedDefs.isBlockedPost(data.thread.post)
74 ) {
75 // fallback
76 return <SembleHeader url={props.url} />;
77 }
78
79 const post = data.thread.post;
80 const record = post.record as AppBskyFeedPost.Record;
81
82 return (
83 <Stack gap={'xs'}>
84 <Group gap={'xs'}>
85 <Badge size="xs" color="lime" leftSection={<IconComponent />}>
86 {metadata.type}
87 </Badge>
88 <Tooltip label={props.url}>
89 <Anchor
90 component={Link}
91 target="_blank"
92 fw={700}
93 c="blue"
94 href={props.url}
95 style={{ display: 'inline-block' }}
96 >
97 <Group gap={0} align="center" wrap="nowrap">
98 {getDomain(props.url)}
99 <RiArrowRightUpLine />
100 </Group>
101 </Anchor>
102 </Tooltip>
103 </Group>
104
105 {/* Post */}
106 <Card radius={'lg'} withBorder>
107 <Stack gap={'xs'}>
108 <Group gap="xs" justify="space-between" wrap="nowrap">
109 <Group gap={'xs'} wrap="nowrap">
110 <Avatar
111 src={post.author.avatar?.replace('avatar', 'avatar_thumbnail')}
112 alt={`${post.author.handle} social preview image`}
113 radius="xl"
114 />
115 <Stack gap={0} flex={1}>
116 <Text c="bright" lineClamp={1} fw={500} w="fit-content">
117 {post.author.displayName || post.author.handle}
118 </Text>
119 <Text c="gray" lineClamp={1} w="fit-content">
120 @{post.author.handle}
121 </Text>
122 </Stack>
123 </Group>
124 <Tooltip
125 label={`View on ${platform.type === SupportedPlatform.BLUESKY_POST ? 'Bluesky' : 'Blacksky'}`}
126 >
127 <Anchor href={props.url} target="_blank">
128 {platformIcon}
129 </Anchor>
130 </Tooltip>
131 </Group>
132 <Stack gap={'xs'} w={'100%'}>
133 <Box>
134 <RichTextRenderer
135 text={record.text}
136 textProps={{ c: 'bright' }}
137 />
138 </Box>
139 {post.embed && <PostEmbed embed={post.embed} />}
140 </Stack>
141 <Text c={'gray'} fz={'sm'} fw={500}>
142 {getFormattedDate(post.indexedAt)}
143 </Text>
144 </Stack>
145 </Card>
146 </Stack>
147 );
148}