Delete mutation, wip dropdown

This commit is contained in:
Eric Bailey
2024-06-19 18:49:10 -05:00
parent 25cc40841d
commit 8d0fd335e9
2 changed files with 222 additions and 139 deletions
+133 -92
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {Pressable, View} from 'react-native' import {View} from 'react-native'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
@@ -8,6 +8,7 @@ import {useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {useDeleteStarterPackMutation} from '#/state/queries/starter-packs'
import {HITSLOP_20} from 'lib/constants' import {HITSLOP_20} from 'lib/constants'
import {makeProfileLink, makeStarterPackLink} from 'lib/routes/links' import {makeProfileLink, makeStarterPackLink} from 'lib/routes/links'
import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
@@ -23,25 +24,31 @@ import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import {bulkWriteFollows} from '#/screens/Onboarding/util' import {bulkWriteFollows} from '#/screens/Onboarding/util'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {DialogControlProps, useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
import {QrCode_Stroke2_Corner0_Rounded as QrCode} from '#/components/icons/QrCode' import {QrCode_Stroke2_Corner0_Rounded as QrCode} from '#/components/icons/QrCode'
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {ListMaybePlaceholder} from '#/components/Lists' import {ListMaybePlaceholder} from '#/components/Lists'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {FeedsList} from '#/components/StarterPack/Main/FeedsList' import {FeedsList} from '#/components/StarterPack/Main/FeedsList'
import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList' import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList'
import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export function StarterPackScreen({ type StarterPackScreeProps = NativeStackScreenProps<
route, CommonNavigatorParams,
}: NativeStackScreenProps<CommonNavigatorParams, 'StarterPack'>) { 'StarterPack'
>
export function StarterPackScreen({route}: StarterPackScreeProps) {
const {_} = useLingui() const {_} = useLingui()
const {name, rkey} = route.params const {name, rkey} = route.params
@@ -83,7 +90,7 @@ export function StarterPackScreen({
items={tabs} items={tabs}
isHeaderReady={true} isHeaderReady={true}
renderHeader={() => ( renderHeader={() => (
<Header starterPack={starterPack} name={name} rkey={rkey} /> <Header starterPack={starterPack} routeParams={route.params} />
)}> )}>
{starterPack.list != null {starterPack.list != null
? ({headerHeight, scrollElRef}) => ( ? ({headerHeight, scrollElRef}) => (
@@ -117,12 +124,10 @@ export function StarterPackScreen({
function Header({ function Header({
starterPack, starterPack,
name, routeParams,
rkey,
}: { }: {
starterPack: AppBskyGraphDefs.StarterPackView starterPack: AppBskyGraphDefs.StarterPackView
name: string routeParams: StarterPackScreeProps['route']['params']
rkey: string
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
@@ -130,8 +135,6 @@ function Header({
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const qrCodeDialogControl = useDialogControl()
const reportDialogControl = useReportDialogControl()
const [isProcessing, setIsProcessing] = React.useState(false) const [isProcessing, setIsProcessing] = React.useState(false)
@@ -191,7 +194,9 @@ function Header({
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
onPress={() => navigation.navigate('StarterPackEdit', {rkey})}> onPress={() =>
navigation.navigate('StarterPackEdit', {rkey: routeParams.rkey})
}>
<ButtonText> <ButtonText>
<Trans>Edit</Trans> <Trans>Edit</Trans>
</ButtonText> </ButtonText>
@@ -210,13 +215,7 @@ function Header({
</ButtonText> </ButtonText>
</Button> </Button>
)} )}
<Dropdown <OverflowMenu routeParams={routeParams} starterPack={starterPack} />
name={name}
rkey={rkey}
starterPack={starterPack}
qrCodeDialogControl={qrCodeDialogControl}
reportDialogControl={reportDialogControl}
/>
</View> </View>
</ProfileSubpageHeader> </ProfileSubpageHeader>
{record.description || joinedAllTimeCount >= 25 ? ( {record.description || joinedAllTimeCount >= 25 ? (
@@ -244,95 +243,94 @@ function Header({
) : null} ) : null}
</View> </View>
) : null} ) : null}
<QrCodeDialog control={qrCodeDialogControl} starterPack={starterPack} />
<ReportDialog
control={reportDialogControl}
params={{
type: 'starterpack',
uri: starterPack.list!.uri,
cid: starterPack.list!.cid,
}}
/>
</> </>
) )
} }
function Dropdown({ function OverflowMenu({
name,
rkey,
starterPack, starterPack,
qrCodeDialogControl, routeParams,
reportDialogControl,
}: { }: {
name: string
rkey: string
starterPack: AppBskyGraphDefs.StarterPackView starterPack: AppBskyGraphDefs.StarterPackView
qrCodeDialogControl: DialogControlProps routeParams: StarterPackScreeProps['route']['params']
reportDialogControl: DialogControlProps
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const qrCodeDialogControl = useDialogControl()
const reportDialogControl = useReportDialogControl()
const deleteDialogControl = useDialogControl()
const navigation = useNavigation<NavigationProp>()
const {
mutateAsync: deleteStarterPack,
isPending: isDeletePending,
error: deleteError,
} = useDeleteStarterPackMutation()
const isOwn = starterPack.creator.did === currentAccount?.did const isOwn = starterPack.creator.did === currentAccount?.did
const onDeleteStarterPack = async () => {
await deleteStarterPack({
rkey: routeParams.rkey,
// TODO need to fix types
listUri: starterPack.list!.uri,
})
logEvent('starterPack:delete', {})
deleteDialogControl.close(() => {
navigation.popToTop()
})
}
return ( return (
<>
<Menu.Root> <Menu.Root>
<Menu.Trigger label={_(msg`Repost or quote post`)}> <Menu.Trigger label={_(msg`Repost or quote post`)}>
{({props, state}) => ( {({props}) => (
<> <Button
{isOwn ? (
<Pressable
style={[
a.px_lg,
a.py_sm,
a.align_center,
a.justify_center,
a.rounded_sm,
{
backgroundColor: isOwn
? t.palette.primary_500
: t.palette.contrast_25,
},
(state.hovered || state.pressed) && {
backgroundColor: isOwn
? t.palette.primary_600
: t.palette.contrast_50,
},
]}
{...props}>
<Text
style={[
a.font_bold,
{
color: isOwn
? 'white'
: t.atoms.text_contrast_medium.color,
},
]}>
<Trans>Share</Trans>
</Text>
</Pressable>
) : (
<Pressable
{...props} {...props}
testID="headerDropdownBtn"
label={_(msg`Open starter pack menu`)}
hitSlop={HITSLOP_20} hitSlop={HITSLOP_20}
style={[ variant="solid"
a.justify_center, color="secondary"
a.align_center, size="small"
a.rounded_full, shape="round">
{height: 36, width: 36}, <ButtonIcon icon={Ellipsis} />
t.atoms.bg_contrast_50, </Button>
(state.hovered || state.pressed) && [t.atoms.bg_contrast_100],
]}
testID="headerDropdownBtn">
<Ellipsis size="lg" fill={t.atoms.text_contrast_medium.color} />
</Pressable>
)}
</>
)} )}
</Menu.Trigger> </Menu.Trigger>
<Menu.Outer style={{minWidth: 170}}> <Menu.Outer style={{minWidth: 170}}>
{isOwn ? (
<>
<Menu.Item
label={_(msg`Edit starter pack`)}
testID="editStarterPackLinkBtn"
onPress={() => {
navigation.navigate('StarterPackEdit', {
rkey: routeParams.rkey,
})
}}>
<Menu.ItemText>
<Trans>Edit</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={Pencil} position="right" />
</Menu.Item>
<Menu.Item
label={_(msg`Delete starter pack`)}
testID="deleteStarterPackBtn"
onPress={() => {
deleteDialogControl.open()
}}>
<Menu.ItemText>
<Trans>Delete</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={Trash} position="right" />
</Menu.Item>
</>
) : (
<>
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Share link`)} label={_(msg`Share link`)}
@@ -342,7 +340,9 @@ function Dropdown({
starterPack: starterPack.uri, starterPack: starterPack.uri,
shareType: 'link', shareType: 'link',
}) })
shareUrl(makeStarterPackLink(name, rkey)) shareUrl(
makeStarterPackLink(routeParams.name, routeParams.rkey),
)
}}> }}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Share link</Trans> <Trans>Share link</Trans>
@@ -359,7 +359,7 @@ function Dropdown({
<Menu.ItemIcon icon={QrCode} position="right" /> <Menu.ItemIcon icon={QrCode} position="right" />
</Menu.Item> </Menu.Item>
</Menu.Group> </Menu.Group>
{!isOwn && (
<Menu.Item <Menu.Item
label={_(msg`Report starter pack`)} label={_(msg`Report starter pack`)}
onPress={reportDialogControl.open}> onPress={reportDialogControl.open}>
@@ -368,8 +368,49 @@ function Dropdown({
</Menu.ItemText> </Menu.ItemText>
<Menu.ItemIcon icon={CircleInfo} position="right" /> <Menu.ItemIcon icon={CircleInfo} position="right" />
</Menu.Item> </Menu.Item>
</>
)} )}
</Menu.Outer> </Menu.Outer>
</Menu.Root> </Menu.Root>
<QrCodeDialog control={qrCodeDialogControl} starterPack={starterPack} />
<ReportDialog
control={reportDialogControl}
params={{
type: 'starterpack',
uri: starterPack.list!.uri,
cid: starterPack.list!.cid,
}}
/>
<Prompt.Outer control={deleteDialogControl}>
<Prompt.TitleText>
<Trans>Delete starter pack?</Trans>
</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>Are you sure you want delete this starter pack?</Trans>
</Prompt.DescriptionText>
<Prompt.Actions>
<Button
variant="solid"
color="negative"
size={gtMobile ? 'small' : 'medium'}
label={_(msg`Yes, delete this starter pack`)}
onPress={onDeleteStarterPack}>
<ButtonText>
<Trans>Delete</Trans>
</ButtonText>
{isDeletePending && <ButtonIcon icon={Loader} />}
</Button>
<Prompt.Cancel />
</Prompt.Actions>
{deleteError && (
<View>
<Text>{deleteError.toString()}</Text>
</View>
)}
</Prompt.Outer>
</>
) )
} }
+42
View File
@@ -0,0 +1,42 @@
import {AtUri} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {useAgent} from '#/state/session'
import {invalidateActorStarterPacksQuery} from 'state/queries/actor-starter-packs'
import {invalidateListMembersQuery} from 'state/queries/list-members'
import {invalidateStarterPack} from 'state/queries/useStarterPackQuery'
export function useDeleteStarterPackMutation() {
const agent = useAgent()
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({listUri, rkey}: {listUri: string; rkey: string}) => {
if (!agent.session) {
throw new Error(`Requires logged in user`)
}
// TODO parallel?
await agent.app.bsky.graph.list.delete({
repo: agent.session.did,
rkey: new AtUri(listUri).rkey,
})
await agent.app.bsky.graph.starterpack.delete({
repo: agent.session.did,
rkey,
})
},
onSuccess: async (_, {listUri, rkey}) => {
await invalidateListMembersQuery({queryClient, uri: listUri})
await invalidateActorStarterPacksQuery({
queryClient,
did: agent.session!.did,
})
await invalidateStarterPack({
queryClient,
did: agent.session!.did,
rkey,
})
},
})
}