Skip to content

Commit

Permalink
refactor(console): move organization template into a signle page
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyijun committed Mar 29, 2024
1 parent 664c67d commit 79c2a53
Show file tree
Hide file tree
Showing 58 changed files with 778 additions and 11 deletions.
3 changes: 3 additions & 0 deletions packages/console/src/assets/icons/research.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/console/src/consts/external-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ export const logtoThirdPartyAppPermissionsLink =
'/docs/recipes/logto-as-idp/permissions-management/';
export const logtoThirdPartyAppBrandingLink = '/docs/recipes/logto-as-idp/branding-customization/';
export const signingKeysLink = '/docs/recipes/openid-connect/signing-keys-rotation/';
export const organizationTemplateLink =
'/docs/recipes/organizations/understand-how-it-works/#organization-template';
5 changes: 5 additions & 0 deletions packages/console/src/consts/page-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ export enum EnterpriseSsoDetailsTabs {
Connection = 'connection',
Experience = 'experience',
}

export enum OrganizationTemplateTabs {
OrgRoles = 'org-roles',
OrgPermissions = 'org-permissions',
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ export const useSidebarMenuItems = (): {
Icon: Role,
title: 'roles',
},
{
Icon: Role,
title: 'organization_template',
isHidden: !isDevFeaturesEnabled,
},
],
},
{
Expand Down
17 changes: 17 additions & 0 deletions packages/console/src/containers/ConsoleContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TenantSettingsTabs,
ApplicationDetailsTabs,
EnterpriseSsoDetailsTabs,
OrganizationTemplateTabs,
} from '@/consts';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
Expand All @@ -33,6 +34,9 @@ import JwtClaims from '@/pages/JwtClaims';
import Mfa from '@/pages/Mfa';
import NotFound from '@/pages/NotFound';
import OrganizationDetails from '@/pages/OrganizationDetails';
import OrganizationTemplate from '@/pages/OrganizationTemplate';
import OrgPermissions from '@/pages/OrganizationTemplate/OrgPermissions';
import OrgRoles from '@/pages/OrganizationTemplate/OrgRoles';
import Organizations from '@/pages/Organizations';
import OrganizationGuide from '@/pages/Organizations/Guide';
import Profile from '@/pages/Profile';
Expand Down Expand Up @@ -180,6 +184,19 @@ function ConsoleContent() {
<Route path={RoleDetailsTabs.M2mApps} element={<RoleApplications />} />
</Route>
</Route>
{isDevFeaturesEnabled && (
<Route path="organization-template" element={<OrganizationTemplate />}>
<Route
index
element={<Navigate replace to={OrganizationTemplateTabs.OrgRoles} />}
/>
<Route path={OrganizationTemplateTabs.OrgRoles} element={<OrgRoles />} />
<Route
path={OrganizationTemplateTabs.OrgPermissions}
element={<OrgPermissions />}
/>
</Route>
)}
<Route path="organizations">
<Route index element={<Organizations />} />
<Route path="create" element={<Organizations />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@
color: var(--color-text-secondary);
}

.learnMore:not(:first-child) {
margin-left: _.unit(1);
}

&.large {
.title {
font: var(--font-title-1);
Expand Down
13 changes: 6 additions & 7 deletions packages/console/src/ds-components/CardTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,12 @@ function CardTitle({
<span>{typeof subtitle === 'string' ? <DynamicT forKey={subtitle} /> : subtitle}</span>
)}
{learnMoreLink?.href && (
<TextLink
href={learnMoreLink.href}
targetBlank={learnMoreLink.targetBlank}
className={styles.learnMore}
>
{t('general.learn_more')}
</TextLink>
<>
{' '}
<TextLink href={learnMoreLink.href} targetBlank={learnMoreLink.targetBlank}>
{t('general.learn_more')}
</TextLink>
</>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '@/scss/underscore' as _;

.filter {
display: flex;
justify-content: space-between;
align-items: center;

.createButton {
margin-left: _.unit(2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { type OrganizationScope } from '@logto/schemas';
import { cond } from '@silverhand/essentials';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';

import Plus from '@/assets/icons/plus.svg';
import ActionsButton from '@/components/ActionsButton';
import Breakable from '@/components/Breakable';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import Search from '@/ds-components/Search';
import Table from '@/ds-components/Table';
import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utils/url';

import * as styles from './index.module.scss';

function OrgPermissions() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [{ keyword }, updateSearchParameters] = useSearchParametersWatcher({
keyword: '',
});

const { data, error, mutate, isLoading } = useSWR<OrganizationScope[], RequestError>(
buildUrl('api/organization-scopes', {
...cond(keyword && { q: formatSearchKeyword(keyword) }),
})
);

return (
<Table
rowGroups={[{ key: 'orgPermissions', data }]}
rowIndexKey="id"
columns={[
{
title: <DynamicT forKey="organization_template.org_permissions.permission_column" />,
dataIndex: 'name',
colSpan: 7,
render: ({ name }) => {
return (
<Tag variant="cell">
<Breakable>{name}</Breakable>
</Tag>
);
},
},
{
title: <DynamicT forKey="organization_template.org_permissions.description_column" />,
dataIndex: 'scopes',
colSpan: 8,
render: ({ description }) => <Breakable>{description ?? '-'}</Breakable>,
},
{
title: null,
dataIndex: 'action',
colSpan: 1,
render: (scope) => (
<ActionsButton
fieldName="organization_template.org_permissions.permission_column"
deleteConfirmation="organizations.organization_permission_delete_confirm"
onEdit={() => {
// Todo @xiaoyijun implement edit

Check warning on line 64 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L64

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement edit'.
}}
onDelete={async () => {
// Todo @xiaoyijun implement deletion

Check warning on line 67 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L67

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement deletion'.
}}
/>
),
},
]}
filter={
<div className={styles.filter}>
<Search
placeholder={t('organization_template.org_roles.search_placeholder')}
defaultValue={keyword}
isClearable={Boolean(keyword)}
onSearch={(keyword) => {
updateSearchParameters({ keyword });
}}
onClearSearch={() => {
updateSearchParameters({ keyword: '' });
}}
/>
<Button
title="organization_template.org_roles.create_org_roles"
className={styles.createButton}
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
console.log('Fuck create');
}}
/>
</div>
}
isLoading={isLoading}
errorMessage={error?.body?.message ?? error?.message}
onRetry={async () => mutate(undefined, true)}
/>
);
}

export default OrgPermissions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '@/scss/underscore' as _;

.permissions {
display: flex;
flex-wrap: wrap;
gap: _.unit(2);
}

.filter {
display: flex;
flex-direction: row-reverse;
align-items: center;
}
97 changes: 97 additions & 0 deletions packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { type OrganizationRoleWithScopes } from '@logto/schemas';
import useSWR from 'swr';

import Plus from '@/assets/icons/plus.svg';
import OrgRoleIcon from '@/assets/icons/role-feature.svg';
import Breakable from '@/components/Breakable';
import ItemPreview from '@/components/ItemPreview';
import ThemedIcon from '@/components/ThemedIcon';
import { defaultPageSize } from '@/consts';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import Table from '@/ds-components/Table';
import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl } from '@/utils/url';

import * as styles from './index.module.scss';

function OrgRoles() {
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});

const { data, error, mutate, isLoading } = useSWR<
[OrganizationRoleWithScopes[], number],
RequestError
>(
buildUrl('api/organization-roles', {
page: String(page),
page_size: String(defaultPageSize),
})
);

const [orgRoles, totalCount] = data ?? [];

return (
<Table
rowGroups={[{ key: 'orgRoles', data: orgRoles }]}
rowIndexKey="id"
columns={[
{
title: <DynamicT forKey="organization_template.org_roles.org_role_column" />,
dataIndex: 'name',
colSpan: 4,
render: ({ name }) => {
return <ItemPreview title={name} icon={<ThemedIcon for={OrgRoleIcon} />} />;
},
},
{
title: <DynamicT forKey="organization_template.org_roles.permissions_column" />,
dataIndex: 'scopes',
colSpan: 12,
render: ({ scopes }) => {
return scopes.length === 0 ? (
'-'
) : (
<div className={styles.permissions}>
{scopes.map(({ id, name }) => (
<Tag key={id} variant="cell">
<Breakable>{name}</Breakable>
</Tag>
))}
</div>
);
},
},
]}
filter={
<div className={styles.filter}>
<Button
title="organization_template.org_roles.create_org_roles"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
// Todo @xiaoyijun implment create org role

Check warning on line 77 in packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx#L77

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implment create org role'.
}}
/>
</div>
}
pagination={{
page,
totalCount,
pageSize: defaultPageSize,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
isLoading={isLoading}
errorMessage={error?.body?.message ?? error?.message}
onRetry={async () => mutate(undefined, true)}
/>
);
}

export default OrgRoles;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@use '@/scss/underscore' as _;

.container {
> *:not(:first-child) {
margin-top: _.unit(4);
}
}

56 changes: 56 additions & 0 deletions packages/console/src/pages/OrganizationTemplate/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
import classNames from 'classnames';
import { Outlet } from 'react-router-dom';

import Research from '@/assets/icons/research.svg';
import PageMeta from '@/components/PageMeta';
import { OrganizationTemplateTabs, organizationTemplateLink } from '@/consts';
import Button from '@/ds-components/Button';
import CardTitle from '@/ds-components/CardTitle';
import DynamicT from '@/ds-components/DynamicT';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import * as pageLayout from '@/scss/page-layout.module.scss';

import * as styles from './index.module.scss';

const basePathname = '/organization-template';

function OrganizationTemplate() {
const { getDocumentationUrl } = useDocumentationUrl();

return (
<div className={classNames(pageLayout.container, styles.container)}>
<PageMeta titleKey="organization_template.title" />
<div className={pageLayout.headline}>
<CardTitle
title="organization_template.title"
subtitle="organization_template.subtitle"
learnMoreLink={{
href: getDocumentationUrl(organizationTemplateLink),
targetBlank: 'noopener',
}}
/>
<Button
icon={<Research />}
title="application_details.check_guide"
type="outline"
onClick={() => {
// Todo @xiaoyijun implement guide

Check warning on line 39 in packages/console/src/pages/OrganizationTemplate/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/index.tsx#L39

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement guide'.
}}
/>
</div>
<TabNav>
<TabNavItem href={`${basePathname}/${OrganizationTemplateTabs.OrgRoles}`}>
<DynamicT forKey="organization_template.org_roles.tab_name" />
</TabNavItem>
<TabNavItem href={`${basePathname}/${OrganizationTemplateTabs.OrgPermissions}`}>
<DynamicT forKey="organization_template.org_permissions.tab_name" />
</TabNavItem>
</TabNav>
<Outlet />
</div>
);
}

export default withAppInsights(OrganizationTemplate);

0 comments on commit 79c2a53

Please sign in to comment.