133 lines
4.1 KiB
TypeScript
133 lines
4.1 KiB
TypeScript
'use client';
|
||
import { ApiError } from '@/types/api';
|
||
import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||
import React, { useState, useRef, type ReactNode } from 'react';
|
||
import { toast, Toaster } from 'sonner';
|
||
import { tokenManager } from './auth/token';
|
||
import { COIN_INSUFFICIENT_ERROR_CODE } from '@/hooks/useWallet';
|
||
import { walletKeys } from './query-keys';
|
||
interface ProvidersProps {
|
||
children: ReactNode;
|
||
}
|
||
|
||
// const ReactQueryDevtoolsProduction = React.lazy(() =>
|
||
// import('@tanstack/react-query-devtools/build/modern/production.js').then((d) => ({
|
||
// default: d.ReactQueryDevtools,
|
||
// }))
|
||
// );
|
||
|
||
const EXPIRED_ERROR_CODES = [
|
||
'10050001',
|
||
'10050002',
|
||
'10050003',
|
||
'10050004',
|
||
'10050005',
|
||
'10050006',
|
||
];
|
||
|
||
export function Providers({ children }: ProvidersProps) {
|
||
const router = useRouter();
|
||
// 用于错误去重的引用
|
||
const errorTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||
const lastErrorRef = useRef<string>('');
|
||
|
||
const [queryClient] = useState(
|
||
() =>
|
||
new QueryClient({
|
||
defaultOptions: {
|
||
queries: {
|
||
// 全局查询配置
|
||
staleTime: 5 * 60 * 1000, // 5分钟后数据过期
|
||
gcTime: 10 * 60 * 1000, // 10分钟后清除缓存
|
||
refetchOnWindowFocus: false, // 窗口聚焦时不重新请求
|
||
retry: 0, // 失败重试次数
|
||
},
|
||
mutations: {
|
||
// 全局修改配置
|
||
retry: 0,
|
||
},
|
||
},
|
||
queryCache: new QueryCache({
|
||
onError: (error) => {
|
||
handleError(error as ApiError);
|
||
},
|
||
}),
|
||
mutationCache: new MutationCache({
|
||
onError: (error) => {
|
||
handleError(error as ApiError);
|
||
},
|
||
}),
|
||
})
|
||
);
|
||
const pathname = usePathname();
|
||
const searchParams = useSearchParams();
|
||
|
||
const searchParamsString = searchParams.toString();
|
||
const redirectURL = `${pathname}${searchParamsString ? `?${searchParamsString}` : ''}`;
|
||
|
||
const handleError = (error: ApiError) => {
|
||
if (error.errorCode === COIN_INSUFFICIENT_ERROR_CODE) {
|
||
queryClient.invalidateQueries({ queryKey: walletKeys.getWalletBalance() });
|
||
}
|
||
if (EXPIRED_ERROR_CODES.includes(error.errorCode)) {
|
||
// TODO: 清除 cookie 中的 st
|
||
// tokenManager.removeToken();
|
||
// router.push('/login?redirect=' + encodeURIComponent(redirectURL));
|
||
return; // 对于登录过期错误,不显示错误toast,直接跳转
|
||
}
|
||
if (error.ignoreError) {
|
||
return;
|
||
}
|
||
|
||
// 错误去重逻辑:只显示最后一次的错误
|
||
const errorKey = `${error.errorCode}:${error.errorMsg}`;
|
||
|
||
// 清除之前的定时器
|
||
if (errorTimeoutRef.current) {
|
||
clearTimeout(errorTimeoutRef.current);
|
||
}
|
||
|
||
// 更新最后一次错误信息
|
||
lastErrorRef.current = errorKey;
|
||
|
||
// 设置新的定时器,延迟显示错误
|
||
errorTimeoutRef.current = setTimeout(() => {
|
||
// 只有当前错误仍然是最后一次错误时才显示
|
||
if (lastErrorRef.current === errorKey) {
|
||
toast.error(error.errorMsg);
|
||
}
|
||
}, 100); // 100ms 延迟,确保能捕获到快速连续的错误
|
||
};
|
||
|
||
return (
|
||
<QueryClientProvider client={queryClient}>
|
||
{children}
|
||
<Toaster
|
||
position="top-center"
|
||
richColors
|
||
// closeButton=
|
||
visibleToasts={1}
|
||
duration={4000}
|
||
// duration={2000000}
|
||
icons={{
|
||
error: <img src="/icons/status-error.svg" />,
|
||
success: <img src="/icons/status-successful.svg" />,
|
||
}}
|
||
toastOptions={{
|
||
className:
|
||
'!bg-surface-base-normal !border-none !px-4 !py-3 !rounded-m !txt-body-m !text-txt-primary-normal',
|
||
}}
|
||
/>
|
||
{/* <ReactQueryDevtools initialIsOpen={true} /> */}
|
||
|
||
{/* {showDevtools && (
|
||
<React.Suspense fallback={null}>
|
||
<ReactQueryDevtoolsProduction />
|
||
</React.Suspense>
|
||
)} */}
|
||
</QueryClientProvider>
|
||
);
|
||
}
|