diff --git a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx index ebc0c2471..f421b472d 100644 --- a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx +++ b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx @@ -1,4 +1,4 @@ -import { useNavigate, type LinkProps } from '@tanstack/react-router'; +import { useNavigate, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; @@ -26,11 +26,7 @@ type MenuItem = { items?: Menu; onSelectFile?: (files: FileList) => void; accept?: string; -} & ({ - to: LinkProps['to']; - params?: LinkProps['params']; - search?: LinkProps['search']; -} | { to?: undefined }); +} & (LinkOptions | { to?: undefined }); interface IDropdownMenuItem { index: number; diff --git a/packages/pl-fe/src/components/dropdown-navigation.tsx b/packages/pl-fe/src/components/dropdown-navigation.tsx index 743fa2853..83a017dea 100644 --- a/packages/pl-fe/src/components/dropdown-navigation.tsx +++ b/packages/pl-fe/src/components/dropdown-navigation.tsx @@ -1,6 +1,6 @@ /* eslint-disable jsx-a11y/interactive-supports-focus */ import { useInfiniteQuery } from '@tanstack/react-query'; -import { Link, type LinkProps } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -29,14 +29,14 @@ import sourceCode from 'pl-fe/utils/code'; import type { Account as AccountEntity } from 'pl-api'; -interface IDropdownNavigationLink extends Partial> { +interface IDropdownNavigationLink extends Partial { href?: string; icon: string; text: string | JSX.Element; onClick: React.EventHandler; } -const DropdownNavigationLink: React.FC = React.memo(({ href, to, params, icon, text, onClick }) => { +const DropdownNavigationLink: React.FC = React.memo(({ href, to, icon, text, onClick, ...rest }) => { const body = ( <>
@@ -49,7 +49,7 @@ const DropdownNavigationLink: React.FC = React.memo(({ if (to) { return ( - + {body} ); diff --git a/packages/pl-fe/src/components/list.tsx b/packages/pl-fe/src/components/list.tsx index 6e3dc084d..70272007a 100644 --- a/packages/pl-fe/src/components/list.tsx +++ b/packages/pl-fe/src/components/list.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { useState } from 'react'; @@ -15,19 +15,18 @@ const List: React.FC = ({ children }) => (
{children}
); -interface IListItem { +type IListItem = { className?: string; label: React.ReactNode; hint?: React.ReactNode; - to?: string; href?: string; onClick?(): void; isSelected?: boolean; children?: React.ReactNode; size?: 'sm' | 'md'; -} +} & (LinkOptions | {}); -const ListItem: React.FC = ({ className, label, hint, children, to, href, onClick, isSelected, size = 'md' }) => { +const ListItem: React.FC = ({ className, label, hint, children, href, onClick, isSelected, size = 'md', ...rest }) => { const [domId] = useState(`list-group-${crypto.randomUUID()}`); const onKeyDown = (e: React.KeyboardEvent) => { @@ -36,7 +35,7 @@ const ListItem: React.FC = ({ className, label, hint, children, to, h } }; - const LabelComp = to || href || onClick ? 'span' : 'label'; + const LabelComp = 'to' in rest || href || onClick ? 'span' : 'label'; const renderChildren = React.useCallback(() => React.Children.map(children, (child) => { if (React.isValidElement(child)) { @@ -72,7 +71,7 @@ const ListItem: React.FC = ({ className, label, hint, children, to, h ) : null}
- {(to || href || onClick) ? ( + {('to' in rest || href || onClick) ? ( {children} @@ -80,12 +79,12 @@ const ListItem: React.FC = ({ className, label, hint, children, to, h ) : null} - {typeof to === 'undefined' && typeof onClick === 'undefined' ? renderChildren() : null} + {!('to' in rest) && typeof onClick === 'undefined' ? renderChildren() : null} ); - if (to) return ( - + if ('to' in rest) return ( + {body} ); diff --git a/packages/pl-fe/src/components/pending-items-row.tsx b/packages/pl-fe/src/components/pending-items-row.tsx index 1f207390c..d179a3255 100644 --- a/packages/pl-fe/src/components/pending-items-row.tsx +++ b/packages/pl-fe/src/components/pending-items-row.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React from 'react'; import { FormattedMessage } from 'react-intl'; @@ -7,17 +7,15 @@ import HStack from 'pl-fe/components/ui/hstack'; import Icon from 'pl-fe/components/ui/icon'; import Text from 'pl-fe/components/ui/text'; -interface IPendingItemsRow { - /** Path to navigate the user when clicked. */ - to: string; +interface IPendingItemsRow extends LinkOptions { /** Number of pending items. */ count: number; /** Size of the icon. */ size?: 'md' | 'lg'; } -const PendingItemsRow: React.FC = ({ to, count, size = 'md' }) => ( - +const PendingItemsRow: React.FC = ({ count, size = 'md', ...props }) => ( +
{ return {childrenWithProps}; }; -interface IRadioItem extends IListItem { +type IRadioItem = IListItem & { label: React.ReactNode; hint?: React.ReactNode; value: string; checked: boolean; onChange?: React.ChangeEventHandler; -} +}; const RadioItem: React.FC = ({ label, hint, checked = false, onChange, value, ...props }) => ( diff --git a/packages/pl-fe/src/components/sidebar-navigation-link.tsx b/packages/pl-fe/src/components/sidebar-navigation-link.tsx index 2d031902d..23685360c 100644 --- a/packages/pl-fe/src/components/sidebar-navigation-link.tsx +++ b/packages/pl-fe/src/components/sidebar-navigation-link.tsx @@ -1,11 +1,11 @@ -import { Link, type LinkProps } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import React from 'react'; import { useSettings } from 'pl-fe/stores/settings'; import Icon from './ui/icon'; -interface ISidebarNavigationLink extends Partial> { +interface ISidebarNavigationLink extends Partial { /** Notification count, if any. */ count?: number; /** Optional max to cap count (ie: N+) */ @@ -22,7 +22,7 @@ interface ISidebarNavigationLink extends Partial): JSX.Element => { - const { icon, activeIcon, text, to, params, count, countMax, onClick } = props; + const { icon, activeIcon, text, to, count, countMax, onClick, ...rest } = props; const isActive = location.pathname === to; const { demetricator } = useSettings(); @@ -40,10 +40,10 @@ const SidebarNavigationLink = React.memo(React.forwardRef((props: ISidebarNaviga activeOptions={{ exact: true }} activeProps={{ className: '⁂-sidebar-navigation-link--active' }} to={to} - params={params} ref={ref} onClick={handleClick} className='⁂-sidebar-navigation-link' + {...rest} > = ({ count, countMax, src, activeSrc, text, to, exact }): JSX.Element => { +const ThumbNavigationLink: React.FC = ({ count, countMax, src, activeSrc, text, exact, ...props }): JSX.Element => { const { demetricator } = useSettings(); const matchRoute = useMatchRoute(); - const icon = (activeSrc && matchRoute({ to }) !== null && activeSrc) || src; + const icon = (activeSrc && matchRoute({ to: props.to, params: props.params, search: props.search }) !== null && activeSrc) || src; return ( - + {!demetricator && count !== undefined ? ( , 'children' | 'className' | 'disabled' | 'onClick' | 'onMouseDown' | 'onKeyDown' | 'onKeyPress' | 'title' | 'type' -> & (Pick | { to?: undefined }) & { +> & (LinkOptions | { to?: undefined }) & { /** Whether this button expands the width of its container. */ block?: boolean; /** URL to an SVG icon to render inside the button. */ diff --git a/packages/pl-fe/src/components/ui/column.tsx b/packages/pl-fe/src/components/ui/column.tsx index 0d743a09e..b202b7b06 100644 --- a/packages/pl-fe/src/components/ui/column.tsx +++ b/packages/pl-fe/src/components/ui/column.tsx @@ -1,4 +1,4 @@ -import { LinkProps, useNavigate, useRouter } from '@tanstack/react-router'; +import { type LinkOptions, useNavigate, useRouter } from '@tanstack/react-router'; import clsx from 'clsx'; import throttle from 'lodash/throttle'; import React, { useCallback, useEffect, useState } from 'react'; @@ -43,8 +43,9 @@ const ColumnHeader: React.FC = ({ label, backHref, backParams, cl interface IColumn { /** Route the back button goes to. */ - backHref?: LinkProps['to']; - backParams?: LinkProps['params']; + backHref?: LinkOptions['to']; + backParams?: LinkOptions['params']; + backSearch?: LinkOptions['search']; /** Column title text. */ label?: string; /** Whether this column should have a transparent background. */ diff --git a/packages/pl-fe/src/components/ui/tabs.tsx b/packages/pl-fe/src/components/ui/tabs.tsx index bed91b7a8..616dc319d 100644 --- a/packages/pl-fe/src/components/ui/tabs.tsx +++ b/packages/pl-fe/src/components/ui/tabs.tsx @@ -11,7 +11,7 @@ import React from 'react'; import Counter from './counter'; -import type { LinkProps } from '@tanstack/react-router'; +import type { LinkOptions } from '@tanstack/react-router'; import './tabs.css'; @@ -117,12 +117,7 @@ type Item = { count?: number; /** Unique name for this tab. */ name: string; -} & ({ - /** Route to visit when the tab is selected. */ - to: LinkProps['to']; - params?: LinkProps['params']; - search?: LinkProps['search']; -} | { to?: undefined }); +} & (LinkOptions | { to?: undefined }); interface ITabs { /** Array of structured tab items. */ diff --git a/packages/pl-fe/src/features/admin/components/counter.tsx b/packages/pl-fe/src/features/admin/components/counter.tsx index f2546679e..4f585c292 100644 --- a/packages/pl-fe/src/features/admin/components/counter.tsx +++ b/packages/pl-fe/src/features/admin/components/counter.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React from 'react'; import { FormattedNumber } from 'react-intl'; @@ -27,24 +27,23 @@ const percIncrease = (a: number, b: number) => { return percent; }; -interface ICounter { +type ICounter = { measure: AdminMeasureKey; startAt: string; endAt: string; label: JSX.Element | string; - to?: string; params?: AdminGetMeasuresParams; target?: string; -} +} & (LinkOptions | {}); const Counter: React.FC = ({ measure, startAt, endAt, label, - to, params, target, + ...rest }) => { const { data } = useMeasures([measure], startAt, endAt, params); @@ -89,9 +88,9 @@ const Counter: React.FC = ({ const className = 'relative flex flex-col rounded bg-gray-200 font-medium dark:bg-gray-800'; - if (to) { + if ('to' in rest) { return ( - + {inner} ); diff --git a/packages/pl-fe/src/features/admin/components/dashcounter.tsx b/packages/pl-fe/src/features/admin/components/dashcounter.tsx index 4f784012c..0296b9f76 100644 --- a/packages/pl-fe/src/features/admin/components/dashcounter.tsx +++ b/packages/pl-fe/src/features/admin/components/dashcounter.tsx @@ -1,29 +1,27 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import React from 'react'; import { FormattedNumber } from 'react-intl'; import Text from 'pl-fe/components/ui/text'; import { isNumber } from 'pl-fe/utils/numbers'; -interface IDashCounter { +type IDashCounter = { count: number | undefined; label: React.ReactNode; - to?: string; percent?: boolean; -} +} & (LinkOptions | {}); /** Displays a (potentially clickable) dashboard statistic. */ -const DashCounter: React.FC = ({ count, label, to = '#', percent = false }) => { +const DashCounter: React.FC = ({ count, label, percent = false, ...rest }) => { if (!isNumber(count)) { return null; } - return ( - + const className = 'flex cursor-pointer flex-col items-center space-y-2 rounded bg-gray-200 p-4 transition-transform hover:-translate-y-1 dark:bg-gray-800'; + + const body = ( + <> = ({ count, label, to = '#', percent = {label} + + ); + + if (!('to' in rest)) { + return {body}; + } + + return ( + + {body} ); }; diff --git a/packages/pl-fe/src/features/admin/tabs/dashboard.tsx b/packages/pl-fe/src/features/admin/tabs/dashboard.tsx index a1811b0d9..13b390f28 100644 --- a/packages/pl-fe/src/features/admin/tabs/dashboard.tsx +++ b/packages/pl-fe/src/features/admin/tabs/dashboard.tsx @@ -100,7 +100,8 @@ const Dashboard: React.FC = () => { measure='resolved_reports' startAt={monthAgo} endAt={today} - to='/pl-fe/admin/reports?resolved=true' + to='/pl-fe/admin/reports' + search={{ resolved: true }} label={} /> @@ -115,7 +116,7 @@ const Dashboard: React.FC = () => { label={} /> - }} />} /> + }} />} /> }} />} /> {/* 0 }} />} /> 0 }} />} /> */} diff --git a/packages/pl-fe/src/features/chats/components/chat-widget/chat-window.tsx b/packages/pl-fe/src/features/chats/components/chat-widget/chat-window.tsx index 4af127018..7bd5b981d 100644 --- a/packages/pl-fe/src/features/chats/components/chat-widget/chat-window.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-widget/chat-window.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkProps } from '@tanstack/react-router'; import React, { useRef } from 'react'; import Avatar from 'pl-fe/components/ui/avatar'; @@ -14,13 +14,13 @@ import Chat from '../chat'; import ChatPaneHeader from './chat-pane-header'; import ChatSettings from './chat-settings'; -const LinkWrapper = ({ enabled, to, children }: { enabled: boolean; to: string; children: React.ReactNode }): JSX.Element => { +const LinkWrapper = ({ enabled, children, ...rest }: LinkProps & { enabled: boolean; children: React.ReactNode }): JSX.Element => { if (!enabled) { return <>{children}; } return ( - + {children} ); @@ -75,7 +75,7 @@ const ChatWindow = () => { )} - +
{chat.account.display_name || `@${chat.account.acct}`} {chat.account.verified && } diff --git a/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx b/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx index f9ec80fe9..eb5322976 100644 --- a/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx +++ b/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React from 'react'; import { FormattedMessage } from 'react-intl'; @@ -60,7 +60,7 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. const getQuotes = () => { if (status.quotes_count) { return ( - + = ({ status }): JSX. ); }; -interface IInteractionCounter { +type IInteractionCounter = { count: number; children: React.ReactNode; onClick?: React.MouseEventHandler; - to?: string; -} +} & (LinkOptions | {}); -const InteractionCounter: React.FC = ({ count, children, onClick, to }) => { +const InteractionCounter: React.FC = ({ count, children, onClick, ...rest }) => { const features = useFeatures(); const className = clsx({ @@ -159,9 +158,9 @@ const InteractionCounter: React.FC = ({ count, children, on ); - if (to) { + if ('to' in rest) { return ( - + {body} ); diff --git a/packages/pl-fe/src/features/ui/components/profile-dropdown.tsx b/packages/pl-fe/src/features/ui/components/profile-dropdown.tsx index b1d6b2487..50ca68ba5 100644 --- a/packages/pl-fe/src/features/ui/components/profile-dropdown.tsx +++ b/packages/pl-fe/src/features/ui/components/profile-dropdown.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -30,7 +30,7 @@ interface IProfileDropdown { type IMenuItem = { text: string | React.ReactElement | null; - to?: string; + linkOptions?: LinkOptions; toggle?: JSX.Element; icon?: string; action?: (event: React.MouseEvent) => void; @@ -63,7 +63,7 @@ const ProfileDropdown: React.FC = ({ account, children }) => { const ProfileDropdownMenu = useMemo(() => { const menu: IMenuItem[] = []; - menu.push({ text: renderAccount(account), to: `/@${account.acct}` }); + menu.push({ text: renderAccount(account), linkOptions: { to: '/@{$username}', params: { username: account.acct } } }); otherAccounts.forEach((otherAccount?: AccountEntity) => { if (otherAccount && otherAccount.id !== account.id) { @@ -80,13 +80,13 @@ const ProfileDropdown: React.FC = ({ account, children }) => { menu.push({ text: intl.formatMessage(messages.add), - to: '/login/add', + linkOptions: { to: '/login/add' }, icon: require('@phosphor-icons/core/regular/plus.svg'), }); menu.push({ text: intl.formatMessage(messages.logout, { acct: account.acct }), - to: '/logout', + linkOptions: { to: '/logout' }, action: handleLogOut, icon: require('@phosphor-icons/core/regular/sign-out.svg'), }); @@ -142,10 +142,10 @@ const MenuItem: React.FC = ({ className, menuItem }) => { {menuItem.text} ); - } else if (menuItem.to) { + } else if (menuItem.linkOptions) { return ( {menuItem.text} diff --git a/packages/pl-fe/src/features/ui/router.tsx b/packages/pl-fe/src/features/ui/router.tsx index f5f663e36..5054e6731 100644 --- a/packages/pl-fe/src/features/ui/router.tsx +++ b/packages/pl-fe/src/features/ui/router.tsx @@ -1181,6 +1181,12 @@ export const errorRoute = createRoute({ component: IntentionalError, }); +export const networkErrorRoute = createRoute({ + getParentRoute: () => layouts.empty, + path: '/error/network', + component: React.lazy(() => Promise.reject(new TypeError('Failed to fetch dynamically imported module: TEST'))), +}); + // Crypto donate export const cryptoDonateRoute = createRoute({ getParentRoute: () => layouts.default, @@ -1285,6 +1291,7 @@ const routeTree = rootRoute.addChildren([ signupRoute, serverInfoRoute, errorRoute, + networkErrorRoute, ]), layouts.event.addChildren([ eventInformationRoute, diff --git a/packages/pl-fe/src/pages/account-lists/circles.tsx b/packages/pl-fe/src/pages/account-lists/circles.tsx index 5ac9fb8ec..6ecf637bd 100644 --- a/packages/pl-fe/src/pages/account-lists/circles.tsx +++ b/packages/pl-fe/src/pages/account-lists/circles.tsx @@ -97,7 +97,8 @@ const CirclesPage: React.FC = () => { {circles.map((circle) => ( diff --git a/packages/pl-fe/src/pages/account-lists/lists.tsx b/packages/pl-fe/src/pages/account-lists/lists.tsx index 6c4915fa7..19023b261 100644 --- a/packages/pl-fe/src/pages/account-lists/lists.tsx +++ b/packages/pl-fe/src/pages/account-lists/lists.tsx @@ -106,7 +106,8 @@ const ListsPage: React.FC = () => { {lists.map((list: any) => ( diff --git a/packages/pl-fe/src/pages/dashboard/report.tsx b/packages/pl-fe/src/pages/dashboard/report.tsx index 0fa92448f..b0797b944 100644 --- a/packages/pl-fe/src/pages/dashboard/report.tsx +++ b/packages/pl-fe/src/pages/dashboard/report.tsx @@ -267,7 +267,8 @@ const ReportPage: React.FC = () => { )} } - to={`/pl-fe/admin/accounts/${report.target_account_id}`} + to='/pl-fe/admin/accounts/$accountId' + params={{ accountId: report.target_account_id }} /> diff --git a/packages/pl-fe/src/pages/developers/developers.tsx b/packages/pl-fe/src/pages/developers/developers.tsx index 7d9158dba..9583cb5ab 100644 --- a/packages/pl-fe/src/pages/developers/developers.tsx +++ b/packages/pl-fe/src/pages/developers/developers.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router'; +import { Link, type LinkOptions } from '@tanstack/react-router'; import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; @@ -12,17 +12,16 @@ const messages = defineMessages({ heading: { id: 'column.developers', defaultMessage: 'Developers' }, }); -interface IDashWidget { - to?: string; +interface IDashWidget extends Partial { onClick?: React.MouseEventHandler; children: React.ReactNode; } -const DashWidget: React.FC = ({ to, onClick, children }) => { +const DashWidget: React.FC = ({ to, onClick, children, ...rest }) => { const className = 'bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-800/75 p-4 rounded flex flex-col items-center justify-center space-y-2'; if (to) { - return {children}; + return {children}; } else { return ; } diff --git a/packages/pl-fe/src/pages/groups/group-members.tsx b/packages/pl-fe/src/pages/groups/group-members.tsx index 6597bc736..ba0fc1f10 100644 --- a/packages/pl-fe/src/pages/groups/group-members.tsx +++ b/packages/pl-fe/src/pages/groups/group-members.tsx @@ -43,7 +43,8 @@ const GroupMembers: React.FC = () => { prepend={(pendingCount > 0) && (
diff --git a/packages/pl-fe/src/pages/groups/manage-group.tsx b/packages/pl-fe/src/pages/groups/manage-group.tsx index 560b2d99e..f4fb6916e 100644 --- a/packages/pl-fe/src/pages/groups/manage-group.tsx +++ b/packages/pl-fe/src/pages/groups/manage-group.tsx @@ -80,7 +80,7 @@ const ManageGroup: React.FC = () => { - + @@ -92,9 +92,9 @@ const ManageGroup: React.FC = () => { - + - + {isOwner && ( diff --git a/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx b/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx index 0dd128d8a..641468b2c 100644 --- a/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx +++ b/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx @@ -96,7 +96,8 @@ const BookmarkFoldersPage: React.FC = () => { @@ -107,7 +108,8 @@ const BookmarkFoldersPage: React.FC = () => { {bookmarkFolders?.map((folder) => ( {folder.emoji ? (