From d13e6901cf9de17f7c097ed5a57d15ddae12193b Mon Sep 17 00:00:00 2001
From: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Date: Thu, 2 Apr 2026 17:36:06 +0800
Subject: [PATCH] refactor: no global loading
---
web/app/(commonLayout)/layout.tsx | 3 ---
web/app/(commonLayout)/role-route-guard.tsx | 16 ++-------------
web/app/components/app-initializer.tsx | 8 ++------
web/app/components/header/header-wrapper.tsx | 4 ++--
web/app/components/splash.tsx | 21 --------------------
web/app/education-apply/hooks.ts | 2 +-
web/app/page.tsx | 19 +++---------------
web/context/global-public-context.tsx | 5 +----
web/next.config.ts | 9 ---------
web/next/navigation.ts | 1 +
10 files changed, 12 insertions(+), 76 deletions(-)
delete mode 100644 web/app/components/splash.tsx
diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx
index 5ac39f1e39..db8644667c 100644
--- a/web/app/(commonLayout)/layout.tsx
+++ b/web/app/(commonLayout)/layout.tsx
@@ -1,5 +1,4 @@
import type { ReactNode } from 'react'
-import * as React from 'react'
import { AppInitializer } from '@/app/components/app-initializer'
import InSiteMessageNotification from '@/app/components/app/in-site-message/notification'
import AmplitudeProvider from '@/app/components/base/amplitude'
@@ -14,7 +13,6 @@ import { EventEmitterContextProvider } from '@/context/event-emitter-provider'
import { ModalContextProvider } from '@/context/modal-context-provider'
import { ProviderContextProvider } from '@/context/provider-context-provider'
import PartnerStack from '../components/billing/partner-stack'
-import Splash from '../components/splash'
import RoleRouteGuard from './role-route-guard'
const Layout = ({ children }: { children: ReactNode }) => {
@@ -37,7 +35,6 @@ const Layout = ({ children }: { children: ReactNode }) => {
-
diff --git a/web/app/(commonLayout)/role-route-guard.tsx b/web/app/(commonLayout)/role-route-guard.tsx
index 483dfef095..37957994d1 100644
--- a/web/app/(commonLayout)/role-route-guard.tsx
+++ b/web/app/(commonLayout)/role-route-guard.tsx
@@ -1,10 +1,8 @@
'use client'
import type { ReactNode } from 'react'
-import { useEffect } from 'react'
-import Loading from '@/app/components/base/loading'
import { useAppContext } from '@/context/app-context'
-import { usePathname, useRouter } from '@/next/navigation'
+import { redirect, usePathname } from '@/next/navigation'
const datasetOperatorRedirectRoutes = ['/apps', '/app', '/explore', '/tools'] as const
@@ -13,21 +11,11 @@ const isPathUnderRoute = (pathname: string, route: string) => pathname === route
export default function RoleRouteGuard({ children }: { children: ReactNode }) {
const { isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext()
const pathname = usePathname()
- const router = useRouter()
const shouldGuardRoute = datasetOperatorRedirectRoutes.some(route => isPathUnderRoute(pathname, route))
const shouldRedirect = shouldGuardRoute && !isLoadingCurrentWorkspace && isCurrentWorkspaceDatasetOperator
- useEffect(() => {
- if (shouldRedirect)
- router.replace('/datasets')
- }, [shouldRedirect, router])
-
- // Block rendering only for guarded routes to avoid permission flicker.
- if (shouldGuardRoute && isLoadingCurrentWorkspace)
- return
-
if (shouldRedirect)
- return null
+ return redirect('/datasets')
return <>{children}>
}
diff --git a/web/app/components/app-initializer.tsx b/web/app/components/app-initializer.tsx
index e08ece6666..22e04520e7 100644
--- a/web/app/components/app-initializer.tsx
+++ b/web/app/components/app-initializer.tsx
@@ -3,7 +3,7 @@
import type { ReactNode } from 'react'
import Cookies from 'js-cookie'
import { parseAsBoolean, useQueryState } from 'nuqs'
-import { useCallback, useEffect, useState } from 'react'
+import { useCallback, useEffect } from 'react'
import {
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
@@ -25,7 +25,6 @@ export const AppInitializer = ({
const searchParams = useSearchParams()
// Tokens are now stored in cookies, no need to check localStorage
const pathname = usePathname()
- const [init, setInit] = useState(false)
const [oauthNewUser] = useQueryState(
'oauth_new_user',
parseAsBoolean.withOptions({ history: 'replace' }),
@@ -87,10 +86,7 @@ export const AppInitializer = ({
const redirectUrl = resolvePostLoginRedirect()
if (redirectUrl) {
location.replace(redirectUrl)
- return
}
-
- setInit(true)
}
catch {
router.replace('/signin')
@@ -98,5 +94,5 @@ export const AppInitializer = ({
})()
}, [isSetupFinished, router, pathname, searchParams, oauthNewUser])
- return init ? children : null
+ return children
}
diff --git a/web/app/components/header/header-wrapper.tsx b/web/app/components/header/header-wrapper.tsx
index c90f01a6b6..09adb2f6bf 100644
--- a/web/app/components/header/header-wrapper.tsx
+++ b/web/app/components/header/header-wrapper.tsx
@@ -18,7 +18,7 @@ const HeaderWrapper = ({
// Check if the current path is a workflow canvas & fullscreen
const inWorkflowCanvas = pathname.endsWith('/workflow')
const isPipelineCanvas = pathname.endsWith('/pipeline')
- const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true'
+ const workflowCanvasMaximize = typeof localStorage !== 'undefined' && localStorage.getItem('workflow-canvas-maximize') === 'true'
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
const { eventEmitter } = useEventEmitterContextContext()
@@ -28,7 +28,7 @@ const HeaderWrapper = ({
})
return (
-
+
{children}
)
diff --git a/web/app/components/splash.tsx b/web/app/components/splash.tsx
deleted file mode 100644
index 4f5f484582..0000000000
--- a/web/app/components/splash.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-'use client'
-import type { FC, PropsWithChildren } from 'react'
-import * as React from 'react'
-import { useIsLogin } from '@/service/use-common'
-import Loading from './base/loading'
-
-const Splash: FC
= () => {
- // would auto redirect to signin page if not logged in
- const { isLoading, data: loginData } = useIsLogin()
- const isLoggedIn = loginData?.logged_in
-
- if (isLoading || !isLoggedIn) {
- return (
-
-
-
- )
- }
- return null
-}
-export default React.memo(Splash)
diff --git a/web/app/education-apply/hooks.ts b/web/app/education-apply/hooks.ts
index 79faa8b3b2..6068545751 100644
--- a/web/app/education-apply/hooks.ts
+++ b/web/app/education-apply/hooks.ts
@@ -133,7 +133,7 @@ const useEducationReverifyNotice = ({
export const useEducationInit = () => {
const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
const setShowEducationExpireNoticeModal = useModalContextSelector(s => s.setShowEducationExpireNoticeModal)
- const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
+ const educationVerifying = typeof localStorage !== 'undefined' && localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
const searchParams = useSearchParams()
const educationVerifyAction = searchParams.get('action')
diff --git a/web/app/page.tsx b/web/app/page.tsx
index 65f8827e01..1daf6fc49b 100644
--- a/web/app/page.tsx
+++ b/web/app/page.tsx
@@ -1,18 +1,5 @@
-import Loading from '@/app/components/base/loading'
-import Link from '@/next/link'
+import { redirect } from '@/next/navigation'
-const Home = async () => {
- return (
-
- )
+export default function Home() {
+ return redirect('/apps')
}
-
-export default Home
diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx
index 3a570fc7ef..e0ff645475 100644
--- a/web/context/global-public-context.tsx
+++ b/web/context/global-public-context.tsx
@@ -3,7 +3,6 @@ import type { FC, PropsWithChildren } from 'react'
import type { SystemFeatures } from '@/types/feature'
import { useQuery } from '@tanstack/react-query'
import { create } from 'zustand'
-import Loading from '@/app/components/base/loading'
import { consoleClient } from '@/service/client'
import { defaultSystemFeatures } from '@/types/feature'
import { fetchSetupStatusWithCache } from '@/utils/setup-status'
@@ -53,13 +52,11 @@ const GlobalPublicStoreProvider: FC = ({
}) => {
// Fetch systemFeatures and setupStatus in parallel to reduce waterfall.
// setupStatus is prefetched here and cached in localStorage for AppInitializer.
- const { isPending } = useSystemFeaturesQuery()
+ useSystemFeaturesQuery()
// Prefetch setupStatus for AppInitializer (result not needed here)
useSetupStatusQuery()
- if (isPending)
- return
return <>{children}>
}
export default GlobalPublicStoreProvider
diff --git a/web/next.config.ts b/web/next.config.ts
index aa4d9318f4..a635d1f538 100644
--- a/web/next.config.ts
+++ b/web/next.config.ts
@@ -21,15 +21,6 @@ const nextConfig: NextConfig = {
// https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors
ignoreBuildErrors: true,
},
- async redirects() {
- return [
- {
- source: '/',
- destination: '/apps',
- permanent: false,
- },
- ]
- },
output: 'standalone',
compiler: {
removeConsole: isDev ? false : { exclude: ['warn', 'error'] },
diff --git a/web/next/navigation.ts b/web/next/navigation.ts
index ec7c112645..7147fb2f5d 100644
--- a/web/next/navigation.ts
+++ b/web/next/navigation.ts
@@ -1,4 +1,5 @@
export {
+ redirect,
useParams,
usePathname,
useRouter,