Files
dify/web/app/components/explore/index.tsx
yyh de62812134 refactor(web): centralize role-based route guards and fix anti-patterns
Problems:
- Identical useEffect + router.replace guard for dataset_operator role
  duplicated across 4 leaf components (tools/page, appDetail/layout,
  explore/index, apps/list), violating DRY and risking permission gaps
  when new routes are added
- datasets/list had a redundant role guard already covered by
  datasets/layout
- datasets/layout used useEffect for redirect, causing content flash
- useEffect-based guards render unauthorized content before redirect
- explore/index used useState + useEffect to derive hasEditPermission,
  an unnecessary render cycle (rerender-derived-state-no-effect)
- apps/list imported 6 @remixicon/react components instead of using
  Tailwind CSS icon classes, inflating JS bundle
- apps/list contained unused validTabs Set (dead code)

Solutions:
- Create RoleRouteGuard component at (commonLayout)/layout.tsx level
  that synchronously checks role during render and returns null before
  children mount, preventing content flash
- Remove all 5 duplicated useEffect guards from leaf components
- Replace datasets/layout useEffect guard with synchronous render check
- Derive hasEditPermission directly during render in explore/index
- Replace @remixicon/react imports with i-ri-* CSS icon classes
- Remove dead code (validTabs)
2026-02-13 14:58:21 +08:00

71 lines
2.4 KiB
TypeScript

'use client'
import type { FC } from 'react'
import type { CurrentTryAppParams } from '@/context/explore-context'
import type { InstalledApp } from '@/models/explore'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Sidebar from '@/app/components/explore/sidebar'
import { useAppContext } from '@/context/app-context'
import ExploreContext from '@/context/explore-context'
import useDocumentTitle from '@/hooks/use-document-title'
import { useMembers } from '@/service/use-common'
export type IExploreProps = {
children: React.ReactNode
}
const Explore: FC<IExploreProps> = ({
children,
}) => {
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
const { userProfile } = useAppContext()
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
const [isFetchingInstalledApps, setIsFetchingInstalledApps] = useState(false)
const { t } = useTranslation()
const { data: membersData } = useMembers()
useDocumentTitle(t('menus.explore', { ns: 'common' }))
const hasEditPermission = membersData?.accounts
? membersData.accounts.find(account => account.id === userProfile.id)?.role !== 'normal'
: false
const [currentTryAppParams, setCurrentTryAppParams] = useState<CurrentTryAppParams | undefined>(undefined)
const [isShowTryAppPanel, setIsShowTryAppPanel] = useState(false)
const setShowTryAppPanel = (showTryAppPanel: boolean, params?: CurrentTryAppParams) => {
if (showTryAppPanel)
setCurrentTryAppParams(params)
else
setCurrentTryAppParams(undefined)
setIsShowTryAppPanel(showTryAppPanel)
}
return (
<div className="flex h-full overflow-hidden border-t border-divider-regular bg-background-body">
<ExploreContext.Provider
value={
{
controlUpdateInstalledApps,
setControlUpdateInstalledApps,
hasEditPermission,
installedApps,
setInstalledApps,
isFetchingInstalledApps,
setIsFetchingInstalledApps,
currentApp: currentTryAppParams,
isShowTryAppPanel,
setShowTryAppPanel,
}
}
>
<Sidebar controlUpdateInstalledApps={controlUpdateInstalledApps} />
<div className="h-full min-h-0 w-0 grow">
{children}
</div>
</ExploreContext.Provider>
</div>
)
}
export default React.memo(Explore)