diff --git a/packages/plugins/users-permissions/admin/src/hooks/useFetchRole/index.js b/packages/plugins/users-permissions/admin/src/hooks/useFetchRole/index.js index 6e9f2fb7d6..057174b12e 100644 --- a/packages/plugins/users-permissions/admin/src/hooks/useFetchRole/index.js +++ b/packages/plugins/users-permissions/admin/src/hooks/useFetchRole/index.js @@ -1,13 +1,17 @@ -import { useCallback, useReducer, useEffect } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useCallback, useReducer, useEffect, useRef } from 'react'; +import { useNotification } from '@strapi/helper-plugin'; import reducer, { initialState } from './reducer'; +import axiosIntance from '../../utils/axiosInstance'; import pluginId from '../../pluginId'; const useFetchRole = id => { const [state, dispatch] = useReducer(reducer, initialState); const toggleNotification = useNotification(); + const isMounted = useRef(null); useEffect(() => { + isMounted.current = true; + if (id) { fetchRole(id); } else { @@ -17,17 +21,23 @@ const useFetchRole = id => { }); } + return () => (isMounted.current = false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); const fetchRole = async roleId => { try { - const { role } = await request(`/${pluginId}/roles/${roleId}`, { method: 'GET' }); + const { + data: { role }, + } = await axiosIntance.get(`/${pluginId}/roles/${roleId}`); - dispatch({ - type: 'GET_DATA_SUCCEEDED', - role, - }); + // Prevent updating state on an unmounted component + if (isMounted.current) { + dispatch({ + type: 'GET_DATA_SUCCEEDED', + role, + }); + } } catch (err) { console.error(err); diff --git a/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/index.js b/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/index.js index 9050524d4b..7d0eb15d75 100644 --- a/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/index.js +++ b/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/index.js @@ -1,20 +1,24 @@ -import React, { useState, useRef } from 'react'; -import { Main, HeaderLayout, Button } from '@strapi/parts'; +import React, { useState } from 'react'; +import { Main, Button, Stack, Box, GridItem, Grid, TextInput, Textarea } from '@strapi/parts'; +import { ContentLayout, HeaderLayout } from '@strapi/parts/Layout'; +import { H3 } from '@strapi/parts/Text'; +import { CheckIcon } from '@strapi/icons'; import { Formik } from 'formik'; import { useIntl } from 'react-intl'; import { useRouteMatch } from 'react-router-dom'; import { - request, - useNotification, useOverlayBlocker, SettingsPageTitle, + LoadingIndicatorPage, + Form, + useNotification, } from '@strapi/helper-plugin'; import getTrad from '../../../utils/getTrad'; import pluginId from '../../../pluginId'; import { usePlugins, useFetchRole } from '../../../hooks'; - import schema from './utils/schema'; +import axiosInstance from '../../../utils/axiosInstance'; const EditPage = () => { const { formatMessage } = useIntl(); @@ -24,43 +28,44 @@ const EditPage = () => { const { params: { id }, } = useRouteMatch(`/settings/${pluginId}/roles/:id`); - const { isLoading } = usePlugins(); - const { role, onSubmitSucceeded } = useFetchRole(id); - const permissionsRef = useRef(); + const { isLoading: isLoadingPlugins } = usePlugins(); + const { role, onSubmitSucceeded, isLoading: isLoadingRole } = useFetchRole(id); - const handleCreateRoleSubmit = data => { + const handleCreateRoleSubmit = async data => { + // Set loading state lockApp(); setIsSubmitting(true); - - const permissions = permissionsRef.current.getPermissions(); - - Promise.resolve( - request(`/${pluginId}/roles/${id}`, { - method: 'PUT', - body: { ...data, ...permissions, users: [] }, - }) - ) - .then(() => { - onSubmitSucceeded({ name: data.name, description: data.description }); - permissionsRef.current.setFormAfterSubmit(); - toggleNotification({ - type: 'success', - message: { id: getTrad('Settings.roles.edited') }, - }); - }) - .catch(err => { - console.error(err); - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - }) - .finally(() => { - setIsSubmitting(false); - unlockApp(); + try { + // Update role in Strapi + await axiosInstance.put(`/${pluginId}/roles/${id}`, { ...data, users: [] }); + // Notify success + onSubmitSucceeded({ name: data.name, description: data.description }); + toggleNotification({ + type: 'success', + message: { + id: getTrad('Settings.roles.edited'), + defaultMessage: 'Role edited', + }, }); + } catch (err) { + console.error(err); + toggleNotification({ + type: 'warning', + message: { + id: 'notification.error', + defaultMessage: 'An error occurred', + }, + }); + } + // Unset loading state + setIsSubmitting(false); + unlockApp(); }; + if (isLoadingRole) { + return ; + } + return (
@@ -70,15 +75,16 @@ const EditPage = () => { onSubmit={handleCreateRoleSubmit} validationSchema={schema} > - {({ handleSubmit }) => ( -
+ {({ handleSubmit, values, handleChange, errors }) => ( + } > {formatMessage({ id: 'app.components.Button.save', @@ -90,7 +96,66 @@ const EditPage = () => { title={role.name} subtitle={role.description} /> - + + + + +

+ {formatMessage({ + id: getTrad('EditPage.form.roles'), + defaultMessage: 'Role details', + })} +

+ + + + + + + + + + + + + -

- Default role given to authenticated user. -

- - -
- `); + + + `); + }); + + it("can edit a users-permissions role's name and description", async () => { + const { getByLabelText, getByRole, getByTestId, getAllByText } = makeAndRenderApp(); + + // Check loading screen + const loader = getByTestId('loader'); + expect(loader).toBeInTheDocument(); + + // After loading, check other elements + await waitForElementToBeRemoved(loader).catch(e => console.error(e)); + const saveButton = getByRole('button', { name: /save/i }); + expect(saveButton).toBeInTheDocument(); + const nameField = getByLabelText(/name/i); + expect(nameField).toBeInTheDocument(); + const descriptionField = getByLabelText(/description/i); + expect(descriptionField).toBeInTheDocument(); + + // Shows error when name is missing + await userEvent.clear(nameField); + expect(nameField).toHaveValue(''); + await userEvent.clear(descriptionField); + expect(descriptionField).toHaveValue(''); + + // Show errors after form submit + await userEvent.click(saveButton); + await waitFor(() => expect(saveButton).not.toBeDisabled()); + const errorMessages = await getAllByText(/invalid value/i); + errorMessages.forEach(errorMessage => expect(errorMessage).toBeInTheDocument()); + }); }); diff --git a/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/tests/server.js b/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/tests/server.js new file mode 100644 index 0000000000..1346b8e008 --- /dev/null +++ b/packages/plugins/users-permissions/admin/src/pages/Roles/EditPage/tests/server.js @@ -0,0 +1,43 @@ +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + +const handlers = [ + // Mock get role route + rest.get('*/users-permissions/roles/:roleId', (req, res, ctx) => { + return res( + ctx.delay(500), + ctx.status(200), + ctx.json({ + role: { + id: req.params.roleId, + name: 'Authenticated', + description: 'Default role given to authenticated user.', + type: 'authenticated', + created_at: '2021-09-08T16:26:18.061Z', + updated_at: '2021-09-08T16:26:18.061Z', + permissions: { + application: { + controllers: { + address: { + create: { + enabled: false, + policy: '', + }, + }, + }, + }, + }, + }, + }) + ); + }), + + // Mock edit role route + rest.put('*/users-permissions/roles/:roleId', (req, res, ctx) => { + return res(ctx.delay(500), ctx.status(200), ctx.json({ ok: true })); + }), +]; + +const server = setupServer(...handlers); + +export default server;