From 58fd32e456828fb1180d277ed069b4d6c94ea90f Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 28 May 2024 13:07:34 +0800 Subject: [PATCH] refactor(console): setup m2m roles after creating m2m app (#5924) --- .../CreateForm/Footer/index.tsx | 0 .../CreateForm/index.module.scss | 0 .../ApplicationCreation}/CreateForm/index.tsx | 5 +- .../components/ApplicationCreation/index.tsx | 57 ++++++ .../RoleAssignmentModal/index.module.scss | 6 + .../components/RoleAssignmentModal/index.tsx | 168 ++++++++++++++++++ .../index.tsx | 4 +- .../components/GuideLibrary/index.tsx | 8 +- .../components/GuideLibraryModal/index.tsx | 6 +- .../console/src/pages/GetStarted/index.tsx | 8 +- .../components/AssignToRoleModal/index.tsx | 119 ------------- .../src/pages/UserDetails/UserRoles/index.tsx | 4 +- .../tests/console/applications/index.test.ts | 21 +++ .../translation/admin-console/applications.ts | 7 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 7 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 7 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 5 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 7 + .../translation/admin-console/applications.ts | 7 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 6 + .../translation/admin-console/applications.ts | 5 + .../translation/admin-console/applications.ts | 5 + .../translation/admin-console/applications.ts | 5 + 28 files changed, 360 insertions(+), 137 deletions(-) rename packages/console/src/{pages/Applications/components => components/ApplicationCreation}/CreateForm/Footer/index.tsx (100%) rename packages/console/src/{pages/Applications/components => components/ApplicationCreation}/CreateForm/index.module.scss (100%) rename packages/console/src/{pages/Applications/components => components/ApplicationCreation}/CreateForm/index.tsx (98%) create mode 100644 packages/console/src/components/ApplicationCreation/index.tsx create mode 100644 packages/console/src/components/RoleAssignmentModal/index.module.scss create mode 100644 packages/console/src/components/RoleAssignmentModal/index.tsx delete mode 100644 packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx diff --git a/packages/console/src/pages/Applications/components/CreateForm/Footer/index.tsx b/packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx similarity index 100% rename from packages/console/src/pages/Applications/components/CreateForm/Footer/index.tsx rename to packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx diff --git a/packages/console/src/pages/Applications/components/CreateForm/index.module.scss b/packages/console/src/components/ApplicationCreation/CreateForm/index.module.scss similarity index 100% rename from packages/console/src/pages/Applications/components/CreateForm/index.module.scss rename to packages/console/src/components/ApplicationCreation/CreateForm/index.module.scss diff --git a/packages/console/src/pages/Applications/components/CreateForm/index.tsx b/packages/console/src/components/ApplicationCreation/CreateForm/index.tsx similarity index 98% rename from packages/console/src/pages/Applications/components/CreateForm/index.tsx rename to packages/console/src/components/ApplicationCreation/CreateForm/index.tsx index 31c172a89..ada8a7984 100644 --- a/packages/console/src/pages/Applications/components/CreateForm/index.tsx +++ b/packages/console/src/components/ApplicationCreation/CreateForm/index.tsx @@ -15,12 +15,11 @@ import RadioGroup, { Radio } from '@/ds-components/RadioGroup'; import TextInput from '@/ds-components/TextInput'; import useApi from '@/hooks/use-api'; import useCurrentUser from '@/hooks/use-current-user'; +import TypeDescription from '@/pages/Applications/components/TypeDescription'; import * as modalStyles from '@/scss/modal.module.scss'; import { applicationTypeI18nKey } from '@/types/applications'; import { trySubmitSafe } from '@/utils/form'; -import TypeDescription from '../TypeDescription'; - import Footer from './Footer'; import * as styles from './index.module.scss'; @@ -31,7 +30,7 @@ type FormData = { isThirdParty?: boolean; }; -type Props = { +export type Props = { readonly isDefaultCreateThirdParty?: boolean; readonly defaultCreateType?: ApplicationType; readonly defaultCreateFrameworkName?: string; diff --git a/packages/console/src/components/ApplicationCreation/index.tsx b/packages/console/src/components/ApplicationCreation/index.tsx new file mode 100644 index 000000000..f52efb5fe --- /dev/null +++ b/packages/console/src/components/ApplicationCreation/index.tsx @@ -0,0 +1,57 @@ +import { ApplicationType, RoleType, type Application } from '@logto/schemas'; +import { useCallback, useState } from 'react'; + +import RoleAssignmentModal from '@/components/RoleAssignmentModal'; +import { isDevFeaturesEnabled } from '@/consts/env'; + +import CreateForm, { type Props as CreateApplicationFormProps } from './CreateForm'; + +type Props = Omit & { + /** + * The callback function that will be called when the application creation process is completed or canceled. + */ + readonly onCompleted?: (createdApp?: Application) => void; +}; + +/** + * The component for handling application creation (including create an application and setup its permissions if needed). + */ +function ApplicationCreation({ onCompleted, ...reset }: Props) { + const [createdMachineToMachineApplication, setCreatedMachineToMachineApplication] = + useState(); + + const createFormModalCloseHandler = useCallback( + (createdApp?: Application) => { + // Todo @xiaoyijun remove dev feature flag + if (isDevFeaturesEnabled && createdApp?.type === ApplicationType.MachineToMachine) { + setCreatedMachineToMachineApplication(createdApp); + return; + } + + onCompleted?.(createdApp); + }, + [onCompleted] + ); + + if (createdMachineToMachineApplication) { + return ( + { + onCompleted?.(createdMachineToMachineApplication); + }} + /> + ); + } + + return ; +} + +export default ApplicationCreation; diff --git a/packages/console/src/components/RoleAssignmentModal/index.module.scss b/packages/console/src/components/RoleAssignmentModal/index.module.scss new file mode 100644 index 000000000..31b48caa2 --- /dev/null +++ b/packages/console/src/components/RoleAssignmentModal/index.module.scss @@ -0,0 +1,6 @@ +@use '@/scss/underscore' as _; + +.hint { + margin-top: _.unit(2); + font: var(--font-body-2); +} diff --git a/packages/console/src/components/RoleAssignmentModal/index.tsx b/packages/console/src/components/RoleAssignmentModal/index.tsx new file mode 100644 index 000000000..2d47df00a --- /dev/null +++ b/packages/console/src/components/RoleAssignmentModal/index.tsx @@ -0,0 +1,168 @@ +import { type AdminConsoleKey } from '@logto/phrases'; +import type { RoleResponse, UserProfileResponse, Application } from '@logto/schemas'; +import { RoleType } from '@logto/schemas'; +import { cond } from '@silverhand/essentials'; +import { useState } from 'react'; +import { toast } from 'react-hot-toast'; +import { Trans, useTranslation } from 'react-i18next'; +import ReactModal from 'react-modal'; + +import RolesTransfer from '@/components/RolesTransfer'; +import Button from '@/ds-components/Button'; +import DangerousRaw from '@/ds-components/DangerousRaw'; +import ModalLayout from '@/ds-components/ModalLayout'; +import TextLink from '@/ds-components/TextLink'; +import useApi from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; +import { getUserTitle } from '@/utils/user'; + +import * as styles from './index.module.scss'; + +type Props = ( + | { + entity: UserProfileResponse; + type: RoleType.User; + } + | { + entity: Application; + type: RoleType.MachineToMachine; + } +) & { + readonly onClose: (success?: boolean) => void; + /** + * The overrides for the modal text. + * If specified, the title will be overridden. + * If not specified, the default title will vary based on the type. + */ + readonly modalTextOverrides?: { + title?: AdminConsoleKey; + subtitle?: AdminConsoleKey; + }; + readonly isSkippable?: boolean; + readonly isMachineToMachineRoleCreationHintVisible?: boolean; +}; + +function RoleAssignmentModal({ + entity, + onClose, + type, + modalTextOverrides, + isSkippable, + isMachineToMachineRoleCreationHintVisible, +}: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [roles, setRoles] = useState([]); + const isForUser = type === RoleType.User; + + const api = useApi(); + + const handleAssign = async () => { + if (isSubmitting || roles.length === 0) { + return; + } + + setIsSubmitting(true); + + try { + await api.post(`api/${isForUser ? 'users' : 'applications'}/${entity.id}/roles`, { + json: { roleIds: roles.map(({ id }) => id) }, + }); + toast.success(t('user_details.roles.role_assigned')); + onClose(true); + } finally { + setIsSubmitting(false); + } + }; + + return ( + { + onClose(); + }} + > + + {t( + isForUser + ? 'user_details.roles.assign_title' + : 'application_details.roles.assign_title', + { name: isForUser ? getUserTitle(entity) : entity.name } + )} + + ) + } + subtitle={ + cond(modalTextOverrides?.subtitle) ?? ( + + {t( + isForUser + ? 'user_details.roles.assign_subtitle' + : 'application_details.roles.assign_subtitle', + { name: isForUser ? getUserTitle(entity) : entity.name } + )} + + ) + } + size="large" + footer={ + <> + {isSkippable && ( +