This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
import { RiArrowDownCircleLine, RiArrowRightSLine, RiVerifiedBadgeLine } from '@remixicon/react'
import type { FC, MouseEvent } from 'react'
import { Fragment, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useMutation } from '@tanstack/react-query'
import PremiumBadge from '../../base/premium-badge'
import SparklesSoft from '../../base/icons/src/public/common/SparklesSoft'
import Button from '../../base/button'
import Soc2 from '../../base/icons/src/public/common/Soc2'
import Iso from '../../base/icons/src/public/common/Iso'
import Gdpr from '../../base/icons/src/public/common/Gdpr'
import Toast from '../../base/toast'
import Tooltip from '../../base/tooltip'
import cn from '@/utils/classnames'
import { useProviderContext } from '@/context/provider-context'
import { Plan } from '@/app/components/billing/type'
import { useModalContext } from '@/context/modal-context'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { getDocDownloadUrl } from '@/service/common'
enum DocName {
SOC2_Type_I = 'SOC2_Type_I',
SOC2_Type_II = 'SOC2_Type_II',
ISO_27001 = 'ISO_27001',
GDPR = 'GDPR',
}
type UpgradeOrDownloadProps = {
doc_name: DocName
}
const UpgradeOrDownload: FC<UpgradeOrDownloadProps> = ({ doc_name }) => {
const { t } = useTranslation()
const { plan } = useProviderContext()
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
const isFreePlan = plan.type === Plan.sandbox
const handlePlanClick = useCallback(() => {
if (isFreePlan)
setShowPricingModal()
else
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING })
}, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
const { isPending, mutate: downloadCompliance } = useMutation({
mutationKey: ['downloadCompliance', doc_name],
mutationFn: async () => {
try {
const ret = await getDocDownloadUrl(doc_name)
const a = document.createElement('a')
a.href = ret.url
a.click()
Toast.notify({
type: 'success',
message: t('common.operation.downloadSuccess'),
})
}
catch (error) {
console.error(error)
Toast.notify({
type: 'error',
message: t('common.operation.downloadFailed'),
})
}
},
})
const whichPlanCanDownloadCompliance = {
[DocName.SOC2_Type_I]: [Plan.professional, Plan.team],
[DocName.SOC2_Type_II]: [Plan.team],
[DocName.ISO_27001]: [Plan.team],
[DocName.GDPR]: [Plan.team, Plan.professional, Plan.sandbox],
}
const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[doc_name].includes(plan.type)
const handleDownloadClick = useCallback((e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
downloadCompliance()
}, [downloadCompliance])
if (isCurrentPlanCanDownload) {
return <Button loading={isPending} disabled={isPending} size='small' variant='secondary' className='flex items-center gap-[1px]' onClick={handleDownloadClick}>
<RiArrowDownCircleLine className='size-[14px] text-components-button-secondary-text-disabled' />
<span className='system-xs-medium px-[3px] text-components-button-secondary-text'>{t('common.operation.download')}</span>
</Button>
}
const upgradeTooltip: Record<Plan, string> = {
[Plan.sandbox]: t('common.compliance.sandboxUpgradeTooltip'),
[Plan.professional]: t('common.compliance.professionalUpgradeTooltip'),
[Plan.team]: '',
[Plan.enterprise]: '',
}
return <Tooltip asChild={false} popupContent={upgradeTooltip[plan.type]}>
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
<SparklesSoft className='flex h-3.5 w-3.5 items-center py-[1px] pl-[3px] text-components-premium-badge-indigo-text-stop-0' />
<div className='system-xs-medium'>
<span className='p-1'>
{t('billing.upgradeBtn.encourageShort')}
</span>
</div>
</PremiumBadge>
</Tooltip>
}
export default function Compliance() {
const itemClassName = `
flex items-center w-full h-10 pl-1 pr-2 py-1 text-text-secondary system-md-regular
rounded-lg hover:bg-state-base-hover gap-1
`
const { t } = useTranslation()
return <Menu as="div" className="relative h-full w-full">
{
({ open }) => (
<>
<MenuButton className={
cn('group flex h-9 w-full items-center gap-1 rounded-lg py-2 pl-3 pr-2 hover:bg-state-base-hover',
open && 'bg-state-base-hover',
)}>
<RiVerifiedBadgeLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-left text-text-secondary'>{t('common.userProfile.compliance')}</div>
<RiArrowRightSLine className='size-[14px] shrink-0 text-text-tertiary' />
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
className={cn(
`absolute top-[1px] z-10 max-h-[70vh] w-[337px] origin-top-right -translate-x-full divide-y divide-divider-subtle overflow-y-scroll
rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px] focus:outline-none
`,
)}
>
<div className="px-1 py-1">
<MenuItem>
<div
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}>
<Soc2 className='size-7 shrink-0' />
<div className='system-md-regular grow truncate px-1 text-text-secondary'>{t('common.compliance.soc2Type1')}</div>
<UpgradeOrDownload doc_name={DocName.SOC2_Type_I} />
</div>
</MenuItem>
<MenuItem>
<div
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}>
<Soc2 className='size-7 shrink-0' />
<div className='system-md-regular grow truncate px-1 text-text-secondary'>{t('common.compliance.soc2Type2')}</div>
<UpgradeOrDownload doc_name={DocName.SOC2_Type_II} />
</div>
</MenuItem>
<MenuItem>
<div
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}>
<Iso className='size-7 shrink-0' />
<div className='system-md-regular grow truncate px-1 text-text-secondary'>{t('common.compliance.iso27001')}</div>
<UpgradeOrDownload doc_name={DocName.ISO_27001} />
</div>
</MenuItem>
<MenuItem>
<div
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}>
<Gdpr className='size-7 shrink-0' />
<div className='system-md-regular grow truncate px-1 text-text-secondary'>{t('common.compliance.gdpr')}</div>
<UpgradeOrDownload doc_name={DocName.GDPR} />
</div>
</MenuItem>
</div>
</MenuItems>
</Transition>
</>
)
}
</Menu>
}

