pl-fe: migrate admin dashboard, cleanup

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-01-03 18:47:15 +01:00
parent a2968004f7
commit 87e5a9490a
9 changed files with 251 additions and 209 deletions

View File

@ -1,4 +1,4 @@
import { Link, useNavigate } from '@tanstack/react-router';
import { Link, linkOptions, useNavigate, useRouter } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { useLayoutEffect, useRef, useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@ -40,17 +40,18 @@ const messages = defineMessages({
const InstanceFavicon: React.FC<IInstanceFavicon> = ({ account, disabled }) => {
const navigate = useNavigate();
const intl = useIntl();
const router = useRouter();
const handleClick: React.MouseEventHandler = (e) => {
e.stopPropagation();
if (disabled) return;
const timelineUrl = `/timeline/${account.domain}`;
const link = linkOptions({ to: '/timeline/$instance', params: { instance: account.domain } });
if (!(e.ctrlKey || e.metaKey)) {
navigate({ to: timelineUrl });
navigate(link);
} else {
window.open(timelineUrl, '_blank');
window.open(router.buildLocation(link).href, '_blank');
}
};

View File

@ -1,4 +1,4 @@
import { useNavigate } from '@tanstack/react-router';
import { linkOptions, useNavigate, useRouter } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { MouseEventHandler } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -33,17 +33,22 @@ interface IQuotedStatus {
const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) => {
const intl = useIntl();
const navigate = useNavigate();
const router = useRouter();
const handleExpandClick: MouseEventHandler<HTMLDivElement> = (e) => {
if (!status) return;
const account = status.account;
if (!compose && e.button === 0) {
const statusUrl = `/@${account.acct}/posts/${status.id}`;
const link = linkOptions({
to: '/@{$username}/posts/$statusId',
params: { username: account.acct, statusId: status.id },
});
if (!(e.ctrlKey || e.metaKey)) {
navigate({ to: '/@{$username}/posts/$statusId', params: { username: account.acct, statusId: status.id } });
navigate(link);
} else {
window.open(statusUrl, '_blank');
const url = router.buildLocation(link).href;
window.open(url, '_blank');
}
e.stopPropagation();
e.preventDefault();

View File

@ -1,4 +1,4 @@
import { Link, useNavigate } from '@tanstack/react-router';
import { Link, linkOptions, useNavigate, useRouter } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { useEffect, useMemo, useRef } from 'react';
import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl';
@ -76,6 +76,7 @@ const Status: React.FC<IStatus> = (props) => {
const intl = useIntl();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const router = useRouter();
const { toggleStatusesMediaHidden } = useStatusMetaActions();
const { openModal } = useModalsActions();
@ -92,7 +93,6 @@ const Status: React.FC<IStatus> = (props) => {
const { mutate: unreblogStatus } = useUnreblogStatus(actualStatus.id);
const isReblog = status.reblog_id;
const statusUrl = '/@{$username}/posts/$postId';
const group = actualStatus.group;
const filterResults = useMemo(() => [...status.filtered, ...actualStatus.filtered].filter(({ filter }) => filter.filter_action === 'warn'), [status.filtered, actualStatus.filtered]);
@ -111,14 +111,20 @@ const Status: React.FC<IStatus> = (props) => {
return;
}
const link = linkOptions({
to: '/@{$username}/posts/$statusId',
params: { username: actualStatus.account.acct, statusId: actualStatus.id },
});
if (!e || !(e.ctrlKey || e.metaKey)) {
if (onClick) {
onClick();
} else {
navigate({ to: '/@{$username}/posts/$statusId', params: { username: actualStatus.account.acct, statusId: actualStatus.id } });
navigate(link);
}
} else {
window.open(statusUrl, '_blank');
const url = router.buildLocation(link).href;
window.open(url, '_blank');
}
};

View File

@ -1,12 +1,19 @@
import React, { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
import { useAdminAccounts } from 'pl-fe/queries/admin/use-accounts';
import UnapprovedAccount from '../components/unapproved-account';
const messages = defineMessages({
heading: { id: 'column.admin.awaiting_approval', defaultMessage: 'Awaiting approval' },
});
const AwaitingApproval: React.FC = () => {
const intl = useIntl();
const { data, isPending, isFetching } = useAdminAccounts({
origin: 'local',
status: 'pending',
@ -19,19 +26,21 @@ const AwaitingApproval: React.FC = () => {
}, [data]);
return (
<ScrollableList
scrollKey='awaitingApproval'
isLoading={isFetching}
showLoading={isPending}
emptyMessageText={<FormattedMessage id='admin.awaiting_approval.empty_message' defaultMessage='There is nobody waiting for approval. When a new user signs up, you can review them here.' />}
listClassName='divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800'
>
{accountIds.map(id => (
<div key={id} className='px-5 py-4'>
<UnapprovedAccount accountId={id} />
</div>
))}
</ScrollableList>
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='awaitingApproval'
isLoading={isFetching}
showLoading={isPending}
emptyMessageText={<FormattedMessage id='admin.awaiting_approval.empty_message' defaultMessage='There is nobody waiting for approval. When a new user signs up, you can review them here.' />}
listClassName='divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800'
>
{accountIds.map(id => (
<div key={id} className='px-5 py-4'>
<UnapprovedAccount accountId={id} />
</div>
))}
</ScrollableList>
</Column>
);
};

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react';
import { FormattedMessage, FormattedNumber } from 'react-intl';
import { defineMessages, FormattedMessage, FormattedNumber, useIntl } from 'react-intl';
import List, { ListItem } from 'pl-fe/components/list';
import { CardTitle } from 'pl-fe/components/ui/card';
import Column from 'pl-fe/components/ui/column';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import { useFeatures } from 'pl-fe/hooks/use-features';
@ -18,7 +19,12 @@ import { Dimension } from '../components/dimension';
import RegistrationModePicker from '../components/registration-mode-picker';
import { Retention } from '../components/retention';
const messages = defineMessages({
heading: { id: 'column.admin.dashboard', defaultMessage: 'Dashboard' },
});
const Dashboard: React.FC = () => {
const intl = useIntl();
const instance = useInstance();
const features = useFeatures();
const { account } = useOwnAccount();
@ -44,199 +50,201 @@ const Dashboard: React.FC = () => {
if (!account) return null;
return (
<Stack space={6} className='mt-4'>
<DashCounters>
{features.mastodonAdminMetrics ? (
<Counter
measure='new_users'
startAt={monthAgo}
endAt={today}
to='/pl-fe/admin/users'
label={<FormattedMessage id='admin.counters.new_users' defaultMessage='new users' />}
/>
) : (
<DashCounter
to='/pl-fe/admin/users'
count={userCount}
label={<FormattedMessage id='admin.dashcounters.user_count_label' defaultMessage='total users' />}
/>
)}
{features.mastodonAdminMetrics ? (
<Counter
measure='active_users'
startAt={monthAgo}
endAt={today}
label={<FormattedMessage id='admin.counters.active_users' defaultMessage='active users' />}
/>
) : (
<DashCounter
count={mau}
label={<FormattedMessage id='admin.dashcounters.mau_label' defaultMessage='monthly active users' />}
/>
)}
{!features.mastodonAdminMetrics && (
<DashCounter
count={retention}
label={<FormattedMessage id='admin.dashcounters.retention_label' defaultMessage='user retention' />}
percent
/>
)}
{features.mastodonAdminMetrics && (
<>
<Column label={intl.formatMessage(messages.heading)}>
<Stack space={6} className='mt-4'>
<DashCounters>
{features.mastodonAdminMetrics ? (
<Counter
measure='interactions'
measure='new_users'
startAt={monthAgo}
endAt={today}
label={<FormattedMessage id='admin.counters.interactions' defaultMessage='interactions' />}
to='/pl-fe/admin/users'
label={<FormattedMessage id='admin.counters.new_users' defaultMessage='new users' />}
/>
) : (
<DashCounter
to='/pl-fe/admin/users'
count={userCount}
label={<FormattedMessage id='admin.dashcounters.user_count_label' defaultMessage='total users' />}
/>
)}
{features.mastodonAdminMetrics ? (
<Counter
measure='opened_reports'
measure='active_users'
startAt={monthAgo}
endAt={today}
to='/pl-fe/admin/reports'
label={<FormattedMessage id='admin.counters.opened_reports' defaultMessage='reports opened' />}
label={<FormattedMessage id='admin.counters.active_users' defaultMessage='active users' />}
/>
<Counter
measure='resolved_reports'
startAt={monthAgo}
endAt={today}
to='/pl-fe/admin/reports'
search={{ resolved: true }}
label={<FormattedMessage id='admin.counters.resolved_reports' defaultMessage='reports resolved' />}
) : (
<DashCounter
count={mau}
label={<FormattedMessage id='admin.dashcounters.mau_label' defaultMessage='monthly active users' />}
/>
</>
)}
<DashCounter
to='/timeline/local'
count={statusCount}
label={<FormattedMessage id='admin.dashcounters.status_count_label' defaultMessage='posts' />}
/>
<DashCounter
count={domainCount}
label={<FormattedMessage id='admin.dashcounters.domain_count_label' defaultMessage='peers' />}
/>
)}
{!features.mastodonAdminMetrics && (
<DashCounter
count={retention}
label={<FormattedMessage id='admin.dashcounters.retention_label' defaultMessage='user retention' />}
percent
/>
)}
{features.mastodonAdminMetrics && (
<>
<Counter
measure='interactions'
startAt={monthAgo}
endAt={today}
label={<FormattedMessage id='admin.counters.interactions' defaultMessage='interactions' />}
/>
<Counter
measure='opened_reports'
startAt={monthAgo}
endAt={today}
to='/pl-fe/admin/reports'
label={<FormattedMessage id='admin.counters.opened_reports' defaultMessage='reports opened' />}
/>
<Counter
measure='resolved_reports'
startAt={monthAgo}
endAt={today}
to='/pl-fe/admin/reports'
search={{ resolved: true }}
label={<FormattedMessage id='admin.counters.resolved_reports' defaultMessage='reports resolved' />}
/>
</>
)}
<DashCounter
to='/timeline/local'
count={statusCount}
label={<FormattedMessage id='admin.dashcounters.status_count_label' defaultMessage='posts' />}
/>
<DashCounter
count={domainCount}
label={<FormattedMessage id='admin.dashcounters.domain_count_label' defaultMessage='peers' />}
/>
<List>
<ListItem size='sm' to='/pl-fe/admin/reports' search={{ resolved: false }} label={<FormattedMessage id='admin.links.pending_reports' defaultMessage='{count, plural, one {{formattedCount} pending report} other {{formattedCount} pending reports}}' values={{ count: pendingReportsCount, formattedCount: <strong><FormattedNumber value={pendingReportsCount} /></strong> }} />} />
<ListItem size='sm' to='/pl-fe/admin/users' label={<FormattedMessage id='admin.links.pending_users' defaultMessage='{count, plural, one {{formattedCount} pending user} other {{formattedCount} pending users}}' values={{ count: awaitingApprovalCount, formattedCount: <strong><FormattedNumber value={awaitingApprovalCount} /></strong> }} />} />
{/* <ListItem size='sm' to='/pl-fe/admin' label={<FormattedMessage id='admin.links.pending_tags' defaultMessage='{count} pending tags' values={{ count: <strong>0</strong> }} />} />
<ListItem size='sm' to='/pl-fe/admin' label={<FormattedMessage id='admin.links.pending_appeals' defaultMessage='{count} pending appeals' values={{ count: <strong>0</strong> }} />} /> */}
</List>
{features.mastodonAdminMetrics && (
<>
<Dimension
dimension='sources'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.sources' defaultMessage='Sign-up sources' />}
/>
<Dimension
dimension='languages'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.top_languages' defaultMessage='Top active languages' />}
/>
<Dimension
dimension='servers'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.top_servers' defaultMessage='Top active servers' />}
/>
<Retention startAt={sixMonthsAgo} endAt={today} frequency='month' />
<Dimension
dimension='software_versions'
startAt={monthAgo}
endAt={today}
params={{ limit: 4 }}
label={<FormattedMessage id='admin.dimensions.software' defaultMessage='Software' />}
/>
<Dimension
dimension='space_usage'
startAt={monthAgo}
endAt={today}
params={{ limit: 3 }}
label={<FormattedMessage id='admin.dimensions.media_storage' defaultMessage='Media storage' />}
/>
</>
)}
</DashCounters>
<List>
<ListItem size='sm' to='/pl-fe/admin/reports' search={{ resolved: false }} label={<FormattedMessage id='admin.links.pending_reports' defaultMessage='{count, plural, one {{formattedCount} pending report} other {{formattedCount} pending reports}}' values={{ count: pendingReportsCount, formattedCount: <strong><FormattedNumber value={pendingReportsCount} /></strong> }} />} />
<ListItem size='sm' to='/pl-fe/admin/users' label={<FormattedMessage id='admin.links.pending_users' defaultMessage='{count, plural, one {{formattedCount} pending user} other {{formattedCount} pending users}}' values={{ count: awaitingApprovalCount, formattedCount: <strong><FormattedNumber value={awaitingApprovalCount} /></strong> }} />} />
{/* <ListItem size='sm' to='/pl-fe/admin' label={<FormattedMessage id='admin.links.pending_tags' defaultMessage='{count} pending tags' values={{ count: <strong>0</strong> }} />} />
<ListItem size='sm' to='/pl-fe/admin' label={<FormattedMessage id='admin.links.pending_appeals' defaultMessage='{count} pending appeals' values={{ count: <strong>0</strong> }} />} /> */}
{features.pleromaAdminAccounts && account.is_admin && (
<ListItem
to='/pl-fe/config'
label={<FormattedMessage id='navigation_bar.plfe_config' defaultMessage='Front-end configuration' />}
/>
)}
{features.pleromaAdminModerationLog && (
<ListItem
to='/pl-fe/admin/log'
label={<FormattedMessage id='column.admin.moderation_log' defaultMessage='Moderation log' />}
/>
)}
{features.pleromaAdminAnnouncements && (
<ListItem
to='/pl-fe/admin/announcements'
label={<FormattedMessage id='column.admin.announcements' defaultMessage='Announcements' />}
/>
)}
{features.adminRules && (
<ListItem
to='/pl-fe/admin/rules'
label={<FormattedMessage id='column.admin.rules' defaultMessage='Instance rules' />}
/>
)}
{features.domains && (
<ListItem
to='/pl-fe/admin/domains'
label={<FormattedMessage id='column.admin.domains' defaultMessage='Domains' />}
/>
)}
</List>
{features.mastodonAdminMetrics && (
{features.pleromaAdminAccounts && account.is_admin && (
<>
<Dimension
dimension='sources'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.sources' defaultMessage='Sign-up sources' />}
/>
<Dimension
dimension='languages'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.top_languages' defaultMessage='Top active languages' />}
/>
<Dimension
dimension='servers'
startAt={monthAgo}
endAt={today}
params={{ limit: 8 }}
label={<FormattedMessage id='admin.dimensions.top_servers' defaultMessage='Top active servers' />}
/>
<Retention startAt={sixMonthsAgo} endAt={today} frequency='month' />
<Dimension
dimension='software_versions'
startAt={monthAgo}
endAt={today}
params={{ limit: 4 }}
label={<FormattedMessage id='admin.dimensions.software' defaultMessage='Software' />}
/>
<Dimension
dimension='space_usage'
startAt={monthAgo}
endAt={today}
params={{ limit: 3 }}
label={<FormattedMessage id='admin.dimensions.media_storage' defaultMessage='Media storage' />}
<CardTitle
title={<FormattedMessage id='admin.dashboard.registration_mode_label' defaultMessage='Registrations' />}
/>
<RegistrationModePicker />
</>
)}
</DashCounters>
<List>
{features.pleromaAdminAccounts && account.is_admin && (
<ListItem
to='/pl-fe/config'
label={<FormattedMessage id='navigation_bar.plfe_config' defaultMessage='Front-end configuration' />}
/>
)}
<CardTitle
title={<FormattedMessage id='admin.dashwidgets.software_header' defaultMessage='Software' />}
/>
{features.pleromaAdminModerationLog && (
<ListItem
to='/pl-fe/admin/log'
label={<FormattedMessage id='column.admin.moderation_log' defaultMessage='Moderation log' />}
/>
)}
<List>
<ListItem label={<FormattedMessage id='admin.software.frontend' defaultMessage='Frontend' />}>
<a
href={sourceCode.ref ? `${sourceCode.url}/tree/${sourceCode.ref}` : sourceCode.url}
className='flex items-center space-x-1 truncate'
target='_blank'
>
<span>{sourceCode.displayName} {sourceCode.version}</span>
{features.pleromaAdminAnnouncements && (
<ListItem
to='/pl-fe/admin/announcements'
label={<FormattedMessage id='column.admin.announcements' defaultMessage='Announcements' />}
/>
)}
{features.adminRules && (
<ListItem
to='/pl-fe/admin/rules'
label={<FormattedMessage id='column.admin.rules' defaultMessage='Instance rules' />}
/>
)}
{features.domains && (
<ListItem
to='/pl-fe/admin/domains'
label={<FormattedMessage id='column.admin.domains' defaultMessage='Domains' />}
/>
)}
</List>
{features.pleromaAdminAccounts && account.is_admin && (
<>
<CardTitle
title={<FormattedMessage id='admin.dashboard.registration_mode_label' defaultMessage='Registrations' />}
/>
<RegistrationModePicker />
</>
)}
<CardTitle
title={<FormattedMessage id='admin.dashwidgets.software_header' defaultMessage='Software' />}
/>
<List>
<ListItem label={<FormattedMessage id='admin.software.frontend' defaultMessage='Frontend' />}>
<a
href={sourceCode.ref ? `${sourceCode.url}/tree/${sourceCode.ref}` : sourceCode.url}
className='flex items-center space-x-1 truncate'
target='_blank'
>
<span>{sourceCode.displayName} {sourceCode.version}</span>
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/arrow-square-out.svg')}
/>
</a>
</ListItem>
{!features.mastodonAdminMetrics && (
<ListItem label={<FormattedMessage id='admin.software.backend' defaultMessage='Backend' />}>
<span>{v.software + (v.build ? `+${v.build}` : '')} {v.version}</span>
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/arrow-square-out.svg')}
/>
</a>
</ListItem>
)}
</List>
</Stack>
{!features.mastodonAdminMetrics && (
<ListItem label={<FormattedMessage id='admin.software.backend' defaultMessage='Backend' />}>
<span>{v.software + (v.build ? `+${v.build}` : '')} {v.version}</span>
</ListItem>
)}
</List>
</Stack>
</Column>
);
};

View File

@ -1,9 +1,10 @@
import { useNavigate } from '@tanstack/react-router';
import React from 'react';
import { FormattedList, FormattedMessage } from 'react-intl';
import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
import HStack from 'pl-fe/components/ui/hstack';
import IconButton from 'pl-fe/components/ui/icon-button';
import Text from 'pl-fe/components/ui/text';
@ -12,7 +13,13 @@ import { useReports } from 'pl-fe/queries/admin/use-reports';
import Report from '../components/report';
const messages = defineMessages({
heading: { id: 'column.admin.reports', defaultMessage: 'Reports' },
});
const Reports: React.FC = () => {
const intl = useIntl();
const { resolved, account_id: accountId, target_account_id: targetAccountId } = adminReportsRoute.useSearch();
const navigate = useNavigate({ from: adminReportsRoute.fullPath });
@ -28,7 +35,7 @@ const Reports: React.FC = () => {
const handleUnsetAccounts = () => navigate({ search: (prev) => ({ resolved: prev.resolved }) });
return (
<>
<Column label={intl.formatMessage(messages.heading)}>
{(accountId || targetAccountId) && (
<HStack className='border-b border-solid border-gray-200 p-2 pb-4 dark:border-gray-800' alignItems='center' space={2}>
<IconButton iconClassName='h-5 w-5' src={require('@phosphor-icons/core/regular/x.svg')} onClick={handleUnsetAccounts} />
@ -64,7 +71,7 @@ const Reports: React.FC = () => {
>
{reportIds.map(report => report && <Report id={report} key={report} />)}
</ScrollableList>
</>
</Column>
);
};

View File

@ -144,6 +144,8 @@ import {
UserIndex,
WrenchedTimeline,
EditEvent,
Reports,
AwaitingApproval,
} from './util/async-components';
import type { Features } from 'pl-api';
@ -992,10 +994,10 @@ export const adminAccountRoute = createRoute({
}),
});
export const adminApprovalRoute = createRoute({
export const adminAwaitingApprovalRoute = createRoute({
getParentRoute: () => layouts.admin,
path: '/pl-fe/admin/approval',
component: Dashboard,
component: AwaitingApproval,
beforeLoad: requireAuthMiddleware(({ context: { isAdmin } }) => {
if (!isAdmin) throw notFound();
}),
@ -1004,7 +1006,7 @@ export const adminApprovalRoute = createRoute({
export const adminReportsRoute = createRoute({
getParentRoute: () => layouts.admin,
path: '/pl-fe/admin/reports',
component: Dashboard,
component: Reports,
validateSearch: v.object({
resolved: v.optional(v.boolean(), false),
account_id: v.optional(v.string()),
@ -1187,7 +1189,7 @@ const routeTree = rootRoute.addChildren([
layouts.admin.addChildren([
adminDashboardRoute,
adminAccountRoute,
adminApprovalRoute,
adminAwaitingApprovalRoute,
adminReportsRoute,
adminReportRoute,
adminLogRoute,

View File

@ -8,6 +8,7 @@ export const AdminAccount = lazy(() => import('pl-fe/pages/dashboard/account'));
export const Aliases = lazy(() => import('pl-fe/pages/settings/aliases'));
export const Announcements = lazy(() => import('pl-fe/pages/dashboard/announcements'));
export const AuthTokenList = lazy(() => import('pl-fe/pages/settings/auth-token-list'));
export const AwaitingApproval = lazy(() => import('pl-fe/features/admin/tabs/awaiting-approval'));
export const Backups = lazy(() => import('pl-fe/pages/settings/backups'));
export const Blocks = lazy(() => import('pl-fe/pages/settings/blocks'));
export const BookmarkFolders = lazy(() => import('pl-fe/pages/status-lists/bookmark-folders'));
@ -23,7 +24,7 @@ export const EditEvent = lazy(() => import('pl-fe/pages/statuses/compose-event')
export const Conversations = lazy(() => import('pl-fe/pages/status-lists/conversations'));
export const CreateApp = lazy(() => import('pl-fe/pages/developers/create-app'));
export const CryptoDonate = lazy(() => import('pl-fe/pages/utils/crypto-donate'));
export const Dashboard = lazy(() => import('pl-fe/pages/dashboard/dashboard'));
export const Dashboard = lazy(() => import('pl-fe/features/admin/tabs/dashboard'));
export const DeleteAccount = lazy(() => import('pl-fe/pages/settings/delete-account'));
export const Developers = lazy(() => import('pl-fe/pages/developers/developers'));
export const Directory = lazy(() => import('pl-fe/pages/account-lists/directory'));
@ -82,6 +83,7 @@ export const PlFeConfig = lazy(() => import('pl-fe/pages/dashboard/pl-fe-config'
export const PublicTimeline = lazy(() => import('pl-fe/pages/timelines/public-timeline'));
export const Quotes = lazy(() => import('pl-fe/pages/status-lists/quotes'));
export const Report = lazy(() => import('pl-fe/pages/dashboard/report'));
export const Reports = lazy(() => import('pl-fe/features/admin/tabs/reports'));
export const RegisterInvite = lazy(() => import('pl-fe/pages/auth/register-with-invite'));
export const RegistrationPage = lazy(() => import('pl-fe/pages/auth/registration'));
export const Relays = lazy(() => import('pl-fe/pages/dashboard/relays'));

View File

@ -368,6 +368,7 @@
"circles.subheading": "Your circles",
"column.admin.account": "Moderate @{acct}",
"column.admin.announcements": "Announcements",
"column.admin.awaiting_approval": "Awaiting approval",
"column.admin.create_announcement": "Create announcement",
"column.admin.create_domain": "Create domain",
"column.admin.create_rule": "Create rule",
@ -378,6 +379,7 @@
"column.admin.edit_rule": "Edit rule",
"column.admin.moderation_log": "Moderation log",
"column.admin.relays": "Instance relays",
"column.admin.reports": "Reports",
"column.admin.reports.filter_message": "You are displaying reports {query}.",
"column.admin.reports.filter_message.account": "from @{acct}",
"column.admin.reports.filter_message.target_account": "targeting @{acct}",