Simplify: общий загрузчик файлов и единые ключи на фронте
- Четыре копии «ресайз → FormData → CSRF из cookie → fetch → разбор конверта ошибки» (аватар, фото партии, фото из админки, иконка ачивки) сведены в lib/upload.ts. Иконка по-прежнему уходит оригиналом: ресайз в JPEG убил бы прозрачность герба. - Три копии readCsrfToken и вторая копия resizeImage удалены — берём readCookie из api/client.ts и resizeImage из lib/image.ts с параметром размера. - Ключи, протухающие от партии, собраны в matchAffectedKeys: раньше они были написаны строками мимо реестра qk в двух местах, и переименование ключа сломало бы инвалидацию молча. - Различение «нет сессии» и «нет связи» вынесено в authProbeRetry и применено к обеим пробам. Прошлый заход чинил только игроцкие гварды, и RequireAdmin по-прежнему выкидывал админа на страницу входа при обрыве связи. - ToastContext больше не пересоздаёт значение контекста: провайдер обёрнут вокруг всего приложения, и каждый тост перерисовывал всех потребителей. - PlaceEditor не трогает DOM, пока цель подсветки не изменилась (было querySelectorAll на каждый pointermove). #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ApiError, api, unwrap } from "../api/client";
|
||||
import { qk } from "../api/queryKeys";
|
||||
import { api, unwrap } from "../api/client";
|
||||
import { matchAffectedKeys, qk } from "../api/queryKeys";
|
||||
import type { FactionRead, MatchCreate, MatchFinish, MatchRead } from "../domain/types";
|
||||
import { resizeImage } from "../lib/image";
|
||||
|
||||
function readCsrfToken(): string | null {
|
||||
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]*)/);
|
||||
return m ? decodeURIComponent(m[1]) : null;
|
||||
}
|
||||
import { uploadFile } from "../lib/upload";
|
||||
|
||||
export function useMatch(matchId: number | null) {
|
||||
return useQuery({
|
||||
@@ -62,12 +57,7 @@ export function useFinishMatch() {
|
||||
qc.invalidateQueries({ queryKey: qk.match(m.id) });
|
||||
qc.invalidateQueries({ queryKey: qk.groupMatches(m.group_id) });
|
||||
qc.invalidateQueries({ queryKey: qk.groupStats(m.group_id) });
|
||||
qc.invalidateQueries({ queryKey: qk.leaderboard });
|
||||
qc.invalidateQueries({ queryKey: qk.home });
|
||||
qc.invalidateQueries({ queryKey: qk.myStats });
|
||||
// Завершённая партия попадает в историю игр и в публичные профили участников.
|
||||
qc.invalidateQueries({ queryKey: ["userMatches"] });
|
||||
qc.invalidateQueries({ queryKey: ["publicProfile"] });
|
||||
for (const key of matchAffectedKeys) qc.invalidateQueries({ queryKey: key });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -93,29 +83,8 @@ export function useDeleteMatch() {
|
||||
export function useUploadMatchAttachment(matchId: number) {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
const blob = await resizeImage(file);
|
||||
const form = new FormData();
|
||||
form.append("file", blob, "photo.jpg");
|
||||
const csrf = readCsrfToken();
|
||||
const base = import.meta.env.VITE_API_BASE_URL || "";
|
||||
const r = await fetch(`${base}/api/matches/${matchId}/attachments`, {
|
||||
method: "POST",
|
||||
body: form,
|
||||
credentials: "include",
|
||||
headers: csrf ? { "X-CSRF-Token": csrf } : {},
|
||||
});
|
||||
if (!r.ok) {
|
||||
let env: { code?: string; message?: string } | undefined;
|
||||
try {
|
||||
env = ((await r.json()) as { error?: typeof env }).error;
|
||||
} catch {
|
||||
/* тело без JSON */
|
||||
}
|
||||
throw new ApiError(env?.message || "Не удалось загрузить", env?.code || "ERROR", r.status);
|
||||
}
|
||||
return await r.json();
|
||||
},
|
||||
mutationFn: async (file: File) =>
|
||||
uploadFile(`/api/matches/${matchId}/attachments`, file),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: qk.match(matchId) }),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user