mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 12:59:18 +08:00
fix(web): preserve settings fallbacks during main nav update
- hide migrated settings tabs from the account settings sidebar - add disabled integrations destination mapping for future migration - keep legacy settings modal fallback until integrations sections are ready - restore main nav active styling and add titles for truncated labels
This commit is contained in:
parent
32192de679
commit
370381dbf4
@ -140,6 +140,7 @@ function ComplianceDocRowItem({
|
||||
[Plan.team]: '',
|
||||
[Plan.enterprise]: '',
|
||||
}
|
||||
const labelTitle = typeof label === 'string' ? label : undefined
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
@ -148,7 +149,7 @@ function ComplianceDocRowItem({
|
||||
onClick={handleSelect}
|
||||
>
|
||||
{icon}
|
||||
<div className="grow truncate px-1 system-md-regular text-text-secondary">{label}</div>
|
||||
<div className="grow truncate px-1 system-md-regular text-text-secondary" title={labelTitle}>{label}</div>
|
||||
<ComplianceDocActionVisual
|
||||
isCurrentPlanCanDownload={isCurrentPlanCanDownload}
|
||||
isPending={isPending}
|
||||
|
||||
@ -44,10 +44,12 @@ function MainNavRadioItemContent({
|
||||
iconClassName,
|
||||
label,
|
||||
}: MainNavRadioItemContentProps) {
|
||||
const labelTitle = typeof label === 'string' ? label : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
{iconClassName && <span aria-hidden className={cn('size-4 shrink-0 text-text-tertiary', iconClassName)} />}
|
||||
<span className="min-w-0 grow truncate px-1 system-md-regular text-text-secondary">{label}</span>
|
||||
<span className="min-w-0 grow truncate px-1 system-md-regular text-text-secondary" title={labelTitle}>{label}</span>
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</>
|
||||
)
|
||||
@ -205,10 +207,10 @@ export function MainNavMenuContent({
|
||||
<div className="flex items-center gap-1 rounded-xl bg-gradient-to-b from-background-section-burn to-background-section p-3">
|
||||
<div className="flex min-w-0 grow flex-col gap-1">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<div className="truncate body-md-medium text-text-primary">{userProfile.name}</div>
|
||||
<div className="max-w-[80px] min-w-0 truncate body-md-medium text-text-primary" title={userProfile.name}>{userProfile.name}</div>
|
||||
{mainNavBadge}
|
||||
</div>
|
||||
<div className="truncate system-xs-regular text-text-tertiary">{userProfile.email}</div>
|
||||
<div className="truncate system-xs-regular text-text-tertiary" title={userProfile.email}>{userProfile.email}</div>
|
||||
</div>
|
||||
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="lg" />
|
||||
</div>
|
||||
|
||||
@ -17,10 +17,12 @@ export function MenuItemContent({
|
||||
label,
|
||||
trailing,
|
||||
}: MenuItemContentProps) {
|
||||
const labelTitle = typeof label === 'string' ? label : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<span aria-hidden className={cn(menuLeadingIconClassName, iconClassName)} />
|
||||
<div className={menuLabelClassName}>{label}</div>
|
||||
<div className={menuLabelClassName} title={labelTitle}>{label}</div>
|
||||
{trailing}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -97,7 +97,7 @@ const WorkplaceSelector = () => {
|
||||
{currentWorkspace?.name[0]?.toLocaleUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-w-[149px] min-w-0 truncate system-sm-medium text-text-secondary max-[800px]:hidden">
|
||||
<div className="max-w-[149px] min-w-0 truncate system-sm-medium text-text-secondary max-[800px]:hidden" title={currentWorkspace?.name}>
|
||||
{currentWorkspace?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,11 @@ import {
|
||||
DEFAULT_ACCOUNT_SETTING_TAB,
|
||||
isValidAccountSettingTab,
|
||||
} from '../constants'
|
||||
import {
|
||||
enableMovedAccountSettingDestinations,
|
||||
getMovedAccountSettingDestination,
|
||||
movedAccountSettingDestinations,
|
||||
} from '../destinations'
|
||||
|
||||
describe('AccountSetting Constants', () => {
|
||||
it('should have correct ACCOUNT_SETTING_MODAL_ACTION', () => {
|
||||
@ -39,4 +44,15 @@ describe('AccountSetting Constants', () => {
|
||||
expect(isValidAccountSettingTab('')).toBe(false)
|
||||
expect(isValidAccountSettingTab('invalid')).toBe(false)
|
||||
})
|
||||
|
||||
it('should keep migrated setting destinations disabled until integrations sections are ready', () => {
|
||||
expect(enableMovedAccountSettingDestinations).toBe(false)
|
||||
expect(movedAccountSettingDestinations[ACCOUNT_SETTING_TAB.PROVIDER]).toBe('/tools?section=provider')
|
||||
expect(movedAccountSettingDestinations[ACCOUNT_SETTING_TAB.DATA_SOURCE]).toBe('/tools?section=data-source')
|
||||
expect(movedAccountSettingDestinations[ACCOUNT_SETTING_TAB.API_BASED_EXTENSION]).toBe('/tools?section=api-based-extension')
|
||||
expect(getMovedAccountSettingDestination(ACCOUNT_SETTING_TAB.PROVIDER)).toBeUndefined()
|
||||
expect(getMovedAccountSettingDestination(ACCOUNT_SETTING_TAB.DATA_SOURCE)).toBeUndefined()
|
||||
expect(getMovedAccountSettingDestination(ACCOUNT_SETTING_TAB.API_BASED_EXTENSION)).toBeUndefined()
|
||||
expect(getMovedAccountSettingDestination(ACCOUNT_SETTING_TAB.BILLING)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@ -175,24 +175,21 @@ describe('AccountSetting', () => {
|
||||
// Assert
|
||||
// Assert
|
||||
expect(screen.getByText('common.userProfile.settings'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.provider'))!.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.provider'))!.not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('common.settings.billing'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.dataSource'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.apiBasedExtension'))!.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.dataSource'))!.not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.apiBasedExtension'))!.not.toBeInTheDocument()
|
||||
expect(screen.getByText('custom.custom'))!.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.language').length).toBeGreaterThan(0)
|
||||
expect(screen.queryByText('common.settings.language'))!.not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should respect the initial tab', () => {
|
||||
it('should keep hidden legacy tab metadata for direct entries', () => {
|
||||
// Act
|
||||
renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.DATA_SOURCE })
|
||||
|
||||
// Assert
|
||||
// Check that the active item title is Data Source
|
||||
const titles = screen.getAllByText('common.settings.dataSource')
|
||||
// One in sidebar, one in header.
|
||||
expect(titles.length).toBeGreaterThan(1)
|
||||
expect(screen.getByText('common.settings.dataSource'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide sidebar labels on mobile', () => {
|
||||
@ -312,8 +309,8 @@ describe('AccountSetting', () => {
|
||||
// Assert
|
||||
// Assert
|
||||
expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.members')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.language'))!.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.members' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.language'))!.not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide billing and custom tabs when disabled', () => {
|
||||
@ -370,13 +367,11 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting({ onTabChange: mockOnTabChange })
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('common.settings.provider'))
|
||||
fireEvent.click(screen.getByText('common.settings.billing'))
|
||||
|
||||
// Assert
|
||||
expect(mockOnTabChange).toHaveBeenCalledWith(ACCOUNT_SETTING_TAB.PROVIDER)
|
||||
// Check for content from ModelProviderPage
|
||||
// Check for content from ModelProviderPage
|
||||
expect(screen.getByText('common.modelProvider.models'))!.toBeInTheDocument()
|
||||
expect(mockOnTabChange).toHaveBeenCalledWith(ACCOUNT_SETTING_TAB.BILLING)
|
||||
expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should navigate through various tabs and show correct details', () => {
|
||||
@ -389,23 +384,11 @@ describe('AccountSetting', () => {
|
||||
// Checking for title in header which is always there
|
||||
expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1)
|
||||
|
||||
// Data Source
|
||||
fireEvent.click(screen.getByText('common.settings.dataSource'))
|
||||
expect(screen.getAllByText('common.settings.dataSource').length).toBeGreaterThan(1)
|
||||
|
||||
// API Based Extension
|
||||
fireEvent.click(screen.getByText('common.settings.apiBasedExtension'))
|
||||
expect(screen.getAllByText('common.settings.apiBasedExtension').length).toBeGreaterThan(1)
|
||||
|
||||
// Custom
|
||||
fireEvent.click(screen.getByText('custom.custom'))
|
||||
// Custom Page uses 'custom.custom' key as well.
|
||||
expect(screen.getAllByText('custom.custom').length).toBeGreaterThan(1)
|
||||
|
||||
// Language
|
||||
fireEvent.click(screen.getAllByText('common.settings.language')[0]!)
|
||||
expect(screen.getAllByText('common.settings.language').length).toBeGreaterThan(1)
|
||||
|
||||
// Members
|
||||
fireEvent.click(screen.getAllByText('common.settings.members')[0]!)
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(1)
|
||||
|
||||
17
web/app/components/header/account-setting/destinations.ts
Normal file
17
web/app/components/header/account-setting/destinations.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { AccountSettingTab } from './constants'
|
||||
import { ACCOUNT_SETTING_TAB } from './constants'
|
||||
|
||||
export const movedAccountSettingDestinations: Partial<Record<AccountSettingTab, string>> = {
|
||||
[ACCOUNT_SETTING_TAB.PROVIDER]: '/tools?section=provider',
|
||||
[ACCOUNT_SETTING_TAB.DATA_SOURCE]: '/tools?section=data-source',
|
||||
[ACCOUNT_SETTING_TAB.API_BASED_EXTENSION]: '/tools?section=api-based-extension',
|
||||
}
|
||||
|
||||
export const enableMovedAccountSettingDestinations = false
|
||||
|
||||
export const getMovedAccountSettingDestination = (tab: AccountSettingTab) => {
|
||||
if (!enableMovedAccountSettingDestinations)
|
||||
return undefined
|
||||
|
||||
return movedAccountSettingDestinations[tab]
|
||||
}
|
||||
@ -52,60 +52,70 @@ export default function AccountSetting({
|
||||
const { enableBilling, enableReplaceWebAppLogo } = useProviderContext()
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
|
||||
const workplaceGroupItems: GroupItem[] = (() => {
|
||||
const settingItems: GroupItem[] = [
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.PROVIDER,
|
||||
name: t('settings.provider', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-brain-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-brain-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.MEMBERS,
|
||||
name: t('settings.members', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-group-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-group-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.BILLING,
|
||||
name: t('settings.billing', { ns: 'common' }),
|
||||
description: t('plansCommon.receiptInfo', { ns: 'billing' }),
|
||||
icon: <span className={cn('i-ri-money-dollar-circle-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-money-dollar-circle-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.DATA_SOURCE,
|
||||
name: t('settings.dataSource', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-database-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-database-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.API_BASED_EXTENSION,
|
||||
name: t('settings.apiBasedExtension', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-puzzle-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-puzzle-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.CUSTOM,
|
||||
name: t('custom', { ns: 'custom' }),
|
||||
icon: <span className={cn('i-ri-color-filter-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-color-filter-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.LANGUAGE,
|
||||
name: t('settings.language', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-translate-2', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-translate-2', iconClassName)} />,
|
||||
},
|
||||
]
|
||||
const activeItem = settingItems.find(item => item.key === activeMenu)
|
||||
|
||||
const visibleSettingItems: GroupItem[] = (() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return []
|
||||
|
||||
const items: GroupItem[] = [
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.PROVIDER,
|
||||
name: t('settings.provider', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-brain-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-brain-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.MEMBERS,
|
||||
name: t('settings.members', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-group-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-group-2-fill', iconClassName)} />,
|
||||
},
|
||||
]
|
||||
const visibleTabs: AccountSettingTab[] = []
|
||||
|
||||
if (enableBilling) {
|
||||
items.push({
|
||||
key: ACCOUNT_SETTING_TAB.BILLING,
|
||||
name: t('settings.billing', { ns: 'common' }),
|
||||
description: t('plansCommon.receiptInfo', { ns: 'billing' }),
|
||||
icon: <span className={cn('i-ri-money-dollar-circle-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-money-dollar-circle-fill', iconClassName)} />,
|
||||
})
|
||||
}
|
||||
if (enableBilling)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.BILLING)
|
||||
|
||||
items.push(
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.DATA_SOURCE,
|
||||
name: t('settings.dataSource', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-database-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-database-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.API_BASED_EXTENSION,
|
||||
name: t('settings.apiBasedExtension', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-puzzle-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-puzzle-2-fill', iconClassName)} />,
|
||||
},
|
||||
)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.MEMBERS)
|
||||
|
||||
if (enableReplaceWebAppLogo || enableBilling) {
|
||||
items.push({
|
||||
key: ACCOUNT_SETTING_TAB.CUSTOM,
|
||||
name: t('custom', { ns: 'custom' }),
|
||||
icon: <span className={cn('i-ri-color-filter-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-color-filter-fill', iconClassName)} />,
|
||||
})
|
||||
}
|
||||
if (enableReplaceWebAppLogo || enableBilling)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.CUSTOM)
|
||||
|
||||
return items
|
||||
return visibleTabs
|
||||
.map(tab => settingItems.find(item => item.key === tab))
|
||||
.filter((item): item is GroupItem => Boolean(item))
|
||||
})()
|
||||
|
||||
const media = useBreakpoints()
|
||||
@ -115,22 +125,9 @@ export default function AccountSetting({
|
||||
{
|
||||
key: 'workspace-group',
|
||||
name: t('settings.workplaceGroup', { ns: 'common' }),
|
||||
items: workplaceGroupItems,
|
||||
},
|
||||
{
|
||||
key: 'account-group',
|
||||
name: t('settings.generalGroup', { ns: 'common' }),
|
||||
items: [
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.LANGUAGE,
|
||||
name: t('settings.language', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-translate-2', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-translate-2', iconClassName)} />,
|
||||
},
|
||||
],
|
||||
items: visibleSettingItems,
|
||||
},
|
||||
]
|
||||
const activeItem = [...menuItems[0]!.items, ...menuItems[1]!.items].find(item => item.key === activeMenu)
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
|
||||
@ -215,7 +212,7 @@ export default function AccountSetting({
|
||||
<div className="mt-1 system-sm-regular text-text-tertiary">{activeItem?.description}</div>
|
||||
)}
|
||||
</div>
|
||||
{activeItem?.key === ACCOUNT_SETTING_TAB.PROVIDER && (
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PROVIDER && (
|
||||
<div className="flex grow justify-end">
|
||||
<SearchInput
|
||||
className="w-[200px]"
|
||||
|
||||
@ -222,8 +222,9 @@ describe('MainNav', () => {
|
||||
expect(homeLink).toHaveClass(
|
||||
'border-transparent',
|
||||
'backdrop-blur-[5px]',
|
||||
'text-saas-dify-blue-inverted',
|
||||
activeEdgeClassName,
|
||||
'after:border-components-main-nav-glass-edge-highlight-first',
|
||||
'after:border-[rgba(255,255,255,0.98)]',
|
||||
)
|
||||
expect(homeLink.className).toContain('bg-[linear-gradient(98.077deg')
|
||||
})
|
||||
|
||||
@ -24,7 +24,8 @@ const AccountSection = ({
|
||||
<button
|
||||
type="button"
|
||||
aria-label={ariaLabel}
|
||||
className={cn('flex max-w-[188px] min-w-0 shrink items-center gap-3 rounded-full py-1 pr-4 pl-1 text-left text-components-main-nav-text transition-colors hover:bg-state-base-hover', isOpen && 'bg-state-base-hover')}
|
||||
title={userProfile.name}
|
||||
className={cn('text-components-main-nav-text flex max-w-[180px] min-w-0 shrink items-center gap-3 rounded-full py-1 pr-4 pl-1 text-left transition-colors hover:bg-state-base-hover', isOpen && 'bg-state-base-hover')}
|
||||
>
|
||||
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="md" className="size-7" />
|
||||
<span className="min-w-0 flex-1 truncate system-md-medium">{userProfile.name}</span>
|
||||
|
||||
@ -8,16 +8,16 @@ const navItemClassName = 'group relative flex h-9 items-center gap-2 rounded-xl
|
||||
|
||||
const activeNavItemClassName = cn(
|
||||
'overflow-hidden border border-transparent',
|
||||
'bg-[linear-gradient(98.077deg,var(--color-components-main-nav-glass-surface-first)_0%,var(--color-components-main-nav-glass-surface-middle-1)_17.98%,var(--color-components-main-nav-glass-surface-middle-2)_58.75%,var(--color-components-main-nav-glass-surface-end)_101.09%)]',
|
||||
'system-md-semibold text-components-main-nav-text-active backdrop-blur-[5px]',
|
||||
'shadow-[0px_4px_8px_0px_var(--color-components-main-nav-glass-shadow-reflection-glow),0px_12px_16px_-4px_var(--color-shadow-shadow-5),0px_4px_6px_-2px_var(--color-shadow-shadow-1),0px_10px_16px_-4px_var(--color-components-main-nav-glass-shadow-reflection)]',
|
||||
'before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:p-px',
|
||||
'before:bg-[linear-gradient(var(--color-components-main-nav-glass-edge-highlight-first),var(--color-components-main-nav-glass-edge-highlight-first))_top/100%_1px_no-repeat,linear-gradient(var(--color-components-main-nav-glass-edge-highlight-end),var(--color-components-main-nav-glass-edge-highlight-end))_bottom/100%_1px_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_left/1px_100%_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_right/1px_100%_no-repeat]',
|
||||
'before:[mask-composite:exclude] before:[content:""] before:[-webkit-mask-composite:xor] before:[-webkit-mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)]',
|
||||
'after:pointer-events-none after:absolute after:inset-[-1px] after:rounded-[inherit] after:border after:border-components-main-nav-glass-edge-highlight-first after:shadow-[inset_0_0_8px_0_var(--color-components-main-nav-glass-inner-glow)] after:[content:""]',
|
||||
'bg-[linear-gradient(98.077deg,rgba(0,51,255,0.08)_0%,rgba(0,51,255,0.12)_17.98%,rgba(0,51,255,0.1)_58.75%,rgba(0,51,255,0.08)_101.09%)]',
|
||||
'system-md-semibold text-saas-dify-blue-inverted backdrop-blur-[5px]',
|
||||
'shadow-[0px_4px_8px_0px_rgba(255,255,255,0),0px_12px_16px_-4px_rgba(9,9,11,0.08),0px_4px_6px_-2px_rgba(9,9,11,0.03),0px_10px_16px_-4px_rgba(0,51,255,0.06)]',
|
||||
'before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:p-px before:content-[\'\']',
|
||||
'before:bg-[linear-gradient(rgba(255,255,255,0.98),rgba(255,255,255,0.98))_top/100%_1px_no-repeat,linear-gradient(rgba(255,255,255,0.42),rgba(255,255,255,0.42))_bottom/100%_1px_no-repeat,linear-gradient(180deg,rgba(0,51,255,0)_0%,rgba(0,51,255,0.6)_50%,rgba(0,51,255,0)_100%)_left/1px_100%_no-repeat,linear-gradient(180deg,rgba(0,51,255,0)_0%,rgba(0,51,255,0.6)_50%,rgba(0,51,255,0)_100%)_right/1px_100%_no-repeat]',
|
||||
'before:[mask-composite:exclude] before:[-webkit-mask-composite:xor] before:[-webkit-mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)]',
|
||||
'after:pointer-events-none after:absolute after:inset-[-1px] after:rounded-[inherit] after:border after:border-[rgba(255,255,255,0.98)] after:shadow-[inset_0_0_8px_0_rgba(255,255,255,0.3)] after:content-[\'\']',
|
||||
)
|
||||
|
||||
const inactiveNavItemClassName = 'system-md-medium bg-components-main-nav-nav-button-bg text-components-main-nav-text hover:bg-state-base-hover hover:text-components-main-nav-text'
|
||||
const inactiveNavItemClassName = 'system-md-medium bg-components-main-nav-nav-button-bg text-components-main-nav-nav-button-text hover:bg-components-main-nav-nav-button-bg-hover hover:text-components-main-nav-nav-button-text'
|
||||
|
||||
const NavIcon = ({
|
||||
icon,
|
||||
@ -43,13 +43,14 @@ const MainNavLink = ({
|
||||
return (
|
||||
<Link
|
||||
href={item.href}
|
||||
title={item.label}
|
||||
className={cn(
|
||||
navItemClassName,
|
||||
activated ? activeNavItemClassName : inactiveNavItemClassName,
|
||||
)}
|
||||
>
|
||||
<NavIcon icon={activated ? item.activeIcon : item.icon} />
|
||||
<span className={cn('truncate', activated && 'text-shadow-[0px_0px_8px_var(--color-components-main-nav-glass-text-glow)]')}>{item.label}</span>
|
||||
<span className={cn('truncate', activated && 'text-shadow-[0px_0px_8px_rgba(49,70,255,0.18)]')}>{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@ -36,13 +36,17 @@ const WorkspaceMenuItemContent = ({
|
||||
icon: ReactNode
|
||||
label: ReactNode
|
||||
trailing?: ReactNode
|
||||
}) => (
|
||||
<>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center text-text-tertiary">{icon}</span>
|
||||
<span className="min-w-0 grow truncate text-left system-md-regular text-text-secondary">{label}</span>
|
||||
{trailing}
|
||||
</>
|
||||
)
|
||||
}) => {
|
||||
const labelTitle = typeof label === 'string' ? label : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center text-text-tertiary">{icon}</span>
|
||||
<span className="min-w-0 grow truncate text-left system-md-regular text-text-secondary" title={labelTitle}>{label}</span>
|
||||
{trailing}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const WorkspaceCard = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -103,7 +107,7 @@ const WorkspaceCard = () => {
|
||||
<WorkspaceIcon name={currentWorkspace.name} className="h-6 w-6 rounded-lg" />
|
||||
<div className="min-w-0 grow">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="max-w-[120px] truncate system-sm-medium text-text-primary">{currentWorkspace.name}</span>
|
||||
<span className="max-w-[120px] truncate system-sm-medium text-text-primary" title={currentWorkspace.name}>{currentWorkspace.name}</span>
|
||||
<WorkspacePlanBadge plan={workspacePlan} />
|
||||
</div>
|
||||
</div>
|
||||
@ -120,13 +124,14 @@ const WorkspaceCard = () => {
|
||||
}}
|
||||
>
|
||||
<span className="i-custom-vender-main-nav-credits h-3 w-3 shrink-0" aria-hidden />
|
||||
<span className="truncate system-xs-medium">{formattedCredits}</span>
|
||||
<span className="truncate system-xs-medium" title={formattedCredits}>{formattedCredits}</span>
|
||||
<span className="shrink-0 system-xs-regular">{t('mainNav.workspace.creditsUnit', { ns: 'common' })}</span>
|
||||
</button>
|
||||
{enableBilling && (
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-[120px] shrink-0 truncate px-1 system-xs-semibold-uppercase text-saas-dify-blue-accessible transition-colors hover:text-saas-dify-blue-static-hover"
|
||||
title={t('upgradeBtn.encourageShort', { ns: 'billing' })}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handlePlanClick()
|
||||
@ -148,7 +153,7 @@ const WorkspaceCard = () => {
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<div className="flex min-w-0 grow flex-col items-start justify-center gap-1">
|
||||
<div className="max-w-[120px] shrink-0 truncate system-xl-medium leading-5 text-text-primary">{currentWorkspace.name}</div>
|
||||
<div className="max-w-[120px] shrink-0 truncate system-xl-medium leading-5 text-text-primary" title={currentWorkspace.name}>{currentWorkspace.name}</div>
|
||||
<WorkspacePlanBadge plan={workspacePlan} />
|
||||
</div>
|
||||
<WorkspaceIcon name={currentWorkspace.name} className="h-9 w-9" />
|
||||
@ -183,6 +188,7 @@ const WorkspaceCard = () => {
|
||||
<button
|
||||
type="button"
|
||||
key={workspace.id}
|
||||
title={workspace.name}
|
||||
className={cn(
|
||||
'flex h-8 w-full items-center gap-2 rounded-lg px-3 py-1 text-left transition-colors hover:bg-state-base-hover',
|
||||
workspace.current && 'text-text-secondary',
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
DEFAULT_ACCOUNT_SETTING_TAB,
|
||||
isValidAccountSettingTab,
|
||||
} from '@/app/components/header/account-setting/constants'
|
||||
import { getMovedAccountSettingDestination } from '@/app/components/header/account-setting/destinations'
|
||||
import {
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from '@/app/education-apply/constants'
|
||||
@ -27,6 +28,7 @@ import {
|
||||
usePricingModal,
|
||||
} from '@/hooks/use-query-params'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useTriggerEventsLimitModal } from './hooks/use-trigger-events-limit-modal'
|
||||
import {
|
||||
ModalContext,
|
||||
@ -82,6 +84,7 @@ export const ModalContextProvider = ({
|
||||
// Use nuqs hooks for URL-based modal state management
|
||||
const [showPricingModal, setPricingModalOpen] = usePricingModal()
|
||||
const [urlAccountModalState, setUrlAccountModalState] = useAccountSettingModal()
|
||||
const router = useRouter()
|
||||
|
||||
const accountSettingCallbacksRef = useRef<Omit<ModalState<AccountSettingTab>, 'payload'> | null>(null)
|
||||
const accountSettingTab = urlAccountModalState.isOpen
|
||||
@ -131,15 +134,36 @@ export const ModalContextProvider = ({
|
||||
return
|
||||
}
|
||||
const { payload, ...callbacks } = resolvedState
|
||||
const movedDestination = getMovedAccountSettingDestination(payload)
|
||||
if (movedDestination) {
|
||||
accountSettingCallbacksRef.current = null
|
||||
setUrlAccountModalState(null)
|
||||
router.push(movedDestination)
|
||||
return
|
||||
}
|
||||
|
||||
accountSettingCallbacksRef.current = callbacks
|
||||
setUrlAccountModalState({ payload })
|
||||
}, [accountSettingTab, setUrlAccountModalState])
|
||||
}, [accountSettingTab, router, setUrlAccountModalState])
|
||||
|
||||
useEffect(() => {
|
||||
if (!urlAccountModalState.isOpen)
|
||||
accountSettingCallbacksRef.current = null
|
||||
}, [urlAccountModalState.isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountSettingTab)
|
||||
return
|
||||
|
||||
const movedDestination = getMovedAccountSettingDestination(accountSettingTab)
|
||||
if (!movedDestination)
|
||||
return
|
||||
|
||||
accountSettingCallbacksRef.current = null
|
||||
setUrlAccountModalState(null)
|
||||
router.push(movedDestination)
|
||||
}, [accountSettingTab, router, setUrlAccountModalState])
|
||||
|
||||
const { plan, isFetchedPlan } = useProviderContext()
|
||||
const {
|
||||
showTriggerEventsLimitModal,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user