Get report flow working again with temporarily fixed report options

This commit is contained in:
Paul Frazee
2024-03-06 10:43:08 -08:00
parent a1fe034c2e
commit c5872f6644
11 changed files with 551 additions and 614 deletions
@@ -0,0 +1,178 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {AppBskyLabelerDefs} from '@atproto/api'
import {useReportOptions, ReportOption} from '#/lib/moderation/useReportOptions'
import {DMCA_LINK} from '#/components/ReportDialog/const'
import {Link} from '#/components/Link'
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {
Button,
ButtonIcon,
ButtonText,
useButtonContext,
} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
import {ReportDialogProps} from './types'
export function SelectReportOptionView({
...props
}: ReportDialogProps & {
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
onSelectReportOption: (reportOption: ReportOption) => void
}) {
const t = useTheme()
const {_} = useLingui()
const allReportOptions = useReportOptions()
const reportOptions = allReportOptions[props.params.type]
const i18n = React.useMemo(() => {
let title = _(msg`Report this content`)
let description = _(msg`Why should this content be reviewed?`)
if (props.params.type === 'account') {
title = _(msg`Report this user`)
description = _(msg`Why should this user be reviewed?`)
} else if (props.params.type === 'post') {
title = _(msg`Report this post`)
description = _(msg`Why should this post be reviewed?`)
} else if (props.params.type === 'list') {
title = _(msg`Report this list`)
description = _(msg`Why should this list be reviewed?`)
}
return {
title,
description,
}
}, [_, props.params.type])
return (
<View style={[a.gap_lg]}>
<View style={[a.justify_center, a.gap_sm]}>
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
{i18n.description}
</Text>
</View>
<Divider />
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
{reportOptions.map(reportOption => {
return (
<Button
key={reportOption.reason}
label={_(msg`Create report for ${reportOption.title}`)}
onPress={() => props.onSelectReportOption(reportOption)}>
<ReportOptionButton
title={reportOption.title}
description={reportOption.description}
/>
</Button>
)
})}
{(props.params.type === 'post' || props.params.type === 'account') && (
<View style={[a.pt_md, a.px_md]}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.p_md,
a.pl_lg,
a.rounded_md,
t.atoms.bg_contrast_900,
]}>
<Text
style={[
a.flex_1,
t.atoms.text_inverted,
a.italic,
a.leading_snug,
]}>
<Trans>Need to report a copyright violation?</Trans>
</Text>
<Link
to={DMCA_LINK}
label={_(msg`View details for reporting a copyright violation`)}
size="small"
variant="solid"
color="secondary">
<ButtonText>View details</ButtonText>
<ButtonIcon position="right" icon={SquareArrowTopRight} />
</Link>
</View>
</View>
)}
</View>
</View>
)
}
function ReportOptionButton({
title,
description,
}: {
title: string
description: string
}) {
const t = useTheme()
const {hovered, focused, pressed} = useButtonContext()
const interacted = hovered || focused || pressed
const styles = React.useMemo(() => {
return {
interacted: {
backgroundColor: t.palette.contrast_50,
},
}
}, [t])
return (
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.p_md,
a.rounded_md,
{paddingRight: 70},
interacted && styles.interacted,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
{title}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
</View>
<View
style={[
a.absolute,
a.inset_0,
a.justify_center,
a.pr_md,
{left: 'auto'},
]}>
<ChevronRight
size="md"
fill={
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
}
/>
</View>
</View>
)
}
+238
View File
@@ -0,0 +1,238 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {AppBskyLabelerDefs} from '@atproto/api'
import {getModerationServiceTitle} from '#/lib/moderation'
import {ReportOption} from '#/lib/moderation/useReportOptions'
import {atoms as a, useTheme, tokens, native} from '#/alf'
import {Text} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {GradientFill} from '#/components/GradientFill'
import * as Toggle from '#/components/forms/Toggle'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {Loader} from '#/components/Loader'
import * as Toast from '#/view/com/util/Toast'
import {ReportDialogProps} from './types'
export function SubmitView({
params,
labelers,
selectedReportOption,
goBack,
onSubmitComplete,
}: ReportDialogProps & {
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
selectedReportOption: ReportOption
goBack: () => void
onSubmitComplete: () => void
}) {
const t = useTheme()
const {_} = useLingui()
const [details, setDetails] = React.useState<string>('')
const [submitting, setSubmitting] = React.useState<boolean>(false)
const [selectedServices, setSelectedServices] = React.useState<string[]>(
labelers?.map(labeler => labeler.creator.did) || [],
)
const submit = React.useCallback(async () => {
setSubmitting(true)
await new Promise(resolve => setTimeout(resolve, 1000))
const $type =
params.type === 'account'
? 'com.atproto.admin.defs#repoRef'
: 'com.atproto.repo.strongRef'
const report = {
reasonType: selectedReportOption.reason,
subject: {
$type,
...params,
},
reason: details,
}
console.log(report)
// await getAgent().createModerationReport(report)
setSubmitting(false)
Toast.show(`Thank you. Your report has been sent.`)
onSubmitComplete()
}, [params, details, selectedReportOption, onSubmitComplete])
return (
<View style={[a.gap_2xl]}>
<Button
size="small"
variant="solid"
color="secondary"
shape="round"
label={_(msg`Go back to previous step`)}
onPress={goBack}>
<ButtonIcon icon={ChevronLeft} />
</Button>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.p_md,
a.rounded_md,
t.atoms.bg_contrast_25,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold]}>
{selectedReportOption.title}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>
{selectedReportOption.description}
</Text>
</View>
<Check size="md" style={[a.pr_sm]} />
</View>
<View style={[a.gap_md]}>
<Text style={[t.atoms.text_contrast_medium]}>
<Trans>Select the moderation service(s) to report to</Trans>
</Text>
<Toggle.Group
label="Select mod services"
values={selectedServices}
onChange={setSelectedServices}>
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
{labelers.map(labeler => {
const title = getModerationServiceTitle({
displayName: labeler.creator.displayName,
handle: labeler.creator.handle,
})
return (
<Toggle.Item
key={labeler.creator.did}
name={labeler.creator.did}
label={title}>
<LabelerToggle title={title} />
</Toggle.Item>
)
})}
</View>
</Toggle.Group>
</View>
<View style={[a.gap_md]}>
<Text style={[t.atoms.text_contrast_medium]}>
<Trans>Optionally provide additional information below:</Trans>
</Text>
<View style={[a.relative, a.w_full]}>
<Dialog.Input
multiline
value={details}
onChangeText={setDetails}
label="Text field"
style={{paddingRight: 60}}
numberOfLines={6}
/>
<View
style={[
a.absolute,
a.flex_row,
a.align_center,
a.pr_md,
a.pb_sm,
{
bottom: 0,
right: 0,
},
]}>
<CharProgress count={details?.length || 0} />
</View>
</View>
</View>
<View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}>
{!selectedServices.length && (
<Text
style={[
a.flex_1,
a.italic,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
<Trans>You must select at least one labeler for a report</Trans>
</Text>
)}
<Button
size="large"
variant="solid"
color="primary"
label={_(msg`Submit`)}
onPress={submit}
disabled={!selectedServices.length}>
<ButtonText>Submit</ButtonText>
{submitting && <ButtonIcon icon={Loader} />}
</Button>
</View>
</View>
)
}
function LabelerToggle({title}: {title: string}) {
const t = useTheme()
const ctx = Toggle.useItemContext()
return (
<View
style={[
a.py_md,
a.px_xl,
a.rounded_full,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{ctx.selected && <GradientFill gradient={tokens.gradients.midnight} />}
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.z_10,
]}>
<Text
style={[
native({marginTop: 2}),
{
color:
t.name === 'light' && ctx.selected
? t.palette.white
: t.atoms.text.color,
},
]}>
{title}
</Text>
<Plus
size="sm"
fill={
ctx.selected
? t.palette.primary_200
: t.atoms.text_contrast_low.color
}
/>
</View>
</View>
)
}
+26 -463
View File
@@ -1,498 +1,65 @@
import React from 'react'
import {View, Linking, Pressable} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {AppBskyLabelerDefs, LabelGroupDefinition} from '@atproto/api'
import {AppBskyLabelerDefs} from '@atproto/api'
import {atoms as a, useTheme, tokens, native} from '#/alf'
import {Text} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import {
Button,
ButtonIcon,
ButtonText,
useButtonContext,
} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
import {
ChevronRight_Stroke2_Corner0_Rounded as ChevronRight,
ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft,
} from '#/components/icons/Chevron'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import * as Toggle from '#/components/forms/Toggle'
import {GradientFill} from '#/components/GradientFill'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {Loader} from '#/components/Loader'
import * as Toast from '#/view/com/util/Toast'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useLabelersDetailedInfoQuery} from '#/state/queries/labeler'
import {
getModerationServiceTitle,
useConfigurableContentLabelGroups,
useConfigurableProfileLabelGroups,
} from '#/lib/moderation'
import {useMyLabelers} from '#/state/queries/preferences'
import {ReportOption} from '#/lib/moderation/useReportOptions'
import {DMCA_LINK} from '#/components/ReportDialog/const'
import {Link} from '#/components/Link'
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
// import {getAgent} from '#/state/session'
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
export type ReportDialogLabelIds = LabelGroupDefinition['id'] | 'other'
export type ReportDialogProps = {
control: Dialog.DialogOuterProps['control']
params:
| {
type: 'content'
uri: string
cid: string
}
| {
type: 'profile'
did: string
}
}
import {atoms as a} from '#/alf'
import {Loader} from '#/components/Loader'
import * as Dialog from '#/components/Dialog'
function LabelGroupButton({
name,
description,
}: {
name: string
description: string
}) {
const t = useTheme()
const {hovered, focused, pressed} = useButtonContext()
const interacted = hovered || focused || pressed
const styles = React.useMemo(() => {
return {
interacted: {
backgroundColor: t.palette.contrast_50,
},
}
}, [t])
return (
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.p_md,
a.rounded_md,
{paddingRight: 70},
interacted && styles.interacted,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
{name}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
</View>
<View
style={[
a.absolute,
a.inset_0,
a.justify_center,
a.pr_md,
{left: 'auto'},
]}>
<ChevronRight
size="md"
fill={
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
}
/>
</View>
</View>
)
}
function LabelerToggle({title}: {title: string}) {
const t = useTheme()
const ctx = Toggle.useItemContext()
return (
<View
style={[
a.py_md,
a.px_xl,
a.rounded_full,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{ctx.selected && <GradientFill gradient={tokens.gradients.midnight} />}
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.z_10,
]}>
<Text
style={[
native({marginTop: 2}),
{
color:
t.name === 'light' && ctx.selected
? t.palette.white
: t.atoms.text.color,
},
]}>
{title}
</Text>
<Plus
size="sm"
fill={
ctx.selected
? t.palette.primary_200
: t.atoms.text_contrast_low.color
}
/>
</View>
</View>
)
}
function SubmitView({
params,
selectedLabelGroup,
goBack,
onSubmitComplete,
}: ReportDialogProps & {
selectedLabelGroup: ReportDialogLabelIds
goBack: () => void
onSubmitComplete: () => void
}) {
const t = useTheme()
const {_} = useLingui()
const labelGroupStrings = useLabelGroupStrings()
const groupInfoStrings = labelGroupStrings[selectedLabelGroup]
const [details, setDetails] = React.useState<string>('')
const [submitting, setSubmitting] = React.useState<boolean>(false)
const supportedLabelers = [] //labelGroupToLabelerMap[selectedLabelGroup]
const [selectedServices, setSelectedServices] = React.useState<string[]>(
supportedLabelers?.map(labeler => labeler.creator.did) || [],
)
const submit = React.useCallback(async () => {
setSubmitting(true)
await new Promise(resolve => setTimeout(resolve, 1000))
const $type =
params.type === 'content'
? 'com.atproto.repo.strongRef'
: 'com.atproto.admin.defs#repoRef'
const report = {
reasonType: selectedLabelGroup, // TODO map to reasons
subject: {
$type,
...params,
},
reason: details,
}
console.log(report)
// await getAgent().createModerationReport(report)
setSubmitting(false)
Toast.show(`Thank you. Your report has been sent.`)
onSubmitComplete()
}, [params, details, selectedLabelGroup, onSubmitComplete])
return (
<View style={[a.gap_2xl]}>
<Button
size="small"
variant="solid"
color="secondary"
shape="round"
label={_(msg`Go back to previous step`)}
onPress={goBack}>
<ButtonIcon icon={ChevronLeft} />
</Button>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.p_md,
a.rounded_md,
t.atoms.bg_contrast_25,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.text_md, a.font_bold]}>{groupInfoStrings.name}</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>
{groupInfoStrings.description}
</Text>
</View>
<Check size="md" style={[a.pr_sm]} />
</View>
<View style={[a.gap_md]}>
<Text style={[t.atoms.text_contrast_medium]}>
Select the moderation service(s) to report to
</Text>
{supportedLabelers ? (
<Toggle.Group
label="Select mod services"
values={selectedServices}
onChange={setSelectedServices}>
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
{supportedLabelers.map(labeler => {
const title = getModerationServiceTitle({
displayName: labeler.creator.displayName,
handle: labeler.creator.handle,
})
return (
<Toggle.Item
key={labeler.creator.did}
name={labeler.creator.did}
label={title}>
<LabelerToggle title={title} />
</Toggle.Item>
)
})}
</View>
</Toggle.Group>
) : (
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text style={[a.italic, t.atoms.text_contrast_medium]}>
None of your subscribed labelers support this content type.
</Text>
</View>
)}
</View>
<View style={[a.gap_md]}>
<Text style={[t.atoms.text_contrast_medium]}>
Optionally provide additional information below:
</Text>
<View style={[a.relative, a.w_full]}>
<Dialog.Input
multiline
value={details}
onChangeText={setDetails}
label="Text field"
style={{paddingRight: 60}}
numberOfLines={6}
/>
<View
style={[
a.absolute,
a.flex_row,
a.align_center,
a.pr_md,
a.pb_sm,
{
bottom: 0,
right: 0,
},
]}>
<CharProgress count={details?.length || 0} />
</View>
</View>
</View>
<View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}>
{!selectedServices.length && (
<Text
style={[
a.flex_1,
a.italic,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
You must select at least one labeler for a report
</Text>
)}
<Button
size="large"
variant="solid"
color="primary"
label={_(msg`Submit`)}
onPress={submit}
disabled={!selectedServices.length}>
<ButtonText>Submit</ButtonText>
{submitting && <ButtonIcon icon={Loader} />}
</Button>
</View>
</View>
)
}
import {ReportDialogProps} from './types'
import {SelectReportOptionView} from './SelectReportOptionView'
import {SubmitView} from './SubmitView'
export function ReportDialogLoaded({
labelGroupToLabelerMap,
...props
}: ReportDialogProps & {
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
labelGroupToLabelerMap: ReturnType<typeof getLabelGroupToLabelerMap>
}) {
const t = useTheme()
const {_} = useLingui()
const control = Dialog.useDialogControl()
const [selectedLabelGroup, setSelectedLabelGroup] = React.useState<
ReportDialogLabelIds | undefined
const [selectedReportOption, setSelectedReportOption] = React.useState<
ReportOption | undefined
>()
const labelGroupStrings = useLabelGroupStrings()
const contentGroups = useConfigurableContentLabelGroups()
const profileGroups = useConfigurableProfileLabelGroups()
const groups = props.params.type === 'content' ? contentGroups : profileGroups
const filteredGroups = groups.filter(group => {
return Boolean(labelGroupToLabelerMap[group.id])
})
const i18n = React.useMemo(() => {
let title = _(msg`Report this post`)
let description = _(msg`Why should this post be reviewed?`)
if (props.params.type === 'profile') {
title = _(msg`Report this user`)
description = _(msg`Why should this user be reviewed?`)
}
return {
title,
description,
}
}, [_, props.params.type])
const next = React.useCallback(
(group: ReportDialogLabelIds | 'copyright') => {
if (group === 'copyright') {
const onSelectReportOption = React.useCallback(
(reportOption: ReportOption) => {
if (reportOption.reason === 'copyright') {
Linking.openURL(DMCA_LINK)
} else {
setSelectedLabelGroup(group)
setSelectedReportOption(reportOption)
}
},
[setSelectedLabelGroup],
[setSelectedReportOption],
)
return (
<>
{selectedLabelGroup ? (
{selectedReportOption ? (
<SubmitView
{...props}
labelGroupToLabelerMap={labelGroupToLabelerMap}
selectedLabelGroup={selectedLabelGroup}
goBack={() => setSelectedLabelGroup(undefined)}
selectedReportOption={selectedReportOption}
goBack={() => setSelectedReportOption(undefined)}
onSubmitComplete={control.close}
/>
) : (
<View style={[a.gap_lg]}>
<View style={[a.justify_center, a.gap_sm]}>
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
{i18n.description}
</Text>
</View>
<Divider />
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
{filteredGroups.map(def => {
const strings = labelGroupStrings[def.id]
return (
<Button
key={def.id}
label={_(msg`Create report for ${strings.name}`)}
onPress={() => next(def.id)}>
<LabelGroupButton
name={strings.name}
description={strings.description}
/>
</Button>
)
})}
<Button
label={_(msg`Create report for other reasons`)}
onPress={() => next('other')}>
<LabelGroupButton
name="Other"
description="An issue not covered by another option"
/>
</Button>
{props.params.type === 'content' && (
<View style={[a.pt_md, a.px_md]}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.p_md,
a.pl_lg,
a.rounded_md,
t.atoms.bg_contrast_900,
]}>
<Text
style={[
a.flex_1,
t.atoms.text_inverted,
a.italic,
a.leading_snug,
]}>
Need to report a copyright violation?
</Text>
<Link
to={DMCA_LINK}
label={_(
msg`View details for reporting a copyright violation`,
)}
size="small"
variant="solid"
color="secondary">
<ButtonText>View details</ButtonText>
<ButtonIcon position="right" icon={SquareArrowTopRight} />
</Link>
</View>
</View>
)}
</View>
</View>
<SelectReportOptionView
{...props}
onSelectReportOption={onSelectReportOption}
/>
)}
</>
)
}
function ReportDialogInner(props: ReportDialogProps) {
const {
isLoading: isPreferencesLoading,
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
const {
isLoading: isLabelersLoading,
data: labelers,
error: labelersError,
} = useLabelersDetailedInfoQuery({
dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [],
})
const isLoading = isPreferencesLoading || isLabelersLoading
const error = preferencesError || labelersError
const {isLoading, data: labelers, error} = useMyLabelers()
const [fakeLoading, setFakeLoading] = React.useState(isLoading)
const labelGroupToLabelerMap = React.useMemo(() => {
if (!labelers) return {}
return getLabelGroupToLabelerMap(labelers)
}, [labelers])
React.useEffect(() => {
// on initial load, show a loading spinner for a hot sec to prevent flash
if (fakeLoading) setTimeout(() => setFakeLoading(false), 500)
@@ -506,12 +73,8 @@ function ReportDialogInner(props: ReportDialogProps) {
{/* Here to capture focus for a hot sec to prevent flash */}
<Pressable accessible={false} />
</View>
) : error || !(preferences && labelers) ? null : ( // TODO
<ReportDialogLoaded
{...props}
labelers={labelers}
labelGroupToLabelerMap={labelGroupToLabelerMap}
/>
) : error || !labelers ? null : ( // TODO
<ReportDialogLoaded {...props} labelers={labelers} />
)}
</Dialog.ScrollableInner>
)
+15
View File
@@ -0,0 +1,15 @@
import * as Dialog from '#/components/Dialog'
export type ReportDialogProps = {
control: Dialog.DialogOuterProps['control']
params:
| {
type: 'post' | 'list' | 'other'
uri: string
cid: string
}
| {
type: 'account'
did: string
}
}
-15
View File
@@ -25,21 +25,6 @@ export function isJustAMute(modui: ModerationUI): boolean {
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
}
export function useConfigurableContentLabelGroups() {
// TODO removeme
return []
}
export function useConfigurableProfileLabelGroups() {
// TODO removeme
return []
}
export function useConfigurableAccountLabelGroups() {
// TODO removeme
return []
}
export function getModerationServiceTitle({
displayName,
handle,
-130
View File
@@ -1,130 +0,0 @@
import {LABEL_GROUPS} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMemo} from 'react'
export type LabelGroupStrings = Record<
keyof typeof LABEL_GROUPS | 'other',
{name: string; description: string}
>
export function useLabelGroupStrings(): LabelGroupStrings {
const {_} = useLingui()
return useMemo(
() => ({
system: {
name: _(msg`System`),
description: _(msg`Moderator overrides for special cases.`),
},
legal: {
name: _(msg`Legal`),
description: _(msg`Content removed for legal reasons.`),
},
'intellectual-property': {
name: _(msg`Intellectual Property`),
description: _(msg`Plagiarism, copying without attribution.`),
},
porn: {
name: _(msg`Explicit Sexual Images`),
description: _(msg`i.e. pornography.`),
},
suggestive: {
name: _(msg`Sexually Suggestive`),
description: _(msg`Does not include nudity.`),
},
nudity: {
name: _(msg`Other Nudity`),
description: _(msg`Including non-sexual and artistic.`),
},
violence: {
name: _(msg`Violent / Bloody`),
description: _(msg`Gore, self-harm, torture.`),
},
'drugs-alcohol': {
name: _(msg`Substance Abuse`),
description: _(msg`Use of drugs or alcohol.`),
},
'self-harm': {
name: _(msg`Self Harm`),
description: _(msg`Suicide, self-harm, eating disorders.`),
},
intolerance: {
name: _(msg`Intolerance`),
description: _(
msg`Content or behavior which is hateful or intolerant toward a group of people.`,
),
},
'bad-behavior': {
name: _(msg`Bad Behavior`),
description: _(
msg`Harassment, bullying, and threats toward other users.`,
),
},
rude: {
name: _(msg`Rude`),
description: _(msg`Behavior which is rude toward other users.`),
},
upsetting: {
name: _(msg`Upsetting`),
description: _(
msg`Shocking, disgusting, or generally upsetting content.`,
),
},
troubling: {
name: _(msg`Troubling`),
description: _(
msg`Bad news, troubling information, or dispiriting content.`,
),
},
'hate-group-mention': {
name: _(msg`Hate Group Coverage`),
description: _(
msg`Images of terror groups, articles covering events, etc.`,
),
},
discourse: {
name: _(msg`Discourse / Drama`),
description: _(
msg`On-going discussions or debates that may be frustrating.`,
),
},
curation: {
name: _(msg`Curation`),
description: _(
msg`Judgment of the moderators to remove content not worth showing.`,
),
},
spam: {
name: _(msg`Spam`),
description: _(msg`Content which doesn't add to the conversation.`),
},
misrepresentation: {
name: _(msg`Misrepresentation`),
description: _(msg`Impersonations, scams.`),
},
security: {
name: _(msg`Security`),
description: _(msg`Potential security attacks.`),
},
misinfo: {
name: _(msg`Misinformation`),
description: _(msg`Content which misleads or defrauds users.`),
},
context: {
name: _(msg`Context`),
description: _(
msg`Helpful annotations to explain intent, such as satire or parody.`,
),
},
bot: {
name: _(msg`Bots`),
description: _(msg`Automated accounts which follow the rules.`),
},
other: {
name: _(msg`Other`),
description: _(msg`Other content not covered by the other categories.`),
},
}),
[_],
)
}
+85
View File
@@ -0,0 +1,85 @@
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMemo} from 'react'
import {ComAtprotoModerationDefs} from '@atproto/api'
export interface ReportOption {
reason: string
title: string
description: string
}
interface ReportOptions {
account: ReportOption[]
post: ReportOption[]
list: ReportOption[]
other: ReportOption[]
}
export function useReportOptions(): ReportOptions {
const {_} = useLingui()
return useMemo(() => {
const other = {
reason: ComAtprotoModerationDefs.REASONOTHER,
title: _(msg`Other`),
description: _(msg`An issue not included in these options`),
}
const common = [
{
reason: ComAtprotoModerationDefs.REASONRUDE,
title: _(msg`Anti-Social Behavior`),
description: _(msg`Harassment, trolling, or intolerance`),
},
{
reason: ComAtprotoModerationDefs.REASONVIOLATION,
title: _(msg`Illegal and Urgent`),
description: _(msg`Glaring violations of law or terms of service`),
},
other,
]
return {
account: [
{
reason: ComAtprotoModerationDefs.REASONMISLEADING,
title: _(msg`Misleading Account`),
description: _(
msg`Impersonation or false claims about identity or affiliation`,
),
},
{
reason: ComAtprotoModerationDefs.REASONSPAM,
title: _(msg`Frequently Posts Unwanted Content`),
description: _(msg`Spam; excessive mentions or replies`),
},
{
reason: ComAtprotoModerationDefs.REASONVIOLATION,
title: _(msg`Name or Description Violates Community Standards`),
description: _(msg`Terms used violate community standards`),
},
other,
],
post: [
{
reason: ComAtprotoModerationDefs.REASONSPAM,
title: _(msg`Spam`),
description: _(msg`Excessive mentions or replies`),
},
{
reason: ComAtprotoModerationDefs.REASONSEXUAL,
title: _(msg`Unwanted Sexual Content`),
description: _(msg`Nudity or pornography not labeled as such`),
},
...common,
],
list: [
{
reason: ComAtprotoModerationDefs.REASONVIOLATION,
title: _(msg`Name or Description Violates Community Standards`),
description: _(msg`Terms used violate community standards`),
},
...common,
],
other: common,
}
}, [_])
}
+1 -1
View File
@@ -270,7 +270,7 @@ export function ProfileHeaderDropdownBtn({
<>
<ReportDialog
control={control}
params={{type: 'profile', did: profile.did}}
params={{type: 'account', did: profile.did}}
/>
<NativeDropdown
-1
View File
@@ -10,7 +10,6 @@ import {track} from '#/lib/analytics/analytics'
import {getAge} from '#/lib/strings/time'
import {getAgent, useSession} from '#/state/session'
import {
ConfigurableLabelGroup,
UsePreferencesQueryResponse,
ThreadViewPreferences,
} from '#/state/queries/preferences/types'
+7 -3
View File
@@ -94,18 +94,22 @@ export function useMyLabelers() {
dids.push(BSKY_LABELER_DID)
}
const labelers = useLabelersDetailedInfoQuery({dids})
return labelers.data || []
return {
isLoading: prefs.isLoading || labelers.isLoading,
error: prefs.error || labelers.error,
data: labelers.data,
}
}
export function useLabelDefinitions() {
const labelers = useMyLabelers()
return {
labelDefs: Object.fromEntries(
labelers.map(labeler => [
(labelers.data || []).map(labeler => [
labeler.creator.did,
interpretLabelValueDefinitions(labeler),
]),
),
labelers,
labelers: labelers.data || [],
}
}
+1 -1
View File
@@ -284,7 +284,7 @@ let PostDropdownBtn = ({
<ReportDialog
control={control}
params={{
type: 'content',
type: 'post',
uri: postUri,
cid: postCid,
}}