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 { 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<string> => {
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<ConnectorFormType>({
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<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,
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<string, unknown>` 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<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}
>

View file

@ -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<Record<string, string>>();
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<ConnectorFormType>({
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<string, unknown>` 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();

View file

@ -1,4 +1,5 @@
import type { ConnectorResponse } from '@logto/schemas';
import { type Nullable } from '@silverhand/essentials';
export type ConnectorGroup<T = ConnectorResponse> = Pick<
ConnectorResponse,
@ -14,11 +15,11 @@ export enum SyncProfileMode {
}
export type ConnectorFormType = {
name?: string;
logo?: string;
logoDark?: Nullable<string>;
target?: string;
syncProfile: SyncProfileMode;
jsonConfig: string; // Support editing configs by the code editor
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 { 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<string, unknown>
) => {
const initFormData = (formItems: ConnectorConfigFormItem[], config?: Record<string, unknown>) => {
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,
};
};