mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
Merge pull request #10873 from strapi/migration/rbac-delete
Migration/rbac delete
This commit is contained in:
commit
070d7d64ea
@ -1,9 +1,9 @@
|
||||
import { Box, Row, Td, Text, Tr, IconButton, BaseCheckbox } from '@strapi/parts';
|
||||
import { Box, Row, Td, Text, Tr, IconButton } from '@strapi/parts';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const RoleRow = ({ onToggle, id, name, description, usersCount, isChecked, icons }) => {
|
||||
const RoleRow = ({ id, name, description, usersCount, icons }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const usersCountText = formatMessage(
|
||||
@ -16,19 +16,6 @@ const RoleRow = ({ onToggle, id, name, description, usersCount, isChecked, icons
|
||||
|
||||
return (
|
||||
<Tr key={id}>
|
||||
{Boolean(onToggle) && (
|
||||
<Td>
|
||||
<BaseCheckbox
|
||||
name="role-checkbox"
|
||||
onValueChange={() => onToggle(id)}
|
||||
value={isChecked}
|
||||
aria-label={formatMessage(
|
||||
{ id: `Roles.RoleRow.select-all`, defaultMessage: 'Select {name} for bulk actions' },
|
||||
{ name }
|
||||
)}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
<Td>
|
||||
<Text textColor="neutral800">{name}</Text>
|
||||
</Td>
|
||||
@ -53,19 +40,12 @@ const RoleRow = ({ onToggle, id, name, description, usersCount, isChecked, icons
|
||||
);
|
||||
};
|
||||
|
||||
RoleRow.defaultProps = {
|
||||
onToggle: undefined,
|
||||
isChecked: undefined,
|
||||
};
|
||||
|
||||
RoleRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
usersCount: PropTypes.number.isRequired,
|
||||
icons: PropTypes.array.isRequired,
|
||||
onToggle: PropTypes.func,
|
||||
isChecked: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default RoleRow;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect, useReducer, useCallback } from 'react';
|
||||
import { request, useNotification } from '@strapi/helper-plugin';
|
||||
import { get } from 'lodash';
|
||||
import { useNotification } from '@strapi/helper-plugin';
|
||||
import get from 'lodash/get';
|
||||
import { axiosInstance } from '../../core/utils';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
@ -23,7 +24,9 @@ const useRolesList = (shouldFetchData = true) => {
|
||||
type: 'GET_DATA',
|
||||
});
|
||||
|
||||
const { data } = await request('/admin/roles', { method: 'GET' });
|
||||
const {
|
||||
data: { data },
|
||||
} = await axiosInstance.get('/admin/roles');
|
||||
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
CustomContentLayout,
|
||||
useRBAC,
|
||||
Search,
|
||||
SettingsPageTitle,
|
||||
useRBAC,
|
||||
useNotification,
|
||||
useFocusWhenNavigate,
|
||||
} from '@strapi/helper-plugin';
|
||||
@ -15,7 +16,6 @@ import get from 'lodash/get';
|
||||
import adminPermissions from '../../../permissions';
|
||||
import DynamicTable from './DynamicTable';
|
||||
import Filters from './Filters';
|
||||
import Search from './Search';
|
||||
import PaginationFooter from './PaginationFooter';
|
||||
import { deleteData, fetchData } from './utils/api';
|
||||
import displayedFilters from './utils/displayedFilters';
|
||||
|
@ -699,46 +699,6 @@ describe('ADMIN | Pages | USERS | ListPage', () => {
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c36 tr:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
border-bottom: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c37 td,
|
||||
.c37 th {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.c37 td:first-of-type,
|
||||
.c37 th:first-of-type {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.c38 {
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
color: #666687;
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
.c38 input {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.c15 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
@ -788,6 +748,46 @@ describe('ADMIN | Pages | USERS | ListPage', () => {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c36 tr:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
border-bottom: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c37 td,
|
||||
.c37 th {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.c37 td:first-of-type,
|
||||
.c37 th:first-of-type {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.c38 {
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
color: #666687;
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
.c38 input {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
<main
|
||||
aria-labelledby="title"
|
||||
class="c0"
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import {
|
||||
ConfirmDialog,
|
||||
LoadingIndicatorPage,
|
||||
PopUpWarning,
|
||||
Search,
|
||||
SettingsPageTitle,
|
||||
request,
|
||||
useNotification,
|
||||
useQuery,
|
||||
useQueryParams,
|
||||
useRBAC,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { AddIcon, DeleteIcon, Duplicate, EditIcon } from '@strapi/icons';
|
||||
@ -20,14 +21,14 @@ import {
|
||||
Tr,
|
||||
TableLabel,
|
||||
VisuallyHidden,
|
||||
BaseCheckbox,
|
||||
Main,
|
||||
ActionLayout,
|
||||
} from '@strapi/parts';
|
||||
import { get } from 'lodash';
|
||||
import matchSorter from 'match-sorter';
|
||||
import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { axiosInstance } from '../../../../../admin/src/core/utils';
|
||||
import { EmptyRole, RoleRow as BaseRoleRow } from '../../../../../admin/src/components/Roles';
|
||||
import { useRolesList } from '../../../../../admin/src/hooks';
|
||||
import adminPermissions from '../../../../../admin/src/permissions';
|
||||
@ -40,8 +41,8 @@ const useSortedRoles = () => {
|
||||
} = useRBAC(adminPermissions.settings.roles);
|
||||
|
||||
const { getData, roles, isLoading } = useRolesList(false);
|
||||
const query = useQuery();
|
||||
const _q = decodeURIComponent(query.get('_q') || '');
|
||||
const [{ query }] = useQueryParams();
|
||||
const _q = query?._q || '';
|
||||
const sortedRoles = matchSorter(roles, _q, { keys: ['name', 'description'] });
|
||||
|
||||
useEffect(() => {
|
||||
@ -63,62 +64,33 @@ const useSortedRoles = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const useRoleActions = ({ getData, canCreate, canDelete, canUpdate, roles, sortedRoles }) => {
|
||||
const useRoleActions = ({ getData, canCreate, canDelete, canUpdate }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const toggleNotification = useNotification();
|
||||
const [isWarningDeleteAllOpened, setIsWarningDeleteAllOpenend] = useState(false);
|
||||
const { push } = useHistory();
|
||||
const [
|
||||
{ selectedRoles, showModalConfirmButtonLoading, shouldRefetchData },
|
||||
dispatch,
|
||||
] = useReducer(reducer, initialState);
|
||||
const [{ selectedRoles, showModalConfirmButtonLoading, roleToDelete }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState
|
||||
);
|
||||
|
||||
const handleClosedModal = () => {
|
||||
if (shouldRefetchData) {
|
||||
getData();
|
||||
}
|
||||
|
||||
// Empty the selected ids when the modal closes
|
||||
dispatch({
|
||||
type: 'RESET_DATA_TO_DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirmDeleteData = async () => {
|
||||
const handleDeleteData = async () => {
|
||||
try {
|
||||
dispatch({
|
||||
type: 'ON_REMOVE_ROLES',
|
||||
});
|
||||
const filteredRoles = selectedRoles.filter(currentId => {
|
||||
const currentRole = roles.find(role => role.id === currentId);
|
||||
|
||||
return currentRole.usersCount === 0;
|
||||
await axiosInstance.post('/admin/roles/batch-delete', {
|
||||
ids: [roleToDelete],
|
||||
});
|
||||
|
||||
if (selectedRoles.length !== filteredRoles.length) {
|
||||
toggleNotification({
|
||||
type: 'info',
|
||||
message: { id: 'Roles.ListPage.notification.delete-all-not-allowed' },
|
||||
});
|
||||
}
|
||||
await getData();
|
||||
|
||||
if (filteredRoles.length) {
|
||||
await request('/admin/roles/batch-delete', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
ids: filteredRoles,
|
||||
},
|
||||
});
|
||||
|
||||
// Empty the selectedRolesId and set the shouldRefetchData to true so the
|
||||
// list is updated when closing the modal
|
||||
dispatch({
|
||||
type: 'ON_REMOVE_ROLES_SUCCEEDED',
|
||||
});
|
||||
}
|
||||
dispatch({
|
||||
type: 'RESET_DATA_TO_DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const errorIds = get(err, ['response', 'payload', 'data', 'ids'], null);
|
||||
|
||||
if (errorIds && Array.isArray(errorIds)) {
|
||||
@ -133,9 +105,8 @@ const useRoleActions = ({ getData, canCreate, canDelete, canUpdate, roles, sorte
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
handleToggleModal();
|
||||
}
|
||||
handleToggleModal();
|
||||
};
|
||||
|
||||
const onRoleDuplicate = useCallback(
|
||||
@ -156,19 +127,6 @@ const useRoleActions = ({ getData, canCreate, canDelete, canUpdate, roles, sorte
|
||||
handleToggleModal();
|
||||
}, []);
|
||||
|
||||
const onRoleToggle = roleId => {
|
||||
dispatch({
|
||||
type: 'ON_SELECTION',
|
||||
id: roleId,
|
||||
});
|
||||
};
|
||||
|
||||
const onAllRolesToggle = () =>
|
||||
dispatch({
|
||||
type: 'TOGGLE_ALL',
|
||||
ids: sortedRoles.map(r => r.id),
|
||||
});
|
||||
|
||||
const handleToggleModal = () => setIsWarningDeleteAllOpenend(prev => !prev);
|
||||
|
||||
const handleGoTo = useCallback(
|
||||
@ -246,16 +204,13 @@ const useRoleActions = ({ getData, canCreate, canDelete, canUpdate, roles, sorte
|
||||
);
|
||||
|
||||
return {
|
||||
handleClosedModal,
|
||||
handleConfirmDeleteData,
|
||||
handleNewRoleClick,
|
||||
onRoleToggle,
|
||||
onAllRolesToggle,
|
||||
getIcons,
|
||||
selectedRoles,
|
||||
isWarningDeleteAllOpened,
|
||||
showModalConfirmButtonLoading,
|
||||
handleToggleModal,
|
||||
handleDeleteData,
|
||||
};
|
||||
};
|
||||
|
||||
@ -271,21 +226,16 @@ const RoleListPage = () => {
|
||||
isLoading,
|
||||
getData,
|
||||
sortedRoles,
|
||||
roles,
|
||||
} = useSortedRoles();
|
||||
|
||||
const {
|
||||
handleClosedModal,
|
||||
handleConfirmDeleteData,
|
||||
handleNewRoleClick,
|
||||
onRoleToggle,
|
||||
onAllRolesToggle,
|
||||
getIcons,
|
||||
selectedRoles,
|
||||
isWarningDeleteAllOpened,
|
||||
showModalConfirmButtonLoading,
|
||||
handleToggleModal,
|
||||
} = useRoleActions({ getData, canCreate, canDelete, canUpdate, roles, sortedRoles });
|
||||
handleDeleteData,
|
||||
} = useRoleActions({ getData, canCreate, canDelete, canUpdate });
|
||||
|
||||
// ! TODO - Show the search bar only if the user is allowed to read - add the search input
|
||||
// canRead
|
||||
@ -293,11 +243,6 @@ const RoleListPage = () => {
|
||||
const rowCount = sortedRoles.length + 1;
|
||||
const colCount = 6;
|
||||
|
||||
const isAllEntriesIndeterminate = selectedRoles.length
|
||||
? selectedRoles.length !== rowCount
|
||||
: false;
|
||||
const isAllChecked = selectedRoles.length ? selectedRoles.length === rowCount : false;
|
||||
|
||||
if (isLoadingForPermissions) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
@ -327,6 +272,7 @@ const RoleListPage = () => {
|
||||
})}
|
||||
as="h2"
|
||||
/>
|
||||
{canRead && <ActionLayout startActions={<Search />} />}
|
||||
{canRead && (
|
||||
<ContentLayout>
|
||||
<Table
|
||||
@ -345,16 +291,6 @@ const RoleListPage = () => {
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{!!onRoleToggle && (
|
||||
<Th>
|
||||
<BaseCheckbox
|
||||
aria-label="Select all entries"
|
||||
indeterminate={isAllEntriesIndeterminate}
|
||||
value={isAllChecked}
|
||||
onChange={onAllRolesToggle}
|
||||
/>
|
||||
</Th>
|
||||
)}
|
||||
<Th>
|
||||
<TableLabel>
|
||||
{formatMessage({
|
||||
@ -394,10 +330,6 @@ const RoleListPage = () => {
|
||||
<BaseRoleRow
|
||||
key={role.id}
|
||||
id={role.id}
|
||||
onToggle={onRoleToggle}
|
||||
isChecked={
|
||||
selectedRoles.findIndex(selectedRoleId => selectedRoleId === role.id) !== -1
|
||||
}
|
||||
name={role.name}
|
||||
description={role.description}
|
||||
usersCount={role.usersCount}
|
||||
@ -409,12 +341,11 @@ const RoleListPage = () => {
|
||||
{!rowCount && !isLoading && <EmptyRole />}
|
||||
</ContentLayout>
|
||||
)}
|
||||
<PopUpWarning
|
||||
isOpen={isWarningDeleteAllOpened}
|
||||
onClosed={handleClosedModal}
|
||||
onConfirm={handleConfirmDeleteData}
|
||||
toggleModal={handleToggleModal}
|
||||
<ConfirmDialog
|
||||
isVisible={isWarningDeleteAllOpened}
|
||||
onConfirm={handleDeleteData}
|
||||
isConfirmButtonLoading={showModalConfirmButtonLoading}
|
||||
onToggleDialog={handleToggleModal}
|
||||
/>
|
||||
</Main>
|
||||
);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import produce from 'immer';
|
||||
|
||||
export const initialState = {
|
||||
selectedRoles: [],
|
||||
roleToDelete: null,
|
||||
showModalConfirmButtonLoading: false,
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
@ -10,57 +10,26 @@ export const initialState = {
|
||||
const reducer = (state, action) =>
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case 'ON_SELECTION': {
|
||||
const { id } = action;
|
||||
const roleIndex = state.selectedRoles.findIndex(roleId => roleId === id);
|
||||
|
||||
if (roleIndex === -1) {
|
||||
draftState.selectedRoles.push(id);
|
||||
} else {
|
||||
draftState.selectedRoles = state.selectedRoles.filter(roleId => roleId !== id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TOGGLE_ALL': {
|
||||
if (state.selectedRoles.length) {
|
||||
draftState.selectedRoles = [];
|
||||
} else {
|
||||
const { ids } = action;
|
||||
draftState.selectedRoles = ids;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ON_REMOVE_ROLES': {
|
||||
draftState.showModalConfirmButtonLoading = true;
|
||||
break;
|
||||
}
|
||||
case 'ON_REMOVE_ROLES_SUCCEEDED': {
|
||||
draftState.shouldRefetchData = true;
|
||||
draftState.roleToDelete = null;
|
||||
break;
|
||||
}
|
||||
case 'RESET_DATA_TO_DELETE': {
|
||||
draftState.shouldRefetchData = false;
|
||||
draftState.selectedRoles = [];
|
||||
draftState.roleToDelete = null;
|
||||
draftState.showModalConfirmButtonLoading = false;
|
||||
break;
|
||||
}
|
||||
case 'SET_ROLE_TO_DELETE': {
|
||||
draftState.selectedRoles = [action.id];
|
||||
draftState.roleToDelete = action.id;
|
||||
|
||||
break;
|
||||
}
|
||||
// Leaving this code for the moment
|
||||
// case 'ON_DUPLICATION': {
|
||||
// const { id } = action;
|
||||
// draftState.roles = state.roles.reduce((acc, c) => {
|
||||
// if (c.id === id) {
|
||||
// return acc.concat([c, { ...c, id: state.roles.length + 1 }]);
|
||||
// }
|
||||
|
||||
// return [...acc, c];
|
||||
// }, []);
|
||||
// break;
|
||||
// }
|
||||
default:
|
||||
return draftState;
|
||||
}
|
||||
|
@ -11,54 +11,18 @@ describe('ADMIN | ee | CONTAINERS | ROLES | ListPage | reducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ON_SELECTION', () => {
|
||||
it('should add the selected role correctly', () => {
|
||||
const action = {
|
||||
type: 'ON_SELECTION',
|
||||
id: 2,
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [],
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [2],
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should remove the selected role correctly', () => {
|
||||
const action = {
|
||||
type: 'ON_SELECTION',
|
||||
id: 2,
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [1, 2],
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [1],
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ON_REMOVE_ROLES', () => {
|
||||
it('should set the showModalConfirmButtonLoading to true', () => {
|
||||
const action = {
|
||||
type: 'ON_REMOVE_ROLES',
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [],
|
||||
roleToDelete: 1,
|
||||
shouldRefetchData: false,
|
||||
showModalConfirmButtonLoading: false,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [],
|
||||
roleToDelete: 1,
|
||||
shouldRefetchData: false,
|
||||
showModalConfirmButtonLoading: true,
|
||||
};
|
||||
@ -73,12 +37,12 @@ describe('ADMIN | ee | CONTAINERS | ROLES | ListPage | reducer', () => {
|
||||
type: 'ON_REMOVE_ROLES_SUCCEEDED',
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [],
|
||||
roleToDelete: 1,
|
||||
shouldRefetchData: false,
|
||||
showModalConfirmButtonLoading: true,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [],
|
||||
roleToDelete: null,
|
||||
shouldRefetchData: true,
|
||||
showModalConfirmButtonLoading: true,
|
||||
};
|
||||
@ -87,26 +51,6 @@ describe('ADMIN | ee | CONTAINERS | ROLES | ListPage | reducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RESET_DATA_TO_DELETE', () => {
|
||||
it('should empty the selected role array and set the shouldRefetchData to false', () => {
|
||||
const action = {
|
||||
type: 'RESET_DATA_TO_DELETE',
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [1, 2, 4],
|
||||
shouldRefetchData: true,
|
||||
showModalConfirmButtonLoading: true,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [],
|
||||
shouldRefetchData: false,
|
||||
showModalConfirmButtonLoading: false,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_ROLE_TO_DELETE', () => {
|
||||
it('should set the selected roles property correctly', () => {
|
||||
const action = {
|
||||
@ -114,11 +58,11 @@ describe('ADMIN | ee | CONTAINERS | ROLES | ListPage | reducer', () => {
|
||||
id: 6,
|
||||
};
|
||||
const initialState = {
|
||||
selectedRoles: [1, 2, 4],
|
||||
roleToDelete: null,
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
const expected = {
|
||||
selectedRoles: [6],
|
||||
roleToDelete: 6,
|
||||
shouldRefetchData: false,
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useQueryParams } from '@strapi/helper-plugin';
|
||||
import { SearchIcon } from '@strapi/icons';
|
||||
import { IconButton } from '@strapi/parts/IconButton';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import useQueryParams from '../../hooks/useQueryParams';
|
||||
|
||||
const Search = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [{ query }, setQuery] = useQueryParams();
|
||||
const [value, setValue] = useState(query._q || '');
|
||||
const [value, setValue] = useState(query?._q || '');
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
@ -179,6 +179,7 @@ export { default as EmptyBodyTable } from './components/EmptyBodyTable';
|
||||
export * from './components/InjectionZone';
|
||||
export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPage';
|
||||
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
|
||||
export { default as Search } from './components/Search';
|
||||
export { default as Status } from './components/Status';
|
||||
|
||||
// New icons
|
||||
|
Loading…
x
Reference in New Issue
Block a user