Let starter pack owners remove opted-out users

This commit is contained in:
vineyardbovines
2026-09-02 14:57:15 -04:00
parent 7ecf1dde7b
commit fbed7d4bb8
@@ -2,18 +2,25 @@ import {forwardRef, useCallback, useImperativeHandle, useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native' import {type ListRenderItemInfo, View} from 'react-native'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky/sdk/moderation' import {type ModerationOpts} from '@bsky/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
import {cleanError} from '#/lib/strings/errors'
import {useAllListMembersQuery} from '#/state/queries/list-members' import {useAllListMembersQuery} from '#/state/queries/list-members'
import {useListMembershipRemoveMutation} from '#/state/queries/list-memberships'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {List, type ListRef} from '#/view/com/util/List' import {List, type ListRef} from '#/view/com/util/List'
import {type SectionRef} from '#/screens/Profile/Sections/types' import {type SectionRef} from '#/screens/Profile/Sections/types'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {Loader} from '#/components/Loader'
import {Default as ProfileCard} from '#/components/ProfileCard' import {Default as ProfileCard} from '#/components/ProfileCard'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
@@ -53,18 +60,17 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
const getSortedProfiles = () => { const getSortedProfiles = () => {
if (!listItems) return if (!listItems) return
if (!isOwn) return listItems
const myIndex = listItems.findIndex( return [...listItems].sort((a, b) => {
item => item.subject.did === currentAccount?.did, if (a.subjectOptedOut !== b.subjectOptedOut) {
) return a.subjectOptedOut ? 1 : -1
return myIndex !== -1 }
? [ if (isOwn) {
listItems[myIndex], if (a.subject.did === currentAccount?.did) return -1
...listItems.slice(0, myIndex), if (b.subject.did === currentAccount?.did) return 1
...listItems.slice(myIndex + 1), }
] return 0
: listItems })
} }
const onScrollToTop = useCallback(() => { const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({ scrollElRef.current?.scrollToOffset({
@@ -94,9 +100,7 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
logContext="StarterPackProfilesList" logContext="StarterPackProfilesList"
/> />
{item.subjectOptedOut ? ( {item.subjectOptedOut ? (
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}> <OptedOutControls item={item} listUri={listUri} canRemove={isOwn} />
<Trans>Opted out of this starter pack</Trans>
</Text>
) : null} ) : null}
</View> </View>
) )
@@ -144,3 +148,56 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
) )
}, },
) )
function OptedOutControls({
item,
listUri,
canRemove,
}: {
item: app.bsky.graph.defs.ListItemView
listUri: string
canRemove: boolean
}) {
const {_} = useLingui()
const t = useTheme()
const {mutate: removeMembership, isPending} = useListMembershipRemoveMutation(
{
onSuccess: () => Toast.show(_(msg`Removed from starter pack`)),
onError: error =>
Toast.show(cleanError(error), {
type: 'error',
}),
},
)
return (
<View style={[a.flex_row, a.align_center, a.justify_between, a.gap_md]}>
<Text style={[a.flex_1, a.text_sm, t.atoms.text_contrast_medium]}>
<Trans>Opted out of this starter pack</Trans>
</Text>
{canRemove ? (
<Button
label={_(msg`Remove user from starter pack`)}
size="small"
variant="solid"
color="secondary"
disabled={isPending}
onPress={() =>
removeMembership({
listUri,
actorDid: item.subject.did,
membershipUri: item.uri,
})
}>
{isPending ? (
<ButtonIcon icon={Loader} />
) : (
<ButtonText>
<Trans>Remove</Trans>
</ButtonText>
)}
</Button>
) : null}
</View>
)
}