From d3d2dc8ad46890dda945f3401375529f1f8a8d02 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 20 May 2024 22:23:36 +0100 Subject: [PATCH] =?UTF-8?q?[=F0=9F=90=B4]=20Appeal=20form=20for=20disabled?= =?UTF-8?q?=20DMs=20(#4126)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add appeal dialog * use useMutation for the labels on me dialog * replace text button with small button --- src/components/dms/NewChatDialog/index.tsx | 16 ++- .../moderation/LabelsOnMeDialog.tsx | 28 ++-- .../Messages/Conversation/ChatDisabled.tsx | 131 +++++++++++++++++- 3 files changed, 158 insertions(+), 17 deletions(-) diff --git a/src/components/dms/NewChatDialog/index.tsx b/src/components/dms/NewChatDialog/index.tsx index fb20b8f4..e57b0aa8 100644 --- a/src/components/dms/NewChatDialog/index.tsx +++ b/src/components/dms/NewChatDialog/index.tsx @@ -1,4 +1,10 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react' +import React, { + useCallback, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' import type {TextInput as TextInputType} from 'react-native' import {View} from 'react-native' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' @@ -293,7 +299,7 @@ function SearchablePeopleList({ const control = Dialog.useDialogContext() const listRef = useRef(null) const {currentAccount} = useSession() - const inputRef = React.useRef(null) + const inputRef = useRef(null) const [searchText, setSearchText] = useState('') @@ -306,7 +312,7 @@ function SearchablePeopleList({ limit: 12, }) - const items = React.useMemo(() => { + const items = useMemo(() => { let _items: Item[] = [] if (isError) { @@ -368,7 +374,7 @@ function SearchablePeopleList({ items.push({type: 'empty', key: 'empty', message: _(msg`No results`)}) } - const renderItems = React.useCallback( + const renderItems = useCallback( ({item}: {item: Item}) => { switch (item.type) { case 'profile': { @@ -395,7 +401,7 @@ function SearchablePeopleList({ [moderationOpts, onCreateChat], ) - React.useLayoutEffect(() => { + useLayoutEffect(() => { if (isWeb) { setImmediate(() => { inputRef?.current?.focus() diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx index e98599b4..8583a226 100644 --- a/src/components/moderation/LabelsOnMeDialog.tsx +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -3,19 +3,21 @@ import {View} from 'react-native' import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' import {useLabelInfo} from '#/lib/moderation/useLabelInfo' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeHandle} from '#/lib/strings/handles' +import {logger} from '#/logger' import {useAgent, useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {Button, ButtonText} from '#/components/Button' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {Divider} from '../Divider' - +import {Loader} from '../Loader' export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' type Subject = @@ -100,7 +102,7 @@ function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { label={label} isSelfLabel={label.src === currentAccount?.did} control={props.control} - onPressAppeal={label => setAppealingLabel(label)} + onPressAppeal={setAppealingLabel} /> ))} @@ -201,8 +203,8 @@ function AppealForm({ const isAccountReport = 'did' in subject const {getAgent} = useAgent() - const onSubmit = async () => { - try { + const {mutate, isPending} = useMutation({ + mutationFn: async () => { const $type = !isAccountReport ? 'com.atproto.repo.strongRef' : 'com.atproto.admin.defs#repoRef' @@ -216,11 +218,18 @@ function AppealForm({ }, reason: details, }) - Toast.show(_(msg`Appeal submitted`)) - } finally { + }, + onError: err => { + logger.error('Failed to submit label appeal', {message: err}) + Toast.show(_(msg`Failed to submit appeal, please try again.`)) + }, + onSuccess: () => { control.close() - } - } + Toast.show(_(msg`Appeal submitted`)) + }, + }) + + const onSubmit = React.useCallback(() => mutate(), [mutate]) return ( <> @@ -281,6 +290,7 @@ function AppealForm({ onPress={onSubmit} label={_(msg`Submit`)}> {_(msg`Submit`)} + {isPending && } diff --git a/src/screens/Messages/Conversation/ChatDisabled.tsx b/src/screens/Messages/Conversation/ChatDisabled.tsx index e7453bfd..faff9596 100644 --- a/src/screens/Messages/Conversation/ChatDisabled.tsx +++ b/src/screens/Messages/Conversation/ChatDisabled.tsx @@ -1,8 +1,17 @@ -import React from 'react' +import React, {useCallback, useState} from 'react' import {View} from 'react-native' -import {Trans} from '@lingui/macro' +import {ComAtprotoModerationDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' -import {atoms as a, useTheme} from '#/alf' +import {logger} from '#/logger' +import {useAgent, useSession} from '#/state/session' +import * as Toast from '#/view/com/util/Toast' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' export function ChatDisabled() { @@ -20,7 +29,123 @@ export function ChatDisabled() { access to chats on Bluesky. + ) } + +function AppealDialog() { + const control = Dialog.useDialogControl() + const {_} = useLingui() + + return ( + <> + + + + + + + + ) +} + +function DialogInner() { + const {_} = useLingui() + const control = Dialog.useDialogContext() + const [details, setDetails] = useState('') + const {gtMobile} = useBreakpoints() + const {getAgent} = useAgent() + const {currentAccount} = useSession() + + const {mutate, isPending} = useMutation({ + mutationFn: async () => { + if (!currentAccount) + throw new Error('No current account, should be unreachable') + await getAgent().createModerationReport({ + reasonType: ComAtprotoModerationDefs.REASONAPPEAL, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: currentAccount.did, + }, + reason: details, + }) + }, + onError: err => { + logger.error('Failed to submit chat appeal', {message: err}) + Toast.show(_(msg`Failed to submit appeal, please try again.`)) + }, + onSuccess: () => { + control.close() + Toast.show(_(msg`Appeal submitted`)) + }, + }) + + const onSubmit = useCallback(() => mutate(), [mutate]) + const onBack = useCallback(() => control.close(), [control]) + + return ( + + + Appeal this decision + + + + This appeal will be sent to the Bluesky moderation service. + + + + + + + + + + + + + ) +}