Feat: settings dark mode (#15184)
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Webhooks } from '@/app/components/base/icons/src/vender/line/development'
|
||||
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
|
||||
import {
|
||||
RiExternalLinkLine,
|
||||
RiPuzzle2Line,
|
||||
} from '@remixicon/react'
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='mb-2 p-6 rounded-2xl bg-gray-50'>
|
||||
<div className='flex items-center justify-center mb-3 w-12 h-12 rounded-[10px] border border-[#EAECF5]'>
|
||||
<Webhooks className='w-6 h-6 text-gray-500' />
|
||||
<div className='mb-2 p-6 rounded-xl bg-background-section'>
|
||||
<div className='flex items-center justify-center mb-3 w-10 h-10 rounded-[10px] bg-components-card-bg-alt backdrop-blur-sm border-[0.5px] border-components-card-border shadow-lg'>
|
||||
<RiPuzzle2Line className='w-5 h-5 text-text-accent' />
|
||||
</div>
|
||||
<div className='mb-2 text-sm text-gray-600'>{t('common.apiBasedExtension.title')}</div>
|
||||
<div className='mb-1 text-text-secondary system-sm-medium'>{t('common.apiBasedExtension.title')}</div>
|
||||
<a
|
||||
className='flex items-center mb-2 h-[18px] text-xs text-primary-600'
|
||||
className='flex items-center system-xs-regular text-text-accent'
|
||||
href={t('common.apiBasedExtension.linkUrl') || '/'}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
>
|
||||
<BookOpen01 className='mr-1 w-3 h-3' />
|
||||
{t('common.apiBasedExtension.link')}
|
||||
<RiExternalLinkLine className='ml-1 w-3 h-3' />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import Item from './item'
|
||||
import Empty from './empty'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { fetchApiBasedExtensionList } from '@/service/common'
|
||||
|
||||
@@ -41,13 +42,14 @@ const ApiBasedExtensionPage = () => {
|
||||
))
|
||||
)
|
||||
}
|
||||
<div
|
||||
className='flex items-center justify-center px-3 h-8 text-[13px] font-medium text-gray-700 rounded-lg bg-gray-50 cursor-pointer'
|
||||
<Button
|
||||
variant='secondary'
|
||||
className='w-full'
|
||||
onClick={handleOpenApiBasedExtensionModal}
|
||||
>
|
||||
<RiAddLine className='mr-2 w-4 h-4' />
|
||||
<RiAddLine className='mr-1 w-4 h-4' />
|
||||
{t('common.apiBasedExtension.add')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -3,8 +3,9 @@ import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
} from '@remixicon/react'
|
||||
import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { ApiBasedExtension } from '@/models/common'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { deleteApiBasedExtension } from '@/service/common'
|
||||
@@ -36,25 +37,25 @@ const Item: FC<ItemProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='group flex items-center mb-2 px-4 py-2 border-[0.5px] border-transparent rounded-xl bg-gray-50 hover:border-gray-200 hover:shadow-xs'>
|
||||
<div className='group flex items-center mb-2 px-4 py-2 border-[0.5px] border-transparent rounded-xl bg-components-input-bg-normal hover:border-components-input-border-active hover:shadow-xs'>
|
||||
<div className='grow'>
|
||||
<div className='mb-0.5 text-[13px] font-medium text-gray-700'>{data.name}</div>
|
||||
<div className='text-xs text-gray-500'>{data.api_endpoint}</div>
|
||||
<div className='mb-0.5 text-[13px] font-medium text-text-secondary'>{data.name}</div>
|
||||
<div className='text-xs text-text-tertiary'>{data.api_endpoint}</div>
|
||||
</div>
|
||||
<div className='hidden group-hover:flex items-center'>
|
||||
<div
|
||||
className='flex items-center mr-1 px-3 h-7 bg-white text-xs font-medium text-gray-700 rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer'
|
||||
<Button
|
||||
className='mr-1'
|
||||
onClick={handleOpenApiBasedExtensionModal}
|
||||
>
|
||||
<Edit02 className='mr-[5px] w-3.5 h-3.5' />
|
||||
<RiEditLine className='mr-1 w-4 h-4' />
|
||||
{t('common.operation.edit')}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center justify-center w-7 h-7 bg-white text-gray-700 rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer'
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</div>
|
||||
<RiDeleteBinLine className='mr-1 w-4 h-4' />
|
||||
{t('common.operation.delete')}
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
showDeleteConfirm
|
||||
|
@@ -101,9 +101,9 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({
|
||||
<a
|
||||
href={t('common.apiBasedExtension.linkUrl') || '/'}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='group flex items-center text-xs text-text-tertiary font-normal hover:text-text-accent'
|
||||
className='group flex items-center text-xs text-text-accent font-normal'
|
||||
>
|
||||
<BookOpen01 className='mr-1 w-3 h-3 text-text-tertiary group-hover:text-text-accent' />
|
||||
<BookOpen01 className='mr-1 w-3 h-3' />
|
||||
{t('common.apiBasedExtension.link')}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type OperateProps = {
|
||||
payload: {
|
||||
@@ -23,13 +24,6 @@ export default function Operate({
|
||||
payload,
|
||||
onAuthAgain,
|
||||
}: OperateProps) {
|
||||
const itemClassName = `
|
||||
flex px-3 py-2 hover:bg-gray-50 text-sm text-gray-700
|
||||
cursor-pointer
|
||||
`
|
||||
const itemIconClassName = `
|
||||
mr-2 mt-[2px] w-4 h-4 text-gray-500
|
||||
`
|
||||
const { t } = useTranslation()
|
||||
const { mutate } = useSWRConfig()
|
||||
|
||||
@@ -54,8 +48,8 @@ export default function Operate({
|
||||
{
|
||||
({ open }) => (
|
||||
<>
|
||||
<Menu.Button className={`flex items-center justify-center w-8 h-8 rounded-lg hover:bg-gray-100 ${open && 'bg-gray-100'}`}>
|
||||
<RiMoreFill className='w-4 h-4' />
|
||||
<Menu.Button className={cn('flex items-center justify-center w-8 h-8 rounded-lg hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
|
||||
<RiMoreFill className='w-4 h-4 text-text-secondary' />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
@@ -66,40 +60,34 @@ export default function Operate({
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="
|
||||
absolute right-0 top-9 w-60 max-w-80
|
||||
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
|
||||
shadow-lg
|
||||
"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 top-9 w-60 max-w-80 origin-top-right rounded-xl bg-components-panel-bg-blur backdrop-blur-sm border-[0.5px] border-components-panel-border shadow-lg">
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
<div
|
||||
className={itemClassName}
|
||||
className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer'
|
||||
onClick={onAuthAgain}
|
||||
>
|
||||
<RiStickyNoteAddLine className={itemIconClassName} />
|
||||
<RiStickyNoteAddLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' />
|
||||
<div>
|
||||
<div className='leading-5'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
|
||||
<div className='leading-5 text-xs text-gray-500'>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>
|
||||
{payload.total} {t('common.dataSource.notion.pagesAuthorized')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<div className={itemClassName} onClick={handleSync}>
|
||||
<RiLoopLeftLine className={itemIconClassName} />
|
||||
<div className='leading-5'>{t('common.dataSource.notion.sync')}</div>
|
||||
<div className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={handleSync}>
|
||||
<RiLoopLeftLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.sync')}</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<Menu.Item>
|
||||
<div className='p-1'>
|
||||
<div className={itemClassName} onClick={handleRemove}>
|
||||
<RiDeleteBinLine className={itemIconClassName} />
|
||||
<div className='leading-5'>{t('common.dataSource.notion.remove')}</div>
|
||||
<div className='p-1 border-t border-divider-subtle'>
|
||||
<div className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={handleRemove}>
|
||||
<RiDeleteBinLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' />
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.remove')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
|
@@ -89,11 +89,11 @@ const ConfigFirecrawlModal: FC<Props> = ({
|
||||
return (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-background-overlay'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='px-8 pt-8'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.configFirecrawl`)}</div>
|
||||
<div className='system-xl-semibold text-text-primary'>{t(`${I18N_PREFIX}.configFirecrawl`)}</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
@@ -114,7 +114,7 @@ const ConfigFirecrawlModal: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className='my-8 flex justify-between items-center h-8'>
|
||||
<a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]' target='_blank' href='https://www.firecrawl.dev/account'>
|
||||
<a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-text-accent' target='_blank' href='https://www.firecrawl.dev/account'>
|
||||
<span>{t(`${I18N_PREFIX}.getApiKeyLinkText`)}</span>
|
||||
<LinkExternal02 className='w-3 h-3' />
|
||||
</a>
|
||||
@@ -138,12 +138,12 @@ const ConfigFirecrawlModal: FC<Props> = ({
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-t-black/5'>
|
||||
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<div className='border-t-[0.5px] border-t-divider-regular'>
|
||||
<div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='text-primary-600 mx-1'
|
||||
className='text-text-accent mx-1'
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||
>
|
||||
|
@@ -75,11 +75,11 @@ const ConfigJinaReaderModal: FC<Props> = ({
|
||||
return (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-background-overlay'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='px-8 pt-8'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.configJinaReader`)}</div>
|
||||
<div className='system-xl-semibold text-text-primary'>{t(`${I18N_PREFIX}.configJinaReader`)}</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
@@ -93,7 +93,7 @@ const ConfigJinaReaderModal: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className='my-8 flex justify-between items-center h-8'>
|
||||
<a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]' target='_blank' href='https://jina.ai/reader/'>
|
||||
<a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-text-accent' target='_blank' href='https://jina.ai/reader/'>
|
||||
<span>{t(`${I18N_PREFIX}.getApiKeyLinkText`)}</span>
|
||||
<LinkExternal02 className='w-3 h-3' />
|
||||
</a>
|
||||
@@ -117,12 +117,12 @@ const ConfigJinaReaderModal: FC<Props> = ({
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-t-black/5'>
|
||||
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<div className='border-t-[0.5px] border-t-divider-regular'>
|
||||
<div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='text-primary-600 mx-1'
|
||||
className='text-text-accent mx-1'
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||
>
|
||||
|
@@ -85,10 +85,10 @@ const DataSourceWebsite: FC<Props> = ({ provider }) => {
|
||||
logo: ({ className }: { className: string }) => (
|
||||
item.provider === DataSourceProvider.fireCrawl
|
||||
? (
|
||||
<div className={cn(className, 'flex items-center justify-center w-5 h-5 bg-white border border-gray-100 text-xs font-medium text-gray-500 rounded ml-3')}>🔥</div>
|
||||
<div className={cn(className, 'flex items-center justify-center w-5 h-5 !bg-background-default border border-divider-subtle text-xs font-medium text-text-tertiary rounded ml-3')}>🔥</div>
|
||||
)
|
||||
: (
|
||||
<div className={cn(className, 'flex items-center justify-center w-5 h-5 bg-white border border-gray-100 text-xs font-medium text-gray-500 rounded ml-3')}>
|
||||
<div className={cn(className, 'flex items-center justify-center w-5 h-5 !bg-background-default border border-divider-subtle text-xs font-medium text-text-tertiary rounded ml-3')}>
|
||||
<span className={s.jinaLogo} />
|
||||
</div>
|
||||
)
|
||||
|
@@ -52,7 +52,7 @@ const ConfigItem: FC<Props> = ({
|
||||
? <Indicator className='shrink-0 mr-[6px]' color='green' />
|
||||
: <Indicator className='shrink-0 mr-[6px]' color='yellow' />
|
||||
}
|
||||
<div className={`shrink-0 mr-3 text-xs font-medium uppercase ${payload.isActive ? 'text-util-colors-green-green-600' : 'text-util-colors-warning-warning-600'}`}>
|
||||
<div className={`shrink-0 mr-3 system-xs-semibold-uppercase ${payload.isActive ? 'text-util-colors-green-green-600' : 'text-util-colors-warning-warning-600'}`}>
|
||||
{
|
||||
payload.isActive
|
||||
? t(isNotion ? 'common.dataSource.notion.connected' : 'common.dataSource.website.active')
|
||||
@@ -70,7 +70,7 @@ const ConfigItem: FC<Props> = ({
|
||||
|
||||
{
|
||||
isWebsite && !readOnly && (
|
||||
<div className='p-2 text-text-tertiary cursor-pointer rounded-md hover:bg-black/5' onClick={onRemove} >
|
||||
<div className='p-2 text-text-tertiary cursor-pointer rounded-md hover:bg-state-base-hover' onClick={onRemove} >
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</div>
|
||||
)
|
||||
|
@@ -8,12 +8,13 @@ import ConfigItem from './config-item'
|
||||
|
||||
import s from './style.module.css'
|
||||
import { DataSourceType } from './types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { DataSourceProvider } from '@/models/common'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
type: DataSourceType
|
||||
provider: DataSourceProvider
|
||||
provider?: DataSourceProvider
|
||||
isConfigured: boolean
|
||||
onConfigure: () => void
|
||||
readOnly: boolean
|
||||
@@ -43,13 +44,13 @@ const Panel: FC<Props> = ({
|
||||
return (
|
||||
<div className='mb-2 bg-background-section-burn rounded-xl'>
|
||||
<div className='flex items-center px-3 py-[9px]'>
|
||||
<div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-divider-subtle rounded-lg bg-background-default')} />
|
||||
<div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-divider-subtle rounded-lg !bg-background-default')} />
|
||||
<div className='grow'>
|
||||
<div className='flex items-center h-5'>
|
||||
<div className='text-sm font-medium text-text-primary'>{t(`common.dataSource.${type}.title`)}</div>
|
||||
{isWebsite && (
|
||||
<div className='ml-1 leading-[18px] px-1.5 rounded-md bg-white border border-gray-100 text-xs font-medium text-gray-700'>
|
||||
<span className='text-gray-500'>{t('common.dataSource.website.with')}</span> { provider === DataSourceProvider.fireCrawl ? '🔥 Firecrawl' : 'Jina Reader'}
|
||||
<div className='ml-1 leading-[18px] px-1.5 rounded-md bg-components-badge-white-to-dark text-xs font-medium text-text-secondary'>
|
||||
<span className='text-text-tertiary'>{t('common.dataSource.website.with')}</span> { provider === DataSourceProvider.fireCrawl ? '🔥 Firecrawl' : 'Jina Reader'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -66,16 +67,13 @@ const Panel: FC<Props> = ({
|
||||
{
|
||||
isConfigured
|
||||
? (
|
||||
<div
|
||||
className={
|
||||
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
|
||||
rounded-md text-xs font-medium text-gray-700
|
||||
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
}
|
||||
<Button
|
||||
disabled={readOnly}
|
||||
className='ml-3'
|
||||
onClick={onConfigure}
|
||||
>
|
||||
{t('common.dataSource.configure')}
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
|
@@ -194,12 +194,12 @@ export default function AccountSetting({
|
||||
</div>
|
||||
<div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b border-divider-regular')}>
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-text-tertiary'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>
|
||||
{activeItem?.name}
|
||||
{activeItem?.description && (
|
||||
<div className='mt-1 system-sm-regular text-text-tertiary'>{activeItem?.description}</div>
|
||||
)}
|
||||
</div>
|
||||
{activeItem?.key === 'provider' && (
|
||||
<div className='grow flex justify-end'>
|
||||
<Input
|
||||
|
@@ -18,9 +18,11 @@ import type { InvitationResult } from '@/models/common'
|
||||
import LogoEmbeddedChatHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import Button from '@/app/components/base/button'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import { NUM_INFINITE } from '@/app/components/billing/config'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import cn from '@/utils/classnames'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const MembersPage = () => {
|
||||
@@ -35,7 +37,13 @@ const MembersPage = () => {
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext()
|
||||
const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers)
|
||||
const { data, mutate } = useSWR(
|
||||
{
|
||||
url: '/workspaces/current/members',
|
||||
params: {},
|
||||
},
|
||||
fetchMembers,
|
||||
)
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
||||
const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([])
|
||||
const [invitedModalVisible, setInvitedModalVisible] = useState(false)
|
||||
@@ -47,17 +55,17 @@ const MembersPage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex items-center mb-4 p-3 gap-1 bg-gray-50 rounded-2xl'>
|
||||
<LogoEmbeddedChatHeader className='!w-10 !h-10' />
|
||||
<div className='grow mx-2'>
|
||||
<div className='text-sm font-medium text-gray-900'>{currentWorkspace?.name}</div>
|
||||
<div className='flex items-center mb-4 p-3 pr-5 gap-3 bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 rounded-xl border-t-[0.5px] border-l-[0.5px] border-divider-subtle'>
|
||||
<LogoEmbeddedChatHeader className='!w-12 !h-12' />
|
||||
<div className='grow'>
|
||||
<div className='system-md-semibold text-text-secondary'>{currentWorkspace?.name}</div>
|
||||
{enableBilling && (
|
||||
<div className='text-xs text-gray-500'>
|
||||
<div className='mt-1 system-xs-medium text-text-tertiary'>
|
||||
{isNotUnlimitedMemberPlan
|
||||
? (
|
||||
<div className='flex space-x-1'>
|
||||
<div>{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}</div>
|
||||
<div className='text-gray-700'>{accounts.length}</div>
|
||||
<div className=''>{accounts.length}</div>
|
||||
<div>/</div>
|
||||
<div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div>
|
||||
</div>
|
||||
@@ -75,14 +83,10 @@ const MembersPage = () => {
|
||||
{isMemberFull && (
|
||||
<UpgradeBtn className='mr-2' loc='member-invite' />
|
||||
)}
|
||||
<div className={
|
||||
`shrink-0 flex items-center py-[7px] px-3 border-[0.5px] border-gray-200
|
||||
text-[13px] font-medium text-primary-600 bg-white
|
||||
shadow-xs rounded-lg ${(isCurrentWorkspaceManager && !isMemberFull) ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
} onClick={() => (isCurrentWorkspaceManager && !isMemberFull) && setInviteModalVisible(true)}>
|
||||
<RiUserAddLine className='w-4 h-4 mr-2 ' />
|
||||
<Button variant='primary' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull} onClick={() => setInviteModalVisible(true)}>
|
||||
<RiUserAddLine className='w-4 h-4 mr-1' />
|
||||
{t('common.members.invite')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className='overflow-visible lg:overflow-visible'>
|
||||
<div className='flex items-center py-[7px] border-b border-divider-regular min-w-[480px]'>
|
||||
@@ -99,18 +103,18 @@ const MembersPage = () => {
|
||||
<div className=''>
|
||||
<div className='text-text-secondary system-sm-medium'>
|
||||
{account.name}
|
||||
{account.status === 'pending' && <span className='ml-1 system-xs-regular text-[#DC6803]'>{t('common.members.pending')}</span>}
|
||||
{account.status === 'pending' && <span className='ml-1 system-xs-medium text-text-warning'>{t('common.members.pending')}</span>}
|
||||
{userProfile.email === account.email && <span className='system-xs-regular text-text-tertiary'>{t('common.members.you')}</span>}
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{account.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 flex items-center w-[104px] py-2 system-xs-regular text-text-secondary'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div>
|
||||
<div className='shrink-0 flex items-center w-[104px] py-2 system-sm-regular text-text-secondary'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div>
|
||||
<div className='shrink-0 w-[96px] flex items-center'>
|
||||
{
|
||||
((isCurrentWorkspaceOwner && account.role !== 'owner') || (isCurrentWorkspaceManager && !['owner', 'admin'].includes(account.role)))
|
||||
? <Operation member={account} operatorRole={currentWorkspace.role} onOperate={mutate} />
|
||||
: <div className='px-3 system-xs-regular text-text-secondary'>{RoleMap[account.role] || RoleMap.normal}</div>
|
||||
: <div className='px-3 system-sm-regular text-text-secondary'>{RoleMap[account.role] || RoleMap.normal}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReactMultiEmail } from 'react-multi-email'
|
||||
import { RiErrorWarningFill } from '@remixicon/react'
|
||||
@@ -59,10 +59,10 @@ const InviteModal = ({
|
||||
<div className={cn(s.wrap)}>
|
||||
<Modal overflowVisible isShow onClose={() => { }} className={cn(s.modal)}>
|
||||
<div className='flex justify-between mb-2'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{t('common.members.inviteTeamMember')}</div>
|
||||
<XMarkIcon className='w-4 h-4 cursor-pointer' onClick={onCancel} />
|
||||
<div className='text-xl font-semibold text-text-primary'>{t('common.members.inviteTeamMember')}</div>
|
||||
<RiCloseLine className='w-4 h-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
|
||||
</div>
|
||||
<div className='mb-3 text-[13px] text-gray-500'>{t('common.members.inviteTeamMemberTip')}</div>
|
||||
<div className='mb-3 text-[13px] text-text-tertiary'>{t('common.members.inviteTeamMemberTip')}</div>
|
||||
{!isEmailSetup && (
|
||||
<div className='grow basis-0 overflow-y-auto pb-4'>
|
||||
<div className='relative mb-1 p-2 rounded-xl border border-components-panel-border shadow-xs'>
|
||||
@@ -80,19 +80,18 @@ const InviteModal = ({
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className='mb-2 text-sm font-medium text-gray-900'>{t('common.members.email')}</div>
|
||||
<div className='mb-2 text-sm font-medium text-text-primary'>{t('common.members.email')}</div>
|
||||
<div className='mb-8 h-36 flex items-stretch'>
|
||||
<ReactMultiEmail
|
||||
className={cn('w-full pt-2 px-3 outline-none border-none',
|
||||
'appearance-none text-sm text-gray-900 rounded-lg overflow-y-auto',
|
||||
s.emailsInput,
|
||||
className={cn('w-full pt-2 px-3 outline-none !bg-components-input-bg-normal border-components-input-border-active',
|
||||
'appearance-none text-sm !text-text-primary rounded-lg overflow-y-auto',
|
||||
)}
|
||||
autoFocus
|
||||
emails={emails}
|
||||
inputClassName='bg-transparent'
|
||||
onChange={setEmails}
|
||||
getLabel={(email, index, removeEmail) =>
|
||||
<div data-tag key={index} className={cn(s.emailBackground)}>
|
||||
<div data-tag key={index} className={cn('bg-components-button-secondary-bg')}>
|
||||
<div data-tag-item>{email}</div>
|
||||
<span data-tag-handle onClick={() => removeEmail(index)}>
|
||||
×
|
||||
|
@@ -34,53 +34,53 @@ const RoleSelector = ({ value, onChange }: RoleSelectorProps) => {
|
||||
onClick={() => setOpen(v => !v)}
|
||||
className='block'
|
||||
>
|
||||
<div className={cn('flex items-center px-3 py-2 rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}`) })}</div>
|
||||
<RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />
|
||||
<div className={cn('flex items-center px-3 py-2 rounded-lg bg-components-input-bg-normal cursor-pointer hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
|
||||
<div className='grow mr-2 text-text-primary text-sm leading-5'>{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}`) })}</div>
|
||||
<RiArrowDownSLine className='shrink-0 w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[336px] bg-white rounded-lg border-[0.5px] bg-gray-200 shadow-lg'>
|
||||
<div className='relative w-[336px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg'>
|
||||
<div className='p-1'>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
<div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => {
|
||||
onChange('normal')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='relative pl-5'>
|
||||
<div className='text-gray-700 text-sm leading-5'>{t('common.members.normal')}</div>
|
||||
<div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.normalTip')}</div>
|
||||
{value === 'normal' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>}
|
||||
<div className='text-text-secondary text-sm leading-5'>{t('common.members.normal')}</div>
|
||||
<div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.normalTip')}</div>
|
||||
{value === 'normal' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
<div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => {
|
||||
onChange('editor')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='relative pl-5'>
|
||||
<div className='text-gray-700 text-sm leading-5'>{t('common.members.editor')}</div>
|
||||
<div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.editorTip')}</div>
|
||||
{value === 'editor' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>}
|
||||
<div className='text-text-secondary text-sm leading-5'>{t('common.members.editor')}</div>
|
||||
<div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.editorTip')}</div>
|
||||
{value === 'editor' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
<div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => {
|
||||
onChange('admin')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='relative pl-5'>
|
||||
<div className='text-gray-700 text-sm leading-5'>{t('common.members.admin')}</div>
|
||||
<div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.adminTip')}</div>
|
||||
{value === 'admin' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>}
|
||||
<div className='text-text-secondary text-sm leading-5'>{t('common.members.admin')}</div>
|
||||
<div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.adminTip')}</div>
|
||||
{value === 'admin' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>}
|
||||
</div>
|
||||
</div>
|
||||
{datasetOperatorEnabled && (
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
<div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => {
|
||||
onChange('dataset_operator')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='relative pl-5'>
|
||||
<div className='text-gray-700 text-sm leading-5'>{t('common.members.datasetOperator')}</div>
|
||||
<div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.datasetOperatorTip')}</div>
|
||||
{value === 'dataset_operator' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>}
|
||||
<div className='text-text-secondary text-sm leading-5'>{t('common.members.datasetOperator')}</div>
|
||||
<div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.datasetOperatorTip')}</div>
|
||||
{value === 'dataset_operator' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.modal {
|
||||
padding: 32px !important;
|
||||
width: 480px !important;
|
||||
background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0) 22.44%), #F9FAFB !important;
|
||||
/* background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0) 22.44%), #F9FAFB !important; */
|
||||
}
|
||||
|
||||
.copyIcon {
|
||||
|
@@ -33,25 +33,25 @@ const InvitedModal = ({
|
||||
<div className='flex justify-between mb-3'>
|
||||
<div className='
|
||||
w-12 h-12 flex items-center justify-center rounded-xl
|
||||
bg-white border-[0.5px] border-gray-100
|
||||
bg-background-section-burn border-[0.5px] border-components-panel-border
|
||||
shadow-xl
|
||||
'>
|
||||
<CheckCircleIcon className='w-[22px] h-[22px] text-[#039855]' />
|
||||
</div>
|
||||
<XMarkIcon className='w-4 h-4 cursor-pointer' onClick={onCancel} />
|
||||
</div>
|
||||
<div className='mb-1 text-xl font-semibold text-gray-900'>{t('common.members.invitationSent')}</div>
|
||||
<div className='mb-1 text-xl font-semibold text-text-primary'>{t('common.members.invitationSent')}</div>
|
||||
{!IS_CE_EDITION && (
|
||||
<div className='mb-10 text-sm text-gray-500'>{t('common.members.invitationSentTip')}</div>
|
||||
<div className='mb-10 text-sm text-text-tertiary'>{t('common.members.invitationSentTip')}</div>
|
||||
)}
|
||||
{IS_CE_EDITION && (
|
||||
<>
|
||||
<div className='mb-5 text-sm text-gray-500'>{t('common.members.invitationSentTip')}</div>
|
||||
<div className='mb-5 text-sm text-text-tertiary'>{t('common.members.invitationSentTip')}</div>
|
||||
<div className='flex flex-col gap-2 mb-9'>
|
||||
{
|
||||
!!successInvitationResults.length
|
||||
&& <>
|
||||
<div className='py-2 text-sm font-Medium text-gray-900'>{t('common.members.invitationLink')}</div>
|
||||
<div className='py-2 text-sm font-Medium text-text-primary'>{t('common.members.invitationLink')}</div>
|
||||
{successInvitationResults.map(item =>
|
||||
<InvitationLink key={item.email} value={item} />)}
|
||||
</>
|
||||
@@ -59,7 +59,7 @@ const InvitedModal = ({
|
||||
{
|
||||
!!failedInvitationResults.length
|
||||
&& <>
|
||||
<div className='py-2 text-sm font-Medium text-gray-900'>{t('common.members.failedInvitationEmails')}</div>
|
||||
<div className='py-2 text-sm font-Medium text-text-primary'>{t('common.members.failedInvitationEmails')}</div>
|
||||
<div className='flex flex-wrap justify-between gap-y-1'>
|
||||
{
|
||||
failedInvitationResults.map(item =>
|
||||
|
@@ -35,21 +35,21 @@ const InvitationLink = ({
|
||||
}, [isCopied])
|
||||
|
||||
return (
|
||||
<div className='flex rounded-lg bg-gray-100 hover:bg-gray-100 border border-gray-200 py-2 items-center'>
|
||||
<div className="flex items-center flex-grow h-5">
|
||||
<div className='flex-grow bg-gray-100 text-[13px] relative h-full'>
|
||||
<div className='flex rounded-lg bg-components-input-bg-normal hover:bg-state-base-hover border border-components-input-border-active py-2 items-center'>
|
||||
<div className="flex items-center grow h-5">
|
||||
<div className='grow text-[13px] relative h-full'>
|
||||
<Tooltip
|
||||
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
|
||||
>
|
||||
<div className='absolute top-0 left-0 w-full pl-2 pr-2 truncate cursor-pointer r-0' onClick={copyHandle}>{value.url}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex-shrink-0 h-4 bg-gray-200 border" />
|
||||
<div className="shrink-0 h-4 bg-divider-regular border" />
|
||||
<Tooltip
|
||||
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
|
||||
>
|
||||
<div className="px-0.5 flex-shrink-0">
|
||||
<div className={`box-border w-[30px] h-[30px] flex items-center justify-center rounded-lg hover:bg-gray-100 cursor-pointer ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={copyHandle}>
|
||||
<div className="px-0.5 shrink-0">
|
||||
<div className={`box-border w-[30px] h-[30px] flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={copyHandle}>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@@ -1,3 +0,0 @@
|
||||
.popup {
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
@@ -4,26 +4,12 @@ import { Fragment, useMemo } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/24/outline'
|
||||
import s from './index.module.css'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Member } from '@/models/common'
|
||||
import { deleteMemberOrCancelInvitation, updateMemberRole } from '@/service/common'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
|
||||
const itemClassName = `
|
||||
flex px-3 py-2 cursor-pointer hover:bg-gray-50 rounded-lg
|
||||
`
|
||||
const itemIconClassName = `
|
||||
w-4 h-4 mt-[2px] mr-1 text-primary-600
|
||||
`
|
||||
const itemTitleClassName = `
|
||||
leading-[20px] text-sm text-gray-700 whitespace-nowrap
|
||||
`
|
||||
const itemDescClassName = `
|
||||
leading-[18px] text-xs text-gray-500 whitespace-nowrap
|
||||
`
|
||||
|
||||
type IOperationProps = {
|
||||
member: Member
|
||||
operatorRole: string
|
||||
@@ -90,15 +76,9 @@ const Operation = ({
|
||||
{
|
||||
({ open }) => (
|
||||
<>
|
||||
<Menu.Button className={cn(
|
||||
`
|
||||
group flex items-center justify-between w-full h-full
|
||||
hover:bg-gray-100 cursor-pointer ${open && 'bg-gray-100'}
|
||||
text-[13px] text-gray-700 px-3
|
||||
`,
|
||||
)}>
|
||||
<Menu.Button className={cn('group px-3 flex items-center justify-between w-full h-full system-sm-regular text-text-secondary cursor-pointer hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
|
||||
{RoleMap[member.role] || RoleMap.normal}
|
||||
<ChevronDownIcon className={`w-4 h-4 group-hover:block ${open ? 'block' : 'hidden'}`} />
|
||||
<ChevronDownIcon className={cn('w-4 h-4 group-hover:block', open ? 'block' : 'hidden')} />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
@@ -110,27 +90,21 @@ const Operation = ({
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={cn(
|
||||
`
|
||||
absolute right-0 top-[52px] z-10 bg-white border-[0.5px] border-gray-200
|
||||
divide-y divide-gray-100 origin-top-right rounded-lg
|
||||
`,
|
||||
s.popup,
|
||||
)}
|
||||
className={cn('absolute origin-top-right right-0 top-[52px] z-10 bg-components-panel-bg-blur backdrop-blur-sm rounded-xl border-[0.5px] border-components-panel-border shadow-lg')}
|
||||
>
|
||||
<div className="px-1 py-1">
|
||||
<div className="p-1">
|
||||
{
|
||||
roleList.map(role => (
|
||||
<Menu.Item key={role}>
|
||||
<div className={itemClassName} onClick={() => handleUpdateMemberRole(role)}>
|
||||
<div className='flex px-3 py-2 cursor-pointer hover:bg-state-base-hover rounded-lg' onClick={() => handleUpdateMemberRole(role)}>
|
||||
{
|
||||
role === member.role
|
||||
? <CheckIcon className={itemIconClassName} />
|
||||
: <div className={itemIconClassName} />
|
||||
? <CheckIcon className='w-4 h-4 mt-[2px] mr-1 text-text-accent' />
|
||||
: <div className='w-4 h-4 mt-[2px] mr-1 text-text-accent' />
|
||||
}
|
||||
<div>
|
||||
<div className={itemTitleClassName}>{t(`common.members.${toHump(role)}`)}</div>
|
||||
<div className={itemDescClassName}>{t(`common.members.${toHump(role)}Tip`)}</div>
|
||||
<div className='system-sm-semibold text-text-secondary whitespace-nowrap'>{t(`common.members.${toHump(role)}`)}</div>
|
||||
<div className='system-xs-regular text-text-tertiary whitespace-nowrap'>{t(`common.members.${toHump(role)}Tip`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
@@ -138,12 +112,12 @@ const Operation = ({
|
||||
}
|
||||
</div>
|
||||
<Menu.Item>
|
||||
<div className='px-1 py-1'>
|
||||
<div className={itemClassName} onClick={handleDeleteMemberOrCancelInvitation}>
|
||||
<div className={itemIconClassName} />
|
||||
<div className='p-1 border-t border-divider-subtle'>
|
||||
<div className='flex px-3 py-2 cursor-pointer hover:bg-state-base-hover rounded-lg' onClick={handleDeleteMemberOrCancelInvitation}>
|
||||
<div className='w-4 h-4 mt-[2px] mr-1 text-text-accent' />
|
||||
<div>
|
||||
<div className={itemTitleClassName}>{t('common.members.removeFromTeam')}</div>
|
||||
<div className={itemDescClassName}>{t('common.members.removeFromTeamTip')}</div>
|
||||
<div className='system-sm-semibold text-text-secondary whitespace-nowrap'>{t('common.members.removeFromTeam')}</div>
|
||||
<div className='system-xs-regular text-text-tertiary whitespace-nowrap'>{t('common.members.removeFromTeamTip')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user