From b20de089ce10037e0a23de90e7fac501550a74ea Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 11 May 2026 15:13:01 +0800 Subject: [PATCH] feat: explore page to home page --- web/app/(commonLayout)/explore/apps/page.tsx | 25 +++++++++++++++---- web/app/(commonLayout)/page.tsx | 8 ++++++ web/app/components/apps/new-app-card.tsx | 2 +- .../explore/sidebar/__tests__/index.spec.tsx | 1 + web/app/components/explore/sidebar/index.tsx | 7 +++--- .../explore-nav/__tests__/index.spec.tsx | 9 ++++--- .../components/header/explore-nav/index.tsx | 7 +++--- .../main-nav/__tests__/index.spec.tsx | 4 +-- web/app/components/main-nav/index.tsx | 6 ++--- web/app/page.tsx | 23 ----------------- web/next.config.ts | 4 +-- 11 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 web/app/(commonLayout)/page.tsx delete mode 100644 web/app/page.tsx diff --git a/web/app/(commonLayout)/explore/apps/page.tsx b/web/app/(commonLayout)/explore/apps/page.tsx index e0da5d64d2..ba91287702 100644 --- a/web/app/(commonLayout)/explore/apps/page.tsx +++ b/web/app/(commonLayout)/explore/apps/page.tsx @@ -1,8 +1,23 @@ -import * as React from 'react' -import AppList from '@/app/components/explore/app-list' +import { redirect } from '@/next/navigation' -const Apps = () => { - return +type AppsPageProps = { + searchParams: Promise> } -export default React.memo(Apps) +const Apps = async ({ searchParams }: AppsPageProps) => { + const resolvedSearchParams = await searchParams + const urlSearchParams = new URLSearchParams() + Object.entries(resolvedSearchParams).forEach(([key, value]) => { + if (value === undefined) + return + if (Array.isArray(value)) { + value.forEach(item => urlSearchParams.append(key, item)) + return + } + urlSearchParams.set(key, value) + }) + const queryString = urlSearchParams.toString() + redirect(queryString ? `/?${queryString}` : '/') +} + +export default Apps diff --git a/web/app/(commonLayout)/page.tsx b/web/app/(commonLayout)/page.tsx new file mode 100644 index 0000000000..0cc61f1131 --- /dev/null +++ b/web/app/(commonLayout)/page.tsx @@ -0,0 +1,8 @@ +import * as React from 'react' +import AppList from '@/app/components/explore/app-list' + +const Home = () => { + return +} + +export default React.memo(Home) diff --git a/web/app/components/apps/new-app-card.tsx b/web/app/components/apps/new-app-card.tsx index fa9d1ac952..c1e55a92cc 100644 --- a/web/app/components/apps/new-app-card.tsx +++ b/web/app/components/apps/new-app-card.tsx @@ -131,7 +131,7 @@ const CreateAppCard = ({ setShowCreateFromDSLModal(false) if (dslUrl) - replace('/') + replace('/apps') }} activeTab={activeTab} dslUrl={dslUrl} diff --git a/web/app/components/explore/sidebar/__tests__/index.spec.tsx b/web/app/components/explore/sidebar/__tests__/index.spec.tsx index cce42d6322..bc2ad23602 100644 --- a/web/app/components/explore/sidebar/__tests__/index.spec.tsx +++ b/web/app/components/explore/sidebar/__tests__/index.spec.tsx @@ -18,6 +18,7 @@ let mockInstalledApps: InstalledApp[] = [] let mockMediaType: string = MediaType.pc vi.mock('@/next/navigation', () => ({ + usePathname: () => '/', useSelectedLayoutSegments: () => mockSegments, useRouter: () => ({ push: mockPush, diff --git a/web/app/components/explore/sidebar/index.tsx b/web/app/components/explore/sidebar/index.tsx index c6128cb560..3885af21f8 100644 --- a/web/app/components/explore/sidebar/index.tsx +++ b/web/app/components/explore/sidebar/index.tsx @@ -18,7 +18,7 @@ import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Link from '@/next/link' -import { useSelectedLayoutSegments } from '@/next/navigation' +import { usePathname, useSelectedLayoutSegments } from '@/next/navigation' import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore' import Item from './app-nav-item' import NoApps from './no-apps' @@ -31,9 +31,10 @@ const expandedSidebarScrollAreaClassNames = { const SideBar = () => { const { t } = useTranslation() + const pathname = usePathname() const segments = useSelectedLayoutSegments() const lastSegment = segments.slice(-1)[0] - const isDiscoverySelected = lastSegment === 'apps' + const isDiscoverySelected = pathname === '/' || lastSegment === 'apps' const { data, isPending } = useGetInstalledApps() const installedApps = data?.installed_apps ?? [] const { mutateAsync: uninstallApp, isPending: isUninstalling } = useUninstallApp() @@ -89,7 +90,7 @@ const SideBar = () => {
diff --git a/web/app/components/header/explore-nav/__tests__/index.spec.tsx b/web/app/components/header/explore-nav/__tests__/index.spec.tsx index 0ef271b034..b01bac710f 100644 --- a/web/app/components/header/explore-nav/__tests__/index.spec.tsx +++ b/web/app/components/header/explore-nav/__tests__/index.spec.tsx @@ -1,15 +1,17 @@ import type { Mock } from 'vitest' import { render, screen } from '@testing-library/react' -import { useSelectedLayoutSegment } from '@/next/navigation' +import { usePathname, useSelectedLayoutSegment } from '@/next/navigation' import ExploreNav from '../index' vi.mock('@/next/navigation', () => ({ + usePathname: vi.fn(), useSelectedLayoutSegment: vi.fn(), })) describe('ExploreNav', () => { beforeEach(() => { vi.clearAllMocks() + ;(usePathname as Mock).mockReturnValue('/apps') }) it('should render correctly when not active', () => { @@ -18,14 +20,15 @@ describe('ExploreNav', () => { const link = screen.getByRole('link') expect(link).toBeInTheDocument() - expect(link).toHaveAttribute('href', '/explore/apps') + expect(link).toHaveAttribute('href', '/') expect(link).toHaveClass('text-components-main-nav-nav-button-text') expect(link).not.toHaveClass('bg-components-main-nav-nav-button-bg-active') expect(screen.getByText('common.menus.explore')).toBeInTheDocument() }) it('should render correctly when active', () => { - (useSelectedLayoutSegment as Mock).mockReturnValue('explore') + ;(usePathname as Mock).mockReturnValue('/') + ;(useSelectedLayoutSegment as Mock).mockReturnValue('explore') render() const link = screen.getByRole('link') diff --git a/web/app/components/header/explore-nav/index.tsx b/web/app/components/header/explore-nav/index.tsx index d8bbc014b9..2de72c4223 100644 --- a/web/app/components/header/explore-nav/index.tsx +++ b/web/app/components/header/explore-nav/index.tsx @@ -7,7 +7,7 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import Link from '@/next/link' -import { useSelectedLayoutSegment } from '@/next/navigation' +import { usePathname, useSelectedLayoutSegment } from '@/next/navigation' type ExploreNavProps = { className?: string @@ -17,12 +17,13 @@ const ExploreNav = ({ className, }: ExploreNavProps) => { const { t } = useTranslation() + const pathname = usePathname() const selectedSegment = useSelectedLayoutSegment() - const activated = selectedSegment === 'explore' + const activated = pathname === '/' || selectedSegment === 'explore' return ( { diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index d9fb2cd58e..c1f16fe396 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -211,7 +211,7 @@ describe('MainNav', () => { expect(screen.getAllByText(Plan.team)).toHaveLength(1) expect(screen.getByRole('button', { name: 'common.account.account' })).not.toHaveTextContent(Plan.team) - expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toHaveAttribute('href', '/explore/apps') + expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toHaveAttribute('href', '/') expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps') expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute('href', '/datasets') expect(screen.getByRole('link', { name: /common.mainNav.integrations/ })).toHaveAttribute('href', '/tools?section=provider') @@ -314,7 +314,7 @@ describe('MainNav', () => { }) it('applies the Figma glass active state to the Home route', () => { - mockPathname = '/explore/apps' + mockPathname = '/' renderMainNav() diff --git a/web/app/components/main-nav/index.tsx b/web/app/components/main-nav/index.tsx index e9500d1814..975e9c98e2 100644 --- a/web/app/components/main-nav/index.tsx +++ b/web/app/components/main-nav/index.tsx @@ -30,9 +30,9 @@ const MainNav = ({ ...(!isCurrentWorkspaceDatasetOperator ? [ { - href: '/explore/apps', + href: '/', label: t('mainNav.home', { ns: 'common' }), - active: (path: string) => path.startsWith('/explore'), + active: (path: string) => path === '/' || path.startsWith('/explore'), icon: 'i-custom-vender-main-nav-home', activeIcon: 'i-custom-vender-main-nav-home-active', }, @@ -78,7 +78,7 @@ const MainNav = ({ const renderLogo = () => (

- + {systemFeatures.branding.enabled && systemFeatures.branding.application_title ? systemFeatures.branding.application_title : 'Dify'} {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo ? ( diff --git a/web/app/page.tsx b/web/app/page.tsx deleted file mode 100644 index a866fd4c39..0000000000 --- a/web/app/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { redirect } from '@/next/navigation' - -type HomePageProps = { - searchParams: Promise> -} - -const Home = async ({ searchParams }: HomePageProps) => { - const resolvedSearchParams = await searchParams - const urlSearchParams = new URLSearchParams() - Object.entries(resolvedSearchParams).forEach(([key, value]) => { - if (value === undefined) - return - if (Array.isArray(value)) { - value.forEach(item => urlSearchParams.append(key, item)) - return - } - urlSearchParams.set(key, value) - }) - const queryString = urlSearchParams.toString() - redirect(queryString ? `/apps?${queryString}` : '/apps') -} - -export default Home diff --git a/web/next.config.ts b/web/next.config.ts index a1c2e410a1..0464b7ffbc 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -28,8 +28,8 @@ const nextConfig: NextConfig = { async redirects() { return [ { - source: '/', - destination: '/apps', + source: '/explore/apps', + destination: '/', permanent: false, }, ]