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,
|
type: ConnectorConfigFormItemType.Text,
|
||||||
required: true,
|
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 { emailRegEx, phoneInputRegEx } from '@logto/core-kit';
|
||||||
import { ConnectorType } from '@logto/schemas';
|
import { ConnectorType } from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
@ -40,6 +41,7 @@ function ConnectorTester({ connectorFactoryId, connectorType, className, parse }
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const isSms = connectorType === ConnectorType.Sms;
|
const isSms = connectorType === ConnectorType.Sms;
|
||||||
|
const isEmailServiceConnector = connectorFactoryId === ServiceConnector.Email;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showTooltip) {
|
if (!showTooltip) {
|
||||||
|
@ -110,7 +112,13 @@ function ConnectorTester({ connectorFactoryId, connectorType, className, parse }
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</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 className={styles.error}>{inputError?.message}</div>
|
||||||
</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 { ConnectorType } from '@logto/schemas';
|
||||||
import type { ConnectorResponse } from '@logto/schemas';
|
import type { ConnectorResponse } from '@logto/schemas';
|
||||||
import type { Optional } from '@silverhand/essentials';
|
import type { Optional } from '@silverhand/essentials';
|
||||||
|
@ -21,6 +22,8 @@ import type { ConnectorFormType } from '@/types/connector';
|
||||||
import { initFormData } from '@/utils/connector-form';
|
import { initFormData } from '@/utils/connector-form';
|
||||||
import { trySubmitSafe } from '@/utils/form';
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
|
|
||||||
|
import EmailServiceConnectorForm from './EmailServiceConnectorForm';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
connectorData: ConnectorResponse;
|
connectorData: ConnectorResponse;
|
||||||
|
@ -60,8 +63,17 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
||||||
reset,
|
reset,
|
||||||
setValue,
|
setValue,
|
||||||
} = methods;
|
} = 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(() => {
|
useEffect(() => {
|
||||||
const { metadata, config, syncProfile } = connectorData;
|
const { metadata, config, syncProfile } = connectorData;
|
||||||
const { name, logo, logoDark } = metadata;
|
const { name, logo, logoDark } = metadata;
|
||||||
|
@ -136,32 +148,30 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
||||||
description="connector_details.settings_description"
|
description="connector_details.settings_description"
|
||||||
learnMoreLink={getDocumentationUrl('/docs/references/connectors')}
|
learnMoreLink={getDocumentationUrl('/docs/references/connectors')}
|
||||||
>
|
>
|
||||||
<BasicForm
|
<BasicForm isStandard={isStandardConnector} isDarkDefaultVisible={Boolean(logoDark)} />
|
||||||
isStandard={connectorData.isStandard}
|
|
||||||
isDarkDefaultVisible={Boolean(connectorData.metadata.logoDark)}
|
|
||||||
/>
|
|
||||||
</FormCard>
|
</FormCard>
|
||||||
)}
|
)}
|
||||||
|
{isEmailServiceConnector ? (
|
||||||
|
<EmailServiceConnectorForm extraInfo={connectorData.extraInfo} />
|
||||||
|
) : (
|
||||||
<FormCard
|
<FormCard
|
||||||
title="connector_details.parameter_configuration"
|
title="connector_details.parameter_configuration"
|
||||||
description={conditional(!isSocialConnector && 'connector_details.settings_description')}
|
description={conditional(
|
||||||
|
!isSocialConnector && 'connector_details.settings_description'
|
||||||
|
)}
|
||||||
learnMoreLink={conditional(
|
learnMoreLink={conditional(
|
||||||
!isSocialConnector && getDocumentationUrl('/docs/references/connectors')
|
!isSocialConnector && getDocumentationUrl('/docs/references/connectors')
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ConfigForm
|
<ConfigForm formItems={formItems} connectorId={id} connectorType={connectorType} />
|
||||||
formItems={connectorData.formItems}
|
|
||||||
connectorId={connectorData.id}
|
|
||||||
connectorType={connectorData.type}
|
|
||||||
/>
|
|
||||||
</FormCard>
|
</FormCard>
|
||||||
{/* Tell typescript that the connectorType is Email or Sms */}
|
)}
|
||||||
{connectorData.type !== ConnectorType.Social && (
|
{!isSocialConnector && (
|
||||||
<FormCard title="connector_details.test_connection">
|
<FormCard title="connector_details.test_connection">
|
||||||
<ConnectorTester
|
<ConnectorTester
|
||||||
connectorFactoryId={connectorData.connectorId}
|
connectorFactoryId={connectorId}
|
||||||
connectorType={connectorData.type}
|
connectorType={connectorType}
|
||||||
parse={() => configParser(watch(), connectorData.formItems)}
|
parse={() => configParser(watch(), formItems)}
|
||||||
/>
|
/>
|
||||||
</FormCard>
|
</FormCard>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in a new issue