@@ -79,7 +79,7 @@ const UpdateContactModal = ({
|
||||
title={
|
||||
mode === 'notification_account'
|
||||
? t('common:support.user.info.notification_receiving_hint')
|
||||
: t('account_info:contact')
|
||||
: t('common:contact_way')
|
||||
}
|
||||
>
|
||||
<ModalBody px={10}>
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import { getInvitationInfo, postAcceptInvitationLink } from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CloseButton,
|
||||
Flex,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalHeader
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from './context';
|
||||
import { isForbidden } from '@fastgpt/service/support/user/team/invitationLink/controllers';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
function Invite({ invitelinkid }: { invitelinkid: string }) {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { onSwitchTeam, refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const onClose = () => {
|
||||
router.push('/account/team');
|
||||
};
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: invitationInfo } = useRequest2(() => getInvitationInfo(invitelinkid), {
|
||||
manual: false,
|
||||
onSuccess: (data) => {
|
||||
if (isForbidden(data)) {
|
||||
toast({
|
||||
description: t('account_team:invitation_link_has_been_invalid'),
|
||||
status: 'warning'
|
||||
});
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onError: onClose
|
||||
});
|
||||
|
||||
const { runAsync: acceptInvitation } = useRequest2(() => postAcceptInvitationLink(invitelinkid), {
|
||||
manual: true,
|
||||
successToast: t('common:common.Success'),
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
description: t('common:common.Success'),
|
||||
status: 'success'
|
||||
});
|
||||
onSwitchTeam(invitationInfo!.teamId);
|
||||
refetchMembers();
|
||||
onClose();
|
||||
},
|
||||
onError: (e) => {
|
||||
toast({
|
||||
description: t('common:common.Error'),
|
||||
status: 'error'
|
||||
});
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{invitationInfo && (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="support/user/usersLight"
|
||||
title={t('account_team:handle_invitation')}
|
||||
iconColor={'primary.600'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Flex
|
||||
key={invitationInfo._id}
|
||||
alignItems={'center'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
>
|
||||
<Avatar src={invitationInfo.teamAvatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{invitationInfo.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button size="sm" variant={'solid'} colorScheme="green" onClick={acceptInvitation}>
|
||||
{t('common:user.team.invite.accept')}
|
||||
</Button>
|
||||
<Button size="sm" ml={2} variant="outline" onClick={onClose}>
|
||||
{t('account_team:ignore')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Invite;
|
||||
@@ -3,11 +3,13 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Grid,
|
||||
HStack,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalFooter
|
||||
ModalFooter,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
InvitationLinkCreateType,
|
||||
@@ -28,22 +30,18 @@ function CreateInvitationModal({ onClose }: { onClose: () => void }) {
|
||||
{ label: t('account_team:1year'), value: '1y' } // 1 year
|
||||
];
|
||||
|
||||
const usedTimesLimitOptions = [
|
||||
{ label: t('account_team:unlimited'), value: -1 },
|
||||
{ label: t('account_team:1person'), value: 1 }
|
||||
];
|
||||
const { register, handleSubmit, watch, setValue } = useForm<InvitationLinkCreateType>({
|
||||
defaultValues: {
|
||||
description: '',
|
||||
expires: expiresOptions[1].value,
|
||||
usedTimesLimit: usedTimesLimitOptions[1].value
|
||||
usedTimesLimit: 1
|
||||
}
|
||||
});
|
||||
|
||||
const expires = watch('expires');
|
||||
const usedTimesLimit = watch('usedTimesLimit');
|
||||
|
||||
const { runAsync: createInvitationLink } = useRequest2(postCreateInvitationLink, {
|
||||
const { runAsync: createInvitationLink, loading } = useRequest2(postCreateInvitationLink, {
|
||||
manual: true,
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed'),
|
||||
@@ -56,39 +54,47 @@ function CreateInvitationModal({ onClose }: { onClose: () => void }) {
|
||||
iconSrc="common/addLight"
|
||||
iconColor="primary.500"
|
||||
title={<Box>{t('account_team:create_invitation_link')}</Box>}
|
||||
minW={'500px'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Grid gap={4} w="full" templateColumns="max-content 1fr" alignItems="center">
|
||||
<FormLabel required={true}>{t('account_team:invitation_link_description')}</FormLabel>
|
||||
<Input
|
||||
placeholder={t('account_team:invitation_link_description')}
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
<Grid gap={6} templateColumns="max-content 1fr" alignItems="center">
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:invitation_link_description')}</FormLabel>
|
||||
<Input
|
||||
placeholder={t('account_team:invitation_link_description')}
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
</>
|
||||
|
||||
<FormLabel required={true}>{t('account_team:expires')}</FormLabel>
|
||||
<MySelect
|
||||
list={expiresOptions}
|
||||
value={expires}
|
||||
onchange={(val) => setValue('expires', val)}
|
||||
minW="120px"
|
||||
/>
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:expires')}</FormLabel>
|
||||
<MySelect
|
||||
list={expiresOptions}
|
||||
value={expires}
|
||||
onchange={(val) => setValue('expires', val)}
|
||||
minW="120px"
|
||||
/>
|
||||
</>
|
||||
|
||||
<FormLabel required={true}>{t('account_team:used_times_limit')}</FormLabel>
|
||||
<MySelect
|
||||
list={usedTimesLimitOptions}
|
||||
value={usedTimesLimit}
|
||||
onchange={(val) => setValue('usedTimesLimit', val)}
|
||||
minW="120px"
|
||||
/>
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:used_times_limit')}</FormLabel>
|
||||
<RadioGroup
|
||||
onChange={(val: '1' | '-1') => setValue('usedTimesLimit', Number(val) as 1 | -1)}
|
||||
value={String(usedTimesLimit)}
|
||||
>
|
||||
<HStack gap={6}>
|
||||
<Radio value="1">{t('account_team:1person')}</Radio>
|
||||
<Radio value="-1">{t('account_team:unlimited')}</Radio>
|
||||
</HStack>
|
||||
</RadioGroup>
|
||||
</>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isLoading={false} onClick={onClose} variant="outline">
|
||||
<Button isLoading={loading} onClick={onClose} variant="outline">
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={false} onClick={handleSubmit(createInvitationLink)} ml="4">
|
||||
<Button isLoading={loading} onClick={handleSubmit(createInvitationLink)} ml="4">
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
@@ -0,0 +1,77 @@
|
||||
import { getInvitationInfo, postAcceptInvitationLink } from '@/web/support/user/team/api';
|
||||
import { Box, Button, Flex, ModalBody, ModalCloseButton } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '../context';
|
||||
|
||||
function Invite({ invitelinkid }: { invitelinkid: string }) {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const onClose = () => {
|
||||
router.push('/account/team');
|
||||
};
|
||||
|
||||
const { data: invitationInfo } = useRequest2(() => getInvitationInfo(invitelinkid), {
|
||||
manual: false,
|
||||
onError: onClose
|
||||
});
|
||||
|
||||
const { runAsync: acceptInvitation, loading: accepting } = useRequest2(
|
||||
() => postAcceptInvitationLink(invitelinkid),
|
||||
{
|
||||
manual: true,
|
||||
successToast: t('common:common.Success'),
|
||||
onSuccess: async () => {
|
||||
onSwitchTeam(invitationInfo!.teamId);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return invitationInfo ? (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="support/user/usersLight"
|
||||
title={t('account_team:handle_invitation')}
|
||||
iconColor={'primary.600'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Flex
|
||||
key={invitationInfo._id}
|
||||
alignItems={'center'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
>
|
||||
<Avatar src={invitationInfo.teamAvatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{invitationInfo.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={'solid'}
|
||||
colorScheme="green"
|
||||
onClick={acceptInvitation}
|
||||
isLoading={accepting}
|
||||
>
|
||||
{t('account_team:accept')}
|
||||
</Button>
|
||||
<Button size="sm" ml={2} variant="outline" onClick={onClose} isLoading={accepting}>
|
||||
{t('account_team:ignore')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default Invite;
|
||||
@@ -1,5 +1,4 @@
|
||||
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||
import Empty from '@/pageComponents/chat/Empty';
|
||||
import { getInvitationLinkList, putUpdateInvitationInfo } from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
@@ -9,9 +8,7 @@ import {
|
||||
Grid,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
@@ -24,7 +21,6 @@ import {
|
||||
import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
@@ -68,7 +64,7 @@ const InviteModal = ({
|
||||
[copyData]
|
||||
);
|
||||
|
||||
const { runAsync: onForbid } = useRequest2(
|
||||
const { runAsync: onForbid, loading: forbiding } = useRequest2(
|
||||
(linkId: string) =>
|
||||
putUpdateInvitationInfo({
|
||||
linkId,
|
||||
@@ -87,21 +83,14 @@ const InviteModal = ({
|
||||
isOpen
|
||||
iconSrc="common/inviteLight"
|
||||
iconColor="primary.600"
|
||||
minW={'600px'}
|
||||
title={
|
||||
<Box>
|
||||
<Box>{t('common:user.team.Invite Member')}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{t('common:user.team.Invite Member Tips')}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw']}
|
||||
title={t('account_team:invite_member')}
|
||||
overflow={'unset'}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '820px']}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalHeader pb="0">
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mx="2">
|
||||
<ModalBody maxH="500px">
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mb={4}>
|
||||
<HStack>
|
||||
<Icon name="common/list" w="16px" />
|
||||
<Box ml="6px" fontSize="md">
|
||||
@@ -110,8 +99,6 @@ const InviteModal = ({
|
||||
</HStack>
|
||||
<Button onClick={onOpenCreate}>{t('account_team:create_invitation_link')}</Button>
|
||||
</Flex>
|
||||
</ModalHeader>
|
||||
<ModalBody maxH="500px">
|
||||
<TableContainer overflowY={'auto'}>
|
||||
<Table fontSize={'sm'} overflow={'unset'}>
|
||||
<Thead>
|
||||
@@ -149,56 +136,57 @@ const InviteModal = ({
|
||||
: item.usedTimesLimit}
|
||||
</Td>
|
||||
<Td>
|
||||
<MyPopover
|
||||
w="fit-content"
|
||||
Trigger={
|
||||
<Box
|
||||
minW="100px"
|
||||
borderRadius="md"
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
p="1.5"
|
||||
w="fit-content"
|
||||
>
|
||||
<AvatarGroup max={3} avatars={item.members.map((i) => i.avatar)} />
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{() => (
|
||||
<Box py="4" maxH="200px" w="fit-content">
|
||||
<Flex mx="4" justifyContent="center" alignItems={'center'}>
|
||||
<Box>{t('account_team:has_invited')}</Box>
|
||||
<Box
|
||||
ml="auto"
|
||||
bg="myGray.200"
|
||||
px="2"
|
||||
borderRadius="md"
|
||||
fontSize="sm"
|
||||
>
|
||||
{item.members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Divider my="2" mx="4" />
|
||||
<Grid
|
||||
{item.members.length > 0 && (
|
||||
<MyPopover
|
||||
w="fit-content"
|
||||
Trigger={
|
||||
<Box
|
||||
borderRadius="md"
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
p="1.5"
|
||||
w="fit-content"
|
||||
mt="2"
|
||||
gridRowGap="4"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
overflow="auto"
|
||||
alignItems="center"
|
||||
mx="4"
|
||||
>
|
||||
{item.members.map((member) => (
|
||||
<Box key={member.tmbId} justifySelf="start">
|
||||
<MemberTag name={member.name} avatar={member.avatar} />
|
||||
<AvatarGroup max={3} avatars={item.members.map((i) => i.avatar)} />
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{() => (
|
||||
<Box py="4" maxH="200px" w="fit-content">
|
||||
<Flex mx="4" justifyContent="center" alignItems={'center'}>
|
||||
<Box>{t('account_team:has_invited')}</Box>
|
||||
<Box
|
||||
ml="auto"
|
||||
bg="myGray.200"
|
||||
px="2"
|
||||
borderRadius="md"
|
||||
fontSize="sm"
|
||||
>
|
||||
{item.members.length}
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
</Flex>
|
||||
<Divider my="2" mx="4" />
|
||||
<Grid
|
||||
w="fit-content"
|
||||
mt="2"
|
||||
gridRowGap="4"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
overflow="auto"
|
||||
alignItems="center"
|
||||
mx="4"
|
||||
>
|
||||
{item.members.map((member) => (
|
||||
<Box key={member.tmbId} justifySelf="start">
|
||||
<MemberTag name={member.name} avatar={member.avatar} />
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{!isForbidden && (
|
||||
@@ -232,6 +220,7 @@ const InviteModal = ({
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={forbiding}
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
onClick={() => {
|
||||
@@ -41,7 +41,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useState } from 'react';
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||
|
||||
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
@@ -236,7 +236,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:user_name')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:contact')}</Th>
|
||||
<Th bgColor="myGray.100">{t('common:contact_way')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:org')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:join_update_time')}</Th>
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
|
||||
@@ -101,6 +101,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||
async (teamId: string) => {
|
||||
await putSwitchTeam(teamId);
|
||||
refetchMembers();
|
||||
return initUserInfo();
|
||||
},
|
||||
{
|
||||
|
||||
@@ -260,7 +260,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
)}
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:contact')}: </Box>
|
||||
<Box {...labelStyles}>{t('common:contact_way')}: </Box>
|
||||
<Box flex={1} {...(!userInfo?.contact ? { color: 'red.600' } : {})}>
|
||||
{userInfo?.contact ? userInfo?.contact : t('account_info:please_bind_contact')}
|
||||
</Box>
|
||||
|
||||
@@ -20,7 +20,9 @@ const PermissionManage = dynamic(
|
||||
);
|
||||
const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
|
||||
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
|
||||
const HandleInviteModal = dynamic(() => import('@/pageComponents/account/team/HandleInviteModal'));
|
||||
const HandleInviteModal = dynamic(
|
||||
() => import('@/pageComponents/account/team/Invite/HandleInviteModal')
|
||||
);
|
||||
|
||||
export enum TeamTabEnum {
|
||||
member = 'member',
|
||||
@@ -99,7 +101,7 @@ const Team = () => {
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex align={'center'} ml={6}>
|
||||
<TeamSelector height={'28px'} onChange={refetchMembers} />
|
||||
<TeamSelector height={'28px'} />
|
||||
</Flex>
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
||||
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
||||
|
||||
Reference in New Issue
Block a user