diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index daffc597f7..503a212b7e 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -7,6 +7,7 @@ "@affine/core": "workspace:*", "@affine/error": "workspace:*", "@affine/graphql": "workspace:*", + "@affine/routes": "workspace:*", "@blocksuite/icons": "^2.2.12", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-alert-dialog": "^1.1.3", @@ -50,7 +51,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.54.1", "react-resizable-panels": "^2.1.7", - "react-router-dom": "^6.28.0", + "react-router-dom": "^7.5.1", "sonner": "^2.0.0", "swr": "^2.2.5", "vaul": "^1.1.1", diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx index f8ba35b90c..320f461516 100644 --- a/packages/frontend/admin/src/app.tsx +++ b/packages/frontend/admin/src/app.tsx @@ -1,11 +1,13 @@ import { Toaster } from '@affine/admin/components/ui/sonner'; -import { wrapCreateBrowserRouterV6 } from '@sentry/react'; +import { lazy, ROUTES } from '@affine/routes'; +import { withSentryReactRouterV7Routing } from '@sentry/react'; import { useEffect } from 'react'; import { - createBrowserRouter as reactRouterCreateBrowserRouter, + BrowserRouter, Navigate, Outlet, - RouterProvider, + Route, + Routes as ReactRouterRoutes, useLocation, } from 'react-router-dom'; import { toast } from 'sonner'; @@ -15,13 +17,28 @@ import { TooltipProvider } from './components/ui/tooltip'; import { isAdmin, useCurrentUser, useServerConfig } from './modules/common'; import { Layout } from './modules/layout'; -const createBrowserRouter = wrapCreateBrowserRouterV6( - reactRouterCreateBrowserRouter +export const Setup = lazy( + () => import(/* webpackChunkName: "setup" */ './modules/setup') +); +export const Accounts = lazy( + () => import(/* webpackChunkName: "accounts" */ './modules/accounts') +); +export const AI = lazy( + () => import(/* webpackChunkName: "ai" */ './modules/ai') +); +export const About = lazy( + () => import(/* webpackChunkName: "about" */ './modules/about') +); +export const Settings = lazy( + () => import(/* webpackChunkName: "settings" */ './modules/settings') +); +export const Auth = lazy( + () => import(/* webpackChunkName: "auth" */ './modules/auth') ); -const _createBrowserRouter = window.SENTRY_RELEASE - ? createBrowserRouter - : reactRouterCreateBrowserRouter; +const Routes = window.SENTRY_RELEASE + ? withSentryReactRouterV7Routing(ReactRouterRoutes) + : ReactRouterRoutes; function AuthenticatedRoutes() { const user = useCurrentUser(); @@ -58,57 +75,6 @@ function RootRoutes() { return ; } -export const router = _createBrowserRouter( - [ - { - path: '/admin', - element: , - children: [ - { - path: '/admin/auth', - lazy: () => import('./modules/auth'), - }, - { - path: '/admin/setup', - lazy: () => import('./modules/setup'), - }, - { - path: '/admin/*', - element: , - children: [ - { - path: 'accounts', - lazy: () => import('./modules/accounts'), - }, - { - path: 'ai', - lazy: () => import('./modules/ai'), - }, - { - path: 'about', - lazy: () => import('./modules/about'), - }, - { - path: 'settings', - children: [ - { - path: '*', - lazy: () => import('./modules/settings'), - }, - ], - }, - ], - }, - ], - }, - ], - { - future: { - v7_normalizeFormMethod: true, - }, - } -); - export const App = () => { return ( @@ -118,7 +84,28 @@ export const App = () => { revalidateOnMount: false, }} > - + + + }> + } /> + } /> + }> + } /> + } /> + } /> + } + > + } + /> + + + + + diff --git a/packages/frontend/admin/src/modules/settings/index.tsx b/packages/frontend/admin/src/modules/settings/index.tsx index 929942e64a..4e523f2797 100644 --- a/packages/frontend/admin/src/modules/settings/index.tsx +++ b/packages/frontend/admin/src/modules/settings/index.tsx @@ -62,7 +62,7 @@ export function SettingsPage() { ); } -export const AdminPanel = ({ +const AdminPanel = ({ appConfig, patchedAppConfig, onUpdate, diff --git a/packages/frontend/admin/src/modules/setup/form.tsx b/packages/frontend/admin/src/modules/setup/form.tsx index 57e6bc6e62..4a398de291 100644 --- a/packages/frontend/admin/src/modules/setup/form.tsx +++ b/packages/frontend/admin/src/modules/setup/form.tsx @@ -165,7 +165,7 @@ export const Form = () => { passwordValue, ]); - const onPrevious = useCallback(() => { + const onPrevious = useAsyncCallback(async () => { if (current === count) { if (serverConfig.initialized === true) { return navigate('/admin', { replace: true }); diff --git a/packages/frontend/admin/tsconfig.json b/packages/frontend/admin/tsconfig.json index 85cab81c21..10c9b44c16 100644 --- a/packages/frontend/admin/tsconfig.json +++ b/packages/frontend/admin/tsconfig.json @@ -12,6 +12,7 @@ { "path": "../core" }, { "path": "../../common/error" }, { "path": "../../common/graphql" }, + { "path": "../routes" }, { "path": "../../common/infra" } ] } diff --git a/packages/frontend/routes/package.json b/packages/frontend/routes/package.json index e70b33168c..1bf35682ea 100644 --- a/packages/frontend/routes/package.json +++ b/packages/frontend/routes/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "exports": { - ".": "./src/routes.ts" + ".": "./src/index.ts" }, "scripts": { "build": "r build.ts" @@ -16,7 +16,8 @@ "query-string": "^9.1.1", "vitest": "^3.0.6" }, - "optionalDependencies": { - "react-router-dom": "^6.28.0" + "peerDependencies": { + "react": "^19.1.0", + "react-router-dom": "^7.5.1" } } diff --git a/packages/frontend/routes/routes.json b/packages/frontend/routes/routes.json index 14a8e00001..8c58ee25b8 100644 --- a/packages/frontend/routes/routes.json +++ b/packages/frontend/routes/routes.json @@ -5,6 +5,8 @@ "admin": { "route": "admin", "children": { + "auth": "auth", + "setup": "setup", "accounts": "accounts", "ai": "ai", "settings": { diff --git a/packages/frontend/routes/src/index.ts b/packages/frontend/routes/src/index.ts new file mode 100644 index 0000000000..c6cbefb6cf --- /dev/null +++ b/packages/frontend/routes/src/index.ts @@ -0,0 +1,2 @@ +export * from './lazy'; +export * from './routes'; diff --git a/packages/frontend/routes/src/lazy.ts b/packages/frontend/routes/src/lazy.ts new file mode 100644 index 0000000000..01821bfe83 --- /dev/null +++ b/packages/frontend/routes/src/lazy.ts @@ -0,0 +1,37 @@ +import React, { + type ComponentProps, + type ComponentType, + lazy as reactLazy, + Suspense, +} from 'react'; + +export function lazy>( + factory: () => Promise>, + fallback?: React.ReactNode +) { + const LazyComponent = reactLazy(() => + factory().then(mod => { + if ('default' in mod) { + return { default: mod.default }; + } else { + const components = Object.values(mod); + if (components.length > 1) { + console.warn('Lazy loaded module has more then one exports'); + } + return { + default: components[0], + }; + } + }) + ); + + return function LazyRoute(props: ComponentProps) { + return React.createElement( + Suspense, + { + fallback, + }, + React.createElement(LazyComponent, props) + ); + }; +} diff --git a/packages/frontend/routes/src/routes.ts b/packages/frontend/routes/src/routes.ts index a55b862eb5..8a0e586e99 100644 --- a/packages/frontend/routes/src/routes.ts +++ b/packages/frontend/routes/src/routes.ts @@ -9,10 +9,13 @@ export const ROUTES = { index: '/', admin: { index: '/admin', + auth: '/admin/auth', + setup: '/admin/setup', accounts: '/admin/accounts', ai: '/admin/ai', settings: { index: '/admin/settings', module: '/admin/settings/:module' }, about: '/admin/about', + notFound: '/admin/404', }, }; // #endregion @@ -22,10 +25,13 @@ export const RELATIVE_ROUTES = { index: '/', admin: { index: 'admin', + auth: 'auth', + setup: 'setup', accounts: 'accounts', ai: 'ai', settings: { index: 'settings', module: ':module' }, about: 'about', + notFound: '404', }, }; // #endregion @@ -33,6 +39,8 @@ export const RELATIVE_ROUTES = { // #region Path Factories const home = () => '/'; const admin = () => '/admin'; +admin.auth = () => '/admin/auth'; +admin.setup = () => '/admin/setup'; admin.accounts = () => '/admin/accounts'; admin.ai = () => '/admin/ai'; const admin_settings = () => '/admin/settings'; @@ -40,5 +48,6 @@ admin_settings.module = (params: { module: string }) => `/admin/settings/${params.module}`; admin.settings = admin_settings; admin.about = () => '/admin/about'; +admin.notFound = () => '/admin/404'; export const FACTORIES = { admin, home }; // #endregion diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index d4f9d8c58c..b42fb972aa 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -1035,6 +1035,7 @@ export const PackageList = [ 'packages/frontend/core', 'packages/common/error', 'packages/common/graphql', + 'packages/frontend/routes', 'packages/common/infra', ], }, diff --git a/yarn.lock b/yarn.lock index da39da46c5..8e8e015327 100644 --- a/yarn.lock +++ b/yarn.lock @@ -180,6 +180,7 @@ __metadata: "@affine/core": "workspace:*" "@affine/error": "workspace:*" "@affine/graphql": "workspace:*" + "@affine/routes": "workspace:*" "@blocksuite/icons": "npm:^2.2.12" "@radix-ui/react-accordion": "npm:^1.2.2" "@radix-ui/react-alert-dialog": "npm:^1.1.3" @@ -227,7 +228,7 @@ __metadata: react-dom: "npm:^19.0.0" react-hook-form: "npm:^7.54.1" react-resizable-panels: "npm:^2.1.7" - react-router-dom: "npm:^6.28.0" + react-router-dom: "npm:^7.5.1" shadcn-ui: "npm:^0.9.4" sonner: "npm:^2.0.0" swr: "npm:^2.2.5" @@ -852,7 +853,7 @@ __metadata: languageName: unknown linkType: soft -"@affine/routes@workspace:packages/frontend/routes": +"@affine/routes@workspace:*, @affine/routes@workspace:packages/frontend/routes": version: 0.0.0-use.local resolution: "@affine/routes@workspace:packages/frontend/routes" dependencies: @@ -860,11 +861,10 @@ __metadata: "@affine-tools/utils": "workspace:*" path-to-regexp: "npm:^8.2.0" query-string: "npm:^9.1.1" - react-router-dom: "npm:^6.28.0" vitest: "npm:^3.0.6" - dependenciesMeta: - react-router-dom: - optional: true + peerDependencies: + react: ^19.1.0 + react-router-dom: ^7.5.1 languageName: unknown linkType: soft @@ -19052,6 +19052,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.1": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 10/f5817cdc84d8977761b12549eba29435e675e65c7fef172bc31737788cd8adc83796bf8abe6d950554e7987325ad2d9ac2971c5bd8ff0c4f81c145f82e4ab1be + languageName: node + linkType: hard + "cookiejar@npm:^2.1.4": version: 2.1.4 resolution: "cookiejar@npm:2.1.4" @@ -29835,6 +29842,18 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^7.5.1": + version: 7.5.1 + resolution: "react-router-dom@npm:7.5.1" + dependencies: + react-router: "npm:7.5.1" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10/a564fd2b3cfa9a4b79dbdbae53b7504fcd678b2aba69a696ae494c228927999b3be9bc4e304a63a0b2247a2943c241bf3bb4fe25d62092de64e16309654f0f2d + languageName: node + linkType: hard + "react-router@npm:6.30.0": version: 6.30.0 resolution: "react-router@npm:6.30.0" @@ -29846,6 +29865,23 @@ __metadata: languageName: node linkType: hard +"react-router@npm:7.5.1": + version: 7.5.1 + resolution: "react-router@npm:7.5.1" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + turbo-stream: "npm:2.4.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10/c5c3bacbb93058f4cbf93557ea4d3376393e071aa59e2f7044060cc4073b2a253fcd29c7c0b89d6c373d56dc7e2494729871e5a72be66988b1c0bd726f7cfc0e + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": version: 2.2.3 resolution: "react-style-singleton@npm:2.2.3" @@ -31033,7 +31069,7 @@ __metadata: languageName: node linkType: hard -"set-cookie-parser@npm:^2.7.1": +"set-cookie-parser@npm:^2.6.0, set-cookie-parser@npm:^2.7.1": version: 2.7.1 resolution: "set-cookie-parser@npm:2.7.1" checksum: 10/c92b1130032693342bca13ea1b1bc93967ab37deec4387fcd8c2a843c0ef2fd9a9f3df25aea5bb3976cd05a91c2cf4632dd6164d6e1814208fb7d7e14edd42b4 @@ -32941,6 +32977,13 @@ __metadata: languageName: node linkType: hard +"turbo-stream@npm:2.4.0": + version: 2.4.0 + resolution: "turbo-stream@npm:2.4.0" + checksum: 10/7079bbc82b58340f783144cd669cc7e598288523103a8d68bb8a4c6bb28c64eccb71d389b33aab07788d3a9030638b795709e15cb8486f722b1cdac59cb58afc + languageName: node + linkType: hard + "tweakpane@npm:^4.0.4": version: 4.0.5 resolution: "tweakpane@npm:4.0.5"