about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/dms/NewChatDialog/index.tsx16
-rw-r--r--src/components/moderation/LabelsOnMeDialog.tsx28
-rw-r--r--src/screens/Messages/Conversation/ChatDisabled.tsx131
3 files changed, 158 insertions, 17 deletions
diff --git a/src/components/dms/NewChatDialog/index.tsx b/src/components/dms/NewChatDialog/index.tsx
index fb20b8f4c..e57b0aa8f 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<BottomSheetFlatListMethods>(null)
   const {currentAccount} = useSession()
-  const inputRef = React.useRef<TextInputType>(null)
+  const inputRef = useRef<TextInputType>(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 e98599b4e..8583a226f 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}
               />
             ))}
           </View>
@@ -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`)}>
           <ButtonText>{_(msg`Submit`)}</ButtonText>
+          {isPending && <ButtonIcon icon={Loader} />}
         </Button>
       </View>
     </>
diff --git a/src/screens/Messages/Conversation/ChatDisabled.tsx b/src/screens/Messages/Conversation/ChatDisabled.tsx
index e7453bfd8..faff95963 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.
           </Trans>
         </Text>
+        <AppealDialog />
       </View>
     </View>
   )
 }
+
+function AppealDialog() {
+  const control = Dialog.useDialogControl()
+  const {_} = useLingui()
+
+  return (
+    <>
+      <Button
+        testID="appealDisabledChatBtn"
+        variant="solid"
+        color="secondary"
+        size="small"
+        onPress={control.open}
+        label={_(msg`Appeal this decision`)}
+        style={a.mt_sm}>
+        <ButtonText>{_(msg`Appeal this decision`)}</ButtonText>
+      </Button>
+
+      <Dialog.Outer control={control}>
+        <Dialog.Handle />
+        <DialogInner />
+      </Dialog.Outer>
+    </>
+  )
+}
+
+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 (
+    <Dialog.ScrollableInner label={_(msg`Appeal this decision`)}>
+      <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}>
+        <Trans>Appeal this decision</Trans>
+      </Text>
+      <Text style={[a.text_md, a.leading_snug]}>
+        <Trans>
+          This appeal will be sent to the Bluesky moderation service.
+        </Trans>
+      </Text>
+      <View style={[a.my_md]}>
+        <Dialog.Input
+          label={_(msg`Text input field`)}
+          placeholder={_(
+            msg`Please explain why you think your chats were incorrectly disabled`,
+          )}
+          value={details}
+          onChangeText={setDetails}
+          autoFocus={true}
+          numberOfLines={3}
+          multiline
+          maxLength={300}
+        />
+      </View>
+
+      <View
+        style={
+          gtMobile
+            ? [a.flex_row, a.justify_between]
+            : [{flexDirection: 'column-reverse'}, a.gap_sm]
+        }>
+        <Button
+          testID="backBtn"
+          variant="solid"
+          color="secondary"
+          size="medium"
+          onPress={onBack}
+          label={_(msg`Back`)}>
+          <ButtonText>{_(msg`Back`)}</ButtonText>
+        </Button>
+        <Button
+          testID="submitBtn"
+          variant="solid"
+          color="primary"
+          size="medium"
+          onPress={onSubmit}
+          label={_(msg`Submit`)}>
+          <ButtonText>{_(msg`Submit`)}</ButtonText>
+          {isPending && <ButtonIcon icon={Loader} />}
+        </Button>
+      </View>
+      <Dialog.Close />
+    </Dialog.ScrollableInner>
+  )
+}