diff --git a/packages/console/src/assets/images/permissions-empty-dark.svg b/packages/console/src/assets/images/permissions-empty-dark.svg
new file mode 100644
index 000000000..f22616e0c
--- /dev/null
+++ b/packages/console/src/assets/images/permissions-empty-dark.svg
@@ -0,0 +1,83 @@
+
diff --git a/packages/console/src/assets/images/permissions-empty.svg b/packages/console/src/assets/images/permissions-empty.svg
new file mode 100644
index 000000000..f25d15829
--- /dev/null
+++ b/packages/console/src/assets/images/permissions-empty.svg
@@ -0,0 +1,80 @@
+
diff --git a/packages/console/src/assets/images/roles-empty-dark.svg b/packages/console/src/assets/images/roles-empty-dark.svg
new file mode 100644
index 000000000..fdd5875dc
--- /dev/null
+++ b/packages/console/src/assets/images/roles-empty-dark.svg
@@ -0,0 +1,89 @@
+
diff --git a/packages/console/src/assets/images/roles-empty.svg b/packages/console/src/assets/images/roles-empty.svg
new file mode 100644
index 000000000..970a68056
--- /dev/null
+++ b/packages/console/src/assets/images/roles-empty.svg
@@ -0,0 +1,84 @@
+
diff --git a/packages/console/src/assets/images/users-empty-dark.svg b/packages/console/src/assets/images/users-empty-dark.svg
new file mode 100644
index 000000000..acdf8b4c1
--- /dev/null
+++ b/packages/console/src/assets/images/users-empty-dark.svg
@@ -0,0 +1,70 @@
+
diff --git a/packages/console/src/assets/images/users-empty.svg b/packages/console/src/assets/images/users-empty.svg
new file mode 100644
index 000000000..c7fc01528
--- /dev/null
+++ b/packages/console/src/assets/images/users-empty.svg
@@ -0,0 +1,70 @@
+
diff --git a/packages/console/src/components/AuditLogTable/index.tsx b/packages/console/src/components/AuditLogTable/index.tsx
index 2fa2902c1..aa2a849b2 100644
--- a/packages/console/src/components/AuditLogTable/index.tsx
+++ b/packages/console/src/components/AuditLogTable/index.tsx
@@ -12,6 +12,7 @@ import type { RequestError } from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl } from '@/utils/url';
+import EmptyDataPlaceholder from '../EmptyDataPlaceholder';
import Table from '../Table';
import type { Column } from '../Table/types';
import ApplicationSelector from './components/ApplicationSelector';
@@ -117,6 +118,7 @@ const AuditLogTable = ({ userId, className }: Props) => {
}
+ placeholder={}
pagination={{
page,
totalCount,
diff --git a/packages/console/src/components/DataEmpty/index.tsx b/packages/console/src/components/DataEmpty/index.tsx
deleted file mode 100644
index 949cbf84e..000000000
--- a/packages/console/src/components/DataEmpty/index.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { AppearanceMode } from '@logto/schemas';
-import type { ReactNode } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import EmptyDark from '@/assets/images/table-empty-dark.svg';
-import Empty from '@/assets/images/table-empty.svg';
-import { useTheme } from '@/hooks/use-theme';
-
-import * as styles from './index.module.scss';
-
-export type Props = {
- title?: string;
- description?: string;
- image?: ReactNode;
- children?: ReactNode;
- imageClassName?: string;
-};
-
-const DataEmpty = ({ title, description, image, imageClassName, children }: Props) => {
- const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
-
- const theme = useTheme();
-
- return (
-
- {image ??
- (theme === AppearanceMode.LightMode ? (
-
- ) : (
-
- ))}
-
{title ?? t('errors.empty')}
- {description &&
{description}
}
- {children}
-
- );
-};
-
-export default DataEmpty;
diff --git a/packages/console/src/components/DataEmpty/index.module.scss b/packages/console/src/components/EmptyDataPlaceholder/index.module.scss
similarity index 52%
rename from packages/console/src/components/DataEmpty/index.module.scss
rename to packages/console/src/components/EmptyDataPlaceholder/index.module.scss
index 194814704..eecfa7350 100644
--- a/packages/console/src/components/DataEmpty/index.module.scss
+++ b/packages/console/src/components/EmptyDataPlaceholder/index.module.scss
@@ -16,4 +16,27 @@
color: var(--color-neutral-50);
margin-bottom: _.unit(2);
}
+
+ &.large {
+ .image {
+ width: 256px;
+ height: 256px;
+ margin-bottom: _.unit(6);
+ }
+ }
+
+ &.medium {
+ .image {
+ width: 200px;
+ height: 200px;
+ margin-bottom: _.unit(6);
+ }
+ }
+
+ &.small {
+ .image {
+ width: 128px;
+ height: 128px;
+ }
+ }
}
diff --git a/packages/console/src/components/EmptyDataPlaceholder/index.tsx b/packages/console/src/components/EmptyDataPlaceholder/index.tsx
new file mode 100644
index 000000000..3591451df
--- /dev/null
+++ b/packages/console/src/components/EmptyDataPlaceholder/index.tsx
@@ -0,0 +1,29 @@
+import { AppearanceMode } from '@logto/schemas';
+import classNames from 'classnames';
+import { useTranslation } from 'react-i18next';
+
+import EmptyDark from '@/assets/images/table-empty-dark.svg';
+import Empty from '@/assets/images/table-empty.svg';
+import { useTheme } from '@/hooks/use-theme';
+
+import * as styles from './index.module.scss';
+
+export type Props = {
+ title?: string;
+ size?: 'large' | 'medium' | 'small';
+};
+
+const EmptyDataPlaceholder = ({ title, size = 'medium' }: Props) => {
+ const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
+ const theme = useTheme();
+ const EmptyImage = theme === AppearanceMode.LightMode ? Empty : EmptyDark;
+
+ return (
+
+
+
{title ?? t('errors.empty')}
+
+ );
+};
+
+export default EmptyDataPlaceholder;
diff --git a/packages/console/src/components/PermissionsTable/index.tsx b/packages/console/src/components/PermissionsTable/index.tsx
index 810005fcc..2c037f380 100644
--- a/packages/console/src/components/PermissionsTable/index.tsx
+++ b/packages/console/src/components/PermissionsTable/index.tsx
@@ -4,6 +4,8 @@ import { conditional } from '@silverhand/essentials';
import { useTranslation } from 'react-i18next';
import Delete from '@/assets/images/delete.svg';
+import PermissionsEmptyDark from '@/assets/images/permissions-empty-dark.svg';
+import PermissionsEmpty from '@/assets/images/permissions-empty.svg';
import Plus from '@/assets/images/plus.svg';
import Button from '@/components/Button';
import IconButton from '@/components/IconButton';
@@ -13,8 +15,11 @@ import type { Column } from '@/components/Table/types';
import TextLink from '@/components/TextLink';
import { Tooltip } from '@/components/Tip';
import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
+import useDocumentationUrl from '@/hooks/use-documentation-url';
+import EmptyDataPlaceholder from '../EmptyDataPlaceholder';
import type { Props as PaginationProps } from '../Pagination';
+import TablePlaceholder from '../Table/TablePlaceholder';
import * as styles from './index.module.scss';
type SearchProps = {
@@ -31,6 +36,7 @@ type Props = {
deleteButtonTitle?: AdminConsoleKey;
isReadOnly?: boolean;
isApiColumnVisible?: boolean;
+ isCreateGuideVisible?: boolean;
pagination?: PaginationProps;
search: SearchProps;
createHandler: () => void;
@@ -46,6 +52,7 @@ const PermissionsTable = ({
deleteButtonTitle = 'general.delete',
isReadOnly = false,
isApiColumnVisible = false,
+ isCreateGuideVisible = false,
pagination,
search: { keyword, searchHandler, clearSearchHandler },
createHandler,
@@ -53,6 +60,7 @@ const PermissionsTable = ({
retryHandler,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
+ const { getDocumentationUrl } = useDocumentationUrl();
const nameColumn: Column = {
title: t('permissions.name_column'),
@@ -146,17 +154,30 @@ const PermissionsTable = ({
}
isLoading={isLoading}
pagination={pagination}
- placeholder={{
- content: isReadOnly ? undefined : (
-