mirror of
https://github.com/logto-io/logto.git
synced 2025-04-14 23:11:31 -05:00
refactor: use connector factories and remove enabled
This commit is contained in:
parent
7ba40a7782
commit
c3a361c437
22 changed files with 195 additions and 187 deletions
packages
console/src
hooks
pages
ConnectorDetails
Connectors
SignInExperience
components/Preview
tabs/SignUpAndSignIn/components/SocialConnectorEditBox
UserDetails/components
types
core/src
schemas/src/types
|
@ -1,10 +1,9 @@
|
|||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import type { ConnectorGroup } from '@/types/connector';
|
||||
import { getConnectorGroups } from '@/pages/Connectors/utils';
|
||||
|
||||
// Group connectors by target
|
||||
const useConnectorGroups = () => {
|
||||
|
@ -15,42 +14,7 @@ const useConnectorGroups = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
return data.reduce<ConnectorGroup[]>((previous, item) => {
|
||||
const groupIndex = previous.findIndex(
|
||||
// Only group social connectors
|
||||
({ target }) => target === item.target && item.type === ConnectorType.Social
|
||||
);
|
||||
|
||||
if (groupIndex === -1) {
|
||||
return [
|
||||
...previous,
|
||||
{
|
||||
id: item.id, // Take first connector's id as groupId, only used for indexing.
|
||||
name: item.name,
|
||||
logo: item.logo,
|
||||
logoDark: item.logoDark,
|
||||
description: item.description,
|
||||
target: item.target,
|
||||
type: item.type,
|
||||
enabled: item.enabled,
|
||||
connectors: [item],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return previous.map((group, index) => {
|
||||
if (index !== groupIndex) {
|
||||
return group;
|
||||
}
|
||||
|
||||
return {
|
||||
...group,
|
||||
connectors: [...group.connectors, item],
|
||||
// Group is enabled when any of its connectors is enabled.
|
||||
enabled: group.enabled || item.enabled,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
return getConnectorGroups(data);
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
|
|
|
@ -8,7 +8,7 @@ const useEnabledConnectorTypes = () => {
|
|||
const { data: connectors } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
|
||||
|
||||
const enabledConnectorTypes = useMemo(
|
||||
() => connectors?.filter(({ enabled }) => enabled).map(({ type }) => type) ?? [],
|
||||
() => connectors?.map(({ type }) => type) ?? [],
|
||||
[connectors]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorPlatform } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
@ -18,15 +17,11 @@ type Props = {
|
|||
|
||||
const ConnectorTabs = ({ target, connectorId }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { data } = useSWR<ConnectorResponse[]>(`/api/connectors?target=${target}`);
|
||||
const { data: connectors } = useSWR<ConnectorResponse[]>(`/api/connectors?target=${target}`);
|
||||
|
||||
const connectors = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.filter(({ enabled }) => enabled);
|
||||
}, [data]);
|
||||
if (!connectors) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (connectors.length === 0) {
|
||||
return null;
|
||||
|
|
|
@ -67,11 +67,7 @@ const ConnectorDetails = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await api
|
||||
.patch(`/api/connectors/${connectorId}/enabled`, {
|
||||
json: { enabled: false },
|
||||
})
|
||||
.json<ConnectorResponse>();
|
||||
await api.delete(`/api/connectors/${connectorId}`).json<ConnectorResponse>();
|
||||
|
||||
setIsDeleted(true);
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ type Props = {
|
|||
|
||||
const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const enabledConnectors = connectors.filter(({ enabled }) => enabled);
|
||||
const connector = enabledConnectors[0];
|
||||
const connector = connectors[0];
|
||||
const theme = useTheme();
|
||||
|
||||
if (!connector) {
|
||||
|
@ -59,7 +58,7 @@ const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
|
|||
{type !== ConnectorType.Social && connector.id}
|
||||
{type === ConnectorType.Social && connectors.length > 1 && (
|
||||
<div className={styles.platforms}>
|
||||
{enabledConnectors.map(
|
||||
{connectors.map(
|
||||
({ id, platform }) =>
|
||||
platform && (
|
||||
<div key={id} className={styles.platform}>
|
||||
|
|
|
@ -19,17 +19,17 @@ type Props = {
|
|||
|
||||
const ConnectorRow = ({ type, connectors, onClickSetup }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const firstEnabledConnector = connectors.find(({ enabled }) => enabled);
|
||||
const inUse = useConnectorInUse(type, firstEnabledConnector?.target);
|
||||
const firstConnector = connectors[0];
|
||||
const inUse = useConnectorInUse(type, firstConnector?.target);
|
||||
const navigate = useNavigate();
|
||||
const showSetupButton = type !== ConnectorType.Social && !firstEnabledConnector;
|
||||
const showSetupButton = type !== ConnectorType.Social && !firstConnector;
|
||||
|
||||
const handleClickRow = () => {
|
||||
if (showSetupButton || !firstEnabledConnector) {
|
||||
if (showSetupButton || !firstConnector) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`/connectors/${firstEnabledConnector.id}`);
|
||||
navigate(`/connectors/${firstConnector.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { ConnectorFactoryResponse } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
|
@ -8,7 +9,7 @@ import type { ConnectorGroup } from '@/types/connector';
|
|||
import * as styles from './PlatformSelector.module.scss';
|
||||
|
||||
type Props = {
|
||||
connectorGroup: ConnectorGroup;
|
||||
connectorGroup: ConnectorGroup<ConnectorFactoryResponse & { added: boolean }>;
|
||||
connectorId?: string;
|
||||
onConnectorIdChange: (value: string) => void;
|
||||
};
|
||||
|
@ -28,13 +29,13 @@ const PlatformSelector = ({ connectorGroup, connectorId, onConnectorIdChange }:
|
|||
</div>
|
||||
<RadioGroup type="plain" name="connector" value={connectorId} onChange={onConnectorIdChange}>
|
||||
{connectorGroup.connectors.map(
|
||||
({ platform, id, enabled }) =>
|
||||
({ platform, id, added }) =>
|
||||
platform && (
|
||||
<Radio
|
||||
key={id}
|
||||
value={id}
|
||||
title={connectorPlatformLabel[platform]}
|
||||
isDisabled={enabled}
|
||||
isDisabled={added}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import ModalLayout from '@/components/ModalLayout';
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import { getConnectorGroups } from '../../utils';
|
||||
import Guide from '../Guide';
|
||||
import PlatformSelector from './PlatformSelector';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -21,25 +24,44 @@ type Props = {
|
|||
};
|
||||
|
||||
const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
||||
const { data: allGroups, connectors, error } = useConnectorGroups();
|
||||
const isLoading = !allGroups && !connectors && !error;
|
||||
const { data: connectors, error: connectorsError } = useSWR<ConnectorResponse[], RequestError>(
|
||||
'/api/connectors'
|
||||
);
|
||||
const { data: factories, error: factoriesError } = useSWR<
|
||||
ConnectorFactoryResponse[],
|
||||
RequestError
|
||||
>('/api/connector-factories');
|
||||
const isLoading = !factories && !connectors && !connectorsError && !factoriesError;
|
||||
const [activeGroupId, setActiveGroupId] = useState<string>();
|
||||
const [activeConnectorId, setActiveConnectorId] = useState<string>();
|
||||
const [activeFactoryId, setActiveFactoryId] = useState<string>();
|
||||
const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false);
|
||||
|
||||
const groups = useMemo(
|
||||
() => allGroups?.filter((group) => group.type === type),
|
||||
[allGroups, type]
|
||||
);
|
||||
const groups = useMemo(() => {
|
||||
if (!factories || !connectors) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allGroups = getConnectorGroups<ConnectorFactoryResponse>(
|
||||
factories.filter(({ type: factoryType }) => factoryType === type)
|
||||
);
|
||||
|
||||
return allGroups.map((group) => ({
|
||||
...group,
|
||||
connectors: group.connectors.map((connector) => ({
|
||||
...connector,
|
||||
added: connectors.some(({ connectorId }) => connector.id === connectorId),
|
||||
})),
|
||||
}));
|
||||
}, [factories, type, connectors]);
|
||||
|
||||
const activeGroup = useMemo(
|
||||
() => groups?.find(({ id }) => id === activeGroupId),
|
||||
() => groups.find(({ id }) => id === activeGroupId),
|
||||
[activeGroupId, groups]
|
||||
);
|
||||
|
||||
const activeConnector = useMemo(
|
||||
() => connectors?.find(({ id }) => id === activeConnectorId),
|
||||
[activeConnectorId, connectors]
|
||||
const activeFactory = useMemo(
|
||||
() => factories?.find(({ id }) => id === activeFactoryId),
|
||||
[activeFactoryId, factories]
|
||||
);
|
||||
|
||||
const cardTitle = useMemo(() => {
|
||||
|
@ -55,10 +77,6 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
}, [type]);
|
||||
|
||||
const handleGroupChange = (groupId: string) => {
|
||||
if (!groups) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveGroupId(groupId);
|
||||
|
||||
const group = groups.find(({ id }) => id === groupId);
|
||||
|
@ -67,20 +85,20 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const firstAvailableConnector = group.connectors.find(({ enabled }) => !enabled);
|
||||
const firstAvailableConnector = group.connectors.find(({ added }) => !added);
|
||||
|
||||
setActiveConnectorId(firstAvailableConnector?.id);
|
||||
setActiveFactoryId(firstAvailableConnector?.id);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsGetStartedModalOpen(false);
|
||||
onClose?.(activeConnectorId);
|
||||
onClose?.(activeFactoryId);
|
||||
setActiveGroupId(undefined);
|
||||
setActiveConnectorId(undefined);
|
||||
setActiveFactoryId(undefined);
|
||||
};
|
||||
|
||||
const modalSize = useMemo(() => {
|
||||
if (!groups || groups.length <= 2) {
|
||||
if (groups.length <= 2) {
|
||||
return 'medium';
|
||||
}
|
||||
|
||||
|
@ -103,7 +121,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
<Button
|
||||
title="general.next"
|
||||
type="primary"
|
||||
disabled={!activeConnectorId}
|
||||
disabled={!activeFactoryId}
|
||||
onClick={() => {
|
||||
setIsGetStartedModalOpen(true);
|
||||
}}
|
||||
|
@ -114,53 +132,48 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
onClose={onClose}
|
||||
>
|
||||
{isLoading && 'Loading...'}
|
||||
{error?.message}
|
||||
{groups && (
|
||||
<RadioGroup
|
||||
name="group"
|
||||
value={activeGroupId}
|
||||
type="card"
|
||||
className={classNames(styles.connectorGroup, styles[modalSize])}
|
||||
onChange={handleGroupChange}
|
||||
>
|
||||
{groups.map(({ id, name, logo, description, connectors }) => {
|
||||
const isDisabled = connectors.every(({ enabled }) => enabled);
|
||||
{factoriesError?.message ?? connectorsError?.message}
|
||||
<RadioGroup
|
||||
name="group"
|
||||
value={activeGroupId}
|
||||
type="card"
|
||||
className={classNames(styles.connectorGroup, styles[modalSize])}
|
||||
onChange={handleGroupChange}
|
||||
>
|
||||
{groups.map(({ id, name, logo, description, connectors }) => {
|
||||
const isDisabled = connectors.every(({ added }) => added);
|
||||
|
||||
return (
|
||||
<Radio key={id} value={id} isDisabled={isDisabled} disabledLabel="general.added">
|
||||
<div className={styles.connector}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
return (
|
||||
<Radio key={id} value={id} isDisabled={isDisabled} disabledLabel="general.added">
|
||||
<div className={styles.connector}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div
|
||||
className={classNames(styles.name, isDisabled && styles.nameWithRightPadding)}
|
||||
>
|
||||
<UnnamedTrans resource={name} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.name,
|
||||
isDisabled && styles.nameWithRightPadding
|
||||
)}
|
||||
>
|
||||
<UnnamedTrans resource={name} />
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<UnnamedTrans resource={description} />
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<UnnamedTrans resource={description} />
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
)}
|
||||
</div>
|
||||
</Radio>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
{activeGroup && (
|
||||
<PlatformSelector
|
||||
connectorGroup={activeGroup}
|
||||
connectorId={activeConnectorId}
|
||||
onConnectorIdChange={setActiveConnectorId}
|
||||
connectorId={activeFactoryId}
|
||||
onConnectorIdChange={setActiveFactoryId}
|
||||
/>
|
||||
)}
|
||||
{activeConnector && (
|
||||
{activeFactory && (
|
||||
<Modal isOpen={isGetStartedModalOpen} className={modalStyles.fullScreen}>
|
||||
<Guide connector={activeConnector} onClose={closeModal} />
|
||||
<Guide connector={activeFactory} onClose={closeModal} />
|
||||
</Modal>
|
||||
)}
|
||||
</ModalLayout>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { isLanguageTag } from '@logto/language-kit';
|
||||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
|
@ -23,7 +23,7 @@ import { safeParseJson } from '@/utilities/json';
|
|||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
connector: ConnectorResponse;
|
||||
connector: ConnectorFactoryResponse;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
|
@ -57,11 +57,10 @@ const Guide = ({ connector, onClose }: Props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const { id: connectorId } = connector;
|
||||
|
||||
await api
|
||||
.patch(`/api/connectors/${connectorId}`, { json: { config: result.data } })
|
||||
.json<ConnectorResponse>();
|
||||
await api
|
||||
.patch(`/api/connectors/${connectorId}/enabled`, { json: { enabled: true } })
|
||||
.post('/api/connectors', { json: { config: result.data, connectorId } })
|
||||
.json<ConnectorResponse>();
|
||||
|
||||
await updateSettings({
|
||||
|
|
|
@ -16,13 +16,7 @@ const SignInExperienceSetupNotice = () => {
|
|||
update,
|
||||
} = useUserPreferences();
|
||||
|
||||
if (!connectors || connectorSieNoticeConfirmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasSetupConnector = connectors.some(({ enabled }) => enabled);
|
||||
|
||||
if (!hasSetupConnector) {
|
||||
if (!connectors || connectors.length > 0 || connectorSieNoticeConfirmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,17 +35,13 @@ const Connectors = () => {
|
|||
const isLightMode = theme === AppearanceMode.LightMode;
|
||||
|
||||
const emailConnector = useMemo(() => {
|
||||
const emailConnectorGroup = data?.find(
|
||||
({ enabled, type }) => enabled && type === ConnectorType.Email
|
||||
);
|
||||
const emailConnectorGroup = data?.find(({ type }) => type === ConnectorType.Email);
|
||||
|
||||
return emailConnectorGroup?.connectors[0];
|
||||
}, [data]);
|
||||
|
||||
const smsConnector = useMemo(() => {
|
||||
const smsConnectorGroup = data?.find(
|
||||
({ enabled, type }) => enabled && type === ConnectorType.Sms
|
||||
);
|
||||
const smsConnectorGroup = data?.find(({ type }) => type === ConnectorType.Sms);
|
||||
|
||||
return smsConnectorGroup?.connectors[0];
|
||||
}, [data]);
|
||||
|
@ -55,7 +51,7 @@ const Connectors = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
return data?.filter(({ enabled, type }) => enabled && type === ConnectorType.Social);
|
||||
return data?.filter(({ type }) => type === ConnectorType.Social);
|
||||
}, [data, isSocial]);
|
||||
|
||||
return (
|
||||
|
|
44
packages/console/src/pages/Connectors/utils/index.ts
Normal file
44
packages/console/src/pages/Connectors/utils/index.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
|
||||
import type { ConnectorGroup } from '@/types/connector';
|
||||
|
||||
export const getConnectorGroups = <
|
||||
T extends ConnectorResponse | ConnectorFactoryResponse = ConnectorResponse
|
||||
>(
|
||||
connectors: T[]
|
||||
) => {
|
||||
return connectors.reduce<Array<ConnectorGroup<T>>>((previous, item) => {
|
||||
const groupIndex = previous.findIndex(
|
||||
// Only group social connectors
|
||||
({ target }) => target === item.target && item.type === ConnectorType.Social
|
||||
);
|
||||
|
||||
if (groupIndex === -1) {
|
||||
return [
|
||||
...previous,
|
||||
{
|
||||
id: item.id, // Take first connector's id as groupId, only used for indexing.
|
||||
name: item.name,
|
||||
logo: item.logo,
|
||||
logoDark: item.logoDark,
|
||||
description: item.description,
|
||||
target: item.target,
|
||||
type: item.type,
|
||||
connectors: [item],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return previous.map((group, index) => {
|
||||
if (index !== groupIndex) {
|
||||
return group;
|
||||
}
|
||||
|
||||
return {
|
||||
...group,
|
||||
connectors: [...group.connectors, item],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
};
|
|
@ -88,18 +88,14 @@ const Preview = ({ signInExperience, className }: Props) => {
|
|||
>(
|
||||
(previous, connectorTarget) => [
|
||||
...previous,
|
||||
...allConnectors.filter(({ target, enabled }) => target === connectorTarget && enabled),
|
||||
...allConnectors.filter(({ target }) => target === connectorTarget),
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const hasEmailConnector = allConnectors.some(
|
||||
({ type, enabled }) => enabled && type === ConnectorType.Email
|
||||
);
|
||||
const hasEmailConnector = allConnectors.some(({ type }) => type === ConnectorType.Email);
|
||||
|
||||
const hasSmsConnector = allConnectors.some(
|
||||
({ type, enabled }) => enabled && type === ConnectorType.Sms
|
||||
);
|
||||
const hasSmsConnector = allConnectors.some(({ type }) => type === ConnectorType.Sms);
|
||||
|
||||
return {
|
||||
signInExperience: {
|
||||
|
|
|
@ -54,13 +54,11 @@ const AddButton = ({ options, onSelected, hasSelectedConnectors }: Props) => {
|
|||
<img src={logo} alt={target} className={styles.logo} />
|
||||
<UnnamedTrans resource={name} className={styles.name} />
|
||||
{connectors.length > 1 &&
|
||||
connectors
|
||||
.filter(({ enabled }) => enabled)
|
||||
.map(({ platform }) => (
|
||||
<div key={platform} className={styles.icon}>
|
||||
{platform && <ConnectorPlatformIcon platform={platform} />}
|
||||
</div>
|
||||
))}
|
||||
connectors.map(({ platform }) => (
|
||||
<div key={platform} className={styles.icon}>
|
||||
{platform && <ConnectorPlatformIcon platform={platform} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DropdownItem>
|
||||
))}
|
||||
|
|
|
@ -20,13 +20,11 @@ const SelectedConnectorItem = ({ data: { logo, target, name, connectors }, onDel
|
|||
<img src={logo} alt={target} className={styles.logo} />
|
||||
<UnnamedTrans resource={name} className={styles.name} />
|
||||
{connectors.length > 1 &&
|
||||
connectors
|
||||
.filter(({ enabled }) => enabled)
|
||||
.map(({ platform }) => (
|
||||
<div key={platform} className={styles.icon}>
|
||||
{platform && <ConnectorPlatformIcon platform={platform} />}
|
||||
</div>
|
||||
))}
|
||||
connectors.map(({ platform }) => (
|
||||
<div key={platform} className={styles.icon}>
|
||||
{platform && <ConnectorPlatformIcon platform={platform} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
|
|
|
@ -54,8 +54,7 @@ const SocialConnectorEditBox = ({ value, onChange }: Props) => {
|
|||
.filter((item): item is ConnectorGroup => Boolean(item));
|
||||
|
||||
const connectorOptions = connectorData.filter(
|
||||
({ target, type, enabled }) =>
|
||||
!value.includes(target) && type === ConnectorType.Social && enabled
|
||||
({ target, type }) => !value.includes(target) && type === ConnectorType.Social
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import type { Identities, ConnectorResponse } from '@logto/schemas';
|
||||
import type { Optional } from '@silverhand/essentials';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
|
||||
import TableError from '@/components/Table/TableError';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
import { getConnectorGroups } from '@/pages/Connectors/utils';
|
||||
|
||||
import * as styles from './UserConnectors.module.scss';
|
||||
|
||||
|
@ -24,8 +27,9 @@ type DisplayConnector = Pick<ConnectorResponse, 'target' | 'logo' | 'name'> & {
|
|||
const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
|
||||
const api = useApi();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { data: connectorGroups, error, mutate } = useConnectorGroups();
|
||||
const { data, error, mutate } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
|
||||
const [deletingConnector, setDeletingConnector] = useState<DisplayConnector>();
|
||||
const connectorGroups = conditional(data && getConnectorGroups(data));
|
||||
const isLoading = !connectorGroups && !error;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
|
||||
export type ConnectorGroup = Pick<
|
||||
export type ConnectorGroup<T = ConnectorResponse> = Pick<
|
||||
ConnectorResponse,
|
||||
'name' | 'logo' | 'logoDark' | 'target' | 'type' | 'description'
|
||||
> & {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
connectors: ConnectorResponse[];
|
||||
connectors: T[];
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ import { findAllConnectors } from '#src/queries/connector.js';
|
|||
import { defaultConnectorMethods } from './consts.js';
|
||||
import { metaUrl } from './meta-url.js';
|
||||
import type { ConnectorFactory, LogtoConnector } from './types.js';
|
||||
import { getConnectorConfig, readUrl, validateConnectorModule } from './utilities/index.js';
|
||||
import { getConnectorConfig, parseMetadata, validateConnectorModule } from './utilities/index.js';
|
||||
|
||||
const currentDirname = path.dirname(fileURLToPath(metaUrl));
|
||||
|
||||
|
@ -52,7 +52,7 @@ export const loadConnectorFactories = async () => {
|
|||
validateConnectorModule(rawConnector);
|
||||
|
||||
return {
|
||||
metadata: rawConnector.metadata,
|
||||
metadata: await parseMetadata(rawConnector.metadata, packagePath),
|
||||
type: rawConnector.type,
|
||||
createConnector,
|
||||
path: packagePath,
|
||||
|
@ -106,22 +106,13 @@ export const getLogtoConnectors = async (): Promise<LogtoConnector[]> => {
|
|||
},
|
||||
});
|
||||
validateConnectorModule(rawConnector);
|
||||
const rawMetadata = await parseMetadata(rawConnector.metadata, packagePath);
|
||||
|
||||
const connector: AllConnector = {
|
||||
...defaultConnectorMethods,
|
||||
...rawConnector,
|
||||
metadata: {
|
||||
...rawConnector.metadata,
|
||||
logo: await readUrl(rawConnector.metadata.logo, packagePath, 'svg'),
|
||||
logoDark:
|
||||
rawConnector.metadata.logoDark &&
|
||||
(await readUrl(rawConnector.metadata.logoDark, packagePath, 'svg')),
|
||||
readme: await readUrl(rawConnector.metadata.readme, packagePath, 'text'),
|
||||
configTemplate: await readUrl(
|
||||
rawConnector.metadata.configTemplate,
|
||||
packagePath,
|
||||
'text'
|
||||
),
|
||||
...rawMetadata,
|
||||
...metadata,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { existsSync } from 'fs';
|
|||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import type { BaseConnector } from '@logto/connector-kit';
|
||||
import type { AllConnector, BaseConnector } from '@logto/connector-kit';
|
||||
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
@ -59,3 +59,13 @@ export const readUrl = async (
|
|||
|
||||
return readFile(path.join(baseUrl, url), 'utf8');
|
||||
};
|
||||
|
||||
export const parseMetadata = async (metadata: AllConnector['metadata'], packagePath: string) => {
|
||||
return {
|
||||
...metadata,
|
||||
logo: await readUrl(metadata.logo, packagePath, 'svg'),
|
||||
logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'svg')),
|
||||
readme: await readUrl(metadata.readme, packagePath, 'text'),
|
||||
configTemplate: await readUrl(metadata.configTemplate, packagePath, 'text'),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
import { emailRegEx, phoneRegEx } from '@logto/core-kit';
|
||||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { arbitraryObjectGuard, Connectors, ConnectorType } from '@logto/schemas';
|
||||
import { buildIdGenerator } from '@logto/shared';
|
||||
import { object, string } from 'zod';
|
||||
|
@ -71,7 +71,13 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
|
|||
|
||||
router.get('/connector-factories', async (ctx, next) => {
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
ctx.body = connectorFactories.map(({ metadata, type }) => ({ type, ...metadata }));
|
||||
const formatedFactories: ConnectorFactoryResponse[] = connectorFactories.map(
|
||||
({ metadata, type }) => ({
|
||||
type,
|
||||
...metadata,
|
||||
})
|
||||
);
|
||||
ctx.body = formatedFactories;
|
||||
|
||||
return next();
|
||||
});
|
||||
|
|
|
@ -8,3 +8,9 @@ export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
|
|||
export type ConnectorResponse = Omit<Connector, 'metadata'> &
|
||||
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
|
||||
ConnectorMetadata;
|
||||
|
||||
export type ConnectorFactoryResponse = Omit<
|
||||
BaseConnector<ConnectorType>,
|
||||
'configGuard' | 'metadata'
|
||||
> &
|
||||
ConnectorMetadata;
|
||||
|
|
Loading…
Add table
Reference in a new issue