View File

@@ -0,0 +1,227 @@
'use client'
import { useTranslation } from 'react-i18next'
import { Fragment, useState } from 'react'
import { useRouter } from 'next/navigation'
import {
RiAccountCircleLine,
RiArrowRightUpLine,
RiBookOpenLine,
RiGithubLine,
RiGraduationCapFill,
RiInformation2Line,
RiLogoutBoxRLine,
RiMap2Line,
RiSettings3Line,
RiStarLine,
RiTShirt2Line,
} from '@remixicon/react'
import Link from 'next/link'
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
import Indicator from '../indicator'
import AccountAbout from '../account-about'
import GithubStar from '../github-star'
import Support from './support'
import Compliance from './compliance'
import PremiumBadge from '@/app/components/base/premium-badge'
import Avatar from '@/app/components/base/avatar'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import { IS_CLOUD_EDITION } from '@/config'
import cn from '@/utils/classnames'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useDocLink } from '@/context/i18n'
import { useLogout } from '@/service/use-common'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
export default function AppSelector() {
const itemClassName = `
flex items-center w-full h-8 pl-3 pr-2 text-text-secondary system-md-regular
rounded-lg hover:bg-state-base-hover cursor-pointer gap-1
`
const router = useRouter()
const [aboutVisible, setAboutVisible] = useState(false)
const { systemFeatures } = useGlobalPublicStore()
const { t } = useTranslation()
const docLink = useDocLink()
const { userProfile, langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { isEducationAccount } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext()
const { mutateAsync: logout } = useLogout()
const handleLogout = async () => {
await logout()
localStorage.removeItem('setup_status')
// Tokens are now stored in cookies and cleared by backend
// To avoid use other account's education notice info
localStorage.removeItem('education-reverify-prev-expire-at')
localStorage.removeItem('education-reverify-has-noticed')
localStorage.removeItem('education-expired-has-noticed')
router.push('/signin')
}
return (
<div className="">
<Menu as="div" className="relative inline-block text-left">
{
({ open, close }) => (
<>
<MenuButton className={cn('inline-flex items-center rounded-[20px] p-0.5 hover:bg-background-default-dodge', open && 'bg-background-default-dodge')}>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} />
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
className="
absolute right-0 mt-1.5 w-60 max-w-80
origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur shadow-lg
backdrop-blur-sm focus:outline-none
"
>
<div className="px-1 py-1">
<MenuItem disabled>
<div className='flex flex-nowrap items-center py-2 pl-3 pr-2'>
<div className='grow'>
<div className='system-md-medium break-all text-text-primary'>
{userProfile.name}
{isEducationAccount && (
<PremiumBadge size='s' color='blue' className='ml-1 !px-2'>
<RiGraduationCapFill className='mr-1 h-3 w-3' />
<span className='system-2xs-medium'>EDU</span>
</PremiumBadge>
)}
</div>
<div className='system-xs-regular break-all text-text-tertiary'>{userProfile.email}</div>
</div>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} />
</div>
</MenuItem>
<MenuItem>
<Link
className={cn(itemClassName, 'group',
'data-[active]:bg-state-base-hover',
)}
href='/account'
target='_self' rel='noopener noreferrer'>
<RiAccountCircleLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.account.account')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</Link>
</MenuItem>
<MenuItem>
<div className={cn(itemClassName,
'data-[active]:bg-state-base-hover',
)} onClick={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })}>
<RiSettings3Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.settings')}</div>
</div>
</MenuItem>
</div>
{!systemFeatures.branding.enabled && <>
<div className='p-1'>
<MenuItem>
<Link
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href={docLink('/introduction')}
target='_blank' rel='noopener noreferrer'>
<RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</Link>
</MenuItem>
<Support closeAccountDropdown={close} />
{IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
</div>
<div className='p-1'>
<MenuItem>
<Link
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href='https://roadmap.dify.ai'
target='_blank' rel='noopener noreferrer'>
<RiMap2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.roadmap')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</Link>
</MenuItem>
<MenuItem>
<Link
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href='https://github.com/langgenius/dify'
target='_blank' rel='noopener noreferrer'>
<RiGithubLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.github')}</div>
<div className='flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]'>
<RiStarLine className='size-3 shrink-0 text-text-tertiary' />
<GithubStar className='system-2xs-medium-uppercase text-text-tertiary' />
</div>
</Link>
</MenuItem>
{
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
<MenuItem>
<div className={cn(itemClassName, 'justify-between',
'data-[active]:bg-state-base-hover',
)} onClick={() => setAboutVisible(true)}>
<RiInformation2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div>
<div className='flex shrink-0 items-center'>
<div className='system-xs-regular mr-2 text-text-tertiary'>{langGeniusVersionInfo.current_version}</div>
<Indicator color={langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
</div>
</MenuItem>
)
}
</div>
</>}
<MenuItem disabled>
<div className='p-1'>
<div className={cn(itemClassName, 'hover:bg-transparent')}>
<RiTShirt2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.theme.theme')}</div>
<ThemeSwitcher />
</div>
</div>
</MenuItem>
<MenuItem>
<div className='p-1' onClick={() => handleLogout()}>
<div
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
>
<RiLogoutBoxRLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.logout')}</div>
</div>
</div>
</MenuItem>
</MenuItems>
</Transition>
</>
)
}
</Menu>
{
aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} />
}
</div >
)
}

