0
Fork 0
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:
Xiao Yijun 2023-03-13 21:05:44 +08:00 committed by GitHub
parent 8e12abea4f
commit b470e0efb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 188 additions and 35 deletions

View file

@ -1,9 +1,5 @@
@use '@/scss/underscore' as _;
.content {
@include _.multi-line-ellipsis(6);
}
.anchor {
display: inline-block;
}

View file

@ -140,7 +140,7 @@ const Tooltip = ({
horizontalAlignment={positionState.horizontalAlign}
isSuccessful={isSuccessful}
>
<div className={styles.content}>{content}</div>
{content}
</TipBubble>,
tooltipDom
)}

View file

@ -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;

View file

@ -30,7 +30,7 @@ const SocialSelector = ({ value, onChange }: Props) => {
</DangerousRaw>
),
value: item.target,
tag: 'general.trial',
tag: 'general.demo',
};
});

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -1,5 +1,10 @@
@use '@/scss/underscore' as _;
.container {
display: flex;
align-items: center;
}
.logoContainer {
width: 40px;
height: 40px;

View file

@ -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>
);
};

View file

@ -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];

View file

@ -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',

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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",

View file

@ -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;

View file

@ -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: '사용 중',

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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: '使用中',

View file

@ -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;