diff --git a/packages/console/src/consts/page-tabs.ts b/packages/console/src/consts/page-tabs.ts
index e6d593026..890f776c4 100644
--- a/packages/console/src/consts/page-tabs.ts
+++ b/packages/console/src/consts/page-tabs.ts
@@ -52,3 +52,8 @@ export enum OrganizationTemplateTabs {
OrganizationRoles = 'organization-roles',
OrganizationPermissions = 'organization-permissions',
}
+
+export enum OrganizationRoleDetailsTabs {
+ Permissions = 'permissions',
+ General = 'general',
+}
diff --git a/packages/console/src/containers/ConsoleContent/index.tsx b/packages/console/src/containers/ConsoleContent/index.tsx
index 0c05bc80e..39f1f84c8 100644
--- a/packages/console/src/containers/ConsoleContent/index.tsx
+++ b/packages/console/src/containers/ConsoleContent/index.tsx
@@ -35,6 +35,7 @@ import GetStarted from '@/pages/GetStarted';
import Mfa from '@/pages/Mfa';
import NotFound from '@/pages/NotFound';
import OrganizationDetails from '@/pages/OrganizationDetails';
+import OrganizationRoleDetails from '@/pages/OrganizationRoleDetails';
import OrganizationTemplate from '@/pages/OrganizationTemplate';
import OrganizationPermissions from '@/pages/OrganizationTemplate/OrganizationPermissions';
import OrganizationRoles from '@/pages/OrganizationTemplate/OrganizationRoles';
@@ -187,20 +188,26 @@ function ConsoleContent() {
{isDevFeaturesEnabled && (
- }>
+ <>
+ }>
+ }
+ />
+ }
+ />
+ }
+ />
+
}
+ path={`organization-template/${OrganizationTemplateTabs.OrganizationRoles}/:id/*`}
+ element={}
/>
- }
- />
- }
- />
-
+ >
)}
} />
diff --git a/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx
new file mode 100644
index 000000000..1824f1775
--- /dev/null
+++ b/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx
@@ -0,0 +1,9 @@
+type Props = {
+ organizationRoleId: string;
+};
+
+function Permissions({ organizationRoleId }: Props) {
+ return TBD
;
+}
+
+export default Permissions;
diff --git a/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx
new file mode 100644
index 000000000..70615c58f
--- /dev/null
+++ b/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx
@@ -0,0 +1,78 @@
+import { type OrganizationRole } from '@logto/schemas';
+import { useForm } from 'react-hook-form';
+import { toast } from 'react-hot-toast';
+import { useTranslation } from 'react-i18next';
+
+import DetailsForm from '@/components/DetailsForm';
+import FormCard from '@/components/FormCard';
+import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
+import { organizationRoleLink } from '@/consts';
+import FormField from '@/ds-components/FormField';
+import TextInput from '@/ds-components/TextInput';
+import useApi from '@/hooks/use-api';
+import useDocumentationUrl from '@/hooks/use-documentation-url';
+import { trySubmitSafe } from '@/utils/form';
+
+type Props = {
+ data: OrganizationRole;
+ onUpdate: (updatedData: OrganizationRole) => void;
+};
+
+function Settings({ data, onUpdate }: Props) {
+ const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
+ const { getDocumentationUrl } = useDocumentationUrl();
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors, isDirty, isSubmitting },
+ } = useForm({ defaultValues: data });
+
+ const api = useApi();
+
+ const onSubmit = handleSubmit(
+ trySubmitSafe(async (formData) => {
+ const updatedData = await api
+ .patch(`api/organization-roles/${data.id}`, { json: formData })
+ .json();
+ reset(updatedData);
+ onUpdate(updatedData);
+ toast.success(t('general.saved'));
+ })
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Settings;
diff --git a/packages/console/src/pages/OrganizationRoleDetails/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/index.tsx
new file mode 100644
index 000000000..9abcfcfdd
--- /dev/null
+++ b/packages/console/src/pages/OrganizationRoleDetails/index.tsx
@@ -0,0 +1,126 @@
+import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
+import { type OrganizationRole } from '@logto/schemas';
+import { useState } from 'react';
+import { toast } from 'react-hot-toast';
+import { useTranslation } from 'react-i18next';
+import { Navigate, Route, Routes, useParams } from 'react-router-dom';
+import useSWR, { useSWRConfig } from 'swr';
+
+import Delete from '@/assets/icons/delete.svg';
+import OrgRoleIcon from '@/assets/icons/role-feature.svg';
+import DetailsPage from '@/components/DetailsPage';
+import DetailsPageHeader from '@/components/DetailsPage/DetailsPageHeader';
+import PageMeta from '@/components/PageMeta';
+import ThemedIcon from '@/components/ThemedIcon';
+import { OrganizationRoleDetailsTabs, OrganizationTemplateTabs } from '@/consts';
+import ConfirmModal from '@/ds-components/ConfirmModal';
+import DynamicT from '@/ds-components/DynamicT';
+import TabNav, { TabNavItem } from '@/ds-components/TabNav';
+import useApi, { type RequestError } from '@/hooks/use-api';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
+
+import Permissions from './Permissions';
+import Settings from './Settings';
+
+const orgRolesPath = `/organization-template/${OrganizationTemplateTabs.OrganizationRoles}`;
+
+function OrganizationRoleDetails() {
+ const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
+
+ const { id } = useParams();
+ const { navigate } = useTenantPathname();
+
+ const { data, error, mutate, isLoading } = useSWR(
+ id && `api/organization-roles/${id}`
+ );
+ const api = useApi();
+ const { mutate: mutateGlobal } = useSWRConfig();
+ const [isDeletionAlertOpen, setIsDeletionAlertOpen] = useState(false);
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ const handleDelete = async () => {
+ if (!data) {
+ return;
+ }
+
+ setIsDeleting(true);
+
+ try {
+ await api.delete(`api/organization-roles/${data.id}`);
+ toast.success(t('organization_role_details.deleted', { name: data.name }));
+ await mutateGlobal('api/roles');
+ navigate(orgRolesPath, { replace: true });
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ return (
+
+
+ {data && (
+ <>
+ }
+ title={data.name}
+ primaryTag={t('organization_role_details.org_role')}
+ identifier={{ name: 'ID', value: data.id }}
+ actionMenuItems={[
+ {
+ title: 'general.delete',
+ icon: ,
+ type: 'danger',
+ onClick: () => {
+ setIsDeletionAlertOpen(true);
+ },
+ },
+ ]}
+ />
+ {
+ setIsDeletionAlertOpen(false);
+ }}
+ onConfirm={handleDelete}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+ }
+ />
+ }
+ />
+
+ >
+ )}
+
+ );
+}
+
+export default withAppInsights(OrganizationRoleDetails);
diff --git a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx
index 46540c604..1cb6360c9 100644
--- a/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx
+++ b/packages/console/src/pages/OrganizationTemplate/OrganizationRoles/index.tsx
@@ -17,13 +17,14 @@ import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
import { buildUrl } from '@/utils/url';
import * as styles from './index.module.scss';
function OrganizationRoles() {
const { getDocumentationUrl } = useDocumentationUrl();
-
+ const { navigate } = useTenantPathname();
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
@@ -49,8 +50,8 @@ function OrganizationRoles() {
title: ,
dataIndex: 'name',
colSpan: 4,
- render: ({ name }) => {
- return } />;
+ render: ({ id, name }) => {
+ return } to={id} />;
},
},
{
@@ -72,6 +73,9 @@ function OrganizationRoles() {
},
},
]}
+ rowClickHandler={({ id }) => {
+ navigate(id);
+ }}
filter={