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 {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {ComAtprotoLabelDefs} from '@atproto/api' import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api'
import {useLabelInfo} from '#/lib/moderation/useLabelInfo' import {useLabelInfo} from '#/lib/moderation/useLabelInfo'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {getAgent} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {InlineLink} from '#/components/Link' import {InlineLink} from '#/components/Link'
import * as Toast from '#/view/com/util/Toast'
export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog'
@@ -34,69 +36,66 @@ export interface LabelsOnMeDialogProps {
export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) {
const {_} = useLingui() const {_} = useLingui()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const [appealingLabel, setAppealingLabel] = React.useState<
ComAtprotoLabelDefs.Label | undefined
>(undefined)
const {subject, labels} = props const {subject, labels} = props
const isAccount = 'did' in subject 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 ( return (
<Dialog.ScrollableInner <Dialog.ScrollableInner
accessibilityDescribedBy="dialog-description" accessibilityDescribedBy="dialog-description"
accessibilityLabelledBy="dialog-title"> accessibilityLabelledBy="dialog-title">
<Text {appealingLabel ? (
nativeID="dialog-title" <AppealForm
style={[a.text_2xl, a.font_bold, a.pb_md, a.leading_tight]}> label={appealingLabel}
{isAccount ? ( subject={subject}
<Trans>Labels on your account</Trans> control={props.control}
) : ( onPressBack={() => setAppealingLabel(undefined)}
<Trans>Labels on your content</Trans> />
)} ) : (
</Text> <>
<Text nativeID="dialog-description" style={[a.text_sm, a.leading_snug]}> <Text
<Trans> nativeID="dialog-title"
You may appeal these labels if you feel they were placed in error. style={[a.text_2xl, a.font_bold, a.pb_md, a.leading_tight]}>
</Trans> {isAccount ? (
</Text> <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]}> <View style={[a.py_lg, a.gap_md]}>
{labels.map(label => ( {labels.map(label => (
<Label <Label
key={`${label.val}-${label.src}`} key={`${label.val}-${label.src}`}
label={label} label={label}
control={props.control} control={props.control}
/> onPressAppeal={label => setAppealingLabel(label)}
))} />
</View> ))}
</View>
<View style={gtMobile && [a.flex_row, a.justify_end]}> <View style={gtMobile && [a.flex_row, a.justify_end]}>
<Button <Button
testID="doneBtn" testID="doneBtn"
variant="outline" variant="outline"
color="primary" color="primary"
size="small" size="small"
onPress={() => props.control.close()} onPress={() => props.control.close()}
label={_(msg`Done`)}> label={_(msg`Done`)}>
{_(msg`Done`)} {_(msg`Done`)}
</Button> </Button>
</View> </View>
</>
)}
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
) )
} }
@@ -114,9 +113,11 @@ export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) {
function Label({ function Label({
label, label,
control, control,
onPressAppeal,
}: { }: {
label: ComAtprotoLabelDefs.Label label: ComAtprotoLabelDefs.Label
control: Dialog.DialogOuterProps['control'] control: Dialog.DialogOuterProps['control']
onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
@@ -127,7 +128,6 @@ function Label({
style={[ style={[
a.p_md, a.p_md,
a.rounded_sm, a.rounded_sm,
// t.atoms.bg_contrast_25,
a.border, a.border,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
a.gap_sm, a.gap_sm,
@@ -142,8 +142,7 @@ function Label({
to={makeProfileLink( to={makeProfileLink(
labeler ? labeler.creator : {did: label.src, handle: ''}, labeler ? labeler.creator : {did: label.src, handle: ''},
)} )}
onPress={() => control.close()} onPress={() => control.close()}>
style={[]}>
{labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src} {labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src}
</InlineLink> </InlineLink>
</View> </View>
@@ -152,7 +151,8 @@ function Label({
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
label={_(msg`Appeal`)}> label={_(msg`Appeal`)}
onPress={() => onPressAppeal(label)}>
<ButtonText> <ButtonText>
<Trans>Appeal</Trans> <Trans>Appeal</Trans>
</ButtonText> </ButtonText>
@@ -161,3 +161,106 @@ function Label({
</View> </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>
</>
)
}