From 23d8fcf5eab8c44418f917e8a0f5cff22cab9407 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:20:46 -0700 Subject: [PATCH] Create mod inbox report details screen (#11641) --- src/Navigation.tsx | 8 +- src/lib/routes/types.ts | 1 + src/routes.ts | 1 + src/screens/Moderation/index.tsx | 8 +- src/screens/ModerationInbox/Details.tsx | 100 ++++++++++++++++++ .../components/ActionSummaryText.tsx | 22 ++++ .../components/ContentBlock.tsx | 28 +++++ .../ModerationInbox/components/ReportRow.tsx | 16 ++- .../components/SubjectPreview.tsx | 57 ++++++++++ .../ModerationInbox/components/Timeline.tsx | 34 ++++++ .../components/TimelineItem.tsx | 72 +++++++++++++ src/screens/ModerationInbox/index.tsx | 6 ++ 12 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 src/screens/ModerationInbox/Details.tsx create mode 100644 src/screens/ModerationInbox/components/ActionSummaryText.tsx create mode 100644 src/screens/ModerationInbox/components/ContentBlock.tsx create mode 100644 src/screens/ModerationInbox/components/SubjectPreview.tsx create mode 100644 src/screens/ModerationInbox/components/Timeline.tsx create mode 100644 src/screens/ModerationInbox/components/TimelineItem.tsx diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 5e9da43c11..b0034aaff1 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -80,6 +80,7 @@ import {FindContactsFlowScreen} from '#/screens/FindContactsFlowScreen' import HashtagScreen from '#/screens/Hashtag' import {LogScreen} from '#/screens/Log' import {MessagesScreen} from '#/screens/Messages/ChatList' +import {renderMessagesSplitViewLayout} from '#/screens/Messages/components/splitView/MessagesSplitViewLayout' import {MessagesConversationScreen} from '#/screens/Messages/Conversation' import {MessagesConversationSettingsScreen} from '#/screens/Messages/ConversationSettings' import {MessagesInboxScreen} from '#/screens/Messages/Inbox' @@ -88,6 +89,7 @@ import {MessagesSettingsScreen} from '#/screens/Messages/Settings' import {ModerationScreen} from '#/screens/Moderation' import {Screen as ModerationVerificationSettings} from '#/screens/Moderation/VerificationSettings' import {ModerationInboxScreen} from '#/screens/ModerationInbox' +import {ModerationInboxReportDetailsScreen} from '#/screens/ModerationInbox/Details' import {ModerationInboxSettingsScreen} from '#/screens/ModerationInbox/Settings' import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings' import {NotificationsActivityListScreen} from '#/screens/Notifications/ActivityList' @@ -141,7 +143,6 @@ import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env' import {InviteScannerScreen} from '#/features/inviteFriends' import {router} from '#/routes' import {Referrer} from '../modules/expo-bluesky-swiss-army' -import {renderMessagesSplitViewLayout} from './screens/Messages/components/splitView/MessagesSplitViewLayout' const navigationRef = createNavigationContainerRef() @@ -190,6 +191,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { getComponent={() => ModerationInboxSettingsScreen} options={{title: title(msg`Mod inbox settings`), requireAuth: true}} /> + ModerationInboxReportDetailsScreen} + options={{title: title(msg`Your report`), requireAuth: true}} + /> ModerationModlistsScreen} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index d5a56b4779..3ff4f0fc84 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -25,6 +25,7 @@ export type CommonNavigatorParams = { Moderation: undefined ModerationInbox: undefined ModerationInboxSettings: undefined + ModerationInboxReportDetails: undefined ModerationModlists: undefined ModerationMutedAccounts: undefined ModerationBlockedAccounts: undefined diff --git a/src/routes.ts b/src/routes.ts index c018bb9caa..eae7c7aa7d 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -19,6 +19,7 @@ export const router = new Router({ Moderation: '/moderation', ModerationInbox: '/moderation/inbox', ModerationInboxSettings: '/moderation/inbox/settings', + ModerationInboxReportDetails: '/moderation/inbox/report/details', ModerationModlists: '/moderation/modlists', ModerationMutedAccounts: '/moderation/muted-accounts', ModerationBlockedAccounts: '/moderation/blocked-accounts', diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 65a971789b..2049c3dc90 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -1,6 +1,7 @@ import {Fragment, useCallback} from 'react' import {Linking, View} from 'react-native' import {LABELS} from '@bsky/sdk/moderation' +import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {getLabelingServiceTitle, isAppLabeler} from '#/lib/moderation' @@ -171,11 +172,14 @@ function SubItem({ ]}> {badge >= UNREAD_NOTIFICATION_CAP ? l({ - message: `${i18n.number(UNREAD_NOTIFICATION_CAP - 1)}+`, + message: `${i18n.number(UNREAD_NOTIFICATION_CAP - 1)}+ updates`, comment: 'Displayed when the number of notifications exceeds the cap – for example, 99+ notifications', }) - : i18n.number(badge)} + : plural(badge, { + one: '# update', + other: '# updates', + })} ) : null} diff --git a/src/screens/ModerationInbox/Details.tsx b/src/screens/ModerationInbox/Details.tsx new file mode 100644 index 0000000000..2bd1dfeba0 --- /dev/null +++ b/src/screens/ModerationInbox/Details.tsx @@ -0,0 +1,100 @@ +import {View} from 'react-native' +import {Trans, useLingui} from '@lingui/react/macro' + +import {NotFoundScreen} from '#/view/screens/NotFound' +import {atoms as a, useTheme} from '#/alf' +import * as Layout from '#/components/Layout' +import {InlineLinkText} from '#/components/Link' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' +import {ActionSummaryText} from './components/ActionSummaryText' +import {ContentBlock} from './components/ContentBlock' +import {SubjectPreview} from './components/SubjectPreview' +import {Timeline} from './components/Timeline' + +export function ModerationInboxReportDetailsScreen() { + const t = useTheme() + const {t: l} = useLingui() + const ax = useAnalytics() + + const isEnabled = ax.features.enabled(ax.features.ModerationInboxEnable) + + if (!isEnabled) { + return + } + + // TODO This is a placeholder value. -dsb + const note = 'Lorem ipsum dolor sit amet.' + + return ( + + + + + + Your report + + + Bluesky Moderation Service + + + + + + + + + + The post you reported has been restored after another review. If + you’d rather not see it, you can mute or block the account. Read + our{' '} + + Community Guidelines + + . + + + + + + + + + Reason: + + + Spam + + + + + + Your note: + + + “{note}” + + + + + + + + + ) +} diff --git a/src/screens/ModerationInbox/components/ActionSummaryText.tsx b/src/screens/ModerationInbox/components/ActionSummaryText.tsx new file mode 100644 index 0000000000..a1f077541d --- /dev/null +++ b/src/screens/ModerationInbox/components/ActionSummaryText.tsx @@ -0,0 +1,22 @@ +import {View} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {H1, Text} from '#/components/Typography' + +export function ActionSummaryText({ + header, + children, +}: React.PropsWithChildren<{header: string}>) { + const t = useTheme() + + // Cleaner to call this ActionSummaryText and suppress lint in one location vs every call site. + // oxlint-disable-next-line bsky-internal/avoid-unwrapped-text + return ( + +

+ {header} +

+ {children} +
+ ) +} diff --git a/src/screens/ModerationInbox/components/ContentBlock.tsx b/src/screens/ModerationInbox/components/ContentBlock.tsx new file mode 100644 index 0000000000..a6ec96fd67 --- /dev/null +++ b/src/screens/ModerationInbox/components/ContentBlock.tsx @@ -0,0 +1,28 @@ +import {View} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {H2} from '#/components/Typography' + +export function ContentBlock({ + header, + children, +}: React.PropsWithChildren<{header: string}>) { + const t = useTheme() + + return ( + +

+ {header} +

+ {children} +
+ ) +} diff --git a/src/screens/ModerationInbox/components/ReportRow.tsx b/src/screens/ModerationInbox/components/ReportRow.tsx index 8adf8b75f5..55d4607391 100644 --- a/src/screens/ModerationInbox/components/ReportRow.tsx +++ b/src/screens/ModerationInbox/components/ReportRow.tsx @@ -4,26 +4,36 @@ import {isBefore, subYears} from 'date-fns' import {atoms as a, useTheme} from '#/alf' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' +import {Link} from '#/components/Link' import {Text} from '#/components/Typography' export function ReportRow({ subject, action, date, + to, unread, }: { subject: string action: string date: Date + to: React.ComponentProps['to'] unread?: boolean }) { const t = useTheme() - const {i18n} = useLingui() + const {i18n, t: l} = useLingui() const shouldShowYear = isBefore(date, subYears(new Date(), 1)) return ( - - + ) } diff --git a/src/screens/ModerationInbox/components/SubjectPreview.tsx b/src/screens/ModerationInbox/components/SubjectPreview.tsx new file mode 100644 index 0000000000..c55066fb12 --- /dev/null +++ b/src/screens/ModerationInbox/components/SubjectPreview.tsx @@ -0,0 +1,57 @@ +import {View} from 'react-native' +import {Trans, useLingui} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' + +// TODO These are placeholder values. -dsb +const POSTED_AT = new Date(2026, 7, 8) + +export function SubjectPreview() { + const t = useTheme() + const {i18n} = useLingui() + + // TODO These are placeholder values. -dsb + const handle = '@deleteme01.bsky.social' + const post = '@deleteme01.bsky.social lorem ipsum dolor sit amet…' + + return ( + + + Reply to {handle} + + + “{post}” + + + + Posted{' '} + {i18n.date(POSTED_AT, { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + + + {/* TODO This is a placeholder for a button. -dsb */} + + + Open post + + + + + ) +} diff --git a/src/screens/ModerationInbox/components/Timeline.tsx b/src/screens/ModerationInbox/components/Timeline.tsx new file mode 100644 index 0000000000..aee23d8ea7 --- /dev/null +++ b/src/screens/ModerationInbox/components/Timeline.tsx @@ -0,0 +1,34 @@ +import {View} from 'react-native' +import {useLingui} from '@lingui/react/macro' + +import {ContentBlock} from './ContentBlock' +import {TimelineItem} from './TimelineItem' + +export function Timeline({items}: {items: {title: string; date?: Date}[]}) { + const {i18n, t: l} = useLingui() + + return ( + + + {items.map(({title, date}, index) => ( + + ))} + + + ) +} diff --git a/src/screens/ModerationInbox/components/TimelineItem.tsx b/src/screens/ModerationInbox/components/TimelineItem.tsx new file mode 100644 index 0000000000..7c126ac03c --- /dev/null +++ b/src/screens/ModerationInbox/components/TimelineItem.tsx @@ -0,0 +1,72 @@ +import {View} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' + +export function TimelineItem({ + title, + date, + last, +}: { + title: string + date?: string + last?: boolean +}) { + const t = useTheme() + + return ( + + + {last ? ( + + ) : ( + + )} + {last ? undefined : ( + + )} + + + {title} + {date ? ( + + {date} + + ) : undefined} + + + ) +} diff --git a/src/screens/ModerationInbox/index.tsx b/src/screens/ModerationInbox/index.tsx index 7bfb6671fd..e1f2f16daf 100644 --- a/src/screens/ModerationInbox/index.tsx +++ b/src/screens/ModerationInbox/index.tsx @@ -115,6 +115,7 @@ function YourReports() { })} action={l`Awaiting review`} date={new Date()} + to="/moderation/inbox/report/details" unread /> ) @@ -194,6 +197,7 @@ function YourAccount() { })} action={l`Violates community guideline: ${guideline}`} date={new Date()} + to="/moderation/inbox/report/details" unread /> )