diff --git a/apps/web/playwright/admin-users.e2e.ts b/apps/web/playwright/admin-users.e2e.ts new file mode 100644 index 0000000000..5b527b3565 --- /dev/null +++ b/apps/web/playwright/admin-users.e2e.ts @@ -0,0 +1,69 @@ +import { expect } from "@playwright/test"; + +import { test } from "./lib/fixtures"; + +test.describe.configure({ mode: "parallel" }); + +test.describe("Admin Users Management", () => { + test("should add a new user successfully", async ({ page, users }) => { + const adminUser = await users.create({ + role: "ADMIN", + }); + + await adminUser.apiLogin(); + + await page.goto("/settings/admin/users/add"); + + await page.waitForLoadState(); + + await page.fill('input[name="name"]', "Test User"); + await page.fill('input[name="username"]', "testuser"); + await page.fill('input[name="email"]', "testuser@example.com"); + await page.fill('input[name="bio"]', "Test user bio"); + + await page.locator('[data-testid="timezone-select"] div').first().click(); + + const responsePromise = page.waitForResponse((response) => + response.url().includes("/api/trpc/users/add") + ); + + await page.click('button[type="submit"]'); + + const response = await responsePromise; + expect(response.status()).toBe(200); + + await page.waitForURL("/settings/admin/users"); + }); + + test("should edit an existing user successfully", async ({ page, users }) => { + const adminUser = await users.create({ + role: "ADMIN", + }); + + const userToEdit = await users.create({ + name: "Edit User", + username: "edituser", + }); + + await adminUser.apiLogin(); + + await page.goto(`/settings/admin/users/${userToEdit.id}/edit`); + + await page.waitForLoadState(); + + await expect(page.locator('input[name="name"]')).toHaveValue("Edit User"); + + await page.fill('input[name="name"]', "Updated User"); + + const responsePromise = page.waitForResponse((response) => + response.url().includes("/api/trpc/users/update") + ); + + await page.click('button[type="submit"]'); + + const response = await responsePromise; + expect(response.status()).toBe(200); + + await page.waitForURL("/settings/admin/users"); + }); +}); diff --git a/packages/features/ee/users/components/UserForm.tsx b/packages/features/ee/users/components/UserForm.tsx index 8208b26079..87b2463fa2 100644 --- a/packages/features/ee/users/components/UserForm.tsx +++ b/packages/features/ee/users/components/UserForm.tsx @@ -78,14 +78,14 @@ export const UserForm = ({ const form = useForm({ defaultValues: { - avatarUrl: defaultValues?.avatarUrl, + avatarUrl: defaultValues?.avatarUrl || null, name: defaultValues?.name, username: defaultValues?.username, email: defaultValues?.email, bio: defaultValues?.bio, - theme: defaultValues?.theme, - defaultScheduleId: defaultValues?.defaultScheduleId, - allowDynamicBooking: defaultValues?.allowDynamicBooking, + theme: defaultValues?.theme || null, + defaultScheduleId: defaultValues?.defaultScheduleId || null, + allowDynamicBooking: defaultValues?.allowDynamicBooking ?? true, locale: { value: defaultLocale, label: new Intl.DisplayNames(defaultLocale, { type: "language" }).of(defaultLocale) || "", diff --git a/packages/features/ee/users/schemas/userBodySchema.ts b/packages/features/ee/users/schemas/userBodySchema.ts index 449e4d5975..c81726eb29 100644 --- a/packages/features/ee/users/schemas/userBodySchema.ts +++ b/packages/features/ee/users/schemas/userBodySchema.ts @@ -1,3 +1,4 @@ +import { optionToValueSchema } from "@calcom/prisma/zod-utils"; import { UserSchema } from "@calcom/prisma/zod/modelSchema/UserSchema"; export const userBodySchema = UserSchema.pick({ @@ -15,4 +16,10 @@ export const userBodySchema = UserSchema.pick({ locale: true, timeFormat: true, allowDynamicBooking: true, +}).extend({ + role: optionToValueSchema(UserSchema.shape.role), + identityProvider: optionToValueSchema(UserSchema.shape.identityProvider), + weekStart: optionToValueSchema(UserSchema.shape.weekStart), + locale: optionToValueSchema(UserSchema.shape.locale), + timeFormat: optionToValueSchema(UserSchema.shape.timeFormat), });