From 97fc519ac14f70a0c2da54bb4381935e95b71a7e Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 4 Jul 2023 11:13:25 +0800 Subject: [PATCH] refactor(console): refactor custom connector form initialization (#4110) --- .../ConnectorContent/index.tsx | 55 +++++-------------- .../src/pages/Connectors/Guide/index.tsx | 30 +++++----- packages/console/src/types/connector.ts | 11 ++-- packages/console/src/utils/connector-form.ts | 43 +++++++++++++-- 4 files changed, 71 insertions(+), 68 deletions(-) diff --git a/packages/console/src/pages/ConnectorDetails/ConnectorContent/index.tsx b/packages/console/src/pages/ConnectorDetails/ConnectorContent/index.tsx index 2cf3e39b6..5817b439b 100644 --- a/packages/console/src/pages/ConnectorDetails/ConnectorContent/index.tsx +++ b/packages/console/src/pages/ConnectorDetails/ConnectorContent/index.tsx @@ -1,7 +1,6 @@ import { ServiceConnector } from '@logto/connector-kit'; import { ConnectorType } from '@logto/schemas'; import type { ConnectorResponse } from '@logto/schemas'; -import type { Optional } from '@silverhand/essentials'; import { conditional } from '@silverhand/essentials'; import { useEffect, useMemo } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; @@ -19,7 +18,7 @@ import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config- import useDocumentationUrl from '@/hooks/use-documentation-url'; import { SyncProfileMode } from '@/types/connector'; import type { ConnectorFormType } from '@/types/connector'; -import { initFormData } from '@/utils/connector-form'; +import { convertResponseToForm } from '@/utils/connector-form'; import { trySubmitSafe } from '@/utils/form'; import EmailServiceConnectorForm from './EmailServiceConnectorForm'; @@ -30,29 +29,22 @@ type Props = { onConnectorUpdated: (connector: ConnectorResponse) => void; }; -const getConnectorTarget = (connectorData: ConnectorResponse): Optional => { - return conditional( - connectorData.type === ConnectorType.Social && - !connectorData.isStandard && - (connectorData.metadata.target ?? connectorData.target) - ); -}; - function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { getDocumentationUrl } = useDocumentationUrl(); const api = useApi(); - - const formConfig = useMemo(() => { - const { formItems, config } = connectorData; - return conditional(formItems && initFormData(formItems, config)) ?? {}; - }, [connectorData]); + const formData = useMemo(() => convertResponseToForm(connectorData), [connectorData]); const methods = useForm({ reValidateMode: 'onBlur', defaultValues: { - syncProfile: SyncProfileMode.OnlyAtRegister, - target: getConnectorTarget(connectorData), + ...formData, + /** + * Note: + * The `formConfig` will be setup in the `useEffect` hook since react-hook-form's `useForm` hook infers `Record` to `{ [x: string]: {} | undefined }` incorrectly, + * this causes we cannot apply the default value of `formConfig` to the form. + */ + formConfig: {}, }, }); @@ -61,7 +53,6 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop handleSubmit, watch, reset, - setValue, } = methods; const { @@ -72,27 +63,13 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop isStandard: isStandardConnector, metadata: { logoDark }, } = connectorData; + const isSocialConnector = connectorType === ConnectorType.Social; const isEmailServiceConnector = connectorId === ServiceConnector.Email; - useEffect(() => { - const { metadata, config, syncProfile } = connectorData; - const { name, logo, logoDark } = metadata; - reset({ - target: getConnectorTarget(connectorData), - logo, - logoDark: logoDark ?? '', - name: name?.en, - jsonConfig: JSON.stringify(config, null, 2), - syncProfile: syncProfile ? SyncProfileMode.EachSignIn : SyncProfileMode.OnlyAtRegister, - }); - /** - * Note: - * Set `formConfig` independently. - * Since react-hook-form's reset function infers `Record` to `{ [x: string]: {} | undefined }` incorrectly. - */ - setValue('formConfig', formConfig, { shouldDirty: false }); - }, [connectorData, formConfig, reset, setValue]); + useEffect(() => { + reset(formData); + }, [formData, reset]); const configParser = useConnectorFormConfigParser(); @@ -133,12 +110,6 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop isSubmitting={isSubmitting} onDiscard={() => { reset(); - /** - * Note: - * Reset `formConfig` manually since react-hook-form's `useForm` hook infers `Record` to `{ [x: string]: {} | undefined }` incorrectly, - * this causes we cannot apply the default value of `formConfig` to the form. - */ - setValue('formConfig', formConfig, { shouldDirty: false }); }} onSubmit={onSubmit} > diff --git a/packages/console/src/pages/Connectors/Guide/index.tsx b/packages/console/src/pages/Connectors/Guide/index.tsx index a551740b5..aa218a8d7 100644 --- a/packages/console/src/pages/Connectors/Guide/index.tsx +++ b/packages/console/src/pages/Connectors/Guide/index.tsx @@ -29,7 +29,7 @@ import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config- import * as modalStyles from '@/scss/modal.module.scss'; import type { ConnectorFormType } from '@/types/connector'; import { SyncProfileMode } from '@/types/connector'; -import { initFormData } from '@/utils/connector-form'; +import { convertFactoryResponseToForm } from '@/utils/connector-form'; import { trySubmitSafe } from '@/utils/form'; import { splitMarkdownByTitle } from '../utils'; @@ -50,37 +50,35 @@ function Guide({ connector, onClose }: Props) { const { updateConfigs } = useConfigs(); const [conflictConnectorName, setConflictConnectorName] = useState>(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { type: connectorType, formItems, target, isStandard, configTemplate } = connector ?? {}; + const { type: connectorType, formItems, isStandard } = connector ?? {}; const { language } = i18next; const isSocialConnector = connectorType !== ConnectorType.Sms && connectorType !== ConnectorType.Email; + const methods = useForm({ reValidateMode: 'onBlur', + defaultValues: { + jsonConfig: '{}', + formConfig: {}, + syncProfile: SyncProfileMode.OnlyAtRegister, + }, }); + const { formState: { isSubmitting }, handleSubmit, watch, setError, reset, - setValue, } = methods; useEffect(() => { - const formConfig = conditional(formItems && initFormData(formItems)); - reset({ - ...(configTemplate ? { jsonConfig: configTemplate } : {}), - ...(isSocialConnector && !isStandard && target ? { target } : {}), - syncProfile: SyncProfileMode.OnlyAtRegister, - }); - /** - * Note: - * Set `formConfig` independently. - * Since react-hook-form's reset function infers `Record` to `{ [x: string]: {} | undefined }` incorrectly. - */ - setValue('formConfig', formConfig ?? {}, { shouldDirty: false }); - }, [formItems, reset, configTemplate, target, isSocialConnector, isStandard, setValue]); + if (!connector) { + return; + } + reset(convertFactoryResponseToForm(connector)); + }, [reset, connector]); const configParser = useConnectorFormConfigParser(); diff --git a/packages/console/src/types/connector.ts b/packages/console/src/types/connector.ts index d56e1a731..ef8d38660 100644 --- a/packages/console/src/types/connector.ts +++ b/packages/console/src/types/connector.ts @@ -1,4 +1,5 @@ import type { ConnectorResponse } from '@logto/schemas'; +import { type Nullable } from '@silverhand/essentials'; export type ConnectorGroup = Pick< ConnectorResponse, @@ -14,11 +15,11 @@ export enum SyncProfileMode { } export type ConnectorFormType = { + name?: string; + logo?: string; + logoDark?: Nullable; + target?: string; + syncProfile: SyncProfileMode; jsonConfig: string; // Support editing configs by the code editor formConfig: Record; // Support custom connector config form - name: string; - logo: string; - logoDark: string; - target: string; - syncProfile: SyncProfileMode; }; diff --git a/packages/console/src/utils/connector-form.ts b/packages/console/src/utils/connector-form.ts index 24253334b..dc5eb4238 100644 --- a/packages/console/src/utils/connector-form.ts +++ b/packages/console/src/utils/connector-form.ts @@ -1,12 +1,12 @@ import type { ConnectorConfigFormItem } from '@logto/connector-kit'; -import { ConnectorConfigFormItemType } from '@logto/connector-kit'; +import { ConnectorConfigFormItemType, ConnectorType } from '@logto/connector-kit'; +import { type ConnectorFactoryResponse, type ConnectorResponse } from '@logto/schemas'; +import { conditional } from '@silverhand/essentials'; +import { SyncProfileMode, type ConnectorFormType } from '@/types/connector'; import { safeParseJson } from '@/utils/json'; -export const initFormData = ( - formItems: ConnectorConfigFormItem[], - config?: Record -) => { +const initFormData = (formItems: ConnectorConfigFormItem[], config?: Record) => { const data: Array<[string, unknown]> = formItems.map((item) => { const value = config?.[item.key] ?? item.defaultValue; @@ -60,3 +60,36 @@ export const parseFormConfig = ( .filter((item): item is [string, unknown] => Array.isArray(item)) ); }; + +export const convertResponseToForm = (connector: ConnectorResponse): ConnectorFormType => { + const { metadata, type, config, syncProfile, isStandard, formItems, target } = connector; + const { name, logo, logoDark } = metadata; + const formConfig = conditional(formItems && initFormData(formItems, config)) ?? {}; + + return { + name: name?.en, + logo, + logoDark, + target: conditional( + type === ConnectorType.Social && !isStandard && (metadata.target ?? target) + ), + syncProfile: syncProfile ? SyncProfileMode.EachSignIn : SyncProfileMode.OnlyAtRegister, + jsonConfig: JSON.stringify(config, null, 2), + formConfig, + }; +}; + +export const convertFactoryResponseToForm = ( + connectorFactory: ConnectorFactoryResponse +): ConnectorFormType => { + const { formItems, configTemplate, type, isStandard, target } = connectorFactory; + const jsonConfig = configTemplate ?? '{}'; + const formConfig = conditional(formItems && initFormData(formItems)) ?? {}; + + return { + target: conditional(type === ConnectorType.Social && !isStandard && target), + syncProfile: SyncProfileMode.OnlyAtRegister, + jsonConfig, + formConfig, + }; +};