diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx index 6f3307723..4e2121475 100644 --- a/packages/pl-fe/src/components/account.tsx +++ b/packages/pl-fe/src/components/account.tsx @@ -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 = ({ 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'); } }; diff --git a/packages/pl-fe/src/components/quoted-status.tsx b/packages/pl-fe/src/components/quoted-status.tsx index 257af823d..157cd67fa 100644 --- a/packages/pl-fe/src/components/quoted-status.tsx +++ b/packages/pl-fe/src/components/quoted-status.tsx @@ -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 = ({ status, onCancel, compose }) => { const intl = useIntl(); const navigate = useNavigate(); + const router = useRouter(); const handleExpandClick: MouseEventHandler = (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(); diff --git a/packages/pl-fe/src/components/status.tsx b/packages/pl-fe/src/components/status.tsx index b2fce589f..b0eae023d 100644 --- a/packages/pl-fe/src/components/status.tsx +++ b/packages/pl-fe/src/components/status.tsx @@ -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 = (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 = (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 = (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'); } }; diff --git a/packages/pl-fe/src/features/admin/tabs/awaiting-approval.tsx b/packages/pl-fe/src/features/admin/tabs/awaiting-approval.tsx index bf32af292..87f7461c6 100644 --- a/packages/pl-fe/src/features/admin/tabs/awaiting-approval.tsx +++ b/packages/pl-fe/src/features/admin/tabs/awaiting-approval.tsx @@ -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 ( - } - listClassName='divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800' - > - {accountIds.map(id => ( -
- -
- ))} -
+ + } + listClassName='divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800' + > + {accountIds.map(id => ( +
+ +
+ ))} +
+
); }; diff --git a/packages/pl-fe/src/features/admin/tabs/dashboard.tsx b/packages/pl-fe/src/features/admin/tabs/dashboard.tsx index 13b390f28..c625de278 100644 --- a/packages/pl-fe/src/features/admin/tabs/dashboard.tsx +++ b/packages/pl-fe/src/features/admin/tabs/dashboard.tsx @@ -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 ( - - - {features.mastodonAdminMetrics ? ( - } - /> - ) : ( - } - /> - )} - {features.mastodonAdminMetrics ? ( - } - /> - ) : ( - } - /> - )} - {!features.mastodonAdminMetrics && ( - } - percent - /> - )} - {features.mastodonAdminMetrics && ( - <> + + + + {features.mastodonAdminMetrics ? ( } + to='/pl-fe/admin/users' + label={} /> + ) : ( + } + /> + )} + {features.mastodonAdminMetrics ? ( } + label={} /> - } + ) : ( + } /> - - )} - } - /> - } - /> + )} + {!features.mastodonAdminMetrics && ( + } + percent + /> + )} + {features.mastodonAdminMetrics && ( + <> + } + /> + } + /> + } + /> + + )} + } + /> + } + /> + + }} />} /> + }} />} /> + {/* 0 }} />} /> + 0 }} />} /> */} + + {features.mastodonAdminMetrics && ( + <> + } + /> + } + /> + } + /> + + } + /> + } + /> + + )} + + - }} />} /> - }} />} /> - {/* 0 }} />} /> - 0 }} />} /> */} + {features.pleromaAdminAccounts && account.is_admin && ( + } + /> + )} + + {features.pleromaAdminModerationLog && ( + } + /> + )} + + {features.pleromaAdminAnnouncements && ( + } + /> + )} + + {features.adminRules && ( + } + /> + )} + + {features.domains && ( + } + /> + )} - {features.mastodonAdminMetrics && ( + + {features.pleromaAdminAccounts && account.is_admin && ( <> - } - /> - } - /> - } - /> - - } - /> - } + } /> + + )} - - - {features.pleromaAdminAccounts && account.is_admin && ( - } - /> - )} + } + /> - {features.pleromaAdminModerationLog && ( - } - /> - )} + + }> + + {sourceCode.displayName} {sourceCode.version} - {features.pleromaAdminAnnouncements && ( - } - /> - )} - - {features.adminRules && ( - } - /> - )} - - {features.domains && ( - } - /> - )} - - - {features.pleromaAdminAccounts && account.is_admin && ( - <> - } - /> - - - - )} - - } - /> - - - }> - - {sourceCode.displayName} {sourceCode.version} - - - - - - {!features.mastodonAdminMetrics && ( - }> - {v.software + (v.build ? `+${v.build}` : '')} {v.version} + + - )} - - + + {!features.mastodonAdminMetrics && ( + }> + {v.software + (v.build ? `+${v.build}` : '')} {v.version} + + )} + + + ); }; diff --git a/packages/pl-fe/src/features/admin/tabs/reports.tsx b/packages/pl-fe/src/features/admin/tabs/reports.tsx index 5a1209cf6..3e032ea08 100644 --- a/packages/pl-fe/src/features/admin/tabs/reports.tsx +++ b/packages/pl-fe/src/features/admin/tabs/reports.tsx @@ -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 ( - <> + {(accountId || targetAccountId) && ( @@ -64,7 +71,7 @@ const Reports: React.FC = () => { > {reportIds.map(report => report && )} - + ); }; diff --git a/packages/pl-fe/src/features/ui/router.tsx b/packages/pl-fe/src/features/ui/router.tsx index 4ecb5f538..ccaad7b75 100644 --- a/packages/pl-fe/src/features/ui/router.tsx +++ b/packages/pl-fe/src/features/ui/router.tsx @@ -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, diff --git a/packages/pl-fe/src/features/ui/util/async-components.ts b/packages/pl-fe/src/features/ui/util/async-components.ts index cbe2357fc..d39375fbf 100644 --- a/packages/pl-fe/src/features/ui/util/async-components.ts +++ b/packages/pl-fe/src/features/ui/util/async-components.ts @@ -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')); diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index 71cbd01df..d11aab655 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -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}",