mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 19:06:51 +08:00
Revert "feat: knowledge admin role" (#6018)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useMount } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import cn from 'classnames'
|
||||
@@ -11,22 +10,19 @@ import Button from '@/app/components/base/button'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import PermissionSelector from '@/app/components/datasets/settings/permission-selector'
|
||||
import PermissionsRadio from '@/app/components/datasets/settings/permissions-radio'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import {
|
||||
useModelList,
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { fetchMembers } from '@/service/common'
|
||||
import type { Member } from '@/models/common'
|
||||
|
||||
type SettingsModalProps = {
|
||||
currentDataset: DataSet
|
||||
@@ -59,11 +55,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const [localeCurrentDataset, setLocaleCurrentDataset] = useState({ ...currentDataset })
|
||||
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>(currentDataset.partial_member_list || [])
|
||||
const [memberList, setMemberList] = useState<Member[]>([])
|
||||
|
||||
const [indexMethod, setIndexMethod] = useState(currentDataset.indexing_technique)
|
||||
const [retrievalConfig, setRetrievalConfig] = useState(localeCurrentDataset?.retrieval_model_dict as RetrievalConfig)
|
||||
|
||||
@@ -100,7 +92,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
try {
|
||||
setLoading(true)
|
||||
const { id, name, description, permission } = localeCurrentDataset
|
||||
const requestParams = {
|
||||
await updateDatasetSetting({
|
||||
datasetId: id,
|
||||
body: {
|
||||
name,
|
||||
@@ -114,16 +106,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
embedding_model: localeCurrentDataset.embedding_model,
|
||||
embedding_model_provider: localeCurrentDataset.embedding_model_provider,
|
||||
},
|
||||
} as any
|
||||
if (permission === 'partial_members') {
|
||||
requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
|
||||
return {
|
||||
user_id: id,
|
||||
role: memberList.find(member => member.id === id)?.role,
|
||||
}
|
||||
})
|
||||
}
|
||||
await updateDatasetSetting(requestParams)
|
||||
})
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
onSave({
|
||||
...localeCurrentDataset,
|
||||
@@ -139,18 +122,6 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const getMembers = async () => {
|
||||
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
||||
if (!accounts)
|
||||
setMemberList([])
|
||||
else
|
||||
setMemberList(accounts)
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
getMembers()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className='overflow-hidden w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
|
||||
@@ -209,13 +180,11 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
<div>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<PermissionSelector
|
||||
disabled={!localeCurrentDataset?.embedding_available || isCurrentWorkspaceDatasetOperator}
|
||||
permission={localeCurrentDataset.permission}
|
||||
value={selectedMemberIDs}
|
||||
<PermissionsRadio
|
||||
disable={!localeCurrentDataset?.embedding_available}
|
||||
value={localeCurrentDataset.permission}
|
||||
onChange={v => handleValueChange('permission', v!)}
|
||||
onMemberSelect={setSelectedMemberIDs}
|
||||
memberList={memberList}
|
||||
itemClassName='sm:!w-[280px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="users-plus">
|
||||
<g id="Solid">
|
||||
<path d="M20 15C20 14.4477 19.5523 14 19 14C18.4477 14 18 14.4477 18 15V17H16C15.4477 17 15 17.4477 15 18C15 18.5523 15.4477 19 16 19H18V21C18 21.5523 18.4477 22 19 22C19.5523 22 20 21.5523 20 21V19H22C22.5523 19 23 18.5523 23 18C23 17.4477 22.5523 17 22 17H20V15Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.181 14.1635C12.4632 14.3073 12.6927 14.5368 12.8365 14.819C12.9896 15.1194 13.0001 15.4476 13 15.7769C13 15.7847 13 15.7924 13 15.8C13 17.2744 12.9995 18.7488 13 20.2231C13.0001 20.3422 13.0001 20.4845 12.9899 20.6098C12.978 20.755 12.9476 20.963 12.8365 21.181C12.6927 21.4632 12.4632 21.6927 12.181 21.8365C11.963 21.9476 11.7551 21.978 11.6098 21.9899C11.4845 22.0001 11.3423 22.0001 11.2231 22C8.4077 21.999 5.59226 21.999 2.77682 22C2.65755 22.0001 2.51498 22.0001 2.38936 21.9898C2.24364 21.9778 2.03523 21.9472 1.81695 21.8356C1.53435 21.6911 1.30428 21.46 1.16109 21.1767C1.05079 20.9585 1.02087 20.7506 1.0095 20.6046C0.999737 20.4791 1.00044 20.3369 1.00103 20.2185C1.00619 19.1792 0.975203 18.0653 1.38061 17.0866C1.88808 15.8614 2.86145 14.8881 4.08659 14.3806C4.59629 14.1695 5.13457 14.0819 5.74331 14.0404C6.33532 14 7.06273 14 7.96449 14C9.05071 14 10.1369 14.0004 11.2231 14C11.5524 13.9999 11.8806 14.0104 12.181 14.1635Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5731 2.91554C14.7803 2.40361 15.3633 2.1566 15.8752 2.36382C17.7058 3.10481 19 4.90006 19 7C19 9.09994 17.7058 10.8952 15.8752 11.6362C15.3633 11.8434 14.7803 11.5964 14.5731 11.0845C14.3658 10.5725 14.6129 9.98953 15.1248 9.7823C16.2261 9.33652 17 8.25744 17 7C17 5.74256 16.2261 4.66348 15.1248 4.2177C14.6129 4.01047 14.3658 3.42748 14.5731 2.91554Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.50001 7C4.50001 4.23858 6.73858 2 9.50001 2C12.2614 2 14.5 4.23858 14.5 7C14.5 9.76142 12.2614 12 9.50001 12C6.73858 12 4.50001 9.76142 4.50001 7Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "users-plus"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Solid"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M20 15C20 14.4477 19.5523 14 19 14C18.4477 14 18 14.4477 18 15V17H16C15.4477 17 15 17.4477 15 18C15 18.5523 15.4477 19 16 19H18V21C18 21.5523 18.4477 22 19 22C19.5523 22 20 21.5523 20 21V19H22C22.5523 19 23 18.5523 23 18C23 17.4477 22.5523 17 22 17H20V15Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M12.181 14.1635C12.4632 14.3073 12.6927 14.5368 12.8365 14.819C12.9896 15.1194 13.0001 15.4476 13 15.7769C13 15.7847 13 15.7924 13 15.8C13 17.2744 12.9995 18.7488 13 20.2231C13.0001 20.3422 13.0001 20.4845 12.9899 20.6098C12.978 20.755 12.9476 20.963 12.8365 21.181C12.6927 21.4632 12.4632 21.6927 12.181 21.8365C11.963 21.9476 11.7551 21.978 11.6098 21.9899C11.4845 22.0001 11.3423 22.0001 11.2231 22C8.4077 21.999 5.59226 21.999 2.77682 22C2.65755 22.0001 2.51498 22.0001 2.38936 21.9898C2.24364 21.9778 2.03523 21.9472 1.81695 21.8356C1.53435 21.6911 1.30428 21.46 1.16109 21.1767C1.05079 20.9585 1.02087 20.7506 1.0095 20.6046C0.999737 20.4791 1.00044 20.3369 1.00103 20.2185C1.00619 19.1792 0.975203 18.0653 1.38061 17.0866C1.88808 15.8614 2.86145 14.8881 4.08659 14.3806C4.59629 14.1695 5.13457 14.0819 5.74331 14.0404C6.33532 14 7.06273 14 7.96449 14C9.05071 14 10.1369 14.0004 11.2231 14C11.5524 13.9999 11.8806 14.0104 12.181 14.1635Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M14.5731 2.91554C14.7803 2.40361 15.3633 2.1566 15.8752 2.36382C17.7058 3.10481 19 4.90006 19 7C19 9.09994 17.7058 10.8952 15.8752 11.6362C15.3633 11.8434 14.7803 11.5964 14.5731 11.0845C14.3658 10.5725 14.6129 9.98953 15.1248 9.7823C16.2261 9.33652 17 8.25744 17 7C17 5.74256 16.2261 4.66348 15.1248 4.2177C14.6129 4.01047 14.3658 3.42748 14.5731 2.91554Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M4.50001 7C4.50001 4.23858 6.73858 2 9.50001 2C12.2614 2 14.5 4.23858 14.5 7C14.5 9.76142 12.2614 12 9.50001 12C6.73858 12 4.50001 9.76142 4.50001 7Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "UsersPlus"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './UsersPlus.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'UsersPlus'
|
||||
|
||||
export default Icon
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as User01 } from './User01'
|
||||
export { default as UserEdit02 } from './UserEdit02'
|
||||
export { default as Users01 } from './Users01'
|
||||
export { default as UsersPlus } from './UsersPlus'
|
||||
|
||||
@@ -37,7 +37,7 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
type="text"
|
||||
name="query"
|
||||
className={cn(
|
||||
'grow block h-[18px] bg-gray-200 border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600',
|
||||
'grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600',
|
||||
focus && '!bg-white hover:bg-white group-hover:bg-white placeholder:!text-gray-400',
|
||||
!focus && value && 'hover:!bg-gray-200 group-hover:!bg-gray-200',
|
||||
white && '!bg-white hover:!bg-white group-hover:!bg-white placeholder:!text-gray-400',
|
||||
|
||||
@@ -66,7 +66,6 @@ export type CurrentPlanInfoBackend = {
|
||||
docs_processing: DocumentProcessingPriority
|
||||
can_replace_logo: boolean
|
||||
model_load_balancing_enabled: boolean
|
||||
dataset_operator_enabled: boolean
|
||||
}
|
||||
|
||||
export type SubscriptionItem = {
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useMount } from 'ahooks'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Dispatch } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { BookOpenIcon } from '@heroicons/react/24/outline'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import { unstable_serialize } from 'swr/infinite'
|
||||
import PermissionSelector from '../permission-selector'
|
||||
import PermissionsRadio from '../permissions-radio'
|
||||
import IndexMethodRadio from '../index-method-radio'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
import type { DataSetListResponse } from '@/models/datasets'
|
||||
import type { DataSet, DataSetListResponse } from '@/models/datasets'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { type RetrievalConfig } from '@/types/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import {
|
||||
useModelList,
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { fetchMembers } from '@/service/common'
|
||||
import type { Member } from '@/models/common'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
const rowClass = `
|
||||
flex justify-between py-4 flex-wrap gap-y-2
|
||||
@@ -38,6 +36,11 @@ const labelClass = `
|
||||
const inputClass = `
|
||||
w-full max-w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none
|
||||
`
|
||||
const useInitialValue: <T>(depend: T, dispatch: Dispatch<T>) => void = (depend, dispatch) => {
|
||||
useEffect(() => {
|
||||
dispatch(depend)
|
||||
}, [depend])
|
||||
}
|
||||
|
||||
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
|
||||
if (!pageIndex || previousPageData.has_more)
|
||||
@@ -49,14 +52,12 @@ const Form = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { mutate } = useSWRConfig()
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { dataset: currentDataset, mutateDatasetRes: mutateDatasets } = useContext(DatasetDetailContext)
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [name, setName] = useState(currentDataset?.name ?? '')
|
||||
const [description, setDescription] = useState(currentDataset?.description ?? '')
|
||||
const [permission, setPermission] = useState(currentDataset?.permission)
|
||||
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>(currentDataset?.partial_member_list || [])
|
||||
const [memberList, setMemberList] = useState<Member[]>([])
|
||||
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
|
||||
const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig)
|
||||
const [embeddingModel, setEmbeddingModel] = useState<DefaultModel>(
|
||||
@@ -77,18 +78,6 @@ const Form = () => {
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
|
||||
const getMembers = async () => {
|
||||
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
||||
if (!accounts)
|
||||
setMemberList([])
|
||||
else
|
||||
setMemberList(accounts)
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
getMembers()
|
||||
})
|
||||
|
||||
const handleSave = async () => {
|
||||
if (loading)
|
||||
return
|
||||
@@ -115,7 +104,7 @@ const Form = () => {
|
||||
})
|
||||
try {
|
||||
setLoading(true)
|
||||
const requestParams = {
|
||||
await updateDatasetSetting({
|
||||
datasetId: currentDataset!.id,
|
||||
body: {
|
||||
name,
|
||||
@@ -129,16 +118,7 @@ const Form = () => {
|
||||
embedding_model: embeddingModel.model,
|
||||
embedding_model_provider: embeddingModel.provider,
|
||||
},
|
||||
} as any
|
||||
if (permission === 'partial_members') {
|
||||
requestParams.body.partial_member_list = selectedMemberIDs.map((id) => {
|
||||
return {
|
||||
user_id: id,
|
||||
role: memberList.find(member => member.id === id)?.role,
|
||||
}
|
||||
})
|
||||
}
|
||||
await updateDatasetSetting(requestParams)
|
||||
})
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
if (mutateDatasets) {
|
||||
await mutateDatasets()
|
||||
@@ -153,6 +133,11 @@ const Form = () => {
|
||||
}
|
||||
}
|
||||
|
||||
useInitialValue<string>(currentDataset?.name ?? '', setName)
|
||||
useInitialValue<string>(currentDataset?.description ?? '', setDescription)
|
||||
useInitialValue<DataSet['permission'] | undefined>(currentDataset?.permission, setPermission)
|
||||
useInitialValue<DataSet['indexing_technique'] | undefined>(currentDataset?.indexing_technique, setIndexMethod)
|
||||
|
||||
return (
|
||||
<div className='w-full sm:w-[800px] p-4 sm:px-16 sm:py-6'>
|
||||
<div className={rowClass}>
|
||||
@@ -189,13 +174,10 @@ const Form = () => {
|
||||
<div>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<PermissionSelector
|
||||
disabled={!currentDataset?.embedding_available || isCurrentWorkspaceDatasetOperator}
|
||||
permission={permission}
|
||||
value={selectedMemberIDs}
|
||||
<PermissionsRadio
|
||||
disable={!currentDataset?.embedding_available}
|
||||
value={permission}
|
||||
onChange={v => setPermission(v)}
|
||||
onMemberSelect={setSelectedMemberIDs}
|
||||
memberList={memberList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import { Check } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users'
|
||||
import type { DatasetPermission } from '@/models/datasets'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import type { Member } from '@/models/common'
|
||||
export type RoleSelectorProps = {
|
||||
disabled?: boolean
|
||||
permission?: DatasetPermission
|
||||
value: string[]
|
||||
memberList: Member[]
|
||||
onChange: (permission?: DatasetPermission) => void
|
||||
onMemberSelect: (v: string[]) => void
|
||||
}
|
||||
|
||||
const PermissionSelector = ({ disabled, permission, value, memberList, onChange, onMemberSelect }: RoleSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [keywords, setKeywords] = useState('')
|
||||
const [searchKeywords, setSearchKeywords] = useState('')
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchKeywords(keywords)
|
||||
}, { wait: 500 })
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
handleSearch()
|
||||
}
|
||||
const selectMember = (member: Member) => {
|
||||
if (value.includes(member.id))
|
||||
onMemberSelect(value.filter(v => v !== member.id))
|
||||
else
|
||||
onMemberSelect([...value, member.id])
|
||||
}
|
||||
|
||||
const selectedMembers = useMemo(() => {
|
||||
return [
|
||||
userProfile,
|
||||
...memberList.filter(member => member.id !== userProfile.id).filter(member => value.includes(member.id)),
|
||||
].map(member => member.name).join(', ')
|
||||
}, [userProfile, value, memberList])
|
||||
const showMe = useMemo(() => {
|
||||
return userProfile.name.includes(searchKeywords) || userProfile.email.includes(searchKeywords)
|
||||
}, [searchKeywords, userProfile])
|
||||
const filteredMemberList = useMemo(() => {
|
||||
return memberList.filter(member => (member.name.includes(searchKeywords) || member.email.includes(searchKeywords)) && member.id !== userProfile.id && ['owner', 'admin', 'editor', 'dataset_operator'].includes(member.role))
|
||||
}, [memberList, searchKeywords, userProfile])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={4}
|
||||
>
|
||||
<div className='relative'>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => !disabled && setOpen(v => !v)}
|
||||
className='block'
|
||||
>
|
||||
{permission === 'only_me' && (
|
||||
<div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200', disabled && 'hover:!bg-gray-100 !cursor-default')}>
|
||||
<Avatar name={userProfile.name} className='shrink-0 mr-2' size={24} />
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
|
||||
{!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
|
||||
</div>
|
||||
)}
|
||||
{permission === 'all_team_members' && (
|
||||
<div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
|
||||
<Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
|
||||
</div>
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsAllMember')}</div>
|
||||
{!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
|
||||
</div>
|
||||
)}
|
||||
{permission === 'partial_members' && (
|
||||
<div className={cn('flex items-center px-3 py-[6px] rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
|
||||
<Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
|
||||
</div>
|
||||
<div title={selectedMembers} className='grow mr-2 text-gray-900 text-sm leading-5 truncate'>{selectedMembers}</div>
|
||||
{!disabled && <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' />}
|
||||
</div>
|
||||
)}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[480px] bg-white rounded-lg border-[0.5px] bg-gray-200 shadow-lg'>
|
||||
<div className='p-1'>
|
||||
<div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
onChange('only_me')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Avatar name={userProfile.name} className='shrink-0 mr-2' size={24} />
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsOnlyMe')}</div>
|
||||
{permission === 'only_me' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
onChange('all_team_members')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#EEF4FF]'>
|
||||
<Users01 className='w-3.5 h-3.5 text-[#444CE7]' />
|
||||
</div>
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsAllMember')}</div>
|
||||
{permission === 'all_team_members' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='pl-3 pr-2 py-1 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => {
|
||||
onChange('partial_members')
|
||||
onMemberSelect([userProfile.id])
|
||||
}}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className={cn('mr-2 flex items-center justify-center w-6 h-6 rounded-lg bg-[#FFF6ED]', permission === 'partial_members' && '!bg-[#EEF4FF]')}>
|
||||
<UsersPlus className={cn('w-3.5 h-3.5 text-[#FB6514]', permission === 'partial_members' && '!text-[#444CE7]')} />
|
||||
</div>
|
||||
<div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('datasetSettings.form.permissionsInvitedMembers')}</div>
|
||||
{permission === 'partial_members' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{permission === 'partial_members' && (
|
||||
<div className='max-h-[360px] border-t-[1px] border-gray-100 p-1 overflow-y-auto'>
|
||||
<div className='sticky left-0 top-0 p-2 pb-1 bg-white'>
|
||||
<SearchInput white value={keywords} onChange={handleKeywordsChange} />
|
||||
</div>
|
||||
{showMe && (
|
||||
<div className='pl-3 pr-[10px] py-1 flex gap-2 items-center rounded-lg'>
|
||||
<Avatar name={userProfile.name} className='shrink-0' size={24} />
|
||||
<div className='grow'>
|
||||
<div className='text-[13px] text-gray-700 font-medium leading-[18px] truncate'>
|
||||
{userProfile.name}
|
||||
<span className='text-xs text-gray-500 font-normal'>{t('datasetSettings.form.me')}</span>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 leading-[18px] truncate'>{userProfile.email}</div>
|
||||
</div>
|
||||
<Check className='shrink-0 w-4 h-4 text-primary-600 opacity-30' />
|
||||
</div>
|
||||
)}
|
||||
{filteredMemberList.map(member => (
|
||||
<div key={member.id} className='pl-3 pr-[10px] py-1 flex gap-2 items-center rounded-lg hover:bg-gray-100 cursor-pointer' onClick={() => selectMember(member)}>
|
||||
<Avatar name={member.name} className='shrink-0' size={24} />
|
||||
<div className='grow'>
|
||||
<div className='text-[13px] text-gray-700 font-medium leading-[18px] truncate'>{member.name}</div>
|
||||
<div className='text-xs text-gray-500 leading-[18px] truncate'>{member.email}</div>
|
||||
</div>
|
||||
{value.includes(member.id) && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</div>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PermissionSelector
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" rx="8" fill="#EEF4FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4043 14.2586C15.5696 13.9296 15.9703 13.7969 16.2993 13.9622C17.3889 14.5095 18.31 15.381 18.9766 16.4548C19.0776 16.6174 19.2246 16.8347 19.2702 17.1291C19.3191 17.4443 19.2335 17.7457 19.1061 17.9749C18.9786 18.2041 18.7676 18.4357 18.4741 18.5605C18.1949 18.6791 17.8913 18.6666 17.6667 18.6666C17.2985 18.6666 17.0001 18.3682 17.0001 18C17.0001 17.6318 17.2985 17.3333 17.6667 17.3333C17.8102 17.3333 17.8856 17.3329 17.9395 17.3292L17.9409 17.3268C17.9536 17.3038 17.8568 17.1789 17.8438 17.158C17.2956 16.2749 16.5524 15.5814 15.7008 15.1536C15.3718 14.9884 15.2391 14.5877 15.4043 14.2586Z" fill="#444CE7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0697 6.01513C14.2336 5.68541 14.6337 5.55095 14.9634 5.71481C16.1691 6.314 17.0001 7.55934 17.0001 8.99998C17.0001 10.4406 16.1691 11.686 14.9634 12.2851C14.6337 12.449 14.2336 12.3145 14.0697 11.9848C13.9059 11.6551 14.0403 11.255 14.37 11.0911C15.14 10.7085 15.6667 9.91515 15.6667 8.99998C15.6667 8.08481 15.14 7.29144 14.37 6.90883C14.0403 6.74497 13.9059 6.34485 14.0697 6.01513Z" fill="#444CE7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66673 8.99998C6.66673 6.97494 8.30835 5.33331 10.3334 5.33331C12.3584 5.33331 14.0001 6.97494 14.0001 8.99998C14.0001 11.025 12.3584 12.6666 10.3334 12.6666C8.30835 12.6666 6.66673 11.025 6.66673 8.99998Z" fill="#444CE7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3334 13.3333C12.4642 13.3333 14.3691 14.5361 15.5315 16.2801C15.6339 16.4337 15.7431 16.5976 15.8194 16.7533C15.9113 16.9407 15.9773 17.156 15.9619 17.4132C15.9496 17.6183 15.8816 17.8086 15.8007 17.9597C15.7198 18.1107 15.5991 18.2728 15.4352 18.3968C15.2157 18.5628 14.9791 18.621 14.77 18.6453C14.5858 18.6667 14.3677 18.6667 14.148 18.6667C11.6059 18.6662 9.06185 18.6662 6.51877 18.6667C6.29908 18.6667 6.08098 18.6667 5.89682 18.6453C5.68769 18.621 5.4511 18.5628 5.23155 18.3968C5.06767 18.2728 4.94702 18.1107 4.86612 17.9597C4.78523 17.8086 4.71719 17.6183 4.70488 17.4132C4.68945 17.156 4.75545 16.9407 4.84734 16.7533C4.92369 16.5976 5.0329 16.4337 5.13531 16.2801C6.2977 14.5361 8.20257 13.3333 10.3334 13.3333Z" fill="#444CE7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,46 @@
|
||||
.user-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url(./assets/user.svg) center center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.wrapper .item:hover {
|
||||
background-color: #ffffff;
|
||||
border-color: #B2CCFF;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.wrapper .item-active {
|
||||
background-color: #ffffff;
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.wrapper .item-active .radio {
|
||||
border-width: 5px;
|
||||
border-color: #155EEF;
|
||||
}
|
||||
|
||||
.wrapper .item-active:hover {
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.wrapper .item.disable {
|
||||
@apply opacity-60;
|
||||
}
|
||||
.wrapper .item-active.disable {
|
||||
@apply opacity-60;
|
||||
}
|
||||
.wrapper .item.disable:hover {
|
||||
@apply bg-gray-25 border border-gray-100 shadow-none cursor-default opacity-60;
|
||||
}
|
||||
.wrapper .item-active.disable:hover {
|
||||
@apply cursor-default opacity-60;
|
||||
border-width: 1.5px;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
import s from './index.module.css'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
|
||||
const itemClass = `
|
||||
flex items-center w-full sm:w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
|
||||
`
|
||||
const radioClass = `
|
||||
w-4 h-4 border-[2px] border-gray-200 rounded-full
|
||||
`
|
||||
type IPermissionsRadioProps = {
|
||||
value?: DataSet['permission']
|
||||
onChange: (v?: DataSet['permission']) => void
|
||||
itemClassName?: string
|
||||
disable?: boolean
|
||||
}
|
||||
|
||||
const PermissionsRadio = ({
|
||||
value,
|
||||
onChange,
|
||||
itemClassName,
|
||||
disable,
|
||||
}: IPermissionsRadioProps) => {
|
||||
const { t } = useTranslation()
|
||||
const options = [
|
||||
{
|
||||
key: 'only_me',
|
||||
text: t('datasetSettings.form.permissionsOnlyMe'),
|
||||
},
|
||||
{
|
||||
key: 'all_team_members',
|
||||
text: t('datasetSettings.form.permissionsAllMember'),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={classNames(s.wrapper, 'flex justify-between w-full flex-wrap gap-y-2')}>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.key}
|
||||
className={classNames(
|
||||
itemClass,
|
||||
itemClassName,
|
||||
s.item,
|
||||
option.key === value && s['item-active'],
|
||||
disable && s.disable,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disable)
|
||||
onChange(option.key as DataSet['permission'])
|
||||
}}
|
||||
>
|
||||
<div className={classNames(s['user-icon'], 'mr-3')} />
|
||||
<div className='grow text-sm text-gray-900'>{option.text}</div>
|
||||
<div className={classNames(radioClass, s.radio)} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PermissionsRadio
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ExploreContext from '@/context/explore-context'
|
||||
import Sidebar from '@/app/components/explore/sidebar'
|
||||
@@ -17,9 +16,8 @@ const Explore: FC<IExploreProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
|
||||
const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { userProfile } = useAppContext()
|
||||
const [hasEditPermission, setHasEditPermission] = useState(false)
|
||||
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
|
||||
|
||||
@@ -34,11 +32,6 @@ const Explore: FC<IExploreProps> = ({
|
||||
})()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return router.replace('/datasets')
|
||||
}, [isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
return (
|
||||
<div className='flex h-full bg-gray-100 border-t border-gray-200 overflow-hidden'>
|
||||
<ExploreContext.Provider
|
||||
|
||||
@@ -35,7 +35,6 @@ import CustomPage from '@/app/components/custom/custom-page'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
const iconClassName = `
|
||||
w-4 h-4 ml-3 mr-2
|
||||
@@ -65,11 +64,8 @@ export default function AccountSetting({
|
||||
const [activeMenu, setActiveMenu] = useState(activeTab)
|
||||
const { t } = useTranslation()
|
||||
const { enableBilling, enableReplaceWebAppLogo } = useProviderContext()
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
|
||||
const workplaceGroupItems = (() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return []
|
||||
return [
|
||||
{
|
||||
key: 'provider',
|
||||
@@ -176,9 +172,7 @@ export default function AccountSetting({
|
||||
{
|
||||
menuItems.map(menuItem => (
|
||||
<div key={menuItem.key} className='mb-4'>
|
||||
{!isCurrentWorkspaceDatasetOperator && (
|
||||
<div className='px-2 mb-[6px] text-[10px] sm:text-xs font-medium text-gray-500'>{menuItem.name}</div>
|
||||
)}
|
||||
<div className='px-2 mb-[6px] text-[10px] sm:text-xs font-medium text-gray-500'>{menuItem.name}</div>
|
||||
<div>
|
||||
{
|
||||
menuItem.items.map(item => (
|
||||
|
||||
@@ -29,7 +29,6 @@ const MembersPage = () => {
|
||||
owner: t('common.members.owner'),
|
||||
admin: t('common.members.admin'),
|
||||
editor: t('common.members.editor'),
|
||||
dataset_operator: t('common.members.datasetOperator'),
|
||||
normal: t('common.members.normal'),
|
||||
}
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Fragment, useCallback, useMemo, useState } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReactMultiEmail } from 'react-multi-email'
|
||||
import { Listbox, Transition } from '@headlessui/react'
|
||||
import { CheckIcon } from '@heroicons/react/20/solid'
|
||||
import cn from 'classnames'
|
||||
import s from './index.module.css'
|
||||
import RoleSelector from './role-selector'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { inviteMember } from '@/service/common'
|
||||
@@ -30,14 +31,29 @@ const InviteModal = ({
|
||||
const { notify } = useContext(ToastContext)
|
||||
|
||||
const { locale } = useContext(I18n)
|
||||
const [role, setRole] = useState<string>('normal')
|
||||
|
||||
const InvitingRoles = useMemo(() => [
|
||||
{
|
||||
name: 'normal',
|
||||
description: t('common.members.normalTip'),
|
||||
},
|
||||
{
|
||||
name: 'editor',
|
||||
description: t('common.members.editorTip'),
|
||||
},
|
||||
{
|
||||
name: 'admin',
|
||||
description: t('common.members.adminTip'),
|
||||
},
|
||||
], [t])
|
||||
const [role, setRole] = useState(InvitingRoles[0])
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) {
|
||||
try {
|
||||
const { result, invitation_results } = await inviteMember({
|
||||
url: '/workspaces/current/members/invite-email',
|
||||
body: { emails, role, language: locale },
|
||||
body: { emails, role: role.name, language: locale },
|
||||
})
|
||||
|
||||
if (result === 'success') {
|
||||
@@ -83,9 +99,53 @@ const InviteModal = ({
|
||||
placeholder={t('common.members.emailPlaceholder') || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='mb-6'>
|
||||
<RoleSelector value={role} onChange={setRole} />
|
||||
</div>
|
||||
<Listbox value={role} onChange={setRole}>
|
||||
<div className="relative pb-6">
|
||||
<Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-gray-100 outline-none border-none appearance-none text-sm text-gray-900 rounded-lg">
|
||||
<span className="block truncate capitalize">{t('common.members.invitedAsRole', { role: t(`common.members.${role.name}`) })}</span>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-200"
|
||||
leaveFrom="opacity-200"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute w-full py-1 my-2 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{InvitingRoles.map(role =>
|
||||
<Listbox.Option
|
||||
key={role.name}
|
||||
className={({ active }) =>
|
||||
`${active ? ' bg-gray-50 rounded-xl' : ' bg-transparent'}
|
||||
cursor-default select-none relative py-2 px-4 mx-2 flex flex-col`
|
||||
}
|
||||
value={role}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className='flex flex-row'>
|
||||
<span
|
||||
className={cn(
|
||||
'text-indigo-600 mr-2',
|
||||
'flex items-center',
|
||||
)}
|
||||
>
|
||||
{selected && (<CheckIcon className="h-5 w-5" aria-hidden="true" />)}
|
||||
</span>
|
||||
<div className=' flex flex-col flex-grow'>
|
||||
<span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block truncate`}>
|
||||
{t(`common.members.${role.name}`)}
|
||||
</span>
|
||||
<span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block text-gray-500`}>
|
||||
{role.description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>,
|
||||
)}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
<Button
|
||||
tabIndex={0}
|
||||
className='w-full'
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import React, { useState } from 'react'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Check } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
export type RoleSelectorProps = {
|
||||
value: string
|
||||
onChange: (role: string) => void
|
||||
}
|
||||
|
||||
const RoleSelector = ({ value, onChange }: RoleSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const { datasetOperatorEnabled } = useProviderContext()
|
||||
|
||||
const toHump = (name: string) => name.replace(/_(\w)/g, (all, letter) => letter.toUpperCase())
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={4}
|
||||
>
|
||||
<div className='relative'>
|
||||
<PortalToFollowElemTrigger
|
||||
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>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[336px] bg-white rounded-lg border-[0.5px] bg-gray-200 shadow-lg'>
|
||||
<div className='p-1'>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 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>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 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>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 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>
|
||||
</div>
|
||||
{datasetOperatorEnabled && (
|
||||
<div className='p-2 rounded-lg hover:bg-gray-50 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</div>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default RoleSelector
|
||||
@@ -1,12 +1,11 @@
|
||||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Fragment, useMemo } from 'react'
|
||||
import { Fragment } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import cn from 'classnames'
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/24/outline'
|
||||
import s from './index.module.css'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import type { Member } from '@/models/common'
|
||||
import { deleteMemberOrCancelInvitation, updateMemberRole } from '@/service/common'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
@@ -34,22 +33,13 @@ const Operation = ({
|
||||
onOperate,
|
||||
}: IOperationProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { datasetOperatorEnabled } = useProviderContext()
|
||||
const RoleMap = {
|
||||
owner: t('common.members.owner'),
|
||||
admin: t('common.members.admin'),
|
||||
editor: t('common.members.editor'),
|
||||
normal: t('common.members.normal'),
|
||||
dataset_operator: t('common.members.datasetOperator'),
|
||||
}
|
||||
const roleList = useMemo(() => {
|
||||
return [
|
||||
...['admin', 'editor', 'normal'],
|
||||
...(datasetOperatorEnabled ? ['dataset_operator'] : []),
|
||||
]
|
||||
}, [datasetOperatorEnabled])
|
||||
const { notify } = useContext(ToastContext)
|
||||
const toHump = (name: string) => name.replace(/_(\w)/g, (all, letter) => letter.toUpperCase())
|
||||
const handleDeleteMemberOrCancelInvitation = async () => {
|
||||
try {
|
||||
await deleteMemberOrCancelInvitation({ url: `/workspaces/current/members/${member.id}` })
|
||||
@@ -109,7 +99,7 @@ const Operation = ({
|
||||
>
|
||||
<div className="px-1 py-1">
|
||||
{
|
||||
roleList.map(role => (
|
||||
['admin', 'editor', 'normal'].map(role => (
|
||||
<Menu.Item key={role}>
|
||||
<div className={itemClassName} onClick={() => handleUpdateMemberRole(role)}>
|
||||
{
|
||||
@@ -118,8 +108,8 @@ const Operation = ({
|
||||
: <div className={itemIconClassName} />
|
||||
}
|
||||
<div>
|
||||
<div className={itemTitleClassName}>{t(`common.members.${toHump(role)}`)}</div>
|
||||
<div className={itemDescClassName}>{t(`common.members.${toHump(role)}Tip`)}</div>
|
||||
<div className={itemTitleClassName}>{t(`common.members.${role}`)}</div>
|
||||
<div className={itemDescClassName}>{t(`common.members.${role}Tip`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
|
||||
@@ -26,7 +26,7 @@ const navClassName = `
|
||||
`
|
||||
|
||||
const Header = () => {
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const media = useBreakpoints()
|
||||
@@ -72,10 +72,10 @@ const Header = () => {
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className='flex items-center'>
|
||||
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
|
||||
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceEditor && <DatasetNav />}
|
||||
<ToolsNav className={navClassName} />
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center flex-shrink-0'>
|
||||
@@ -91,10 +91,10 @@ const Header = () => {
|
||||
</div>
|
||||
{(isMobile && isShowNavMenu) && (
|
||||
<div className='w-full flex flex-col p-2 gap-y-1'>
|
||||
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
|
||||
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceEditor && <DatasetNav />}
|
||||
<ToolsNav className={navClassName} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -113,7 +113,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{!isApp && isCurrentWorkspaceEditor && (
|
||||
{!isApp && (
|
||||
<Menu.Button className='p-1 w-full'>
|
||||
<div onClick={() => onCreate('')} className={cn(
|
||||
'flex items-center gap-2 px-3 py-[6px] rounded-lg cursor-pointer hover:bg-gray-100',
|
||||
|
||||
Reference in New Issue
Block a user