diff --git a/packages/console/src/hooks/use-console-routes/routes/api-resources.tsx b/packages/console/src/hooks/use-console-routes/routes/api-resources.tsx
index 7b53fffa1..4e284d237 100644
--- a/packages/console/src/hooks/use-console-routes/routes/api-resources.tsx
+++ b/packages/console/src/hooks/use-console-routes/routes/api-resources.tsx
@@ -1,6 +1,9 @@
-import { type RouteObject } from 'react-router-dom';
+import { Navigate, type RouteObject } from 'react-router-dom';
+import { ApiResourceDetailsTabs } from '@/consts';
import ApiResourceDetails from '@/pages/ApiResourceDetails';
+import ApiResourcePermissions from '@/pages/ApiResourceDetails/ApiResourcePermissions';
+import ApiResourceSettings from '@/pages/ApiResourceDetails/ApiResourceSettings';
import ApiResources from '@/pages/ApiResources';
export const apiResources: RouteObject = {
@@ -9,6 +12,14 @@ export const apiResources: RouteObject = {
{ index: true, element: },
{ path: 'create', element: },
{ path: ':id/guide/:guideId', element: },
- { path: ':id/*', element: },
+ {
+ path: ':id/*',
+ element: ,
+ children: [
+ { index: true, element: },
+ { path: ApiResourceDetailsTabs.Permissions, element: },
+ { path: ApiResourceDetailsTabs.General, element: },
+ ],
+ },
],
};
diff --git a/packages/console/src/hooks/use-console-routes/routes/organization-template.tsx b/packages/console/src/hooks/use-console-routes/routes/organization-template.tsx
index 52c26c61c..21d30eb6c 100644
--- a/packages/console/src/hooks/use-console-routes/routes/organization-template.tsx
+++ b/packages/console/src/hooks/use-console-routes/routes/organization-template.tsx
@@ -1,7 +1,9 @@
import { Navigate, type RouteObject } from 'react-router-dom';
-import { OrganizationTemplateTabs } from '@/consts';
+import { OrganizationRoleDetailsTabs, OrganizationTemplateTabs } from '@/consts';
import OrganizationRoleDetails from '@/pages/OrganizationRoleDetails';
+import Permissions from '@/pages/OrganizationRoleDetails/Permissions';
+import Settings from '@/pages/OrganizationRoleDetails/Settings';
import OrganizationTemplate from '@/pages/OrganizationTemplate';
import OrganizationPermissions from '@/pages/OrganizationTemplate/OrganizationPermissions';
import OrganizationRoles from '@/pages/OrganizationTemplate/OrganizationRoles';
@@ -25,5 +27,13 @@ export const organizationTemplate: RouteObject[] = [
{
path: `organization-template/${OrganizationTemplateTabs.OrganizationRoles}/:id/*`,
element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ { path: OrganizationRoleDetailsTabs.Permissions, element: },
+ { path: OrganizationRoleDetailsTabs.General, element: },
+ ],
},
];
diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx
index a586114cf..05033cab4 100644
--- a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx
+++ b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/index.tsx
@@ -1,8 +1,9 @@
-import { isManagementApi, type Resource, type ScopeResponse } from '@logto/schemas';
+import { type ScopeResponse } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
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 PermissionsTable from '@/components/PermissionsTable';
@@ -12,17 +13,17 @@ import useApi from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utils/url';
+import { type ApiResourceDetailsOutletContext } from '../types';
+
import CreatePermissionModal from './components/CreatePermissionModal';
const pageSize = defaultPageSize;
-type Props = {
- resource: Resource;
-};
-
-function ApiResourcePermissions({ resource }: Props) {
- const { id: resourceId, indicator } = resource;
- const isLogtoManagementApiResource = isManagementApi(indicator);
+function ApiResourcePermissions() {
+ const {
+ resource: { id: resourceId },
+ isLogtoManagementApiResource,
+ } = useOutletContext();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourceSettings/index.tsx b/packages/console/src/pages/ApiResourceDetails/ApiResourceSettings/index.tsx
index 7b2ff862f..48cc37de9 100644
--- a/packages/console/src/pages/ApiResourceDetails/ApiResourceSettings/index.tsx
+++ b/packages/console/src/pages/ApiResourceDetails/ApiResourceSettings/index.tsx
@@ -1,7 +1,8 @@
-import { isManagementApi, type Resource } from '@logto/schemas';
+import { type Resource } from '@logto/schemas';
import { useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { Trans, useTranslation } from 'react-i18next';
+import { useOutletContext } from 'react-router-dom';
import DetailsForm from '@/components/DetailsForm';
import FormCard from '@/components/FormCard';
@@ -14,14 +15,11 @@ import useApi from '@/hooks/use-api';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import { trySubmitSafe } from '@/utils/form';
-type Props = {
- resource: Resource;
- isDeleting: boolean;
- onResourceUpdated: (updatedData: Resource) => void;
-};
+import { type ApiResourceDetailsOutletContext } from '../types';
-function ApiResourceSettings({ resource, isDeleting, onResourceUpdated }: Props) {
- const isLogtoManagementApiResource = isManagementApi(resource.indicator);
+function ApiResourceSettings() {
+ const { resource, isDeleting, isLogtoManagementApiResource, onResourceUpdated } =
+ useOutletContext();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
diff --git a/packages/console/src/pages/ApiResourceDetails/index.tsx b/packages/console/src/pages/ApiResourceDetails/index.tsx
index f8dddb270..7635a3fa1 100644
--- a/packages/console/src/pages/ApiResourceDetails/index.tsx
+++ b/packages/console/src/pages/ApiResourceDetails/index.tsx
@@ -6,9 +6,7 @@ import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import { Trans, useTranslation } from 'react-i18next';
-// FIXME: @yijun
-// eslint-disable-next-line no-restricted-imports
-import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
+import { Outlet, useLocation, useParams } from 'react-router-dom';
import useSWR from 'swr';
import ApiResourceDark from '@/assets/icons/api-resource-dark.svg';
@@ -30,12 +28,11 @@ import useDocumentationUrl from '@/hooks/use-documentation-url';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import useTheme from '@/hooks/use-theme';
-import ApiResourcePermissions from './ApiResourcePermissions';
-import ApiResourceSettings from './ApiResourceSettings';
import GuideDrawer from './components/GuideDrawer';
import GuideModal from './components/GuideModal';
import ManagementApiNotice from './components/ManagementApiNotice';
import * as styles from './index.module.scss';
+import { type ApiResourceDetailsOutletContext } from './types';
const icons = {
[Theme.Light]: { ApiIcon: ApiResource, ManagementApiIcon: ManagementApiResource },
@@ -180,25 +177,16 @@ function ApiResourceDetails() {
{t('api_resource_details.general_tab')}
-
- } />
- }
- />
- {
- void mutate(updatedData);
- }}
- />
- }
- />
-
+
>
)}
diff --git a/packages/console/src/pages/ApiResourceDetails/types.ts b/packages/console/src/pages/ApiResourceDetails/types.ts
new file mode 100644
index 000000000..455720bef
--- /dev/null
+++ b/packages/console/src/pages/ApiResourceDetails/types.ts
@@ -0,0 +1,8 @@
+import type { Resource } from '@logto/schemas';
+
+export type ApiResourceDetailsOutletContext = {
+ resource: Resource;
+ isDeleting: boolean;
+ isLogtoManagementApiResource: boolean;
+ onResourceUpdated: (resource: Resource) => void;
+};
diff --git a/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx
index 6b799c265..5dde3b42b 100644
--- a/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx
+++ b/packages/console/src/pages/OrganizationRoleDetails/Permissions/index.tsx
@@ -2,6 +2,7 @@ import { type Scope, type OrganizationScope } from '@logto/schemas';
import { useCallback, useMemo, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
+import { useOutletContext } from 'react-router-dom';
import Plus from '@/assets/icons/plus.svg';
import ActionsButton from '@/components/ActionsButton';
@@ -17,6 +18,8 @@ import Tag from '@/ds-components/Tag';
import useApi from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
+import { type OrganizationRoleDetailsOutletContext } from '../types';
+
import ResourceName from './ResourceName';
import * as styles from './index.module.scss';
import useOrganizationRoleScopes from './use-organization-role-scopes';
@@ -25,11 +28,11 @@ type OrganizationRoleScope = OrganizationScope | Scope;
const isResourceScope = (scope: OrganizationRoleScope): scope is Scope => 'resourceId' in scope;
-type Props = {
- organizationRoleId: string;
-};
+function Permissions() {
+ const {
+ organizationRole: { id: organizationRoleId },
+ } = useOutletContext();
-function Permissions({ organizationRoleId }: Props) {
const organizationRolePath = `api/organization-roles/${organizationRoleId}`;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
diff --git a/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx
index 70615c58f..3872c33f5 100644
--- a/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx
+++ b/packages/console/src/pages/OrganizationRoleDetails/Settings/index.tsx
@@ -2,6 +2,7 @@ import { type OrganizationRole } from '@logto/schemas';
import { useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
+import { useOutletContext } from 'react-router-dom';
import DetailsForm from '@/components/DetailsForm';
import FormCard from '@/components/FormCard';
@@ -13,12 +14,12 @@ 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;
-};
+import { type OrganizationRoleDetailsOutletContext } from '../types';
+
+function Settings() {
+ const { organizationRole, isDeleting, onOrganizationRoleUpdated } =
+ useOutletContext();
-function Settings({ data, onUpdate }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
const {
@@ -26,17 +27,17 @@ function Settings({ data, onUpdate }: Props) {
handleSubmit,
reset,
formState: { errors, isDirty, isSubmitting },
- } = useForm({ defaultValues: data });
+ } = useForm({ defaultValues: organizationRole });
const api = useApi();
const onSubmit = handleSubmit(
trySubmitSafe(async (formData) => {
const updatedData = await api
- .patch(`api/organization-roles/${data.id}`, { json: formData })
+ .patch(`api/organization-roles/${organizationRole.id}`, { json: formData })
.json();
reset(updatedData);
- onUpdate(updatedData);
+ onOrganizationRoleUpdated(updatedData);
toast.success(t('general.saved'));
})
);
@@ -70,7 +71,9 @@ function Settings({ data, onUpdate }: Props) {
/>
-
+
);
}
diff --git a/packages/console/src/pages/OrganizationRoleDetails/index.tsx b/packages/console/src/pages/OrganizationRoleDetails/index.tsx
index d7d162520..b64fbf8ce 100644
--- a/packages/console/src/pages/OrganizationRoleDetails/index.tsx
+++ b/packages/console/src/pages/OrganizationRoleDetails/index.tsx
@@ -1,12 +1,10 @@
import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
import { type OrganizationRole } from '@logto/schemas';
import classNames from 'classnames';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
-// FIXME: @yijun
-// eslint-disable-next-line no-restricted-imports
-import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
+import { Outlet, useLocation, useParams } from 'react-router-dom';
import useSWR, { useSWRConfig } from 'swr';
import Delete from '@/assets/icons/delete.svg';
@@ -22,9 +20,8 @@ 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';
import * as styles from './index.module.scss';
+import { type OrganizationRoleDetailsOutletContext } from './types';
const orgRolesPath = `/organization-template/${OrganizationTemplateTabs.OrganizationRoles}`;
@@ -44,6 +41,11 @@ function OrganizationRoleDetails() {
const [isDeletionAlertOpen, setIsDeletionAlertOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
+ // Close deletion alert when navigating to another page
+ useEffect(() => {
+ setIsDeletionAlertOpen(false);
+ }, [pathname]);
+
const handleDelete = async () => {
if (!data) {
return;
@@ -110,20 +112,15 @@ function OrganizationRoleDetails() {
-
- }
- />
- }
- />
- }
- />
-
+
>
)}
diff --git a/packages/console/src/pages/OrganizationRoleDetails/types.ts b/packages/console/src/pages/OrganizationRoleDetails/types.ts
new file mode 100644
index 000000000..d77bbe0ce
--- /dev/null
+++ b/packages/console/src/pages/OrganizationRoleDetails/types.ts
@@ -0,0 +1,7 @@
+import { type OrganizationRole } from '@logto/schemas';
+
+export type OrganizationRoleDetailsOutletContext = {
+ organizationRole: OrganizationRole;
+ isDeleting: boolean;
+ onOrganizationRoleUpdated: (organizationRole: OrganizationRole) => void;
+};