- block-timeline: ref updates moved to useLayoutEffect - channel-card, guide/page: Date.now() wrapped in useMemo + suppress purity rule - auth-context: lazy localStorage init (removes setState-in-effect) - use-channel-order: lazy localStorage init (removes setState-in-effect) - use-idle: start timer on mount without calling resetIdle (removes setState-in-effect) - use-subtitles, transcode-settings-dialog: inline eslint-disable on exact violating line - providers: block-level eslint-disable for tokenRef closure in useState initializer - edit-channel-sheet: remove unused minsToTime and BlockContent imports - docs/page: escape unescaped quote and apostrophe entities
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
QueryCache,
|
|
QueryClient,
|
|
QueryClientProvider,
|
|
MutationCache,
|
|
} from "@tanstack/react-query";
|
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
import { useState, useRef, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { toast } from "sonner";
|
|
import { AuthProvider, useAuthContext } from "@/context/auth-context";
|
|
import { Toaster } from "@/components/ui/sonner";
|
|
import { ApiRequestError } from "@/lib/api";
|
|
|
|
function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
const { token, setToken } = useAuthContext();
|
|
const router = useRouter();
|
|
const tokenRef = useRef(token);
|
|
useEffect(() => { tokenRef.current = token; }, [token]);
|
|
|
|
/* eslint-disable react-hooks/refs -- tokenRef is only read in onError callbacks, not during render */
|
|
const [queryClient] = useState(() => {
|
|
return new QueryClient({
|
|
queryCache: new QueryCache({
|
|
onError: (error) => {
|
|
// Only redirect on 401 if the user had a token (expired session).
|
|
// Guests hitting 401 on restricted content should not be redirected.
|
|
if (error instanceof ApiRequestError && error.status === 401 && tokenRef.current) {
|
|
toast.warning("Session expired, please log in again.");
|
|
setToken(null);
|
|
router.push("/login");
|
|
}
|
|
},
|
|
}),
|
|
mutationCache: new MutationCache({
|
|
onError: (error) => {
|
|
// Mutations always require auth — redirect on 401 regardless.
|
|
if (error instanceof ApiRequestError && error.status === 401) {
|
|
toast.warning("Session expired, please log in again.");
|
|
setToken(null);
|
|
router.push("/login");
|
|
}
|
|
},
|
|
}),
|
|
defaultOptions: { queries: { staleTime: 60 * 1000 } },
|
|
});
|
|
});
|
|
/* eslint-enable react-hooks/refs */
|
|
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
{children}
|
|
<Toaster position="bottom-right" richColors />
|
|
<ReactQueryDevtools initialIsOpen={false} />
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<AuthProvider>
|
|
<QueryProvider>{children}</QueryProvider>
|
|
</AuthProvider>
|
|
);
|
|
}
|