Implement the label appeal flow

This commit is contained in:
Paul Frazee
2024-03-06 00:17:09 -08:00
parent 26a338f586
commit a1fe034c2e
+162 -59
View File
@@ -2,17 +2,19 @@ import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {ComAtprotoLabelDefs} from '@atproto/api'
import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api'
import {useLabelInfo} from '#/lib/moderation/useLabelInfo'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeHandle} from '#/lib/strings/handles'
import {getAgent} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import {Button, ButtonText} from '#/components/Button'
import {InlineLink} from '#/components/Link'
import * as Toast from '#/view/com/util/Toast'
export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog'
@@ -34,69 +36,66 @@ export interface LabelsOnMeDialogProps {
export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const [appealingLabel, setAppealingLabel] = React.useState<
ComAtprotoLabelDefs.Label | undefined
>(undefined)
const {subject, labels} = props
const isAccount = 'did' in subject
// TODO
// const submit = async () => {
// try {
// const $type = !isAccountReport
// ? 'com.atproto.repo.strongRef'
// : 'com.atproto.admin.defs#repoRef'
// await getAgent().createModerationReport({
// reasonType: ComAtprotoModerationDefs.REASONAPPEAL,
// subject: {
// $type,
// ...props,
// },
// reason: details,
// })
// Toast.show(_(msg`We'll look into your appeal promptly.`))
// } finally {
// closeModal()
// }
// }
return (
<Dialog.ScrollableInner
accessibilityDescribedBy="dialog-description"
accessibilityLabelledBy="dialog-title">
<Text
nativeID="dialog-title"
style={[a.text_2xl, a.font_bold, a.pb_md, a.leading_tight]}>
{isAccount ? (
<Trans>Labels on your account</Trans>
) : (
<Trans>Labels on your content</Trans>
)}
</Text>
<Text nativeID="dialog-description" style={[a.text_sm, a.leading_snug]}>
<Trans>
You may appeal these labels if you feel they were placed in error.
</Trans>
</Text>
{appealingLabel ? (
<AppealForm
label={appealingLabel}
subject={subject}
control={props.control}
onPressBack={() => setAppealingLabel(undefined)}
/>
) : (
<>
<Text
nativeID="dialog-title"
style={[a.text_2xl, a.font_bold, a.pb_md, a.leading_tight]}>
{isAccount ? (
<Trans>Labels on your account</Trans>
) : (
<Trans>Labels on your content</Trans>
)}
</Text>
<Text
nativeID="dialog-description"
style={[a.text_sm, a.leading_snug]}>
<Trans>
You may appeal these labels if you feel they were placed in error.
</Trans>
</Text>
<View style={[a.py_lg, a.gap_md]}>
{labels.map(label => (
<Label
key={`${label.val}-${label.src}`}
label={label}
control={props.control}
/>
))}
</View>
<View style={[a.py_lg, a.gap_md]}>
{labels.map(label => (
<Label
key={`${label.val}-${label.src}`}
label={label}
control={props.control}
onPressAppeal={label => setAppealingLabel(label)}
/>
))}
</View>
<View style={gtMobile && [a.flex_row, a.justify_end]}>
<Button
testID="doneBtn"
variant="outline"
color="primary"
size="small"
onPress={() => props.control.close()}
label={_(msg`Done`)}>
{_(msg`Done`)}
</Button>
</View>
<View style={gtMobile && [a.flex_row, a.justify_end]}>
<Button
testID="doneBtn"
variant="outline"
color="primary"
size="small"
onPress={() => props.control.close()}
label={_(msg`Done`)}>
{_(msg`Done`)}
</Button>
</View>
</>
)}
</Dialog.ScrollableInner>
)
}
@@ -114,9 +113,11 @@ export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) {
function Label({
label,
control,
onPressAppeal,
}: {
label: ComAtprotoLabelDefs.Label
control: Dialog.DialogOuterProps['control']
onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void
}) {
const t = useTheme()
const {_} = useLingui()
@@ -127,7 +128,6 @@ function Label({
style={[
a.p_md,
a.rounded_sm,
// t.atoms.bg_contrast_25,
a.border,
t.atoms.border_contrast_low,
a.gap_sm,
@@ -142,8 +142,7 @@ function Label({
to={makeProfileLink(
labeler ? labeler.creator : {did: label.src, handle: ''},
)}
onPress={() => control.close()}
style={[]}>
onPress={() => control.close()}>
{labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src}
</InlineLink>
</View>
@@ -152,7 +151,8 @@ function Label({
variant="solid"
color="secondary"
size="small"
label={_(msg`Appeal`)}>
label={_(msg`Appeal`)}
onPress={() => onPressAppeal(label)}>
<ButtonText>
<Trans>Appeal</Trans>
</ButtonText>
@@ -161,3 +161,106 @@ function Label({
</View>
)
}
function AppealForm({
label,
subject,
control,
onPressBack,
}: {
label: ComAtprotoLabelDefs.Label
subject: Subject
control: Dialog.DialogOuterProps['control']
onPressBack: () => void
}) {
const {_} = useLingui()
const {labeler, strings} = useLabelInfo(label)
const {gtMobile} = useBreakpoints()
const [details, setDetails] = React.useState('')
const isAccountReport = 'did' in subject
const onSubmit = async () => {
try {
const $type = !isAccountReport
? 'com.atproto.repo.strongRef'
: 'com.atproto.admin.defs#repoRef'
await getAgent().createModerationReport({
reasonType: ComAtprotoModerationDefs.REASONAPPEAL,
subject: {
$type,
...subject,
},
reason: details,
})
Toast.show(_(msg`Appeal submitted.`))
} finally {
control.close()
}
}
return (
<>
<Text
nativeID="dialog-title"
style={[a.text_2xl, a.font_bold, a.pb_md, a.leading_tight]}>
<Trans>Appeal "{strings.name}" label</Trans>
</Text>
<Text nativeID="dialog-description" style={[a.text_sm, a.leading_snug]}>
<Trans>
This appeal will be sent to{' '}
<InlineLink
to={makeProfileLink(
labeler ? labeler.creator : {did: label.src, handle: ''},
)}
onPress={() => control.close()}>
{labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src}
</InlineLink>
.
</Trans>
</Text>
<View style={[a.my_md]}>
<Dialog.Input
label={_(msg`Text input field`)}
placeholder={_(
msg`Please explain why you think this label was incorrectly applied by ${
labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src
}`,
)}
value={details}
onChangeText={setDetails}
autoFocus={true}
numberOfLines={3}
multiline={true}
textAlignVertical="top"
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="small"
onPress={onPressBack}
label={_(msg`Back`)}>
{_(msg`Back`)}
</Button>
<Button
testID="submitBtn"
variant="solid"
color="primary"
size="small"
onPress={onSubmit}
label={_(msg`Submit`)}>
{_(msg`Submit`)}
</Button>
</View>
</>
)
}