fix: A user joining from invite link of a team doesn't automatically become member of the org (#12774)
* fix: Add org membership when invite link for a team in an org is generated * Run tests sequentially till we fix emails fixture
This commit is contained in:
parent
7d2500a32f
commit
a28e8ff39b
|
@ -365,7 +365,7 @@ test.describe("Booking round robin event", () => {
|
|||
teammates: teamMatesObj,
|
||||
}
|
||||
);
|
||||
const team = await testUser.getFirstTeam();
|
||||
const team = await testUser.getFirstTeamMembership();
|
||||
await page.goto(`/team/${team.team.slug}`);
|
||||
});
|
||||
|
||||
|
@ -373,7 +373,7 @@ test.describe("Booking round robin event", () => {
|
|||
const [testUser] = users.get();
|
||||
testUser.apiLogin();
|
||||
|
||||
const team = await testUser.getFirstTeam();
|
||||
const team = await testUser.getFirstTeamMembership();
|
||||
|
||||
// Click first event type (round robin)
|
||||
await page.click('[data-testid="event-type-link"]');
|
||||
|
|
|
@ -458,7 +458,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
|||
logout: async () => {
|
||||
await page.goto("/auth/logout");
|
||||
},
|
||||
getFirstTeam: async () => {
|
||||
getFirstTeamMembership: async () => {
|
||||
const memberships = await prisma.membership.findMany({
|
||||
where: { userId: user.id },
|
||||
include: { team: true },
|
||||
|
|
|
@ -84,7 +84,7 @@ test.describe("Stripe integration", () => {
|
|||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
});
|
||||
await owner.apiLogin();
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
const teamEvent = await owner.getFirstTeamEvent(team.id);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { Browser, Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
@ -13,169 +14,435 @@ test.afterEach(async ({ users, emails }) => {
|
|||
emails?.deleteAll();
|
||||
});
|
||||
|
||||
test.describe("Organization", () => {
|
||||
test("Invitation (non verified)", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await test.step("To the organization by email (external user)", async () => {
|
||||
const invitedUserEmail = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`;
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
||||
await page.locator('button:text("Send invite")').click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
// Check newly invited member exists and is pending
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(1);
|
||||
|
||||
assertInviteLink(inviteLink);
|
||||
|
||||
// Follow invite link in new window
|
||||
const context = await browser.newContext();
|
||||
const signupPage = await context.newPage();
|
||||
signupPage.goto(inviteLink);
|
||||
await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toBeDisabled();
|
||||
await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toBeDisabled();
|
||||
await signupPage.waitForLoadState("networkidle");
|
||||
|
||||
// Check required fields
|
||||
await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await signupPage.locator("button[type=submit]").click();
|
||||
await signupPage.waitForURL("/getting-started?from=signup");
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await context.close();
|
||||
await signupPage.close();
|
||||
|
||||
// Check newly invited member is not pending anymore
|
||||
await page.bringToFront();
|
||||
test.describe.serial("Organization", () => {
|
||||
test.describe("Email not matching orgAutoAcceptEmail", () => {
|
||||
test("Org Invitation", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
page.locator(`[data-testid="login-form"]`);
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(0);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await test.step("By email", async () => {
|
||||
const invitedUserEmail = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`;
|
||||
await inviteAnEmail(page, invitedUserEmail);
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: false,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
assertInviteLink(inviteLink);
|
||||
await signupFromEmailInviteLink(browser, inviteLink);
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("By invite link", async () => {
|
||||
const inviteLink = await copyInviteLink(page);
|
||||
const email = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`;
|
||||
await signupFromInviteLink({ browser, inviteLink, email });
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("To the organization by invite link", async () => {
|
||||
// Get the invite link
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
|
||||
test("Team invitation", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, hasSubteam: true });
|
||||
await orgOwner.apiLogin();
|
||||
const { team } = await orgOwner.getFirstTeamMembership();
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
|
||||
const inviteLink = await getInviteLink(page);
|
||||
// Follow invite link in new window
|
||||
const context = await browser.newContext();
|
||||
const inviteLinkPage = await context.newPage();
|
||||
await inviteLinkPage.goto(inviteLink);
|
||||
await inviteLinkPage.waitForLoadState("networkidle");
|
||||
await test.step("By email", async () => {
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
const invitedUserEmail = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`;
|
||||
await inviteAnEmail(page, invitedUserEmail);
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
page,
|
||||
teamId: team.id,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: false,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
// Check required fields
|
||||
const button = inviteLinkPage.locator("button[type=submit][disabled]");
|
||||
await expect(button).toBeVisible(); // email + 3 password hints
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: false,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
// Happy path
|
||||
const email = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`;
|
||||
await inviteLinkPage.locator("input[name=email]").fill(email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await inviteLinkPage.waitForURL("/getting-started");
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await page.waitForLoadState("networkidle");
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${team.name}'s admin invited you to join the team ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
assertInviteLink(inviteLink);
|
||||
|
||||
await signupFromEmailInviteLink(browser, inviteLink);
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
page,
|
||||
teamId: team.id,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("By invite link", async () => {
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
const inviteLink = await copyInviteLink(page);
|
||||
const email = `rick-${Date.now()}@domain.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`;
|
||||
await signupFromInviteLink({ browser, inviteLink, email });
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
teamId: team.id,
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: email,
|
||||
});
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: email,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("Invitation (verified)", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await test.step("To the organization by email (internal user)", async () => {
|
||||
const invitedUserEmail = `rick-${Date.now()}@example.com`;
|
||||
const usernameDerivedFromEmail = invitedUserEmail.split("@")[0];
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
||||
await page.locator('button:text("Send invite")').click();
|
||||
test.describe("Email matching orgAutoAcceptEmail and a Verified Organization", () => {
|
||||
test("Org Invitation", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.waitForLoadState("networkidle");
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
assertInviteLink(inviteLink);
|
||||
await test.step("By email", async () => {
|
||||
const invitedUserEmail = `rick-${Date.now()}@example.com`;
|
||||
const usernameDerivedFromEmail = invitedUserEmail.split("@")[0];
|
||||
await inviteAnEmail(page, invitedUserEmail);
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
// Check newly invited member exists and is not pending
|
||||
await expect(
|
||||
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
|
||||
).toHaveCount(0);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
// Follow invite link in new window
|
||||
const context = await browser.newContext();
|
||||
const signupPage = await context.newPage();
|
||||
signupPage.goto(inviteLink);
|
||||
await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toBeDisabled();
|
||||
await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toBeDisabled();
|
||||
await signupPage.waitForLoadState("networkidle");
|
||||
assertInviteLink(inviteLink);
|
||||
await signupFromEmailInviteLink(browser, inviteLink);
|
||||
|
||||
// Check required fields
|
||||
await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await signupPage.locator("button[type=submit]").click();
|
||||
await signupPage.waitForURL("/getting-started?from=signup");
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await context.close();
|
||||
await signupPage.close();
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("By invite link", async () => {
|
||||
const inviteLink = await copyInviteLink(page);
|
||||
const email = `rick-${Date.now()}@example.com`;
|
||||
const usernameDerivedFromEmail = email.split("@")[0];
|
||||
await signupFromInviteLink({ browser, inviteLink, email });
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("To the organization by invite link", async () => {
|
||||
// Get the invite link
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
|
||||
test("Team Invitation", async ({ browser, page, users, emails }) => {
|
||||
const orgOwner = await users.create(undefined, {
|
||||
hasTeam: true,
|
||||
isOrg: true,
|
||||
hasSubteam: true,
|
||||
isOrgVerified: true,
|
||||
});
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
const { team } = await orgOwner.getFirstTeamMembership();
|
||||
|
||||
const inviteLink = await getInviteLink(page);
|
||||
// Follow invite link in new window
|
||||
const context = await browser.newContext();
|
||||
const inviteLinkPage = await context.newPage();
|
||||
await inviteLinkPage.goto(inviteLink);
|
||||
await inviteLinkPage.waitForLoadState("networkidle");
|
||||
await orgOwner.apiLogin();
|
||||
|
||||
// Check required fields
|
||||
const button = inviteLinkPage.locator("button[type=submit][disabled]");
|
||||
await expect(button).toBeVisible(); // email + 3 password hints
|
||||
await test.step("By email", async () => {
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
const invitedUserEmail = `rick-${Date.now()}@example.com`;
|
||||
const usernameDerivedFromEmail = invitedUserEmail.split("@")[0];
|
||||
await inviteAnEmail(page, invitedUserEmail);
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
page,
|
||||
teamId: team.id,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
// Happy path
|
||||
const email = `rick-${Date.now()}@example.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${email.split("@")[0]}`;
|
||||
await inviteLinkPage.locator("input[name=email]").fill(email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await inviteLinkPage.waitForURL("/getting-started");
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
const inviteLink = await expectInvitationEmailToBeReceived(
|
||||
page,
|
||||
emails,
|
||||
invitedUserEmail,
|
||||
`${team.name}'s admin invited you to join the organization ${org.name} on Cal.com`,
|
||||
"signup?token"
|
||||
);
|
||||
|
||||
assertInviteLink(inviteLink);
|
||||
|
||||
await signupFromEmailInviteLink(browser, inviteLink);
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
page,
|
||||
teamId: team.id,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: invitedUserEmail,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("By invite link", async () => {
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
|
||||
const inviteLink = await copyInviteLink(page);
|
||||
const email = `rick-${Date.now()}@example.com`;
|
||||
// '-domain' because the email doesn't match orgAutoAcceptEmail
|
||||
const usernameDerivedFromEmail = `${email.split("@")[0]}`;
|
||||
|
||||
await signupFromInviteLink({ browser, inviteLink, email });
|
||||
|
||||
const dbUser = await prisma.user.findUnique({ where: { email } });
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfTeam({
|
||||
teamId: team.id,
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: email,
|
||||
});
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
email: email,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function signupFromInviteLink({
|
||||
browser,
|
||||
inviteLink,
|
||||
email,
|
||||
}: {
|
||||
browser: Browser;
|
||||
inviteLink: string;
|
||||
email: string;
|
||||
}) {
|
||||
const context = await browser.newContext();
|
||||
const inviteLinkPage = await context.newPage();
|
||||
await inviteLinkPage.goto(inviteLink);
|
||||
await inviteLinkPage.waitForLoadState("networkidle");
|
||||
|
||||
// Check required fields
|
||||
const button = inviteLinkPage.locator("button[type=submit][disabled]");
|
||||
await expect(button).toBeVisible(); // email + 3 password hints
|
||||
|
||||
await inviteLinkPage.locator("input[name=email]").fill(email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await inviteLinkPage.waitForURL("/getting-started");
|
||||
return { email };
|
||||
}
|
||||
|
||||
async function signupFromEmailInviteLink(browser: Browser, inviteLink: string) {
|
||||
// Follow invite link in new window
|
||||
const context = await browser.newContext();
|
||||
const signupPage = await context.newPage();
|
||||
|
||||
signupPage.goto(inviteLink);
|
||||
await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toBeDisabled();
|
||||
await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toBeDisabled();
|
||||
await signupPage.waitForLoadState("networkidle");
|
||||
// Check required fields
|
||||
await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await signupPage.locator("button[type=submit]").click();
|
||||
await signupPage.waitForURL("/getting-started?from=signup");
|
||||
await context.close();
|
||||
await signupPage.close();
|
||||
}
|
||||
|
||||
async function inviteAnEmail(page: Page, invitedUserEmail: string) {
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
|
||||
await page.locator('button:text("Send invite")').click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
}
|
||||
|
||||
async function expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
username,
|
||||
email,
|
||||
role,
|
||||
isMemberShipAccepted,
|
||||
}: {
|
||||
page: Page;
|
||||
username: string;
|
||||
role: string;
|
||||
isMemberShipAccepted: boolean;
|
||||
email: string;
|
||||
}) {
|
||||
// Check newly invited member is not pending anymore
|
||||
await page.goto("/settings/organizations/members");
|
||||
expect(await page.locator(`[data-testid="member-${username}-username"]`).textContent()).toBe(username);
|
||||
expect(await page.locator(`[data-testid="member-${username}-email"]`).textContent()).toBe(email);
|
||||
expect((await page.locator(`[data-testid="member-${username}-role"]`).textContent())?.toLowerCase()).toBe(
|
||||
role.toLowerCase()
|
||||
);
|
||||
if (isMemberShipAccepted) {
|
||||
await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeHidden();
|
||||
} else {
|
||||
await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
async function expectUserToBeAMemberOfTeam({
|
||||
page,
|
||||
teamId,
|
||||
email,
|
||||
role,
|
||||
username,
|
||||
isMemberShipAccepted,
|
||||
}: {
|
||||
page: Page;
|
||||
username: string;
|
||||
role: string;
|
||||
teamId: number;
|
||||
isMemberShipAccepted: boolean;
|
||||
email: string;
|
||||
}) {
|
||||
// Check newly invited member is not pending anymore
|
||||
await page.goto(`/settings/teams/${teamId}/members`);
|
||||
expect(
|
||||
(
|
||||
await page.locator(`[data-testid="member-${username}"] [data-testid=member-role]`).textContent()
|
||||
)?.toLowerCase()
|
||||
).toBe(role.toLowerCase());
|
||||
if (isMemberShipAccepted) {
|
||||
await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeHidden();
|
||||
} else {
|
||||
await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function assertInviteLink(inviteLink: string | null | undefined): asserts inviteLink is string {
|
||||
if (!inviteLink) throw new Error("Invite link not found");
|
||||
}
|
||||
|
||||
async function copyInviteLink(page: Page) {
|
||||
await page.locator('button:text("Add")').click();
|
||||
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
|
||||
const inviteLink = await getInviteLink(page);
|
||||
return inviteLink;
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ test.describe("Signup Flow Test", async () => {
|
|||
|
||||
const t = await localize("en");
|
||||
const teamOwner = await users.create(undefined, { hasTeam: true });
|
||||
const { team } = await teamOwner.getFirstTeam();
|
||||
const { team } = await teamOwner.getFirstTeamMembership();
|
||||
await teamOwner.apiLogin();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
|
|
@ -17,7 +17,7 @@ test.describe("Team", () => {
|
|||
test("Invitation (non verified)", async ({ browser, page, users, emails }) => {
|
||||
const t = await localize("en");
|
||||
const teamOwner = await users.create(undefined, { hasTeam: true });
|
||||
const { team } = await teamOwner.getFirstTeam();
|
||||
const { team } = await teamOwner.getFirstTeamMembership();
|
||||
await teamOwner.apiLogin();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
@ -98,7 +98,7 @@ test.describe("Team", () => {
|
|||
test("Invitation (verified)", async ({ browser, page, users, emails }) => {
|
||||
const t = await localize("en");
|
||||
const teamOwner = await users.create({ name: `team-owner-${Date.now()}` }, { hasTeam: true });
|
||||
const { team } = await teamOwner.getFirstTeam();
|
||||
const { team } = await teamOwner.getFirstTeamMembership();
|
||||
await teamOwner.apiLogin();
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
|
|
@ -22,7 +22,7 @@ test.describe("Teams - NonOrg", () => {
|
|||
|
||||
test("Team Onboarding Invite Members", async ({ page, users }) => {
|
||||
const user = await users.create(undefined, { hasTeam: true });
|
||||
const { team } = await user.getFirstTeam();
|
||||
const { team } = await user.getFirstTeamMembership();
|
||||
const inviteeEmail = `${user.username}+invitee@example.com`;
|
||||
|
||||
await user.apiLogin();
|
||||
|
@ -80,7 +80,7 @@ test.describe("Teams - NonOrg", () => {
|
|||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
}
|
||||
);
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
await page.goto(`/team/${team.slug}/${teamEventSlug}`);
|
||||
|
@ -118,7 +118,7 @@ test.describe("Teams - NonOrg", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
await page.goto(`/team/${team.slug}/${teamEventSlug}`);
|
||||
|
@ -235,7 +235,7 @@ test.describe("Teams - NonOrg", () => {
|
|||
);
|
||||
|
||||
await owner.apiLogin();
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
|
||||
// Mark team as private
|
||||
await page.goto(`/settings/teams/${team.id}/members`);
|
||||
|
@ -348,7 +348,7 @@ test.describe("Teams - Org", () => {
|
|||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
}
|
||||
);
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
await page.goto(`/team/${team.slug}/${teamEventSlug}`);
|
||||
|
@ -397,7 +397,7 @@ test.describe("Teams - Org", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
await page.goto(`/team/${team.slug}/${teamEventSlug}`);
|
||||
|
@ -448,7 +448,7 @@ test.describe("Teams - Org", () => {
|
|||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
}
|
||||
);
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
|
||||
const teamSlugUpperCase = team.slug?.toUpperCase();
|
||||
|
|
|
@ -18,7 +18,7 @@ test.afterAll(async ({ users }) => {
|
|||
test.describe("Unpublished", () => {
|
||||
test("Regular team profile", async ({ page, users }) => {
|
||||
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true });
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { requestedSlug } = team.metadata as { requestedSlug: string };
|
||||
await page.goto(`/team/${requestedSlug}`);
|
||||
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
|
||||
|
@ -33,7 +33,7 @@ test.describe("Unpublished", () => {
|
|||
isUnpublished: true,
|
||||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
});
|
||||
const { team } = await owner.getFirstTeam();
|
||||
const { team } = await owner.getFirstTeamMembership();
|
||||
const { requestedSlug } = team.metadata as { requestedSlug: string };
|
||||
const { slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
|
||||
await page.goto(`/team/${requestedSlug}/${teamEventSlug}`);
|
||||
|
|
|
@ -4,6 +4,7 @@ import stripe from "@calcom/app-store/stripepayment/lib/server";
|
|||
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
|
||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
|
||||
import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getLocaleFromRequest } from "@calcom/lib/getLocaleFromRequest";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
@ -12,7 +13,7 @@ import { createWebUser as syncServicesCreateWebUser } from "@calcom/lib/sync/Syn
|
|||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { signupSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { joinAnyChildTeamOnOrgInvite } from "../utils/organization";
|
||||
|
@ -147,32 +148,10 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
|
|||
});
|
||||
|
||||
// Wrapping in a transaction as if one fails we want to rollback the whole thing to preventa any data inconsistencies
|
||||
const membership = await prisma.$transaction(async (tx) => {
|
||||
if (teamMetadata?.isOrganization) {
|
||||
await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
organizationId: team.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
const membership = await tx.membership.upsert({
|
||||
where: {
|
||||
userId_teamId: { userId: user.id, teamId: team.id },
|
||||
},
|
||||
update: {
|
||||
accepted: true,
|
||||
},
|
||||
create: {
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
return membership;
|
||||
const { membership } = await createOrUpdateMemberships({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
});
|
||||
|
||||
closeComUpsertTeamUser(team, user, membership.role);
|
||||
|
|
|
@ -3,13 +3,14 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import { checkPremiumUsername } from "@calcom/ee/common/lib/checkPremiumUsername";
|
||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
|
||||
import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships";
|
||||
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { signupSchema } from "@calcom/prisma/zod-utils";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
|
@ -86,32 +87,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
});
|
||||
|
||||
const membership = await prisma.$transaction(async (tx) => {
|
||||
if (teamMetadata?.isOrganization) {
|
||||
await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
organizationId: team.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
const membership = await tx.membership.upsert({
|
||||
where: {
|
||||
userId_teamId: { userId: user.id, teamId: team.id },
|
||||
},
|
||||
update: {
|
||||
accepted: true,
|
||||
},
|
||||
create: {
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
return membership;
|
||||
const { membership } = await createOrUpdateMemberships({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
});
|
||||
|
||||
closeComUpsertTeamUser(team, user, membership.role);
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import type z from "zod";
|
||||
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const createOrUpdateMemberships = async ({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
}: {
|
||||
user: Pick<User, "id">;
|
||||
team: Pick<Team, "id" | "parentId">;
|
||||
teamMetadata: z.infer<typeof teamMetadataSchema>;
|
||||
}) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
if (teamMetadata?.isOrganization) {
|
||||
await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
organizationId: team.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
const membership = await tx.membership.upsert({
|
||||
where: {
|
||||
userId_teamId: { userId: user.id, teamId: team.id },
|
||||
},
|
||||
update: {
|
||||
accepted: true,
|
||||
},
|
||||
create: {
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
const orgMembership = null;
|
||||
if (team.parentId) {
|
||||
await tx.membership.upsert({
|
||||
where: {
|
||||
userId_teamId: { userId: user.id, teamId: team.parentId },
|
||||
},
|
||||
update: {
|
||||
accepted: true,
|
||||
},
|
||||
create: {
|
||||
userId: user.id,
|
||||
teamId: team.parentId,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
return { membership, orgMembership };
|
||||
});
|
||||
};
|
|
@ -146,11 +146,15 @@ export default function MemberListItem(props: Props) {
|
|||
<div className="flex">
|
||||
<UserAvatar size="sm" user={props.member} className="h-10 w-10 rounded-full" />
|
||||
<div className="ms-3 inline-block">
|
||||
<div className="mb-1 flex">
|
||||
<span className="text-default mr-2 text-sm font-bold leading-4">{name}</span>
|
||||
{!props.member.accepted && <TeamPill color="orange" text={t("pending")} />}
|
||||
<div className="mb-1 flex" data-testid={`member-${props.member.username}`}>
|
||||
<span data-testid="member-name" className="text-default mr-2 text-sm font-bold leading-4">
|
||||
{name}
|
||||
</span>
|
||||
{!props.member.accepted && (
|
||||
<TeamPill data-testid="member-pending" color="orange" text={t("pending")} />
|
||||
)}
|
||||
{isAdmin && props.member.accepted && appList}
|
||||
{props.member.role && <TeamRole role={props.member.role} />}
|
||||
{props.member.role && <TeamRole data-testid="member-role" role={props.member.role} />}
|
||||
</div>
|
||||
<div className="text-default flex items-center">
|
||||
<span
|
||||
|
|
|
@ -5,31 +5,38 @@ import { MembershipRole } from "@calcom/prisma/enums";
|
|||
|
||||
type PillColor = "blue" | "green" | "red" | "orange";
|
||||
|
||||
interface Props {
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
text: string;
|
||||
color?: PillColor;
|
||||
}
|
||||
|
||||
export default function TeamPill(props: Props) {
|
||||
const { color, text, ...rest } = props;
|
||||
return (
|
||||
<div
|
||||
className={classNames("text-medium self-center rounded-md px-1 py-0.5 text-xs ltr:mr-1 rtl:ml-1", {
|
||||
" bg-subtle text-emphasis": !props.color,
|
||||
" bg-info text-info": props.color === "blue",
|
||||
" bg-error text-error ": props.color === "red",
|
||||
" bg-attention text-attention": props.color === "orange",
|
||||
})}>
|
||||
{props.text}
|
||||
" bg-subtle text-emphasis": !color,
|
||||
" bg-info text-info": color === "blue",
|
||||
" bg-error text-error ": color === "red",
|
||||
" bg-attention text-attention": color === "orange",
|
||||
})}
|
||||
{...rest}>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TeamRole(props: { role: MembershipRole }) {
|
||||
interface TeamRoleProps extends Omit<React.ComponentProps<typeof TeamPill>, "text"> {
|
||||
role: MembershipRole;
|
||||
}
|
||||
|
||||
export function TeamRole(props: TeamRoleProps) {
|
||||
const { t } = useLocale();
|
||||
const { role, ...rest } = props;
|
||||
const keys: Record<MembershipRole, PillColor | undefined> = {
|
||||
[MembershipRole.OWNER]: "blue",
|
||||
[MembershipRole.ADMIN]: "red",
|
||||
[MembershipRole.MEMBER]: undefined,
|
||||
};
|
||||
return <TeamPill text={t(props.role.toLowerCase())} color={keys[props.role]} />;
|
||||
return <TeamPill text={t(role.toLowerCase())} color={keys[role]} {...rest} />;
|
||||
}
|
||||
|
|
|
@ -166,10 +166,16 @@ export function UserListTable() {
|
|||
<div className="flex items-center gap-2">
|
||||
<Avatar size="sm" alt={username || email} imageSrc={`${domain}/${username}/avatar.png`} />
|
||||
<div className="">
|
||||
<div className="text-emphasis text-sm font-medium leading-none">
|
||||
<div
|
||||
data-testid={`member-${username}-username`}
|
||||
className="text-emphasis text-sm font-medium leading-none">
|
||||
{username || "No username"}
|
||||
</div>
|
||||
<div className="text-subtle mt-1 text-sm leading-none">{email}</div>
|
||||
<div
|
||||
data-testid={`member-${username}-email`}
|
||||
className="text-subtle mt-1 text-sm leading-none">
|
||||
{email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -185,9 +191,10 @@ export function UserListTable() {
|
|||
accessorFn: (data) => data.role,
|
||||
header: "Role",
|
||||
cell: ({ row, table }) => {
|
||||
const { role } = row.original;
|
||||
const { role, username } = row.original;
|
||||
return (
|
||||
<Badge
|
||||
data-testid={`member-${username}-role`}
|
||||
variant={role === "MEMBER" ? "gray" : "blue"}
|
||||
onClick={() => {
|
||||
table.getColumn("role")?.setFilterValue([role]);
|
||||
|
@ -204,12 +211,13 @@ export function UserListTable() {
|
|||
id: "teams",
|
||||
header: "Teams",
|
||||
cell: ({ row }) => {
|
||||
const { teams, accepted, email } = row.original;
|
||||
const { teams, accepted, email, username } = row.original;
|
||||
// TODO: Implement click to filter
|
||||
return (
|
||||
<div className="flex h-full flex-wrap items-center gap-2">
|
||||
{accepted ? null : (
|
||||
<Badge
|
||||
data-testid2={`member-${username}-pending`}
|
||||
variant="red"
|
||||
className="text-xs"
|
||||
data-testid={`email-${email.replace("@", "")}-pending`}>
|
||||
|
|
|
@ -95,13 +95,22 @@ export const validateAndGetCorrectedUsernameInTeam = async (
|
|||
select: {
|
||||
metadata: true,
|
||||
parentId: true,
|
||||
parent: {
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log("validateAndGetCorrectedUsernameInTeam", {
|
||||
teamId,
|
||||
team,
|
||||
});
|
||||
const teamData = { ...team, metadata: teamMetadataSchema.parse(team?.metadata) };
|
||||
|
||||
if (teamData.metadata?.isOrganization || teamData.parentId) {
|
||||
const orgMetadata = teamData.metadata;
|
||||
const organization = teamData.metadata?.isOrganization ? teamData : teamData.parent;
|
||||
if (organization) {
|
||||
const orgMetadata = teamMetadataSchema.parse(organization.metadata);
|
||||
// Organization context -> org-context username check
|
||||
const orgId = teamData.parentId || teamId;
|
||||
return validateAndGetCorrectedUsernameAndEmail({
|
||||
|
|
|
@ -7,7 +7,6 @@ import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { getMembersHandler } from "../organizations/getMembers.handler";
|
||||
import type { TCreateInviteInputSchema } from "./createInvite.schema";
|
||||
|
||||
type CreateInviteOptions = {
|
||||
|
@ -23,11 +22,7 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) =
|
|||
|
||||
if (!membership || !membership?.team) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const teamMetadata = teamMetadataSchema.parse(membership.team.metadata);
|
||||
const isOrg = !!(membership.team?.parentId === null && teamMetadata?.isOrganization);
|
||||
const orgMembers = await getMembersHandler({
|
||||
ctx,
|
||||
input: { teamIdToExclude: teamId, distinctUser: true },
|
||||
});
|
||||
const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || teamMetadata?.isOrganization);
|
||||
|
||||
if (input.token) {
|
||||
const existingToken = await prisma.verificationToken.findFirst({
|
||||
|
@ -36,7 +31,7 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) =
|
|||
if (!existingToken) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
return {
|
||||
token: existingToken.token,
|
||||
inviteLink: await getInviteLink(existingToken.token, isOrg, orgMembers?.length),
|
||||
inviteLink: await getInviteLink(existingToken.token, isOrganizationOrATeamInOrganization),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,13 +45,13 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) =
|
|||
},
|
||||
});
|
||||
|
||||
return { token, inviteLink: await getInviteLink(token, isOrg, orgMembers?.length) };
|
||||
return { token, inviteLink: await getInviteLink(token, isOrganizationOrATeamInOrganization) };
|
||||
};
|
||||
|
||||
async function getInviteLink(token = "", isOrg = false, orgMembers = 0) {
|
||||
async function getInviteLink(token = "", isOrgContext = false) {
|
||||
const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`;
|
||||
const orgInviteLink = `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/getting-started`;
|
||||
if (isOrg || orgMembers > 0) return orgInviteLink;
|
||||
if (isOrgContext) return orgInviteLink;
|
||||
return teamInviteLink;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user