Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(console): update console routes #5715

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -9,6 +12,14 @@ export const apiResources: RouteObject = {
{ index: true, element: <ApiResources /> },
{ path: 'create', element: <ApiResources /> },
{ path: ':id/guide/:guideId', element: <ApiResourceDetails /> },
{ path: ':id/*', element: <ApiResourceDetails /> },
{
path: ':id/*',
element: <ApiResourceDetails />,
children: [
{ index: true, element: <Navigate replace to={ApiResourceDetailsTabs.Permissions} /> },
{ path: ApiResourceDetailsTabs.Permissions, element: <ApiResourcePermissions /> },
{ path: ApiResourceDetailsTabs.General, element: <ApiResourceSettings /> },
],
},
],
};
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -25,5 +27,13 @@ export const organizationTemplate: RouteObject[] = [
{
path: `organization-template/${OrganizationTemplateTabs.OrganizationRoles}/:id/*`,
element: <OrganizationRoleDetails />,
children: [
{
index: true,
element: <Navigate replace to={OrganizationRoleDetailsTabs.Permissions} />,
},
{ path: OrganizationRoleDetailsTabs.Permissions, element: <Permissions /> },
{ path: OrganizationRoleDetailsTabs.General, element: <Settings /> },
],
},
];
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<ApiResourceDetailsOutletContext>();

const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });

Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<ApiResourceDetailsOutletContext>();

const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
Expand Down
36 changes: 12 additions & 24 deletions packages/console/src/pages/ApiResourceDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 },
Expand Down Expand Up @@ -180,25 +177,16 @@ function ApiResourceDetails() {
{t('api_resource_details.general_tab')}
</TabNavItem>
</TabNav>
<Routes>
<Route index element={<Navigate replace to={ApiResourceDetailsTabs.Permissions} />} />
<Route
path={ApiResourceDetailsTabs.Permissions}
element={<ApiResourcePermissions resource={data} />}
/>
<Route
path={ApiResourceDetailsTabs.General}
element={
<ApiResourceSettings
resource={data}
isDeleting={isDeleting}
onResourceUpdated={(updatedData: Resource) => {
void mutate(updatedData);
}}
/>
}
/>
</Routes>
<Outlet
context={
{
resource: data,
isDeleting,
isLogtoManagementApiResource,
onResourceUpdated: mutate,
} satisfies ApiResourceDetailsOutletContext
}
/>
</>
)}
</DetailsPage>
Expand Down
8 changes: 8 additions & 0 deletions packages/console/src/pages/ApiResourceDetails/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Resource } from '@logto/schemas';

export type ApiResourceDetailsOutletContext = {
resource: Resource;
isDeleting: boolean;
isLogtoManagementApiResource: boolean;
onResourceUpdated: (resource: Resource) => void;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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<OrganizationRoleDetailsOutletContext>();

function Permissions({ organizationRoleId }: Props) {
const organizationRolePath = `api/organization-roles/${organizationRoleId}`;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -13,30 +14,30 @@ 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<OrganizationRoleDetailsOutletContext>();

function Settings({ data, onUpdate }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
const {
register,
handleSubmit,
reset,
formState: { errors, isDirty, isSubmitting },
} = useForm<OrganizationRole>({ defaultValues: data });
} = useForm<OrganizationRole>({ 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<OrganizationRole>();
reset(updatedData);
onUpdate(updatedData);
onOrganizationRoleUpdated(updatedData);
toast.success(t('general.saved'));
})
);
Expand Down Expand Up @@ -70,7 +71,9 @@ function Settings({ data, onUpdate }: Props) {
/>
</FormField>
</FormCard>
<UnsavedChangesAlertModal hasUnsavedChanges={isDirty} />
<UnsavedChangesAlertModal
hasUnsavedChanges={!isDeleting && isDirty} // Should not block navigation back to list page when deleting
/>
</DetailsForm>
);
}
Expand Down
37 changes: 17 additions & 20 deletions packages/console/src/pages/OrganizationRoleDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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}`;

Expand All @@ -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;
Expand Down Expand Up @@ -110,20 +112,15 @@ function OrganizationRoleDetails() {
<DynamicT forKey="organization_role_details.general.tab" />
</TabNavItem>
</TabNav>
<Routes>
<Route
index
element={<Navigate replace to={OrganizationRoleDetailsTabs.Permissions} />}
/>
<Route
path={OrganizationRoleDetailsTabs.Permissions}
element={<Permissions organizationRoleId={data.id} />}
/>
<Route
path={OrganizationRoleDetailsTabs.General}
element={<Settings data={data} onUpdate={mutate} />}
/>
</Routes>
<Outlet
context={
{
organizationRole: data,
isDeleting,
onOrganizationRoleUpdated: mutate,
} satisfies OrganizationRoleDetailsOutletContext
}
/>
</>
)}
</DetailsPage>
Expand Down
7 changes: 7 additions & 0 deletions packages/console/src/pages/OrganizationRoleDetails/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type OrganizationRole } from '@logto/schemas';

export type OrganizationRoleDetailsOutletContext = {
organizationRole: OrganizationRole;
isDeleting: boolean;
onOrganizationRoleUpdated: (organizationRole: OrganizationRole) => void;
};
Loading