Партии: фото в общем комментарии (до 10), загрузка при завершении

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 21:34:39 +03:00
co-authored by Claude Opus 4.8
parent 020c694a44
commit 8ac1b5acef
14 changed files with 790 additions and 11 deletions
+51 -1
View File
@@ -1,8 +1,14 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, unwrap } from "../api/client";
import { ApiError, api, unwrap } from "../api/client";
import { 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;
}
export function useMatch(matchId: number | null) {
return useQuery({
@@ -73,3 +79,47 @@ export function useDeleteMatch() {
onSuccess: () => qc.invalidateQueries(),
});
}
// Медиа партии: фото грузим multipart'ом отдельным fetch (с ресайзом и CSRF из cookie).
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();
},
onSuccess: () => qc.invalidateQueries({ queryKey: qk.match(matchId) }),
});
}
export function useDeleteMatchAttachment(matchId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (attachmentId: number) =>
unwrap(
await api.DELETE("/api/matches/{match_id}/attachments/{attachment_id}", {
params: { path: { match_id: matchId, attachment_id: attachmentId } },
}),
),
onSuccess: () => qc.invalidateQueries({ queryKey: qk.match(matchId) }),
});
}