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

feat(console): create email service connector (#4108)

This commit is contained in:
Xiao Yijun 2023-07-05 12:09:08 +08:00 committed by GitHub
parent 44c09baba9
commit 244537ca03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 69 additions and 24 deletions

View file

@ -13,7 +13,7 @@ import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
import type { RequestError } from '@/hooks/use-api'; import type { RequestError } from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss'; import * as modalStyles from '@/scss/modal.module.scss';
import { getConnectorGroups } from '../utils'; import { getConnectorGroups } from '../../pages/Connectors/utils';
import PlatformSelector from './PlatformSelector'; import PlatformSelector from './PlatformSelector';
import Skeleton from './Skeleton'; import Skeleton from './Skeleton';
@ -26,7 +26,7 @@ type Props = {
onClose?: (connectorId?: string) => void; onClose?: (connectorId?: string) => void;
}; };
function CreateForm({ onClose, isOpen: isFormOpen, type }: Props) { function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
const { data: existingConnectors, error: connectorsError } = useSWR< const { data: existingConnectors, error: connectorsError } = useSWR<
ConnectorResponse[], ConnectorResponse[],
RequestError RequestError
@ -186,4 +186,4 @@ function CreateForm({ onClose, isOpen: isFormOpen, type }: Props) {
); );
} }
export default CreateForm; export default CreateConnectorForm;

View file

