From 31eb4f1a2dc523f260e11d4ee8cd66b7924615b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 19 Dec 2025 14:51:11 +0100 Subject: [PATCH] pl-fe: wip router migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/package.json | 1 + .../chats/components/chat-page/chat-page.tsx | 6 +- packages/pl-fe/src/features/ui/index.tsx | 24 +- packages/pl-fe/src/features/ui/router.tsx | 1311 +++++++++++++++++ packages/pl-fe/src/features/ui/routes.tsx | 43 - .../src/features/ui/util/async-components.ts | 1 + .../features/ui/util/react-router-helpers.tsx | 2 +- packages/pl-fe/src/init/pl-fe-mount.tsx | 40 +- packages/pl-fe/src/layouts/default-layout.tsx | 2 +- packages/pl-fe/src/layouts/event-layout.tsx | 4 +- packages/pl-fe/src/layouts/group-layout.tsx | 5 +- packages/pl-fe/src/layouts/profile-layout.tsx | 5 +- .../src/layouts/remote-instance-layout.tsx | 7 +- .../src/normalizers/pl-fe/pl-fe-config.ts | 1 - .../src/pages/account-lists/followers.tsx | 13 +- .../src/pages/account-lists/following.tsx | 13 +- .../src/pages/accounts/account-timeline.tsx | 19 +- .../pl-fe/src/pages/dashboard/account.tsx | 11 +- .../src/pages/dashboard/pl-fe-config.tsx | 12 - packages/pl-fe/src/pages/dashboard/report.tsx | 11 +- .../src/pages/dashboard/theme-editor.tsx | 5 +- packages/pl-fe/src/pages/drive/drive.tsx | 17 +- .../pl-fe/src/pages/groups/edit-group.tsx | 9 +- .../pages/groups/group-blocked-members.tsx | 11 +- .../pl-fe/src/pages/groups/group-gallery.tsx | 9 +- .../pl-fe/src/pages/groups/group-members.tsx | 9 +- .../groups/group-membership-requests.tsx | 10 +- .../pl-fe/src/pages/groups/manage-group.tsx | 13 +- .../pl-fe/src/pages/settings/edit-filter.tsx | 21 +- .../src/pages/status-lists/bookmarks.tsx | 12 +- .../status-lists/favourited-statuses.tsx | 14 +- .../src/pages/statuses/compose-event.tsx | 36 +- .../src/pages/statuses/event-discussion.tsx | 12 +- .../src/pages/statuses/event-information.tsx | 15 +- packages/pl-fe/src/pages/statuses/status.tsx | 25 +- .../src/pages/timelines/group-timeline.tsx | 13 +- .../src/pages/timelines/hashtag-timeline.tsx | 11 +- .../src/pages/timelines/link-timeline.tsx | 24 +- .../src/pages/timelines/remote-timeline.tsx | 12 +- packages/pl-fe/src/pages/utils/share.tsx | 19 +- packages/pl-fe/src/reducers/instance.ts | 2 +- packages/pl-fe/vite.config.ts | 54 +- pnpm-lock.yaml | 676 ++++++++- 43 files changed, 2157 insertions(+), 403 deletions(-) create mode 100644 packages/pl-fe/src/features/ui/router.tsx delete mode 100644 packages/pl-fe/src/features/ui/routes.tsx diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index 82359c9b1..1d17b9863 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -69,6 +69,7 @@ "@tanstack/react-query": "^5.62.11", "@tanstack/react-router": "^1.141.6", "@tanstack/react-router-devtools": "^1.141.6", + "@tanstack/router-plugin": "^1.141.7", "@transfem-org/sfm-js": "^0.24.6", "@twemoji/svg": "^15.0.0", "@uidotdev/usehooks": "^2.4.1", diff --git a/packages/pl-fe/src/features/chats/components/chat-page/chat-page.tsx b/packages/pl-fe/src/features/chats/components/chat-page/chat-page.tsx index 5eed33f42..cf7d8c761 100644 --- a/packages/pl-fe/src/features/chats/components/chat-page/chat-page.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-page/chat-page.tsx @@ -10,11 +10,7 @@ import ChatPageSettings from './components/chat-page-settings'; import ChatPageShoutbox from './components/chat-page-shoutbox'; import ChatPageSidebar from './components/chat-page-sidebar'; -interface IChatPage { - chatId?: string; -} - -const ChatPage: React.FC = ({ chatId }) => { +const ChatPage: React.FC = () => { const history = useHistory(); const path = history.location.pathname; diff --git a/packages/pl-fe/src/features/ui/index.tsx b/packages/pl-fe/src/features/ui/index.tsx index 4acea535e..c9a41f765 100644 --- a/packages/pl-fe/src/features/ui/index.tsx +++ b/packages/pl-fe/src/features/ui/index.tsx @@ -1,5 +1,7 @@ +import { Outlet } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { Suspense, lazy, useEffect, useRef } from 'react'; +import { Toaster } from 'react-hot-toast'; import { matchPath, Redirect, Switch, useHistory, useLocation } from 'react-router-dom'; import { fetchConfig } from 'pl-fe/actions/admin'; @@ -50,6 +52,7 @@ import { isStandalone } from 'pl-fe/utils/state'; import BackgroundShapes from './components/background-shapes'; import { + ModalRoot, AboutPage, AccountGallery, AccountHoverCard, @@ -159,7 +162,6 @@ import { WrappedRoute } from './util/react-router-helpers'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. import 'pl-fe/components/status'; -import { Outlet } from '@tanstack/react-router'; interface ISwitchingColumnsArea { children: React.ReactNode; @@ -172,7 +174,7 @@ const SwitchingColumnsArea: React.FC = React.memo(({ chil const { isLoggedIn } = useLoggedIn(); const standalone = useAppSelector(isStandalone); - const { authenticatedProfile, cryptoAddresses, redirectRootNoLogin } = usePlFeConfig(); + const { cryptoAddresses, redirectRootNoLogin } = usePlFeConfig(); const hasCrypto = cryptoAddresses.length > 0; // NOTE: Mastodon and Pleroma route some basenames to the backend. @@ -287,10 +289,10 @@ const SwitchingColumnsArea: React.FC = React.memo(({ chil {(features.followedHashtagsList) && } {features.interactionRequests && } - - - - + + + + @@ -305,7 +307,7 @@ const SwitchingColumnsArea: React.FC = React.memo(({ chil {features.groups && } {features.groups && } {features.groups && } - {features.groups && } + {features.groups && } {features.groups && } {features.groups && } {features.groups && } @@ -534,6 +536,14 @@ const UI: React.FC = React.memo(() => { + + + + + ); }); diff --git a/packages/pl-fe/src/features/ui/router.tsx b/packages/pl-fe/src/features/ui/router.tsx new file mode 100644 index 000000000..04742570a --- /dev/null +++ b/packages/pl-fe/src/features/ui/router.tsx @@ -0,0 +1,1311 @@ +import { + type ParsedLocation, + createRootRouteWithContext, + createRoute, + createRouter, + notFound, + redirect, + RouterProvider, +} from '@tanstack/react-router'; +import React, { useMemo } from 'react'; +import * as v from 'valibot'; + +import { FE_SUBDIRECTORY, WITH_LANDING_PAGE } from 'pl-fe/build-config'; +import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { useFeatures } from 'pl-fe/hooks/use-features'; +import { useInstance } from 'pl-fe/hooks/use-instance'; +import { useLoggedIn } from 'pl-fe/hooks/use-logged-in'; +import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; +import AdminLayout from 'pl-fe/layouts/admin-layout'; +import ChatsLayout from 'pl-fe/layouts/chats-layout'; +import DefaultLayout from 'pl-fe/layouts/default-layout'; +import EmptyLayout from 'pl-fe/layouts/empty-layout'; +import EventLayout from 'pl-fe/layouts/event-layout'; +import EventsLayout from 'pl-fe/layouts/events-layout'; +import ExternalLoginLayout from 'pl-fe/layouts/external-login-layout'; +import GroupLayout from 'pl-fe/layouts/group-layout'; +import GroupsLayout from 'pl-fe/layouts/groups-layout'; +import HomeLayout from 'pl-fe/layouts/home-layout'; +import LandingLayout from 'pl-fe/layouts/landing-layout'; +import ManageGroupsLayout from 'pl-fe/layouts/manage-groups-layout'; +import ProfileLayout from 'pl-fe/layouts/profile-layout'; +import RemoteInstanceLayout from 'pl-fe/layouts/remote-instance-layout'; +import SearchLayout from 'pl-fe/layouts/search-layout'; +import StatusLayout from 'pl-fe/layouts/status-layout'; +import { instanceInitialState } from 'pl-fe/reducers/instance'; +import { isStandalone } from 'pl-fe/utils/state'; + +import ColumnLoading from './components/column-loading'; +import { + AboutPage, + AccountGallery, + AccountTimeline, + AdminAccount, + Aliases, + Announcements, + AuthTokenList, + Backups, + Blocks, + BookmarkFolders, + Bookmarks, + BubbleTimeline, + ChatIndex, + Circle, + Circles, + CircleTimeline, + CommunityTimeline, + ComposeEvent, + Conversations, + CreateApp, + CryptoDonate, + Dashboard, + DeleteAccount, + Developers, + Directory, + DomainBlocks, + Domains, + DraftStatuses, + Drive, + EditEmail, + EditFilter, + EditGroup, + EditPassword, + EditProfile, + EventDiscussion, + EventInformation, + Events, + ExportData, + ExternalLogin, + FavouritedStatuses, + FederationRestrictions, + Filters, + FollowedTags, + Followers, + Following, + FollowRequests, + GenericNotFound, + GroupBlockedMembers, + GroupGallery, + GroupMembers, + GroupMembershipRequests, + GroupTimeline, + Groups, + HashtagTimeline, + HomeTimeline, + ImportData, + IntentionalError, + InteractionPolicies, + InteractionRequests, + LandingPage, + LandingTimeline, + LinkTimeline, + Lists, + ListTimeline, + LoginPage, + LogoutPage, + ManageGroup, + MfaForm, + Migration, + ModerationLog, + Mutes, + NewStatus, + Notifications, + OutgoingFollowRequests, + PasswordReset, + PinnedStatuses, + PlFeConfig, + PublicTimeline, + Quotes, + RegisterInvite, + RegistrationPage, + Relays, + RemoteTimeline, + Report, + Rules, + ScheduledStatuses, + Search, + ServerInfo, + ServiceWorkerInfo, + Settings, + SettingsStore, + Share, + Status, + TestTimeline, + ThemeEditor, + Privacy, + UserIndex, + WrenchedTimeline, + EditEvent, +} from './util/async-components'; + +import type { Features } from 'pl-api'; + +interface RouterContext { + instance: ReturnType; + features: ReturnType; + isStandalone: boolean; + isLoggedIn: boolean; + isAdmin: boolean; + hasCrypto: boolean; +} + +const rootRoute = createRootRouteWithContext()({ + component: React.lazy(() => import('pl-fe/features/ui')), +}); + +const layouts = { + admin: createRoute({ + getParentRoute: () => rootRoute, + id: 'admin-layout', + component: AdminLayout, + }), + chats: createRoute({ + getParentRoute: () => rootRoute, + id: 'chats-layout', + component: ChatsLayout, + }), + default: createRoute({ + getParentRoute: () => rootRoute, + id: 'default-layout', + component: DefaultLayout, + }), + empty: createRoute({ + getParentRoute: () => rootRoute, + id: 'empty-layout', + component: EmptyLayout, + }), + event: createRoute({ + getParentRoute: () => rootRoute, + path: '/@{$username}/events/$statusId', + component: EventLayout, + }), + events: createRoute({ + getParentRoute: () => rootRoute, + id: 'events-layout', + component: EventsLayout, + }), + externalLogin: createRoute({ + getParentRoute: () => rootRoute, + id: 'external-login-layout', + component: ExternalLoginLayout, + }), + group: createRoute({ + getParentRoute: () => rootRoute, + path: '/groups/$groupId', + component: GroupLayout, + }), + groups: createRoute({ + getParentRoute: () => rootRoute, + id: 'groups-layout', + component: GroupsLayout, + }), + home: createRoute({ + getParentRoute: () => rootRoute, + id: 'home-layout', + component: HomeLayout, + }), + landing: createRoute({ + getParentRoute: () => rootRoute, + id: 'landing-layout', + component: LandingLayout, + }), + manageGroups: createRoute({ + getParentRoute: () => rootRoute, + id: 'manage-groups-layout', + component: ManageGroupsLayout, + }), + profile: createRoute({ + getParentRoute: () => rootRoute, + path: '/@{$username}', + component: ProfileLayout, + }), + remoteInstance: createRoute({ + getParentRoute: () => rootRoute, + path: '/timeline/$instance', + component: RemoteInstanceLayout, + }), + search: createRoute({ + getParentRoute: () => rootRoute, + id: 'search-layout', + component: SearchLayout, + }), + status: createRoute({ + getParentRoute: () => rootRoute, + id: 'status-layout', + component: StatusLayout, + }), +}; + +const requireAuth = ({ context: { isLoggedIn }, location }: { context: RouterContext; location: ParsedLocation }) => { + localStorage.setItem('plfe:redirect_uri', location.href); + if (!isLoggedIn) throw redirect({ + to: '/login', + }); +}; + +// Root routes +export const homeTimelineRoute = createRoute({ + getParentRoute: () => layouts.home, + path: '/', + component: HomeTimeline, + beforeLoad: ({ context: { isLoggedIn } }) => { + if (!isLoggedIn) throw notFound(); + }, +}); + +export const landingTimelineRoute = createRoute({ + getParentRoute: () => layouts.landing, + path: '/', + component: LandingTimeline, + beforeLoad: ({ context: { isLoggedIn } }) => { + if (isLoggedIn) throw notFound(); + }, +}); + +// Auth routes +export const logoutRoute = createRoute({ + getParentRoute: () => layouts.empty, + path: '/logout', + component: LogoutPage, +}); + +export const loginRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/login', + component: LoginPage, +}); + +export const loginAddRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/login/add', + component: LoginPage, +}); + +export const loginExternalRoute = createRoute({ + getParentRoute: () => layouts.externalLogin, + path: '/login/external', + component: ExternalLogin, +}); + +export const resetPasswordRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/reset-password', + component: PasswordReset, +}); + +export const inviteRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/invite/$token', + component: RegisterInvite, +}); + +export const signupRoute = createRoute({ + getParentRoute: () => layouts.empty, + path: '/signup', + component: RegistrationPage, + beforeLoad: ({ context: { features, instance } }) => { + if (!features.accountCreation || !instance.registrations.enabled) throw notFound(); + }, +}); + +// Timeline routes +export const localTimelineRoute = createRoute({ + getParentRoute: () => layouts.home, + path: '/timeline/local', + component: CommunityTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.federating) throw notFound(); + }, +}); + +export const federatedTimelineRoute = createRoute({ + getParentRoute: () => layouts.home, + path: '/timeline/fediverse', + component: PublicTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.federating) throw notFound(); + }, +}); + +export const bubbleTimelineRoute = createRoute({ + getParentRoute: () => layouts.home, + path: '/timeline/bubble', + component: BubbleTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.bubbleTimeline) throw notFound(); + }, +}); + +export const wrenchedTimelineRoute = createRoute({ + getParentRoute: () => layouts.home, + path: '/timeline/wrenched', + component: WrenchedTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.wrenchedTimeline) throw notFound(); + }, +}); + +export const remoteTimelineRoute = createRoute({ + getParentRoute: () => layouts.remoteInstance, + path: '/', + component: RemoteTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.federating) throw notFound(); + }, +}); + +// Conversations +export const conversationsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/conversations', + component: Conversations, + beforeLoad: ({ context: { features } }) => { + if (!features.conversations) throw notFound(); + }, +}); + +// Tags and links +export const hashtagTimelineRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/tags/$id', + component: HashtagTimeline, +}); + +export const linkTimelineRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/links/$url', + component: LinkTimeline, +}); + +// Lists and circles +export const listsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/lists', + component: Lists, + beforeLoad: ({ context: { features } }) => { + if (!features.lists) throw notFound(); + }, +}); + +export const listTimelineRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/list/$id', + component: ListTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.lists) throw notFound(); + }, +}); + +export const circlesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/circles', + component: Circles, + beforeLoad: ({ context: { features } }) => { + if (!features.circles) throw notFound(); + }, +}); + +export const circleTimelineRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/circles/$id', + component: CircleTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.circles) throw notFound(); + }, +}); + +// Bookmarks +export const bookmarkFoldersRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/bookmarks', + component: BookmarkFolders, + beforeLoad: ({ context: { features } }) => { + if (!features.bookmarks) throw notFound(); + if (!features.bookmarkFolders) throw redirect({ to: '/bookmarks/$folderId', params: { folderId: 'all' } }); + }, +}); + +export const bookmarksRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/bookmarks/$folderId', + component: Bookmarks, + beforeLoad: ({ context: { features } }) => { + if (!features.bookmarks) throw notFound(); + }, +}); + +// Notifications +export const notificationsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/notifications', + component: Notifications, +}); + +// Search and directory +export const searchRoute = createRoute({ + getParentRoute: () => layouts.search, + path: '/search', + component: Search, +}); + +export const directoryRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/directory', + component: Directory, + beforeLoad: ({ context: { features } }) => { + if (!features.profileDirectory) throw notFound(); + }, +}); + +// Events +export const eventsRoute = createRoute({ + getParentRoute: () => layouts.events, + path: '/events', + component: Events, + beforeLoad: ({ context: { features } }) => { + if (!features.events) throw notFound(); + }, +}); + +export const newEventRoute = createRoute({ + getParentRoute: () => layouts.events, + path: '/events/new', + component: ComposeEvent, + beforeLoad: ({ context: { features } }) => { + if (!features.events) throw notFound(); + }, +}); + +// Chats +export const chatsIndexRoute = createRoute({ + getParentRoute: () => layouts.chats, + path: '/chats', + component: ChatIndex, + beforeLoad: ({ context: { features } }) => { + if (!features.chats) throw notFound(); + }, +}); + +export const chatsNewRoute = createRoute({ + getParentRoute: () => layouts.chats, + path: '/chats/new', + component: ChatIndex, + beforeLoad: ({ context: { features } }) => { + if (!features.chats) throw notFound(); + }, +}); + +export const chatsSettingsRoute = createRoute({ + getParentRoute: () => layouts.chats, + path: '/chats/settings', + component: ChatIndex, + beforeLoad: ({ context: { features } }) => { + if (!features.chats) throw notFound(); + }, +}); + +export const shoutboxRoute = createRoute({ + getParentRoute: () => layouts.chats, + path: '/chats/shoutbox', + component: ChatIndex, + beforeLoad: ({ context: { features } }) => { + if (!features.shoutbox) throw notFound(); + }, +}); + +export const chatIdRoute = createRoute({ + getParentRoute: () => layouts.chats, + path: '/chats/$chatId', + component: ChatIndex, + beforeLoad: ({ context: { features } }) => { + if (!features.chats) throw notFound(); + }, +}); + +// Follow requests and blocks +export const followRequestsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/follow_requests', + component: FollowRequests, +}); + +export const outgoingFollowRequestsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/outgoing_follow_requests', + component: OutgoingFollowRequests, + beforeLoad: ({ context: { features } }) => { + if (!features.outgoingFollowRequests) throw notFound(); + }, +}); + +export const blocksRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/blocks', + component: Blocks, +}); + +export const domainBlocksRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/domain_blocks', + component: DomainBlocks, + beforeLoad: ({ context: { features } }) => { + if (!features.federating) throw notFound(); + }, +}); + +export const mutesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/mutes', + component: Mutes, +}); + +// Filters +export const filtersRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/filters', + component: Filters, + beforeLoad: ({ context: { features } }) => { + if (!features.filters && !features.filtersV2) throw notFound(); + }, +}); + +export const editFilterRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/filters/$filterId', + component: EditFilter, + beforeLoad: ({ context: { features } }) => { + if (!features.filters && !features.filtersV2) throw notFound(); + }, +}); + +// Followed tags +export const followedTagsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/followed_tags', + component: FollowedTags, + beforeLoad: ({ context: { features } }) => { + if (!features.followedHashtagsList) throw notFound(); + }, +}); + +// Interaction requests +export const interactionRequestsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/interaction_requests', + component: InteractionRequests, + beforeLoad: ({ context: { features } }) => { + if (!features.interactionRequests) throw notFound(); + }, +}); + +// Profile routes +export const profileRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/', + component: AccountTimeline, + validateSearch: v.object({ + with_replies: v.optional(v.boolean(), false), + }), +}); + +export const profileFollowersRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/followers', + component: Followers, +}); + +export const profileFollowingRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/following', + component: Following, +}); + +export const profileMediaRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/media', + component: AccountGallery, +}); + +export const profileTaggedRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/tagged/$tag', + component: AccountTimeline, +}); + +export const profileFavoritesRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/favorites', + component: FavouritedStatuses, +}); + +export const profilePinsRoute = createRoute({ + getParentRoute: () => layouts.profile, + path: '/pins', + component: PinnedStatuses, +}); + +// Status routes +export const statusRoute = createRoute({ + getParentRoute: () => layouts.status, + path: '/@{$username}/posts/$statusId', + component: Status, +}); + +export const statusQuotesRoute = createRoute({ + getParentRoute: () => layouts.status, + path: '/@{$username}/posts/$statusId/quotes', + component: Quotes, +}); + +export const statusesIdRoute = createRoute({ + getParentRoute: () => layouts.status, + path: '/statuses/$statusId', + component: Status, +}); + +export const postsIdRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/posts/$statusId', + component: Status, +}); + +export const noticeRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/notice/$statusId', + component: Status, +}); + +export const notesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/notes/$statusId', + component: Status, +}); + +// Event routes +export const eventInformationRoute = createRoute({ + getParentRoute: () => layouts.event, + path: '/', + component: EventInformation, + beforeLoad: ({ context: { features } }) => { + if (!features.events) throw notFound(); + }, +}); + +export const eventEditRoute = createRoute({ + getParentRoute: () => layouts.event, + path: '/edit', + component: EditEvent, + beforeLoad: ({ context: { features } }) => { + if (!features.events) throw notFound(); + }, +}); + +export const eventDiscussionRoute = createRoute({ + getParentRoute: () => layouts.event, + path: '/discussion', + component: EventDiscussion, + beforeLoad: ({ context: { features } }) => { + if (!features.events) throw notFound(); + }, +}); + +// Groups routes +export const groupsRoute = createRoute({ + getParentRoute: () => layouts.groups, + path: '/groups', + component: Groups, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const groupTimelineRoute = createRoute({ + getParentRoute: () => layouts.group, + path: '/', + component: GroupTimeline, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const groupMembersRoute = createRoute({ + getParentRoute: () => layouts.group, + path: '/members', + component: GroupMembers, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const groupGalleryRoute = createRoute({ + getParentRoute: () => layouts.group, + path: '/media', + component: GroupGallery, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const manageGroupRoute = createRoute({ + getParentRoute: () => layouts.manageGroups, + path: '/groups/$groupId/manage', + component: ManageGroup, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const editGroupRoute = createRoute({ + getParentRoute: () => layouts.manageGroups, + path: '/groups/$groupId/manage/edit', + component: EditGroup, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const groupBlocksRoute = createRoute({ + getParentRoute: () => layouts.manageGroups, + path: '/groups/$groupId/manage/blocks', + component: GroupBlockedMembers, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +export const groupMembershipRequestsRoute = createRoute({ + getParentRoute: () => layouts.manageGroups, + path: '/groups/$groupId/manage/requests', + component: GroupMembershipRequests, + beforeLoad: ({ context: { features } }) => { + if (!features.groups) throw notFound(); + }, +}); + +// Statuses +export const newStatusRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/statuses/new', + component: NewStatus, +}); + +export const scheduledStatusesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/scheduled_statuses', + component: ScheduledStatuses, + beforeLoad: ({ context: { features } }) => { + if (!features.scheduledStatuses) throw notFound(); + }, +}); + +export const draftStatusesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/draft_statuses', + component: DraftStatuses, +}); + +// Drive +export const driveRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/drive/{-$folderId}', + component: Drive, + beforeLoad: ({ context: { features } }) => { + if (!features.drive) throw notFound(); + }, +}); + +// Circle +export const circleRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/circle', + component: Circle, +}); + +// Settings routes +export const settingsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings', + component: Settings, +}); + +export const settingsProfileRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/profile', + component: EditProfile, +}); + +export const settingsExportRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/export', + component: ExportData, +}); + +export const settingsImportRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/import', + component: ImportData, + beforeLoad: ({ context: { features } }) => { + if (!features.importBlocks && !features.importFollows && !features.importMutes) throw notFound(); + }, +}); + +export const settingsAliasesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/aliases', + component: Aliases, + beforeLoad: ({ context: { features } }) => { + if (!features.manageAccountAliases) throw notFound(); + }, +}); + +export const settingsMigrationRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/migration', + component: Migration, + beforeLoad: ({ context: { features } }) => { + if (!features.accountMoving) throw notFound(); + }, +}); + +export const settingsBackupsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/backups', + component: Backups, + beforeLoad: ({ context: { features } }) => { + if (!features.accountBackups) throw notFound(); + }, +}); + +export const settingsEmailRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/email', + component: EditEmail, +}); + +export const settingsPasswordRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/password', + component: EditPassword, +}); + +export const settingsAccountRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/account', + component: DeleteAccount, +}); + +export const settingsMfaRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/mfa', + component: MfaForm, +}); + +export const settingsTokensRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/tokens', + component: AuthTokenList, +}); + +export const settingsInteractionPoliciesRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/interaction_policies', + component: InteractionPolicies, + beforeLoad: ({ context: { features } }) => { + if (!features.interactionRequests) throw notFound(); + }, +}); + +export const settingsPrivacyRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/settings/privacy', + component: Privacy, +}); + +// PlFe config +export const plFeConfigRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/pl-fe/config', + component: PlFeConfig, +}); + +// Admin routes +export const adminDashboardRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin', + component: Dashboard, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminAccountRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/accounts/$accountId', + component: AdminAccount, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminApprovalRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/approval', + component: Dashboard, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminReportsRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/reports', + component: Dashboard, + beforeLoad: (options) => { + requireAuth(options); + if (!options.context.isAdmin) throw notFound(); + }, +}); + +export const adminReportRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/reports/$reportId', + component: Report, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminLogRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/log', + component: ModerationLog, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminUsersRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/users', + component: UserIndex, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminThemeRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/theme', + component: ThemeEditor, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminRelaysRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/relays', + component: Relays, + beforeLoad: ({ context: { isAdmin } }) => { + if (!isAdmin) throw notFound(); + }, +}); + +export const adminAnnouncementsRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/announcements', + component: Announcements, + beforeLoad: ({ context: { features, isAdmin } }) => { + if (!isAdmin || features.announcements) throw notFound(); + }, +}); + +export const adminDomainsRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/domains', + component: Domains, + beforeLoad: ({ context: { features, isAdmin } }) => { + if (!isAdmin || features.domains) throw notFound(); + }, +}); + +export const adminRulesRoute = createRoute({ + getParentRoute: () => layouts.admin, + path: '/pl-fe/admin/rules', + component: Rules, + beforeLoad: ({ context: { features, isAdmin } }) => { + if (!isAdmin || features.adminRules) throw notFound(); + }, +}); + +// Info and other routes +export const serverInfoRoute = createRoute({ + getParentRoute: () => layouts.empty, + path: '/info', + component: ServerInfo, +}); + +export const aboutRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/about/$slug', + component: AboutPage, +}); + +export const aboutIndexRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/about', + component: AboutPage, +}); + +export const shareRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/share', + component: Share, + validateSearch: v.object({ + title: v.optional(v.string(), ''), + text: v.optional(v.string(), ''), + url: v.optional(v.string(), ''), + }), +}); + +// Developers routes +export const developersRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/developers', + component: Developers, +}); + +export const developersAppsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/developers/apps/create', + component: CreateApp, +}); + +export const developersSettingsStoreRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/developers/settings_store', + component: SettingsStore, +}); + +export const developersTimelineRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/developers/timeline', + component: TestTimeline, +}); + +export const developersSwRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/developers/sw', + component: ServiceWorkerInfo, +}); + +export const errorRoute = createRoute({ + getParentRoute: () => layouts.empty, + path: '/error', + component: IntentionalError, +}); + +// Crypto donate +export const cryptoDonateRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/donate/crypto', + component: CryptoDonate, + beforeLoad: ({ context: { hasCrypto } }) => { + if (!hasCrypto) throw notFound(); + }, +}); + +// Federation restrictions +export const federationRestrictionsRoute = createRoute({ + getParentRoute: () => layouts.default, + path: '/federation_restrictions', + component: FederationRestrictions, + beforeLoad: ({ context: { features } }) => { + if (!features.federating) throw notFound(); + }, +}); + +const routeTree = rootRoute.addChildren([ + layouts.admin.addChildren([ + adminDashboardRoute, + adminAccountRoute, + adminApprovalRoute, + adminReportsRoute, + adminReportRoute, + adminLogRoute, + adminUsersRoute, + adminThemeRoute, + adminRelaysRoute, + adminAnnouncementsRoute, + adminDomainsRoute, + adminRulesRoute, + ]), + layouts.chats.addChildren([ + chatsIndexRoute, + chatsNewRoute, + chatsSettingsRoute, + shoutboxRoute, + chatIdRoute, + ]), + layouts.default.addChildren([ + conversationsRoute, + hashtagTimelineRoute, + linkTimelineRoute, + listsRoute, + listTimelineRoute, + circlesRoute, + circleTimelineRoute, + bookmarkFoldersRoute, + bookmarksRoute, + notificationsRoute, + directoryRoute, + followRequestsRoute, + outgoingFollowRequestsRoute, + blocksRoute, + domainBlocksRoute, + mutesRoute, + filtersRoute, + editFilterRoute, + followedTagsRoute, + interactionRequestsRoute, + newStatusRoute, + scheduledStatusesRoute, + draftStatusesRoute, + driveRoute, + circleRoute, + settingsRoute, + settingsProfileRoute, + settingsExportRoute, + settingsImportRoute, + settingsAliasesRoute, + settingsMigrationRoute, + settingsBackupsRoute, + settingsEmailRoute, + settingsPasswordRoute, + settingsAccountRoute, + settingsMfaRoute, + settingsTokensRoute, + settingsInteractionPoliciesRoute, + settingsPrivacyRoute, + plFeConfigRoute, + aboutRoute, + aboutIndexRoute, + shareRoute, + developersRoute, + developersAppsRoute, + developersSettingsStoreRoute, + developersTimelineRoute, + developersSwRoute, + cryptoDonateRoute, + federationRestrictionsRoute, + postsIdRoute, + noticeRoute, + notesRoute, + loginRoute, + loginAddRoute, + resetPasswordRoute, + inviteRoute, + ]), + layouts.empty.addChildren([ + logoutRoute, + signupRoute, + serverInfoRoute, + errorRoute, + ]), + layouts.event.addChildren([ + eventInformationRoute, + eventDiscussionRoute, + ]), + layouts.events.addChildren([ + eventsRoute, + newEventRoute, + eventEditRoute, + ]), + layouts.externalLogin.addChildren([loginExternalRoute]), + layouts.group.addChildren([ + groupTimelineRoute, + groupMembersRoute, + groupGalleryRoute, + ]), + layouts.groups.addChildren([groupsRoute]), + layouts.home.addChildren([ + homeTimelineRoute, + localTimelineRoute, + federatedTimelineRoute, + bubbleTimelineRoute, + wrenchedTimelineRoute, + ]), + layouts.landing.addChildren([landingTimelineRoute]), + layouts.manageGroups.addChildren([ + manageGroupRoute, + editGroupRoute, + groupBlocksRoute, + groupMembershipRequestsRoute, + ]), + layouts.profile.addChildren([ + profileRoute, + profileFollowersRoute, + profileFollowingRoute, + profileMediaRoute, + profileTaggedRoute, + profileFavoritesRoute, + profilePinsRoute, + ]), + layouts.remoteInstance.addChildren([remoteTimelineRoute]), + layouts.search.addChildren([searchRoute]), + layouts.status.addChildren([ + statusRoute, + statusQuotesRoute, + statusesIdRoute, + ]), +]); + +const router = createRouter({ + routeTree, + basepath: FE_SUBDIRECTORY, + context: { + instance: instanceInitialState, + features: {} as Features, + isLoggedIn: false, + isStandalone: false, + isAdmin: false, + hasCrypto: false, + // instance, + // features, + // isStandalone: standalone, + // isLoggedIn, + // isAdmin: true, + // hasCrypto, + }, + defaultNotFoundComponent: GenericNotFound, + defaultPendingComponent: ColumnLoading, +}); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +const RouterWithContext = () => { + const instance = useInstance(); + const features = useFeatures(); + const { isLoggedIn } = useLoggedIn(); + const standalone = useAppSelector(isStandalone); + const { cryptoAddresses } = usePlFeConfig(); + const hasCrypto = cryptoAddresses.length > 0; + + const context = useMemo(() => ({ + instance, + features, + isLoggedIn, + isStandalone: standalone, + hasCrypto, + }), [features.version, isLoggedIn, standalone, hasCrypto]); + + return ( + + ); +}; + +export { layouts, rootRoute, router, RouterWithContext }; diff --git a/packages/pl-fe/src/features/ui/routes.tsx b/packages/pl-fe/src/features/ui/routes.tsx deleted file mode 100644 index 275d40a6b..000000000 --- a/packages/pl-fe/src/features/ui/routes.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { createRootRoute, createRoute } from '@tanstack/react-router'; - -import AdminLayout from 'pl-fe/layouts/admin-layout'; -import ChatsLayout from 'pl-fe/layouts/chats-layout'; -import DefaultLayout from 'pl-fe/layouts/default-layout'; -import EmptyLayout from 'pl-fe/layouts/empty-layout'; -import EventLayout from 'pl-fe/layouts/event-layout'; -import EventsLayout from 'pl-fe/layouts/events-layout'; -import ExternalLoginLayout from 'pl-fe/layouts/external-login-layout'; -import GroupLayout from 'pl-fe/layouts/group-layout'; -import GroupsLayout from 'pl-fe/layouts/groups-layout'; -import HomeLayout from 'pl-fe/layouts/home-layout'; -import LandingLayout from 'pl-fe/layouts/landing-layout'; -import ManageGroupsLayout from 'pl-fe/layouts/manage-groups-layout'; -import ProfileLayout from 'pl-fe/layouts/profile-layout'; -import RemoteInstanceLayout from 'pl-fe/layouts/remote-instance-layout'; -import SearchLayout from 'pl-fe/layouts/search-layout'; -import StatusLayout from 'pl-fe/layouts/status-layout'; - -import UI from '.'; - -const rootRoute = createRootRoute({ - component: UI, -}); - -const layouts = { - admin: createRoute({ getParentRoute: () => rootRoute, id: 'admin-layout', component: AdminLayout }), - chats: createRoute({ getParentRoute: () => rootRoute, id: 'chats-layout', component: ChatsLayout }), - default: createRoute({ getParentRoute: () => rootRoute, id: 'default-layout', component: DefaultLayout }), - empty: createRoute({ getParentRoute: () => rootRoute, id: 'empty-layout', component: EmptyLayout }), - event: createRoute({ getParentRoute: () => rootRoute, id: 'event-layout', component: EventLayout }), - events: createRoute({ getParentRoute: () => rootRoute, id: 'events-layout', component: EventsLayout }), - externalLogin: createRoute({ getParentRoute: () => rootRoute, id: 'external-login-layout', component: ExternalLoginLayout }), - group: createRoute({ getParentRoute: () => rootRoute, id: 'group-layout', component: GroupLayout }), - groups: createRoute({ getParentRoute: () => rootRoute, id: 'groups-layout', component: GroupsLayout }), - home: createRoute({ getParentRoute: () => rootRoute, id: 'home-layout', component: HomeLayout }), - landing: createRoute({ getParentRoute: () => rootRoute, id: 'landing-layout', component: LandingLayout }), - manageGroups: createRoute({ getParentRoute: () => rootRoute, id: 'manage-groups-layout', component: ManageGroupsLayout }), - profile: createRoute({ getParentRoute: () => rootRoute, id: 'profile-layout', component: ProfileLayout }), - remoteInstance: createRoute({ getParentRoute: () => rootRoute, id: 'remote-instance-layout', component: RemoteInstanceLayout }), - search: createRoute({ getParentRoute: () => rootRoute, id: 'search-layout', component: SearchLayout }), - status: createRoute({ getParentRoute: () => rootRoute, id: 'status-layout', component: StatusLayout }), -}; 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 1477abf59..cbe2357fc 100644 --- a/packages/pl-fe/src/features/ui/util/async-components.ts +++ b/packages/pl-fe/src/features/ui/util/async-components.ts @@ -19,6 +19,7 @@ export const Circles = lazy(() => import('pl-fe/pages/account-lists/circles')); export const CircleTimeline = lazy(() => import('pl-fe/pages/timelines/circle-timeline')); export const CommunityTimeline = lazy(() => import('pl-fe/pages/timelines/community-timeline')); export const ComposeEvent = lazy(() => import('pl-fe/pages/statuses/compose-event')); +export const EditEvent = lazy(() => import('pl-fe/pages/statuses/compose-event').then(m => ({ default: m.EditEventPage }))); 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')); diff --git a/packages/pl-fe/src/features/ui/util/react-router-helpers.tsx b/packages/pl-fe/src/features/ui/util/react-router-helpers.tsx index 46be0a2ba..2aedca14b 100644 --- a/packages/pl-fe/src/features/ui/util/react-router-helpers.tsx +++ b/packages/pl-fe/src/features/ui/util/react-router-helpers.tsx @@ -1,3 +1,4 @@ +import { Outlet } from '@tanstack/react-router'; import React, { Suspense, useEffect, useRef } from 'react'; import { ErrorBoundary, type FallbackProps } from 'react-error-boundary'; import { Redirect, Route, useHistory, RouteProps, RouteComponentProps, match as MatchType, useLocation } from 'react-router-dom'; @@ -8,7 +9,6 @@ import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; import ColumnForbidden from '../components/column-forbidden'; import ColumnLoading from '../components/column-loading'; import ErrorColumn from '../components/error-column'; -import { Outlet } from '@tanstack/react-router'; type LayoutProps = { params?: MatchType['params']; diff --git a/packages/pl-fe/src/init/pl-fe-mount.tsx b/packages/pl-fe/src/init/pl-fe-mount.tsx index ac2d02bcc..7b4c52ad7 100644 --- a/packages/pl-fe/src/init/pl-fe-mount.tsx +++ b/packages/pl-fe/src/init/pl-fe-mount.tsx @@ -1,49 +1,17 @@ import React, { Suspense } from 'react'; -import { Toaster } from 'react-hot-toast'; -import { BrowserRouter } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; -// @ts-ignore: it doesn't have types -import { ScrollContext } from 'react-router-scroll-4'; -import * as BuildConfig from 'pl-fe/build-config'; import LoadingScreen from 'pl-fe/components/loading-screen'; import SiteErrorBoundary from 'pl-fe/components/site-error-boundary'; -import { ModalRoot } from 'pl-fe/features/ui/util/async-components'; -import { useCachedLocationHandler } from 'pl-fe/utils/redirect'; - -const UI = React.lazy(() => import('pl-fe/features/ui')); +import { RouterWithContext } from 'pl-fe/features/ui/router'; /** Highest level node with the Redux store. */ const PlFeMount = () => { - useCachedLocationHandler(); - - // @ts-ignore: I don't actually know what these should be, lol - const shouldUpdateScroll = (prevRouterProps, { location }) => - !(location.state?.plFeModalKey && location.state?.plFeModalKey !== prevRouterProps?.location?.state?.plFeModalKey) - && !(location.state?.plFeDropdownKey && location.state?.plFeDropdownKey !== prevRouterProps?.location?.state?.plFeDropdownKey); return ( - - - - <> - }> - - - - - - - - - - - - + }> + + ); }; diff --git a/packages/pl-fe/src/layouts/default-layout.tsx b/packages/pl-fe/src/layouts/default-layout.tsx index a599173db..3e7e1c11d 100644 --- a/packages/pl-fe/src/layouts/default-layout.tsx +++ b/packages/pl-fe/src/layouts/default-layout.tsx @@ -11,7 +11,7 @@ import { import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; -const DefaultLayout: React.FC = ({ children }) => { +const DefaultLayout: React.FC = () => { const me = useAppSelector(state => state.me); const features = useFeatures(); diff --git a/packages/pl-fe/src/layouts/event-layout.tsx b/packages/pl-fe/src/layouts/event-layout.tsx index a4929c7ac..5bff2415e 100644 --- a/packages/pl-fe/src/layouts/event-layout.tsx +++ b/packages/pl-fe/src/layouts/event-layout.tsx @@ -9,6 +9,7 @@ import Layout from 'pl-fe/components/ui/layout'; import Tabs from 'pl-fe/components/ui/tabs'; import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status'; import LinkFooter from 'pl-fe/features/ui/components/link-footer'; +import { layouts } from 'pl-fe/features/ui/router'; import { EventHeader, SignUpPanel, @@ -22,11 +23,12 @@ import { makeGetStatus } from 'pl-fe/selectors'; const getStatus = makeGetStatus(); const EventLayout = () => { + const { statusId } = layouts.event.useParams(); + const me = useAppSelector(state => state.me); const features = useFeatures(); const history = useHistory(); - const statusId = params?.statusId!; const status = useAppSelector(state => getStatus(state, { id: statusId }) || undefined); diff --git a/packages/pl-fe/src/layouts/group-layout.tsx b/packages/pl-fe/src/layouts/group-layout.tsx index 06ee82bad..d453d65ec 100644 --- a/packages/pl-fe/src/layouts/group-layout.tsx +++ b/packages/pl-fe/src/layouts/group-layout.tsx @@ -13,6 +13,7 @@ import Tabs from 'pl-fe/components/ui/tabs'; import Text from 'pl-fe/components/ui/text'; import GroupHeader from 'pl-fe/features/group/components/group-header'; import LinkFooter from 'pl-fe/features/ui/components/link-footer'; +import { layouts } from 'pl-fe/features/ui/router'; import { GroupMediaPanel, SignUpPanel, @@ -45,12 +46,12 @@ const PrivacyBlankslate = () => ( /** Layout to display a group. */ const GroupLayout = () => { + const { groupId } = layouts.group.useParams(); + const intl = useIntl(); const match = useRouteMatch(); const { account: me } = useOwnAccount(); - const groupId = params?.groupId || ''; - const { group } = useGroup(groupId); const { accounts: pending } = useGroupMembershipRequests(groupId); diff --git a/packages/pl-fe/src/layouts/profile-layout.tsx b/packages/pl-fe/src/layouts/profile-layout.tsx index 9eb79daab..a20e9fad8 100644 --- a/packages/pl-fe/src/layouts/profile-layout.tsx +++ b/packages/pl-fe/src/layouts/profile-layout.tsx @@ -10,6 +10,7 @@ import Layout from 'pl-fe/components/ui/layout'; import Tabs from 'pl-fe/components/ui/tabs'; import Header from 'pl-fe/features/account/components/header'; import LinkFooter from 'pl-fe/features/ui/components/link-footer'; +import { layouts } from 'pl-fe/features/ui/router'; import { WhoToFollowPanel, ProfileInfoPanel, @@ -25,9 +26,9 @@ import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; import { getAcct } from 'pl-fe/utils/accounts'; /** Layout to display a user's profile. */ -const ProfileLayout = () => { +const ProfileLayout: React.FC = () => { + const { username } = layouts.profile.useParams(); const history = useHistory(); - const username = params?.username || ''; const { account, isUnauthorized } = useAccountLookup(username, { withRelationship: true }); diff --git a/packages/pl-fe/src/layouts/remote-instance-layout.tsx b/packages/pl-fe/src/layouts/remote-instance-layout.tsx index dac8cbc68..d8f656b7c 100644 --- a/packages/pl-fe/src/layouts/remote-instance-layout.tsx +++ b/packages/pl-fe/src/layouts/remote-instance-layout.tsx @@ -3,6 +3,7 @@ import React from 'react'; import Layout from 'pl-fe/components/ui/layout'; import LinkFooter from 'pl-fe/features/ui/components/link-footer'; +import { layouts } from 'pl-fe/features/ui/router'; import { PromoPanel, InstanceInfoPanel, @@ -14,7 +15,7 @@ import { federationRestrictionsDisclosed } from 'pl-fe/utils/state'; /** Layout for viewing a remote instance timeline. */ const RemoteInstanceLayout = () => { - const host = params!.instance!; + const { instance } = layouts.remoteInstance.useParams(); const { account } = useOwnAccount(); const disclosed = useAppSelector(federationRestrictionsDisclosed); @@ -27,9 +28,9 @@ const RemoteInstanceLayout = () => { - + {(disclosed || account?.is_admin) && ( - + )} diff --git a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts index c1f258e4f..1392b3d6a 100644 --- a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts +++ b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts @@ -69,7 +69,6 @@ const plFeConfigSchema = coerceObject({ defaultLocale: v.fallback(v.string(), ''), // v.fallback(v.optional(v.string()), undefined), locales: filteredArray(v.string()), })), {}), - authenticatedProfile: v.fallback(v.boolean(), false), linkFooterMessage: v.fallback(v.string(), ''), links: v.fallback(v.record(v.string(), v.string()), {}), tileServer: v.fallback(v.string(), ''), diff --git a/packages/pl-fe/src/pages/account-lists/followers.tsx b/packages/pl-fe/src/pages/account-lists/followers.tsx index eb81834b8..5e1fac0ec 100644 --- a/packages/pl-fe/src/pages/account-lists/followers.tsx +++ b/packages/pl-fe/src/pages/account-lists/followers.tsx @@ -7,23 +7,20 @@ import ScrollableList from 'pl-fe/components/scrollable-list'; import Column from 'pl-fe/components/ui/column'; import Spinner from 'pl-fe/components/ui/spinner'; import AccountContainer from 'pl-fe/containers/account-container'; +import { profileFollowersRoute } from 'pl-fe/features/ui/router'; import { useFollowers } from 'pl-fe/queries/account-lists/use-follows'; const messages = defineMessages({ heading: { id: 'column.followers', defaultMessage: 'Followers' }, }); -interface IFollowersPage { - params?: { - username?: string; - }; -} - /** Displays a list of accounts who follow the given account. */ -const FollowersPage: React.FC = ({ params }) => { +const FollowersPage: React.FC = () => { + const { username } = profileFollowersRoute.useParams(); + const intl = useIntl(); - const { account, isUnavailable } = useAccountLookup(params?.username); + const { account, isUnavailable } = useAccountLookup(username); const { data = [], diff --git a/packages/pl-fe/src/pages/account-lists/following.tsx b/packages/pl-fe/src/pages/account-lists/following.tsx index 6c29bf946..d609acac3 100644 --- a/packages/pl-fe/src/pages/account-lists/following.tsx +++ b/packages/pl-fe/src/pages/account-lists/following.tsx @@ -7,23 +7,20 @@ import ScrollableList from 'pl-fe/components/scrollable-list'; import Column from 'pl-fe/components/ui/column'; import Spinner from 'pl-fe/components/ui/spinner'; import AccountContainer from 'pl-fe/containers/account-container'; +import { profileFollowingRoute } from 'pl-fe/features/ui/router'; import { useFollowing } from 'pl-fe/queries/account-lists/use-follows'; const messages = defineMessages({ heading: { id: 'column.following', defaultMessage: 'Following' }, }); -interface IFollowingPage { - params?: { - username?: string; - }; -} - /** Displays a list of accounts the given user is following. */ -const FollowingPage: React.FC = ({ params }) => { +const FollowingPage: React.FC = () => { + const { username } = profileFollowingRoute.useParams(); + const intl = useIntl(); - const { account, isUnavailable } = useAccountLookup(params?.username); + const { account, isUnavailable } = useAccountLookup(username); const { data = [], diff --git a/packages/pl-fe/src/pages/accounts/account-timeline.tsx b/packages/pl-fe/src/pages/accounts/account-timeline.tsx index 4f9bc76d7..fc2fc5e69 100644 --- a/packages/pl-fe/src/pages/accounts/account-timeline.tsx +++ b/packages/pl-fe/src/pages/accounts/account-timeline.tsx @@ -10,6 +10,7 @@ import StatusList from 'pl-fe/components/status-list'; import Card, { CardBody } from 'pl-fe/components/ui/card'; import Spinner from 'pl-fe/components/ui/spinner'; import Text from 'pl-fe/components/ui/text'; +import { profileRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; @@ -18,20 +19,16 @@ import { useSettings } from 'pl-fe/stores/settings'; const getStatusIds = makeGetStatusIds(); -interface IAccountTimelinePage { - params: { - username: string; - }; - withReplies?: boolean; -} +const AccountTimelinePage: React.FC = () => { + const { username } = profileRoute.useParams(); + const { with_replies: withReplies = false } = profileRoute.useSearch(); -const AccountTimelinePage: React.FC = ({ params, withReplies = false }) => { const history = useHistory(); const dispatch = useAppDispatch(); const features = useFeatures(); const settings = useSettings(); - const { account } = useAccountLookup(params.username, { withRelationship: true }); + const { account } = useAccountLookup(username, { withRelationship: true }); const [accountLoading, setAccountLoading] = useState(!account); const path = withReplies ? `${account?.id}:with_replies` : account?.id; @@ -44,13 +41,13 @@ const AccountTimelinePage: React.FC = ({ params, withRepli const isLoading = useAppSelector(state => state.timelines[`account:${path}`]?.isLoading === true); const hasMore = useAppSelector(state => state.timelines[`account:${path}`]?.hasMore === true); - const accountUsername = account?.username || params.username; + const accountUsername = account?.username || username; useEffect(() => { - dispatch(fetchAccountByUsername(params.username, history)) + dispatch(fetchAccountByUsername(username, history)) .then(() => setAccountLoading(false)) .catch(() => setAccountLoading(false)); - }, [params.username]); + }, [username]); useEffect(() => { if (account) { diff --git a/packages/pl-fe/src/pages/dashboard/account.tsx b/packages/pl-fe/src/pages/dashboard/account.tsx index ef20281ad..a68854f70 100644 --- a/packages/pl-fe/src/pages/dashboard/account.tsx +++ b/packages/pl-fe/src/pages/dashboard/account.tsx @@ -20,6 +20,7 @@ import Text from 'pl-fe/components/ui/text'; import Toggle from 'pl-fe/components/ui/toggle'; import { SelectDropdown } from 'pl-fe/features/forms'; import ColumnLoading from 'pl-fe/features/ui/components/column-loading'; +import { adminAccountRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useFeatures } from 'pl-fe/hooks/use-features'; import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; @@ -136,14 +137,8 @@ const BadgeInput: React.FC = ({ badges, onChange }) => { ); }; -type RouteParams = { accountId: string }; - -interface IAdminAccountPage { - params: RouteParams; -} - -const AdminAccountPage: React.FC = (props) => { - const { accountId } = props.params; +const AdminAccountPage: React.FC = () => { + const { accountId } = adminAccountRoute.useParams(); const intl = useIntl(); const dispatch = useAppDispatch(); diff --git a/packages/pl-fe/src/pages/dashboard/pl-fe-config.tsx b/packages/pl-fe/src/pages/dashboard/pl-fe-config.tsx index fdb25ede7..bf7ddbd25 100644 --- a/packages/pl-fe/src/pages/dashboard/pl-fe-config.tsx +++ b/packages/pl-fe/src/pages/dashboard/pl-fe-config.tsx @@ -40,8 +40,6 @@ const messages = defineMessages({ rawJSONInvalid: { id: 'plfe_config.raw_json_invalid', defaultMessage: 'is invalid' }, displayFqnLabel: { id: 'plfe_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' }, greentextLabel: { id: 'plfe_config.greentext_label', defaultMessage: 'Enable greentext support' }, - authenticatedProfileLabel: { id: 'plfe_config.authenticated_profile_label', defaultMessage: 'Profiles require authentication' }, - authenticatedProfileHint: { id: 'plfe_config.authenticated_profile_hint', defaultMessage: 'Users must be logged-in to view replies and media on user profiles.' }, mediaPreviewLabel: { id: 'plfe_config.media_preview_label', defaultMessage: 'Prefer preview media for thumbnails' }, mediaPreviewHint: { id: 'plfe_config.media_preview_hint', defaultMessage: 'Some backends provide an optimized version of media for display in timelines. However, these preview images may be too small without additional configuration.' }, tileServerLabel: { id: 'plfe_config.tile_server_label', defaultMessage: 'Map tile server' }, @@ -267,16 +265,6 @@ const PlFeConfigEditor: React.FC = () => { /> - - e.target.checked)} - /> - - = ({ statusIds }) => { ); }; -type RouteParams = { reportId: string }; - -interface IReportPage { - params: RouteParams; -} - -const ReportPage: React.FC = (props) => { - const { reportId } = props.params; +const ReportPage: React.FC = () => { + const { reportId } = adminReportRoute.useParams(); const features = useFeatures(); const intl = useIntl(); diff --git a/packages/pl-fe/src/pages/dashboard/theme-editor.tsx b/packages/pl-fe/src/pages/dashboard/theme-editor.tsx index 49946a882..4297be77f 100644 --- a/packages/pl-fe/src/pages/dashboard/theme-editor.tsx +++ b/packages/pl-fe/src/pages/dashboard/theme-editor.tsx @@ -42,11 +42,8 @@ const messages = defineMessages({ colorGradientEnd: { id: 'theme_editor.colors.gradient_end', defaultMessage: 'Gradient End' }, }); -interface IThemeEditor { -} - /** UI for editing Tailwind theme colors. */ -const ThemeEditorPage: React.FC = () => { +const ThemeEditorPage: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); diff --git a/packages/pl-fe/src/pages/drive/drive.tsx b/packages/pl-fe/src/pages/drive/drive.tsx index 4566b08fd..c9ddf52e0 100644 --- a/packages/pl-fe/src/pages/drive/drive.tsx +++ b/packages/pl-fe/src/pages/drive/drive.tsx @@ -13,6 +13,7 @@ import Icon from 'pl-fe/components/ui/icon'; import IconButton from 'pl-fe/components/ui/icon-button'; import { MIMETYPE_ICONS } from 'pl-fe/components/upload'; import ColumnLoading from 'pl-fe/features/ui/components/column-loading'; +import { driveRoute } from 'pl-fe/features/ui/router'; import { useCreateDriveFileMutation, useDeleteDriveFileMutation, useMoveDriveFileMutation, useUpdateDriveFileMutation } from 'pl-fe/queries/drive/use-drive-file'; import { useCreateDriveFolderMutation, useDeleteDriveFolderMutation, useDriveFolderQuery, useMoveDriveFolderMutation, useUpdateDriveFolderMutation } from 'pl-fe/queries/drive/use-drive-folder'; import { useModalsActions } from 'pl-fe/stores/modals'; @@ -463,19 +464,15 @@ const Folder: React.FC = ({ folder }) => { ); }; -interface IDrivePage { - params?: { - folderId?: string; - }; -} +const DrivePage: React.FC = () => { + const { folderId } = driveRoute.useParams(); -const DrivePage: React.FC = ({ params }) => { const intl = useIntl(); const { openModal } = useModalsActions(); - const { data, isPending } = useDriveFolderQuery(params?.folderId); - const { mutate: uploadFile } = useCreateDriveFileMutation(params?.folderId); + const { data, isPending } = useDriveFolderQuery(folderId); + const { mutate: uploadFile } = useCreateDriveFileMutation(folderId); const { mutate: createFolder } = useCreateDriveFolderMutation(); const items: Menu = [ @@ -499,7 +496,7 @@ const DrivePage: React.FC = ({ params }) => { confirm: , singleLine: true, onConfirm: (value: string) => { - createFolder({ name: value, parentId: params?.folderId }, { + createFolder({ name: value, parentId: folderId }, { onSuccess: () => toast.success(messages.newFolderSuccess), onError: () => toast.error(messages.newFolderError), }); @@ -523,7 +520,7 @@ const DrivePage: React.FC = ({ params }) => { action={} >
- +
{isEmpty ? ( { + const { groupId } = editGroupRoute.useParams(); -const EditGroup: React.FC = ({ params: { groupId } }) => { const intl = useIntl(); const instance = useInstance(); diff --git a/packages/pl-fe/src/pages/groups/group-blocked-members.tsx b/packages/pl-fe/src/pages/groups/group-blocked-members.tsx index 2e9933a59..8cf9f22a0 100644 --- a/packages/pl-fe/src/pages/groups/group-blocked-members.tsx +++ b/packages/pl-fe/src/pages/groups/group-blocked-members.tsx @@ -10,11 +10,10 @@ import Column from 'pl-fe/components/ui/column'; import HStack from 'pl-fe/components/ui/hstack'; import Spinner from 'pl-fe/components/ui/spinner'; import ColumnForbidden from 'pl-fe/features/ui/components/column-forbidden'; +import { groupBlocksRoute } from 'pl-fe/features/ui/router'; import { useGroupBlocks, useUnblockGroupUserMutation } from 'pl-fe/queries/groups/use-group-blocks'; import toast from 'pl-fe/toast'; -type RouteParams = { groupId: string }; - const messages = defineMessages({ heading: { id: 'column.group_blocked_members', defaultMessage: 'Banned members' }, unblock: { id: 'group.group_mod_unblock', defaultMessage: 'Unban' }, @@ -54,15 +53,11 @@ const BlockedMember: React.FC = ({ accountId, groupId }) => { ); }; -interface IGroupBlockedMembers { - params: RouteParams; -} +const GroupBlockedMembers: React.FC = () => { + const { groupId } = groupBlocksRoute.useParams(); -const GroupBlockedMembers: React.FC = ({ params }) => { const intl = useIntl(); - const groupId = params?.groupId; - const { group } = useGroup(groupId); const { data: accountIds } = useGroupBlocks(groupId); diff --git a/packages/pl-fe/src/pages/groups/group-gallery.tsx b/packages/pl-fe/src/pages/groups/group-gallery.tsx index a1852a7ba..6dcf91f2f 100644 --- a/packages/pl-fe/src/pages/groups/group-gallery.tsx +++ b/packages/pl-fe/src/pages/groups/group-gallery.tsx @@ -6,16 +6,13 @@ import LoadMore from 'pl-fe/components/load-more'; import MissingIndicator from 'pl-fe/components/missing-indicator'; import Column from 'pl-fe/components/ui/column'; import Spinner from 'pl-fe/components/ui/spinner'; +import { groupGalleryRoute } from 'pl-fe/features/ui/router'; import { type AccountGalleryAttachment, useGroupGallery } from 'pl-fe/hooks/use-account-gallery'; import { MediaItem } from 'pl-fe/pages/accounts/account-gallery'; import { useModalsActions } from 'pl-fe/stores/modals'; -interface IGroupGallery { - params: { groupId: string }; -} - -const GroupGallery: React.FC = (props) => { - const { groupId } = props.params; +const GroupGallery: React.FC = () => { + const { groupId } = groupGalleryRoute.useParams(); const { openModal } = useModalsActions(); diff --git a/packages/pl-fe/src/pages/groups/group-members.tsx b/packages/pl-fe/src/pages/groups/group-members.tsx index ad36da058..6597bc736 100644 --- a/packages/pl-fe/src/pages/groups/group-members.tsx +++ b/packages/pl-fe/src/pages/groups/group-members.tsx @@ -8,14 +8,11 @@ import { PendingItemsRow } from 'pl-fe/components/pending-items-row'; import ScrollableList from 'pl-fe/components/scrollable-list'; import GroupMemberListItem from 'pl-fe/features/group/components/group-member-list-item'; import PlaceholderAccount from 'pl-fe/features/placeholder/components/placeholder-account'; +import { groupMembersRoute } from 'pl-fe/features/ui/router'; import { useGroupMembers } from 'pl-fe/queries/groups/use-group-members'; -interface IGroupMembers { - params: { groupId: string }; -} - -const GroupMembers: React.FC = (props) => { - const { groupId } = props.params; +const GroupMembers: React.FC = () => { + const { groupId } = groupMembersRoute.useParams(); const { group, isFetching: isFetchingGroup } = useGroup(groupId); const { data: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER); diff --git a/packages/pl-fe/src/pages/groups/group-membership-requests.tsx b/packages/pl-fe/src/pages/groups/group-membership-requests.tsx index a052cf6a6..5c82579b8 100644 --- a/packages/pl-fe/src/pages/groups/group-membership-requests.tsx +++ b/packages/pl-fe/src/pages/groups/group-membership-requests.tsx @@ -10,13 +10,12 @@ import Column from 'pl-fe/components/ui/column'; import HStack from 'pl-fe/components/ui/hstack'; import Spinner from 'pl-fe/components/ui/spinner'; import ColumnForbidden from 'pl-fe/features/ui/components/column-forbidden'; +import { groupMembershipRequestsRoute } from 'pl-fe/features/ui/router'; import toast from 'pl-fe/toast'; import type { Account as AccountEntity } from 'pl-api'; import type { PlfeResponse } from 'pl-fe/api'; -type RouteParams = { groupId: string }; - const messages = defineMessages({ heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' }, authorizeFail: { id: 'group.group_mod_authorize.fail', defaultMessage: 'Failed to approve @{name}' }, @@ -50,12 +49,9 @@ const MembershipRequest: React.FC = ({ account, onAuthorize, ); }; -interface IGroupMembershipRequests { - params: RouteParams; -} +const GroupMembershipRequests: React.FC = () => { + const { groupId } = groupMembershipRequestsRoute.useParams(); -const GroupMembershipRequests: React.FC = ({ params }) => { - const groupId = params?.groupId; const intl = useIntl(); const { group } = useGroup(groupId); diff --git a/packages/pl-fe/src/pages/groups/manage-group.tsx b/packages/pl-fe/src/pages/groups/manage-group.tsx index e653605bb..73d30fd0e 100644 --- a/packages/pl-fe/src/pages/groups/manage-group.tsx +++ b/packages/pl-fe/src/pages/groups/manage-group.tsx @@ -12,11 +12,10 @@ import Spinner from 'pl-fe/components/ui/spinner'; import Text from 'pl-fe/components/ui/text'; import Emojify from 'pl-fe/features/emoji/emojify'; import ColumnForbidden from 'pl-fe/features/ui/components/column-forbidden'; +import { manageGroupRoute } from 'pl-fe/features/ui/router'; import { useModalsActions } from 'pl-fe/stores/modals'; import toast from 'pl-fe/toast'; -type RouteParams = { groupId: string }; - const messages = defineMessages({ heading: { id: 'column.manage_group', defaultMessage: 'Manage group' }, editGroup: { id: 'manage_group.edit_group', defaultMessage: 'Edit group' }, @@ -31,18 +30,14 @@ const messages = defineMessages({ deleteSuccess: { id: 'group.delete.success', defaultMessage: 'Group successfully deleted' }, }); -interface IManageGroup { - params: RouteParams; -} - -const ManageGroup: React.FC = ({ params }) => { - const { groupId: id } = params; +const ManageGroup: React.FC = () => { + const { groupId } = manageGroupRoute.useParams(); const { openModal } = useModalsActions(); const history = useHistory(); const intl = useIntl(); - const { group } = useGroup(id); + const { group } = useGroup(groupId); const deleteGroup = useDeleteGroup(); diff --git a/packages/pl-fe/src/pages/settings/edit-filter.tsx b/packages/pl-fe/src/pages/settings/edit-filter.tsx index f643a1b2b..5a3d76380 100644 --- a/packages/pl-fe/src/pages/settings/edit-filter.tsx +++ b/packages/pl-fe/src/pages/settings/edit-filter.tsx @@ -19,6 +19,7 @@ import Streamfield from 'pl-fe/components/ui/streamfield'; import Text from 'pl-fe/components/ui/text'; import Toggle from 'pl-fe/components/ui/toggle'; import { SelectDropdown } from 'pl-fe/features/forms'; +import { editFilterRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useFeatures } from 'pl-fe/hooks/use-features'; import toast from 'pl-fe/toast'; @@ -32,10 +33,6 @@ interface IFilterField { _destroy?: boolean; } -interface IEditFilter { - params: { id?: string }; -} - const messages = defineMessages({ subheading_add_new: { id: 'column.filters.subheading_add_new', defaultMessage: 'Add new filter' }, title: { id: 'column.filters.title', defaultMessage: 'Title' }, @@ -97,7 +94,9 @@ const FilterField: StreamfieldComponent = ({ value, onChange }) => ); }; -const EditFilterPage: React.FC = ({ params }) => { +const EditFilterPage: React.FC = () => { + const { filterId } = editFilterRoute.useParams(); + const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); @@ -150,8 +149,8 @@ const EditFilterPage: React.FC = ({ params }) => { context.push('account'); } - dispatch(params.id - ? updateFilter(params.id, title, expiresIn, context, filterAction, keywords) + dispatch(filterId !== 'new' + ? updateFilter(filterId, title, expiresIn, context, filterAction, keywords) : createFilter(title, expiresIn, context, filterAction, keywords)).then(() => { history.push('/filters'); }).catch(() => { @@ -168,9 +167,9 @@ const EditFilterPage: React.FC = ({ params }) => { : keywords.filter((_, index) => index !== i)); useEffect(() => { - if (params.id) { + if (filterId !== 'new') { setLoading(true); - dispatch(fetchFilter(params.id))?.then((filter) => { + dispatch(fetchFilter(filterId))?.then((filter) => { if (filter) { setTitle(filter.title); setHomeTimeline(filter.context.includes('home')); @@ -186,7 +185,7 @@ const EditFilterPage: React.FC = ({ params }) => { setLoading(false); }); } - }, [params.id]); + }, [filterId]); if (notFound) return ; @@ -299,7 +298,7 @@ const EditFilterPage: React.FC = ({ params }) => { diff --git a/packages/pl-fe/src/pages/status-lists/bookmarks.tsx b/packages/pl-fe/src/pages/status-lists/bookmarks.tsx index 6ea93d4c1..589835302 100644 --- a/packages/pl-fe/src/pages/status-lists/bookmarks.tsx +++ b/packages/pl-fe/src/pages/status-lists/bookmarks.tsx @@ -6,6 +6,7 @@ import DropdownMenu from 'pl-fe/components/dropdown-menu'; import PullToRefresh from 'pl-fe/components/pull-to-refresh'; import StatusList from 'pl-fe/components/status-list'; import Column from 'pl-fe/components/ui/column'; +import { bookmarksRoute } from 'pl-fe/features/ui/router'; import { useBookmarks } from 'pl-fe/queries/status-lists/use-bookmarks'; import { useBookmarkFolder, useDeleteBookmarkFolder } from 'pl-fe/queries/statuses/use-bookmark-folders'; import { useModalsActions } from 'pl-fe/stores/modals'; @@ -22,17 +23,12 @@ const messages = defineMessages({ deleteFolderFail: { id: 'bookmarks.delete_folder.fail', defaultMessage: 'Failed to delete folder' }, }); -interface IBookmarks { - params?: { - id?: string; - }; -} - -const BookmarksPage: React.FC = ({ params }) => { +const BookmarksPage: React.FC = () => { const intl = useIntl(); const history = useHistory(); - const folderId = params?.id; + let folderId: string | undefined = bookmarksRoute.useParams().folderId; + if (folderId === 'all') folderId = undefined; const { openModal } = useModalsActions(); const { data: folder } = useBookmarkFolder(folderId); diff --git a/packages/pl-fe/src/pages/status-lists/favourited-statuses.tsx b/packages/pl-fe/src/pages/status-lists/favourited-statuses.tsx index 3091c1f01..343da99a3 100644 --- a/packages/pl-fe/src/pages/status-lists/favourited-statuses.tsx +++ b/packages/pl-fe/src/pages/status-lists/favourited-statuses.tsx @@ -5,6 +5,7 @@ import { useAccountLookup } from 'pl-fe/api/hooks/accounts/use-account-lookup'; import MissingIndicator from 'pl-fe/components/missing-indicator'; import StatusList from 'pl-fe/components/status-list'; import Column from 'pl-fe/components/ui/column'; +import { profileFavoritesRoute } from 'pl-fe/features/ui/router'; import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; import { useFavourites } from 'pl-fe/queries/status-lists/use-favourites'; @@ -12,19 +13,14 @@ const messages = defineMessages({ heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, }); -interface IFavourites { - params?: { - username?: string; - }; -} - /** Timeline displaying a user's favourited statuses. */ -const FavouritedStatusesPage: React.FC = ({ params }) => { +const FavouritedStatusesPage: React.FC = () => { + const { username } = profileFavoritesRoute.useParams(); + const intl = useIntl(); const { account: ownAccount } = useOwnAccount(); - const { account, isUnavailable } = useAccountLookup(params?.username, { withRelationship: true }); + const { account, isUnavailable } = useAccountLookup(username, { withRelationship: true }); - const username = params?.username || ''; const isOwnAccount = username.toLowerCase() === ownAccount?.acct?.toLowerCase(); const accountId = isOwnAccount ? undefined : account?.id; diff --git a/packages/pl-fe/src/pages/statuses/compose-event.tsx b/packages/pl-fe/src/pages/statuses/compose-event.tsx index 49dfc88c4..74f227545 100644 --- a/packages/pl-fe/src/pages/statuses/compose-event.tsx +++ b/packages/pl-fe/src/pages/statuses/compose-event.tsx @@ -7,6 +7,7 @@ import Stack from 'pl-fe/components/ui/stack'; import Tabs from 'pl-fe/components/ui/tabs'; import { EditEvent } from 'pl-fe/features/compose-event/tabs/edit-event'; import { ManagePendingParticipants } from 'pl-fe/features/compose-event/tabs/manage-pending-participants'; +import { eventEditRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; const messages = defineMessages({ @@ -16,19 +17,11 @@ const messages = defineMessages({ pending: { id: 'compose_event.tabs.pending', defaultMessage: 'Manage requests' }, }); -type RouteParams = { - statusId?: string; -}; - -interface IComposeEventPage { - params: RouteParams; -} - -const ComposeEventPage: React.FC = ({ params }) => { +const EditEventPage = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const statusId = params.statusId || null; + const { statusId } = eventEditRoute.useParams(); const [tab, setTab] = useState<'edit' | 'pending'>('edit'); @@ -54,13 +47,28 @@ const ComposeEventPage: React.FC = ({ params }) => { }; return ( - + - {statusId && renderTabs()} - {tab === 'edit' ? : } + {renderTabs()} + {tab === 'edit' ? : } ); }; -export { ComposeEventPage as default }; +const ComposeEventPage = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + useEffect(() => () => { + dispatch(cancelEventCompose()); + }, []); + + return ( + + + + ); +}; + +export { ComposeEventPage as default, EditEventPage }; diff --git a/packages/pl-fe/src/pages/statuses/event-discussion.tsx b/packages/pl-fe/src/pages/statuses/event-discussion.tsx index 0cd1a724b..f8674993b 100644 --- a/packages/pl-fe/src/pages/statuses/event-discussion.tsx +++ b/packages/pl-fe/src/pages/statuses/event-discussion.tsx @@ -11,24 +11,18 @@ import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder import { makeGetDescendantsIds } from 'pl-fe/features/status/components/thread'; import ThreadStatus from 'pl-fe/features/status/components/thread-status'; import PendingStatus from 'pl-fe/features/ui/components/pending-status'; +import { eventDiscussionRoute } from 'pl-fe/features/ui/router'; import { ComposeForm } from 'pl-fe/features/ui/util/async-components'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { makeGetStatus } from 'pl-fe/selectors'; import { selectChild } from 'pl-fe/utils/scroll-utils'; -import type { MediaAttachment } from 'pl-api'; import type { VirtuosoHandle } from 'react-virtuoso'; -type RouteParams = { statusId: string }; +const EventDiscussionPage: React.FC = () => { + const { statusId } = eventDiscussionRoute.useParams(); -interface IEventDiscussion { - params: RouteParams; - onOpenMedia: (media: Array, index: number) => void; - onOpenVideo: (video: MediaAttachment, time: number) => void; -} - -const EventDiscussionPage: React.FC = ({ params: { statusId: statusId } }) => { const intl = useIntl(); const dispatch = useAppDispatch(); diff --git a/packages/pl-fe/src/pages/statuses/event-information.tsx b/packages/pl-fe/src/pages/statuses/event-information.tsx index a6ca1c004..451df5aaa 100644 --- a/packages/pl-fe/src/pages/statuses/event-information.tsx +++ b/packages/pl-fe/src/pages/statuses/event-information.tsx @@ -8,24 +8,21 @@ import HStack from 'pl-fe/components/ui/hstack'; import Icon from 'pl-fe/components/ui/icon'; import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; +import { eventInformationRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; import { makeGetStatus } from 'pl-fe/selectors'; import { useModalsActions } from 'pl-fe/stores/modals'; -type RouteParams = { statusId: string }; +const EventInformationPage: React.FC = () => { + const { statusId } = eventInformationRoute.useParams(); -interface IEventInformation { - params: RouteParams; -} - -const EventInformationPage: React.FC = ({ params }) => { const dispatch = useAppDispatch(); const getStatus = useCallback(makeGetStatus(), []); const intl = useIntl(); - const status = useAppSelector(state => getStatus(state, { id: params.statusId }))!; + const status = useAppSelector(state => getStatus(state, { id: statusId }))!; const { openModal } = useModalsActions(); const { tileServer } = usePlFeConfig(); @@ -33,12 +30,12 @@ const EventInformationPage: React.FC = ({ params }) => { const [isLoaded, setIsLoaded] = useState(!!status); useEffect(() => { - dispatch(fetchStatus(params.statusId, intl)).then(() => { + dispatch(fetchStatus(statusId, intl)).then(() => { setIsLoaded(true); }).catch(() => { setIsLoaded(true); }); - }, [params.statusId]); + }, [statusId]); const handleShowMap: React.MouseEventHandler = (e) => { e.preventDefault(); diff --git a/packages/pl-fe/src/pages/statuses/status.tsx b/packages/pl-fe/src/pages/statuses/status.tsx index 5cfd5290e..c07c4c917 100644 --- a/packages/pl-fe/src/pages/statuses/status.tsx +++ b/packages/pl-fe/src/pages/statuses/status.tsx @@ -11,6 +11,7 @@ import Column from 'pl-fe/components/ui/column'; import Stack from 'pl-fe/components/ui/stack'; import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status'; import Thread from 'pl-fe/features/status/components/thread'; +import { statusRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { makeGetStatus } from 'pl-fe/selectors'; @@ -37,22 +38,14 @@ const messages = defineMessages({ expandAll: { id: 'status.thread.expand_all', defaultMessage: 'Expand all posts' }, }); -type RouteParams = { - statusId: string; - groupId?: string; -}; +const StatusPage: React.FC = () => { + const { statusId } = statusRoute.useParams(); -interface IStatusDetails { - params: RouteParams; -} - -const StatusPage: React.FC = (props) => { const dispatch = useAppDispatch(); const intl = useIntl(); const getStatus = useCallback(makeGetStatus(), []); - const status = useAppSelector((state) => getStatus(state, { id: props.params.statusId })); - + const status = useAppSelector((state) => getStatus(state, { id: statusId })); const [expandAllStatuses, setExpandAllStatuses] = useState<() => void>(); const [isLoaded, setIsLoaded] = useState(!!status); @@ -60,8 +53,6 @@ const StatusPage: React.FC = (props) => { /** Fetch the status (and context) from the API. */ const fetchData = () => { - const { params } = props; - const { statusId } = params; return dispatch(fetchStatusWithContext(statusId, intl)); }; @@ -72,7 +63,7 @@ const StatusPage: React.FC = (props) => { }).catch(() => { setIsLoaded(true); }); - }, [props.params.statusId]); + }, [statusId]); const handleRefresh = () => fetchData(); @@ -125,12 +116,6 @@ const StatusPage: React.FC = (props) => { ); } - if (status.group && typeof status.group === 'object') { - if (status.group.id && !props.params.groupId) { - return ; - } - } - const titleMessage = () => { if (status.visibility === 'direct') return messages.titleDirect; return messages.title; diff --git a/packages/pl-fe/src/pages/timelines/group-timeline.tsx b/packages/pl-fe/src/pages/timelines/group-timeline.tsx index d021ef695..1f2a02ac9 100644 --- a/packages/pl-fe/src/pages/timelines/group-timeline.tsx +++ b/packages/pl-fe/src/pages/timelines/group-timeline.tsx @@ -11,6 +11,7 @@ import Avatar from 'pl-fe/components/ui/avatar'; import HStack from 'pl-fe/components/ui/hstack'; import Stack from 'pl-fe/components/ui/stack'; import Timeline from 'pl-fe/features/ui/components/timeline'; +import { groupTimelineRoute } from 'pl-fe/features/ui/router'; import { ComposeForm } from 'pl-fe/features/ui/util/async-components'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; @@ -18,22 +19,16 @@ import { useDraggedFiles } from 'pl-fe/hooks/use-dragged-files'; import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; import { makeGetStatusIds } from 'pl-fe/selectors'; -type RouteParams = { groupId: string }; - -interface IGroupTimelinePage { - params: RouteParams; -} - const getStatusIds = makeGetStatusIds(); -const GroupTimelinePage: React.FC = (props) => { +const GroupTimelinePage: React.FC = () => { + const { groupId } = groupTimelineRoute.useParams(); + const intl = useIntl(); const { account } = useOwnAccount(); const dispatch = useAppDispatch(); const composer = useRef(null); - const { groupId } = props.params; - const { group } = useGroup(groupId); const composeId = `group:${groupId}`; diff --git a/packages/pl-fe/src/pages/timelines/hashtag-timeline.tsx b/packages/pl-fe/src/pages/timelines/hashtag-timeline.tsx index 5e384d3f2..624419fea 100644 --- a/packages/pl-fe/src/pages/timelines/hashtag-timeline.tsx +++ b/packages/pl-fe/src/pages/timelines/hashtag-timeline.tsx @@ -7,20 +7,15 @@ import List, { ListItem } from 'pl-fe/components/list'; import Column from 'pl-fe/components/ui/column'; import Toggle from 'pl-fe/components/ui/toggle'; import Timeline from 'pl-fe/features/ui/components/timeline'; +import { hashtagTimelineRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useFeatures } from 'pl-fe/hooks/use-features'; import { useLoggedIn } from 'pl-fe/hooks/use-logged-in'; import { useFollowHashtagMutation, useUnfollowHashtagMutation } from 'pl-fe/queries/hashtags/use-followed-tags'; import { useHashtag } from 'pl-fe/queries/hashtags/use-hashtag'; -interface IHashtagTimelinePage { - params?: { - id?: string; - }; -} - -const HashtagTimelinePage: React.FC = ({ params }) => { - const tagId = params?.id || ''; +const HashtagTimelinePage: React.FC = () => { + const { id: tagId } = hashtagTimelineRoute.useParams(); const features = useFeatures(); const dispatch = useAppDispatch(); diff --git a/packages/pl-fe/src/pages/timelines/link-timeline.tsx b/packages/pl-fe/src/pages/timelines/link-timeline.tsx index 2a6f3c000..2ad5f033c 100644 --- a/packages/pl-fe/src/pages/timelines/link-timeline.tsx +++ b/packages/pl-fe/src/pages/timelines/link-timeline.tsx @@ -4,41 +4,37 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { clearTimeline, fetchLinkTimeline } from 'pl-fe/actions/timelines'; import Column from 'pl-fe/components/ui/column'; import Timeline from 'pl-fe/features/ui/components/timeline'; +import { linkTimelineRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; const messages = defineMessages({ header: { id: 'column.link_timeline', defaultMessage: 'Posts linking to {url}' }, }); -interface ILinkTimelinePage { - params?: { - url?: string; - }; -} - -const LinkTimelinePage: React.FC = ({ params }) => { - const url = decodeURIComponent(params?.url || ''); +const LinkTimelinePage: React.FC = () => { + const { url } = linkTimelineRoute.useParams(); + const decodedUrl = decodeURIComponent(url || ''); const intl = useIntl(); const dispatch = useAppDispatch(); const handleLoadMore = () => { - dispatch(fetchLinkTimeline(url, true)); + dispatch(fetchLinkTimeline(decodedUrl, true)); }; useEffect(() => { - dispatch(clearTimeline(`link:${url}`)); - dispatch(fetchLinkTimeline(url)); - }, [url]); + dispatch(clearTimeline(`link:${decodedUrl}`)); + dispatch(fetchLinkTimeline(decodedUrl)); + }, [decodedUrl]); return ( } emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')} diff --git a/packages/pl-fe/src/pages/timelines/remote-timeline.tsx b/packages/pl-fe/src/pages/timelines/remote-timeline.tsx index b3d57af0a..fde83a2fb 100644 --- a/packages/pl-fe/src/pages/timelines/remote-timeline.tsx +++ b/packages/pl-fe/src/pages/timelines/remote-timeline.tsx @@ -10,21 +10,17 @@ import IconButton from 'pl-fe/components/ui/icon-button'; import Text from 'pl-fe/components/ui/text'; import PinnedHostsPicker from 'pl-fe/features/remote-timeline/components/pinned-hosts-picker'; import Timeline from 'pl-fe/features/ui/components/timeline'; +import { remoteTimelineRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useSettings } from 'pl-fe/stores/settings'; -interface IRemoteTimelinePage { - params?: { - instance?: string; - }; -} - /** View statuses from a remote instance. */ -const RemoteTimelinePage: React.FC = ({ params }) => { +const RemoteTimelinePage: React.FC = () => { + const { instance } = remoteTimelineRoute.useParams(); + const history = useHistory(); const dispatch = useAppDispatch(); - const instance = params?.instance as string; const settings = useSettings(); const timelineId = 'remote'; diff --git a/packages/pl-fe/src/pages/utils/share.tsx b/packages/pl-fe/src/pages/utils/share.tsx index 171114469..c28ad10b3 100644 --- a/packages/pl-fe/src/pages/utils/share.tsx +++ b/packages/pl-fe/src/pages/utils/share.tsx @@ -1,31 +1,26 @@ +import { useNavigate } from '@tanstack/react-router'; import React, { useEffect } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; import { openComposeWithText } from 'pl-fe/actions/compose'; +import { shareRoute } from 'pl-fe/features/ui/router'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; const SharePage: React.FC = () => { const dispatch = useAppDispatch(); - const history = useHistory(); + const navigate = useNavigate(); - const { search } = useLocation(); + const params = shareRoute.useSearch(); useEffect(() => { - const params = new URLSearchParams(search); - - const text = [ - params.get('title'), - params.get('text'), - params.get('url'), - ] + const text = [params.title, params.text, params.url] .filter(v => v) .join('\n\n'); + navigate({ to: '/' }); + if (text) { dispatch(openComposeWithText('compose-modal', text)); } - - history.replace('/'); }); return null; diff --git a/packages/pl-fe/src/reducers/instance.ts b/packages/pl-fe/src/reducers/instance.ts index 938155011..720eb3209 100644 --- a/packages/pl-fe/src/reducers/instance.ts +++ b/packages/pl-fe/src/reducers/instance.ts @@ -100,4 +100,4 @@ const instance = (state = initialState, action: AdminActions | InstanceAction | return state; } }; -export { instance as default }; +export { instance as default, initialState as instanceInitialState }; diff --git a/packages/pl-fe/vite.config.ts b/packages/pl-fe/vite.config.ts index d6f727d25..5d6ff8a8d 100644 --- a/packages/pl-fe/vite.config.ts +++ b/packages/pl-fe/vite.config.ts @@ -74,9 +74,7 @@ const config = defineConfig(({ command }) => ({ }, ], display: 'standalone', - display_override: [ - 'window-controls-overlay', - ], + display_override: ['window-controls-overlay'], theme_color: '#d80482', categories: ['social'], share_target: { @@ -130,25 +128,32 @@ const config = defineConfig(({ command }) => ({ filename: 'sw.ts', }), viteStaticCopy({ - targets: [{ - src: './node_modules/@twemoji/svg/*', - dest: 'packs/emoji/', - }, { - src: './favicon.ico', - dest: '.', - }, { - src: './src/instance', - dest: '.', - }, { - src: './custom/instance', - dest: '.', - }, { - src: './node_modules/fasttext.wasm.js/dist/models/language-identification/assets/lid.176.ftz', - dest: 'fastText/models/', - }, { - src: './node_modules/fasttext.wasm.js/dist/core/fastText.common.wasm', - dest: 'fastText/', - }], + targets: [ + { + src: './node_modules/@twemoji/svg/*', + dest: 'packs/emoji/', + }, + { + src: './favicon.ico', + dest: '.', + }, + { + src: './src/instance', + dest: '.', + }, + { + src: './custom/instance', + dest: '.', + }, + { + src: './node_modules/fasttext.wasm.js/dist/models/language-identification/assets/lid.176.ftz', + dest: 'fastText/models/', + }, + { + src: './node_modules/fasttext.wasm.js/dist/core/fastText.common.wasm', + dest: 'fastText/', + }, + ], }), bundleStats(), { @@ -167,7 +172,10 @@ const config = defineConfig(({ command }) => ({ ], resolve: { alias: [ - { find: 'pl-fe', replacement: fileURLToPath(new URL('./src', import.meta.url)) }, + { + find: 'pl-fe', + replacement: fileURLToPath(new URL('./src', import.meta.url)), + }, ], dedupe: ['@floating-ui/react', 'tabbable', 'query-string', 'valibot'], }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 731c07aba..156459246 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: '@tanstack/react-router-devtools': specifier: ^1.141.6 version: 1.141.6(@tanstack/react-router@1.141.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.141.6)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.10) + '@tanstack/router-plugin': + specifier: ^1.141.7 + version: 1.141.7(@tanstack/react-router@1.141.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.17.0)(sass-embedded@1.93.3)(sass@1.93.3)(terser@5.44.0))(webpack@5.101.0(esbuild@0.24.2)) '@transfem-org/sfm-js': specifier: ^0.24.6 version: 0.24.8 @@ -672,10 +675,6 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} @@ -696,6 +695,10 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -710,6 +713,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.27.1': resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} engines: {node: '>=6.9.0'} @@ -729,6 +738,10 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -777,6 +790,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -803,6 +820,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -851,6 +873,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -1151,6 +1185,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-escapes@7.27.1': resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -1186,6 +1226,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.0.0': resolution: {integrity: sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==} @@ -1209,6 +1255,10 @@ packages: resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} @@ -1217,6 +1267,10 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@bufbuild/protobuf@2.10.1': resolution: {integrity: sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==} @@ -1316,6 +1370,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} @@ -1328,6 +1388,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} @@ -1340,6 +1406,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} @@ -1352,6 +1424,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} @@ -1364,6 +1442,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} @@ -1376,6 +1460,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} @@ -1388,6 +1478,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} @@ -1400,6 +1496,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} @@ -1412,6 +1514,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} @@ -1424,6 +1532,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} @@ -1436,6 +1550,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} @@ -1448,6 +1568,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} @@ -1460,6 +1586,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} @@ -1472,6 +1604,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} @@ -1484,6 +1622,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} @@ -1496,6 +1640,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} @@ -1508,12 +1658,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.24.2': resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -1526,12 +1688,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.24.2': resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} @@ -1544,6 +1718,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} @@ -1556,6 +1742,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} @@ -1568,6 +1760,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} @@ -1580,6 +1778,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} @@ -1592,6 +1796,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2422,12 +2632,45 @@ packages: csstype: optional: true + '@tanstack/router-generator@1.141.7': + resolution: {integrity: sha512-SgOI/PmG3IGRf5q9bbYVE9xH1tP1ah0jIzGiI2w1D1nlljU+rd1DpSY7kEr9P6EHJpwDeb50DNi4Aq1WbEljSQ==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.141.7': + resolution: {integrity: sha512-znYaRYaUIEl2uJ+lP2qkC//dKtowb2IwU7jOGa7ygnCRVpK3TcTUMezfyI67jfDiB0rM8ICj5sqONfhN9I/F2g==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.141.6 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' + vite-plugin-solid: ^2.11.10 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.141.0': + resolution: {integrity: sha512-/eFGKCiix1SvjxwgzrmH4pHjMiMxc+GA4nIbgEkG2RdAJqyxLcRhd7RPLG0/LZaJ7d0ad3jrtRqsHLv2152Vbw==} + engines: {node: '>=12'} + '@tanstack/store@0.7.7': resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==} '@tanstack/store@0.8.0': resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + '@tanstack/virtual-file-routes@1.141.0': + resolution: {integrity: sha512-CJrWtr6L9TVzEImm9S7dQINx+xJcYP/aDkIi6gnaWtIgbZs1pnzsE0yJc2noqXZ+yAOqLx3TBGpBEs9tS0P9/A==} + engines: {node: '>=12'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -3070,6 +3313,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -3143,6 +3390,10 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -3180,6 +3431,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-dead-code-elimination@1.0.11: + resolution: {integrity: sha512-mwq3W3e/pKSI6TG8lXMiDWvEi1VXYlSBlJlB3l+I0bAb5u1RNUl88udos85eOPNK3m5EXK9uO7d2g08pesTySQ==} + babel-plugin-polyfill-corejs2@0.4.14: resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -3616,6 +3870,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3785,6 +4043,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3945,6 +4208,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -4037,6 +4305,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@10.1.3: resolution: {integrity: sha512-D+w75Ub8T55yor7fPgN06rkCAUbAYw2vpxJmmjv/GDAcvCnv9g7IvHhIZoxzRZThrXPFI2maeY24pPbtyYU7Lg==} @@ -5446,6 +5723,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -5682,6 +5964,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -6110,6 +6396,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -6422,6 +6712,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6477,6 +6771,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -6589,6 +6888,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -6826,6 +7129,9 @@ packages: resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + webpack@5.101.0: resolution: {integrity: sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==} engines: {node: '>=10.13.0'} @@ -6991,6 +7297,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand-mutative@1.3.0: resolution: {integrity: sha512-bnW3k8PO/eqz39w86W84DjZIxRR8ZDHO9l6MYzxYhkXJknoz7Wl4L5HZSCLDmZy2tphUKso3cVjNm6YG5QM5YA==} peerDependencies: @@ -7025,8 +7334,8 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': dependencies: @@ -7049,8 +7358,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.0': {} - '@babel/compat-data@7.28.4': {} '@babel/core@7.28.0': @@ -7063,8 +7370,8 @@ snapshots: '@babel/helpers': 7.28.2 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -7096,7 +7403,7 @@ snapshots: '@babel/generator@7.28.0': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 @@ -7109,15 +7416,23 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.4 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.0 + '@babel/compat-data': 7.28.4 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.1 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -7134,6 +7449,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -7161,10 +7489,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -7173,7 +7508,7 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color @@ -7204,9 +7539,9 @@ snapshots: '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -7221,6 +7556,8 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': @@ -7234,7 +7571,7 @@ snapshots: '@babel/helpers@7.28.2': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@babel/helpers@7.28.4': dependencies: @@ -7243,12 +7580,16 @@ snapshots: '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@babel/parser@7.28.4': dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -7298,6 +7639,16 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -7624,6 +7975,17 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -7730,6 +8092,17 @@ snapshots: '@babel/types': 7.28.4 esutils: 2.0.3 + '@babel/preset-typescript@7.28.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.0.0': dependencies: regenerator-runtime: 0.12.1 @@ -7741,8 +8114,8 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@babel/traverse@7.28.0': dependencies: @@ -7751,7 +8124,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -7768,6 +8141,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.2': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -7778,6 +8163,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bufbuild/protobuf@2.10.1': {} '@bundle-stats/cli-utils@4.21.1(core-js@3.44.0)': @@ -7876,144 +8266,222 @@ snapshots: '@esbuild/aix-ppc64@0.24.2': optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true '@esbuild/android-arm64@0.24.2': optional: true + '@esbuild/android-arm64@0.27.2': + optional: true + '@esbuild/android-arm@0.21.5': optional: true '@esbuild/android-arm@0.24.2': optional: true + '@esbuild/android-arm@0.27.2': + optional: true + '@esbuild/android-x64@0.21.5': optional: true '@esbuild/android-x64@0.24.2': optional: true + '@esbuild/android-x64@0.27.2': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true '@esbuild/darwin-arm64@0.24.2': optional: true + '@esbuild/darwin-arm64@0.27.2': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true '@esbuild/darwin-x64@0.24.2': optional: true + '@esbuild/darwin-x64@0.27.2': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true '@esbuild/freebsd-arm64@0.24.2': optional: true + '@esbuild/freebsd-arm64@0.27.2': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true '@esbuild/freebsd-x64@0.24.2': optional: true + '@esbuild/freebsd-x64@0.27.2': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true '@esbuild/linux-arm64@0.24.2': optional: true + '@esbuild/linux-arm64@0.27.2': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true '@esbuild/linux-arm@0.24.2': optional: true + '@esbuild/linux-arm@0.27.2': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true '@esbuild/linux-ia32@0.24.2': optional: true + '@esbuild/linux-ia32@0.27.2': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true '@esbuild/linux-loong64@0.24.2': optional: true + '@esbuild/linux-loong64@0.27.2': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true '@esbuild/linux-mips64el@0.24.2': optional: true + '@esbuild/linux-mips64el@0.27.2': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true '@esbuild/linux-ppc64@0.24.2': optional: true + '@esbuild/linux-ppc64@0.27.2': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true '@esbuild/linux-riscv64@0.24.2': optional: true + '@esbuild/linux-riscv64@0.27.2': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true '@esbuild/linux-s390x@0.24.2': optional: true + '@esbuild/linux-s390x@0.27.2': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true '@esbuild/linux-x64@0.24.2': optional: true + '@esbuild/linux-x64@0.27.2': + optional: true + '@esbuild/netbsd-arm64@0.24.2': optional: true + '@esbuild/netbsd-arm64@0.27.2': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true '@esbuild/netbsd-x64@0.24.2': optional: true + '@esbuild/netbsd-x64@0.27.2': + optional: true + '@esbuild/openbsd-arm64@0.24.2': optional: true + '@esbuild/openbsd-arm64@0.27.2': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true '@esbuild/openbsd-x64@0.24.2': optional: true + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true '@esbuild/sunos-x64@0.24.2': optional: true + '@esbuild/sunos-x64@0.27.2': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true '@esbuild/win32-arm64@0.24.2': optional: true + '@esbuild/win32-arm64@0.27.2': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true '@esbuild/win32-ia32@0.24.2': optional: true + '@esbuild/win32-ia32@0.27.2': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true '@esbuild/win32-x64@0.24.2': optional: true + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -8201,8 +8669,8 @@ snapshots: '@jridgewell/source-map@0.3.10': dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/source-map@0.3.11': dependencies: @@ -9009,10 +9477,61 @@ snapshots: optionalDependencies: csstype: 3.1.3 + '@tanstack/router-generator@1.141.7': + dependencies: + '@tanstack/router-core': 1.141.6 + '@tanstack/router-utils': 1.141.0 + '@tanstack/virtual-file-routes': 1.141.0 + prettier: 3.7.4 + recast: 0.23.11 + source-map: 0.7.6 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.141.7(@tanstack/react-router@1.141.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.17.0)(sass-embedded@1.93.3)(sass@1.93.3)(terser@5.44.0))(webpack@5.101.0(esbuild@0.24.2))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@tanstack/router-core': 1.141.6 + '@tanstack/router-generator': 1.141.7 + '@tanstack/router-utils': 1.141.0 + '@tanstack/virtual-file-routes': 1.141.0 + babel-dead-code-elimination: 1.0.11 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.141.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + vite: 5.4.21(@types/node@22.17.0)(sass-embedded@1.93.3)(sass@1.93.3)(terser@5.44.0) + webpack: 5.101.0(esbuild@0.24.2) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.141.0': + dependencies: + '@babel/core': 7.28.4 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.4) + ansis: 4.2.0 + diff: 8.0.2 + pathe: 2.0.3 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - supports-color + '@tanstack/store@0.7.7': {} '@tanstack/store@0.8.0': {} + '@tanstack/virtual-file-routes@1.141.0': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -9079,23 +9598,23 @@ snapshots: '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/dom-chromium-ai@0.0.11': {} @@ -9316,7 +9835,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.7.3) '@typescript-eslint/types': 8.38.0 - debug: 4.4.1 + debug: 4.4.3 typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -9325,7 +9844,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.9.2) '@typescript-eslint/types': 8.38.0 - debug: 4.4.1 + debug: 4.4.3 typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -9610,7 +10129,7 @@ snapshots: '@vue/compiler-core@3.5.18': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.4 '@vue/shared': 3.5.18 entities: 4.5.0 estree-walker: 2.0.2 @@ -9821,6 +10340,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@4.2.0: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -9921,6 +10442,10 @@ snapshots: ast-types-flow@0.0.8: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + astral-regex@2.0.0: {} async-function@1.0.0: {} @@ -9949,6 +10474,15 @@ snapshots: axobject-query@4.1.0: {} + babel-dead-code-elimination@1.0.11: + dependencies: + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: '@babel/compat-data': 7.28.4 @@ -10064,7 +10598,7 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.25.1 + browserslist: 4.27.0 caniuse-lite: 1.0.30001731 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -10376,6 +10910,8 @@ snapshots: didyoumean@1.2.2: {} + diff@8.0.2: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -10649,6 +11185,35 @@ snapshots: '@esbuild/win32-ia32': 0.24.2 '@esbuild/win32-x64': 0.24.2 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -10992,6 +11557,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -11070,6 +11637,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@10.1.3: dependencies: flat-cache: 6.1.12 @@ -12471,6 +13042,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.7.4: {} + pretty-bytes@5.6.0: {} pretty-bytes@6.1.1: {} @@ -12733,6 +13306,14 @@ snapshots: readdirp@4.1.2: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -13170,6 +13751,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 @@ -13320,7 +13903,7 @@ snapshots: stylehacks@6.1.1(postcss@8.5.6): dependencies: - browserslist: 4.25.1 + browserslist: 4.27.0 postcss: 8.5.6 postcss-selector-parser: 6.1.2 @@ -13557,6 +14140,11 @@ snapshots: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@1.1.1: {} tinyrainbow@1.2.0: {} @@ -13608,6 +14196,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -13716,6 +14311,13 @@ snapshots: universalify@2.0.1: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.2 @@ -14025,6 +14627,8 @@ snapshots: webpack-sources@3.3.3: {} + webpack-virtual-modules@0.6.2: {} + webpack@5.101.0(esbuild@0.24.2): dependencies: '@types/eslint-scope': 3.7.7 @@ -14286,6 +14890,8 @@ snapshots: yocto-queue@0.1.0: {} + zod@3.25.76: {} + zustand-mutative@1.3.0(@types/react@18.3.23)(mutative@1.2.0)(react@18.3.1)(zustand@5.0.7(@types/react@18.3.23)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))): dependencies: '@types/react': 18.3.23