mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): implement email service connector details page (#4089)
This commit is contained in:
parent
909591f1c5
commit
bbf0551273
5 changed files with 178 additions and 24 deletions
|
@ -80,6 +80,21 @@ export const defaultMetadata: ConnectorMetadata = {
|
|||
type: ConnectorConfigFormItemType.Text,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'fromName',
|
||||
label: 'From Name',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
},
|
||||
{
|
||||
key: 'companyAddress',
|
||||
label: 'Company Address',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
},
|
||||
{
|
||||
key: 'appLogo',
|
||||
label: 'App Logo',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ServiceConnector } from '@logto/connector-kit';
|
||||
import { emailRegEx, phoneInputRegEx } from '@logto/core-kit';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
@ -40,6 +41,7 @@ function ConnectorTester({ connectorFactoryId, connectorType, className, parse }
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const api = useApi();
|
||||
const isSms = connectorType === ConnectorType.Sms;
|
||||
const isEmailServiceConnector = connectorFactoryId === ServiceConnector.Email;
|
||||
|
||||
useEffect(() => {
|
||||
if (!showTooltip) {
|
||||
|
@ -110,7 +112,13 @@ function ConnectorTester({ connectorFactoryId, connectorType, className, parse }
|
|||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={styles.description}>{t('connector_details.test_sender_description')}</div>
|
||||
<div className={styles.description}>
|
||||
{t(
|
||||
isEmailServiceConnector
|
||||
? 'connector_details.logto_email.test_notes'
|
||||
: 'connector_details.test_sender_description'
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.error}>{inputError?.message}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.imageFieldHeadline {
|
||||
margin-bottom: _.unit(2);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { urlRegEx } from '@logto/connector-kit';
|
||||
import { conditional, conditionalString } from '@silverhand/essentials';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
import FormCard from '@/components/FormCard';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
import ImageUploaderField from '@/ds-components/Uploader/ImageUploaderField';
|
||||
import useUserAssetsService from '@/hooks/use-user-assets-service';
|
||||
import { type ConnectorFormType } from '@/types/connector';
|
||||
import { uriValidator } from '@/utils/validator';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
extraInfo?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const extraInfoGuard = z.object({
|
||||
fromEmail: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Note:
|
||||
* This `EmailServiceConnectorForm` is hard-coded since the custom connector config form does not support i18n and we need i18n for our built-in connectors.
|
||||
*
|
||||
* TODO: @xiaoyijun @darcyYe remove this hard-coded form once the custom connector config form supports i18n.
|
||||
*/
|
||||
function EmailServiceConnectorForm({ extraInfo }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { isReady: isUserAssetsServiceReady } = useUserAssetsService();
|
||||
const parsedExtraInfo = extraInfoGuard.safeParse(extraInfo ?? {});
|
||||
|
||||
const {
|
||||
control,
|
||||
register,
|
||||
formState: {
|
||||
errors: { formConfig: fromConfigErrors },
|
||||
},
|
||||
} = useFormContext<ConnectorFormType>();
|
||||
|
||||
const validateInput = (value: string) => {
|
||||
const containUrl = urlRegEx.test(value);
|
||||
return containUrl ? t('connector_details.logto_email.urls_not_allowed') : true;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormCard
|
||||
title="connector_details.logto_email.email_template_title"
|
||||
description="connector_details.logto_email.template_description"
|
||||
>
|
||||
<FormField title="connector_details.logto_email.from_email_field">
|
||||
<TextInput
|
||||
readOnly
|
||||
value={conditionalString(parsedExtraInfo.success && parsedExtraInfo.data.fromEmail)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
title="connector_details.logto_email.from_name_field"
|
||||
tip={<DynamicT forKey="connector_details.logto_email.from_name_tip" />}
|
||||
>
|
||||
<TextInput
|
||||
{...register('formConfig.fromName', {
|
||||
validate: (value) => validateInput(conditionalString(value)),
|
||||
})}
|
||||
error={fromConfigErrors?.fromName?.message}
|
||||
placeholder={t('connector_details.logto_email.from_name_placeholder')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
title="connector_details.logto_email.company_address_field"
|
||||
tip={<DynamicT forKey="connector_details.logto_email.company_address_tip" />}
|
||||
>
|
||||
<TextInput
|
||||
{...register('formConfig.companyAddress', {
|
||||
validate: (value) => validateInput(conditionalString(value)),
|
||||
})}
|
||||
error={fromConfigErrors?.companyAddress?.message}
|
||||
placeholder={t('connector_details.logto_email.company_address_placeholder')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
title="connector_details.logto_email.app_logo_field"
|
||||
tip={<DynamicT forKey="connector_details.logto_email.app_logo_tip" />}
|
||||
headlineClassName={conditional(isUserAssetsServiceReady && styles.imageFieldHeadline)}
|
||||
>
|
||||
{isUserAssetsServiceReady ? (
|
||||
<Controller
|
||||
name="formConfig.appLogo"
|
||||
control={control}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<ImageUploaderField
|
||||
name={name}
|
||||
value={conditionalString(value)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
{...register('formConfig.appLogo', {
|
||||
validate: (value) =>
|
||||
!value || uriValidator(conditionalString(value)) || t('errors.invalid_uri_format'),
|
||||
})}
|
||||
error={fromConfigErrors?.appLogo?.message}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</FormCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmailServiceConnectorForm;
|
|
@ -1,3 +1,4 @@
|
|||
import { ServiceConnector } from '@logto/connector-kit';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import type { Optional } from '@silverhand/essentials';
|
||||
|
@ -21,6 +22,8 @@ import type { ConnectorFormType } from '@/types/connector';
|
|||
import { initFormData } from '@/utils/connector-form';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
|
||||
import EmailServiceConnectorForm from './EmailServiceConnectorForm';
|
||||
|
||||
type Props = {
|
||||
isDeleted: boolean;
|
||||
connectorData: ConnectorResponse;
|
||||
|
@ -60,8 +63,17 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
|||
reset,
|
||||
setValue,
|
||||
} = methods;
|
||||
const isSocialConnector = connectorData.type === ConnectorType.Social;
|
||||
|
||||
const {
|
||||
id,
|
||||
connectorId,
|
||||
type: connectorType,
|
||||
formItems,
|
||||
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;
|
||||
|
@ -136,32 +148,30 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
|||
description="connector_details.settings_description"
|
||||
learnMoreLink={getDocumentationUrl('/docs/references/connectors')}
|
||||
>
|
||||
<BasicForm
|
||||
isStandard={connectorData.isStandard}
|
||||
isDarkDefaultVisible={Boolean(connectorData.metadata.logoDark)}
|
||||
/>
|
||||
<BasicForm isStandard={isStandardConnector} isDarkDefaultVisible={Boolean(logoDark)} />
|
||||
</FormCard>
|
||||
)}
|
||||
<FormCard
|
||||
title="connector_details.parameter_configuration"
|
||||
description={conditional(!isSocialConnector && 'connector_details.settings_description')}
|
||||
learnMoreLink={conditional(
|
||||
!isSocialConnector && getDocumentationUrl('/docs/references/connectors')
|
||||
)}
|
||||
>
|
||||
<ConfigForm
|
||||
formItems={connectorData.formItems}
|
||||
connectorId={connectorData.id}
|
||||
connectorType={connectorData.type}
|
||||
/>
|
||||
</FormCard>
|
||||
{/* Tell typescript that the connectorType is Email or Sms */}
|
||||
{connectorData.type !== ConnectorType.Social && (
|
||||
{isEmailServiceConnector ? (
|
||||
<EmailServiceConnectorForm extraInfo={connectorData.extraInfo} />
|
||||
) : (
|
||||
<FormCard
|
||||
title="connector_details.parameter_configuration"
|
||||
description={conditional(
|
||||
!isSocialConnector && 'connector_details.settings_description'
|
||||
)}
|
||||
learnMoreLink={conditional(
|
||||
!isSocialConnector && getDocumentationUrl('/docs/references/connectors')
|
||||
)}
|
||||
>
|
||||
<ConfigForm formItems={formItems} connectorId={id} connectorType={connectorType} />
|
||||
</FormCard>
|
||||
)}
|
||||
{!isSocialConnector && (
|
||||
<FormCard title="connector_details.test_connection">
|
||||
<ConnectorTester
|
||||
connectorFactoryId={connectorData.connectorId}
|
||||
connectorType={connectorData.type}
|
||||
parse={() => configParser(watch(), connectorData.formItems)}
|
||||
connectorFactoryId={connectorId}
|
||||
connectorType={connectorType}
|
||||
parse={() => configParser(watch(), formItems)}
|
||||
/>
|
||||
</FormCard>
|
||||
)}
|
||||
|
|
Loading…
Reference in a new issue