diff --git a/src/assets/images/password.svg b/src/assets/images/password.svg new file mode 100644 index 00000000..bd36b4b7 --- /dev/null +++ b/src/assets/images/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 1a86fbc3..a06a5963 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -6,9 +6,6 @@ export default { 'common.button.shortcut': 'Keyboard Shortcut', 'common.button.add': 'Add', 'common.button.login': 'Log in', - 'common.button.login.local': 'Log in with local user', - 'common.button.oidclogin': 'Log in with OIDC', - 'common.button.samllogin': 'Log in with SAML', 'common.button.select': 'Select', 'common.button.selected': 'Selected', 'common.button.continue': 'Continue', @@ -257,5 +254,6 @@ export default { 'common.select.count': '{count} selected', 'common.login.auth': 'Authenticating...', 'common.login.auth.failed': 'Authentication failed', - 'common.login.thirdparty': 'or login with' + 'common.login.password': 'Log in with Password', + 'common.external.login': 'Log in with {type}' }; diff --git a/src/locales/ja-JP/common.ts b/src/locales/ja-JP/common.ts index 488f0398..0cc25a8a 100644 --- a/src/locales/ja-JP/common.ts +++ b/src/locales/ja-JP/common.ts @@ -6,9 +6,6 @@ export default { 'common.button.shortcut': 'キーボードショートカット', 'common.button.add': '追加', 'common.button.login': 'ログイン', - 'common.button.login.local': 'ローカルユーザーでログイン', - 'common.button.oidclogin': 'OIDC でログイン', - 'common.button.samllogin': 'SAML でログイン', 'common.button.select': '選択', 'common.button.selected': '選択済み', 'common.button.continue': '続行', @@ -257,7 +254,8 @@ export default { 'common.select.count': '{count} selected', 'common.login.auth': 'Authenticating...', 'common.login.auth.failed': 'Authentication failed', - 'common.login.thirdparty': 'or login with' + 'common.login.password': 'Log in with Password', + 'common.external.login': 'Log in with {type}' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -277,6 +275,7 @@ export default { // 14. 'common.button.clearSelection': 'Clear Selection', // 15. 'common.select.count': '{count} selected', // 16. 'common.login.auth': 'Authenticating...', -// 17. 'common.login.auth.failed': 'Authentication failed' -// 18. 'common.login.thirdparty': 'or login with' +// 17. 'common.login.auth.failed': 'Authentication failed', +// 18. 'common.external.login': 'Log in with {type}' +// 19. 'common.login.password': 'Log in with Password', // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/common.ts b/src/locales/ru-RU/common.ts index 6ac9f537..2f6755be 100644 --- a/src/locales/ru-RU/common.ts +++ b/src/locales/ru-RU/common.ts @@ -6,9 +6,6 @@ export default { 'common.button.shortcut': 'Сочетания клавиш', 'common.button.add': 'Добавить', 'common.button.login': 'Войти', - 'common.button.login.local': 'Войти с локальным пользователем', - 'common.button.oidclogin': 'Войти с OIDC', - 'common.button.samllogin': 'Войти с SAML', 'common.button.select': 'Выбрать', 'common.button.selected': 'Выбрано', 'common.button.continue': 'Продолжить', @@ -256,9 +253,11 @@ export default { 'common.select.count': '{count} Выбрано', 'common.login.auth': 'Аутентификация...', 'common.login.auth.failed': 'Ошибка аутентификации', - 'common.login.thirdparty': 'or login with' + 'common.login.password': 'Log in with Password', + 'common.external.login': 'Log in with {type}' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== -// 1. 'common.login.thirdparty': 'or login with' +// 1. 'common.external.login': 'Log in with {type}' +// 2. 'common.login.password': 'Log in with Password' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index e1a723ac..3c730b7b 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -6,9 +6,6 @@ export default { 'common.button.shortcut': '快捷键', 'common.button.add': '添加', 'common.button.login': '登录', - 'common.button.login.local': '本地用户登录', - 'common.button.oidclogin': '使用 OIDC 登录', - 'common.button.samllogin': '使用 SAML 登录', 'common.button.select': '选择', 'common.button.selected': '已选择', 'common.button.continue': '继续', @@ -250,5 +247,6 @@ export default { 'common.select.count': '已选 {count} 项', 'common.login.auth': '认证中...', 'common.login.auth.failed': '认证失败', - 'common.login.thirdparty': '或使用以下方式登录' + 'common.login.password': '使用密码登录', + 'common.external.login': '使用 {type} 登录' }; diff --git a/src/pages/login/components/local-user-form.tsx b/src/pages/login/components/local-user-form.tsx new file mode 100644 index 00000000..d2d6e7c5 --- /dev/null +++ b/src/pages/login/components/local-user-form.tsx @@ -0,0 +1,96 @@ +import SealInput from '@/components/seal-form/seal-input'; +import externalLinks from '@/constants/external-links'; +import { LockOutlined, UserOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Checkbox, Form, FormInstance } from 'antd'; + +interface LocalUserFormProps { + handleLogin: (values: any) => void; + form: FormInstance; +} + +const LocalUserForm: React.FC = (props) => { + const { handleLogin, form } = props; + const intl = useIntl(); + + return ( +
+ + } + /> + + + + } + label={intl.formatMessage({ id: 'common.form.password' })} + /> + +
+ + + + {intl.formatMessage({ id: 'common.login.rember' })} + + + + +
+ +
+ ); +}; + +export default LocalUserForm; diff --git a/src/pages/login/components/login-form.tsx b/src/pages/login/components/login-form.tsx index 52e461d6..ac290f64 100644 --- a/src/pages/login/components/login-form.tsx +++ b/src/pages/login/components/login-form.tsx @@ -3,12 +3,9 @@ import OIDCIcon from '@/assets/images/oidc.svg'; import SAMLIcon from '@/assets/images/saml.svg'; import { userAtom } from '@/atoms/user'; import LangSelect from '@/components/lang-select'; -import SealInput from '@/components/seal-form/seal-input'; import ThemeDropActions from '@/components/theme-toggle/theme-drop-actions'; -import externalLinks from '@/constants/external-links'; -import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { useIntl, useModel } from '@umijs/max'; -import { Button, Checkbox, Divider, Form, Spin, Tooltip, message } from 'antd'; +import { Button, Divider, Form, Spin, message } from 'antd'; import { createStyles } from 'antd-style'; import { useAtom } from 'jotai'; import { useMemo, useState } from 'react'; @@ -17,22 +14,34 @@ import styled from 'styled-components'; import { useLocalAuth } from '../hooks/use-local-auth'; import { useSSOAuth } from '../hooks/use-sso-auth'; import { checkDefaultPage } from '../utils'; +import LocalUserForm from './local-user-form'; const Buttons = styled.div` display: flex; - justify-content: center; - align-items: center; - gap: 32px; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 24px; + width: 360px; +`; + +const BackButton = styled(Button).attrs({ + type: 'link', + size: 'small', + block: true +})` + margin-top: 20px; `; const ButtonWrapper = styled(Button).attrs({ - shape: 'circle', - size: 'large', - color: 'default', - variant: 'filled' -})` - height: 42px; - width: 42px; + shape: 'round', + block: true +})``; + +const ButtonText = styled.span` + display: flex; + align-items: center; + gap: 8px; `; const DividerWrapper = styled(Divider)` @@ -68,6 +77,16 @@ const useStyles = createStyles(({ token, css }) => ({ .title { font-weight: bold; } + `, + welcome: css` + display: flex; + margin-bottom: 32px; + font-size: 20px; + justify-content: center; + align-items: center; + .text { + color: ${token.colorText}; + } ` })); @@ -79,20 +98,15 @@ const LoginForm = () => { const [authError, setAuthError] = useState(null); const intl = useIntl(); const [form] = Form.useForm(); + const [isPassword, setIsPassword] = useState(false); - const renderWelCome = useMemo(() => { + const renderWelCome = () => { return ( -
+
- {intl?.formatMessage({ id: 'users.login.title' })} + + {intl?.formatMessage({ id: 'users.login.title' })} + logo {
); - }, [intl]); + }; const gotoDefaultPage = async (userInfo: any) => { checkDefaultPage(userInfo, true); @@ -162,34 +176,56 @@ const LoginForm = () => { onError: handleOnError }); + const handleLoginWithPassword = () => { + setIsPassword(true); + }; + + const handleLoginWithThirdParty = () => { + if (SSOAuth.options.oidc) { + SSOAuth.loginWithOIDC(); + } else if (SSOAuth.options.saml) { + SSOAuth.loginWithSAML(); + } + }; + const hasThirdPartyLogin = useMemo(() => { return SSOAuth.options.oidc || SSOAuth.options.saml; }, [SSOAuth.options]); - const renderThirdPartyLoginButtons = () => { - if (!hasThirdPartyLogin) return null; + const renderLoginButtons = () => { + // do not render login buttons if using password login or no third-party login + if (!hasThirdPartyLogin || isPassword) return null; + return ( - <> - - {intl.formatMessage({ id: 'common.login.thirdparty' })} - - - {SSOAuth.options.oidc && ( - - - - - - )} - {SSOAuth.options.saml && ( - - - - - - )} - - + + {SSOAuth.options.oidc && ( + + + + {intl.formatMessage( + { id: 'common.external.login' }, + { type: 'OIDC' } + )} + + + )} + {SSOAuth.options.saml && ( + + + + {intl.formatMessage( + { id: 'common.external.login' }, + { type: 'SAML' } + )} + + + )} + + ); }; @@ -212,84 +248,21 @@ const LoginForm = () => { ) : ( -
- {renderWelCome} - - } - /> - - - - } - label={intl.formatMessage({ id: 'common.form.password' })} - /> - -
- - - - {intl.formatMessage({ id: 'common.login.rember' })} - - - - -
- - {renderThirdPartyLoginButtons()} -
+ <> + {renderWelCome()} + {renderLoginButtons()} + {(!hasThirdPartyLogin || isPassword) && ( + + )} + {hasThirdPartyLogin && isPassword && ( + + {intl.formatMessage( + { id: 'common.external.login' }, + { type: SSOAuth.options.oidc ? 'OIDC' : 'SAML' } + )} + + )} + )}
diff --git a/src/pages/login/hooks/use-local-auth.ts b/src/pages/login/hooks/use-local-auth.ts index f6171308..acc90cd6 100644 --- a/src/pages/login/hooks/use-local-auth.ts +++ b/src/pages/login/hooks/use-local-auth.ts @@ -83,7 +83,7 @@ export const useLocalAuth = ({ } onSuccess?.(userInfo); - } catch (error) { + } catch (error: any) { onError?.(error); } }; diff --git a/src/pages/login/hooks/use-sso-auth.ts b/src/pages/login/hooks/use-sso-auth.ts index 1e32a7f9..14bc7b2e 100644 --- a/src/pages/login/hooks/use-sso-auth.ts +++ b/src/pages/login/hooks/use-sso-auth.ts @@ -45,27 +45,27 @@ export function useSSOAuth({ window.location.href = '/auth/saml/login'; }; - useEffect(() => { - fetchAuthConfig('/auth_config') - .then((authConfig) => { - setLoginOption({ - oidc: !!authConfig.is_oidc, - saml: !!authConfig.is_saml - }); - }) - .catch(() => { - setLoginOption({ oidc: false, saml: false }); + const init = async () => { + try { + const authConfig = await fetchAuthConfig('/auth_config'); + setLoginOption({ + oidc: !!authConfig.is_oidc, + saml: !!authConfig.is_saml }); - - if (sso) { - fetchUserInfo() - .then((userInfo) => { - onSuccess?.(userInfo); - }) - .catch((error) => { - onError?.(error); - }); + if (sso) { + if (authConfig.is_oidc) { + oidcLogin(); + } else if (authConfig.is_saml) { + samlLogin(); + } + } + } catch { + setLoginOption({ oidc: false, saml: false }); } + }; + + useEffect(() => { + init(); }, []); return { diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 5059818b..459534be 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,8 +1,10 @@ +import { userAtom } from '@/atoms/user'; import useTabActive from '@/hooks/use-tab-active'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; import { TabsProps } from 'antd'; -import React, { useCallback, useState } from 'react'; +import { useAtom } from 'jotai'; +import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import Appearance from './components/appearance'; import ModifyPasswordn from './components/modify-password'; @@ -15,23 +17,35 @@ const Wrapper = styled.div` const Profile: React.FC = () => { const intl = useIntl(); + const [userInfo, setUserInfo] = useAtom(userAtom); const { setTabActive, getTabActive, tabsMap } = useTabActive(); const [activeKey, setActiveKey] = useState( - getTabActive(tabsMap.userSettings) || 'modify-password' + userInfo?.source === 'Local' ? 'modify-password' : 'appearance' ); - const items: TabsProps['items'] = [ - { - key: 'modify-password', - label: intl.formatMessage({ id: 'users.form.updatepassword' }), - children: - }, - { - key: 'appearance', - label: intl.formatMessage({ id: 'common.appearance' }), - children: + const items: TabsProps['items'] = useMemo(() => { + if (userInfo?.source !== 'Local') { + return [ + { + key: 'appearance', + label: intl.formatMessage({ id: 'common.appearance' }), + children: + } + ]; } - ]; + return [ + { + key: 'modify-password', + label: intl.formatMessage({ id: 'users.form.updatepassword' }), + children: + }, + { + key: 'appearance', + label: intl.formatMessage({ id: 'common.appearance' }), + children: + } + ]; + }, [userInfo?.source]); const handleChangeTab = useCallback((key: string) => { setActiveKey(key);