diff --git a/packages/console/src/pages/Connectors/CreateForm/PlatformSelector/index.module.scss b/packages/console/src/components/CreateConnectorForm/PlatformSelector/index.module.scss similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/PlatformSelector/index.module.scss rename to packages/console/src/components/CreateConnectorForm/PlatformSelector/index.module.scss diff --git a/packages/console/src/pages/Connectors/CreateForm/PlatformSelector/index.tsx b/packages/console/src/components/CreateConnectorForm/PlatformSelector/index.tsx similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/PlatformSelector/index.tsx rename to packages/console/src/components/CreateConnectorForm/PlatformSelector/index.tsx diff --git a/packages/console/src/pages/Connectors/CreateForm/Skeleton/index.module.scss b/packages/console/src/components/CreateConnectorForm/Skeleton/index.module.scss similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/Skeleton/index.module.scss rename to packages/console/src/components/CreateConnectorForm/Skeleton/index.module.scss diff --git a/packages/console/src/pages/Connectors/CreateForm/Skeleton/index.tsx b/packages/console/src/components/CreateConnectorForm/Skeleton/index.tsx similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/Skeleton/index.tsx rename to packages/console/src/components/CreateConnectorForm/Skeleton/index.tsx diff --git a/packages/console/src/pages/Connectors/CreateForm/constants.ts b/packages/console/src/components/CreateConnectorForm/constants.ts similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/constants.ts rename to packages/console/src/components/CreateConnectorForm/constants.ts diff --git a/packages/console/src/pages/Connectors/CreateForm/index.module.scss b/packages/console/src/components/CreateConnectorForm/index.module.scss similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/index.module.scss rename to packages/console/src/components/CreateConnectorForm/index.module.scss diff --git a/packages/console/src/pages/Connectors/CreateForm/index.tsx b/packages/console/src/components/CreateConnectorForm/index.tsx similarity index 96% rename from packages/console/src/pages/Connectors/CreateForm/index.tsx rename to packages/console/src/components/CreateConnectorForm/index.tsx index 8b7926e59..55d3aa3bc 100644 --- a/packages/console/src/pages/Connectors/CreateForm/index.tsx +++ b/packages/console/src/components/CreateConnectorForm/index.tsx @@ -13,7 +13,7 @@ import RadioGroup, { Radio } from '@/ds-components/RadioGroup'; import type { RequestError } from '@/hooks/use-api'; import * as modalStyles from '@/scss/modal.module.scss'; -import { getConnectorGroups } from '../utils'; +import { getConnectorGroups } from '../../pages/Connectors/utils'; import PlatformSelector from './PlatformSelector'; import Skeleton from './Skeleton'; @@ -26,7 +26,7 @@ type Props = { onClose?: (connectorId?: string) => void; }; -function CreateForm({ onClose, isOpen: isFormOpen, type }: Props) { +function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) { const { data: existingConnectors, error: connectorsError } = useSWR< ConnectorResponse[], RequestError @@ -186,4 +186,4 @@ function CreateForm({ onClose, isOpen: isFormOpen, type }: Props) { ); } -export default CreateForm; +export default CreateConnectorForm; diff --git a/packages/console/src/pages/Connectors/CreateForm/utils.ts b/packages/console/src/components/CreateConnectorForm/utils.ts similarity index 100% rename from packages/console/src/pages/Connectors/CreateForm/utils.ts rename to packages/console/src/components/CreateConnectorForm/utils.ts diff --git a/packages/console/src/hooks/use-api.ts b/packages/console/src/hooks/use-api.ts index d86ea96bc..6f1dc814c 100644 --- a/packages/console/src/hooks/use-api.ts +++ b/packages/console/src/hooks/use-api.ts @@ -18,7 +18,7 @@ export class RequestError extends Error { } } -type StaticApiProps = { +export type StaticApiProps = { prefixUrl?: URL; hideErrorToast?: boolean; resourceIndicator?: string; diff --git a/packages/console/src/hooks/use-connector-api.ts b/packages/console/src/hooks/use-connector-api.ts new file mode 100644 index 000000000..3904ec6b5 --- /dev/null +++ b/packages/console/src/hooks/use-connector-api.ts @@ -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 = {}) => { + const api = useApi(props); + const { updateConfigs } = useConfigs(); + + const createConnector = async (payload: unknown) => { + const connector = await api + .post('api/connectors', { + json: payload, + }) + .json(); + await updateConfigs({ passwordlessConfigured: true }); + return connector; + }; + + return { + createConnector, + }; +}; + +export default useConnectorApi; diff --git a/packages/console/src/pages/ConnectorDetails/index.tsx b/packages/console/src/pages/ConnectorDetails/index.tsx index 9c3e6425b..731fb6453 100644 --- a/packages/console/src/pages/ConnectorDetails/index.tsx +++ b/packages/console/src/pages/ConnectorDetails/index.tsx @@ -1,4 +1,5 @@ import { withAppInsights } from '@logto/app-insights/react'; +import { ServiceConnector } from '@logto/connector-kit'; import { ConnectorType } from '@logto/schemas'; import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas'; import { useEffect, useState } from 'react'; @@ -11,6 +12,7 @@ import Delete from '@/assets/icons/delete.svg'; import More from '@/assets/icons/more.svg'; import Reset from '@/assets/icons/reset.svg'; import ConnectorLogo from '@/components/ConnectorLogo'; +import CreateConnectorForm from '@/components/CreateConnectorForm'; import DeleteConnectorConfirmModal from '@/components/DeleteConnectorConfirmModal'; import DetailsPage from '@/components/DetailsPage'; import Drawer from '@/components/Drawer'; @@ -26,10 +28,9 @@ import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import Tag from '@/ds-components/Tag'; import type { RequestError } 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 CreateForm from '../Connectors/CreateForm'; - import ConnectorContent from './ConnectorContent'; import ConnectorTabs from './ConnectorTabs'; import ConnectorTypeName from './ConnectorTypeName'; @@ -43,6 +44,7 @@ const getConnectorsPathname = (isSocial: boolean) => function ConnectorDetails() { const { pathname } = useLocation(); const { connectorId } = useParams(); + const { createConnector } = useConnectorApi(); const { mutate: mutateGlobal } = useSWRConfig(); const [isDeleted, setIsDeleted] = useState(false); const [isReadMeOpen, setIsReadMeOpen] = useState(false); @@ -199,13 +201,25 @@ function ConnectorDetails() { {t('general.delete')} - { + onClose={async (connectorId?: string) => { setIsSetupOpen(false); 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}`); } }} diff --git a/packages/console/src/pages/Connectors/Guide/index.tsx b/packages/console/src/pages/Connectors/Guide/index.tsx index aa218a8d7..44e01a7b7 100644 --- a/packages/console/src/pages/Connectors/Guide/index.tsx +++ b/packages/console/src/pages/Connectors/Guide/index.tsx @@ -1,6 +1,6 @@ import { isLanguageTag } from '@logto/language-kit'; 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 { conditional } from '@silverhand/essentials'; import i18next from 'i18next'; @@ -23,8 +23,7 @@ import CardTitle from '@/ds-components/CardTitle'; import DangerousRaw from '@/ds-components/DangerousRaw'; import IconButton from '@/ds-components/IconButton'; import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; -import useApi from '@/hooks/use-api'; -import useConfigs from '@/hooks/use-configs'; +import useConnectorApi from '@/hooks/use-connector-api'; import { useConnectorFormConfigParser } from '@/hooks/use-connector-form-config-parser'; import * as modalStyles from '@/scss/modal.module.scss'; import type { ConnectorFormType } from '@/types/connector'; @@ -44,10 +43,9 @@ type Props = { }; function Guide({ connector, onClose }: Props) { - const api = useApi({ hideErrorToast: true }); + const { createConnector } = useConnectorApi({ hideErrorToast: true }); const navigate = useNavigate(); const callbackConnectorId = useRef(generateStandardId()); - const { updateConfigs } = useConfigs(); const [conflictConnectorName, setConflictConnectorName] = useState>(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { type: connectorType, formItems, isStandard } = connector ?? {}; @@ -126,13 +124,7 @@ function Guide({ connector, onClose }: Props) { : basePayload; try { - const createdConnector = await api - .post('api/connectors', { - json: payload, - }) - .json(); - - await updateConfigs({ passwordlessConfigured: true }); + const createdConnector = await createConnector(payload); onClose(); toast.success(t('general.saved')); diff --git a/packages/console/src/pages/Connectors/index.tsx b/packages/console/src/pages/Connectors/index.tsx index cc51fa97f..c23e34a23 100644 --- a/packages/console/src/pages/Connectors/index.tsx +++ b/packages/console/src/pages/Connectors/index.tsx @@ -1,4 +1,5 @@ import { withAppInsights } from '@logto/app-insights/react'; +import { ServiceConnector } from '@logto/connector-kit'; import { ConnectorType } from '@logto/schemas'; import type { ConnectorFactoryResponse } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; @@ -10,6 +11,7 @@ import useSWR from 'swr'; import Plus from '@/assets/icons/plus.svg'; import SocialConnectorEmptyDark from '@/assets/images/social-connector-empty-dark.svg'; import SocialConnectorEmpty from '@/assets/images/social-connector-empty.svg'; +import CreateConnectorForm from '@/components/CreateConnectorForm'; import ListPage from '@/components/ListPage'; import { defaultEmailConnectorGroup, defaultSmsConnectorGroup } from '@/consts'; import { ConnectorsTabs } from '@/consts/page-tabs'; @@ -17,6 +19,7 @@ import Button from '@/ds-components/Button'; import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import TablePlaceholder from '@/ds-components/Table/TablePlaceholder'; import type { RequestError } from '@/hooks/use-api'; +import useConnectorApi from '@/hooks/use-connector-api'; import useConnectorGroups from '@/hooks/use-connector-groups'; import useDocumentationUrl from '@/hooks/use-documentation-url'; import DemoConnectorNotice from '@/onboarding/components/DemoConnectorNotice'; @@ -26,7 +29,6 @@ import ConnectorName from './ConnectorName'; import ConnectorStatus from './ConnectorStatus'; import ConnectorStatusField from './ConnectorStatusField'; import ConnectorTypeColumn from './ConnectorTypeColumn'; -import CreateForm from './CreateForm'; import Guide from './Guide'; import SignInExperienceSetupNotice from './SignInExperienceSetupNotice'; import * as styles from './index.module.scss'; @@ -63,7 +65,7 @@ function Connectors() { const isSocial = tab === ConnectorsTabs.Social; const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { getDocumentationUrl } = useDocumentationUrl(); - + const { createConnector } = useConnectorApi(); const { data, error, mutate } = useConnectorGroups(); const { data: factories, error: factoriesError } = useSWR< ConnectorFactoryResponse[], @@ -201,11 +203,23 @@ function Connectors() { }} widgets={ <> - { + onClose={async (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 }); return;