diff --git a/apps/client/src/features/workspace/components/members/components/invite-action-menu.tsx b/apps/client/src/features/workspace/components/members/components/invite-action-menu.tsx
index 045047af..5472a9c2 100644
--- a/apps/client/src/features/workspace/components/members/components/invite-action-menu.tsx
+++ b/apps/client/src/features/workspace/components/members/components/invite-action-menu.tsx
@@ -5,8 +5,10 @@ import { modals } from "@mantine/modals";
import {
useResendInvitationMutation,
useRevokeInvitationMutation,
+ useGetInviteLink,
} from "@/features/workspace/queries/workspace-query.ts";
import { useTranslation } from "react-i18next";
+import { notifications } from "@mantine/notifications";
interface Props {
invitationId: string;
@@ -15,6 +17,17 @@ export default function InviteActionMenu({ invitationId }: Props) {
const { t } = useTranslation();
const resendInvitationMutation = useResendInvitationMutation();
const revokeInvitationMutation = useRevokeInvitationMutation();
+ const { data: inviteLink, error, } = useGetInviteLink(invitationId);
+
+ const onCopyLink = async () => {
+ if (error) {
+ notifications.show({ message: error.message, color: "red" })
+ } else {
+ navigator.clipboard.writeText(inviteLink.inviteLink)
+ notifications.show({ message: "Invite link copied to clipboard!"})
+ }
+ }
+
const onResend = async () => {
await resendInvitationMutation.mutateAsync({ invitationId });
@@ -58,6 +71,8 @@ export default function InviteActionMenu({ invitationId }: Props) {
{t("Resend invitation")}
+ Copy invite link
+ Resend invitation
{
+ return useQuery({
+ queryKey:["inviteLink",invitationId],
+ queryFn: () => getInviteLink({ invitationId }),
+ })
+}
+
export function useCreateInvitationMutation() {
const queryClient = useQueryClient();
diff --git a/apps/client/src/features/workspace/services/workspace-service.ts b/apps/client/src/features/workspace/services/workspace-service.ts
index e1bc2071..dd8bf8a4 100644
--- a/apps/client/src/features/workspace/services/workspace-service.ts
+++ b/apps/client/src/features/workspace/services/workspace-service.ts
@@ -5,6 +5,7 @@ import {
IInvitation,
IWorkspace,
IAcceptInvite,
+ IInvitationLink,
} from "../types/workspace.types";
import { IPagination, QueryParams } from "@/lib/types.ts";
@@ -53,6 +54,13 @@ export async function acceptInvitation(data: IAcceptInvite): Promise {
await api.post("/workspace/invites/accept", data);
}
+export async function getInviteLink(data: {
+ invitationId: string;
+}): Promise {
+ const req = await api.post("/workspace/invites/link", data);
+ return req.data;
+}
+
export async function resendInvitation(data: {
invitationId: string;
}): Promise {
diff --git a/apps/client/src/features/workspace/types/workspace.types.ts b/apps/client/src/features/workspace/types/workspace.types.ts
index 5f424be5..faec5590 100644
--- a/apps/client/src/features/workspace/types/workspace.types.ts
+++ b/apps/client/src/features/workspace/types/workspace.types.ts
@@ -28,6 +28,10 @@ export interface IInvitation {
createdAt: Date;
}
+export interface IInvitationLink {
+ inviteLink: string;
+}
+
export interface IAcceptInvite {
invitationId: string;
name: string;
diff --git a/apps/server/src/core/workspace/controllers/workspace.controller.ts b/apps/server/src/core/workspace/controllers/workspace.controller.ts
index bb69e7d5..557f71e6 100644
--- a/apps/server/src/core/workspace/controllers/workspace.controller.ts
+++ b/apps/server/src/core/workspace/controllers/workspace.controller.ts
@@ -237,4 +237,30 @@ export class WorkspaceController {
secure: this.environmentService.isHttps(),
});
}
+
+ @HttpCode(HttpStatus.OK)
+ @Post('invites/link')
+ async getInviteLink(
+ @Body() inviteDto: InvitationIdDto,
+ @AuthUser() user: User,
+ @AuthWorkspace() workspace: Workspace,
+ ) {
+ if (this.environmentService.isCloud()) {
+ throw new ForbiddenException();
+ }
+
+ const ability = this.workspaceAbility.createForUser(user, workspace);
+ if (
+ ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
+ ) {
+ throw new ForbiddenException();
+ }
+ const inviteLink =
+ await this.workspaceInvitationService.getInvitationLinkById(
+ inviteDto.invitationId,
+ workspace.id,
+ );
+
+ return { inviteLink };
+ }
}
diff --git a/apps/server/src/core/workspace/services/workspace-invitation.service.ts b/apps/server/src/core/workspace/services/workspace-invitation.service.ts
index fa42208c..35a1f143 100644
--- a/apps/server/src/core/workspace/services/workspace-invitation.service.ts
+++ b/apps/server/src/core/workspace/services/workspace-invitation.service.ts
@@ -71,6 +71,21 @@ export class WorkspaceInvitationService {
return invitation;
}
+ async getInvitationTokenById(invitationId: string, workspaceId: string) {
+ const invitation = await this.db
+ .selectFrom('workspaceInvitations')
+ .select(['token'])
+ .where('id', '=', invitationId)
+ .where('workspaceId', '=', workspaceId)
+ .executeTakeFirst();
+
+ if (!invitation) {
+ throw new NotFoundException('Invitation not found');
+ }
+
+ return invitation;
+ }
+
async createInvitation(
inviteUserDto: InviteUserDto,
workspaceId: string,
@@ -256,7 +271,6 @@ export class WorkspaceInvitationService {
invitationId: string,
workspaceId: string,
): Promise {
- //
const invitation = await this.db
.selectFrom('workspaceInvitations')
.selectAll()
@@ -292,13 +306,28 @@ export class WorkspaceInvitationService {
.execute();
}
+ async getInvitationLinkById(
+ invitationId: string,
+ workspaceId: string,
+ ): Promise {
+ const token = await this.getInvitationTokenById(invitationId, workspaceId);
+ return this.buildInviteLink(invitationId, token.token);
+ }
+
+ async buildInviteLink(
+ invitationId: string,
+ inviteToken: string,
+ ): Promise {
+ return `${this.environmentService.getAppUrl()}/invites/${invitationId}?token=${inviteToken}`;
+ }
+
async sendInvitationMail(
invitationId: string,
inviteeEmail: string,
inviteToken: string,
invitedByName: string,
): Promise {
- const inviteLink = `${this.environmentService.getAppUrl()}/invites/${invitationId}?token=${inviteToken}`;
+ const inviteLink = await this.buildInviteLink(invitationId, inviteToken);
const emailTemplate = InvitationEmail({
inviteLink,