mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 17:29:44 +00:00
Update permission (#1522)
* Permission (#1442) * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * feat: add permission display in the team manager modal * feat: add permission i18n * feat: let team module acquire permission ablity * feat: add ownerPermission property into metaData * feat: team premission system * feat: extract the resourcePermission from resource schemas * fix: move enum definition to constant * feat: auth member permission handler, invite user * feat: permission manage * feat: adjust the style * feat: team card style - add a new icon * feat: team permission in guest mode * chore: change the type * chore: delete useless file * chore: delete useless code * feat: do not show owner in PermissionManage view * chore: fix style * fix: icon remove fill * feat: adjust the codes --------- Co-authored-by: Archer <545436317@qq.com> * perf: permission modal * lock --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
48
projects/app/src/components/common/Rowtabs/index.tsx
Normal file
48
projects/app/src/components/common/Rowtabs/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, BoxProps, border } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
type Props = BoxProps & {
|
||||
list: {
|
||||
icon?: string;
|
||||
label: string | React.ReactNode;
|
||||
value: string;
|
||||
}[];
|
||||
value: string;
|
||||
onChange: (e: string) => void;
|
||||
};
|
||||
|
||||
const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
|
||||
return (
|
||||
<Box display={'inline-flex'} px={'3px'} {...props}>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.value}
|
||||
flex={'1 0 0'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
px={px}
|
||||
py={py}
|
||||
userSelect={'none'}
|
||||
whiteSpace={'noWrap'}
|
||||
borderBottom={'2px solid'}
|
||||
{...(value === item.value
|
||||
? {
|
||||
bg: 'white',
|
||||
color: 'primary.600',
|
||||
borderColor: 'primary.600'
|
||||
}
|
||||
: {
|
||||
borderColor: 'myGray.100',
|
||||
onClick: () => onChange(item.value)
|
||||
})}
|
||||
>
|
||||
{item.icon && <MyIcon name={item.icon as any} mr={1} w={'14px'} />}
|
||||
<Box>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RowTabs;
|
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalFooter,
|
||||
Grid,
|
||||
Input,
|
||||
Flex,
|
||||
Checkbox,
|
||||
CloseButton,
|
||||
InputGroup,
|
||||
InputLeftElement
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
import {
|
||||
hasManage,
|
||||
constructPermission,
|
||||
PermissionList
|
||||
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||
const members = useContextSelector(TeamContext, (v) =>
|
||||
v.members.filter((member) => {
|
||||
return member.tmbId != userInfo!.team.tmbId && !hasManage(member.permission);
|
||||
})
|
||||
);
|
||||
const [selected, setSelected] = useState<typeof members>([]);
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [searched, setSearched] = useState<typeof members>(members);
|
||||
|
||||
const { mutate: submit, isLoading } = useRequest({
|
||||
mutationFn: async () => {
|
||||
console.log(selected);
|
||||
return updateMemberPermission({
|
||||
teamId: userInfo!.team.teamId,
|
||||
permission: constructPermission([
|
||||
PermissionList['Read'],
|
||||
PermissionList['Write'],
|
||||
PermissionList['Manage']
|
||||
]).value,
|
||||
memberIds: selected.map((item) => {
|
||||
return item.tmbId;
|
||||
})
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
refetchMembers();
|
||||
onSuccess();
|
||||
},
|
||||
successToast: '成功',
|
||||
errorToast: '失败'
|
||||
});
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc={'support/permission/collaborator'}
|
||||
maxW={['90vw']}
|
||||
minW={['900px']}
|
||||
overflow={'unset'}
|
||||
title={
|
||||
<Box>
|
||||
<Box>添加管理员</Box>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody py={6} px={10}>
|
||||
<Grid
|
||||
templateColumns="1fr 1fr"
|
||||
h="448px"
|
||||
borderRadius="8px"
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
>
|
||||
<Flex flexDirection="column" p="4">
|
||||
<InputGroup alignItems="center" h="32px" my="2" py="1">
|
||||
<InputLeftElement>
|
||||
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder="搜索用户名"
|
||||
fontSize="lg"
|
||||
bg={'myGray.50'}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value);
|
||||
setSearched(
|
||||
members.filter((member) => member.memberName.includes(e.target.value))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Flex flexDirection="column" mt={3}>
|
||||
{searched.map((member) => {
|
||||
return (
|
||||
<Flex
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
fontSize="lg"
|
||||
alignItems="center"
|
||||
key={member.tmbId}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
onClick={() => {
|
||||
if (selected.indexOf(member) == -1) {
|
||||
setSelected([...selected, member]);
|
||||
} else {
|
||||
setSelected([...selected.filter((item) => item.tmbId != member.tmbId)]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox isChecked={selected.includes(member)} size="lg" />
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
{member.memberName}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4">
|
||||
<Box mt={3}>已选: {selected.length} 个</Box>
|
||||
<Box mt={5}>
|
||||
{selected.map((member) => {
|
||||
return (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
key={member.tmbId}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
>
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<Box w="full" fontSize="lg">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
<CloseButton
|
||||
onClick={() =>
|
||||
setSelected([...selected.filter((item) => item.tmbId != member.tmbId)])
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<Button h={'30px'} isLoading={isLoading} onClick={submit}>
|
||||
确定
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddManagerModal;
|
@@ -0,0 +1,103 @@
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, MenuButton, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberRoleMap,
|
||||
TeamMemberStatusMap
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
|
||||
function MemberTable() {
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const openRemoveMember = useContextSelector(TeamContext, (v) => v.openRemoveMember);
|
||||
const onRemoveMember = useContextSelector(TeamContext, (v) => v.onRemoveMember);
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<TableContainer overflow={'unset'}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead bg={'myWhite.400'}>
|
||||
<Tr>
|
||||
<Th>{t('common.Username')}</Th>
|
||||
<Th>{t('user.team.Role')}</Th>
|
||||
<Th>{t('common.Status')}</Th>
|
||||
<Th>{t('common.Action')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||
{item.memberName}
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||
</Td>
|
||||
<Td>
|
||||
{hasManage(
|
||||
members.find((item) => item.tmbId === userInfo?.team.tmbId)?.permission!
|
||||
) &&
|
||||
item.role !== TeamMemberRoleEnum.owner &&
|
||||
item.tmbId !== userInfo?.team.tmbId && (
|
||||
<MyMenu
|
||||
width={20}
|
||||
trigger="hover"
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
bg: 'myWhite.600'
|
||||
}}
|
||||
borderRadius={'md'}
|
||||
px={2}
|
||||
py={1}
|
||||
lineHeight={1}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'primary.500' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
label: t('user.team.Remove Member Tip'),
|
||||
onClick: () =>
|
||||
openRemoveMember(
|
||||
() =>
|
||||
onRemoveMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.Remove Member Confirm Tip', {
|
||||
username: item.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberTable;
|
@@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TeamContext } from '.';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import AddManagerModal from './AddManager';
|
||||
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
constructPermission,
|
||||
hasManage,
|
||||
PermissionList
|
||||
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
function PermissionManage() {
|
||||
const { t } = useTranslation();
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const {
|
||||
isOpen: isOpenAddManager,
|
||||
onOpen: onOpenAddManager,
|
||||
onClose: onCloseAddManager
|
||||
} = useDisclosure();
|
||||
|
||||
const { mutate: removeManager } = useRequest({
|
||||
mutationFn: async (memberId: string) => {
|
||||
return updateMemberPermission({
|
||||
teamId: userInfo!.team.teamId,
|
||||
permission: constructPermission([PermissionList['Read'], PermissionList['Write']]).value,
|
||||
memberIds: [memberId]
|
||||
});
|
||||
},
|
||||
successToast: 'Success',
|
||||
errorToast: 'Error',
|
||||
onSuccess: () => {
|
||||
refetchMembers();
|
||||
},
|
||||
onError: () => {
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} flex={'1'} h={['auto', '100%']} bg={'white'}>
|
||||
{isOpenAddManager && (
|
||||
<AddManagerModal onClose={onCloseAddManager} onSuccess={onCloseAddManager} />
|
||||
)}
|
||||
|
||||
<Flex
|
||||
mx={'5'}
|
||||
flexDirection={'row'}
|
||||
alignItems={'center'}
|
||||
rowGap={'8'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Flex>
|
||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'} alignItems={'center'}>
|
||||
{t('user.team.role.Admin')}
|
||||
</Box>
|
||||
<Box
|
||||
fontSize={['xs']}
|
||||
color={'myGray.500'}
|
||||
bgColor={'myGray.100'}
|
||||
alignItems={'center'}
|
||||
alignContent={'center'}
|
||||
mx={'6'}
|
||||
px={'3'}
|
||||
borderRadius={'sm'}
|
||||
>
|
||||
可邀请, 删除成员
|
||||
</Box>
|
||||
</Flex>
|
||||
{userInfo?.team.role === 'owner' && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
onOpenAddManager();
|
||||
}}
|
||||
>
|
||||
添加管理员
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex mt="4" mx="4">
|
||||
{members.map((member) => {
|
||||
if (hasManage(member.permission) && member.role !== TeamMemberRoleEnum.owner) {
|
||||
return (
|
||||
<Tag key={member.memberName} mx={'2'} px="4" py="2" bg="myGray.100">
|
||||
<Avatar src={member.avatar} w="20px" />
|
||||
<TagLabel fontSize={'md'} alignItems="center" mr="6" ml="2">
|
||||
{member.memberName}
|
||||
</TagLabel>
|
||||
{userInfo?.team.role === 'owner' && (
|
||||
<MyIcon
|
||||
name="common/trash"
|
||||
w="16px"
|
||||
color="myGray.500"
|
||||
cursor="pointer"
|
||||
onClick={() => {
|
||||
removeManager(member.tmbId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default PermissionManage;
|
@@ -0,0 +1,183 @@
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { TeamContext } from '.';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import RowTabs from '../../../../common/Rowtabs';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import MemberTable from './MemberTable';
|
||||
import PermissionManage from './PermissionManage';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
type TabListType = Pick<React.ComponentProps<typeof RowTabs>, 'list'>['list'];
|
||||
enum TabListEnum {
|
||||
member = 'member',
|
||||
permission = 'permission'
|
||||
}
|
||||
|
||||
function TeamCard() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { userT } = useI18n();
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const onOpenInvite = useContextSelector(TeamContext, (v) => v.onOpenInvite);
|
||||
const onOpenTeamTagsAsync = useContextSelector(TeamContext, (v) => v.onOpenTeamTagsAsync);
|
||||
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||
const onLeaveTeam = useContextSelector(TeamContext, (v) => v.onLeaveTeam);
|
||||
|
||||
const { userInfo, teamPlanStatus } = useUserStore();
|
||||
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
content: t('user.team.member.Confirm Leave')
|
||||
});
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const Tablist: TabListType = [
|
||||
{
|
||||
icon: 'support/team/memberLight',
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: TabListEnum.member
|
||||
},
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
label: t('common.Role'),
|
||||
value: TabListEnum.permission
|
||||
}
|
||||
];
|
||||
|
||||
const [tab, setTab] = useState<string>(Tablist[0].value);
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
flex={'1'}
|
||||
h={['auto', '100%']}
|
||||
bg={'white'}
|
||||
minH={['50vh', 'auto']}
|
||||
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={5}
|
||||
py={4}
|
||||
borderBottom={'1.5px solid'}
|
||||
borderBottomColor={'myGray.100'}
|
||||
mb={3}
|
||||
>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
||||
{userInfo?.team.teamName}
|
||||
</Box>
|
||||
{userInfo?.team.role === TeamMemberRoleEnum.owner && (
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w={'14px'}
|
||||
ml={2}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex px={5} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<RowTabs
|
||||
overflow={'auto'}
|
||||
list={Tablist}
|
||||
value={tab}
|
||||
onChange={(v) => {
|
||||
setTab(v as string);
|
||||
}}
|
||||
></RowTabs>
|
||||
<Flex alignItems={'center'}>
|
||||
{hasManage(
|
||||
members.find((item) => item.tmbId.toString() === userInfo?.team.tmbId.toString())
|
||||
?.permission!
|
||||
) &&
|
||||
tab === 'member' && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="common/inviteLight" w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
onOpenTeamTagsAsync();
|
||||
}}
|
||||
>
|
||||
{t('user.team.Team Tags Async')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.role !== TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
|
||||
onClick={() => {
|
||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||
}}
|
||||
>
|
||||
{t('user.team.Leave Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
||||
{tab === 'member' ? <MemberTable /> : <PermissionManage />}
|
||||
</Box>
|
||||
<ConfirmLeaveTeamModal />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamCard;
|
@@ -0,0 +1,117 @@
|
||||
import { Box, Button, Flex, IconButton } from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import EditModal, { defaultForm } from './EditModal';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
|
||||
function TeamList() {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const editTeamData = useContextSelector(TeamContext, (v) => v.editTeamData);
|
||||
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||
const myTeams = useContextSelector(TeamContext, (v) => v.myTeams);
|
||||
const refetchTeam = useContextSelector(TeamContext, (v) => v.refetchTeam);
|
||||
const onSwitchTeam = useContextSelector(TeamContext, (v) => v.onSwitchTeam);
|
||||
// get the list of teams
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
w={['auto', '270px']}
|
||||
h={['auto', '100%']}
|
||||
pt={3}
|
||||
px={5}
|
||||
mb={[2, 0]}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
py={2}
|
||||
h={'40px'}
|
||||
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
|
||||
>
|
||||
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
|
||||
{t('common.Team')}
|
||||
</Box>
|
||||
{/* if there is no team */}
|
||||
{myTeams.length < 1 && (
|
||||
<IconButton
|
||||
variant={'ghost'}
|
||||
border={'none'}
|
||||
icon={
|
||||
<MyIcon
|
||||
name={'common/addCircleLight'}
|
||||
w={['16px', '18px']}
|
||||
color={'primary.500'}
|
||||
cursor={'pointer'}
|
||||
/>
|
||||
}
|
||||
aria-label={''}
|
||||
onClick={() => setEditTeamData(defaultForm)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
|
||||
{myTeams.map((team) => (
|
||||
<Flex
|
||||
key={team.teamId}
|
||||
alignItems={'center'}
|
||||
mt={3}
|
||||
borderRadius={'md'}
|
||||
p={3}
|
||||
cursor={'default'}
|
||||
gap={3}
|
||||
{...(userInfo?.team?.teamId === team.teamId
|
||||
? {
|
||||
bg: 'primary.200'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
{...(team.role === TeamMemberRoleEnum.owner
|
||||
? {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{team.teamName}
|
||||
</Box>
|
||||
{userInfo?.team?.teamId === team.teamId ? (
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
|
||||
) : (
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={'whitePrimary'}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
>
|
||||
{t('user.team.Check Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
{!!editTeamData && (
|
||||
<EditModal
|
||||
defaultData={editTeamData}
|
||||
onClose={() => setEditTeamData(undefined)}
|
||||
onSuccess={() => {
|
||||
refetchTeam();
|
||||
initUserInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamList;
|
@@ -2,66 +2,59 @@ import React, { useMemo, useState } from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
getTeamList,
|
||||
getTeamMembers,
|
||||
putSwitchTeam,
|
||||
putUpdateMember,
|
||||
delRemoveMember,
|
||||
delLeaveTeam
|
||||
getTeamList,
|
||||
delLeaveTeam,
|
||||
putSwitchTeam
|
||||
} from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
useDisclosure,
|
||||
MenuButton
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { Box, useDisclosure } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberRoleMap,
|
||||
TeamMemberStatusEnum,
|
||||
TeamMemberStatusMap
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { FormDataType, defaultForm } from './EditModal';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import TeamList from './TeamList';
|
||||
import TeamCard from './TeamCard';
|
||||
import { FormDataType } from './EditModal';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
|
||||
const EditModal = dynamic(() => import('./EditModal'));
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
|
||||
|
||||
export const TeamContext = createContext<{
|
||||
editTeamData?: FormDataType;
|
||||
setEditTeamData: React.Dispatch<React.SetStateAction<any>>;
|
||||
members: Awaited<ReturnType<typeof getTeamMembers>>;
|
||||
myTeams: Awaited<ReturnType<typeof getTeamList>>;
|
||||
refetchTeam: ReturnType<typeof useQuery>['refetch'];
|
||||
onSwitchTeam: ReturnType<typeof useRequest>['mutate'];
|
||||
refetchMembers: ReturnType<typeof useQuery>['refetch'];
|
||||
openRemoveMember: ReturnType<typeof useConfirm>['openConfirm'];
|
||||
onOpenInvite: ReturnType<typeof useDisclosure>['onOpen'];
|
||||
onOpenTeamTagsAsync: ReturnType<typeof useDisclosure>['onOpen'];
|
||||
onRemoveMember: ReturnType<typeof useRequest>['mutate'];
|
||||
onLeaveTeam: ReturnType<typeof useRequest>['mutate'];
|
||||
}>({} as any);
|
||||
|
||||
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { toast } = useToast();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
content: t('user.team.member.Confirm Leave')
|
||||
});
|
||||
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isFetching: isLoadingTeams,
|
||||
refetch: refetchTeam
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
|
||||
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||
const {
|
||||
@@ -70,25 +63,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onClose: onCloseTeamTagsAsync
|
||||
} = useDisclosure();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isFetching: isLoadingTeams,
|
||||
refetch: refetchTeam
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
const defaultTeam = useMemo(
|
||||
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
||||
[myTeams]
|
||||
);
|
||||
|
||||
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
||||
mutationFn: async (teamId: string) => {
|
||||
const token = await putSwitchTeam(teamId);
|
||||
token && setToken(token);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
});
|
||||
|
||||
// member action
|
||||
const { data: members = [], refetch: refetchMembers } = useQuery(
|
||||
['getMembers', userInfo?.team?.teamId],
|
||||
@@ -104,6 +78,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
|
||||
mutationFn: delRemoveMember,
|
||||
onSuccess() {
|
||||
@@ -112,13 +87,27 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
successToast: t('user.team.Remove Member Success'),
|
||||
errorToast: t('user.team.Remove Member Failed')
|
||||
});
|
||||
|
||||
const defaultTeam = useMemo(
|
||||
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
||||
[myTeams]
|
||||
);
|
||||
|
||||
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
||||
mutationFn: async (teamId: string) => {
|
||||
const token = await putSwitchTeam(teamId);
|
||||
token && setToken(token);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
});
|
||||
|
||||
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
||||
mutationFn: async (teamId?: string) => {
|
||||
if (!teamId) return;
|
||||
// change to personal team
|
||||
// get members
|
||||
await onSwitchTeam(defaultTeam.teamId);
|
||||
|
||||
onSwitchTeam(defaultTeam.teamId);
|
||||
return delLeaveTeam(teamId);
|
||||
},
|
||||
onSuccess() {
|
||||
@@ -128,7 +117,22 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
});
|
||||
|
||||
return !!userInfo?.team ? (
|
||||
<>
|
||||
<TeamContext.Provider
|
||||
value={{
|
||||
myTeams: myTeams,
|
||||
refetchTeam: refetchTeam,
|
||||
onSwitchTeam: onSwitchTeam,
|
||||
members: members,
|
||||
refetchMembers: refetchMembers,
|
||||
openRemoveMember: openRemoveMember,
|
||||
onOpenInvite: onOpenInvite,
|
||||
onOpenTeamTagsAsync: onOpenTeamTagsAsync,
|
||||
onRemoveMember: onRemoveMember,
|
||||
editTeamData: editTeamData,
|
||||
setEditTeamData: setEditTeamData,
|
||||
onLeaveTeam: onLeaveTeam
|
||||
}}
|
||||
>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
@@ -140,323 +144,20 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
|
||||
{/* teams */}
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
w={['auto', '270px']}
|
||||
h={['auto', '100%']}
|
||||
pt={3}
|
||||
px={5}
|
||||
mb={[2, 0]}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
py={2}
|
||||
h={'40px'}
|
||||
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
|
||||
>
|
||||
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
|
||||
{t('common.Team')}
|
||||
</Box>
|
||||
{myTeams.length < 1 && (
|
||||
<IconButton
|
||||
variant={'ghost'}
|
||||
border={'none'}
|
||||
icon={
|
||||
<MyIcon
|
||||
name={'common/addCircleLight'}
|
||||
w={['16px', '18px']}
|
||||
color={'primary.500'}
|
||||
cursor={'pointer'}
|
||||
/>
|
||||
}
|
||||
aria-label={''}
|
||||
onClick={() => setEditTeamData(defaultForm)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
|
||||
{myTeams.map((team) => (
|
||||
<Flex
|
||||
key={team.teamId}
|
||||
alignItems={'center'}
|
||||
mt={3}
|
||||
borderRadius={'md'}
|
||||
p={3}
|
||||
cursor={'default'}
|
||||
gap={3}
|
||||
{...(userInfo?.team?.teamId === team.teamId
|
||||
? {
|
||||
bg: 'primary.200'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
{...(team.role === TeamMemberRoleEnum.owner
|
||||
? {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{team.teamName}
|
||||
</Box>
|
||||
{userInfo?.team?.teamId === team.teamId ? (
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
|
||||
) : (
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={'whitePrimary'}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
>
|
||||
{t('user.team.Check Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* team card */}
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
flex={'1'}
|
||||
h={['auto', '100%']}
|
||||
bg={'white'}
|
||||
minH={['50vh', 'auto']}
|
||||
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={5}
|
||||
py={4}
|
||||
borderBottom={'1.5px solid'}
|
||||
borderBottomColor={'myGray.100'}
|
||||
mb={3}
|
||||
>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
||||
{userInfo.team.teamName}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w={'14px'}
|
||||
ml={2}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex px={5} alignItems={'center'}>
|
||||
<MyIcon name="support/team/memberLight" w={'14px'} />
|
||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
onOpenTeamTagsAsync();
|
||||
}}
|
||||
>
|
||||
{t('user.team.Team Tags Async')}
|
||||
</Button>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
|
||||
onClick={() => {
|
||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||
}}
|
||||
>
|
||||
{t('user.team.Leave Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
||||
<TableContainer overflow={'unset'}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead bg={'myWhite.400'}>
|
||||
<Tr>
|
||||
<Th>{t('common.Username')}</Th>
|
||||
<Th>{t('user.team.Role')}</Th>
|
||||
<Th>{t('common.Status')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||
{item.memberName}
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||
</Td>
|
||||
<Td>
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner &&
|
||||
item.role !== TeamMemberRoleEnum.owner && (
|
||||
<MyMenu
|
||||
width={20}
|
||||
trigger="click"
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
bg: 'myWhite.600'
|
||||
}}
|
||||
borderRadius={'md'}
|
||||
px={2}
|
||||
py={1}
|
||||
lineHeight={1}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'primary.500' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
isActive: item.role === TeamMemberRoleEnum.visitor,
|
||||
label: t('user.team.Invite Role Visitor Tip'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
role: TeamMemberRoleEnum.visitor
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
isActive: item.role === TeamMemberRoleEnum.admin,
|
||||
label: t('user.team.Invite Role Admin Tip'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
role: TeamMemberRoleEnum.admin
|
||||
});
|
||||
}
|
||||
},
|
||||
...(item.status === TeamMemberStatusEnum.reject
|
||||
? [
|
||||
{
|
||||
label: t('user.team.Reinvite'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.waiting
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: t('user.team.Remove Member Tip'),
|
||||
onClick: () =>
|
||||
openRemoveMember(
|
||||
() =>
|
||||
onRemoveMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.Remove Member Confirm Tip', {
|
||||
username: item.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Flex>
|
||||
<TeamList />
|
||||
<TeamCard />
|
||||
<Loading
|
||||
loading={
|
||||
isSwitchTeam ||
|
||||
isLoadingTeams ||
|
||||
isLoadingUpdateMember ||
|
||||
isLoadingRemoveMember ||
|
||||
isLoadingLeaveTeam
|
||||
isLoadingTeams ||
|
||||
isLoadingLeaveTeam ||
|
||||
isSwitchTeam
|
||||
}
|
||||
fixed={false}
|
||||
/>
|
||||
</Box>
|
||||
</MyModal>
|
||||
{!!editTeamData && (
|
||||
<EditModal
|
||||
defaultData={editTeamData}
|
||||
onClose={() => setEditTeamData(undefined)}
|
||||
onSuccess={() => {
|
||||
refetchTeam();
|
||||
initUserInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenInvite && userInfo?.team?.teamId && (
|
||||
<InviteModal
|
||||
teamId={userInfo.team.teamId}
|
||||
@@ -466,8 +167,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
)}
|
||||
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
|
||||
<ConfirmRemoveMemberModal />
|
||||
<ConfirmLeaveTeamModal />
|
||||
</>
|
||||
</TeamContext.Provider>
|
||||
) : null;
|
||||
};
|
||||
|
||||
|
@@ -190,7 +190,7 @@ export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
currentTab: content?.query?.currentTab || TabEnum.info,
|
||||
...(await serviceSideProps(content, ['publish']))
|
||||
...(await serviceSideProps(content, ['publish', 'user']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
2
projects/app/src/types/i18n.d.ts
vendored
2
projects/app/src/types/i18n.d.ts
vendored
@@ -5,6 +5,7 @@ import app from '../../i18n/zh/app.json';
|
||||
import file from '../../i18n/zh/file.json';
|
||||
import publish from '../../i18n/zh/publish.json';
|
||||
import workflow from '../../i18n/zh/workflow.json';
|
||||
import user from '../../i18n/zh/user.json';
|
||||
|
||||
export interface I18nNamespaces {
|
||||
common: typeof common;
|
||||
@@ -13,6 +14,7 @@ export interface I18nNamespaces {
|
||||
file: typeof file;
|
||||
publish: typeof publish;
|
||||
workflow: typeof workflow;
|
||||
user: typeof user;
|
||||
}
|
||||
|
||||
export type I18nNsType = (keyof I18nNamespaces)[];
|
||||
|
@@ -9,6 +9,7 @@ type I18nContextType = {
|
||||
fileT: TFunction<['file'], undefined>;
|
||||
publishT: TFunction<['publish'], undefined>;
|
||||
workflowT: TFunction<['workflow'], undefined>;
|
||||
userT: TFunction<['user'], undefined>;
|
||||
};
|
||||
|
||||
export const I18nContext = createContext<I18nContextType>({
|
||||
@@ -23,6 +24,7 @@ const I18nContextProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { t: fileT } = useTranslation('file');
|
||||
const { t: publishT } = useTranslation('publish');
|
||||
const { t: workflowT } = useTranslation('workflow');
|
||||
const { t: userT } = useTranslation('user');
|
||||
|
||||
return (
|
||||
<I18nContext.Provider
|
||||
@@ -32,7 +34,8 @@ const I18nContextProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
datasetT,
|
||||
fileT,
|
||||
publishT,
|
||||
workflowT
|
||||
workflowT,
|
||||
userT
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
InviteMemberProps,
|
||||
InviteMemberResponse,
|
||||
UpdateInviteProps,
|
||||
UpdateTeamMemberPermissionProps,
|
||||
UpdateTeamMemberProps,
|
||||
UpdateTeamProps
|
||||
} from '@fastgpt/global/support/user/team/controller.d';
|
||||
@@ -41,6 +42,8 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
|
||||
PUT('/proApi/support/user/team/member/updateInvite', data);
|
||||
export const delLeaveTeam = (teamId: string) =>
|
||||
DELETE('/proApi/support/user/team/member/leave', { teamId });
|
||||
export const updateMemberPermission = (data: UpdateTeamMemberPermissionProps) =>
|
||||
PUT('/proApi/support/user/team/member/updatePermission', data);
|
||||
|
||||
/* --------------- team tags ---------------- */
|
||||
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
|
||||
|
Reference in New Issue
Block a user