diff options
Diffstat (limited to 'src/view/com/modals/report')
-rw-r--r-- | src/view/com/modals/report/InputIssueDetails.tsx | 100 | ||||
-rw-r--r-- | src/view/com/modals/report/Modal.tsx | 223 | ||||
-rw-r--r-- | src/view/com/modals/report/ReasonOptions.tsx | 123 | ||||
-rw-r--r-- | src/view/com/modals/report/SendReportButton.tsx | 62 | ||||
-rw-r--r-- | src/view/com/modals/report/types.ts | 8 |
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', -} |