diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/LikesDialog.tsx | 129 | ||||
-rw-r--r-- | src/components/ProfileCard.tsx | 53 |
2 files changed, 39 insertions, 143 deletions
diff --git a/src/components/LikesDialog.tsx b/src/components/LikesDialog.tsx deleted file mode 100644 index cb000b433..000000000 --- a/src/components/LikesDialog.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import {useCallback, useMemo} from 'react' -import {ActivityIndicator, FlatList, View} from 'react-native' -import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {cleanError} from '#/lib/strings/errors' -import {logger} from '#/logger' -import {useLikedByQuery} from '#/state/queries/post-liked-by' -import {useResolveUriQuery} from '#/state/queries/resolve-uri' -import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' -import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {atoms as a, useTheme} from '#/alf' -import * as Dialog from '#/components/Dialog' -import {Loader} from '#/components/Loader' -import {Text} from '#/components/Typography' - -interface LikesDialogProps { - control: Dialog.DialogOuterProps['control'] - uri: string -} - -export function LikesDialog(props: LikesDialogProps) { - return ( - <Dialog.Outer control={props.control}> - <Dialog.Handle /> - <LikesDialogInner {...props} /> - </Dialog.Outer> - ) -} - -export function LikesDialogInner({control, uri}: LikesDialogProps) { - const {_} = useLingui() - const t = useTheme() - - const { - data: resolvedUri, - error: resolveError, - isFetched: hasFetchedResolvedUri, - } = useResolveUriQuery(uri) - const { - data, - isFetching: isFetchingLikedBy, - isFetched: hasFetchedLikedBy, - isFetchingNextPage, - hasNextPage, - fetchNextPage, - isError, - error: likedByError, - } = useLikedByQuery(resolvedUri?.uri) - - const isLoading = !hasFetchedResolvedUri || !hasFetchedLikedBy - const likes = useMemo(() => { - if (data?.pages) { - return data.pages.flatMap(page => page.likes) - } - return [] - }, [data]) - - const onEndReached = useCallback(async () => { - if (isFetchingLikedBy || !hasNextPage || isError) return - try { - await fetchNextPage() - } catch (err) { - logger.error('Failed to load more likes', {message: err}) - } - }, [isFetchingLikedBy, hasNextPage, isError, fetchNextPage]) - - const renderItem = useCallback( - ({item}: {item: GetLikes.Like}) => { - return ( - <ProfileCardWithFollowBtn - key={item.actor.did} - profile={item.actor} - onPress={() => control.close()} - /> - ) - }, - [control], - ) - - return ( - <Dialog.Inner label={_(msg`Users that have liked this content or profile`)}> - <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_lg]}> - <Trans>Liked by</Trans> - </Text> - - {isLoading ? ( - <View style={{minHeight: 300}}> - <Loader size="xl" /> - </View> - ) : resolveError || likedByError || !data ? ( - <ErrorMessage message={cleanError(resolveError || likedByError)} /> - ) : likes.length === 0 ? ( - <View style={[t.atoms.bg_contrast_50, a.px_md, a.py_xl, a.rounded_md]}> - <Text style={[a.text_center]}> - <Trans> - Nobody has liked this yet. Maybe you should be the first! - </Trans> - </Text> - </View> - ) : ( - <FlatList - data={likes} - keyExtractor={item => item.actor.did} - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - ListFooterComponent={ - <ListFooterComponent isFetching={isFetchingNextPage} /> - } - /> - )} - - <Dialog.Close /> - </Dialog.Inner> - ) -} - -function ListFooterComponent({isFetching}: {isFetching: boolean}) { - if (isFetching) { - return ( - <View style={a.pt_lg}> - <ActivityIndicator /> - </View> - ) - } - return null -} diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index beb09cdc7..394ff9946 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -8,15 +8,15 @@ import { import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {getModerationCauseKey} from '#/lib/moderation' import {type LogEvents} from '#/lib/statsig/statsig' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useSession} from '#/state/session' -import {ProfileCardPills} from '#/view/com/profile/ProfileCard' import * as Toast from '#/view/com/util/Toast' -import {UserAvatar} from '#/view/com/util/UserAvatar' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import { Button, @@ -27,6 +27,7 @@ import { import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {Link as InternalLink, type LinkProps} from '#/components/Link' +import * as Pills from '#/components/Pills' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' @@ -35,13 +36,15 @@ export function Default({ profile, moderationOpts, logContext = 'ProfileCard', + testID, }: { profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' + testID?: string }) { return ( - <Link profile={profile}> + <Link testID={testID} profile={profile}> <Card profile={profile} moderationOpts={moderationOpts} @@ -60,8 +63,6 @@ export function Card({ moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' }) { - const moderation = moderateProfile(profile, moderationOpts) - return ( <Outer> <Header> @@ -74,10 +75,7 @@ export function Card({ /> </Header> - <ProfileCardPills - followedBy={Boolean(profile.viewer?.followedBy)} - moderation={moderation} - /> + <Labels profile={profile} moderationOpts={moderationOpts} /> <Description profile={profile} /> </Outer> @@ -87,7 +85,7 @@ export function Card({ export function Outer({ children, }: { - children: React.ReactElement | React.ReactElement[] + children: React.ReactNode | React.ReactNode[] }) { return <View style={[a.w_full, a.flex_1, a.gap_xs]}>{children}</View> } @@ -95,7 +93,7 @@ export function Outer({ export function Header({ children, }: { - children: React.ReactElement | React.ReactElement[] + children: React.ReactNode | React.ReactNode[] }) { return <View style={[a.flex_row, a.align_center, a.gap_sm]}>{children}</View> } @@ -137,10 +135,9 @@ export function Avatar({ const moderation = moderateProfile(profile, moderationOpts) return ( - <UserAvatar + <PreviewableUserAvatar size={40} - avatar={profile.avatar} - type={profile.associated?.labeler ? 'labeler' : 'user'} + profile={profile} moderation={moderation.ui('avatar')} /> ) @@ -415,3 +412,31 @@ export function FollowButtonInner({ </View> ) } + +export function Labels({ + profile, + moderationOpts, +}: { + profile: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts +}) { + const moderation = moderateProfile(profile, moderationOpts) + const modui = moderation.ui('profileList') + const followedBy = profile.viewer?.followedBy + + if (!followedBy && !modui.inform && !modui.alert) { + return null + } + + return ( + <Pills.Row style={[a.pt_xs]}> + {followedBy && <Pills.FollowsYou />} + {modui.alerts.map(alert => ( + <Pills.Label key={getModerationCauseKey(alert)} cause={alert} /> + ))} + {modui.informs.map(inform => ( + <Pills.Label key={getModerationCauseKey(inform)} cause={inform} /> + ))} + </Pills.Row> + ) +} |