fix: invite link (#4229)

* fix: invite link

* feat: create invite link and copy it directly
This commit is contained in:
Finley Ge
2025-03-19 19:59:55 +08:00
committed by archer
parent 077350e651
commit 73451dbc64
6 changed files with 70 additions and 34 deletions

View File

@@ -1,15 +1,18 @@
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel } from '../../../../common/mongo';
import { InvitationSchemaType } from './type';
import addDays from 'date-fns/esm/fp/addDays/index.js';
import { randomUUID } from 'crypto';
const { Schema } = connectionMongo;
export const InvitationCollectionName = 'team_invitation_links';
const InvitationSchema = new Schema({
linkId: {
type: String,
required: true,
unique: true,
default: () => randomUUID()
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,

View File

@@ -2,6 +2,7 @@ import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
export type InvitationSchemaType = {
_id: string;
linkId: string;
teamId: string;
usedTimesLimit?: number;
forbidden?: boolean;
@@ -25,11 +26,10 @@ export type InvitationLinkCreateType = {
expires: InvitationLinkExpiresType;
usedTimesLimit: 1 | -1;
};
export type InvitationLinkUpdateType = Partial<
Omit<InvitationSchemaType, 'members' | 'teamId' | '_id'>
> & {
linkId: string;
};
// export type InvitationLinkUpdateType = Partial<
// Omit<InvitationSchemaType, 'members' | 'teamId' | '_id'>
// >;
export type InvitationInfoType = InvitationSchemaType & {
teamAvatar: string;

View File

@@ -22,7 +22,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
function CreateInvitationModal({ onClose }: { onClose: () => void }) {
function CreateInvitationModal({ onClose }: { onClose: (linkId?: string) => void }) {
const { t } = useTranslation();
const expiresOptions: Array<{ label: string; value: InvitationLinkExpiresType }> = [
{ label: t('account_team:30mins'), value: '30m' }, // 30 mins
@@ -45,6 +45,9 @@ function CreateInvitationModal({ onClose }: { onClose: () => void }) {
manual: true,
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed'),
onSuccess: (data) => {
onClose(data);
},
onFinally: () => onClose()
});
@@ -55,7 +58,7 @@ function CreateInvitationModal({ onClose }: { onClose: () => void }) {
iconColor="primary.500"
title={<Box>{t('account_team:create_invitation_link')}</Box>}
>
<ModalCloseButton onClick={onClose} />
<ModalCloseButton onClick={() => onClose()} />
<ModalBody>
<Grid gap={6} templateColumns="max-content 1fr" alignItems="center">
<>
@@ -91,7 +94,7 @@ function CreateInvitationModal({ onClose }: { onClose: () => void }) {
</Grid>
</ModalBody>
<ModalFooter>
<Button isLoading={loading} onClick={onClose} variant="outline">
<Button isLoading={loading} onClick={() => onClose()} variant="outline">
{t('common:common.Cancel')}
</Button>
<Button isLoading={loading} onClick={handleSubmit(createInvitationLink)} ml="4">

View File

@@ -1,6 +1,6 @@
import MemberTag from '@/components/support/user/team/Info/MemberTag';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getInvitationLinkList, putUpdateInvitationInfo } from '@/web/support/user/team/api';
import { getInvitationLinkList, putForbidInvitationLink } from '@/web/support/user/team/api';
import { useUserStore } from '@/web/support/user/useUserStore';
import {
Box,
@@ -79,18 +79,11 @@ const InviteModal = ({
[copyData]
);
const { runAsync: onForbid, loading: forbiding } = useRequest2(
(linkId: string) =>
putUpdateInvitationInfo({
linkId,
forbidden: true
}),
{
manual: true,
onSuccess: refetchInvitationLinkList,
successToast: t('account_team:forbid_success')
}
);
const { runAsync: onForbid, loading: forbiding } = useRequest2(putForbidInvitationLink, {
manual: true,
onSuccess: refetchInvitationLinkList,
successToast: t('account_team:forbid_success')
});
return (
<MyModal
@@ -134,7 +127,7 @@ const InviteModal = ({
{invitationLinkList?.map((item) => {
const isForbidden = item.forbidden || new Date(item.expires) < new Date();
return (
<Tr key={item._id} overflow={'unset'}>
<Tr key={item.linkId} overflow={'unset'}>
<Td maxW="200px" minW="100px">
{item.description}
</Td>
@@ -209,7 +202,7 @@ const InviteModal = ({
<Button
size="sm"
variant="outline"
onClick={() => onCopy(item._id)}
onClick={() => onCopy(item.linkId)}
color="myGray.900"
>
<Icon name="common/link" w="16px" mr="1" />
@@ -239,7 +232,7 @@ const InviteModal = ({
variant="outline"
colorScheme="red"
onClick={() => {
onForbid(item._id);
onForbid(item.linkId);
onClosePopover();
}}
>
@@ -268,7 +261,17 @@ const InviteModal = ({
</ModalFooter>
{isOpenCreate && (
<CreateInvitationModal
onClose={() => Promise.all([onCloseCreate(), refetchInvitationLinkList()])}
onClose={(linkId?: string) =>
Promise.all([
onCloseCreate(),
refetchInvitationLinkList(),
(() => {
if (linkId) {
onCopy(linkId);
}
})()
])
}
/>
)}
</MyModal>

View File

@@ -21,7 +21,6 @@ import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fe
import type {
InvitationInfoType,
InvitationLinkCreateType,
InvitationLinkUpdateType,
InvitationType
} from '@fastgpt/service/support/user/team/invitationLink/type';
@@ -66,9 +65,8 @@ export const postAcceptInvitationLink = (linkId: string) =>
export const getInvitationInfo = (linkId: string) =>
GET<InvitationInfoType>(`/proApi/support/user/team/invitationLink/info`, { linkId });
export const putUpdateInvitationInfo = (data: InvitationLinkUpdateType) =>
PUT('/proApi/support/user/team/invitationLink/update', data);
export const putForbidInvitationLink = (linkId: string) =>
PUT<string>(`/proApi/support/user/team/invitationLink/forbid`, { linkId });
/* -------------- team collaborator -------------------- */
export const getTeamClbs = () =>

View File

@@ -32,3 +32,32 @@ export async function getRootUser(): Promise<parseHeaderCertRet> {
tmbId: tmb?._id
};
}
export async function getUser(username: string): Promise<parseHeaderCertRet> {
const user = await MongoUser.create({
username,
password: '123456'
});
const team = await MongoTeam.create({
name: 'test team',
ownerId: user._id
});
const tmb = await MongoTeamMember.create({
teamId: team._id,
userId: user._id,
status: 'active'
});
return {
userId: user._id,
apikey: '',
appId: '',
authType: AuthUserTypeEnum.token,
isRoot: false,
sourceName: undefined,
teamId: tmb?.teamId,
tmbId: tmb?._id
};
}