diff --git a/packages/e2e-tests/fixtures/admin/create-user-page.js b/packages/e2e-tests/fixtures/admin/create-user-page.js index 135b38fb..ddf0f6e6 100644 --- a/packages/e2e-tests/fixtures/admin/create-user-page.js +++ b/packages/e2e-tests/fixtures/admin/create-user-page.js @@ -1,3 +1,5 @@ +const { expect } = require('@playwright/test'); + const { faker } = require('@faker-js/faker'); const { AuthenticatedPage } = require('../authenticated-page'); @@ -11,11 +13,17 @@ export class AdminCreateUserPage extends AuthenticatedPage { super(page); this.fullNameInput = page.getByTestId('full-name-input'); this.emailInput = page.getByTestId('email-input'); - this.roleInput = page.getByTestId('role.id-autocomplete'); + this.roleInput = page.getByTestId('roleId-autocomplete'); this.createButton = page.getByTestId('create-button'); this.pageTitle = page.getByTestId('create-user-title'); - this.invitationEmailInfoAlert = page.getByTestId('invitation-email-info-alert'); - this.acceptInvitationLink = page.getByTestId('invitation-email-info-alert').getByRole('link'); + this.invitationEmailInfoAlert = page.getByTestId( + 'invitation-email-info-alert' + ); + this.acceptInvitationLink = page + .getByTestId('invitation-email-info-alert') + .getByRole('link'); + this.createUserSuccessAlert = page.getByTestId('create-user-success-alert'); + this.fieldError = page.locator('p[id$="-helper-text"]'); } seed(seed) { @@ -28,4 +36,8 @@ export class AdminCreateUserPage extends AuthenticatedPage { email: faker.internet.email().toLowerCase(), }; } + + async expectCreateUserSuccessAlertToBeVisible() { + await expect(this.createUserSuccessAlert).toBeVisible(); + } } diff --git a/packages/e2e-tests/tests/admin/manage-roles.spec.js b/packages/e2e-tests/tests/admin/manage-roles.spec.js index 00299c5d..1e9a405f 100644 --- a/packages/e2e-tests/tests/admin/manage-roles.spec.js +++ b/packages/e2e-tests/tests/admin/manage-roles.spec.js @@ -35,9 +35,8 @@ test.describe('Role management page', () => { await adminCreateRolePage.closeSnackbar(); }); - let roleRow = await test.step( - 'Make sure role data is correct', - async () => { + let roleRow = + await test.step('Make sure role data is correct', async () => { const roleRow = await adminRolesPage.getRoleRowByName( 'Create Edit Test' ); @@ -48,8 +47,7 @@ test.describe('Role management page', () => { await expect(roleData.canEdit).toBe(true); await expect(roleData.canDelete).toBe(true); return roleRow; - } - ); + }); await test.step('Edit the role', async () => { await adminRolesPage.clickEditRole(roleRow); @@ -67,9 +65,8 @@ test.describe('Role management page', () => { await adminEditRolePage.closeSnackbar(); }); - roleRow = await test.step( - 'Make sure changes reflected on roles page', - async () => { + roleRow = + await test.step('Make sure changes reflected on roles page', async () => { await adminRolesPage.isMounted(); const roleRow = await adminRolesPage.getRoleRowByName( 'Create Update Test' @@ -81,8 +78,7 @@ test.describe('Role management page', () => { await expect(roleData.canEdit).toBe(true); await expect(roleData.canDelete).toBe(true); return roleRow; - } - ); + }); await test.step('Delete the role', async () => { await adminRolesPage.clickDeleteRole(roleRow); @@ -184,49 +180,39 @@ test.describe('Role management page', () => { await expect(snackbar.variant).toBe('success'); await adminCreateRolePage.closeSnackbar(); }); - await test.step( - 'Create a new user with the "Delete Role" role', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill('User Role Test'); - await adminCreateUserPage.emailInput.fill( - 'user-role-test@automatisch.io' - ); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page - .getByRole('option', { name: 'Delete Role', exact: true }) - .click(); - await adminCreateUserPage.createButton.click(); - await adminCreateUserPage.snackbar.waitFor({ - state: 'attached', - }); - await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ - state: 'attached', - }); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - } - ); - await test.step( - 'Try to delete "Delete Role" role when new user has it', - async () => { - await adminRolesPage.navigateTo(); - const row = await adminRolesPage.getRoleRowByName('Delete Role'); - const modal = await adminRolesPage.clickDeleteRole(row); - await modal.deleteButton.click(); - await adminRolesPage.snackbar.waitFor({ - state: 'attached', - }); - const snackbar = await adminRolesPage.getSnackbarData('snackbar-delete-role-error'); - await expect(snackbar.variant).toBe('error'); - await adminRolesPage.closeSnackbar(); - await modal.close(); - } - ); + await test.step('Create a new user with the "Delete Role" role', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill('User Role Test'); + await adminCreateUserPage.emailInput.fill( + 'user-role-test@automatisch.io' + ); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Delete Role', exact: true }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ + state: 'attached', + }); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); + }); + + await test.step('Try to delete "Delete Role" role when new user has it', async () => { + await adminRolesPage.navigateTo(); + const row = await adminRolesPage.getRoleRowByName('Delete Role'); + const modal = await adminRolesPage.clickDeleteRole(row); + await modal.deleteButton.click(); + await adminRolesPage.snackbar.waitFor({ + state: 'attached', + }); + const snackbar = await adminRolesPage.getSnackbarData( + 'snackbar-delete-role-error' + ); + await expect(snackbar.variant).toBe('error'); + await adminRolesPage.closeSnackbar(); + await modal.close(); + }); await test.step('Change the role the user has', async () => { await adminUsersPage.navigateTo(); await adminUsersPage.usersLoader.waitFor({ @@ -301,24 +287,16 @@ test.describe('Role management page', () => { .getByRole('option', { name: 'Cannot Delete Role' }) .click(); await adminCreateUserPage.createButton.click(); - await adminCreateUserPage.snackbar.waitFor({ - state: 'attached', - }); await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ state: 'attached', }); - const snackbar = await adminCreateUserPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminCreateUserPage.closeSnackbar(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); }); await test.step('Delete this user', async () => { await adminUsersPage.navigateTo(); const row = await adminUsersPage.findUserPageWithEmail( 'user-delete-role-test@automatisch.io' ); - // await test.waitForTimeout(10000); const modal = await adminUsersPage.clickDeleteUser(row); await modal.deleteButton.click(); await adminUsersPage.snackbar.waitFor({ @@ -385,17 +363,10 @@ test('Accessibility of role management page', async ({ .getByRole('option', { name: 'Basic Test' }) .click(); await adminCreateUserPage.createButton.click(); - await adminCreateUserPage.snackbar.waitFor({ - state: 'attached', - }); await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ state: 'attached', }); - const snackbar = await adminCreateUserPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminCreateUserPage.closeSnackbar(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); }); await test.step('Logout and login to the basic role user', async () => { @@ -409,42 +380,35 @@ test('Accessibility of role management page', async ({ await page.getByTestId('logout-item').click(); const acceptInvitationPage = new AcceptInvitation(page); - await acceptInvitationPage.open(acceptInvitatonToken); - await acceptInvitationPage.acceptInvitation('sample'); const loginPage = new LoginPage(page); - - // await loginPage.isMounted(); await loginPage.login('basic-role-test@automatisch.io', 'sample'); await expect(loginPage.loginButton).not.toBeVisible(); await expect(page).toHaveURL('/flows'); }); - await test.step( - 'Navigate to the admin settings page and make sure it is blank', - async () => { - const pageUrl = new URL(page.url()); - const url = `${pageUrl.origin}/admin-settings/users`; - await page.goto(url); - await page.waitForTimeout(750); - const isUnmounted = await page.evaluate(() => { - // eslint-disable-next-line no-undef - const root = document.querySelector('#root'); + await test.step('Navigate to the admin settings page and make sure it is blank', async () => { + const pageUrl = new URL(page.url()); + const url = `${pageUrl.origin}/admin-settings/users`; + await page.goto(url); + await page.waitForTimeout(750); + const isUnmounted = await page.evaluate(() => { + // eslint-disable-next-line no-undef + const root = document.querySelector('#root'); - if (root) { - // We have react query devtools only in dev env. - // In production, there is nothing in root. - // That's why `<= 1`. - return root.children.length <= 1; - } + if (root) { + // We have react query devtools only in dev env. + // In production, there is nothing in root. + // That's why `<= 1`. + return root.children.length <= 1; + } - return false; - }); - await expect(isUnmounted).toBe(true); - } - ); + return false; + }); + await expect(isUnmounted).toBe(true); + }); await test.step('Log back into the admin account', async () => { await page.goto('/'); diff --git a/packages/e2e-tests/tests/admin/manage-users.spec.js b/packages/e2e-tests/tests/admin/manage-users.spec.js index d6fc1507..af7f7083 100644 --- a/packages/e2e-tests/tests/admin/manage-users.spec.js +++ b/packages/e2e-tests/tests/admin/manage-users.spec.js @@ -5,281 +5,221 @@ const { test, expect } = require('../../fixtures/index'); * otherwise tests will fail since users are only *soft*-deleted */ test.describe('User management page', () => { - test.beforeEach(async ({ adminUsersPage }) => { await adminUsersPage.navigateTo(); await adminUsersPage.closeSnackbar(); }); - test( - 'User creation and deletion process', - async ({ adminCreateUserPage, adminEditUserPage, adminUsersPage }) => { - adminCreateUserPage.seed(9000); - const user = adminCreateUserPage.generateUser(); - await adminUsersPage.usersLoader.waitFor({ - state: 'detached' /* Note: state: 'visible' introduces flakiness + test('User creation and deletion process', async ({ + adminCreateUserPage, + adminEditUserPage, + adminUsersPage, + }) => { + adminCreateUserPage.seed(9000); + const user = adminCreateUserPage.generateUser(); + await adminUsersPage.usersLoader.waitFor({ + state: 'detached' /* Note: state: 'visible' introduces flakiness because visibility: hidden is used as part of the state transition in notistack, see https://github.com/iamhosseindhv/notistack/blob/122f47057eb7ce5a1abfe923316cf8475303e99a/src/transitions/Collapse/Collapse.tsx#L110 - */ + */, + }); + await test.step('Create a user', async () => { + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(user.fullName); + await adminCreateUserPage.emailInput.fill(user.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ + state: 'attached', }); - await test.step( - 'Create a user', - async () => { - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(user.fullName); - await adminCreateUserPage.emailInput.fill(user.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ - state: 'attached' - }); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.navigateTo(); - await adminUsersPage.closeSnackbar(); - } + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); + await adminUsersPage.navigateTo(); + }); + await test.step('Check the user exists with the expected properties', async () => { + await adminUsersPage.findUserPageWithEmail(user.email); + const userRow = await adminUsersPage.getUserRowByEmail(user.email); + const data = await adminUsersPage.getRowData(userRow); + await expect(data.email).toBe(user.email); + await expect(data.fullName).toBe(user.fullName); + await expect(data.role).toBe('Admin'); + }); + await test.step('Edit user info and make sure the edit works correctly', async () => { + await adminUsersPage.findUserPageWithEmail(user.email); + + let userRow = await adminUsersPage.getUserRowByEmail(user.email); + await adminUsersPage.clickEditUser(userRow); + await adminEditUserPage.waitForLoad(user.fullName); + const newUserInfo = adminEditUserPage.generateUser(); + await adminEditUserPage.fullNameInput.fill(newUserInfo.fullName); + await adminEditUserPage.updateButton.click(); + + const snackbar = await adminUsersPage.getSnackbarData( + 'snackbar-edit-user-success' ); - await test.step( - 'Check the user exists with the expected properties', - async () => { - await adminUsersPage.findUserPageWithEmail(user.email); - const userRow = await adminUsersPage.getUserRowByEmail(user.email); - const data = await adminUsersPage.getRowData(userRow); - await expect(data.email).toBe(user.email); - await expect(data.fullName).toBe(user.fullName); - await expect(data.role).toBe('Admin'); - } + await expect(snackbar.variant).toBe('success'); + await adminUsersPage.closeSnackbar(); + + await adminUsersPage.findUserPageWithEmail(user.email); + userRow = await adminUsersPage.getUserRowByEmail(user.email); + const rowData = await adminUsersPage.getRowData(userRow); + await expect(rowData.fullName).toBe(newUserInfo.fullName); + }); + await test.step('Delete user and check the page confirms this deletion', async () => { + await adminUsersPage.findUserPageWithEmail(user.email); + const userRow = await adminUsersPage.getUserRowByEmail(user.email); + await adminUsersPage.clickDeleteUser(userRow); + const modal = adminUsersPage.deleteUserModal; + await modal.deleteButton.click(); + + const snackbar = await adminUsersPage.getSnackbarData( + 'snackbar-delete-user-success' ); - await test.step( - 'Edit user info and make sure the edit works correctly', - async () => { - await adminUsersPage.findUserPageWithEmail(user.email); + await expect(snackbar.variant).toBe('success'); + await adminUsersPage.closeSnackbar(); + await expect(userRow).not.toBeVisible(false); + }); + }); - let userRow = await adminUsersPage.getUserRowByEmail(user.email); - await adminUsersPage.clickEditUser(userRow); - await adminEditUserPage.waitForLoad(user.fullName); - const newUserInfo = adminEditUserPage.generateUser(); - await adminEditUserPage.fullNameInput.fill(newUserInfo.fullName); - await adminEditUserPage.updateButton.click(); + test('Creating a user which has been deleted', async ({ + adminCreateUserPage, + adminUsersPage, + }) => { + adminCreateUserPage.seed(9100); + const testUser = adminCreateUserPage.generateUser(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-edit-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - - await adminUsersPage.findUserPageWithEmail(user.email); - userRow = await adminUsersPage.getUserRowByEmail(user.email); - const rowData = await adminUsersPage.getRowData(userRow); - await expect(rowData.fullName).toBe(newUserInfo.fullName); - } - ); - await test.step( - 'Delete user and check the page confirms this deletion', - async () => { - await adminUsersPage.findUserPageWithEmail(user.email); - const userRow = await adminUsersPage.getUserRowByEmail(user.email); - await adminUsersPage.clickDeleteUser(userRow); - const modal = adminUsersPage.deleteUserModal; - await modal.deleteButton.click(); - - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-delete-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - await expect(userRow).not.toBeVisible(false); - } - ); + await test.step('Create the test user', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(testUser.fullName); + await adminCreateUserPage.emailInput.fill(testUser.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); }); - test( - 'Creating a user which has been deleted', - async ({ adminCreateUserPage, adminUsersPage }) => { - adminCreateUserPage.seed(9100); - const testUser = adminCreateUserPage.generateUser(); - - await test.step( - 'Create the test user', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(testUser.fullName); - await adminCreateUserPage.emailInput.fill(testUser.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - } + await test.step('Delete the created user', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.findUserPageWithEmail(testUser.email); + const userRow = await adminUsersPage.getUserRowByEmail(testUser.email); + await adminUsersPage.clickDeleteUser(userRow); + const modal = adminUsersPage.deleteUserModal; + await modal.deleteButton.click(); + const snackbar = await adminUsersPage.getSnackbarData( + 'snackbar-delete-user-success' ); + await expect(snackbar).not.toBeNull(); + await expect(snackbar.variant).toBe('success'); + await adminUsersPage.closeSnackbar(); + await expect(userRow).not.toBeVisible(false); + }); - await test.step( - 'Delete the created user', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.findUserPageWithEmail(testUser.email); - const userRow = await adminUsersPage.getUserRowByEmail(testUser.email); - await adminUsersPage.clickDeleteUser(userRow); - const modal = adminUsersPage.deleteUserModal; - await modal.deleteButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-delete-user-success' - ); - await expect(snackbar).not.toBeNull(); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - await expect(userRow).not.toBeVisible(false); - } - ); + await test.step('Create the user again', async () => { + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(testUser.fullName); + await adminCreateUserPage.emailInput.fill(testUser.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await expect(adminCreateUserPage.fieldError).toHaveCount(1); + }); + }); - await test.step( - 'Create the user again', - async () => { - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(testUser.fullName); - await adminCreateUserPage.emailInput.fill(testUser.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData('snackbar-error'); - await expect(snackbar.variant).toBe('error'); - await adminUsersPage.closeSnackbar(); - } - ); - } - ); + test('Creating a user which already exists', async ({ + adminCreateUserPage, + adminUsersPage, + page, + }) => { + adminCreateUserPage.seed(9200); + const testUser = adminCreateUserPage.generateUser(); - test( - 'Creating a user which already exists', - async ({ adminCreateUserPage, adminUsersPage, page }) => { - adminCreateUserPage.seed(9200); - const testUser = adminCreateUserPage.generateUser(); + await test.step('Create the test user', async () => { + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(testUser.fullName); + await adminCreateUserPage.emailInput.fill(testUser.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); + }); - await test.step( - 'Create the test user', - async () => { - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(testUser.fullName); - await adminCreateUserPage.emailInput.fill(testUser.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - } - ); + await test.step('Create the user again', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(testUser.fullName); + await adminCreateUserPage.emailInput.fill(testUser.email); + const createUserPageUrl = page.url(); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); - await test.step( - 'Create the user again', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(testUser.fullName); - await adminCreateUserPage.emailInput.fill(testUser.email); - const createUserPageUrl = page.url(); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); + await expect(page.url()).toBe(createUserPageUrl); + await expect(adminCreateUserPage.fieldError).toHaveCount(1); + }); + }); - await expect(page.url()).toBe(createUserPageUrl); - const snackbar = await adminUsersPage.getSnackbarData('snackbar-error'); - await expect(snackbar.variant).toBe('error'); - await adminUsersPage.closeSnackbar(); - } - ); - } - ); + test('Editing a user to have the same email as another user should not be allowed', async ({ + adminCreateUserPage, + adminEditUserPage, + adminUsersPage, + page, + }) => { + adminCreateUserPage.seed(9300); + const user1 = adminCreateUserPage.generateUser(); + const user2 = adminCreateUserPage.generateUser(); + await test.step('Create the first user', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(user1.fullName); + await adminCreateUserPage.emailInput.fill(user1.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); + }); - test( - 'Editing a user to have the same email as another user should not be allowed', - async ({ - adminCreateUserPage, adminEditUserPage, adminUsersPage, page - }) => { - adminCreateUserPage.seed(9300); - const user1 = adminCreateUserPage.generateUser(); - const user2 = adminCreateUserPage.generateUser(); - await test.step( - 'Create the first user', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(user1.fullName); - await adminCreateUserPage.emailInput.fill(user1.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - } - ); + await test.step('Create the second user', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.createUserButton.click(); + await adminCreateUserPage.fullNameInput.fill(user2.fullName); + await adminCreateUserPage.emailInput.fill(user2.email); + await adminCreateUserPage.roleInput.click(); + await adminCreateUserPage.page + .getByRole('option', { name: 'Admin' }) + .click(); + await adminCreateUserPage.createButton.click(); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); + }); - await test.step( - 'Create the second user', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(user2.fullName); - await adminCreateUserPage.emailInput.fill(user2.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page.getByRole( - 'option', { name: 'Admin' } - ).click(); - await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - } - ); + await test.step('Try editing the second user to have the email of the first user', async () => { + await adminUsersPage.navigateTo(); + await adminUsersPage.findUserPageWithEmail(user2.email); + let userRow = await adminUsersPage.getUserRowByEmail(user2.email); + await adminUsersPage.clickEditUser(userRow); + await adminEditUserPage.waitForLoad(user2.fullName); + await adminEditUserPage.emailInput.fill(user1.email); + const editPageUrl = page.url(); + await adminEditUserPage.updateButton.click(); - await test.step( - 'Try editing the second user to have the email of the first user', - async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.findUserPageWithEmail(user2.email); - let userRow = await adminUsersPage.getUserRowByEmail(user2.email); - await adminUsersPage.clickEditUser(userRow); - await adminEditUserPage.waitForLoad(user2.fullName); - await adminEditUserPage.emailInput.fill(user1.email); - const editPageUrl = page.url(); - await adminEditUserPage.updateButton.click(); - - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-error' - ); - await expect(snackbar.variant).toBe('error'); - await adminUsersPage.closeSnackbar(); - await expect(page.url()).toBe(editPageUrl); - } - ); - } - ); + const snackbar = await adminUsersPage.getSnackbarData('snackbar-error'); + await expect(snackbar.variant).toBe('error'); + await adminUsersPage.closeSnackbar(); + await expect(page.url()).toBe(editPageUrl); + }); + }); }); diff --git a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js index 98114c27..6f46454a 100644 --- a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js +++ b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js @@ -7,198 +7,191 @@ test('Ensure creating a new flow works', async ({ page }) => { ); }); -test( - 'Create a new flow with a Scheduler step then an Ntfy step', - async ({ flowEditorPage, page }) => { - await test.step('create flow', async () => { - await test.step('navigate to new flow page', async () => { - await page.getByTestId('create-flow-button').click(); - await page.waitForURL( - /\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/ - ); +test('Create a new flow with a Scheduler step then an Ntfy step', async ({ + flowEditorPage, + page, +}) => { + await test.step('create flow', async () => { + await test.step('navigate to new flow page', async () => { + await page.getByTestId('create-flow-button').click(); + await page.waitForURL( + /\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/ + ); + }); + + await test.step('has two steps by default', async () => { + await expect(page.getByTestId('flow-step')).toHaveCount(2); + }); + }); + + await test.step('setup Scheduler trigger', async () => { + await test.step('choose app and event substep', async () => { + await test.step('choose application', async () => { + await flowEditorPage.appAutocomplete.click(); + await page.getByRole('option', { name: 'Scheduler' }).click(); }); - - await test.step('has two steps by default', async () => { - await expect(page.getByTestId('flow-step')).toHaveCount(2); + + await test.step('choose and event', async () => { + await expect(flowEditorPage.eventAutocomplete).toBeVisible(); + await flowEditorPage.eventAutocomplete.click(); + await page.getByRole('option', { name: 'Every hour' }).click(); + }); + + await test.step('continue to next step', async () => { + await flowEditorPage.continueButton.click(); + }); + + await test.step('collapses the substep', async () => { + await expect(flowEditorPage.appAutocomplete).not.toBeVisible(); + await expect(flowEditorPage.eventAutocomplete).not.toBeVisible(); }); }); - await test.step('setup Scheduler trigger', async () => { - await test.step('choose app and event substep', async () => { - await test.step('choose application', async () => { - await flowEditorPage.appAutocomplete.click(); - await page - .getByRole('option', { name: 'Scheduler' }) - .click(); - }); - - await test.step('choose and event', async () => { - await expect(flowEditorPage.eventAutocomplete).toBeVisible(); - await flowEditorPage.eventAutocomplete.click(); - await page - .getByRole('option', { name: 'Every hour' }) - .click(); - }); - - await test.step('continue to next step', async () => { - await flowEditorPage.continueButton.click(); - }); - - await test.step('collapses the substep', async () => { - await expect(flowEditorPage.appAutocomplete).not.toBeVisible(); - await expect(flowEditorPage.eventAutocomplete).not.toBeVisible(); - }); + await test.step('set up a trigger', async () => { + await test.step('choose "yes" in "trigger on weekends?"', async () => { + await expect(flowEditorPage.trigger).toBeVisible(); + await flowEditorPage.trigger.click(); + await page.getByRole('option', { name: 'Yes' }).click(); }); - await test.step('set up a trigger', async () => { - await test.step('choose "yes" in "trigger on weekends?"', async () => { - await expect(flowEditorPage.trigger).toBeVisible(); - await flowEditorPage.trigger.click(); - await page.getByRole('option', { name: 'Yes' }).click(); - }); - - await test.step('continue to next step', async () => { - await flowEditorPage.continueButton.click(); - }); - - await test.step('collapses the substep', async () => { - await expect(flowEditorPage.trigger).not.toBeVisible(); - }); + await test.step('continue to next step', async () => { + await flowEditorPage.continueButton.click(); }); - await test.step('test trigger', async () => { - await test.step('show sample output', async () => { - await expect(flowEditorPage.testOutput).not.toBeVisible(); - await flowEditorPage.continueButton.click(); - await expect(flowEditorPage.testOutput).toBeVisible(); - await flowEditorPage.screenshot({ - path: 'Scheduler trigger test output.png', - }); - await flowEditorPage.continueButton.click(); - }); + await test.step('collapses the substep', async () => { + await expect(flowEditorPage.trigger).not.toBeVisible(); }); }); - await test.step('arrange Ntfy action', async () => { - await test.step('choose app and event substep', async () => { - await test.step('choose application', async () => { - await flowEditorPage.appAutocomplete.click(); - await page.getByRole('option', { name: 'Ntfy' }).click(); - }); - - await test.step('choose an event', async () => { - await expect(flowEditorPage.eventAutocomplete).toBeVisible(); - await flowEditorPage.eventAutocomplete.click(); - await page - .getByRole('option', { name: 'Send message' }) - .click(); - }); - - await test.step('continue to next step', async () => { - await flowEditorPage.continueButton.click(); - }); - - await test.step('collapses the substep', async () => { - await expect(flowEditorPage.appAutocomplete).not.toBeVisible(); - await expect(flowEditorPage.eventAutocomplete).not.toBeVisible(); - }); - }); - - await test.step('choose connection substep', async () => { - await test.step('choose connection list item', async () => { - await flowEditorPage.connectionAutocomplete.click(); - await page.getByRole('option').first().click(); - }); - - await test.step('continue to next step', async () => { - await flowEditorPage.continueButton.click(); - }); - - await test.step('collapses the substep', async () => { - await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible(); - }); - }); - - await test.step('set up action substep', async () => { - await test.step('fill topic and message body', async () => { - await page - .getByTestId('parameters.topic-power-input') - .locator('[contenteditable]') - .fill('Topic'); - await page - .getByTestId('parameters.message-power-input') - .locator('[contenteditable]') - .fill('Message body'); - }); - - await test.step('continue to next step', async () => { - await flowEditorPage.continueButton.click(); - }); - - await test.step('collapses the substep', async () => { - await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible(); - }); - }); - - await test.step('test trigger substep', async () => { - await test.step('show sample output', async () => { - await expect(flowEditorPage.testOutput).not.toBeVisible(); - await page - .getByTestId('flow-substep-continue-button') - .first() - .click(); - await expect(flowEditorPage.testOutput).toBeVisible(); - await flowEditorPage.screenshot({ - path: 'Ntfy action test output.png', - }); - await flowEditorPage.continueButton.click(); - }); - }); - }); - - await test.step('publish and unpublish', async () => { - await test.step('publish flow', async () => { - await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible(); - await expect(flowEditorPage.publishFlowButton).toBeVisible(); - await flowEditorPage.publishFlowButton.click(); - await expect(flowEditorPage.publishFlowButton).not.toBeVisible(); - }); - - await test.step('shows read-only sticky snackbar', async () => { - await expect(flowEditorPage.infoSnackbar).toBeVisible(); + await test.step('test trigger', async () => { + await test.step('show sample output', async () => { + await expect(flowEditorPage.testOutput).not.toBeVisible(); + await flowEditorPage.continueButton.click(); + await expect(flowEditorPage.testOutput).toBeVisible(); await flowEditorPage.screenshot({ - path: 'Published flow.png', + path: 'Scheduler trigger test output.png', }); + await flowEditorPage.continueButton.click(); }); - - await test.step('unpublish from snackbar', async () => { + }); + }); + + await test.step('arrange Ntfy action', async () => { + await test.step('choose app and event substep', async () => { + await test.step('choose application', async () => { + await flowEditorPage.appAutocomplete.click(); + await page.getByRole('option', { name: 'Ntfy' }).click(); + }); + + await test.step('choose an event', async () => { + await expect(flowEditorPage.eventAutocomplete).toBeVisible(); + await flowEditorPage.eventAutocomplete.click(); + await page.getByRole('option', { name: 'Send message' }).click(); + }); + + await test.step('continue to next step', async () => { + await flowEditorPage.continueButton.click(); + }); + + await test.step('collapses the substep', async () => { + await expect(flowEditorPage.appAutocomplete).not.toBeVisible(); + await expect(flowEditorPage.eventAutocomplete).not.toBeVisible(); + }); + }); + + await test.step('choose connection substep', async () => { + await test.step('choose connection list item', async () => { + await flowEditorPage.connectionAutocomplete.click(); await page - .getByTestId('unpublish-flow-from-snackbar') + .getByRole('option') + .filter({ hasText: 'Add new connection' }) .click(); - await expect(flowEditorPage.infoSnackbar).not.toBeVisible(); }); - - await test.step('publish once again', async () => { - await expect(flowEditorPage.publishFlowButton).toBeVisible(); - await flowEditorPage.publishFlowButton.click(); - await expect(flowEditorPage.publishFlowButton).not.toBeVisible(); + + await test.step('continue to next step', async () => { + await page.getByTestId('create-connection-button').click(); }); - - await test.step('unpublish from layout top bar', async () => { - await expect(flowEditorPage.unpublishFlowButton).toBeVisible(); - await flowEditorPage.unpublishFlowButton.click(); - await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible(); + + await test.step('collapses the substep', async () => { + await flowEditorPage.continueButton.click(); + await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible(); + }); + }); + + await test.step('set up action substep', async () => { + await test.step('fill topic and message body', async () => { + await page + .getByTestId('parameters.topic-power-input') + .locator('[contenteditable]') + .fill('Topic'); + await page + .getByTestId('parameters.message-power-input') + .locator('[contenteditable]') + .fill('Message body'); + }); + + await test.step('continue to next step', async () => { + await flowEditorPage.continueButton.click(); + }); + + await test.step('collapses the substep', async () => { + await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible(); + }); + }); + + await test.step('test trigger substep', async () => { + await test.step('show sample output', async () => { + await expect(flowEditorPage.testOutput).not.toBeVisible(); + await page.getByTestId('flow-substep-continue-button').first().click(); + await expect(flowEditorPage.testOutput).toBeVisible(); await flowEditorPage.screenshot({ - path: 'Unpublished flow.png', + path: 'Ntfy action test output.png', }); + await flowEditorPage.continueButton.click(); }); }); - - await test.step('in layout', async () => { - await test.step('can go back to flows page', async () => { - await page.getByTestId('editor-go-back-button').click(); - await expect(page).toHaveURL('/flows'); + }); + + await test.step('publish and unpublish', async () => { + await test.step('publish flow', async () => { + await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible(); + await expect(flowEditorPage.publishFlowButton).toBeVisible(); + await flowEditorPage.publishFlowButton.click(); + await expect(flowEditorPage.publishFlowButton).not.toBeVisible(); + }); + + await test.step('shows read-only sticky snackbar', async () => { + await expect(flowEditorPage.infoSnackbar).toBeVisible(); + await flowEditorPage.screenshot({ + path: 'Published flow.png', }); }); - } -); \ No newline at end of file + + await test.step('unpublish from snackbar', async () => { + await page.getByTestId('unpublish-flow-from-snackbar').click(); + await expect(flowEditorPage.infoSnackbar).not.toBeVisible(); + }); + + await test.step('publish once again', async () => { + await expect(flowEditorPage.publishFlowButton).toBeVisible(); + await flowEditorPage.publishFlowButton.click(); + await expect(flowEditorPage.publishFlowButton).not.toBeVisible(); + }); + + await test.step('unpublish from layout top bar', async () => { + await expect(flowEditorPage.unpublishFlowButton).toBeVisible(); + await flowEditorPage.unpublishFlowButton.click(); + await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible(); + await flowEditorPage.screenshot({ + path: 'Unpublished flow.png', + }); + }); + }); + + await test.step('in layout', async () => { + await test.step('can go back to flows page', async () => { + await page.getByTestId('editor-go-back-button').click(); + await expect(page).toHaveURL('/flows'); + }); + }); +}); diff --git a/packages/e2e-tests/tests/my-profile/profile-updates.spec.js b/packages/e2e-tests/tests/my-profile/profile-updates.spec.js index d77962e4..fc0ce7d0 100644 --- a/packages/e2e-tests/tests/my-profile/profile-updates.spec.js +++ b/packages/e2e-tests/tests/my-profile/profile-updates.spec.js @@ -33,10 +33,7 @@ publicTest.describe('My Profile', () => { .getByRole('option', { name: 'Admin' }) .click(); await adminCreateUserPage.createButton.click(); - const snackbar = await adminUsersPage.getSnackbarData( - 'snackbar-create-user-success' - ); - await expect(snackbar.variant).toBe('success'); + await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); }); await publicTest.step('copy invitation link', async () => { diff --git a/packages/web/src/components/Container/index.jsx b/packages/web/src/components/Container/index.jsx index ffafaa14..ef75335b 100644 --- a/packages/web/src/components/Container/index.jsx +++ b/packages/web/src/components/Container/index.jsx @@ -1,10 +1,19 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import MuiContainer from '@mui/material/Container'; -export default function Container(props) { - return ; +export default function Container({ maxWidth = 'lg', ...props }) { + return ; } -Container.defaultProps = { - maxWidth: 'lg', +Container.propTypes = { + maxWidth: PropTypes.oneOf([ + 'xs', + 'sm', + 'md', + 'lg', + 'xl', + false, + PropTypes.string, + ]), }; diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index c9422557..a5fa024a 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -226,10 +226,11 @@ "userForm.email": "Email", "userForm.role": "Role", "userForm.password": "Password", + "userForm.mandatoryInput": "{inputName} is required.", + "userForm.validateEmail": "Email must be valid.", "createUser.submit": "Create", "createUser.successfullyCreated": "The user has been created.", "createUser.invitationEmailInfo": "Invitation email will be sent if SMTP credentials are valid. Otherwise, you can share the invitation link manually: ", - "createUser.error": "Error while creating the user.", "editUserPage.title": "Edit user", "editUser.status": "Status", "editUser.submit": "Update", @@ -250,8 +251,10 @@ "createRolePage.title": "Create role", "roleForm.name": "Name", "roleForm.description": "Description", + "roleForm.mandatoryInput": "{inputName} is required.", "createRole.submit": "Create", "createRole.successfullyCreated": "The role has been created.", + "createRole.permissionsError": "Permissions are invalid.", "editRole.submit": "Update", "editRole.successfullyUpdated": "The role has been updated.", "roleList.name": "Name", diff --git a/packages/web/src/pages/CreateRole/index.ee.jsx b/packages/web/src/pages/CreateRole/index.ee.jsx index 99a66901..88ac1d82 100644 --- a/packages/web/src/pages/CreateRole/index.ee.jsx +++ b/packages/web/src/pages/CreateRole/index.ee.jsx @@ -1,10 +1,14 @@ import LoadingButton from '@mui/lab/LoadingButton'; import Grid from '@mui/material/Grid'; import Stack from '@mui/material/Stack'; +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; import PermissionCatalogField from 'components/PermissionCatalogField/index.ee'; import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import * as React from 'react'; import { useNavigate } from 'react-router-dom'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; import Container from 'components/Container'; import Form from 'components/Form'; @@ -19,6 +23,40 @@ import useFormatMessage from 'hooks/useFormatMessage'; import useAdminCreateRole from 'hooks/useAdminCreateRole'; import usePermissionCatalog from 'hooks/usePermissionCatalog.ee'; +const getValidationSchema = (formatMessage) => { + const getMandatoryFieldMessage = (fieldTranslationId) => + formatMessage('roleForm.mandatoryInput', { + inputName: formatMessage(fieldTranslationId), + }); + + return yup.object().shape({ + name: yup + .string() + .trim() + .required(getMandatoryFieldMessage('roleForm.name')), + description: yup.string().trim(), + }); +}; + +const getPermissionsErrorMessage = (error) => { + const errors = error?.response?.data?.errors; + + if (errors) { + const permissionsErrors = Object.keys(errors) + .filter((key) => key.startsWith('permissions')) + .reduce((obj, key) => { + obj[key] = errors[key]; + return obj; + }, {}); + + if (Object.keys(permissionsErrors).length > 0) { + return JSON.stringify(permissionsErrors, null, 2); + } + } + + return null; +}; + export default function CreateRole() { const navigate = useNavigate(); const formatMessage = useFormatMessage(); @@ -27,6 +65,7 @@ export default function CreateRole() { useAdminCreateRole(); const { data: permissionCatalogData, isLoading: isPermissionCatalogLoading } = usePermissionCatalog(); + const [permissionError, setPermissionError] = React.useState(null); const defaultValues = React.useMemo( () => ({ @@ -44,6 +83,7 @@ export default function CreateRole() { const handleRoleCreation = async (roleData) => { try { + setPermissionError(null); const permissions = getPermissions(roleData.computedPermissions); await createRole({ @@ -61,16 +101,13 @@ export default function CreateRole() { navigate(URLS.ROLES); } catch (error) { - const errors = Object.values(error.response.data.errors); - - for (const [errorMessage] of errors) { - enqueueSnackbar(errorMessage, { - variant: 'error', - SnackbarProps: { - 'data-test': 'snackbar-error', - }, - }); + const permissionError = getPermissionsErrorMessage(error); + if (permissionError) { + setPermissionError(permissionError); } + + const errors = error?.response?.data?.errors; + throw errors || error; } }; @@ -84,39 +121,67 @@ export default function CreateRole() { -
- - + ( + + - + - + - - {formatMessage('createRole.submit')} - - - + {permissionError && ( + + + {formatMessage('createRole.permissionsError')} + +
+                      {permissionError}
+                    
+
+ )} + + {errors?.root?.general && !permissionError && ( + + {errors?.root?.general?.message} + + )} + + + {formatMessage('createRole.submit')} + +
+ )} + />
diff --git a/packages/web/src/pages/CreateUser/index.jsx b/packages/web/src/pages/CreateUser/index.jsx index ad96ba96..5eb04376 100644 --- a/packages/web/src/pages/CreateUser/index.jsx +++ b/packages/web/src/pages/CreateUser/index.jsx @@ -3,9 +3,10 @@ import Grid from '@mui/material/Grid'; import Stack from '@mui/material/Stack'; import Alert from '@mui/material/Alert'; import MuiTextField from '@mui/material/TextField'; -import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import * as React from 'react'; import { useQueryClient } from '@tanstack/react-query'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; import Can from 'components/Can'; import Container from 'components/Container'; @@ -16,50 +17,70 @@ import TextField from 'components/TextField'; import useFormatMessage from 'hooks/useFormatMessage'; import useRoles from 'hooks/useRoles.ee'; import useAdminCreateUser from 'hooks/useAdminCreateUser'; +import useCurrentUserAbility from 'hooks/useCurrentUserAbility'; function generateRoleOptions(roles) { return roles?.map(({ name: label, id: value }) => ({ label, value })); } +const getValidationSchema = (formatMessage, canUpdateRole) => { + const getMandatoryFieldMessage = (fieldTranslationId) => + formatMessage('userForm.mandatoryInput', { + inputName: formatMessage(fieldTranslationId), + }); + + return yup.object().shape({ + fullName: yup + .string() + .trim() + .required(getMandatoryFieldMessage('userForm.fullName')), + email: yup + .string() + .trim() + .email(formatMessage('userForm.validateEmail')) + .required(getMandatoryFieldMessage('userForm.email')), + ...(canUpdateRole + ? { + roleId: yup + .string() + .required(getMandatoryFieldMessage('userForm.role')), + } + : {}), + }); +}; + +const defaultValues = { + fullName: '', + email: '', + roleId: '', +}; + export default function CreateUser() { const formatMessage = useFormatMessage(); const { mutateAsync: createUser, isPending: isCreateUserPending, data: createdUser, + isSuccess: createUserSuccess, } = useAdminCreateUser(); const { data: rolesData, loading: isRolesLoading } = useRoles(); const roles = rolesData?.data; - const enqueueSnackbar = useEnqueueSnackbar(); const queryClient = useQueryClient(); + const currentUserAbility = useCurrentUserAbility(); + const canUpdateRole = currentUserAbility.can('update', 'Role'); const handleUserCreation = async (userData) => { try { await createUser({ fullName: userData.fullName, email: userData.email, - roleId: userData.role?.id, + roleId: userData.roleId, }); queryClient.invalidateQueries({ queryKey: ['admin', 'users'] }); - - enqueueSnackbar(formatMessage('createUser.successfullyCreated'), { - variant: 'success', - persist: true, - SnackbarProps: { - 'data-test': 'snackbar-create-user-success', - }, - }); } catch (error) { - enqueueSnackbar(formatMessage('createUser.error'), { - variant: 'error', - persist: true, - SnackbarProps: { - 'data-test': 'snackbar-error', - }, - }); - - throw new Error('Failed while creating!'); + const errors = error?.response?.data?.errors; + throw errors || error; } }; @@ -73,74 +94,111 @@ export default function CreateUser() { -
- - - - - - - ( + + ( - - )} - loading={isRolesLoading} + error={!!errors?.fullName} + helperText={errors?.fullName?.message} /> - - - {formatMessage('createUser.submit')} - + - {createdUser && ( - + ( + + )} + loading={isRolesLoading} + showHelperText={false} + /> + + + {errors?.root?.general && ( + + {errors?.root?.general?.message} + + )} + + {createUserSuccess && ( + + {formatMessage('createUser.successfullyCreated')} + + )} + + {createdUser && ( + + {formatMessage('createUser.invitationEmailInfo', { + link: () => ( + + {createdUser.data.acceptInvitationUrl} + + ), + })} + + )} + + - {formatMessage('createUser.invitationEmailInfo', { - link: () => ( - - {createdUser.data.acceptInvitationUrl} - - ), - })} - - )} - -
+ {formatMessage('createUser.submit')} + + + )} + />