about summary refs log tree commit diff
path: root/src/view/com/modals/report
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals/report')
-rw-r--r--src/view/com/modals/report/InputIssueDetails.tsx100
-rw-r--r--src/view/com/modals/report/Modal.tsx223
-rw-r--r--src/view/com/modals/report/ReasonOptions.tsx123
-rw-r--r--src/view/com/modals/report/SendReportButton.tsx62
-rw-r--r--src/view/com/modals/report/types.ts8
5 files changed, 0 insertions, 516 deletions
diff --git a/src/view/com/modals/report/InputIssueDetails.tsx b/src/view/com/modals/report/InputIssueDetails.tsx
deleted file mode 100644
index 2bc86f75e..000000000
--- a/src/view/com/modals/report/InputIssueDetails.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react'
-import {View, TouchableOpacity, StyleSheet} from 'react-native'
-import {TextInput} from '../util'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {CharProgress} from '../../composer/char-progress/CharProgress'
-import {Text} from '../../util/text/Text'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {s} from 'lib/styles'
-import {SendReportButton} from './SendReportButton'
-import {Trans, msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-export function InputIssueDetails({
-  details,
-  setDetails,
-  goBack,
-  submitReport,
-  isProcessing,
-}: {
-  details: string | undefined
-  setDetails: (v: string) => void
-  goBack: () => void
-  submitReport: () => void
-  isProcessing: boolean
-}) {
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const {isMobile} = useWebMediaQueries()
-
-  return (
-    <View
-      style={{
-        marginTop: isMobile ? 12 : 0,
-      }}>
-      <TouchableOpacity
-        testID="addDetailsBtn"
-        style={[s.mb10, styles.backBtn]}
-        onPress={goBack}
-        accessibilityRole="button"
-        accessibilityLabel={_(msg`Add details`)}
-        accessibilityHint="Add more details to your report">
-        <FontAwesomeIcon size={18} icon="angle-left" style={[pal.link]} />
-        <Text style={[pal.text, s.f18, pal.link]}>
-          {' '}
-          <Trans>Back</Trans>
-        </Text>
-      </TouchableOpacity>
-      <View style={[pal.btn, styles.detailsInputContainer]}>
-        <TextInput
-          accessibilityLabel={_(msg`Text input field`)}
-          accessibilityHint="Enter a reason for reporting this post."
-          placeholder="Enter a reason or any other details here."
-          placeholderTextColor={pal.textLight.color}
-          value={details}
-          onChangeText={setDetails}
-          autoFocus={true}
-          numberOfLines={3}
-          multiline={true}
-          textAlignVertical="top"
-          maxLength={300}
-          style={[styles.detailsInput, pal.text]}
-        />
-        <View style={styles.detailsInputBottomBar}>
-          <View style={styles.charCounter}>
-            <CharProgress count={details?.length || 0} />
-          </View>
-        </View>
-      </View>
-      <SendReportButton onPress={submitReport} isProcessing={isProcessing} />
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  backBtn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  detailsInputContainer: {
-    borderRadius: 8,
-  },
-  detailsInput: {
-    paddingHorizontal: 12,
-    paddingTop: 12,
-    paddingBottom: 12,
-    borderRadius: 8,
-    minHeight: 100,
-    fontSize: 16,
-  },
-  detailsInputBottomBar: {
-    alignSelf: 'flex-end',
-  },
-  charCounter: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingRight: 10,
-    paddingBottom: 8,
-  },
-})
diff --git a/src/view/com/modals/report/Modal.tsx b/src/view/com/modals/report/Modal.tsx
deleted file mode 100644
index abbad9b40..000000000
--- a/src/view/com/modals/report/Modal.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import React, {useState, useMemo} from 'react'
-import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native'
-import {ScrollView} from 'react-native-gesture-handler'
-import {AtUri} from '@atproto/api'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {s} from 'lib/styles'
-import {Text} from '../../util/text/Text'
-import * as Toast from '../../util/Toast'
-import {ErrorMessage} from '../../util/error/ErrorMessage'
-import {cleanError} from 'lib/strings/errors'
-import {usePalette} from 'lib/hooks/usePalette'
-import {SendReportButton} from './SendReportButton'
-import {InputIssueDetails} from './InputIssueDetails'
-import {ReportReasonOptions} from './ReasonOptions'
-import {CollectionId} from './types'
-import {Trans, msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useModalControls} from '#/state/modals'
-import {getAgent} from '#/state/session'
-
-const DMCA_LINK = 'https://bsky.social/about/support/copyright'
-
-export const snapPoints = [575]
-
-const CollectionNames = {
-  [CollectionId.FeedGenerator]: 'Feed',
-  [CollectionId.Profile]: 'Profile',
-  [CollectionId.List]: 'List',
-  [CollectionId.Post]: 'Post',
-}
-
-type ReportComponentProps =
-  | {
-      uri: string
-      cid: string
-    }
-  | {
-      did: string
-    }
-
-export function Component(content: ReportComponentProps) {
-  const {closeModal} = useModalControls()
-  const pal = usePalette('default')
-  const {isMobile} = useWebMediaQueries()
-  const [isProcessing, setIsProcessing] = useState(false)
-  const [showDetailsInput, setShowDetailsInput] = useState(false)
-  const [error, setError] = useState<string>('')
-  const [issue, setIssue] = useState<string>('')
-  const [details, setDetails] = useState<string>('')
-  const isAccountReport = 'did' in content
-  const subjectKey = isAccountReport ? content.did : content.uri
-  const atUri = useMemo(
-    () => (!isAccountReport ? new AtUri(subjectKey) : null),
-    [isAccountReport, subjectKey],
-  )
-
-  const submitReport = async () => {
-    setError('')
-    if (!issue) {
-      return
-    }
-    setIsProcessing(true)
-    try {
-      if (issue === '__copyright__') {
-        Linking.openURL(DMCA_LINK)
-        closeModal()
-        return
-      }
-      const $type = !isAccountReport
-        ? 'com.atproto.repo.strongRef'
-        : 'com.atproto.admin.defs#repoRef'
-      await getAgent().createModerationReport({
-        reasonType: issue,
-        subject: {
-          $type,
-          ...content,
-        },
-        reason: details,
-      })
-      Toast.show("Thank you for your report! We'll look into it promptly.")
-
-      closeModal()
-      return
-    } catch (e: any) {
-      setError(cleanError(e))
-      setIsProcessing(false)
-    }
-  }
-
-  const goBack = () => {
-    setShowDetailsInput(false)
-  }
-
-  return (
-    <ScrollView testID="reportModal" style={[s.flex1, pal.view]}>
-      <View
-        style={[
-          styles.container,
-          isMobile && {
-            paddingBottom: 40,
-          },
-        ]}>
-        {showDetailsInput ? (
-          <InputIssueDetails
-            details={details}
-            setDetails={setDetails}
-            goBack={goBack}
-            submitReport={submitReport}
-            isProcessing={isProcessing}
-          />
-        ) : (
-          <SelectIssue
-            setShowDetailsInput={setShowDetailsInput}
-            error={error}
-            issue={issue}
-            setIssue={setIssue}
-            submitReport={submitReport}
-            isProcessing={isProcessing}
-            atUri={atUri}
-          />
-        )}
-      </View>
-    </ScrollView>
-  )
-}
-
-// If no atUri is passed, that means the reporting collection is account
-const getCollectionNameForReport = (atUri: AtUri | null) => {
-  if (!atUri) return 'Account'
-  // Generic fallback for any collection being reported
-  return CollectionNames[atUri.collection as CollectionId] || 'Content'
-}
-
-const SelectIssue = ({
-  error,
-  setShowDetailsInput,
-  issue,
-  setIssue,
-  submitReport,
-  isProcessing,
-  atUri,
-}: {
-  error: string | undefined
-  setShowDetailsInput: (v: boolean) => void
-  issue: string | undefined
-  setIssue: (v: string) => void
-  submitReport: () => void
-  isProcessing: boolean
-  atUri: AtUri | null
-}) => {
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const collectionName = getCollectionNameForReport(atUri)
-  const onSelectIssue = (v: string) => setIssue(v)
-  const goToDetails = () => {
-    if (issue === '__copyright__') {
-      Linking.openURL(DMCA_LINK)
-      return
-    }
-    setShowDetailsInput(true)
-  }
-
-  return (
-    <>
-      <Text style={[pal.text, styles.title]}>
-        <Trans>Report {collectionName}</Trans>
-      </Text>
-      <Text style={[pal.textLight, styles.description]}>
-        <Trans>What is the issue with this {collectionName}?</Trans>
-      </Text>
-      <View style={{marginBottom: 10}}>
-        <ReportReasonOptions
-          atUri={atUri}
-          selectedIssue={issue}
-          onSelectIssue={onSelectIssue}
-        />
-      </View>
-      {error ? <ErrorMessage message={error} /> : undefined}
-      {/* If no atUri is present, the report would be for account in which case, we allow sending without specifying a reason */}
-      {issue || !atUri ? (
-        <>
-          <SendReportButton
-            onPress={submitReport}
-            isProcessing={isProcessing}
-          />
-          <TouchableOpacity
-            testID="addDetailsBtn"
-            style={styles.addDetailsBtn}
-            onPress={goToDetails}
-            accessibilityRole="button"
-            accessibilityLabel={_(msg`Add details`)}
-            accessibilityHint="Add more details to your report">
-            <Text style={[s.f18, pal.link]}>
-              <Trans>Add details to report</Trans>
-            </Text>
-          </TouchableOpacity>
-        </>
-      ) : undefined}
-    </>
-  )
-}
-
-const styles = StyleSheet.create({
-  container: {
-    paddingHorizontal: 10,
-  },
-  title: {
-    textAlign: 'center',
-    fontWeight: 'bold',
-    fontSize: 24,
-    marginBottom: 12,
-  },
-  description: {
-    textAlign: 'center',
-    fontSize: 17,
-    paddingHorizontal: 22,
-    marginBottom: 10,
-  },
-  addDetailsBtn: {
-    padding: 14,
-    alignSelf: 'center',
-  },
-})
diff --git a/src/view/com/modals/report/ReasonOptions.tsx b/src/view/com/modals/report/ReasonOptions.tsx
deleted file mode 100644
index 23b49b664..000000000
--- a/src/view/com/modals/report/ReasonOptions.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import {View} from 'react-native'
-import React, {useMemo} from 'react'
-import {AtUri, ComAtprotoModerationDefs} from '@atproto/api'
-
-import {Text} from '../../util/text/Text'
-import {UsePaletteValue, usePalette} from 'lib/hooks/usePalette'
-import {RadioGroup, RadioGroupItem} from 'view/com/util/forms/RadioGroup'
-import {CollectionId} from './types'
-
-type ReasonMap = Record<string, {title: string; description: string}>
-const CommonReasons = {
-  [ComAtprotoModerationDefs.REASONRUDE]: {
-    title: 'Anti-Social Behavior',
-    description: 'Harassment, trolling, or intolerance',
-  },
-  [ComAtprotoModerationDefs.REASONVIOLATION]: {
-    title: 'Illegal and Urgent',
-    description: 'Glaring violations of law or terms of service',
-  },
-  [ComAtprotoModerationDefs.REASONOTHER]: {
-    title: 'Other',
-    description: 'An issue not included in these options',
-  },
-}
-const CollectionToReasonsMap: Record<string, ReasonMap> = {
-  [CollectionId.Post]: {
-    [ComAtprotoModerationDefs.REASONSPAM]: {
-      title: 'Spam',
-      description: 'Excessive mentions or replies',
-    },
-    [ComAtprotoModerationDefs.REASONSEXUAL]: {
-      title: 'Unwanted Sexual Content',
-      description: 'Nudity or pornography not labeled as such',
-    },
-    __copyright__: {
-      title: 'Copyright Violation',
-      description: 'Contains copyrighted material',
-    },
-    ...CommonReasons,
-  },
-  [CollectionId.List]: {
-    ...CommonReasons,
-    [ComAtprotoModerationDefs.REASONVIOLATION]: {
-      title: 'Name or Description Violates Community Standards',
-      description: 'Terms used violate community standards',
-    },
-  },
-}
-const AccountReportReasons = {
-  [ComAtprotoModerationDefs.REASONMISLEADING]: {
-    title: 'Misleading Account',
-    description: 'Impersonation or false claims about identity or affiliation',
-  },
-  [ComAtprotoModerationDefs.REASONSPAM]: {
-    title: 'Frequently Posts Unwanted Content',
-    description: 'Spam; excessive mentions or replies',
-  },
-  [ComAtprotoModerationDefs.REASONVIOLATION]: {
-    title: 'Name or Description Violates Community Standards',
-    description: 'Terms used violate community standards',
-  },
-}
-
-const Option = ({
-  pal,
-  title,
-  description,
-}: {
-  pal: UsePaletteValue
-  description: string
-  title: string
-}) => {
-  return (
-    <View>
-      <Text style={pal.text} type="md-bold">
-        {title}
-      </Text>
-      <Text style={pal.textLight}>{description}</Text>
-    </View>
-  )
-}
-
-// This is mostly just content copy without almost any logic
-// so this may grow over time and it makes sense to split it up into its own file
-// to keep it separate from the actual reporting modal logic
-const useReportRadioOptions = (pal: UsePaletteValue, atUri: AtUri | null) =>
-  useMemo(() => {
-    let items: ReasonMap = {...CommonReasons}
-    // If no atUri is passed, that means the reporting collection is account
-    if (!atUri) {
-      items = {...AccountReportReasons}
-    }
-
-    if (atUri?.collection && CollectionToReasonsMap[atUri.collection]) {
-      items = {...CollectionToReasonsMap[atUri.collection]}
-    }
-
-    return Object.entries(items).map(([key, {title, description}]) => ({
-      key,
-      label: <Option pal={pal} title={title} description={description} />,
-    }))
-  }, [pal, atUri])
-
-export const ReportReasonOptions = ({
-  atUri,
-  selectedIssue,
-  onSelectIssue,
-}: {
-  atUri: AtUri | null
-  selectedIssue?: string
-  onSelectIssue: (key: string) => void
-}) => {
-  const pal = usePalette('default')
-  const ITEMS: RadioGroupItem[] = useReportRadioOptions(pal, atUri)
-  return (
-    <RadioGroup
-      items={ITEMS}
-      onSelect={onSelectIssue}
-      testID="reportReasonRadios"
-      initialSelection={selectedIssue}
-    />
-  )
-}
diff --git a/src/view/com/modals/report/SendReportButton.tsx b/src/view/com/modals/report/SendReportButton.tsx
deleted file mode 100644
index 40c239bff..000000000
--- a/src/view/com/modals/report/SendReportButton.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react'
-import LinearGradient from 'react-native-linear-gradient'
-import {
-  ActivityIndicator,
-  StyleSheet,
-  TouchableOpacity,
-  View,
-} from 'react-native'
-import {Text} from '../../util/text/Text'
-import {s, gradients, colors} from 'lib/styles'
-import {Trans, msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-export function SendReportButton({
-  onPress,
-  isProcessing,
-}: {
-  onPress: () => void
-  isProcessing: boolean
-}) {
-  const {_} = useLingui()
-  // loading state
-  // =
-  if (isProcessing) {
-    return (
-      <View style={[styles.btn, s.mt10]}>
-        <ActivityIndicator />
-      </View>
-    )
-  }
-  return (
-    <TouchableOpacity
-      testID="sendReportBtn"
-      style={s.mt10}
-      onPress={onPress}
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Report post`)}
-      accessibilityHint={`Reports post with reason and details`}>
-      <LinearGradient
-        colors={[gradients.blueLight.start, gradients.blueLight.end]}
-        start={{x: 0, y: 0}}
-        end={{x: 1, y: 1}}
-        style={[styles.btn]}>
-        <Text style={[s.white, s.bold, s.f18]}>
-          <Trans>Send Report</Trans>
-        </Text>
-      </LinearGradient>
-    </TouchableOpacity>
-  )
-}
-
-const styles = StyleSheet.create({
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    width: '100%',
-    borderRadius: 32,
-    padding: 14,
-    backgroundColor: colors.gray1,
-  },
-})
diff --git a/src/view/com/modals/report/types.ts b/src/view/com/modals/report/types.ts
deleted file mode 100644
index ca947ecbd..000000000
--- a/src/view/com/modals/report/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// TODO: ATM, @atproto/api does not export ids but it does have these listed at @atproto/api/client/lexicons
-// once we start exporting the ids from the @atproto/ap package, replace these hardcoded ones
-export enum CollectionId {
-  FeedGenerator = 'app.bsky.feed.generator',
-  Profile = 'app.bsky.actor.profile',
-  List = 'app.bsky.graph.list',
-  Post = 'app.bsky.feed.post',
-}