View File

@@ -0,0 +1,115 @@
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
import { RiArrowRightSLine, RiArrowRightUpLine, RiChatSmile2Line, RiDiscordLine, RiDiscussLine, RiMailSendLine, RiQuestionLine } from '@remixicon/react'
import { Fragment } from 'react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
import { useProviderContext } from '@/context/provider-context'
import { Plan } from '@/app/components/billing/type'
import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils'
import { mailToSupport } from '../utils/util'
import { useAppContext } from '@/context/app-context'
import { ZENDESK_WIDGET_KEY } from '@/config'
type SupportProps = {
closeAccountDropdown: () => void
}
export default function Support({ closeAccountDropdown }: SupportProps) {
const itemClassName = `
flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular
rounded-lg hover:bg-state-base-hover cursor-pointer gap-1
`
const { t } = useTranslation()
const { plan } = useProviderContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const hasDedicatedChannel = plan.type !== Plan.sandbox
return <Menu as="div" className="relative h-full w-full">
{
({ open }) => (
<>
<MenuButton className={
cn('group flex h-9 w-full items-center gap-1 rounded-lg py-2 pl-3 pr-2 hover:bg-state-base-hover',
open && 'bg-state-base-hover',
)}>
<RiQuestionLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-left text-text-secondary'>{t('common.userProfile.support')}</div>
<RiArrowRightSLine className='size-[14px] shrink-0 text-text-tertiary' />
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
className={cn(
`absolute top-[1px] z-10 max-h-[70vh] w-[216px] origin-top-right -translate-x-full divide-y divide-divider-subtle overflow-y-auto
rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px] focus:outline-none
`,
)}
>
<div className="px-1 py-1">
{hasDedicatedChannel && (
<MenuItem>
{ZENDESK_WIDGET_KEY && ZENDESK_WIDGET_KEY.trim() !== '' ? (
<button
className={cn(itemClassName, 'group justify-between text-left data-[active]:bg-state-base-hover')}
onClick={() => {
toggleZendeskWindow(true)
closeAccountDropdown()
}}
>
<RiChatSmile2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.contactUs')}</div>
</button>
) : (
<a
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo?.current_version)}
target='_blank' rel='noopener noreferrer'>
<RiMailSendLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.emailSupport')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</a>
)}
</MenuItem>
)}
<MenuItem>
<Link
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href='https://forum.dify.ai/'
target='_blank' rel='noopener noreferrer'>
<RiDiscussLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.forum')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</Link>
</MenuItem>
<MenuItem>
<Link
className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover',
)}
href='https://discord.gg/5AEfbxcd9k'
target='_blank' rel='noopener noreferrer'>
<RiDiscordLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.community')}</div>
<RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
</Link>
</MenuItem>
</div>
</MenuItems>
</Transition>
</>
)
}
</Menu>
}

