fix(tradein/ui): чистить TanStack Query cache при смене identity (#2567) (#2651)
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m2s
Deploy Trade-In / deploy (push) Successful in 1m9s

This commit is contained in:
bot-backend 2026-08-05 10:06:31 +00:00
parent 59c072fc4c
commit 5162659277
3 changed files with 24 additions and 5 deletions

View file

@ -20,7 +20,6 @@ import { useRouter } from "next/navigation";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiFetch, HTTPError } from "@/lib/api";
import { ME_QUERY_KEY } from "@/lib/useMe";
import { AnonSupportWidget } from "@/components/auth/AnonSupportWidget";
interface LoginInput {
@ -140,7 +139,11 @@ export default function LoginPage() {
const loginMutation = useMutation({
mutationFn: loginRequest,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ME_QUERY_KEY });
// #2567: вход под другим юзером без предварительного logout (тот же
// девайс/вкладка) — снимаем ВЕСЬ кэш, не только /me, иначе
// team/employees, история оценок и т.п. предыдущего юзера переживают
// login и могут отрисоваться до первого рефетча.
queryClient.clear();
router.push(sanitizeNext(readNextParam()));
},
});

View file

@ -42,6 +42,7 @@
* задвоил бы префикс в `/trade-in/trade-in/history`.
*/
import { useQueryClient } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
@ -62,6 +63,7 @@ export default function GuardedRoute({
children: React.ReactNode;
}) {
const router = useRouter();
const queryClient = useQueryClient();
// Абсолютный путь сайта: BASE_PATH + rawPath. Аккуратно с двойным слэшем
// на `/`: `BASE_PATH = "/trade-in"` + `"/"` → `/trade-in/` (ок).
const absolutePath = BASE_PATH
@ -85,6 +87,13 @@ export default function GuardedRoute({
useEffect(() => {
if (!shouldRedirectToLogin) return;
// #2567: сессия протухла (401) — снимаем кэш ДО редиректа на /login. Тот
// же самый девайс/вкладка может через секунду залогиниться другим
// юзером; без clear() здесь team/employees, история оценок и т.п.
// предыдущего юзера дожидались бы login-точки (её `clear()` тоже
// сработает, но лучше не полагаться на единственную точку сброса
// 401 — самостоятельный путь смены identity, не только logout/login).
queryClient.clear();
// PR #2562 review finding 3: deep-links carry их state в query (`/v2?id=
// <uuid>` — см. next.config.ts redirect comment про restore-by-id). Без
// `window.location.search` юзер, чья сессия истекла mid-session на такой
@ -93,7 +102,7 @@ export default function GuardedRoute({
// поэтому `window` тут безопасен без typeof-guard.
const next = `${rawPath}${window.location.search}`;
router.push(`/login?next=${encodeURIComponent(next)}`);
}, [shouldRedirectToLogin, rawPath, router]);
}, [shouldRedirectToLogin, rawPath, router, queryClient]);
// #801: preview-страница самодостаточна (свой QueryClient с фейковым me),
// RBAC к ней не применяем. Только под флагом — в проде по умолчанию выключено.

View file

@ -14,13 +14,20 @@
* локальный logout (кэш + редирект) выполняется в `onSettled`, а не только
* `onSuccess`: сетевой сбой / уже-протухшая сессия не должны запирать юзера
* на странице без возможности разлогиниться.
*
* #2567: `clear()`, а не точечный invalidate ME_QUERY_KEY иначе кэш
* остального (team/employees, история оценок, квоты) переживает logout и
* может отрисоваться следующему юзеру той же вкладки до первого рефетча.
* `clear()` безопасен для in-flight запросов: TanStack Query создаёт для
* снятого из кэша ключа новый `Query`-инстанс при следующей подписке, так
* что поздний resolve старого fetch пишет в осиротевший объект, а не в
* видимый кэш.
*/
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { apiFetch } from "@/lib/api";
import { ME_QUERY_KEY } from "@/lib/useMe";
async function logoutRequest(): Promise<void> {
await apiFetch<{ ok: boolean }>("/api/v1/auth/logout", { method: "POST" });
@ -33,7 +40,7 @@ export function useLogout() {
return useMutation({
mutationFn: logoutRequest,
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ME_QUERY_KEY });
queryClient.clear();
router.push("/login");
},
});