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
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:
parent
59c072fc4c
commit
5162659277
3 changed files with 24 additions and 5 deletions
|
|
@ -20,7 +20,6 @@ import { useRouter } from "next/navigation";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiFetch, HTTPError } from "@/lib/api";
|
import { apiFetch, HTTPError } from "@/lib/api";
|
||||||
import { ME_QUERY_KEY } from "@/lib/useMe";
|
|
||||||
import { AnonSupportWidget } from "@/components/auth/AnonSupportWidget";
|
import { AnonSupportWidget } from "@/components/auth/AnonSupportWidget";
|
||||||
|
|
||||||
interface LoginInput {
|
interface LoginInput {
|
||||||
|
|
@ -140,7 +139,11 @@ export default function LoginPage() {
|
||||||
const loginMutation = useMutation({
|
const loginMutation = useMutation({
|
||||||
mutationFn: loginRequest,
|
mutationFn: loginRequest,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ME_QUERY_KEY });
|
// #2567: вход под другим юзером без предварительного logout (тот же
|
||||||
|
// девайс/вкладка) — снимаем ВЕСЬ кэш, не только /me, иначе
|
||||||
|
// team/employees, история оценок и т.п. предыдущего юзера переживают
|
||||||
|
// login и могут отрисоваться до первого рефетча.
|
||||||
|
queryClient.clear();
|
||||||
router.push(sanitizeNext(readNextParam()));
|
router.push(sanitizeNext(readNextParam()));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
* задвоил бы префикс в `/trade-in/trade-in/history`.
|
* задвоил бы префикс в `/trade-in/trade-in/history`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
|
@ -62,6 +63,7 @@ export default function GuardedRoute({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
// Абсолютный путь сайта: BASE_PATH + rawPath. Аккуратно с двойным слэшем
|
// Абсолютный путь сайта: BASE_PATH + rawPath. Аккуратно с двойным слэшем
|
||||||
// на `/`: `BASE_PATH = "/trade-in"` + `"/"` → `/trade-in/` (ок).
|
// на `/`: `BASE_PATH = "/trade-in"` + `"/"` → `/trade-in/` (ок).
|
||||||
const absolutePath = BASE_PATH
|
const absolutePath = BASE_PATH
|
||||||
|
|
@ -85,6 +87,13 @@ export default function GuardedRoute({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldRedirectToLogin) return;
|
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=
|
// PR #2562 review finding 3: deep-links carry их state в query (`/v2?id=
|
||||||
// <uuid>` — см. next.config.ts redirect comment про restore-by-id). Без
|
// <uuid>` — см. next.config.ts redirect comment про restore-by-id). Без
|
||||||
// `window.location.search` юзер, чья сессия истекла mid-session на такой
|
// `window.location.search` юзер, чья сессия истекла mid-session на такой
|
||||||
|
|
@ -93,7 +102,7 @@ export default function GuardedRoute({
|
||||||
// поэтому `window` тут безопасен без typeof-guard.
|
// поэтому `window` тут безопасен без typeof-guard.
|
||||||
const next = `${rawPath}${window.location.search}`;
|
const next = `${rawPath}${window.location.search}`;
|
||||||
router.push(`/login?next=${encodeURIComponent(next)}`);
|
router.push(`/login?next=${encodeURIComponent(next)}`);
|
||||||
}, [shouldRedirectToLogin, rawPath, router]);
|
}, [shouldRedirectToLogin, rawPath, router, queryClient]);
|
||||||
|
|
||||||
// #801: preview-страница самодостаточна (свой QueryClient с фейковым me),
|
// #801: preview-страница самодостаточна (свой QueryClient с фейковым me),
|
||||||
// RBAC к ней не применяем. Только под флагом — в проде по умолчанию выключено.
|
// RBAC к ней не применяем. Только под флагом — в проде по умолчанию выключено.
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,20 @@
|
||||||
* локальный logout (кэш + редирект) выполняется в `onSettled`, а не только
|
* локальный logout (кэш + редирект) выполняется в `onSettled`, а не только
|
||||||
* `onSuccess`: сетевой сбой / уже-протухшая сессия не должны запирать юзера
|
* `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 { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { ME_QUERY_KEY } from "@/lib/useMe";
|
|
||||||
|
|
||||||
async function logoutRequest(): Promise<void> {
|
async function logoutRequest(): Promise<void> {
|
||||||
await apiFetch<{ ok: boolean }>("/api/v1/auth/logout", { method: "POST" });
|
await apiFetch<{ ok: boolean }>("/api/v1/auth/logout", { method: "POST" });
|
||||||
|
|
@ -33,7 +40,7 @@ export function useLogout() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: logoutRequest,
|
mutationFn: logoutRequest,
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ME_QUERY_KEY });
|
queryClient.clear();
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue