Browse Source

♻️ refactor(auth, ui): simplify Loading component & optimize OAuth2Callback flow

* Removed `prompt` prop from `Loading` and switched to built-in Spin indicator with default size `small`
* Dropped overlay background to make the spinner more reusable
* Replaced custom text span; callers can now supply tip via their own UI if needed
* Cleaned up `OAuth2Callback`:
  - Eliminated unused state/variables
  - Added MAX_RETRIES with incremental back-off
  - Centralized error handling via try/catch
  - Streamlined navigation logic on success/failure
  - Updated imports to match new Loading signature

BREAKING CHANGE: `Loading` no longer accepts a `prompt` prop. Update all invocations accordingly.
t0ng7u 7 months ago
parent
commit
bbc5584f80

+ 39 - 26
web/src/components/auth/OAuth2Callback.js

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useContext, useEffect } from 'react';
 import { useNavigate, useSearchParams } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
 import { API, showError, showSuccess, updateAPI, setUserData } from '../../helpers';
@@ -7,22 +7,28 @@ import Loading from '../common/Loading';
 
 const OAuth2Callback = (props) => {
   const { t } = useTranslation();
-  const [searchParams, setSearchParams] = useSearchParams();
+  const [searchParams] = useSearchParams();
+  const [, userDispatch] = useContext(UserContext);
+  const navigate = useNavigate();
 
-  const [userState, userDispatch] = useContext(UserContext);
-  const [prompt, setPrompt] = useState(t('处理中...'));
+  // 最大重试次数
+  const MAX_RETRIES = 3;
 
-  let navigate = useNavigate();
+  const sendCode = async (code, state, retry = 0) => {
+    try {
+      const { data: resData } = await API.get(
+        `/api/oauth/${props.type}?code=${code}&state=${state}`,
+      );
+
+      const { success, message, data } = resData;
+
+      if (!success) {
+        throw new Error(message || 'OAuth2 callback error');
+      }
 
-  const sendCode = async (code, state, count) => {
-    const res = await API.get(
-      `/api/oauth/${props.type}?code=${code}&state=${state}`,
-    );
-    const { success, message, data } = res.data;
-    if (success) {
       if (message === 'bind') {
         showSuccess(t('绑定成功!'));
-        navigate('/console/setting');
+        navigate('/console/personal');
       } else {
         userDispatch({ type: 'login', payload: data });
         localStorage.setItem('user', JSON.stringify(data));
@@ -31,27 +37,34 @@ const OAuth2Callback = (props) => {
         showSuccess(t('登录成功!'));
         navigate('/console/token');
       }
-    } else {
-      showError(message);
-      if (count === 0) {
-        setPrompt(t('操作失败,重定向至登录界面中...'));
-        navigate('/console/setting'); // in case this is failed to bind GitHub
-        return;
+    } catch (error) {
+      if (retry < MAX_RETRIES) {
+        // 递增的退避等待
+        await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 2000));
+        return sendCode(code, state, retry + 1);
       }
-      count++;
-      setPrompt(t('出现错误,第 ${count} 次重试中...', { count }));
-      await new Promise((resolve) => setTimeout(resolve, count * 2000));
-      await sendCode(code, state, count);
+
+      // 重试次数耗尽,提示错误并返回设置页面
+      showError(error.message || t('授权失败'));
+      navigate('/console/personal');
     }
   };
 
   useEffect(() => {
-    let code = searchParams.get('code');
-    let state = searchParams.get('state');
-    sendCode(code, state, 0).then();
+    const code = searchParams.get('code');
+    const state = searchParams.get('state');
+
+    // 参数缺失直接返回
+    if (!code) {
+      showError(t('未获取到授权码'));
+      navigate('/console/personal');
+      return;
+    }
+
+    sendCode(code, state);
   }, []);
 
-  return <Loading prompt={prompt} />;
+  return <Loading />;
 };
 
 export default OAuth2Callback;

+ 6 - 14
web/src/components/common/Loading.js

@@ -1,22 +1,14 @@
 import React from 'react';
 import { Spin } from '@douyinfe/semi-ui';
-import { useTranslation } from 'react-i18next';
 
-const Loading = ({ prompt: name = '', size = 'large' }) => {
-  const { t } = useTranslation();
+const Loading = ({ size = 'small' }) => {
 
   return (
-    <div className="fixed inset-0 w-screen h-screen flex items-center justify-center bg-white/80 z-[1000]">
-      <div className="flex flex-col items-center">
-        <Spin
-          size={size}
-          spinning={true}
-          tip={null}
-        />
-        <span className="whitespace-nowrap mt-2 text-center" style={{ color: 'var(--semi-color-primary)' }}>
-          {name ? t('{{name}}', { name }) : t('加载中...')}
-        </span>
-      </div>
+    <div className="fixed inset-0 w-screen h-screen flex items-center justify-center">
+      <Spin
+        size={size}
+        spinning={true}
+      />
     </div>
   );
 };

+ 0 - 2
web/src/i18n/locales/en.json

@@ -179,7 +179,6 @@
   "注销": "Logout",
   "登录": "Sign in",
   "注册": "Sign up",
-  "加载{name}中...": "Loading {name}...",
   "未登录或登录已过期,请重新登录!": "Not logged in or session expired. Please login again!",
   "用户登录": "User Login",
   "密码": "Password",
@@ -933,7 +932,6 @@
   "更新令牌后需等待几分钟生效": "It will take a few minutes to take effect after updating the token.",
   "一小时": "One hour",
   "新建数量": "New quantity",
-  "加载失败,请稍后重试": "Loading failed, please try again later",
   "未设置": "Not set",
   "API文档": "API documentation",
   "不是合法的 JSON 字符串": "Not a valid JSON string",