@ -18,7 +18,7 @@ export class RequestError extends Error {
} }
} }
type StaticApiProps = { export type StaticApiProps = {
prefixUrl?: URL; prefixUrl?: URL;
hideErrorToast?: boolean; hideErrorToast?: boolean;
resourceIndicator?: string; resourceIndicator?: string;

View file

@ -0,0 +1,25 @@
import { type ConnectorResponse } from '@logto/schemas';
import useApi, { type StaticApiProps } from './use-api';
import useConfigs from './use-configs';
const useConnectorApi = (props: Omit<StaticApiProps, 'prefixUrl'> = {}) => {
const api = useApi(props);
const { updateConfigs } = useConfigs();
const createConnector = async (payload: unknown) => {
const connector = await api
.post('api/connectors', {
json: payload,
})
.json<ConnectorResponse>();
await updateConfigs({ passwordlessConfigured: true });
return connector;
};
return {
createConnector,
};
};
export default useConnectorApi;

View file

@ -1,4 +1,5 @@
import { withAppInsights } from '@logto/app-insights/react'; import { withAppInsights } from '@logto/app-insights/react';
import { ServiceConnector } from '@logto/connector-kit';
import { ConnectorType } from '@logto/schemas'; import { ConnectorType } from '@logto/schemas';
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas'; import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -11,6 +12,7 @@ import Delete from '@/assets/icons/delete.svg';
import More from '@/assets/icons/more.svg'; import More from '@/assets/icons/more.svg';
import Reset from '@/assets/icons/reset.svg'; import Reset from '@/assets/icons/reset.svg';
import ConnectorLogo from '@/components/ConnectorLogo'; import ConnectorLogo from '@/components/ConnectorLogo';
import CreateConnectorForm from '@/components/CreateConnectorForm';
import DeleteConnectorConfirmModal from '@/components/DeleteConnectorConfirmModal'; import DeleteConnectorConfirmModal from '@/components/DeleteConnectorConfirmModal';
import DetailsPage from '@/components/DetailsPage'; import DetailsPage from '@/components/DetailsPage';
import Drawer from '@/components/Drawer'; import Drawer from '@/components/Drawer';
@ -26,10 +28,9 @@ import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import Tag from '@/ds-components/Tag'; import Tag from '@/ds-components/Tag';
import type { RequestError } from '@/hooks/use-api'; import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import useConnectorApi from '@/hooks/use-connector-api';
import useConnectorInUse from '@/hooks/use-connector-in-use'; import useConnectorInUse from '@/hooks/use-connector-in-use';
import CreateForm from '../Connectors/CreateForm';
import ConnectorContent from './ConnectorContent'; import ConnectorContent from './ConnectorContent';
import ConnectorTabs from './ConnectorTabs'; import ConnectorTabs from './ConnectorTabs';
import ConnectorTypeName from './ConnectorTypeName'; import ConnectorTypeName from './ConnectorTypeName';
@ -43,6 +44,7 @@ const getConnectorsPathname = (isSocial: boolean) =>
function ConnectorDetails() { function ConnectorDetails() {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { connectorId } = useParams(); const { connectorId } = useParams();
const { createConnector } = useConnectorApi();
const { mutate: mutateGlobal } = useSWRConfig(); const { mutate: mutateGlobal } = useSWRConfig();
const [isDeleted, setIsDeleted] = useState(false); const [isDeleted, setIsDeleted] = useState(false);
const [isReadMeOpen, setIsReadMeOpen] = useState(false); const [isReadMeOpen, setIsReadMeOpen] = useState(false);
@ -199,13 +201,25 @@ function ConnectorDetails() {
{t('general.delete')} {t('general.delete')}
</ActionMenuItem> </ActionMenuItem>
</ActionMenu> </ActionMenu>
<CreateForm <CreateConnectorForm
isOpen={isSetupOpen} isOpen={isSetupOpen}
type={data.type} type={data.type}
onClose={(connectorId?: string) => { onClose={async (connectorId?: string) => {
setIsSetupOpen(false); setIsSetupOpen(false);
if (connectorId) { if (connectorId) {
/**
* Note:
* The "Email Service Connector" is a built-in connector that can be directly created without the need for setup in the guide.
*/
if (connectorId === ServiceConnector.Email) {
const created = await createConnector({ connectorId });
navigate(`/connectors/${ConnectorsTabs.Passwordless}/${created.id}`, {
replace: true,
});
return;
}
navigate(`${getConnectorsPathname(isSocial)}/guide/${connectorId}`); navigate(`${getConnectorsPathname(isSocial)}/guide/${connectorId}`);
} }
}} }}

View file

@ -1,6 +1,6 @@
import { isLanguageTag } from '@logto/language-kit'; import { isLanguageTag } from '@logto/language-kit';
import { ConnectorType } from '@logto/schemas'; import { ConnectorType } from '@logto/schemas';
import type { ConnectorFactoryResponse, RequestErrorBody, ConnectorResponse } from '@logto/schemas'; import type { ConnectorFactoryResponse, RequestErrorBody } from '@logto/schemas';
import { generateStandardId } from '@logto/shared/universal'; import { generateStandardId } from '@logto/shared/universal';
import { conditional } from '@silverhand/essentials'; import { conditional } from '@silverhand/essentials';
import i18next from 'i18next'; import i18next from 'i18next';
@ -23,8 +23,7 @@ import CardTitle from '@/ds-components/CardTitle';
import DangerousRaw from '@/ds-components/DangerousRaw'; import DangerousRaw from '@/ds-components/DangerousRaw';
import IconButton from '@/ds-components/IconButton'; import IconButton from '@/ds-components/IconButton';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import useApi from '@/hooks/use-api'; import useConnectorApi from '@/hooks/use-connector-api';
import useConfigs from '@/hooks/use-configs';
import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config-parser'; import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config-parser';
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';
@ -44,10 +43,9 @@ type Props = {
}; };
function Guide({ connector, onClose }: Props) { function Guide({ connector, onClose }: Props) {
const api = useApi({ hideErrorToast: true }); const { createConnector } = useConnectorApi({ hideErrorToast: true });
const navigate = useNavigate(); const navigate = useNavigate();
const callbackConnectorId = useRef(generateStandardId()); const callbackConnectorId = useRef(generateStandardId());
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, isStandard } = connector ?? {}; const { type: connectorType, formItems, isStandard } = connector ?? {};
@ -126,13 +124,7 @@ function Guide({ connector, onClose }: Props) {
: basePayload; : basePayload;
try { try {
const createdConnector = await api const createdConnector = await createConnector(payload);
.post('api/connectors', {
json: payload,
})
.json<ConnectorResponse>();
await updateConfigs({ passwordlessConfigured: true });
onClose(); onClose();
toast.success(t('general.saved')); toast.success(t('general.saved'));

View file

@ -1,4 +1,5 @@
import { withAppInsights } from '@logto/app-insights/react'; import { withAppInsights } from '@logto/app-insights/react';
import { ServiceConnector } from '@logto/connector-kit';
import { ConnectorType } from '@logto/schemas'; import { ConnectorType } from '@logto/schemas';
import type { ConnectorFactoryResponse } from '@logto/schemas'; import type { ConnectorFactoryResponse } from '@logto/schemas';
import { conditional } from '@silverhand/essentials'; import { conditional } from '@silverhand/essentials';
@ -10,6 +11,7 @@ import useSWR from 'swr';
import Plus from '@/assets/icons/plus.svg'; import Plus from '@/assets/icons/plus.svg';
import SocialConnectorEmptyDark from '@/assets/images/social-connector-empty-dark.svg'; import SocialConnectorEmptyDark from '@/assets/images/social-connector-empty-dark.svg';
import SocialConnectorEmpty from '@/assets/images/social-connector-empty.svg'; import SocialConnectorEmpty from '@/assets/images/social-connector-empty.svg';
import CreateConnectorForm from '@/components/CreateConnectorForm';
import ListPage from '@/components/ListPage'; import ListPage from '@/components/ListPage';
import { defaultEmailConnectorGroup, defaultSmsConnectorGroup } from '@/consts'; import { defaultEmailConnectorGroup, defaultSmsConnectorGroup } from '@/consts';
import { ConnectorsTabs } from '@/consts/page-tabs'; import { ConnectorsTabs } from '@/consts/page-tabs';
@ -17,6 +19,7 @@ import Button from '@/ds-components/Button';
import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import TablePlaceholder from '@/ds-components/Table/TablePlaceholder'; import TablePlaceholder from '@/ds-components/Table/TablePlaceholder';
import type { RequestError } from '@/hooks/use-api'; import type { RequestError } from '@/hooks/use-api';
import useConnectorApi from '@/hooks/use-connector-api';
import useConnectorGroups from '@/hooks/use-connector-groups'; import useConnectorGroups from '@/hooks/use-connector-groups';
import useDocumentationUrl from '@/hooks/use-documentation-url'; import useDocumentationUrl from '@/hooks/use-documentation-url';
import DemoConnectorNotice from '@/onboarding/components/DemoConnectorNotice'; import DemoConnectorNotice from '@/onboarding/components/DemoConnectorNotice';
@ -26,7 +29,6 @@ import ConnectorName from './ConnectorName';
import ConnectorStatus from './ConnectorStatus'; import ConnectorStatus from './ConnectorStatus';
import ConnectorStatusField from './ConnectorStatusField'; import ConnectorStatusField from './ConnectorStatusField';
import ConnectorTypeColumn from './ConnectorTypeColumn'; import ConnectorTypeColumn from './ConnectorTypeColumn';
import CreateForm from './CreateForm';
import Guide from './Guide'; import Guide from './Guide';
import SignInExperienceSetupNotice from './SignInExperienceSetupNotice'; import SignInExperienceSetupNotice from './SignInExperienceSetupNotice';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
@ -63,7 +65,7 @@ function Connectors() {
const isSocial = tab === ConnectorsTabs.Social; const isSocial = tab === ConnectorsTabs.Social;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl(); const { getDocumentationUrl } = useDocumentationUrl();
const { createConnector } = useConnectorApi();
const { data, error, mutate } = useConnectorGroups(); const { data, error, mutate } = useConnectorGroups();
const { data: factories, error: factoriesError } = useSWR< const { data: factories, error: factoriesError } = useSWR<
ConnectorFactoryResponse[], ConnectorFactoryResponse[],
@ -201,11 +203,23 @@ function Connectors() {
}} }}
widgets={ widgets={
<> <>
<CreateForm <CreateConnectorForm
isOpen={Boolean(createConnectorType)} isOpen={Boolean(createConnectorType)}
type={createConnectorType} type={createConnectorType}
onClose={(id) => { onClose={async (id) => {
if (createConnectorType && id) { if (createConnectorType && id) {
/**
* Note:
* The "Email Service Connector" is a built-in connector that can be directly created without the need for setup in the guide.
*/
if (id === ServiceConnector.Email) {
const created = await createConnector({ connectorId: id });
navigate(`/connectors/${ConnectorsTabs.Passwordless}/${created.id}`, {
replace: true,
});
return;
}
navigate(buildGuidePathname(createConnectorType, id), { replace: true }); navigate(buildGuidePathname(createConnectorType, id), { replace: true });
return; return;