View File

@@ -0,0 +1,5 @@
.popup {
left: 4px;
transform: translateX(-100%);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
}

View File

@@ -0,0 +1,95 @@
import { Fragment } from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react'
import { RiArrowDownSLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import PlanBadge from '@/app/components/header/plan-badge'
import { switchWorkspace } from '@/service/common'
import { useWorkspacesContext } from '@/context/workspace-context'
import { ToastContext } from '@/app/components/base/toast'
import type { Plan } from '@/app/components/billing/type'
const WorkplaceSelector = () => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const { workspaces } = useWorkspacesContext()
const currentWorkspace = workspaces.find(v => v.current)
const handleSwitchWorkspace = async (tenant_id: string) => {
try {
if (currentWorkspace?.id === tenant_id)
return
await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
location.assign(`${location.origin}${basePath}`)
}
catch {
notify({ type: 'error', message: t('common.provider.saveFailed') })
}
}
return (
<Menu as="div" className="min-w-0">
{
({ open }) => (
<>
<MenuButton className={cn(
`
group flex w-full cursor-pointer items-center
p-0.5 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} rounded-[10px]
`,
)}>
<div className='mr-1.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px] max-[800px]:mr-0'>
<span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{currentWorkspace?.name[0]?.toLocaleUpperCase()}</span>
</div>
<div className='flex min-w-0 items-center'>
<div className={'system-sm-medium min-w-0 max-w-[149px] truncate text-text-secondary max-[800px]:hidden'}>{currentWorkspace?.name}</div>
<RiArrowDownSLine className='h-4 w-4 shrink-0 text-text-secondary' />
</div>
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
anchor="bottom start"
className={cn(
`
shadows-shadow-lg absolute left-[-15px] z-[1000] mt-1 flex max-h-[400px] w-[280px] flex-col items-start overflow-y-auto
rounded-xl bg-components-panel-bg-blur backdrop-blur-[5px]
`,
)}
>
<div className="flex w-full flex-col items-start self-stretch rounded-xl border-[0.5px] border-components-panel-border p-1 pb-2 shadow-lg ">
<div className='flex items-start self-stretch px-3 pb-0.5 pt-1'>
<span className='system-xs-medium-uppercase flex-1 text-text-tertiary'>{t('common.userProfile.workspace')}</span>
</div>
{
workspaces.map(workspace => (
<div className='flex items-center gap-2 self-stretch rounded-lg py-1 pl-3 pr-2 hover:bg-state-base-hover' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
<div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'>
<span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{workspace?.name[0]?.toLocaleUpperCase()}</span>
</div>
<div className='system-md-regular line-clamp-1 grow cursor-pointer overflow-hidden text-ellipsis text-text-secondary'>{workspace.name}</div>
<PlanBadge plan={workspace.plan as Plan} />
</div>
))
}
</div>
</MenuItems>
</Transition>
</>
)
}
</Menu>
)
}
export default WorkplaceSelector