mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
feat(console): add demo state to connectors (#3382)
This commit is contained in:
parent
8e12abea4f
commit
b470e0efb7
26 changed files with 188 additions and 35 deletions
|
@ -1,9 +1,5 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.content {
|
||||
@include _.multi-line-ellipsis(6);
|
||||
}
|
||||
|
||||
.anchor {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ const Tooltip = ({
|
|||
horizontalAlignment={positionState.horizontalAlign}
|
||||
isSuccessful={isSuccessful}
|
||||
>
|
||||
<div className={styles.content}>{content}</div>
|
||||
{content}
|
||||
</TipBubble>,
|
||||
tooltipDom
|
||||
)}
|
||||
|
|
|
@ -55,3 +55,8 @@ export const defaultEmailConnectorGroup: ConnectorGroup = {
|
|||
logoDark: null,
|
||||
target: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* Note: this feature has not been implemented yet; @xiaoyijun will refactor this once the internal Logto connectors are ready.
|
||||
*/
|
||||
export const isDemoConnector = true;
|
||||
|
|
|
@ -30,7 +30,7 @@ const SocialSelector = ({ value, onChange }: Props) => {
|
|||
</DangerousRaw>
|
||||
),
|
||||
value: item.target,
|
||||
tag: 'general.trial',
|
||||
tag: 'general.demo',
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useSWRConfig } from 'swr';
|
||||
|
||||
import Delete from '@/assets/images/delete.svg';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import IconButton from '@/components/IconButton';
|
||||
import { Tooltip } from '@/components/Tip';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useConnectorInUse from '@/hooks/use-connector-in-use';
|
||||
import type { ConnectorGroup } from '@/types/connector';
|
||||
|
||||
type Props = {
|
||||
connectorGroup: ConnectorGroup;
|
||||
};
|
||||
|
||||
const ConnectorDeleteButton = ({ connectorGroup }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { mutate: mutateGlobal } = useSWRConfig();
|
||||
const { connectors } = connectorGroup;
|
||||
const { isConnectorInUse } = useConnectorInUse();
|
||||
|
||||
const firstConnector = connectors[0];
|
||||
const isSocial = firstConnector?.type === ConnectorType.Social;
|
||||
const inUse = isConnectorInUse(firstConnector);
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false);
|
||||
|
||||
const api = useApi();
|
||||
|
||||
const onDeleteClick = async () => {
|
||||
if (!isSocial || !inUse) {
|
||||
await handleDelete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDeleteAlertOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!firstConnector) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { connectors } = connectorGroup;
|
||||
|
||||
await Promise.all(
|
||||
connectors.map(async (connector) => {
|
||||
await api.delete(`api/connectors/${connector.id}`).json<ConnectorResponse>();
|
||||
})
|
||||
);
|
||||
|
||||
toast.success(t('connector_details.connector_deleted'));
|
||||
await mutateGlobal('api/connectors');
|
||||
};
|
||||
|
||||
if (!firstConnector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip content={<div>{t('general.delete')}</div>}>
|
||||
<IconButton onClick={onDeleteClick}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteAlertOpen}
|
||||
confirmButtonText="general.delete"
|
||||
onCancel={() => {
|
||||
setIsDeleteAlertOpen(false);
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="connector_details.in_use_deletion_description"
|
||||
components={{ name: <UnnamedTrans resource={firstConnector.name} /> }}
|
||||
/>
|
||||
</ConfirmModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectorDeleteButton;
|
|
@ -0,0 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.tag {
|
||||
font: var(--font-label-3);
|
||||
background-color: var(--color-alert-99);
|
||||
padding: _.unit(0.5) _.unit(1.5);
|
||||
border-radius: 10px;
|
||||
user-select: none;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Tooltip } from '@/components/Tip';
|
||||
|
||||
import * as styles from './DemoTag.module.scss';
|
||||
|
||||
const DemoTag = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<Tooltip content={<div>{t('connectors.demo_tip')}</div>}>
|
||||
<div className={styles.tag}>{t('general.demo')}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoTag;
|
|
@ -1,5 +1,10 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
|
|
@ -15,13 +15,15 @@ import { ConnectorsTabs } from '@/consts/page-tabs';
|
|||
import ConnectorPlatformIcon from '@/icons/ConnectorPlatformIcon';
|
||||
import type { ConnectorGroup } from '@/types/connector';
|
||||
|
||||
import DemoTag from './DemoTag';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
connectorGroup: ConnectorGroup;
|
||||
isDemo?: boolean;
|
||||
};
|
||||
|
||||
const ConnectorName = ({ connectorGroup }: Props) => {
|
||||
const ConnectorName = ({ connectorGroup, isDemo = false }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { type, connectors } = connectorGroup;
|
||||
const connector = connectors[0];
|
||||
|
@ -58,12 +60,12 @@ const ConnectorName = ({ connectorGroup }: Props) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<ItemPreview
|
||||
title={<UnnamedTrans resource={connector.name} />}
|
||||
subtitle={
|
||||
<>
|
||||
{type !== ConnectorType.Social && connector.id}
|
||||
{type === ConnectorType.Social && hasNonUniversalConnector && (
|
||||
<div className={styles.container}>
|
||||
<ItemPreview
|
||||
title={<UnnamedTrans resource={connector.name} />}
|
||||
subtitle={
|
||||
type === ConnectorType.Social &&
|
||||
hasNonUniversalConnector && (
|
||||
<div className={styles.platforms}>
|
||||
{connectors.map(
|
||||
({ id, platform }) =>
|
||||
|
@ -75,16 +77,17 @@ const ConnectorName = ({ connectorGroup }: Props) => {
|
|||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
icon={<ConnectorLogo data={connector} />}
|
||||
to={`/connectors/${
|
||||
connector.type === ConnectorType.Social
|
||||
? ConnectorsTabs.Social
|
||||
: ConnectorsTabs.Passwordless
|
||||
}/${connector.id}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
icon={<ConnectorLogo data={connector} />}
|
||||
to={`/connectors/${
|
||||
connector.type === ConnectorType.Social
|
||||
? ConnectorsTabs.Social
|
||||
: ConnectorsTabs.Passwordless
|
||||
}/${connector.id}`}
|
||||
/>
|
||||
{isDemo && <DemoTag />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -15,13 +15,14 @@ import CardTitle from '@/components/CardTitle';
|
|||
import TabNav, { TabNavItem } from '@/components/TabNav';
|
||||
import Table from '@/components/Table';
|
||||
import TablePlaceholder from '@/components/Table/TablePlaceholder';
|
||||
import { defaultEmailConnectorGroup, defaultSmsConnectorGroup } from '@/consts';
|
||||
import { defaultEmailConnectorGroup, defaultSmsConnectorGroup, isDemoConnector } from '@/consts';
|
||||
import { ConnectorsTabs } from '@/consts/page-tabs';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import * as resourcesStyles from '@/scss/resources.module.scss';
|
||||
|
||||
import ConnectorDeleteButton from './components/ConnectorDeleteButton';
|
||||
import ConnectorName from './components/ConnectorName';
|
||||
import ConnectorStatus from './components/ConnectorStatus';
|
||||
import ConnectorStatusField from './components/ConnectorStatusField';
|
||||
|
@ -126,7 +127,9 @@ const Connectors = () => {
|
|||
title: t('connectors.connector_name'),
|
||||
dataIndex: 'name',
|
||||
colSpan: 6,
|
||||
render: (connectorGroup) => <ConnectorName connectorGroup={connectorGroup} />,
|
||||
render: (connectorGroup) => (
|
||||
<ConnectorName connectorGroup={connectorGroup} isDemo={isDemoConnector} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('connectors.connector_type'),
|
||||
|
@ -137,11 +140,19 @@ const Connectors = () => {
|
|||
{
|
||||
title: <ConnectorStatusField />,
|
||||
dataIndex: 'status',
|
||||
colSpan: 5,
|
||||
colSpan: 4,
|
||||
render: (connectorGroup) => <ConnectorStatus connectorGroup={connectorGroup} />,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
dataIndex: 'delete',
|
||||
colSpan: 1,
|
||||
render: (connectorGroup) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
isDemoConnector ? <ConnectorDeleteButton connectorGroup={connectorGroup} /> : null,
|
||||
},
|
||||
]}
|
||||
isRowClickable={({ connectors }) => Boolean(connectors[0])}
|
||||
isRowClickable={({ connectors }) => Boolean(connectors[0]) && !isDemoConnector}
|
||||
rowClickHandler={({ connectors }) => {
|
||||
const firstConnector = connectors[0];
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ const connectors = {
|
|||
tab_email_sms: 'E-Mail und SMS Connectoren',
|
||||
tab_social: 'Social Connectoren',
|
||||
connector_name: 'Connectorname',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: 'Typ',
|
||||
connector_status: 'Anmeldeoberfläche',
|
||||
connector_status_in_use: 'In Benutzung',
|
||||
|
|
|
@ -51,7 +51,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -7,6 +7,8 @@ const connectors = {
|
|||
tab_email_sms: 'Email and SMS connectors',
|
||||
tab_social: 'Social connectors',
|
||||
connector_name: 'Connector name',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.',
|
||||
connector_type: 'Type',
|
||||
connector_status: 'Sign in Experience',
|
||||
connector_status_in_use: 'In use',
|
||||
|
|
|
@ -50,7 +50,7 @@ const general = {
|
|||
try_now: 'Try Now',
|
||||
multiple_form_field: '(Multiple)',
|
||||
cap_limit: 'Cap limit',
|
||||
trial: 'Trail',
|
||||
demo: 'Demo',
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -8,6 +8,8 @@ const connectors = {
|
|||
tab_email_sms: 'Connecteurs Email et SMS',
|
||||
tab_social: 'Connecteurs sociaux',
|
||||
connector_name: 'Nom du connecteur',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: 'Type',
|
||||
connector_status: 'Experience de connexion',
|
||||
connector_status_in_use: "En cours d'utilisation",
|
||||
|
|
|
@ -51,7 +51,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -8,6 +8,8 @@ const connectors = {
|
|||
tab_email_sms: '이메일/SMS 연동',
|
||||
tab_social: '소셜 연동',
|
||||
connector_name: '연동 이름',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: '종류',
|
||||
connector_status: '로그인 경험',
|
||||
connector_status_in_use: '사용 중',
|
||||
|
|
|
@ -50,7 +50,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -8,6 +8,8 @@ const connectors = {
|
|||
tab_email_sms: 'Conectores de e-mail e SMS',
|
||||
tab_social: 'Conectores sociais',
|
||||
connector_name: 'Nome do conector',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: 'Tipo',
|
||||
connector_status: 'Experiência de login',
|
||||
connector_status_in_use: 'Em uso',
|
||||
|
|
|
@ -51,7 +51,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -7,6 +7,8 @@ const connectors = {
|
|||
tab_email_sms: 'Conectores de Email e SMS',
|
||||
tab_social: 'Conectores sociais',
|
||||
connector_name: 'Nome do conector',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: 'Tipo',
|
||||
connector_status: 'Experiência de login',
|
||||
connector_status_in_use: 'Em uso',
|
||||
|
|
|
@ -50,7 +50,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -8,6 +8,8 @@ const connectors = {
|
|||
tab_email_sms: 'E-posta ve SMS connectorları',
|
||||
tab_social: 'Social connectorlar',
|
||||
connector_name: 'Connector adı',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: 'Tip',
|
||||
connector_status: 'Oturum açma deneyimi',
|
||||
connector_status_in_use: 'Kullanımda',
|
||||
|
|
|
@ -51,7 +51,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
|
@ -7,6 +7,8 @@ const connectors = {
|
|||
tab_email_sms: '短信和邮件连接器',
|
||||
tab_social: '社交连接器',
|
||||
connector_name: '连接器名称',
|
||||
demo_tip:
|
||||
'This connector has been preconfigured for demonstration purposes only. It should not be utilized in a production environment. Once you have completed your testing, be sure to provide your own credentials and set up your own connectors. The one you created will replace the trial version.', // UNTRANSLATED
|
||||
connector_type: '类型',
|
||||
connector_status: '登录体验',
|
||||
connector_status_in_use: '使用中',
|
||||
|
|
|
@ -50,7 +50,7 @@ const general = {
|
|||
try_now: 'Try Now', // UNTRANSLATED
|
||||
multiple_form_field: '(Multiple)', // UNTRANSLATED
|
||||
cap_limit: 'Cap limit', // UNTRANSLATED
|
||||
trial: 'Trail', // UNTRANSLATED
|
||||
demo: 'Demo', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default general;
|
||||
|
|
Loading…
Add table
Reference in a new issue