Админ: управление медиа партии (добавление/удаление в любой момент)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 21:38:06 +03:00
co-authored by Claude Opus 4.8
parent 8ac1b5acef
commit 043595beff
5 changed files with 362 additions and 4 deletions
+59
View File
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ApiError, api, unwrap } from "../api/client";
import { qk } from "../api/queryKeys";
import type { AdminMe, MatchUpdate } from "../domain/types";
import { resizeImage } from "../lib/image";
function readCsrfToken(): string | null {
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]*)/);
@@ -87,6 +88,64 @@ export function useAdminMatch(matchId: number | null) {
});
}
// ─── Медиа партии (админ правит в любой момент) ──────────────────────────────
export function useAdminMatchAttachments(matchId: number | null) {
return useQuery({
queryKey: ["adminMatchAttachments", matchId],
enabled: matchId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/admin/matches/{match_id}/attachments", {
params: { path: { match_id: matchId as number } },
}),
),
});
}
export function useAdminUploadAttachment(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/admin/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: ["adminMatchAttachments", matchId] }),
});
}
export function useAdminDeleteAttachment(matchId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (attachmentId: number) =>
unwrap(
await api.DELETE("/api/admin/matches/{match_id}/attachments/{attachment_id}", {
params: { path: { match_id: matchId, attachment_id: attachmentId } },
}),
),
onSuccess: () => qc.invalidateQueries({ queryKey: ["adminMatchAttachments", matchId] }),
});
}
export function useAdminUpdateMatch() {
const qc = useQueryClient();
return useMutation({