diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index fcdac429f9..c111be43dc 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -12,33 +12,28 @@ test.describe("Managed Event Types tests", () => { const memberUser = await users.create(); // First we work with owner user, logging in await adminUser.apiLogin(); - await page.goto("/event-types"); - // Making sure page loads completely - await page.waitForLoadState("networkidle"); + // Let's create a team - await page.goto("/teams"); + await page.goto("/settings/teams/new"); await test.step("Managed event option exists for team admin", async () => { - // Proceed to create a team - await page.locator("text=Create Team").click(); - await page.waitForURL("/settings/teams/new"); - // Filling team creation form wizard + await page.locator('input[name="name"]').waitFor(); await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`); await page.locator("text=Continue").click(); await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i); - await page.locator('[data-testid="new-member-button"]').click(); + await page.getByTestId("new-member-button").click(); await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`); - await page.locator('[data-testid="invite-new-member-button"]').click(); - await page.waitForLoadState("networkidle"); + await page.getByTestId("invite-new-member-button").click(); + // wait for the second member to be added to the pending-member-list. + await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor(); + // and publish await page.locator("text=Publish team").click(); - await page.waitForLoadState("networkidle"); - + await page.waitForURL("/settings/teams/**"); // Going to create an event type await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); - await page.click("[data-testid=new-event-type-dropdown]"); - await page.click("[data-testid=option-team-1]"); + await page.getByTestId("new-event-type-dropdown").click(); + await page.getByTestId("option-team-1").click(); // Expecting we can add a managed event type as team owner await expect(page.locator('button[value="MANAGED"]')).toBeVisible(); @@ -46,51 +41,50 @@ test.describe("Managed Event Types tests", () => { await page.click('button[value="MANAGED"]'); await page.fill("[name=title]", "managed"); await page.click("[type=submit]"); + + await page.waitForURL("event-types/**"); }); await test.step("Managed event type has unlocked fields for admin", async () => { - await page.waitForSelector('[data-testid="update-eventtype"]'); + await page.getByTestId("update-eventtype").waitFor(); await expect(page.locator('input[name="title"]')).toBeEditable(); await expect(page.locator('input[name="slug"]')).toBeEditable(); await expect(page.locator('input[name="length"]')).toBeEditable(); + await adminUser.logout(); }); await test.step("Managed event type exists for added member", async () => { // Now we need to accept the invitation as member and come back in as admin to // assign the member in the managed event type - await adminUser.logout(); await memberUser.apiLogin(); - await page.goto("/event-types"); - // We wait until loading is finished - await page.waitForSelector('[data-testid="event-types"]'); + await page.goto("/teams"); - await page.waitForLoadState("networkidle"); await page.locator('button[data-testid^="accept-invitation"]').click(); - await page.waitForLoadState("networkidle"); + await page.getByText("Member").waitFor(); + await memberUser.logout(); // Coming back as team owner to assign member user to managed event await adminUser.apiLogin(); await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); - await page.locator('[data-testid="event-types"] a[title="managed"]').click(); - await page.locator('[data-testid="vertical-tab-assignment"]').click(); + await page.getByTestId("event-types").locator('a[title="managed"]').click(); + await page.getByTestId("vertical-tab-assignment").click(); await page.locator('[class$="control"]').filter({ hasText: "Select..." }).click(); - await page.locator("#react-select-5-option-1").click(); + await page.getByTestId(`select-option-${memberUser.id}`).click(); await page.locator('[type="submit"]').click(); - await page.waitForLoadState("networkidle"); - await adminUser.logout(); + await page.getByTestId("toast-success").waitFor(); - // Coming back as member user to see if there is a managed event present after assignment - await memberUser.apiLogin(); - await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); - await expect(page.locator('[data-testid="event-types"] a[title="managed"]')).toBeVisible(); + await adminUser.logout(); }); await test.step("Managed event type has locked fields for added member", async () => { - page.locator('[data-testid="event-types"] a[title="managed"]').click(); - await page.waitForLoadState("networkidle"); + // Coming back as member user to see if there is a managed event present after assignment + await memberUser.apiLogin(); + await page.goto("/event-types"); + + await page.getByTestId("event-types").locator('a[title="managed"]').click(); + await page.waitForURL("event-types/**"); + await expect(page.locator('input[name="title"]')).not.toBeEditable(); await expect(page.locator('input[name="slug"]')).not.toBeEditable(); await expect(page.locator('input[name="length"]')).not.toBeEditable(); diff --git a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts index c99fc6acab..7dfaf94a0a 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -463,7 +463,7 @@ async function addAllTypesOfFieldsAndSaveForm( // Click on the field type dropdown. await page.locator(".data-testid-field-type").nth(nth).click(); // Click on the dropdown option. - await page.locator(`[data-testid="select-option-${fieldTypeLabel}"]`).click(); + await page.locator(`[data-testid^="select-option-"]`).filter({ hasText: fieldTypeLabel }).click(); } else { // Set the identifier manually for the first field to test out a case when identifier isn't computed from label automatically // First field type is by default selected. So, no need to choose from dropdown diff --git a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts index 898b4bea67..b86cb4da03 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts @@ -45,13 +45,39 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { // Extract this from the input so it doesn't get saved in the db // eslint-disable-next-line userId, - // eslint-disable-next-line - teamId, bookingFields, offsetStart, ...rest } = input; + const eventType = await ctx.prisma.eventType.findUniqueOrThrow({ + where: { id }, + select: { + children: { + select: { + userId: true, + }, + }, + workflows: { + select: { + workflowId: true, + }, + }, + team: { + select: { + name: true, + id: true, + }, + }, + }, + }); + + if (input.teamId && eventType.team?.id && input.teamId !== eventType.team.id) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const teamId = input.teamId || eventType.team?.id; + ensureUniqueBookingFields(bookingFields); const data: Prisma.EventTypeUpdateInput = { @@ -149,7 +175,23 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }; } - if (hosts) { + if (teamId && hosts) { + // check if all hosts can be assigned (memberships that have accepted invite) + const memberships = + (await ctx.prisma.membership.findMany({ + where: { + teamId, + accepted: true, + }, + })) || []; + const teamMemberIds = memberships.map((membership) => membership.userId); + // guard against missing IDs, this may mean a member has just been removed + // or this request was forged. + if (!hosts.every((host) => teamMemberIds.includes(host.userId))) { + throw new TRPCError({ + code: "FORBIDDEN", + }); + } data.hosts = { deleteMany: {}, create: hosts.map((host) => ({ @@ -256,48 +298,26 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }); } } - const [oldEventType, eventType] = await ctx.prisma.$transaction([ - ctx.prisma.eventType.findFirst({ - where: { id }, - select: { - children: { - select: { - userId: true, - }, - }, - workflows: { - select: { - workflowId: true, - }, - }, - team: { - select: { - name: true, - }, - }, - }, - }), - ctx.prisma.eventType.update({ - where: { id }, - data, - }), - ]); + const updatedEventType = await ctx.prisma.eventType.update({ + where: { id }, + data, + }); // Handling updates to children event types (managed events types) await updateChildrenEventTypes({ eventTypeId: id, currentUserId: ctx.user.id, - oldEventType, + oldEventType: eventType, hashedLink, connectedLink, - updatedEventType: eventType, + updatedEventType, children, prisma: ctx.prisma, }); const res = ctx.res as NextApiResponse; if (typeof res?.revalidate !== "undefined") { try { - await res?.revalidate(`/${ctx.user.username}/${eventType.slug}`); + await res?.revalidate(`/${ctx.user.username}/${updatedEventType.slug}`); } catch (e) { // if reach this it is because the event type page has not been created, so it is not possible to revalidate it logger.debug((e as Error)?.message); diff --git a/packages/ui/components/form/select/components.tsx b/packages/ui/components/form/select/components.tsx index 3a5a4462d0..f555f36b69 100644 --- a/packages/ui/components/form/select/components.tsx +++ b/packages/ui/components/form/select/components.tsx @@ -43,8 +43,8 @@ export const OptionComponent = < // This gets styled in the select classNames prop now - handles overrides with styles vs className here doesnt
- - {props.label} + + {props.label || <> } {(props.data as unknown as ExtendedOption).needsUpgrade && } {props.isSelected && }