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:
parent
44c09baba9
commit
244537ca03
13 changed files with 69 additions and 24 deletions
|
@ -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;
|
|
@ -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;
|
||||||
|
|
25
packages/console/src/hooks/use-connector-api.ts
Normal file
25
packages/console/src/hooks/use-connector-api.ts
Normal 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;
|
|
@ -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}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue