Files
bsky-social-app/src/components/dms/AfterReportDialog.tsx
T
2026-06-03 09:28:11 -07:00

224 lines
6.3 KiB
TypeScript

import {memo, useState} from 'react'
import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
import {
useProfileBlockMutationQueue,
useProfileQuery,
} from '#/state/queries/profile'
import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as Toggle from '#/components/forms/Toggle'
import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
type ReportDialogParams = {
convoId: string
did: string
}
/**
* Dialog shown after a report is submitted, allowing the user to block the
* reporter and/or leave the conversation.
*/
export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({
control,
params,
currentScreen,
onClose,
}: {
control: Dialog.DialogControlProps
params: ReportDialogParams
currentScreen: 'list' | 'conversation'
onClose?: () => void
}): React.ReactNode {
const {t: l} = useLingui()
return (
<Dialog.Outer
control={control}
onClose={onClose}
nativeOptions={{preventExpansion: true}}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={l`Would you like to block this user and/or delete this conversation?`}
style={[web({maxWidth: 400})]}>
<DialogInner params={params} currentScreen={currentScreen} />
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
})
function DialogInner({
params,
currentScreen,
}: {
params: ReportDialogParams
currentScreen: 'list' | 'conversation'
}) {
const t = useTheme()
const {t: l} = useLingui()
const control = Dialog.useDialogContext()
const {
data: profile,
isPending,
isError,
} = useProfileQuery({
did: params.did,
})
return isPending ? (
<View style={[a.w_full, a.py_5xl, a.align_center]}>
<Loader size="lg" />
</View>
) : isError || !profile ? (
<View style={[a.w_full, a.gap_lg]}>
<View style={[a.justify_center, a.gap_sm]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>Report submitted</Trans>
</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
<Trans>Our moderation team has received your report.</Trans>
</Text>
</View>
<Button
label={l`Close`}
onPress={() => control.close()}
size={platform({native: 'small', web: 'large'})}
color="secondary">
<ButtonText>
<Trans>Close</Trans>
</ButtonText>
</Button>
</View>
) : (
<DoneStep
convoId={params.convoId}
currentScreen={currentScreen}
profile={profile}
/>
)
}
function DoneStep({
convoId,
currentScreen,
profile,
}: {
convoId: string
currentScreen: 'list' | 'conversation'
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>()
const control = Dialog.useDialogContext()
const {gtMobile} = useBreakpoints()
const t = useTheme()
const [actions, setActions] = useState<string[]>(['block', 'leave'])
const shadow = useProfileShadow(profile)
const [queueBlock] = useProfileBlockMutationQueue(shadow)
const {mutate: leaveConvo} = useLeaveConvo(convoId, {
onMutate: () => {
if (currentScreen === 'conversation') {
navigation.dispatch(
StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}),
)
}
},
onError: () => {
Toast.show(l`Could not leave chat`, {
type: 'error',
})
},
})
let btnText = l`Done`
let toastMsg: string | undefined
if (actions.includes('leave') && actions.includes('block')) {
btnText = l`Block and delete`
toastMsg = l({message: 'Conversation deleted', context: 'toast'})
} else if (actions.includes('leave')) {
btnText = l`Delete conversation`
toastMsg = l({message: 'Conversation deleted', context: 'toast'})
} else if (actions.includes('block')) {
btnText = l`Block user`
toastMsg = l({message: 'User blocked', context: 'toast'})
}
const onPressPrimaryAction = () => {
control.close(() => {
if (actions.includes('block')) {
void queueBlock()
}
if (actions.includes('leave')) {
leaveConvo()
}
if (toastMsg) {
Toast.show(toastMsg, {
type: 'success',
})
}
})
}
return (
<View style={a.gap_2xl}>
<View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
<Text style={[a.text_2xl, a.font_semi_bold]}>
<Trans>Report submitted</Trans>
</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
<Trans>Our moderation team has received your report.</Trans>
</Text>
</View>
<Toggle.Group
label={l`Block user and/or delete this conversation`}
values={actions}
onChange={setActions}>
<View style={[a.gap_md]}>
<Toggle.Item name="block" label={l`Block user`}>
<Toggle.Checkbox />
<Toggle.LabelText style={[a.text_md]}>
<Trans>Block user</Trans>
</Toggle.LabelText>
</Toggle.Item>
<Toggle.Item name="leave" label={l`Delete conversation`}>
<Toggle.Checkbox />
<Toggle.LabelText style={[a.text_md]}>
<Trans>Delete conversation</Trans>
</Toggle.LabelText>
</Toggle.Item>
</View>
</Toggle.Group>
<View style={[a.gap_sm]}>
<Button
label={btnText}
onPress={onPressPrimaryAction}
size="large"
color={actions.length > 0 ? 'negative' : 'primary'}>
<ButtonText>{btnText}</ButtonText>
</Button>
<Button
label={l`Close`}
onPress={() => control.close()}
size="large"
color="secondary">
<ButtonText>
<Trans>Close</Trans>
</ButtonText>
</Button>
</View>
</View>
)
}