Skip to content

Commit

Permalink
feat(console): m2m pages in organizations
Browse files Browse the repository at this point in the history
  • Loading branch information
gao-sun committed Jun 23, 2024
1 parent 88f94c7 commit 46fcdf2
Show file tree
Hide file tree
Showing 32 changed files with 84 additions and 164 deletions.
13 changes: 4 additions & 9 deletions packages/console/src/components/ItemPreview/UserPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,10 @@ type Props = {
/**
* A subset of User schema type that is used in the preview component.
*/
readonly user: {
id: UserInfo['id'];
avatar?: UserInfo['avatar'];
name?: UserInfo['name'];
primaryEmail?: UserInfo['primaryEmail'];
primaryPhone?: UserInfo['primaryPhone'];
username?: UserInfo['username'];
isSuspended?: UserInfo['isSuspended'];
};
readonly user: Partial<
Pick<UserInfo, 'avatar' | 'name' | 'primaryEmail' | 'primaryPhone' | 'username' | 'isSuspended'>
> &
Pick<UserInfo, 'id'>;
/**
* Whether to provide a link to user details page. Explicitly set to `false` to hide it.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/console/src/components/ItemPreview/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@
}
}
}

.icon {
flex-shrink: 0;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type OrganizationScope } from '@logto/schemas';
import { type OrganizationRole, type RoleType } from '@logto/schemas';
import classNames from 'classnames';

import RoleIcon from '@/assets/icons/role-feature.svg';
Expand Down Expand Up @@ -30,18 +30,21 @@ type Props = {
readonly onChange: (value: Array<Option<string>>) => void;
readonly keyword: string;
readonly setKeyword: (keyword: string) => void;
readonly roleType: RoleType;
};

function OrganizationRolesSelect({ value, onChange, keyword, setKeyword }: Props) {
const { data: scopes, isLoading } = useSearchValues<OrganizationScope>(
function OrganizationRolesSelect({ value, onChange, keyword, setKeyword, roleType }: Props) {
const { data: roles, isLoading } = useSearchValues<OrganizationRole>(
'api/organization-roles',
keyword
);

return (
<MultiSelect
value={value}
options={scopes.map(({ id, name }) => ({ value: id, title: name }))}
options={roles
.filter(({ type }) => type === roleType)
.map(({ id, name }) => ({ value: id, title: name }))}
placeholder="organizations.search_role_placeholder"
isOptionsLoading={isLoading}
renderOption={RoleOption}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { condArray } from '@silverhand/essentials';
import { Navigate, type RouteObject } from 'react-router-dom';

import { isDevFeaturesEnabled } from '@/consts/env';
import OrganizationDetails from '@/pages/OrganizationDetails';
import MachineToMachine from '@/pages/OrganizationDetails/MachineToMachine';
import Members from '@/pages/OrganizationDetails/Members';
import Settings from '@/pages/OrganizationDetails/Settings';
import { OrganizationDetailsTabs } from '@/pages/OrganizationDetails/types';
Expand All @@ -15,11 +17,15 @@ export const organizations: RouteObject = {
{
path: ':id/*',
element: <OrganizationDetails />,
children: [
children: condArray(
{ index: true, element: <Navigate replace to={OrganizationDetailsTabs.Settings} /> },
{ path: OrganizationDetailsTabs.Settings, element: <Settings /> },
{ path: OrganizationDetailsTabs.Members, element: <Members /> },
],
isDevFeaturesEnabled && {
path: OrganizationDetailsTabs.MachineToMachine,
element: <MachineToMachine />,
}
),
}
),
};
23 changes: 2 additions & 21 deletions packages/console/src/pages/Applications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { useLocation } from 'react-router-dom';

import Plus from '@/assets/icons/plus.svg';
import ApplicationCreation from '@/components/ApplicationCreation';
import ApplicationIcon from '@/components/ApplicationIcon';
import ChargeNotification from '@/components/ChargeNotification';
import { type SelectedGuide } from '@/components/Guide/GuideCard';
import ItemPreview from '@/components/ItemPreview';
import ApplicationPreview from '@/components/ItemPreview/ApplicationPreview';
import PageMeta from '@/components/PageMeta';
import { isCloud } from '@/consts/env';
import Button from '@/ds-components/Button';
Expand All @@ -20,7 +19,6 @@ import Table from '@/ds-components/Table';
import useApplicationsUsage from '@/hooks/use-applications-usage';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import * as pageLayout from '@/scss/page-layout.module.scss';
import { applicationTypeI18nKey } from '@/types/applications';
import { buildUrl } from '@/utils/url';

import GuideLibrary from './components/GuideLibrary';
Expand Down Expand Up @@ -178,24 +176,7 @@ function Applications({ tab }: Props) {
title: t('applications.application_name'),
dataIndex: 'name',
colSpan: 6,
render: ({ id, name, type, isThirdParty }) => (
<ItemPreview
title={name}
subtitle={
isThirdParty
? t(`${applicationTypeI18nKey.thirdParty}.title`)
: t(`${applicationTypeI18nKey[type]}.title`)
}
icon={
<ApplicationIcon
className={styles.icon}
type={type}
isThirdParty={isThirdParty}
/>
}
to={buildDetailsPathname(id)}
/>
),
render: (data) => <ApplicationPreview data={data} />,
},
{
title: t('applications.app_id'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type User, type Organization } from '@logto/schemas';
import { type User, type Organization, RoleType } from '@logto/schemas';
import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -34,25 +34,25 @@ function AddMembersToOrganization({ organization, isOpen, onClose }: Props) {
formState: { isSubmitting },
} = useForm<{
users: User[];
scopes: Array<Option<string>>;
roles: Array<Option<string>>;
}>({
defaultValues: { users: [], scopes: [] },
defaultValues: { users: [], roles: [] },
});
const [keyword, setKeyword] = useState('');

const onSubmit = handleSubmit(
trySubmitSafe(async (data) => {
trySubmitSafe(async ({ users, roles }) => {
await api.post(`api/organizations/${organization.id}/users`, {
json: {
userIds: data.users.map(({ id }) => id),
userIds: users.map(({ id }) => id),
},
});

if (data.scopes.length > 0) {
if (roles.length > 0) {
await api.post(`api/organizations/${organization.id}/users/roles`, {
json: {
userIds: data.users.map(({ id }) => id),
organizationRoleIds: data.scopes.map(({ value }) => value),
userIds: users.map(({ id }) => id),
organizationRoleIds: roles.map(({ value }) => value),
},
});
}
Expand Down Expand Up @@ -126,10 +126,11 @@ function AddMembersToOrganization({ organization, isOpen, onClose }: Props) {
</FormField>
<FormField title="organization_details.add_with_organization_role">
<Controller
name="scopes"
name="roles"
control={control}
render={({ field: { onChange, value } }) => (
<OrganizationRolesSelect
roleType={RoleType.User}
keyword={keyword}
setKeyword={setKeyword}
value={value}
Expand Down

This file was deleted.

11 changes: 6 additions & 5 deletions packages/console/src/pages/OrganizationDetails/Members/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import useActionTranslation from '@/hooks/use-action-translation';
import useApi, { type RequestError } from '@/hooks/use-api';
import { buildUrl } from '@/utils/url';

import EditOrganizationRolesModal from '../EditOrganizationRolesModal';
import { type OrganizationDetailsOutletContext } from '../types';

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

const pageSize = defaultPageSize;
Expand Down Expand Up @@ -68,13 +68,13 @@ function Members() {
columns={[
{
dataIndex: 'user',
title: 'User',
title: t('organization_details.user'),
colSpan: 4,
render: (user) => <UserPreview user={user} />,
},
{
dataIndex: 'roles',
title: 'Organization roles',
title: t('organization_details.roles'),
colSpan: 6,
render: ({ organizationRoles }) => {
if (organizationRoles.length === 0) {
Expand All @@ -94,7 +94,7 @@ function Members() {
},
{
dataIndex: 'lastSignInAt',
title: 'Last sign-in',
title: t('users.latest_sign_in'),
colSpan: 5,
render: ({ lastSignInAt }) => <DateTime>{lastSignInAt}</DateTime>,
},
Expand Down Expand Up @@ -153,8 +153,9 @@ function Members() {
{userToBeEdited && (
<EditOrganizationRolesModal
isOpen
type="user"
organizationId={organization.id}
user={userToBeEdited}
data={userToBeEdited}
onClose={() => {
setUserToBeEdited(undefined);
void mutate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type SignInExperience,
type Organization,
type SsoConnectorWithProviderConfig,
RoleType,
} from '@logto/schemas';
import { useState, useCallback, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
Expand Down Expand Up @@ -296,6 +297,7 @@ function Settings() {
control={control}
render={({ field: { onChange, value } }) => (
<OrganizationRolesSelect
roleType={RoleType.User}
keyword={keyword}
setKeyword={setKeyword}
value={value}
Expand All @@ -309,6 +311,8 @@ function Settings() {
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />
</DetailsForm>
);

// eslint-disable-next-line max-lines -- Should be ok once dev features flag is removed
}

export default Settings;
7 changes: 7 additions & 0 deletions packages/console/src/pages/OrganizationDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Skeleton from '@/components/DetailsPage/Skeleton';
import Drawer from '@/components/Drawer';
import PageMeta from '@/components/PageMeta';
import ThemedIcon from '@/components/ThemedIcon';
import { isDevFeaturesEnabled } from '@/consts/env';
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import useApi, { type RequestError } from '@/hooks/use-api';
Expand Down Expand Up @@ -133,6 +134,12 @@ function OrganizationDetails() {
<TabNavItem href={`${pathname}/${id}/${OrganizationDetailsTabs.Members}`}>
{t('organizations.members')}
</TabNavItem>
{/* TODO: Remove */}
{isDevFeaturesEnabled && (
<TabNavItem href={`${pathname}/${id}/${OrganizationDetailsTabs.MachineToMachine}`}>
{t('organizations.machine_to_machine')}
</TabNavItem>
)}
</TabNav>
<Outlet
context={
Expand Down
1 change: 1 addition & 0 deletions packages/console/src/pages/OrganizationDetails/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export type OrganizationDetailsOutletContext = {
export enum OrganizationDetailsTabs {
Settings = 'settings',
Members = 'members',
MachineToMachine = 'machine-to-machine',
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { type CommonQueryMethods, sql } from '@silverhand/slonik';

import { type SearchOptions, buildSearchSql } from '#src/database/utils.js';
import { TwoRelationsQueries, type GetEntitiesOptions } from '#src/utils/RelationQueries.js';
import { convertToIdentifiers } from '#src/utils/sql.js';
import { conditionalSql, convertToIdentifiers } from '#src/utils/sql.js';

import { type applicationSearchKeys } from '../application.js';

Expand All @@ -28,7 +28,7 @@ export class ApplicationRelationQueries extends TwoRelationsQueries<

async getOrganizationsByApplicationId(
applicationId: string,
{ limit, offset }: GetEntitiesOptions
options?: GetEntitiesOptions

Check warning on line 31 in packages/core/src/queries/organization/application-relations.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/queries/organization/application-relations.ts#L31

Added line #L31 was not covered by tests
): Promise<[totalCount: number, organizations: readonly OrganizationWithRoles[]]> {
const organizations = convertToIdentifiers(Organizations, true);
const roles = convertToIdentifiers(OrganizationRoles, true);
Expand Down Expand Up @@ -57,8 +57,7 @@ export class ApplicationRelationQueries extends TwoRelationsQueries<
on ${relations.fields.organizationRoleId} = ${roles.fields.id}
where ${fields.applicationId} = ${applicationId}
group by ${organizations.fields.id}
limit ${limit}
offset ${offset}
${conditionalSql(options, ({ limit, offset }) => sql`limit ${limit} offset ${offset}`)}

Check warning on line 60 in packages/core/src/queries/organization/application-relations.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/queries/organization/application-relations.ts#L60

Added line #L60 was not covered by tests
`),
]);

Expand Down
Loading

0 comments on commit 46fcdf2

Please sign in to comment.