mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(console): group connectors in add modal (#1029)
This commit is contained in:
parent
79ea8b2c68
commit
fa420c9fcb
9 changed files with 132 additions and 26 deletions
packages
console/src
components/RadioGroup
consts
hooks
pages/Connectors/components/CreateForm
types
phrases/src/locales
|
@ -113,5 +113,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--color-disabled);
|
||||
|
||||
.indicator {
|
||||
border-color: var(--color-disabled);
|
||||
|
||||
&::before {
|
||||
background: var(--color-layer-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { I18nKey } from '@logto/phrases';
|
||||
import { AdminConsoleKey, I18nKey } from '@logto/phrases';
|
||||
import { ConnectorPlatform, ConnectorType } from '@logto/schemas';
|
||||
|
||||
import emailConnectorIcon from '@/assets/images/connector-email.svg';
|
||||
|
@ -26,11 +26,11 @@ export const connectorIconPlaceHolder: IconPlaceHolder = Object.freeze({
|
|||
});
|
||||
|
||||
type ConnectorPlatformLabel = {
|
||||
[key in ConnectorPlatform]: I18nKey;
|
||||
[key in ConnectorPlatform]: AdminConsoleKey;
|
||||
};
|
||||
|
||||
export const connectorPlatformLabel: ConnectorPlatformLabel = Object.freeze({
|
||||
[ConnectorPlatform.Native]: 'admin_console.connectors.platform.native',
|
||||
[ConnectorPlatform.Universal]: 'admin_console.connectors.platform.universal',
|
||||
[ConnectorPlatform.Web]: 'admin_console.connectors.platform.web',
|
||||
[ConnectorPlatform.Native]: 'connectors.platform.native',
|
||||
[ConnectorPlatform.Universal]: 'connectors.platform.universal',
|
||||
[ConnectorPlatform.Web]: 'connectors.platform.web',
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ConnectorDTO } from '@logto/schemas';
|
||||
import { ConnectorDTO, ConnectorType } from '@logto/schemas';
|
||||
import { useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
|
@ -15,7 +15,10 @@ const useConnectorGroups = () => {
|
|||
}
|
||||
|
||||
return data.reduce<ConnectorGroup[]>((previous, item) => {
|
||||
const groupIndex = previous.findIndex(({ target }) => target === item.target);
|
||||
const groupIndex = previous.findIndex(
|
||||
// Only group social connectors
|
||||
({ target }) => target === item.target && item.type === ConnectorType.Social
|
||||
);
|
||||
|
||||
if (groupIndex === -1) {
|
||||
return [
|
||||
|
@ -24,6 +27,7 @@ const useConnectorGroups = () => {
|
|||
id: item.id, // Take first connector's id as groupId, only used for indexing.
|
||||
name: item.name,
|
||||
logo: item.logo,
|
||||
description: item.description,
|
||||
target: item.target,
|
||||
type: item.type,
|
||||
enabled: item.enabled,
|
||||
|
@ -50,6 +54,7 @@ const useConnectorGroups = () => {
|
|||
return {
|
||||
...rest,
|
||||
data: groups,
|
||||
connectors: data,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.platforms {
|
||||
margin-top: _.unit(6);
|
||||
|
||||
.title {
|
||||
font: var(--font-subhead-2);
|
||||
margin-bottom: _.unit(3);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import { connectorPlatformLabel } from '@/consts/connectors';
|
||||
import { ConnectorGroup } from '@/types/connector';
|
||||
|
||||
import * as styles from './PlatformSelector.module.scss';
|
||||
|
||||
type Props = {
|
||||
connectorGroup: ConnectorGroup;
|
||||
connectorId?: string;
|
||||
onConnectorIdChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const PlatformSelector = ({ connectorGroup, connectorId, onConnectorIdChange }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
if (connectorGroup.connectors.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.platforms}>
|
||||
<div className={styles.title}>
|
||||
<UnnamedTrans resource={connectorGroup.name} />
|
||||
{t('connectors.add_multi_platform')}
|
||||
</div>
|
||||
<RadioGroup type="plain" name="connector" value={connectorId} onChange={onConnectorIdChange}>
|
||||
{connectorGroup.connectors.map(
|
||||
({ platform, id, enabled }) =>
|
||||
platform && (
|
||||
<Radio
|
||||
key={id}
|
||||
value={id}
|
||||
title={connectorPlatformLabel[platform]}
|
||||
isDisabled={enabled}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlatformSelector;
|
|
@ -1,16 +1,16 @@
|
|||
import { ConnectorDTO, ConnectorType } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import React, { 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 { RequestError } from '@/hooks/use-api';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import GuideModal from '../GuideModal';
|
||||
import PlatformSelector from './PlatformSelector';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
|
@ -20,14 +20,20 @@ type Props = {
|
|||
};
|
||||
|
||||
const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
||||
const { data, error } = useSWR<ConnectorDTO[], RequestError>('/api/connectors');
|
||||
const isLoading = !data && !error;
|
||||
const { data: allGroups, connectors, error } = useConnectorGroups();
|
||||
const isLoading = !allGroups && !connectors && !error;
|
||||
const [activeGroupId, setActiveGroupId] = useState<string>();
|
||||
const [activeConnectorId, setActiveConnectorId] = useState<string>();
|
||||
const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false);
|
||||
|
||||
const connectors = useMemo(
|
||||
() => data?.filter((connector) => connector.type === type),
|
||||
[data, type]
|
||||
const groups = useMemo(
|
||||
() => allGroups?.filter((group) => group.type === type),
|
||||
[allGroups, type]
|
||||
);
|
||||
|
||||
const activeGroup = useMemo(
|
||||
() => groups?.find(({ id }) => id === activeGroupId),
|
||||
[activeGroupId, groups]
|
||||
);
|
||||
|
||||
const activeConnector = useMemo(
|
||||
|
@ -47,6 +53,24 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
return 'connectors.setup_title.social';
|
||||
}, [type]);
|
||||
|
||||
const handleGroupChange = (groupId: string) => {
|
||||
if (!groups) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveGroupId(groupId);
|
||||
|
||||
const group = groups.find(({ id }) => id === groupId);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstAvailableConnector = group.connectors.find(({ enabled }) => !enabled);
|
||||
|
||||
setActiveConnectorId(firstAvailableConnector?.id);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsGetStartedModalOpen(false);
|
||||
onClose?.();
|
||||
|
@ -76,18 +100,13 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
>
|
||||
{isLoading && 'Loading...'}
|
||||
{error && error}
|
||||
{connectors && (
|
||||
<RadioGroup
|
||||
name="connector"
|
||||
value={activeConnectorId}
|
||||
type="card"
|
||||
onChange={setActiveConnectorId}
|
||||
>
|
||||
{connectors.map(({ id, name, logo, description, enabled }) => (
|
||||
{groups && (
|
||||
<RadioGroup name="group" value={activeGroupId} type="card" onChange={handleGroupChange}>
|
||||
{groups.map(({ id, name, logo, description, connectors }) => (
|
||||
<Radio
|
||||
key={id}
|
||||
value={id}
|
||||
isDisabled={enabled}
|
||||
isDisabled={connectors.every(({ enabled }) => enabled)}
|
||||
className={styles.connector}
|
||||
disabledLabel="connectors.added"
|
||||
>
|
||||
|
@ -97,7 +116,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
<div className={styles.name}>
|
||||
<UnnamedTrans resource={name} />
|
||||
</div>
|
||||
<div className={styles.connectorId}>{id}</div>
|
||||
{type !== ConnectorType.Social && <div className={styles.connectorId}>{id}</div>}
|
||||
<div className={styles.description}>
|
||||
<UnnamedTrans resource={description} />
|
||||
</div>
|
||||
|
@ -105,6 +124,13 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
|||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
{activeGroup && (
|
||||
<PlatformSelector
|
||||
connectorGroup={activeGroup}
|
||||
connectorId={activeConnectorId}
|
||||
onConnectorIdChange={setActiveConnectorId}
|
||||
/>
|
||||
)}
|
||||
{activeConnector && (
|
||||
<GuideModal
|
||||
connector={activeConnector}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { ConnectorDTO } from '@logto/schemas';
|
||||
|
||||
export type ConnectorGroup = Pick<ConnectorDTO, 'name' | 'logo' | 'target' | 'type'> & {
|
||||
export type ConnectorGroup = Pick<
|
||||
ConnectorDTO,
|
||||
'name' | 'logo' | 'target' | 'type' | 'description'
|
||||
> & {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
connectors: ConnectorDTO[];
|
||||
|
|
|
@ -268,6 +268,7 @@ const translation = {
|
|||
web: 'Web',
|
||||
native: 'Native',
|
||||
},
|
||||
add_multi_platform: ' supports multiple platform, select a platform to continue',
|
||||
},
|
||||
connector_details: {
|
||||
back_to_connectors: 'Back to Connectors',
|
||||
|
|
|
@ -264,6 +264,7 @@ const translation = {
|
|||
web: '网页',
|
||||
native: '原生',
|
||||
},
|
||||
add_multi_platform: ' 支持多平台,请选择一个平台继续',
|
||||
},
|
||||
connector_details: {
|
||||
back_to_connectors: '返回连接器',
|
||||
|
|
Loading…
Add table
Reference in a new issue