diff --git a/src/components/dms/BlockOrDeleteDialog.tsx b/src/components/dms/BlockOrDeleteDialog.tsx new file mode 100644 index 0000000000..96120ab2d6 --- /dev/null +++ b/src/components/dms/BlockOrDeleteDialog.tsx @@ -0,0 +1,187 @@ +import {memo, useState} from 'react' +import {View} from 'react-native' +import {type AppBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {StackActions, useNavigation} from '@react-navigation/native' +import type React from 'react' + +import {type NavigationProp} from '#/lib/routes/types' +import {isNative} from '#/platform/detection' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' +import { + useProfileBlockMutationQueue, + useProfileQuery, +} from '#/state/queries/profile' +import * as Toast from '#/view/com/util/Toast' +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 {Text} from '#/components/Typography' + +type ReportDialogParams = { + convoId: string + message: ChatBskyConvoDefs.MessageView +} + +export const BlockOrDeleteDialog = memo(function BlockOrDeleteDialogInner({ + control, + params, + currentScreen, +}: { + control: Dialog.DialogControlProps + params: ReportDialogParams + currentScreen: 'list' | 'conversation' +}): React.ReactNode { + const {_} = useLingui() + return ( + + + + + + + + ) +}) + +function DialogInner({ + params, + currentScreen, +}: { + params: ReportDialogParams + currentScreen: 'list' | 'conversation' +}) { + const {data: profile} = useProfileQuery({ + did: params.message.sender.did, + }) + + return profile ? ( + + ) : ( + + + + ) +} + +function DoneStep({ + convoId, + currentScreen, + profile, +}: { + convoId: string + currentScreen: 'list' | 'conversation' + profile: AppBskyActorDefs.ProfileViewDetailed +}) { + const {_} = useLingui() + const navigation = useNavigation() + const control = Dialog.useDialogContext() + const {gtMobile} = useBreakpoints() + const t = useTheme() + const [actions, setActions] = useState(['block', 'leave']) + const shadow = useProfileShadow(profile) + const [queueBlock] = useProfileBlockMutationQueue(shadow) + + const {mutate: leaveConvo} = useLeaveConvo(convoId, { + onMutate: () => { + if (currentScreen === 'conversation') { + navigation.dispatch( + StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}), + ) + } + }, + onError: () => { + Toast.show(_(msg`Could not leave chat`), 'xmark') + }, + }) + + let btnText = _(msg`Done`) + let toastMsg: string | undefined + if (actions.includes('leave') && actions.includes('block')) { + btnText = _(msg`Block and Delete`) + toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'})) + } else if (actions.includes('leave')) { + btnText = _(msg`Delete Conversation`) + toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'})) + } else if (actions.includes('block')) { + btnText = _(msg`Block User`) + toastMsg = _(msg({message: 'User blocked', context: 'toast'})) + } + + const onPressPrimaryAction = () => { + control.close(() => { + if (actions.includes('block')) { + queueBlock() + } + if (actions.includes('leave')) { + leaveConvo() + } + if (toastMsg) { + Toast.show(toastMsg, 'check') + } + }) + } + + return ( + + + + Report submitted + + + Our moderation team has received your report. + + + + + + + + Block user + + + + + + Delete conversation + + + + + + + + + + + ) +} diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 152b686f86..c952c7ae22 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -18,6 +18,7 @@ import {type ViewStyleProp} from '#/alf' import {atoms as a} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' +import {BlockOrDeleteDialog} from '#/components/dms/BlockOrDeleteDialog' import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' @@ -65,6 +66,7 @@ let ConvoMenu = ({ const leaveConvoControl = Prompt.usePromptControl() const reportControl = Prompt.usePromptControl() const blockedByListControl = Prompt.usePromptControl() + const blockOrDeleteControl = Prompt.usePromptControl() const {listBlocks} = blockInfo @@ -113,14 +115,27 @@ let ConvoMenu = ({ currentScreen={currentScreen} /> {latestReportableMessage ? ( - + <> + { + blockOrDeleteControl.open() + }} + /> + + ) : ( )} diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index cbf8798a89..5f81c383ab 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -15,6 +15,7 @@ import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import * as ContextMenu from '#/components/ContextMenu' import {type TriggerProps} from '#/components/ContextMenu/types' +import {BlockOrDeleteDialog} from '#/components/dms/BlockOrDeleteDialog' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' @@ -37,6 +38,7 @@ export let MessageContextMenu = ({ const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() + const blockOrDeleteControl = usePromptControl() const langPrefs = useLanguagePrefs() const translate = useTranslate() @@ -178,6 +180,17 @@ export let MessageContextMenu = ({ convoId: convo.convo.id, message, }} + onClose={() => { + blockOrDeleteControl.open() + }} + /> + { logger.metric('reportDialog:close', {}, {statsig: false}) - }, []) + props.onClose?.() + }, [props]) return ( diff --git a/src/components/moderation/ReportDialog/types.ts b/src/components/moderation/ReportDialog/types.ts index 3691b6cd65..c5d0f1a7eb 100644 --- a/src/components/moderation/ReportDialog/types.ts +++ b/src/components/moderation/ReportDialog/types.ts @@ -68,4 +68,5 @@ export type ParsedReportSubject = export type ReportDialogProps = { control: Dialog.DialogOuterProps['control'] subject: ParsedReportSubject + onClose?: () => void } diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx index 41e92be6a3..1d11fcca74 100644 --- a/src/screens/Messages/components/RequestButtons.tsx +++ b/src/screens/Messages/components/RequestButtons.tsx @@ -25,6 +25,7 @@ import { EmailDialogScreenID, useEmailDialogControl, } from '#/components/dialogs/EmailDialog' +import {BlockOrDeleteDialog} from '#/components/dms/BlockOrDeleteDialog' import {CircleX_Stroke2_Corner0_Rounded} from '#/components/icons/CircleX' import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag' import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person' @@ -100,6 +101,7 @@ export function RejectMenu({ }, [queueBlock, leaveConvo, _]) const reportControl = useDialogControl() + const blockOrDeleteControl = useDialogControl() const lastMessage = ChatBskyConvoDefs.isMessageView(convo.lastMessage) ? convo.lastMessage @@ -162,14 +164,27 @@ export function RejectMenu({ {lastMessage && ( - + <> + { + blockOrDeleteControl.open() + }} + /> + + )} )