mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 07:37:09 +08:00
feat: explore page to home page
This commit is contained in:
parent
c67235ca8c
commit
b20de089ce
@ -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 <AppList />
|
||||
type AppsPageProps = {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
8
web/app/(commonLayout)/page.tsx
Normal file
8
web/app/(commonLayout)/page.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import AppList from '@/app/components/explore/app-list'
|
||||
|
||||
const Home = () => {
|
||||
return <AppList />
|
||||
}
|
||||
|
||||
export default React.memo(Home)
|
||||
@ -131,7 +131,7 @@ const CreateAppCard = ({
|
||||
setShowCreateFromDSLModal(false)
|
||||
|
||||
if (dslUrl)
|
||||
replace('/')
|
||||
replace('/apps')
|
||||
}}
|
||||
activeTab={activeTab}
|
||||
dslUrl={dslUrl}
|
||||
|
||||
@ -18,6 +18,7 @@ let mockInstalledApps: InstalledApp[] = []
|
||||
let mockMediaType: string = MediaType.pc
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
usePathname: () => '/',
|
||||
useSelectedLayoutSegments: () => mockSegments,
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
|
||||
@ -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 = () => {
|
||||
<div className={cn('flex h-full w-fit shrink-0 cursor-pointer flex-col px-3 pt-6 sm:w-[240px]', isFold && 'sm:w-[56px]')}>
|
||||
<div className={cn(isDiscoverySelected ? 'text-text-accent' : 'text-text-tertiary')}>
|
||||
<Link
|
||||
href="/explore/apps"
|
||||
href="/"
|
||||
aria-label={isMobile || isFold ? t('sidebar.title', { ns: 'explore' }) : undefined}
|
||||
className={cn(isDiscoverySelected ? 'bg-state-base-active' : 'hover:bg-state-base-hover', 'flex h-8 items-center gap-2 rounded-lg px-1 mobile:w-fit mobile:justify-center pc:w-full pc:justify-start')}
|
||||
>
|
||||
|
||||
@ -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(<ExploreNav />)
|
||||
|
||||
const link = screen.getByRole('link')
|
||||
|
||||
@ -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 (
|
||||
<Link
|
||||
href="/explore/apps"
|
||||
href="/"
|
||||
className={cn(className, 'group', activated && 'bg-components-main-nav-nav-button-bg-active shadow-md', activated ? 'text-components-main-nav-nav-button-text-active' : 'text-components-main-nav-nav-button-text hover:bg-components-main-nav-nav-button-bg-hover')}
|
||||
>
|
||||
{
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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 = () => (
|
||||
<h1 className="min-w-0">
|
||||
<Link href={isCurrentWorkspaceDatasetOperator ? '/datasets' : '/apps'} className="flex h-8 shrink-0 items-center overflow-hidden px-2 indent-[-9999px] whitespace-nowrap">
|
||||
<Link href="/" className="flex h-8 shrink-0 items-center overflow-hidden px-2 indent-[-9999px] whitespace-nowrap">
|
||||
{systemFeatures.branding.enabled && systemFeatures.branding.application_title ? systemFeatures.branding.application_title : 'Dify'}
|
||||
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
? (
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import { redirect } from '@/next/navigation'
|
||||
|
||||
type HomePageProps = {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||||
}
|
||||
|
||||
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
|
||||
@ -28,8 +28,8 @@ const nextConfig: NextConfig = {
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
destination: '/apps',
|
||||
source: '/explore/apps',
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user