5078 ability to invite team members (#5750)
## Added features - update team member setting page - add a section to send invitation by email - add a new invitation email - update email font to 'Trebuchet MS' as Google Inter font is not working, we need to use a web safe font https://templates.mailchimp.com/design/typography/ ## Demo https://github.com/twentyhq/twenty/assets/29927851/c731d883-1599-4281-87e3-0671f36994ae ## Invitation Email 
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class SendInviteLink {
|
||||
@Field(() => Boolean, {
|
||||
description: 'Boolean that confirms query was dispatched',
|
||||
})
|
||||
success: boolean;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { ArrayNotEmpty, IsArray, IsEmail } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class SendInviteLinkInput {
|
||||
@Field(() => [String])
|
||||
@IsArray()
|
||||
@ArrayNotEmpty()
|
||||
@IsEmail({}, { each: true })
|
||||
emails: string[];
|
||||
}
|
||||
@ -8,6 +8,8 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/billing.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
import { WorkspaceService } from './workspace.service';
|
||||
|
||||
@ -46,6 +48,14 @@ describe('WorkspaceService', () => {
|
||||
provide: BillingService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EmailService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import assert from 'assert';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SendInviteLinkEmail } from 'twenty-emails';
|
||||
import { render } from '@react-email/render';
|
||||
|
||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@ -13,6 +15,9 @@ import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/a
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/billing.service';
|
||||
import { SendInviteLink } from 'src/engine/core-modules/workspace/dtos/send-invite-link.entity';
|
||||
import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
constructor(
|
||||
@ -25,6 +30,8 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly billingService: BillingService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly emailService: EmailService,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
@ -92,6 +99,46 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
await this.reassignOrRemoveUserDefaultWorkspace(workspaceId, userId);
|
||||
}
|
||||
|
||||
async sendInviteLink(
|
||||
emails: string[],
|
||||
workspace: Workspace,
|
||||
sender: User,
|
||||
): Promise<SendInviteLink> {
|
||||
if (!workspace?.inviteHash) {
|
||||
return { success: false };
|
||||
}
|
||||
const frontBaseURL = this.environmentService.get('FRONT_BASE_URL');
|
||||
const inviteLink = `${frontBaseURL}/invite/${workspace.inviteHash}`;
|
||||
|
||||
for (const email of emails) {
|
||||
const emailData = {
|
||||
link: inviteLink,
|
||||
workspace: { name: workspace.displayName, logo: workspace.logo },
|
||||
sender: { email: sender.email, firstName: sender.firstName },
|
||||
};
|
||||
const emailTemplate = SendInviteLinkEmail(emailData);
|
||||
const html = render(emailTemplate, {
|
||||
pretty: true,
|
||||
});
|
||||
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
await this.emailService.send({
|
||||
from: `${this.environmentService.get(
|
||||
'EMAIL_FROM_NAME',
|
||||
)} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`,
|
||||
to: email,
|
||||
subject: 'Join your team on Twenty',
|
||||
text,
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
private async reassignOrRemoveUserDefaultWorkspace(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
|
||||
@ -25,6 +25,8 @@ import { BillingSubscription } from 'src/engine/core-modules/billing/entities/bi
|
||||
import { BillingService } from 'src/engine/core-modules/billing/billing.service';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import { SendInviteLink } from 'src/engine/core-modules/workspace/dtos/send-invite-link.entity';
|
||||
import { SendInviteLinkInput } from 'src/engine/core-modules/workspace/dtos/send-invite-link.input';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
|
||||
@ -122,4 +124,17 @@ export class WorkspaceResolver {
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => SendInviteLink)
|
||||
async sendInviteLink(
|
||||
@Args() sendInviteLinkInput: SendInviteLinkInput,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<SendInviteLink> {
|
||||
return await this.workspaceService.sendInviteLink(
|
||||
sendInviteLinkInput.emails,
|
||||
workspace,
|
||||
user,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user