diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index 9f561963b..b3071fc2b 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -39,12 +39,15 @@ const Main = () => { } /> - } /> - } /> } /> } /> + + } /> + } /> + } /> + diff --git a/packages/console/src/assets/images/social-connectors-placeholder.svg b/packages/console/src/assets/images/social-connectors-placeholder.svg new file mode 100644 index 000000000..6cfcf2665 --- /dev/null +++ b/packages/console/src/assets/images/social-connectors-placeholder.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/console/src/components/AppContent/index.module.scss b/packages/console/src/components/AppContent/index.module.scss index 00553aef0..59a056911 100644 --- a/packages/console/src/components/AppContent/index.module.scss +++ b/packages/console/src/components/AppContent/index.module.scss @@ -40,6 +40,8 @@ --color-text-default: #202223; --color-foggy: #888; --color-disabled: #c4c7c7; + --color-border: #c4c7c7; + --color-green: #66bb6a; } $font-family: 'SF UI Text', 'SF Pro Display', sans-serif; diff --git a/packages/console/src/components/Sidebar/index.tsx b/packages/console/src/components/Sidebar/index.tsx index 53446001d..94c4bcc13 100644 --- a/packages/console/src/components/Sidebar/index.tsx +++ b/packages/console/src/components/Sidebar/index.tsx @@ -24,7 +24,7 @@ const Sidebar = () => { key={title} titleKey={title} icon={} - isActive={location.pathname === getPath(title)} + isActive={location.pathname.startsWith(getPath(title))} /> ))} diff --git a/packages/console/src/components/Status/index.module.scss b/packages/console/src/components/Status/index.module.scss new file mode 100644 index 000000000..6f638ed4a --- /dev/null +++ b/packages/console/src/components/Status/index.module.scss @@ -0,0 +1,18 @@ +@use '@/scss/underscore' as _; + +.status { + display: flex; + align-items: center; +} + +.icon { + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: _.unit(2); + background: var(--color-green); +} + +.status.offline .icon { + background: var(--color-neutral-70); +} diff --git a/packages/console/src/components/Status/index.tsx b/packages/console/src/components/Status/index.tsx new file mode 100644 index 000000000..16b9ffbf3 --- /dev/null +++ b/packages/console/src/components/Status/index.tsx @@ -0,0 +1,18 @@ +import classNames from 'classnames'; +import React, { ReactNode } from 'react'; + +import styles from './index.module.scss'; + +type Props = { + status: 'operational' | 'offline'; + children: ReactNode; +}; + +const Status = ({ status, children }: Props) => ( +
+
+
{children}
+
+); + +export default Status; diff --git a/packages/console/src/components/TabNav/TabNavLink.module.scss b/packages/console/src/components/TabNav/TabNavLink.module.scss new file mode 100644 index 000000000..3b168d9b5 --- /dev/null +++ b/packages/console/src/components/TabNav/TabNavLink.module.scss @@ -0,0 +1,18 @@ +@use '@/scss/underscore' as _; + +.link { + font-size: var(--font-body-2); + margin-right: _.unit(6); + padding-bottom: _.unit(1); + + a { + color: unset; + text-decoration: none; + } +} + +.selected { + color: var(--color-primary); + border-bottom: 1px solid var(--color-primary); + margin-bottom: -1px; +} diff --git a/packages/console/src/components/TabNav/TabNavLink.tsx b/packages/console/src/components/TabNav/TabNavLink.tsx new file mode 100644 index 000000000..8aa92a403 --- /dev/null +++ b/packages/console/src/components/TabNav/TabNavLink.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; + +import * as styles from './TabNavLink.module.scss'; + +type Props = { + href: string; + children: React.ReactNode; +}; + +const TabNavLink = ({ children, href }: Props) => { + const location = useLocation(); + const selected = location.pathname === href; + + return ( +
+ {children} +
+ ); +}; + +export default TabNavLink; diff --git a/packages/console/src/components/TabNav/index.module.scss b/packages/console/src/components/TabNav/index.module.scss new file mode 100644 index 000000000..f4c4c4a54 --- /dev/null +++ b/packages/console/src/components/TabNav/index.module.scss @@ -0,0 +1,7 @@ +@use '@/scss/underscore' as _; + +.nav { + border-bottom: 1px solid var(--color-border); + display: flex; + margin: _.unit(6) 0 _.unit(1); +} diff --git a/packages/console/src/components/TabNav/index.tsx b/packages/console/src/components/TabNav/index.tsx new file mode 100644 index 000000000..d865d41fc --- /dev/null +++ b/packages/console/src/components/TabNav/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import * as styles from './index.module.scss'; + +export { default as TabNavLink } from './TabNavLink'; + +type Props = { + children: React.ReactNode; +}; + +const TabNav = ({ children }: Props) => ; + +export default TabNav; diff --git a/packages/console/src/pages/Connectors/components/ConnectorName.module.scss b/packages/console/src/pages/Connectors/components/ConnectorName.module.scss new file mode 100644 index 000000000..348f2c69e --- /dev/null +++ b/packages/console/src/pages/Connectors/components/ConnectorName.module.scss @@ -0,0 +1,10 @@ +a.link { + color: inherit; + text-decoration: none; +} + +.logo { + width: 50px; + height: 50px; + border-radius: 5px; +} diff --git a/packages/console/src/pages/Connectors/components/ConnectorName.tsx b/packages/console/src/pages/Connectors/components/ConnectorName.tsx new file mode 100644 index 000000000..857c844c3 --- /dev/null +++ b/packages/console/src/pages/Connectors/components/ConnectorName.tsx @@ -0,0 +1,42 @@ +import { ConnectorDTO } from '@logto/schemas'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; + +import ImagePlaceholder from '@/components/ImagePlaceholder'; +import ItemPreview from '@/components/ItemPreview'; + +import * as styles from './ConnectorName.module.scss'; + +type Props = { + connector?: ConnectorDTO; + titlePlaceholder?: string; +}; + +const ConnectorName = ({ connector, titlePlaceholder = '' }: Props) => { + const { + i18n: { language }, + } = useTranslation(); + + if (!connector) { + return } />; + } + + return ( + + + ) : ( + + ) + } + /> + + ); +}; + +export default ConnectorName; diff --git a/packages/console/src/pages/Connectors/components/ConnectorRow.tsx b/packages/console/src/pages/Connectors/components/ConnectorRow.tsx new file mode 100644 index 000000000..fa2f69da5 --- /dev/null +++ b/packages/console/src/pages/Connectors/components/ConnectorRow.tsx @@ -0,0 +1,57 @@ +import { ConnectorDTO, ConnectorType } from '@logto/schemas'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import Button from '@/components/Button'; +import Status from '@/components/Status'; + +import ConnectorName from './ConnectorName'; + +type Props = { + type: ConnectorType; + connector?: ConnectorDTO; +}; + +const ConnectorRow = ({ type, connector }: Props) => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const typeLabel = useMemo(() => { + if (type === ConnectorType.Email) { + return t('connectors.type.email'); + } + + if (type === ConnectorType.SMS) { + return t('connectors.type.sms'); + } + + return t('connectors.type.social'); + }, [type, t]); + + return ( + + + + + {typeLabel} + + {type === ConnectorType.Social && ( + + {t( + connector?.enabled + ? 'connectors.connector_status_enabled' + : 'connectors.connector_status_disabled' + )} + + )} + {type !== ConnectorType.Social && connector && ( + {t('connectors.connector_status_enabled')} + )} + {type !== ConnectorType.Social && !connector && ( +
+ + {t('connectors.tab_email_sms')} + {t('connectors.tab_social')} + + {error && ( - + )} {isLoading && ( - + )} - {data?.map(({ id, metadata: { name, logo }, enabled }) => ( - - - + + )} + {!isLoading && !isSocial && ( + + )} + {!isLoading && !isSocial && ( + + )} + {socialConnectors?.map((connector) => ( + ))}
{t('connectors.connector_name')}{t('connectors.connector_type')} {t('connectors.connector_status')}
error occurred: {error.metadata.code}error occurred: {error.metadata.code}
loadingloading
- - - ) : ( - - ) - } - /> - - - {enabled - ? t('connectors.connector_status_enabled') - : t('connectors.connector_status_disabled')} + {socialConnectors?.length === 0 && ( +
+
+
+ +
+
{t('connectors.type.social')}
+
{t('connectors.social_connector_eg')}
+
diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index d29972b28..49ade6af3 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -83,10 +83,15 @@ const translation = { title: 'Connectors', subtitle: 'Setup connectors to enable passwordless and social sign in experience.', create: 'Add Connector', + set_up: 'Set Up', + tab_email_sms: 'Email and SMS connectors', + tab_social: 'Social connectors', connector_name: 'Connector Name', + connector_type: 'Type', connector_status: 'Status', connector_status_enabled: 'Enabled', connector_status_disabled: 'Disabled', + social_connector_eg: 'e.g.: Google, Facebook, Twitter', type: { email: 'Email Sender', sms: 'SMS Sender', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 8652b54f2..cd341d20f 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -85,10 +85,15 @@ const translation = { title: '连接器', subtitle: 'Setup connectors to enable passwordless and social sign in experience.', create: '添加连接器', + set_up: '设置', + tab_email_sms: '邮件/短信服务商', + tab_social: '社会化登录', connector_name: '连接器', + connector_type: '类型', connector_status: '状态', connector_status_enabled: '已启用', connector_status_disabled: '已禁用', + social_connector_eg: '如: 微信登录,支付宝登录,微博登录', type: { email: '邮件服务商', sms: '短信服务商',