0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(console): refactor custom connector form initialization (#4110)

This commit is contained in:
Xiao Yijun 2023-07-04 11:13:25 +08:00 committed by GitHub
parent bbf0551273
commit 97fc519ac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 68 deletions

View file

@ -1,7 +1,6 @@
import { ServiceConnector } from '@logto/connector-kit'; import { ServiceConnector } from '@logto/connector-kit';
import { ConnectorType } from '@logto/schemas'; import { ConnectorType } from '@logto/schemas';
import type { ConnectorResponse } from '@logto/schemas'; import type { ConnectorResponse } from '@logto/schemas';
import type { Optional } from '@silverhand/essentials';
import { conditional } from '@silverhand/essentials'; import { conditional } from '@silverhand/essentials';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; 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 useDocumentationUrl from '@/hooks/use-documentation-url';
import { SyncProfileMode } from '@/types/connector'; import { SyncProfileMode } from '@/types/connector';
import type { ConnectorFormType } 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 { trySubmitSafe } from '@/utils/form';
import EmailServiceConnectorForm from './EmailServiceConnectorForm'; import EmailServiceConnectorForm from './EmailServiceConnectorForm';
@ -30,29 +29,22 @@ type Props = {
onConnectorUpdated: (connector: ConnectorResponse) => void; onConnectorUpdated: (connector: ConnectorResponse) => void;
}; };
const getConnectorTarget = (connectorData: ConnectorResponse): Optional<string> => {
return conditional(
connectorData.type === ConnectorType.Social &&
!connectorData.isStandard &&
(connectorData.metadata.target ?? connectorData.target)
);
};
function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Props) { function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl(); const { getDocumentationUrl } = useDocumentationUrl();
const api = useApi(); const api = useApi();
const formData = useMemo(() => convertResponseToForm(connectorData), [connectorData]);
const formConfig = useMemo(() => {
const { formItems, config } = connectorData;
return conditional(formItems && initFormData(formItems, config)) ?? {};
}, [connectorData]);
const methods = useForm<ConnectorFormType>({ const methods = useForm<ConnectorFormType>({
reValidateMode: 'onBlur', reValidateMode: 'onBlur',
defaultValues: { defaultValues: {
syncProfile: SyncProfileMode.OnlyAtRegister, ...formData,
target: getConnectorTarget(connectorData), /**
* Note:
* The `formConfig` will be setup in the `useEffect` hook since react-hook-form's `useForm` hook infers `Record<string, unknown>` 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, handleSubmit,
watch, watch,
reset, reset,
setValue,
} = methods; } = methods;
const { const {
@ -72,27 +63,13 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
isStandard: isStandardConnector, isStandard: isStandardConnector,
metadata: { logoDark }, metadata: { logoDark },
} = connectorData; } = connectorData;
const isSocialConnector = connectorType === ConnectorType.Social; const isSocialConnector = connectorType === ConnectorType.Social;
const isEmailServiceConnector = connectorId === ServiceConnector.Email; const isEmailServiceConnector = connectorId === ServiceConnector.Email;
useEffect(() => {
const { metadata, config, syncProfile } = connectorData;
const { name, logo, logoDark } = metadata;
reset({ useEffect(() => {
target: getConnectorTarget(connectorData), reset(formData);
logo, }, [formData, reset]);
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<string, unknown>` to `{ [x: string]: {} | undefined }` incorrectly.
*/
setValue('formConfig', formConfig, { shouldDirty: false });
}, [connectorData, formConfig, reset, setValue]);
const configParser = useConnectorFormConfigParser(); const configParser = useConnectorFormConfigParser();
@ -133,12 +110,6 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
onDiscard={() => { onDiscard={() => {
reset(); reset();
/**
* Note:
* Reset `formConfig` manually since react-hook-form's `useForm` hook infers `Record<string, unknown>` 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} onSubmit={onSubmit}
> >

View file

@ -29,7 +29,7 @@ import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config-
import * as modalStyles from '@/scss/modal.module.scss'; import * as modalStyles from '@/scss/modal.module.scss';
import type { ConnectorFormType } from '@/types/connector'; import type { ConnectorFormType } from '@/types/connector';
import { SyncProfileMode } 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 { trySubmitSafe } from '@/utils/form';
import { splitMarkdownByTitle } from '../utils'; import { splitMarkdownByTitle } from '../utils';
@ -50,37 +50,35 @@ function Guide({ connector, onClose }: Props) {
const { updateConfigs } = useConfigs(); const { updateConfigs } = useConfigs();
const [conflictConnectorName, setConflictConnectorName] = useState<Record<string, string>>(); const [conflictConnectorName, setConflictConnectorName] = useState<Record<string, string>>();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); 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 { language } = i18next;
const isSocialConnector = const isSocialConnector =
connectorType !== ConnectorType.Sms && connectorType !== ConnectorType.Email; connectorType !== ConnectorType.Sms && connectorType !== ConnectorType.Email;
const methods = useForm<ConnectorFormType>({ const methods = useForm<ConnectorFormType>({
reValidateMode: 'onBlur', reValidateMode: 'onBlur',
defaultValues: {
jsonConfig: '{}',
formConfig: {},
syncProfile: SyncProfileMode.OnlyAtRegister,
},
}); });
const { const {
formState: { isSubmitting }, formState: { isSubmitting },
handleSubmit, handleSubmit,
watch, watch,
setError, setError,
reset, reset,
setValue,
} = methods; } = methods;
useEffect(() => { useEffect(() => {
const formConfig = conditional(formItems && initFormData(formItems)); if (!connector) {
reset({ return;
...(configTemplate ? { jsonConfig: configTemplate } : {}), }
...(isSocialConnector && !isStandard && target ? { target } : {}), reset(convertFactoryResponseToForm(connector));
syncProfile: SyncProfileMode.OnlyAtRegister, }, [reset, connector]);
});
/**
* Note:
* Set `formConfig` independently.
* Since react-hook-form's reset function infers `Record<string, unknown>` to `{ [x: string]: {} | undefined }` incorrectly.
*/
setValue('formConfig', formConfig ?? {}, { shouldDirty: false });
}, [formItems, reset, configTemplate, target, isSocialConnector, isStandard, setValue]);
const configParser = useConnectorFormConfigParser(); const configParser = useConnectorFormConfigParser();

View file

@ -1,4 +1,5 @@
import type { ConnectorResponse } from '@logto/schemas'; import type { ConnectorResponse } from '@logto/schemas';
import { type Nullable } from '@silverhand/essentials';
export type ConnectorGroup<T = ConnectorResponse> = Pick< export type ConnectorGroup<T = ConnectorResponse> = Pick<
ConnectorResponse, ConnectorResponse,
@ -14,11 +15,11 @@ export enum SyncProfileMode {
} }
export type ConnectorFormType = { export type ConnectorFormType = {
name?: string;
logo?: string;
logoDark?: Nullable<string>;
target?: string;
syncProfile: SyncProfileMode;
jsonConfig: string; // Support editing configs by the code editor jsonConfig: string; // Support editing configs by the code editor
formConfig: Record<string, unknown>; // Support custom connector config form formConfig: Record<string, unknown>; // Support custom connector config form
name: string;
logo: string;
logoDark: string;
target: string;
syncProfile: SyncProfileMode;
}; };

View file

@ -1,12 +1,12 @@
import type { ConnectorConfigFormItem } from '@logto/connector-kit'; 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'; import { safeParseJson } from '@/utils/json';
export const initFormData = ( const initFormData = (formItems: ConnectorConfigFormItem[], config?: Record<string, unknown>) => {
formItems: ConnectorConfigFormItem[],
config?: Record<string, unknown>
) => {
const data: Array<[string, unknown]> = formItems.map((item) => { const data: Array<[string, unknown]> = formItems.map((item) => {
const value = config?.[item.key] ?? item.defaultValue; const value = config?.[item.key] ?? item.defaultValue;
@ -60,3 +60,36 @@ export const parseFormConfig = (
.filter((item): item is [string, unknown] => Array.isArray(item)) .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,
};
};