mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): replace useTableSearchParams
with useSearchParametersWatcher
(#3027)
This commit is contained in:
parent
f16f9d2403
commit
bc62796e5c
15 changed files with 182 additions and 212 deletions
|
@ -9,7 +9,7 @@ import ApplicationName from '@/components/ApplicationName';
|
||||||
import UserName from '@/components/UserName';
|
import UserName from '@/components/UserName';
|
||||||
import { defaultPageSize } from '@/consts';
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import usePageSearchParameters from '@/hooks/use-page-search-parameters';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl } from '@/utilities/url';
|
||||||
|
|
||||||
import Table from '../Table';
|
import Table from '../Table';
|
||||||
|
@ -28,7 +28,7 @@ const AuditLogTable = ({ userId, className }: Props) => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const pageSize = defaultPageSize;
|
const pageSize = defaultPageSize;
|
||||||
const [{ page, event, applicationId }, updatePageSearchParameters] = usePageSearchParameters({
|
const [{ page, event, applicationId }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
page: 1,
|
page: 1,
|
||||||
event: '',
|
event: '',
|
||||||
applicationId: '',
|
applicationId: '',
|
||||||
|
@ -103,7 +103,7 @@ const AuditLogTable = ({ userId, className }: Props) => {
|
||||||
<EventSelector
|
<EventSelector
|
||||||
value={event}
|
value={event}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
updatePageSearchParameters({ event, page: undefined });
|
updateSearchParameters({ event, page: undefined });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,18 +111,18 @@ const AuditLogTable = ({ userId, className }: Props) => {
|
||||||
<ApplicationSelector
|
<ApplicationSelector
|
||||||
value={applicationId}
|
value={applicationId}
|
||||||
onChange={(applicationId) => {
|
onChange={(applicationId) => {
|
||||||
updatePageSearchParameters({ applicationId, page: undefined });
|
updateSearchParameters({ applicationId, page: undefined });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex: Number(page),
|
page,
|
||||||
totalCount,
|
totalCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
onChange: (page) => {
|
onChange: (page) => {
|
||||||
updatePageSearchParameters({ page });
|
updateSearchParameters({ page });
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Previous from './Previous';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
pageIndex: number;
|
page: number;
|
||||||
totalCount?: number;
|
totalCount?: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -20,7 +20,7 @@ export type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Pagination = ({
|
const Pagination = ({
|
||||||
pageIndex,
|
page,
|
||||||
totalCount,
|
totalCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
className,
|
className,
|
||||||
|
@ -42,8 +42,8 @@ const Pagination = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const min = (pageIndex - 1) * pageSize + 1;
|
const min = (page - 1) * pageSize + 1;
|
||||||
const max = Math.min(pageIndex * pageSize, cachedTotalCount);
|
const max = Math.min(page * pageSize, cachedTotalCount);
|
||||||
const isPicoMode = mode === 'pico';
|
const isPicoMode = mode === 'pico';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -54,13 +54,13 @@ const Pagination = ({
|
||||||
<ReactPaginate
|
<ReactPaginate
|
||||||
className={styles.pagination}
|
className={styles.pagination}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
forcePage={pageIndex - 1}
|
forcePage={page - 1}
|
||||||
pageLabelBuilder={(page: number) => (
|
pageLabelBuilder={(pageNumber: number) => (
|
||||||
<Button
|
<Button
|
||||||
type={page === pageIndex ? 'outline' : 'default'}
|
type={pageNumber === page ? 'outline' : 'default'}
|
||||||
className={classNames(styles.button, page === pageIndex && styles.active)}
|
className={classNames(styles.button, pageNumber === page && styles.active)}
|
||||||
size="small"
|
size="small"
|
||||||
title={<DangerousRaw>{page}</DangerousRaw>}
|
title={<DangerousRaw>{pageNumber}</DangerousRaw>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
previousLabel={<Button className={styles.button} size="small" icon={<Previous />} />}
|
previousLabel={<Button className={styles.button} size="small" icon={<Previous />} />}
|
||||||
|
|
|
@ -13,9 +13,8 @@ import TextInput from '@/components/TextInput';
|
||||||
import { defaultPageSize } from '@/consts';
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useDebounce from '@/hooks/use-debounce';
|
import useDebounce from '@/hooks/use-debounce';
|
||||||
import { formatKeyword } from '@/hooks/use-table-search-params';
|
|
||||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import SourceUserItem from '../SourceUserItem';
|
import SourceUserItem from '../SourceUserItem';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -26,20 +25,21 @@ type Props = {
|
||||||
selectedUsers: User[];
|
selectedUsers: User[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
const searchDelay = 500;
|
const searchDelay = 500;
|
||||||
|
|
||||||
const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
|
const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [pageIndex, setPageIndex] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
const debounce = useDebounce();
|
const debounce = useDebounce();
|
||||||
|
|
||||||
const url = buildUrl('/api/users', {
|
const url = buildUrl('/api/users', {
|
||||||
excludeRoleId: roleId,
|
excludeRoleId: roleId,
|
||||||
hideAdminUser: String(true),
|
hideAdminUser: String(true),
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(defaultPageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error } = useSWR<[User[], number], RequestError>(url);
|
const { data, error } = useSWR<[User[], number], RequestError>(url);
|
||||||
|
@ -50,7 +50,7 @@ const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
|
||||||
|
|
||||||
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
setPageIndex(1);
|
setPage(1);
|
||||||
setKeyword(event.target.value);
|
setKeyword(event.target.value);
|
||||||
}, searchDelay);
|
}, searchDelay);
|
||||||
};
|
};
|
||||||
|
@ -97,12 +97,12 @@ const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<Pagination
|
<Pagination
|
||||||
mode="pico"
|
mode="pico"
|
||||||
pageIndex={pageIndex}
|
page={page}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
pageSize={defaultPageSize}
|
pageSize={pageSize}
|
||||||
className={transferLayout.boxPagination}
|
className={transferLayout.boxPagination}
|
||||||
onChange={(page) => {
|
onChange={(page) => {
|
||||||
setPageIndex(page);
|
setPage(page);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Search from '@/assets/images/search.svg';
|
||||||
import DataEmpty from '@/components/DataEmpty';
|
import DataEmpty from '@/components/DataEmpty';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
import TextInput from '@/components/TextInput';
|
import TextInput from '@/components/TextInput';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useDebounce from '@/hooks/use-debounce';
|
import useDebounce from '@/hooks/use-debounce';
|
||||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||||
|
@ -24,20 +25,20 @@ type Props = {
|
||||||
onChange: (value: RoleResponse[]) => void;
|
onChange: (value: RoleResponse[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = defaultPageSize;
|
||||||
const searchDelay = 500;
|
const searchDelay = 500;
|
||||||
|
|
||||||
const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
|
const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const [pageIndex, setPageIndex] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
|
|
||||||
const debounce = useDebounce();
|
const debounce = useDebounce();
|
||||||
|
|
||||||
const url = buildUrl('/api/roles', {
|
const url = buildUrl('/api/roles', {
|
||||||
excludeUserId: userId,
|
excludeUserId: userId,
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: `%${keyword}%` }),
|
...conditional(keyword && { search: `%${keyword}%` }),
|
||||||
});
|
});
|
||||||
|
@ -53,7 +54,7 @@ const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
|
||||||
|
|
||||||
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
setPageIndex(1);
|
setPage(1);
|
||||||
setKeyword(event.target.value);
|
setKeyword(event.target.value);
|
||||||
}, searchDelay);
|
}, searchDelay);
|
||||||
};
|
};
|
||||||
|
@ -98,12 +99,12 @@ const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<Pagination
|
<Pagination
|
||||||
mode="pico"
|
mode="pico"
|
||||||
pageIndex={pageIndex}
|
page={page}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
className={transferLayout.boxPagination}
|
className={transferLayout.boxPagination}
|
||||||
onChange={(page) => {
|
onChange={(page) => {
|
||||||
setPageIndex(page);
|
setPage(page);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
type Parameters = Record<string, string | number>;
|
type Parameters = Record<string, string | number>;
|
||||||
|
|
||||||
type UsePageSearchParametersReturn<T extends Parameters = Parameters> = [
|
type UseSearchParametersWatcherReturn<T extends Parameters = Parameters> = [
|
||||||
{
|
{
|
||||||
[K in keyof T]: T[K];
|
[K in keyof T]: T[K];
|
||||||
},
|
},
|
||||||
|
@ -12,17 +12,17 @@ type UsePageSearchParametersReturn<T extends Parameters = Parameters> = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage page search parameters
|
* Watch search parameters
|
||||||
*
|
*
|
||||||
* @param config Define search parameter keys and their default value. E.g., `{ page: 1, keyword: '' }`
|
* @param config Define search parameter keys and their default value. E.g., `{ page: 1, keyword: '' }`
|
||||||
* @returns [pageSearchParams, updatePageSearchParams]
|
* @returns [searchParams, updateSearchParams]
|
||||||
*/
|
*/
|
||||||
const usePageSearchParameters = <T extends Parameters>(
|
const useSearchParametersWatcher = <T extends Parameters>(
|
||||||
config: T
|
config: T
|
||||||
): UsePageSearchParametersReturn<T> => {
|
): UseSearchParametersWatcherReturn<T> => {
|
||||||
const [searchParameters, setSearchParameters] = useSearchParams();
|
const [searchParameters, setSearchParameters] = useSearchParams();
|
||||||
|
|
||||||
const updatePageSearchParameters = useCallback(
|
const updateSearchParameters = useCallback(
|
||||||
(parameters: Partial<T>) => {
|
(parameters: Partial<T>) => {
|
||||||
const baseParameters = new URLSearchParams(searchParameters);
|
const baseParameters = new URLSearchParams(searchParameters);
|
||||||
|
|
||||||
|
@ -54,9 +54,9 @@ const usePageSearchParameters = <T extends Parameters>(
|
||||||
|
|
||||||
return [parameterKey, parameterValue];
|
return [parameterKey, parameterValue];
|
||||||
})
|
})
|
||||||
) as UsePageSearchParametersReturn<T>[0],
|
) as UseSearchParametersWatcherReturn<T>[0],
|
||||||
updatePageSearchParameters,
|
updateSearchParameters,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePageSearchParameters;
|
export default useSearchParametersWatcher;
|
|
@ -1,62 +0,0 @@
|
||||||
/* eslint-disable unicorn/prevent-abbreviations */
|
|
||||||
import type { Optional } from '@silverhand/essentials';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { defaultPageSize } from '@/consts';
|
|
||||||
|
|
||||||
type Props = Optional<{
|
|
||||||
pageSize?: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const pageIndexKey = 'page';
|
|
||||||
const keywordKey = 'keyword';
|
|
||||||
|
|
||||||
export const formatKeyword = (keyword: string) => `%${keyword}%`;
|
|
||||||
|
|
||||||
const useTableSearchParams = ({ pageSize = defaultPageSize }: Props = {}) => {
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const [searchParamsState, setSearchParamsState] = useState<URLSearchParams>(searchParams);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSearchParams(searchParamsState);
|
|
||||||
}, [searchParamsState, setSearchParams]);
|
|
||||||
|
|
||||||
const setPageIndex = (pageIndex: number) => {
|
|
||||||
setSearchParamsState((previousParams) => {
|
|
||||||
const params = new URLSearchParams(previousParams);
|
|
||||||
params.set(pageIndexKey, String(pageIndex));
|
|
||||||
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setKeyword = (value: string) => {
|
|
||||||
setSearchParamsState((previousParams) => {
|
|
||||||
const params = new URLSearchParams(previousParams);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
params.set(keywordKey, value);
|
|
||||||
} else {
|
|
||||||
params.delete(keywordKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
pagination: {
|
|
||||||
pageIndex: Number(searchParamsState.get(pageIndexKey) ?? 1),
|
|
||||||
pageSize,
|
|
||||||
setPageIndex,
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
keyword: searchParamsState.get(keywordKey) ?? '',
|
|
||||||
setKeyword,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useTableSearchParams;
|
|
||||||
/* eslint-enable unicorn/prevent-abbreviations */
|
|
|
@ -8,14 +8,17 @@ import useSWR from 'swr';
|
||||||
|
|
||||||
import ConfirmModal from '@/components/ConfirmModal';
|
import ConfirmModal from '@/components/ConfirmModal';
|
||||||
import PermissionsTable from '@/components/PermissionsTable';
|
import PermissionsTable from '@/components/PermissionsTable';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import type { ApiResourceDetailsOutletContext } from '../types';
|
import type { ApiResourceDetailsOutletContext } from '../types';
|
||||||
import CreatePermissionModal from './components/CreatePermissionModal';
|
import CreatePermissionModal from './components/CreatePermissionModal';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const ApiResourcePermissions = () => {
|
const ApiResourcePermissions = () => {
|
||||||
const {
|
const {
|
||||||
resource: { id: resourceId },
|
resource: { id: resourceId },
|
||||||
|
@ -24,17 +27,17 @@ const ApiResourcePermissions = () => {
|
||||||
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const {
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
page: 1,
|
||||||
search: { keyword, setKeyword },
|
keyword: '',
|
||||||
} = useTableSearchParams();
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
||||||
resourceId &&
|
resourceId &&
|
||||||
buildUrl(`/api/resources/${resourceId}/scopes`, {
|
buildUrl(`/api/resources/${resourceId}/scopes`, {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -77,20 +80,26 @@ const ApiResourcePermissions = () => {
|
||||||
errorMessage={error?.body?.message ?? error?.message}
|
errorMessage={error?.body?.message ?? error?.message}
|
||||||
retryHandler={async () => mutate(undefined, true)}
|
retryHandler={async () => mutate(undefined, true)}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
search={{
|
search={{
|
||||||
keyword,
|
keyword,
|
||||||
searchHandler: (value) => {
|
searchHandler: (keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({
|
||||||
setPageIndex(1);
|
keyword,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
clearSearchHandler: () => {
|
clearSearchHandler: () => {
|
||||||
setKeyword('');
|
updateSearchParameters({
|
||||||
setPageIndex(1);
|
keyword: '',
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { AppearanceMode } from '@logto/schemas';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
|
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
|
||||||
|
@ -15,8 +15,10 @@ import CopyToClipboard from '@/components/CopyToClipboard';
|
||||||
import ItemPreview from '@/components/ItemPreview';
|
import ItemPreview from '@/components/ItemPreview';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
|
import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import * as modalStyles from '@/scss/modal.module.scss';
|
import * as modalStyles from '@/scss/modal.module.scss';
|
||||||
import * as resourcesStyles from '@/scss/resources.module.scss';
|
import * as resourcesStyles from '@/scss/resources.module.scss';
|
||||||
|
@ -25,23 +27,23 @@ import { buildUrl } from '@/utilities/url';
|
||||||
import CreateForm from './components/CreateForm';
|
import CreateForm from './components/CreateForm';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
const apiResourcesPathname = '/api-resources';
|
const apiResourcesPathname = '/api-resources';
|
||||||
const createApiResourcePathname = `${apiResourcesPathname}/create`;
|
const createApiResourcePathname = `${apiResourcesPathname}/create`;
|
||||||
const buildDetailsPathname = (id: string) =>
|
const buildDetailsPathname = (id: string) =>
|
||||||
`${apiResourcesPathname}/${id}/${ApiResourceDetailsTabs.Settings}`;
|
`${apiResourcesPathname}/${id}/${ApiResourceDetailsTabs.Settings}`;
|
||||||
|
|
||||||
const pageSize = 20;
|
|
||||||
|
|
||||||
const ApiResources = () => {
|
const ApiResources = () => {
|
||||||
const { pathname } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const isCreateNew = pathname.endsWith('/create');
|
const isCreateNew = pathname.endsWith('/create');
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
|
||||||
const search = query.toString();
|
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
page: 1,
|
||||||
|
});
|
||||||
|
|
||||||
const url = buildUrl('/api/resources', {
|
const url = buildUrl('/api/resources', {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -146,12 +148,12 @@ const ApiResources = () => {
|
||||||
onRetry={async () => mutate(undefined, true)}
|
onRetry={async () => mutate(undefined, true)}
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
pageIndex={pageIndex}
|
page={page}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
className={styles.pagination}
|
className={styles.pagination}
|
||||||
onChange={(page) => {
|
onChange={(page) => {
|
||||||
setQuery({ page: String(page) });
|
updateSearchParameters({ page });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Application } from '@logto/schemas';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import Plus from '@/assets/images/plus.svg';
|
import Plus from '@/assets/images/plus.svg';
|
||||||
|
@ -13,7 +13,9 @@ import CopyToClipboard from '@/components/CopyToClipboard';
|
||||||
import ItemPreview from '@/components/ItemPreview';
|
import ItemPreview from '@/components/ItemPreview';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import * as modalStyles from '@/scss/modal.module.scss';
|
import * as modalStyles from '@/scss/modal.module.scss';
|
||||||
import * as resourcesStyles from '@/scss/resources.module.scss';
|
import * as resourcesStyles from '@/scss/resources.module.scss';
|
||||||
import { applicationTypeI18nKey } from '@/types/applications';
|
import { applicationTypeI18nKey } from '@/types/applications';
|
||||||
|
@ -22,22 +24,23 @@ import { buildUrl } from '@/utilities/url';
|
||||||
import CreateForm from './components/CreateForm';
|
import CreateForm from './components/CreateForm';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const applicationsPathname = '/applications';
|
const applicationsPathname = '/applications';
|
||||||
const createApplicationPathname = `${applicationsPathname}/create`;
|
const createApplicationPathname = `${applicationsPathname}/create`;
|
||||||
const buildDetailsPathname = (id: string) => `${applicationsPathname}/${id}`;
|
const buildDetailsPathname = (id: string) => `${applicationsPathname}/${id}`;
|
||||||
|
|
||||||
const Applications = () => {
|
const Applications = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pathname } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const isCreateNew = pathname === createApplicationPathname;
|
const isCreateNew = pathname === createApplicationPathname;
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
|
||||||
const search = query.toString();
|
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
page: 1,
|
||||||
|
});
|
||||||
|
|
||||||
const url = buildUrl('/api/applications', {
|
const url = buildUrl('/api/applications', {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,12 +140,12 @@ const Applications = () => {
|
||||||
onRetry={async () => mutate(undefined, true)}
|
onRetry={async () => mutate(undefined, true)}
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
pageIndex={pageIndex}
|
page={page}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
className={styles.pagination}
|
className={styles.pagination}
|
||||||
onChange={(page) => {
|
onChange={(page) => {
|
||||||
setQuery({ page: String(page) });
|
updateSearchParameters({ page });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,14 +8,17 @@ import useSWR from 'swr';
|
||||||
|
|
||||||
import ConfirmModal from '@/components/ConfirmModal';
|
import ConfirmModal from '@/components/ConfirmModal';
|
||||||
import PermissionsTable from '@/components/PermissionsTable';
|
import PermissionsTable from '@/components/PermissionsTable';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import type { RoleDetailsOutletContext } from '../types';
|
import type { RoleDetailsOutletContext } from '../types';
|
||||||
import AssignPermissionsModal from './components/AssignPermissionsModal';
|
import AssignPermissionsModal from './components/AssignPermissionsModal';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const RolePermissions = () => {
|
const RolePermissions = () => {
|
||||||
const {
|
const {
|
||||||
role: { id: roleId },
|
role: { id: roleId },
|
||||||
|
@ -23,17 +26,17 @@ const RolePermissions = () => {
|
||||||
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const {
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
page: 1,
|
||||||
search: { keyword, setKeyword },
|
keyword: '',
|
||||||
} = useTableSearchParams();
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
||||||
roleId &&
|
roleId &&
|
||||||
buildUrl(`/api/roles/${roleId}/scopes`, {
|
buildUrl(`/api/roles/${roleId}/scopes`, {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -80,20 +83,20 @@ const RolePermissions = () => {
|
||||||
errorMessage={error?.body?.message ?? error?.message}
|
errorMessage={error?.body?.message ?? error?.message}
|
||||||
retryHandler={async () => mutate(undefined, true)}
|
retryHandler={async () => mutate(undefined, true)}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
search={{
|
search={{
|
||||||
keyword,
|
keyword,
|
||||||
searchHandler: (value) => {
|
searchHandler: (keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({ keyword, page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
},
|
},
|
||||||
clearSearchHandler: () => {
|
clearSearchHandler: () => {
|
||||||
setKeyword('');
|
updateSearchParameters({ keyword: '', page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,15 +18,18 @@ import Search from '@/components/Search';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { Tooltip } from '@/components/Tip';
|
import { Tooltip } from '@/components/Tip';
|
||||||
import UserAvatar from '@/components/UserAvatar';
|
import UserAvatar from '@/components/UserAvatar';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import type { RoleDetailsOutletContext } from '../types';
|
import type { RoleDetailsOutletContext } from '../types';
|
||||||
import AssignUsersModal from './components/AssignUsersModal';
|
import AssignUsersModal from './components/AssignUsersModal';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const RoleUsers = () => {
|
const RoleUsers = () => {
|
||||||
const {
|
const {
|
||||||
role: { id: roleId },
|
role: { id: roleId },
|
||||||
|
@ -34,17 +37,17 @@ const RoleUsers = () => {
|
||||||
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const {
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
page: 1,
|
||||||
search: { keyword, setKeyword },
|
keyword: '',
|
||||||
} = useTableSearchParams();
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
|
||||||
roleId &&
|
roleId &&
|
||||||
buildUrl(`/api/roles/${roleId}/users`, {
|
buildUrl(`/api/roles/${roleId}/users`, {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -132,13 +135,11 @@ const RoleUsers = () => {
|
||||||
defaultValue={keyword}
|
defaultValue={keyword}
|
||||||
isClearable={Boolean(keyword)}
|
isClearable={Boolean(keyword)}
|
||||||
placeholder={t('general.search_placeholder')}
|
placeholder={t('general.search_placeholder')}
|
||||||
onSearch={(value) => {
|
onSearch={(keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({ keyword, page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
onClearSearch={() => {
|
onClearSearch={() => {
|
||||||
setKeyword('');
|
updateSearchParameters({ keyword: '', page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -153,10 +154,12 @@ const RoleUsers = () => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={{
|
||||||
content: (
|
content: (
|
||||||
|
|
|
@ -10,10 +10,11 @@ import CardTitle from '@/components/CardTitle';
|
||||||
import ItemPreview from '@/components/ItemPreview';
|
import ItemPreview from '@/components/ItemPreview';
|
||||||
import Search from '@/components/Search';
|
import Search from '@/components/Search';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import * as pageStyles from '@/scss/resources.module.scss';
|
import * as pageStyles from '@/scss/resources.module.scss';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import AssignedUsers from './components/AssignedUsers';
|
import AssignedUsers from './components/AssignedUsers';
|
||||||
import CreateRoleModal from './components/CreateRoleModal';
|
import CreateRoleModal from './components/CreateRoleModal';
|
||||||
|
@ -23,21 +24,23 @@ const rolesPathname = '/roles';
|
||||||
const createRolePathname = `${rolesPathname}/create`;
|
const createRolePathname = `${rolesPathname}/create`;
|
||||||
const buildDetailsPathname = (id: string) => `${rolesPathname}/${id}`;
|
const buildDetailsPathname = (id: string) => `${rolesPathname}/${id}`;
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const Roles = () => {
|
const Roles = () => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { pathname, search } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isOnCreatePage = pathname === createRolePathname;
|
const isOnCreatePage = pathname === createRolePathname;
|
||||||
|
|
||||||
const {
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
page: 1,
|
||||||
search: { keyword, setKeyword },
|
keyword: '',
|
||||||
} = useTableSearchParams();
|
});
|
||||||
|
|
||||||
const url = buildUrl('/api/roles', {
|
const url = buildUrl('/api/roles', {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[RoleResponse[], number], RequestError>(url);
|
const { data, error, mutate } = useSWR<[RoleResponse[], number], RequestError>(url);
|
||||||
|
@ -97,21 +100,21 @@ const Roles = () => {
|
||||||
placeholder={t('roles.search')}
|
placeholder={t('roles.search')}
|
||||||
defaultValue={keyword}
|
defaultValue={keyword}
|
||||||
isClearable={Boolean(keyword)}
|
isClearable={Boolean(keyword)}
|
||||||
onSearch={(value) => {
|
onSearch={(keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({ keyword, page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
onClearSearch={() => {
|
onClearSearch={() => {
|
||||||
setKeyword('');
|
updateSearchParameters({ keyword: '', page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
totalCount,
|
totalCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={{
|
||||||
content: (
|
content: (
|
||||||
|
|
|
@ -15,31 +15,34 @@ import Search from '@/components/Search';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import TextLink from '@/components/TextLink';
|
import TextLink from '@/components/TextLink';
|
||||||
import { Tooltip } from '@/components/Tip';
|
import { Tooltip } from '@/components/Tip';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import type { UserDetailsOutletContext } from '../types';
|
import type { UserDetailsOutletContext } from '../types';
|
||||||
import AssignRolesModal from './components/AssignRolesModal';
|
import AssignRolesModal from './components/AssignRolesModal';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
|
|
||||||
const UserRoles = () => {
|
const UserRoles = () => {
|
||||||
const { user } = useOutletContext<UserDetailsOutletContext>();
|
const { user } = useOutletContext<UserDetailsOutletContext>();
|
||||||
const { id: userId } = user;
|
const { id: userId } = user;
|
||||||
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const {
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
page: 1,
|
||||||
search: { keyword, setKeyword },
|
keyword: '',
|
||||||
} = useTableSearchParams();
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[Role[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[Role[], number], RequestError>(
|
||||||
buildUrl(`/api/users/${userId}/roles`, {
|
buildUrl(`/api/users/${userId}/roles`, {
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -117,13 +120,11 @@ const UserRoles = () => {
|
||||||
defaultValue={keyword}
|
defaultValue={keyword}
|
||||||
isClearable={Boolean(keyword)}
|
isClearable={Boolean(keyword)}
|
||||||
placeholder={t('user_details.roles.search')}
|
placeholder={t('user_details.roles.search')}
|
||||||
onSearch={(value) => {
|
onSearch={(keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({ keyword, page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
onClearSearch={() => {
|
onClearSearch={() => {
|
||||||
setKeyword('');
|
updateSearchParameters({ keyword: '', page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -138,10 +139,12 @@ const UserRoles = () => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={{
|
||||||
content: (
|
content: (
|
||||||
|
|
|
@ -13,15 +13,17 @@ import ItemPreview from '@/components/ItemPreview';
|
||||||
import Search from '@/components/Search';
|
import Search from '@/components/Search';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import UserAvatar from '@/components/UserAvatar';
|
import UserAvatar from '@/components/UserAvatar';
|
||||||
|
import { defaultPageSize } from '@/consts';
|
||||||
import { UserDetailsTabs } from '@/consts/page-tabs';
|
import { UserDetailsTabs } from '@/consts/page-tabs';
|
||||||
import type { RequestError } from '@/hooks/use-api';
|
import type { RequestError } from '@/hooks/use-api';
|
||||||
import useTableSearchParams from '@/hooks/use-table-search-params';
|
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
|
||||||
import * as resourcesStyles from '@/scss/resources.module.scss';
|
import * as resourcesStyles from '@/scss/resources.module.scss';
|
||||||
import { buildUrl } from '@/utilities/url';
|
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
|
||||||
|
|
||||||
import CreateForm from './components/CreateForm';
|
import CreateForm from './components/CreateForm';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const pageSize = defaultPageSize;
|
||||||
const usersPathname = '/users';
|
const usersPathname = '/users';
|
||||||
const createUserPathname = `${usersPathname}/create`;
|
const createUserPathname = `${usersPathname}/create`;
|
||||||
const buildDetailsPathname = (id: string) => `${usersPathname}/${id}/${UserDetailsTabs.Settings}`;
|
const buildDetailsPathname = (id: string) => `${usersPathname}/${id}/${UserDetailsTabs.Settings}`;
|
||||||
|
@ -30,16 +32,17 @@ const Users = () => {
|
||||||
const { pathname, search } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const isCreateNew = pathname === createUserPathname;
|
const isCreateNew = pathname === createUserPathname;
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const {
|
|
||||||
pagination: { pageIndex, pageSize, setPageIndex },
|
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
|
||||||
search: { keyword, setKeyword },
|
page: 1,
|
||||||
} = useTableSearchParams();
|
keyword: '',
|
||||||
|
});
|
||||||
|
|
||||||
const url = buildUrl('/api/users', {
|
const url = buildUrl('/api/users', {
|
||||||
hideAdminUser: String(true),
|
hideAdminUser: String(true),
|
||||||
page: String(pageIndex),
|
page: String(page),
|
||||||
page_size: String(pageSize),
|
page_size: String(pageSize),
|
||||||
...conditional(keyword && { search: `%${keyword}%` }),
|
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<[User[], number], RequestError>(url);
|
const { data, error, mutate } = useSWR<[User[], number], RequestError>(url);
|
||||||
|
@ -115,13 +118,11 @@ const Users = () => {
|
||||||
placeholder={t('users.search')}
|
placeholder={t('users.search')}
|
||||||
defaultValue={keyword}
|
defaultValue={keyword}
|
||||||
isClearable={Boolean(keyword)}
|
isClearable={Boolean(keyword)}
|
||||||
onSearch={(value) => {
|
onSearch={(keyword) => {
|
||||||
setKeyword(value);
|
updateSearchParameters({ keyword, page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
onClearSearch={() => {
|
onClearSearch={() => {
|
||||||
setKeyword('');
|
updateSearchParameters({ keyword: '', page: 1 });
|
||||||
setPageIndex(1);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -143,10 +144,12 @@ const Users = () => {
|
||||||
navigate(buildDetailsPathname(id));
|
navigate(buildDetailsPathname(id));
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
onChange: setPageIndex,
|
onChange: (page) => {
|
||||||
|
updateSearchParameters({ page });
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onRetry={async () => mutate(undefined, true)}
|
onRetry={async () => mutate(undefined, true)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
export const buildUrl = (path: string, searchParameters: Record<string, string>) =>
|
export const buildUrl = (path: string, searchParameters: Record<string, string>) =>
|
||||||
`${path}?${new URLSearchParams(searchParameters).toString()}`;
|
`${path}?${new URLSearchParams(searchParameters).toString()}`;
|
||||||
|
|
||||||
|
export const formatSearchKeyword = (keyword: string) => `%${keyword}%`;
|
||||||
|
|
Loading…
Reference in a new issue