Update the reporting flow to first select a recipient if the user has multiple labelers (#3258)
parent
1b10c7bc08
commit
959121f394
|
@ -0,0 +1,115 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {Button, useButtonContext} from '#/components/Button'
|
||||||
|
import {Divider} from '#/components/Divider'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||||
|
|
||||||
|
import {ReportDialogProps} from './types'
|
||||||
|
|
||||||
|
export function SelectLabelerView({
|
||||||
|
...props
|
||||||
|
}: ReportDialogProps & {
|
||||||
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
|
onSelectLabeler: (v: string) => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<View style={[a.justify_center, a.gap_sm]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold]}>
|
||||||
|
<Trans>Select moderation service</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans>Who do you want to send this report to?</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||||
|
{props.labelers.map(labeler => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={labeler.creator.did}
|
||||||
|
label={_(msg`Send report to ${labeler.creator.displayName}`)}
|
||||||
|
onPress={() => props.onSelectLabeler(labeler.creator.did)}>
|
||||||
|
<LabelerButton
|
||||||
|
title={labeler.creator.displayName || labeler.creator.handle}
|
||||||
|
description={labeler.creator.description || ''}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LabelerButton({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {hovered, pressed} = useButtonContext()
|
||||||
|
const interacted = hovered || 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}]} numberOfLines={3}>
|
||||||
|
{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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -18,7 +18,10 @@ import {
|
||||||
useButtonContext,
|
useButtonContext,
|
||||||
} from '#/components/Button'
|
} from '#/components/Button'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
import {
|
||||||
|
ChevronRight_Stroke2_Corner0_Rounded as ChevronRight,
|
||||||
|
ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft,
|
||||||
|
} from '#/components/icons/Chevron'
|
||||||
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
|
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
|
||||||
|
|
||||||
import {ReportDialogProps} from './types'
|
import {ReportDialogProps} from './types'
|
||||||
|
@ -28,6 +31,7 @@ export function SelectReportOptionView({
|
||||||
}: ReportDialogProps & {
|
}: ReportDialogProps & {
|
||||||
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
onSelectReportOption: (reportOption: ReportOption) => void
|
onSelectReportOption: (reportOption: ReportOption) => void
|
||||||
|
goBack: () => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -60,6 +64,18 @@ export function SelectReportOptionView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.gap_lg]}>
|
<View style={[a.gap_lg]}>
|
||||||
|
{props.labelers?.length > 1 ? (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
shape="round"
|
||||||
|
label={_(msg`Go back to previous step`)}
|
||||||
|
onPress={props.goBack}>
|
||||||
|
<ButtonIcon icon={ChevronLeft} />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<View style={[a.justify_center, a.gap_sm]}>
|
<View style={[a.justify_center, a.gap_sm]}>
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
||||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
|
|
|
@ -24,11 +24,13 @@ import {getAgent} from '#/state/session'
|
||||||
export function SubmitView({
|
export function SubmitView({
|
||||||
params,
|
params,
|
||||||
labelers,
|
labelers,
|
||||||
|
selectedLabeler,
|
||||||
selectedReportOption,
|
selectedReportOption,
|
||||||
goBack,
|
goBack,
|
||||||
onSubmitComplete,
|
onSubmitComplete,
|
||||||
}: ReportDialogProps & {
|
}: ReportDialogProps & {
|
||||||
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
|
selectedLabeler: string
|
||||||
selectedReportOption: ReportOption
|
selectedReportOption: ReportOption
|
||||||
goBack: () => void
|
goBack: () => void
|
||||||
onSubmitComplete: () => void
|
onSubmitComplete: () => void
|
||||||
|
@ -37,9 +39,9 @@ export function SubmitView({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const [details, setDetails] = React.useState<string>('')
|
const [details, setDetails] = React.useState<string>('')
|
||||||
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
||||||
const [selectedServices, setSelectedServices] = React.useState<string[]>(
|
const [selectedServices, setSelectedServices] = React.useState<string[]>([
|
||||||
labelers?.map(labeler => labeler.creator.did) || [],
|
selectedLabeler,
|
||||||
)
|
])
|
||||||
const [error, setError] = React.useState('')
|
const [error, setError] = React.useState('')
|
||||||
|
|
||||||
const submit = React.useCallback(async () => {
|
const submit = React.useCallback(async () => {
|
||||||
|
|
|
@ -12,9 +12,11 @@ import * as Dialog from '#/components/Dialog'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
import {ReportDialogProps} from './types'
|
import {ReportDialogProps} from './types'
|
||||||
|
import {SelectLabelerView} from './SelectLabelerView'
|
||||||
import {SelectReportOptionView} from './SelectReportOptionView'
|
import {SelectReportOptionView} from './SelectReportOptionView'
|
||||||
import {SubmitView} from './SubmitView'
|
import {SubmitView} from './SubmitView'
|
||||||
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
|
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
|
||||||
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
|
|
||||||
export function ReportDialog(props: ReportDialogProps) {
|
export function ReportDialog(props: ReportDialogProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -33,9 +35,6 @@ function ReportDialogInner(props: ReportDialogProps) {
|
||||||
error,
|
error,
|
||||||
} = useMyLabelersQuery()
|
} = useMyLabelersQuery()
|
||||||
const isLoading = useDelayedLoading(500, isLabelerLoading)
|
const isLoading = useDelayedLoading(500, isLabelerLoading)
|
||||||
const [selectedReportOption, setSelectedReportOption] = React.useState<
|
|
||||||
ReportOption | undefined
|
|
||||||
>()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.ScrollableInner label="Report Dialog">
|
<Dialog.ScrollableInner label="Report Dialog">
|
||||||
|
@ -51,23 +50,46 @@ function ReportDialogInner(props: ReportDialogProps) {
|
||||||
<Trans>Something went wrong, please try again.</Trans>
|
<Trans>Something went wrong, please try again.</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : selectedReportOption ? (
|
|
||||||
<SubmitView
|
|
||||||
{...props}
|
|
||||||
labelers={labelers}
|
|
||||||
selectedReportOption={selectedReportOption}
|
|
||||||
goBack={() => setSelectedReportOption(undefined)}
|
|
||||||
onSubmitComplete={() => props.control.close()}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<SelectReportOptionView
|
<ReportDialogLoaded labelers={labelers} {...props} />
|
||||||
{...props}
|
|
||||||
labelers={labelers}
|
|
||||||
onSelectReportOption={setSelectedReportOption}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dialog.Close />
|
<Dialog.Close />
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ReportDialogLoaded(
|
||||||
|
props: ReportDialogProps & {
|
||||||
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const [selectedLabeler, setSelectedLabeler] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>(props.labelers.length === 1 ? props.labelers[0].creator.did : undefined)
|
||||||
|
const [selectedReportOption, setSelectedReportOption] = React.useState<
|
||||||
|
ReportOption | undefined
|
||||||
|
>()
|
||||||
|
|
||||||
|
if (selectedReportOption && selectedLabeler) {
|
||||||
|
return (
|
||||||
|
<SubmitView
|
||||||
|
{...props}
|
||||||
|
selectedLabeler={selectedLabeler}
|
||||||
|
selectedReportOption={selectedReportOption}
|
||||||
|
goBack={() => setSelectedReportOption(undefined)}
|
||||||
|
onSubmitComplete={() => props.control.close()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (selectedLabeler) {
|
||||||
|
return (
|
||||||
|
<SelectReportOptionView
|
||||||
|
{...props}
|
||||||
|
goBack={() => setSelectedLabeler(undefined)}
|
||||||
|
onSelectReportOption={setSelectedReportOption}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <SelectLabelerView {...props} onSelectLabeler={setSelectedLabeler} />
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue