From 0f3216564dbda03f29b6f1ded0603b991bd2b45d Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 22 May 2025 21:42:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat(ui):=20Add=20loading=20states=20t?= =?UTF-8?q?o=20all=20authentication=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add loading indicators to improve user experience during authentication processes: - Implement loading states for all login and registration buttons - Add try/catch/finally structure to properly handle async operations - Create wrapper functions for OAuth redirect operations - Set loading state for verification code submission - Update modal confirmation buttons with loading state - Add proper error handling with user feedback - Split generic loading state into specific state variables This change enhances user experience by providing clear visual feedback during authentication actions. --- web/src/components/LoginForm.js | 212 +++++++++++++++++++++-------- web/src/components/RegisterForm.js | 206 +++++++++++++++++++--------- 2 files changed, 293 insertions(+), 125 deletions(-) diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js index cbf794ab..3ed799f1 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/LoginForm.js @@ -53,6 +53,15 @@ const LoginForm = () => { const [status, setStatus] = useState({}); const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false); const [showEmailLogin, setShowEmailLogin] = useState(false); + const [wechatLoading, setWechatLoading] = useState(false); + const [githubLoading, setGithubLoading] = useState(false); + const [oidcLoading, setOidcLoading] = useState(false); + const [linuxdoLoading, setLinuxdoLoading] = useState(false); + const [emailLoginLoading, setEmailLoginLoading] = useState(false); + const [loginLoading, setLoginLoading] = useState(false); + const [resetPasswordLoading, setResetPasswordLoading] = useState(false); + const [otherLoginOptionsLoading, setOtherLoginOptionsLoading] = useState(false); + const [wechatCodeSubmitLoading, setWechatCodeSubmitLoading] = useState(false); const { t } = useTranslation(); const logo = getLogo(); @@ -79,7 +88,9 @@ const LoginForm = () => { }, []); const onWeChatLoginClicked = () => { + setWechatLoading(true); setShowWeChatLoginModal(true); + setWechatLoading(false); }; const onSubmitWeChatVerificationCode = async () => { @@ -87,20 +98,27 @@ const LoginForm = () => { showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); return; } - const res = await API.get( - `/api/oauth/wechat?code=${inputs.wechat_verification_code}`, - ); - const { success, message, data } = res.data; - if (success) { - userDispatch({ type: 'login', payload: data }); - localStorage.setItem('user', JSON.stringify(data)); - setUserData(data); - updateAPI(); - navigate('/'); - showSuccess('登录成功!'); - setShowWeChatLoginModal(false); - } else { - showError(message); + setWechatCodeSubmitLoading(true); + try { + const res = await API.get( + `/api/oauth/wechat?code=${inputs.wechat_verification_code}`, + ); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + setUserData(data); + updateAPI(); + navigate('/'); + showSuccess('登录成功!'); + setShowWeChatLoginModal(false); + } else { + showError(message); + } + } catch (error) { + showError('登录失败,请重试'); + } finally { + setWechatCodeSubmitLoading(false); } }; @@ -114,33 +132,40 @@ const LoginForm = () => { return; } setSubmitted(true); - if (username && password) { - const res = await API.post( - `/api/user/login?turnstile=${turnstileToken}`, - { - username, - password, - }, - ); - const { success, message, data } = res.data; - if (success) { - userDispatch({ type: 'login', payload: data }); - setUserData(data); - updateAPI(); - showSuccess('登录成功!'); - if (username === 'root' && password === '123456') { - Modal.error({ - title: '您正在使用默认密码!', - content: '请立刻修改默认密码!', - centered: true, - }); + setLoginLoading(true); + try { + if (username && password) { + const res = await API.post( + `/api/user/login?turnstile=${turnstileToken}`, + { + username, + password, + }, + ); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + setUserData(data); + updateAPI(); + showSuccess('登录成功!'); + if (username === 'root' && password === '123456') { + Modal.error({ + title: '您正在使用默认密码!', + content: '请立刻修改默认密码!', + centered: true, + }); + } + navigate('/console'); + } else { + showError(message); } - navigate('/console'); } else { - showError(message); + showError('请输入用户名和密码!'); } - } else { - showError('请输入用户名和密码!'); + } catch (error) { + showError('登录失败,请重试'); + } finally { + setLoginLoading(false); } } @@ -162,20 +187,81 @@ const LoginForm = () => { params[field] = response[field]; } }); - const res = await API.get(`/api/oauth/telegram/login`, { params }); - const { success, message, data } = res.data; - if (success) { - userDispatch({ type: 'login', payload: data }); - localStorage.setItem('user', JSON.stringify(data)); - showSuccess('登录成功!'); - setUserData(data); - updateAPI(); - navigate('/'); - } else { - showError(message); + try { + const res = await API.get(`/api/oauth/telegram/login`, { params }); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + showSuccess('登录成功!'); + setUserData(data); + updateAPI(); + navigate('/'); + } else { + showError(message); + } + } catch (error) { + showError('登录失败,请重试'); } }; + // 包装的GitHub登录点击处理 + const handleGitHubClick = () => { + setGithubLoading(true); + try { + onGitHubOAuthClicked(status.github_client_id); + } finally { + // 由于重定向,这里不会执行到,但为了完整性添加 + setTimeout(() => setGithubLoading(false), 3000); + } + }; + + // 包装的OIDC登录点击处理 + const handleOIDCClick = () => { + setOidcLoading(true); + try { + onOIDCClicked( + status.oidc_authorization_endpoint, + status.oidc_client_id + ); + } finally { + // 由于重定向,这里不会执行到,但为了完整性添加 + setTimeout(() => setOidcLoading(false), 3000); + } + }; + + // 包装的LinuxDO登录点击处理 + const handleLinuxDOClick = () => { + setLinuxdoLoading(true); + try { + onLinuxDOOAuthClicked(status.linuxdo_client_id); + } finally { + // 由于重定向,这里不会执行到,但为了完整性添加 + setTimeout(() => setLinuxdoLoading(false), 3000); + } + }; + + // 包装的邮箱登录选项点击处理 + const handleEmailLoginClick = () => { + setEmailLoginLoading(true); + setShowEmailLogin(true); + setEmailLoginLoading(false); + }; + + // 包装的重置密码点击处理 + const handleResetPasswordClick = () => { + setResetPasswordLoading(true); + navigate('/reset'); + setResetPasswordLoading(false); + }; + + // 包装的其他登录选项点击处理 + const handleOtherLoginOptionsClick = () => { + setOtherLoginOptionsLoading(true); + setShowEmailLogin(false); + setOtherLoginOptionsLoading(false); + }; + const renderOAuthOptions = () => { return (
@@ -199,6 +285,7 @@ const LoginForm = () => { icon={} style={{ color: '#07C160' }} />} size="large" onClick={onWeChatLoginClicked} + loading={wechatLoading} > {t('使用 微信 继续')} @@ -211,7 +298,8 @@ const LoginForm = () => { type="tertiary" icon={} size="large" - onClick={() => onGitHubOAuthClicked(status.github_client_id)} + onClick={handleGitHubClick} + loading={githubLoading} > {t('使用 GitHub 继续')} @@ -224,12 +312,8 @@ const LoginForm = () => { type="tertiary" icon={} size="large" - onClick={() => - onOIDCClicked( - status.oidc_authorization_endpoint, - status.oidc_client_id - ) - } + onClick={handleOIDCClick} + loading={oidcLoading} > {t('使用 OIDC 继续')} @@ -242,7 +326,8 @@ const LoginForm = () => { type="tertiary" icon={} size="large" - onClick={() => onLinuxDOOAuthClicked(status.linuxdo_client_id)} + onClick={handleLinuxDOClick} + loading={linuxdoLoading} > {t('使用 LinuxDO 继续')} @@ -267,7 +352,8 @@ const LoginForm = () => { className="w-full h-12 flex items-center justify-center bg-black text-white !rounded-full hover:bg-gray-800 transition-colors" icon={} size="large" - onClick={() => setShowEmailLogin(true)} + onClick={handleEmailLoginClick} + loading={emailLoginLoading} > {t('使用 邮箱 登录')} @@ -340,6 +426,7 @@ const LoginForm = () => { htmlType="submit" size="large" onClick={handleSubmit} + loading={loginLoading} > {t('继续')} @@ -349,7 +436,8 @@ const LoginForm = () => { type='tertiary' className="w-full !rounded-full" size="large" - onClick={() => navigate('/reset')} + onClick={handleResetPasswordClick} + loading={resetPasswordLoading} > {t('忘记密码?')} @@ -366,7 +454,8 @@ const LoginForm = () => { type="tertiary" className="w-full !rounded-full" size="large" - onClick={() => setShowEmailLogin(false)} + onClick={handleOtherLoginOptionsClick} + loading={otherLoginOptionsLoading} > {t('其他登录选项')} @@ -390,6 +479,9 @@ const LoginForm = () => { okText={t('登录')} size="small" centered={true} + okButtonProps={{ + loading: wechatCodeSubmitLoading, + }} >
微信二维码 diff --git a/web/src/components/RegisterForm.js b/web/src/components/RegisterForm.js index 405f59a2..c0368caa 100644 --- a/web/src/components/RegisterForm.js +++ b/web/src/components/RegisterForm.js @@ -55,6 +55,15 @@ const RegisterForm = () => { const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false); const [showEmailRegister, setShowEmailRegister] = useState(false); const [status, setStatus] = useState({}); + const [wechatLoading, setWechatLoading] = useState(false); + const [githubLoading, setGithubLoading] = useState(false); + const [oidcLoading, setOidcLoading] = useState(false); + const [linuxdoLoading, setLinuxdoLoading] = useState(false); + const [emailRegisterLoading, setEmailRegisterLoading] = useState(false); + const [registerLoading, setRegisterLoading] = useState(false); + const [verificationCodeLoading, setVerificationCodeLoading] = useState(false); + const [otherRegisterOptionsLoading, setOtherRegisterOptionsLoading] = useState(false); + const [wechatCodeSubmitLoading, setWechatCodeSubmitLoading] = useState(false); let navigate = useNavigate(); const logo = getLogo(); @@ -79,7 +88,9 @@ const RegisterForm = () => { }, []); const onWeChatLoginClicked = () => { + setWechatLoading(true); setShowWeChatLoginModal(true); + setWechatLoading(false); }; const onSubmitWeChatVerificationCode = async () => { @@ -87,20 +98,27 @@ const RegisterForm = () => { showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); return; } - const res = await API.get( - `/api/oauth/wechat?code=${inputs.wechat_verification_code}`, - ); - const { success, message, data } = res.data; - if (success) { - userDispatch({ type: 'login', payload: data }); - localStorage.setItem('user', JSON.stringify(data)); - setUserData(data); - updateAPI(); - navigate('/'); - showSuccess('登录成功!'); - setShowWeChatLoginModal(false); - } else { - showError(message); + setWechatCodeSubmitLoading(true); + try { + const res = await API.get( + `/api/oauth/wechat?code=${inputs.wechat_verification_code}`, + ); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + setUserData(data); + updateAPI(); + navigate('/'); + showSuccess('登录成功!'); + setShowWeChatLoginModal(false); + } else { + showError(message); + } + } catch (error) { + showError('登录失败,请重试'); + } finally { + setWechatCodeSubmitLoading(false); } }; @@ -122,23 +140,28 @@ const RegisterForm = () => { showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); return; } - setLoading(true); - if (!affCode) { - affCode = localStorage.getItem('aff'); + setRegisterLoading(true); + try { + if (!affCode) { + affCode = localStorage.getItem('aff'); + } + inputs.aff_code = affCode; + const res = await API.post( + `/api/user/register?turnstile=${turnstileToken}`, + inputs, + ); + const { success, message } = res.data; + if (success) { + navigate('/login'); + showSuccess('注册成功!'); + } else { + showError(message); + } + } catch (error) { + showError('注册失败,请重试'); + } finally { + setRegisterLoading(false); } - inputs.aff_code = affCode; - const res = await API.post( - `/api/user/register?turnstile=${turnstileToken}`, - inputs, - ); - const { success, message } = res.data; - if (success) { - navigate('/login'); - showSuccess('注册成功!'); - } else { - showError(message); - } - setLoading(false); } } @@ -148,17 +171,64 @@ const RegisterForm = () => { showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); return; } - setLoading(true); - const res = await API.get( - `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`, - ); - const { success, message } = res.data; - if (success) { - showSuccess('验证码发送成功,请检查你的邮箱!'); - } else { - showError(message); + setVerificationCodeLoading(true); + try { + const res = await API.get( + `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess('验证码发送成功,请检查你的邮箱!'); + } else { + showError(message); + } + } catch (error) { + showError('发送验证码失败,请重试'); + } finally { + setVerificationCodeLoading(false); } - setLoading(false); + }; + + const handleGitHubClick = () => { + setGithubLoading(true); + try { + onGitHubOAuthClicked(status.github_client_id); + } finally { + setTimeout(() => setGithubLoading(false), 3000); + } + }; + + const handleOIDCClick = () => { + setOidcLoading(true); + try { + onOIDCClicked( + status.oidc_authorization_endpoint, + status.oidc_client_id + ); + } finally { + setTimeout(() => setOidcLoading(false), 3000); + } + }; + + const handleLinuxDOClick = () => { + setLinuxdoLoading(true); + try { + onLinuxDOOAuthClicked(status.linuxdo_client_id); + } finally { + setTimeout(() => setLinuxdoLoading(false), 3000); + } + }; + + const handleEmailRegisterClick = () => { + setEmailRegisterLoading(true); + setShowEmailRegister(true); + setEmailRegisterLoading(false); + }; + + const handleOtherRegisterOptionsClick = () => { + setOtherRegisterOptionsLoading(true); + setShowEmailRegister(false); + setOtherRegisterOptionsLoading(false); }; const onTelegramLoginClicked = async (response) => { @@ -178,17 +248,21 @@ const RegisterForm = () => { params[field] = response[field]; } }); - const res = await API.get(`/api/oauth/telegram/login`, { params }); - const { success, message, data } = res.data; - if (success) { - userDispatch({ type: 'login', payload: data }); - localStorage.setItem('user', JSON.stringify(data)); - showSuccess('登录成功!'); - setUserData(data); - updateAPI(); - navigate('/'); - } else { - showError(message); + try { + const res = await API.get(`/api/oauth/telegram/login`, { params }); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + showSuccess('登录成功!'); + setUserData(data); + updateAPI(); + navigate('/'); + } else { + showError(message); + } + } catch (error) { + showError('登录失败,请重试'); } }; @@ -215,6 +289,7 @@ const RegisterForm = () => { icon={} style={{ color: '#07C160' }} />} size="large" onClick={onWeChatLoginClicked} + loading={wechatLoading} > {t('使用 微信 继续')} @@ -227,7 +302,8 @@ const RegisterForm = () => { type="tertiary" icon={} size="large" - onClick={() => onGitHubOAuthClicked(status.github_client_id)} + onClick={handleGitHubClick} + loading={githubLoading} > {t('使用 GitHub 继续')} @@ -240,12 +316,8 @@ const RegisterForm = () => { type="tertiary" icon={} size="large" - onClick={() => - onOIDCClicked( - status.oidc_authorization_endpoint, - status.oidc_client_id - ) - } + onClick={handleOIDCClick} + loading={oidcLoading} > {t('使用 OIDC 继续')} @@ -258,7 +330,8 @@ const RegisterForm = () => { type="tertiary" icon={} size="large" - onClick={() => onLinuxDOOAuthClicked(status.linuxdo_client_id)} + onClick={handleLinuxDOClick} + loading={linuxdoLoading} > {t('使用 LinuxDO 继续')} @@ -283,7 +356,8 @@ const RegisterForm = () => { className="w-full h-12 flex items-center justify-center bg-black text-white !rounded-full hover:bg-gray-800 transition-colors" icon={} size="large" - onClick={() => setShowEmailRegister(true)} + onClick={handleEmailRegisterClick} + loading={emailRegisterLoading} > {t('使用 邮箱 注册')} @@ -375,7 +449,7 @@ const RegisterForm = () => { suffix={ @@ -420,7 +495,8 @@ const RegisterForm = () => { type="tertiary" className="w-full !rounded-full" size="large" - onClick={() => setShowEmailRegister(false)} + onClick={handleOtherRegisterOptionsClick} + loading={otherRegisterOptionsLoading} > {t('其他注册选项')} @@ -436,7 +512,6 @@ const RegisterForm = () => { ); }; - // 微信登录模态框 const renderWeChatLoginModal = () => { return ( { okText={t('登录')} size="small" centered={true} + okButtonProps={{ + loading: wechatCodeSubmitLoading, + }} >
微信二维码 @@ -472,7 +550,6 @@ const RegisterForm = () => { return (
- {/* 背景图片容器 - 放大并保持居中 */}
{ }} >
- {/* 半透明遮罩层 */}