diff --git a/packages/console/src/components/Table/index.module.scss b/packages/console/src/components/Table/index.module.scss index 872cbc0eb..472c3a4f6 100644 --- a/packages/console/src/components/Table/index.module.scss +++ b/packages/console/src/components/Table/index.module.scss @@ -5,6 +5,17 @@ display: flex; flex-direction: column; + .filterContainer { + background-color: var(--color-layer-1); + border-radius: 12px 12px 0 0; + padding: _.unit(3) _.unit(3) 0; + + .filter { + border-bottom: 1px solid var(--color-divider); + padding-bottom: _.unit(3); + } + } + table { border: none; } @@ -25,6 +36,10 @@ } } } + + &.hideTopBorderRadius { + border-radius: 0; + } } .bodyTable { diff --git a/packages/console/src/components/Table/index.tsx b/packages/console/src/components/Table/index.tsx index 7114a908c..d067babf2 100644 --- a/packages/console/src/components/Table/index.tsx +++ b/packages/console/src/components/Table/index.tsx @@ -24,6 +24,7 @@ type Props< rowGroups: Array>; columns: Array>; rowIndexKey: TName; + filter?: ReactNode; isRowClickable?: (row: TFieldValues) => boolean; rowClickHandler?: (row: TFieldValues) => void; className?: string; @@ -42,6 +43,7 @@ const Table = < rowGroups, columns, rowIndexKey, + filter, rowClickHandler, isRowClickable = () => Boolean(rowClickHandler), className, @@ -60,7 +62,18 @@ const Table = < return (
- + {filter && ( +
+
{filter}
+
+ )} +
{columns.map(({ title, colSpan, dataIndex }) => ( diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx new file mode 100644 index 000000000..ad33ec86c --- /dev/null +++ b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx @@ -0,0 +1,88 @@ +import type { Scope } from '@logto/schemas'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import ReactModal from 'react-modal'; + +import Button from '@/components/Button'; +import FormField from '@/components/FormField'; +import ModalLayout from '@/components/ModalLayout'; +import TextInput from '@/components/TextInput'; +import useApi from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; + +type Props = { + resourceId: string; + onClose: (scope?: Scope) => void; +}; + +type CreatePermissionFormData = Pick; + +const CreatePermissionModal = ({ resourceId, onClose }: Props) => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { + handleSubmit, + register, + formState: { isSubmitting }, + } = useForm(); + + const api = useApi(); + + const onSubmit = handleSubmit(async (formData) => { + if (isSubmitting) { + return; + } + + const createdScope = await api + .post(`/api/resources/${resourceId}/scopes`, { json: formData }) + .json(); + + onClose(createdScope); + }); + + return ( + { + onClose(); + }} + > + + } + onClose={onClose} + > +
+ + + + + + + +
+
+ ); +}; + +export default CreatePermissionModal; diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.module.scss b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.module.scss index fd99f28e7..649e3c6b6 100644 --- a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.module.scss +++ b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.module.scss @@ -4,6 +4,12 @@ margin-bottom: _.unit(6); color: var(--color-text); + .filter { + display: flex; + justify-content: space-between; + align-items: center; + } + .name { display: inline-block; font: var(--font-body-medium); diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx index 031238397..83353ca38 100644 --- a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx +++ b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx @@ -1,15 +1,20 @@ import type { Scope } from '@logto/schemas'; +import { useState } from 'react'; +import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { useOutletContext } from 'react-router-dom'; import useSWR from 'swr'; import Delete from '@/assets/images/delete.svg'; +import Plus from '@/assets/images/plus.svg'; import Button from '@/components/Button'; import IconButton from '@/components/IconButton'; +import Search from '@/components/Search'; import Table from '@/components/Table'; import type { RequestError } from '@/hooks/use-api'; import type { ApiResourceDetailsOutletContext } from '../types'; +import CreatePermissionModal from './components/CreatePermissionModal'; import * as styles from './index.module.scss'; const ApiResourcePermissions = () => { @@ -25,50 +30,82 @@ const ApiResourcePermissions = () => { const isLoading = !scopes && !error; + const [isCreateFormOpen, setIsCreateFormOpen] = useState(false); + return ( -
{name}
, - }, - { - title: t('api_resource_details.permission.description_column'), - dataIndex: 'description', - colSpan: 9, - render: ({ description }) =>
{description}
, - }, - { - title: null, - dataIndex: 'delete', - colSpan: 1, - render: () => ( - - - + <> +
{name}
, + }, + { + title: t('api_resource_details.permission.description_column'), + dataIndex: 'description', + colSpan: 9, + render: ({ description }) =>
{description}
, + }, + { + title: null, + dataIndex: 'delete', + colSpan: 1, + render: () => ( + + + + ), + }, + ]} + filter={ +
+ +
+ } + isLoading={isLoading} + placeholder={{ + content: ( +