From d820c0883d5557f011b4de8fafa1ff9f68b0d5de Mon Sep 17 00:00:00 2001
From: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
Date: Mon, 26 Aug 2024 18:42:46 +0200
Subject: [PATCH] Add quick links to Administration and Moderation Reports from
 Web UI (#24838)

---
 .../features/getting_started/index.jsx        | 14 +++++++++++++-
 .../ui/components/navigation_panel.jsx        | 11 +++++++++--
 app/javascript/mastodon/locales/en.json       |  2 ++
 app/javascript/mastodon/permissions.ts        | 19 +++++++++++++++++++
 4 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx
index 628bbe62b..8d26115df 100644
--- a/app/javascript/mastodon/features/getting_started/index.jsx
+++ b/app/javascript/mastodon/features/getting_started/index.jsx
@@ -12,9 +12,11 @@ import { connect } from 'react-redux';
 import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
 import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
 import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
+import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
 import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
 import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
+import AdministrationIcon from '@/material-icons/400-24px/manufacturing.svg?react';
 import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
 import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
 import PublicIcon from '@/material-icons/400-24px/public.svg?react';
@@ -25,6 +27,7 @@ import Column from 'mastodon/components/column';
 import ColumnHeader from 'mastodon/components/column_header';
 import LinkFooter from 'mastodon/features/ui/components/link_footer';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
+import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
 
 import { me, showTrends } from '../../initial_state';
 import { NavigationBar } from '../compose/components/navigation_bar';
@@ -43,6 +46,8 @@ const messages = defineMessages({
   direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
   bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  administration: { id: 'navigation_bar.administration', defaultMessage: 'Administration' },
+  moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
@@ -99,7 +104,7 @@ class GettingStarted extends ImmutablePureComponent {
 
   render () {
     const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
-    const { signedIn } = this.props.identity;
+    const { signedIn, permissions } = this.props.identity;
 
     const navItems = [];
 
@@ -136,6 +141,13 @@ class GettingStarted extends ImmutablePureComponent {
         <ColumnSubheading key='header-settings' text={intl.formatMessage(messages.settings_subheading)} />,
         <ColumnLink key='preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />,
       );
+
+      if (canManageReports(permissions)) {
+        navItems.push(<ColumnLink key='moderation' href='/admin/reports' icon='flag' iconComponent={ModerationIcon} text={intl.formatMessage(messages.moderation)} />);
+      }
+      if (canViewAdminDashboard(permissions)) {
+        navItems.push(<ColumnLink key='administration' href='/admin/dashboard' icon='tachometer' iconComponent={AdministrationIcon} text={intl.formatMessage(messages.administration)} />);
+      }
     }
 
     return (
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index 2648923bf..d30ea0ac5 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -7,16 +7,17 @@ import { Link } from 'react-router-dom';
 
 import { useSelector, useDispatch } from 'react-redux';
 
-
 import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
 import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
 import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
 import ExploreActiveIcon from '@/material-icons/400-24px/explore-fill.svg?react';
 import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
+import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
 import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
 import HomeIcon from '@/material-icons/400-24px/home.svg?react';
 import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
 import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
+import AdministrationIcon from '@/material-icons/400-24px/manufacturing.svg?react';
 import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
 import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
 import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
@@ -34,6 +35,7 @@ import { NavigationPortal } from 'mastodon/components/navigation_portal';
 import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
 import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
 import { transientSingleColumn } from 'mastodon/is_mobile';
+import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
 import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
 
 import ColumnLink from './column_link';
@@ -51,6 +53,8 @@ const messages = defineMessages({
   bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
   lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  administration: { id: 'navigation_bar.administration', defaultMessage: 'Administration' },
+  moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
   followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
   about: { id: 'navigation_bar.about', defaultMessage: 'About' },
   search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
@@ -114,7 +118,7 @@ class NavigationPanel extends Component {
 
   render () {
     const { intl } = this.props;
-    const { signedIn, disabledAccountId } = this.props.identity;
+    const { signedIn, disabledAccountId, permissions } = this.props.identity;
 
     let banner = undefined;
 
@@ -176,6 +180,9 @@ class NavigationPanel extends Component {
             <hr />
 
             <ColumnLink transparent href='/settings/preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} />
+
+            {canManageReports(permissions) && <ColumnLink transparent href='/admin/reports' icon='flag' iconComponent={ModerationIcon} text={intl.formatMessage(messages.moderation)} />}
+            {canViewAdminDashboard(permissions) && <ColumnLink transparent href='/admin/dashboard' icon='tachometer' iconComponent={AdministrationIcon} text={intl.formatMessage(messages.administration)} />}
           </>
         )}
 
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 88920431f..343af4042 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -467,6 +467,7 @@
   "mute_modal.you_wont_see_mentions": "You won't see posts that mention them.",
   "mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.",
   "navigation_bar.about": "About",
+  "navigation_bar.administration": "Administration",
   "navigation_bar.advanced_interface": "Open in advanced web interface",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.bookmarks": "Bookmarks",
@@ -483,6 +484,7 @@
   "navigation_bar.follows_and_followers": "Follows and followers",
   "navigation_bar.lists": "Lists",
   "navigation_bar.logout": "Logout",
+  "navigation_bar.moderation": "Moderation",
   "navigation_bar.mutes": "Muted users",
   "navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.",
   "navigation_bar.personal": "Personal",
diff --git a/app/javascript/mastodon/permissions.ts b/app/javascript/mastodon/permissions.ts
index b583535c0..8f015610e 100644
--- a/app/javascript/mastodon/permissions.ts
+++ b/app/javascript/mastodon/permissions.ts
@@ -1,4 +1,23 @@
 export const PERMISSION_INVITE_USERS = 0x0000000000010000;
 export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
 export const PERMISSION_MANAGE_FEDERATION = 0x0000000000000020;
+
 export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
+export const PERMISSION_VIEW_DASHBOARD = 0x0000000000000008;
+
+// These helpers don't quite align with the names/categories in UserRole,
+// but are likely "good enough" for the use cases at present.
+//
+// See: https://docs.joinmastodon.org/entities/Role/#permission-flags
+
+export function canViewAdminDashboard(permissions: number) {
+  return (
+    (permissions & PERMISSION_VIEW_DASHBOARD) === PERMISSION_VIEW_DASHBOARD
+  );
+}
+
+export function canManageReports(permissions: number) {
+  return (
+    (permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS
+  );
+}