This commit is contained in:
Hailey
2025-09-24 20:42:25 -07:00
committed by Eric Bailey
parent 9d1219bab0
commit 11d4747b72
3 changed files with 59 additions and 91 deletions
@@ -37,7 +37,7 @@ import {type ReportDialogProps, type ReportSubject} from './types'
import {parseReportSubject} from './utils/parseReportSubject' import {parseReportSubject} from './utils/parseReportSubject'
import { import {
type ReportOption, type ReportOption,
TopLevelReportOption, type ReportOptionCategory,
useReportOptions, useReportOptions,
} from './utils/useReportOptions' } from './utils/useReportOptions'
@@ -99,7 +99,7 @@ function Inner(props: ReportDialogProps) {
} = useMyLabelersQuery({excludeNonConfigurableLabelers: true}) } = useMyLabelersQuery({excludeNonConfigurableLabelers: true})
const isLoading = useDelayedLoading(500, isLabelerLoading) const isLoading = useDelayedLoading(500, isLabelerLoading)
const copy = useCopyForSubject(props.subject) const copy = useCopyForSubject(props.subject)
const {reasons, sortedReasons, getTopLevelReason} = useReportOptions() const {sortedCategories, getCategory} = useReportOptions()
const [state, dispatch] = React.useReducer(reducer, initialState) const [state, dispatch] = React.useReducer(reducer, initialState)
/** /**
@@ -241,32 +241,32 @@ function Inner(props: ReportDialogProps) {
</Admonition.Outer> </Admonition.Outer>
) : ( ) : (
<> <>
{state.selectedTopLevelOption ? ( {state.selectedCategory ? (
<View style={[a.flex_row, a.align_center, a.gap_md]}> <View style={[a.flex_row, a.align_center, a.gap_md]}>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
<TopLevelOptionCard option={state.selectedTopLevelOption} /> <CategoryCard option={state.selectedCategory} />
</View> </View>
<Button <Button
testID="report:clearTopLevelOption" testID="report:clearCategory"
label={_(msg`Change report reason`)} label={_(msg`Change report reason`)}
size="tiny" size="tiny"
variant="solid" variant="solid"
color="secondary" color="secondary"
shape="round" shape="round"
onPress={() => { onPress={() => {
dispatch({type: 'clearTopLevelOption'}) dispatch({type: 'clearCategory'})
}}> }}>
<ButtonIcon icon={X} /> <ButtonIcon icon={X} />
</Button> </Button>
</View> </View>
) : ( ) : (
<View style={[a.gap_sm]}> <View style={[a.gap_sm]}>
{sortedReasons.map(o => ( {sortedCategories.map(o => (
<TopLevelOptionCard <CategoryCard
key={o.key} key={o.key}
option={o} option={o}
onSelect={() => { onSelect={() => {
dispatch({type: 'selectTopLevelOption', option: o}) dispatch({type: 'selectCategory', option: o})
}} }}
/> />
))} ))}
@@ -314,7 +314,7 @@ function Inner(props: ReportDialogProps) {
<StepOuter> <StepOuter>
<StepTitle <StepTitle
index={2} index={2}
title={_(msg`Select a sub-category`)} title={_(msg`Select a reason`)}
activeIndex1={state.activeStepIndex1} activeIndex1={state.activeStepIndex1}
/> />
{state.selectedOption ? ( {state.selectedOption ? (
@@ -323,77 +323,40 @@ function Inner(props: ReportDialogProps) {
<OptionCard option={state.selectedOption} /> <OptionCard option={state.selectedOption} />
</View> </View>
<Button <Button
testID="report:clearTopLevelOption" testID="report:clearReportOption"
label={_(msg`Change report reason`)} label={_(msg`Change report reason`)}
size="tiny" size="tiny"
variant="solid" variant="solid"
color="secondary" color="secondary"
shape="round" shape="round"
onPress={() => { onPress={() => {
dispatch({type: 'clearTopLevelOption'}) dispatch({type: 'clearCategory'})
}}> }}>
<ButtonIcon icon={X} /> <ButtonIcon icon={X} />
</Button> </Button>
</View> </View>
) : state.selectedTopLevelOption ? ( ) : state.selectedCategory ? (
<View style={[a.gap_sm]}> <View style={[a.gap_sm]}>
{getTopLevelReason(state.selectedTopLevelOption.key).options.map( {getCategory(state.selectedCategory.key).options.map(o => (
o => ( <OptionCard
<OptionCard key={o.reason}
key={o.reason} option={o}
option={o} onSelect={() => {
onSelect={() => { dispatch({type: 'selectOption', option: o})
dispatch({type: 'selectOption', option: o}) }}
}} />
/> ))}
),
)}
{['post', 'account'].includes(props.subject.type) && (
<Link
to={SUPPORT_PAGE}
label={_(
msg`Need to report a copyright violation, legal request, or regulatory compliance issue?`,
)}>
{({hovered, pressed}) => (
<View
style={[
a.flex_row,
a.align_center,
a.w_full,
a.px_md,
a.py_sm,
a.rounded_sm,
a.border,
hovered || pressed
? [t.atoms.border_contrast_high]
: [t.atoms.border_contrast_low],
]}>
<Text style={[a.flex_1, a.italic, a.leading_snug]}>
<Trans>
Need to report a copyright violation, legal request,
or regulatory compliance issue?
</Trans>
</Text>
<SquareArrowTopRight
size="sm"
fill={t.atoms.text.color}
/>
</View>
)}
</Link>
)}
</View> </View>
) : null} ) : null}
</StepOuter> </StepOuter>
<StepOuter> <StepOuter>
<StepTitle <StepTitle
index={2} index={3}
title={_(msg`Select moderation service`)} title={_(msg`Select moderation service`)}
activeIndex1={state.activeStepIndex1} activeIndex1={state.activeStepIndex1}
/> />
{state.activeStepIndex1 >= 2 && ( {state.activeStepIndex1 >= 3 && (
<> <>
{state.selectedLabeler ? ( {state.selectedLabeler ? (
<> <>
@@ -466,11 +429,11 @@ function Inner(props: ReportDialogProps) {
<StepOuter> <StepOuter>
<StepTitle <StepTitle
index={3} index={4}
title={_(msg`Submit report`)} title={_(msg`Submit report`)}
activeIndex1={state.activeStepIndex1} activeIndex1={state.activeStepIndex1}
/> />
{state.activeStepIndex1 === 3 && ( {state.activeStepIndex1 === 4 && (
<> <>
<View style={[a.pb_xs, a.gap_xs]}> <View style={[a.pb_xs, a.gap_xs]}>
<Text style={[a.leading_snug, a.pb_xs]}> <Text style={[a.leading_snug, a.pb_xs]}>
@@ -649,12 +612,12 @@ function StepTitle({
) )
} }
function TopLevelOptionCard({ function CategoryCard({
option, option,
onSelect, onSelect,
}: { }: {
option: TopLevelReportOption option: ReportOptionCategory
onSelect?: (option: TopLevelReportOption) => void onSelect?: (option: ReportOptionCategory) => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
+13 -10
View File
@@ -1,9 +1,12 @@
import {type AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api' import {type AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api'
import {TopLevelReportOption, type ReportOption} from './utils/useReportOptions' import {
type ReportOption,
type ReportOptionCategory,
} from './utils/useReportOptions'
export type ReportState = { export type ReportState = {
selectedTopLevelOption?: TopLevelReportOption selectedCategory?: ReportOptionCategory
selectedOption?: ReportOption selectedOption?: ReportOption
selectedLabeler?: AppBskyLabelerDefs.LabelerViewDetailed selectedLabeler?: AppBskyLabelerDefs.LabelerViewDetailed
details?: string details?: string
@@ -14,11 +17,11 @@ export type ReportState = {
export type ReportAction = export type ReportAction =
| { | {
type: 'selectTopLevelOption' type: 'selectCategory'
option: TopLevelReportOption option: ReportOptionCategory
} }
| { | {
type: 'clearTopLevelOption' type: 'clearCategory'
} }
| { | {
type: 'selectOption' type: 'selectOption'
@@ -50,7 +53,7 @@ export type ReportAction =
} }
export const initialState: ReportState = { export const initialState: ReportState = {
selectedTopLevelOption: undefined, selectedCategory: undefined,
selectedOption: undefined, selectedOption: undefined,
selectedLabeler: undefined, selectedLabeler: undefined,
details: undefined, details: undefined,
@@ -60,17 +63,17 @@ export const initialState: ReportState = {
export function reducer(state: ReportState, action: ReportAction): ReportState { export function reducer(state: ReportState, action: ReportAction): ReportState {
switch (action.type) { switch (action.type) {
case 'selectTopLevelOption': case 'selectCategory':
return { return {
...state, ...state,
selectedTopLevelOption: action.option, selectedCategory: action.option,
activeStepIndex1: 2, activeStepIndex1: 2,
detailsOpen: !!state.details, detailsOpen: !!state.details,
} }
case 'clearTopLevelOption': case 'clearCategory':
return { return {
...state, ...state,
selectedTopLevelOption: undefined, selectedCategory: undefined,
selectedOption: undefined, selectedOption: undefined,
selectedLabeler: undefined, selectedLabeler: undefined,
activeStepIndex1: 1, activeStepIndex1: 1,
@@ -11,7 +11,7 @@ export type TopLevelReason =
| 'civicIntegrity' | 'civicIntegrity'
| 'other' | 'other'
export interface TopLevelReportOption { export interface ReportOptionCategory {
key: TopLevelReason key: TopLevelReason
title: string title: string
description: string description: string
@@ -20,15 +20,15 @@ export interface TopLevelReportOption {
isOther?: boolean isOther?: boolean
} }
export interface TopLevelReportOptions { export interface ReportOptionCategories {
childSafety: TopLevelReportOption childSafety: ReportOptionCategory
violencePhysicalHarm: TopLevelReportOption violencePhysicalHarm: ReportOptionCategory
sexualAdultContent: TopLevelReportOption sexualAdultContent: ReportOptionCategory
harassmentHate: TopLevelReportOption harassmentHate: ReportOptionCategory
misleading: TopLevelReportOption misleading: ReportOptionCategory
ruleBreaking: TopLevelReportOption ruleBreaking: ReportOptionCategory
civicIntegrity: TopLevelReportOption civicIntegrity: ReportOptionCategory
other: TopLevelReportOption other: ReportOptionCategory
} }
export interface ReportOption { export interface ReportOption {
@@ -39,7 +39,7 @@ export interface ReportOption {
export function useReportOptions() { export function useReportOptions() {
const {_} = useLingui() const {_} = useLingui()
const reasons = { const categories = {
childSafety: { childSafety: {
key: 'childSafety', key: 'childSafety',
title: _(msg`Child Safety`), title: _(msg`Child Safety`),
@@ -297,11 +297,13 @@ export function useReportOptions() {
}, },
} }
const sortedReasons = Object.values(reasons).sort((a, b) => a.sort - b.sort) const sortedCategories = (
Object.values(categories) as ReportOptionCategory[]
).sort((a, b) => a.sort - b.sort)
const getTopLevelReason = (reasonName: TopLevelReason) => { const getCategory = (reasonName: TopLevelReason) => {
return reasons[reasonName] return categories[reasonName]
} }
return {reasons, sortedReasons, getTopLevelReason} return {categories, sortedCategories, getCategory}
} }