♻️ 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.
This commit is contained in:
@@ -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, count) => {
|
||||
const res = await API.get(
|
||||
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 } = res.data;
|
||||
if (success) {
|
||||
|
||||
const { success, message, data } = resData;
|
||||
|
||||
if (!success) {
|
||||
throw new Error(message || 'OAuth2 callback error');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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">
|
||||
<div className="fixed inset-0 w-screen h-screen flex items-center justify-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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user