Replace the report modal entirely with the report dialog

This commit is contained in:
Paul Frazee
2024-03-12 17:39:35 -07:00
parent 36478a3eea
commit 80bdbe6c60
17 changed files with 45 additions and 579 deletions
@@ -47,6 +47,9 @@ export function SelectReportOptionView({
} else if (props.params.type === 'list') {
title = _(msg`Report this list`)
description = _(msg`Why should this list be reviewed?`)
} else if (props.params.type === 'feedgen') {
title = _(msg`Report this feed`)
description = _(msg`Why should this feed be reviewed?`)
}
return {
+1 -1
View File
@@ -4,7 +4,7 @@ export type ReportDialogProps = {
control: Dialog.DialogOuterProps['control']
params:
| {
type: 'post' | 'list' | 'other'
type: 'post' | 'list' | 'feedgen' | 'other'
uri: string
cid: string
}
-1
View File
@@ -1,4 +1,3 @@
export const LOGIN_INCLUDE_DEV_SERVERS = true
export const PWI_ENABLED = true
export const NEW_ONBOARDING_ENABLED = true
export const NEW_REPORT_DIALOG_ENABLED = true
+8
View File
@@ -79,6 +79,14 @@ export function useReportOptions(): ReportOptions {
},
...common,
],
feedgen: [
{
reason: ComAtprotoModerationDefs.REASONVIOLATION,
title: _(msg`Name or Description Violates Community Standards`),
description: _(msg`Terms used violate community standards`),
},
...common,
],
other: common,
}
}, [_])
+4 -12
View File
@@ -19,7 +19,6 @@ import {shareUrl} from 'lib/sharing'
import {logger} from '#/logger'
import {useSession} from '#/state/session'
import {Shadow} from '#/state/cache/types'
import {NEW_REPORT_DIALOG_ENABLED} from '#/lib/build-flags'
import {atoms as a, useTheme} from '#/alf'
import * as Toast from 'view/com/util/Toast'
@@ -40,7 +39,7 @@ export function ProfileHeaderDropdownBtn({
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
const queryClient = useQueryClient()
const control = useReportDialogControl()
const reportDialogControl = useReportDialogControl()
const invalidateProfileQuery = React.useCallback(() => {
queryClient.invalidateQueries({
@@ -137,15 +136,8 @@ export function ProfileHeaderDropdownBtn({
const onPressReportAccount = React.useCallback(() => {
track('ProfileHeader:ReportAccountButtonClicked')
if (NEW_REPORT_DIALOG_ENABLED) {
control.open()
} else {
openModal({
name: 'report',
did: profile.did,
})
}
}, [track, openModal, profile, control])
reportDialogControl.open()
}, [track, reportDialogControl])
const isMe = React.useMemo(
() => currentAccount?.did === profile.did,
@@ -269,7 +261,7 @@ export function ProfileHeaderDropdownBtn({
return dropdownItems?.length ? (
<>
<ReportDialog
control={control}
control={reportDialogControl}
params={{type: 'account', did: profile.did}}
/>
-13
View File
@@ -26,16 +26,6 @@ export interface EditProfileModal {
onUpdate?: () => void
}
export type ReportModal = {
name: 'report'
} & (
| {
uri: string
cid: string
}
| {did: string}
)
export interface CreateOrEditListModal {
name: 'create-or-edit-list'
purpose?: string
@@ -181,9 +171,6 @@ export type Modal =
| ContentLanguagesSettingsModal
| PostLanguagesSettingsModal
// Moderation
| ReportModal
// Lists
| CreateOrEditListModal
| UserAddRemoveListsModal
-4
View File
@@ -16,7 +16,6 @@ import * as UserAddRemoveListsModal from './UserAddRemoveLists'
import * as ListAddUserModal from './ListAddRemoveUsers'
import * as AltImageModal from './AltImage'
import * as EditImageModal from './AltImage'
import * as ReportModal from './report/Modal'
import * as DeleteAccountModal from './DeleteAccount'
import * as ChangeHandleModal from './ChangeHandle'
import * as InviteCodesModal from './InviteCodes'
@@ -69,9 +68,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints
element = <EditProfileModal.Component {...activeModal} />
} else if (activeModal?.name === 'report') {
snapPoints = ReportModal.snapPoints
element = <ReportModal.Component {...activeModal} />
} else if (activeModal?.name === 'create-or-edit-list') {
snapPoints = CreateOrEditListModal.snapPoints
element = <CreateOrEditListModal.Component {...activeModal} />
-3
View File
@@ -9,7 +9,6 @@ import {useModals, useModalControls} from '#/state/modals'
import type {Modal as ModalIface} from '#/state/modals'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
import * as ReportModal from './report/Modal'
import * as CreateOrEditListModal from './CreateOrEditList'
import * as UserAddRemoveLists from './UserAddRemoveLists'
import * as ListAddUserModal from './ListAddRemoveUsers'
@@ -79,8 +78,6 @@ function Modal({modal}: {modal: ModalIface}) {
element = <ConfirmModal.Component {...modal} />
} else if (modal.name === 'edit-profile') {
element = <EditProfileModal.Component {...modal} />
} else if (modal.name === 'report') {
element = <ReportModal.Component {...modal} />
} else if (modal.name === 'create-or-edit-list') {
element = <CreateOrEditListModal.Component {...modal} />
} else if (modal.name === 'user-add-remove-lists') {
@@ -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,
},
})
-223
View File
@@ -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',
},
})
@@ -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}
/>
)
}
@@ -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,
},
})
-8
View File
@@ -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',
}
+3 -9
View File
@@ -83,7 +83,7 @@ let PostDropdownBtn = ({
const hiddenPosts = useHiddenPosts()
const {hidePost} = useHiddenPostsApi()
const openLink = useOpenLink()
const control = useReportDialogControl()
const reportDialogControl = useReportDialogControl()
const navigation = useNavigation()
const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
@@ -171,7 +171,7 @@ let PostDropdownBtn = ({
return (
<>
<ReportDialog
control={control}
control={reportDialogControl}
params={{
type: 'post',
uri: postUri,
@@ -307,13 +307,7 @@ let PostDropdownBtn = ({
<Menu.Item
testID="postDropdownReportBtn"
label={_(msg`Report post`)}
onPress={() => {
openModal({
name: 'report',
uri: postUri,
cid: postCid,
})
}}>
onPress={() => reportDialogControl.open()}>
<Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText>
<Menu.ItemIcon icon={Warning} position="right" />
</Menu.Item>
+2 -5
View File
@@ -183,12 +183,12 @@ function ProfileScreenLoaded({
const sectionTitles = useMemo<string[]>(() => {
return [
showFiltersTab ? _(msg`Labels`) : undefined,
showListsTab ? _(msg`Lists`) : undefined,
showPostsTab ? _(msg`Posts`) : undefined,
showRepliesTab ? _(msg`Replies`) : undefined,
showMediaTab ? _(msg`Media`) : undefined,
showLikesTab ? _(msg`Likes`) : undefined,
showFeedsTab ? _(msg`Feeds`) : undefined,
showListsTab ? _(msg`Lists`) : undefined,
].filter(Boolean) as string[]
}, [
showPostsTab,
@@ -212,9 +212,6 @@ function ProfileScreenLoaded({
if (showFiltersTab) {
filtersIndex = nextIndex++
}
if (showListsTab && profile.associated?.labeler) {
listsIndex = nextIndex++
}
if (showPostsTab) {
postsIndex = nextIndex++
}
@@ -230,7 +227,7 @@ function ProfileScreenLoaded({
if (showFeedsTab) {
feedsIndex = nextIndex++
}
if (showListsTab && !profile.associated?.labeler) {
if (showListsTab) {
listsIndex = nextIndex++
}
+12 -9
View File
@@ -42,7 +42,7 @@ import {ComposeIcon2} from 'lib/icons'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {useFeedSourceInfoQuery, FeedSourceFeedInfo} from '#/state/queries/feed'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {
@@ -150,7 +150,7 @@ export function ProfileFeedScreenInner({
const {_} = useLingui()
const pal = usePalette('default')
const {hasSession, currentAccount} = useSession()
const {openModal} = useModalControls()
const reportDialogControl = useReportDialogControl()
const {openComposer} = useComposerControls()
const {track} = useAnalytics()
const feedSectionRef = React.useRef<SectionRef>(null)
@@ -246,13 +246,8 @@ export function ProfileFeedScreenInner({
}, [feedInfo, track])
const onPressReport = React.useCallback(() => {
if (!feedInfo) return
openModal({
name: 'report',
uri: feedInfo.uri,
cid: feedInfo.cid,
})
}, [openModal, feedInfo])
reportDialogControl.open()
}, [reportDialogControl])
const onCurrentPageSelected = React.useCallback(
(index: number) => {
@@ -391,6 +386,14 @@ export function ProfileFeedScreenInner({
return (
<View style={s.hContentRegion}>
<ReportDialog
control={reportDialogControl}
params={{
type: 'feedgen',
uri: feedInfo.uri,
cid: feedInfo.cid,
}}
/>
<PagerWithHeader
items={SECTION_TITLES}
isHeaderReady={true}
+12 -6
View File
@@ -39,6 +39,7 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {
useListQuery,
@@ -235,6 +236,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const navigation = useNavigation<NavigationProp>()
const {currentAccount} = useSession()
const {openModal, closeModal} = useModalControls()
const reportDialogControl = useReportDialogControl()
const listMuteMutation = useListMuteMutation()
const listBlockMutation = useListBlockMutation()
const listDeleteMutation = useListDeleteMutation()
@@ -398,12 +400,8 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
])
const onPressReport = useCallback(() => {
openModal({
name: 'report',
uri: list.uri,
cid: list.cid,
})
}, [openModal, list])
reportDialogControl.open()
}, [reportDialogControl])
const onPressShare = useCallback(() => {
const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`)
@@ -574,6 +572,14 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
isOwner={list.creator.did === currentAccount?.did}
creator={list.creator}
avatarType="list">
<ReportDialog
control={reportDialogControl}
params={{
type: 'list',
uri: list.uri,
cid: list.cid,
}}
/>
{isCurateList || isPinned ? (
<Button
testID={isPinned ? 'unpinBtn' : 'pinBtn'}