crush-level-web/src/lib/providers.tsx

133 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}