mirror of
https://github.com/langgenius/dify.git
synced 2026-08-02 10:46:38 +08:00
232 lines
9.6 KiB
TypeScript
232 lines
9.6 KiB
TypeScript
'use client'
|
|
import type { MemberInviteResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
|
import type { Role } from '@/models/access-control'
|
|
import type { Member } from '@/models/common'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
|
import { useAtomValue } from 'jotai'
|
|
import { useCallback, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
|
import { NUM_INFINITE } from '@/app/components/billing/config'
|
|
import { Plan } from '@/app/components/billing/type'
|
|
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
|
import { userProfileEmailAtom } from '@/context/account-state'
|
|
import { useLocale } from '@/context/i18n'
|
|
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
|
import { useProviderContext } from '@/context/provider-context'
|
|
import { currentWorkspaceAtom, isCurrentWorkspaceOwnerAtom } from '@/context/workspace-state'
|
|
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
|
import { getAccessControlTemplateLanguage, LanguagesSupported } from '@/i18n-config/language'
|
|
import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles'
|
|
import { useMembers } from '@/service/use-common'
|
|
import { hasPermission } from '@/utils/permission'
|
|
import EditWorkspaceModal from './edit-workspace-modal'
|
|
import InviteButton from './invite-button'
|
|
import { InviteModal } from './invite-modal'
|
|
import InvitedModal from './invited-modal'
|
|
import MemberDetailsModal from './member-details-modal'
|
|
import MemberRow from './member-row'
|
|
import TransferOwnershipModal from './transfer-ownership-modal'
|
|
|
|
const MembersPage = () => {
|
|
const { t } = useTranslation()
|
|
const locale = useLocale()
|
|
const language = getAccessControlTemplateLanguage(locale)
|
|
|
|
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
|
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
|
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
|
const { data, refetch } = useMembers(language)
|
|
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
|
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
|
const [invitationResults, setInvitationResults] = useState<
|
|
MemberInviteResponse['invitation_results'] | null
|
|
>(null)
|
|
const accounts = data?.accounts || []
|
|
const { plan, enableBilling, isAllowTransferWorkspace } = useProviderContext()
|
|
const isNotUnlimitedMemberPlan =
|
|
enableBilling && plan.type !== Plan.team && plan.type !== Plan.enterprise
|
|
const isMemberFull =
|
|
enableBilling && isNotUnlimitedMemberPlan && accounts.length >= plan.total.teamMembers
|
|
const [editWorkspaceModalVisible, setEditWorkspaceModalVisible] = useState(false)
|
|
const [showTransferOwnershipModal, setShowTransferOwnershipModal] = useState(false)
|
|
const [detailsMember, setDetailsMember] = useState<Member | null>(null)
|
|
|
|
const canManageMembers = hasPermission(workspacePermissionKeys, 'workspace.member.manage')
|
|
const roleColumnLabel = systemFeatures.rbac_enabled
|
|
? t(($) => $['members.roles'], { ns: 'common' })
|
|
: t(($) => $['members.role'], { ns: 'common' })
|
|
|
|
const handleOpenDetails = useCallback((member: Member) => {
|
|
setDetailsMember(member)
|
|
}, [])
|
|
|
|
const handleCloseDetails = useCallback(() => {
|
|
setDetailsMember(null)
|
|
}, [])
|
|
|
|
const { mutateAsync: updateRolesOfMember } = useUpdateRolesOfMember()
|
|
|
|
const handleAssignRolesSubmit = (roles: Role[]) => {
|
|
const roleIds = systemFeatures.rbac_enabled
|
|
? roles.map((role) => role.id)
|
|
: roles.slice(0, 1).map((role) => role.id)
|
|
|
|
updateRolesOfMember(
|
|
{
|
|
memberId: detailsMember!.id,
|
|
roleIds,
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
toast.success(t(($) => $['actionMsg.modifiedSuccessfully'], { ns: 'common' }))
|
|
refetch()
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
const handleTransferOwnership = useCallback(() => {
|
|
setShowTransferOwnershipModal(true)
|
|
}, [])
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col">
|
|
<div className="mb-6 flex items-center gap-3 rounded-xl border-t-[0.5px] border-l-[0.5px] border-divider-subtle bg-linear-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 py-2 pr-5 pl-2">
|
|
<WorkspaceAvatar name={currentWorkspace.name} size="2xl" />
|
|
<div className="grow">
|
|
<div className="flex items-center gap-1 system-md-semibold text-text-secondary">
|
|
<span>{currentWorkspace?.name}</span>
|
|
{isCurrentWorkspaceOwner && (
|
|
<span>
|
|
<Tooltip>
|
|
<TooltipTrigger
|
|
render={
|
|
<button
|
|
type="button"
|
|
aria-label={t(($) => $['account.editWorkspaceInfo'], { ns: 'common' })}
|
|
className="cursor-pointer rounded-md border-none bg-transparent p-1 hover:bg-black/5"
|
|
onClick={() => {
|
|
setEditWorkspaceModalVisible(true)
|
|
}}
|
|
>
|
|
<span
|
|
aria-hidden="true"
|
|
className="i-ri-pencil-line size-4 text-text-tertiary"
|
|
/>
|
|
</button>
|
|
}
|
|
/>
|
|
<TooltipContent>
|
|
{t(($) => $['account.editWorkspaceInfo'], { ns: 'common' })}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="mt-1 system-xs-medium text-text-tertiary">
|
|
{enableBilling && isNotUnlimitedMemberPlan ? (
|
|
<div className="flex space-x-1">
|
|
<div>
|
|
{t(($) => $['plansCommon.member'], { ns: 'billing' })}
|
|
{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
|
|
</div>
|
|
<div className="">{accounts.length}</div>
|
|
<div>/</div>
|
|
<div>
|
|
{plan.total.teamMembers === NUM_INFINITE
|
|
? t(($) => $['plansCommon.unlimited'], { ns: 'billing' })
|
|
: plan.total.teamMembers}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex space-x-1">
|
|
<div>{accounts.length}</div>
|
|
<div>
|
|
{t(($) => $['plansCommon.memberAfter'], { ns: 'billing' })}
|
|
{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{isMemberFull && <UpgradeBtn className="mr-2" loc="member-invite" />}
|
|
<div className="shrink-0">
|
|
{canManageMembers && (
|
|
<InviteModal
|
|
open={inviteModalVisible}
|
|
trigger={<InviteButton />}
|
|
isEmailSetup={systemFeatures.is_email_setup}
|
|
onOpenChange={setInviteModalVisible}
|
|
onSend={setInvitationResults}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="overflow-visible lg:overflow-visible">
|
|
<div className="flex min-w-120 items-center border-b border-divider-regular py-1.75">
|
|
<div className="w-65 shrink-0 px-3 system-xs-medium-uppercase text-text-tertiary">
|
|
{t(($) => $['members.name'], { ns: 'common' })}
|
|
</div>
|
|
<div className="w-30 shrink-0 system-xs-medium-uppercase text-text-tertiary">
|
|
{t(($) => $['members.lastActive'], { ns: 'common' })}
|
|
</div>
|
|
<div className="min-w-0 grow px-3 system-xs-medium-uppercase text-text-tertiary">
|
|
{roleColumnLabel}
|
|
</div>
|
|
</div>
|
|
<div className="relative min-w-120">
|
|
{accounts.map((account) => (
|
|
<MemberRow
|
|
key={account.id}
|
|
member={account}
|
|
roles={account.roles}
|
|
isCurrentUser={userProfileEmail === account.email}
|
|
canManage={canManageMembers}
|
|
canTransferOwnership={isCurrentWorkspaceOwner && isAllowTransferWorkspace}
|
|
allowMultipleRoles={systemFeatures.rbac_enabled}
|
|
onOpenDetails={handleOpenDetails}
|
|
onTransferOwnership={handleTransferOwnership}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{invitationResults && (
|
|
<InvitedModal
|
|
invitationResults={invitationResults}
|
|
onCancel={() => setInvitationResults(null)}
|
|
/>
|
|
)}
|
|
{editWorkspaceModalVisible && (
|
|
<EditWorkspaceModal onCancel={() => setEditWorkspaceModalVisible(false)} />
|
|
)}
|
|
{showTransferOwnershipModal && (
|
|
<TransferOwnershipModal
|
|
show={showTransferOwnershipModal}
|
|
onClose={() => setShowTransferOwnershipModal(false)}
|
|
/>
|
|
)}
|
|
{detailsMember && (
|
|
<MemberDetailsModal
|
|
member={detailsMember}
|
|
canAssignRoles={
|
|
canManageMembers &&
|
|
detailsMember.role !== 'owner' &&
|
|
userProfileEmail !== detailsMember.email
|
|
}
|
|
allowMultipleRoles={systemFeatures.rbac_enabled}
|
|
onClose={handleCloseDetails}
|
|
onAssignSubmit={handleAssignRolesSubmit}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default MembersPage
|