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"; function readCsrfToken(): string | null { const m = document.cookie.match(/(?:^|; )csrf_token=([^;]*)/); return m ? decodeURIComponent(m[1]) : null; } export function useAdminMe() { return useQuery({ queryKey: qk.adminMe, queryFn: async (): Promise => { const r = await api.GET("/api/admin/me"); if (r.response.status === 401 || r.response.status === 403) return null; return unwrap(r); }, }); } export function useAdminLogin() { const qc = useQueryClient(); return useMutation({ mutationFn: async (body: { username: string; password: string }) => unwrap(await api.POST("/api/admin/auth/login", { body })), onSuccess: (me) => qc.setQueryData(qk.adminMe, me), }); } export function useAdminLogout() { const qc = useQueryClient(); return useMutation({ mutationFn: async () => unwrap(await api.POST("/api/admin/auth/logout")), onSuccess: () => qc.setQueryData(qk.adminMe, null), }); } export function useAdminUsers(query?: string) { return useQuery({ queryKey: [...qk.adminUsers, query ?? ""], queryFn: async () => unwrap( await api.GET("/api/admin/users", { params: { query: query ? { query } : {} }, }), ), }); } export function useAdminGroups() { return useQuery({ queryKey: qk.adminGroups, queryFn: async () => unwrap(await api.GET("/api/admin/groups")), }); } export interface AdminMatchFilters { group_id?: number | null; user_id?: number | null; faction_id?: number | null; } export function useAdminMatches(filters: AdminMatchFilters = {}) { const query: Record = {}; if (filters.group_id != null) query.group_id = filters.group_id; if (filters.user_id != null) query.user_id = filters.user_id; if (filters.faction_id != null) query.faction_id = filters.faction_id; return useQuery({ queryKey: [...qk.adminMatches, query], queryFn: async () => unwrap(await api.GET("/api/admin/matches", { params: { query } })), }); } export function useAdminMatch(matchId: number | null) { return useQuery({ queryKey: ["adminMatch", matchId], enabled: matchId != null, queryFn: async () => unwrap( await api.GET("/api/admin/matches/{match_id}", { params: { path: { match_id: matchId as number } }, }), ), }); } export function useAdminUpdateMatch() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { matchId: number; body: MatchUpdate }) => unwrap( await api.PATCH("/api/admin/matches/{match_id}", { params: { path: { match_id: args.matchId } }, body: args.body, }), ), onSuccess: (_d, args) => { qc.invalidateQueries({ queryKey: qk.adminMatches }); qc.invalidateQueries({ queryKey: ["adminMatch", args.matchId] }); }, }); } export function useAdminFactions() { return useQuery({ queryKey: ["adminFactions"], queryFn: async () => unwrap(await api.GET("/api/admin/factions")), }); } export function useAdminRenameFaction() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { factionId: number; name_ru: string }) => unwrap( await api.PATCH("/api/admin/factions/{faction_id}", { params: { path: { faction_id: args.factionId } }, body: { name_ru: args.name_ru }, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: ["adminFactions"] }), }); } export function useAdminLogs(filters?: { action?: string; entity_type?: string }) { return useQuery({ queryKey: [...qk.adminLogs, filters?.action ?? "", filters?.entity_type ?? ""], queryFn: async () => unwrap( await api.GET("/api/admin/audit-logs", { params: { query: { ...(filters?.action ? { action: filters.action } : {}), ...(filters?.entity_type ? { entity_type: filters.entity_type } : {}), }, }, }), ), }); } export function useAdminDeleteMatch() { const qc = useQueryClient(); return useMutation({ mutationFn: async (matchId: number) => unwrap( await api.DELETE("/api/admin/matches/{match_id}", { params: { path: { match_id: matchId } }, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminMatches }), }); } export function useAdminUpdateUser() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { userId: number; nickname?: string; is_active?: boolean }) => unwrap( await api.PATCH("/api/admin/users/{user_id}", { params: { path: { user_id: args.userId } }, body: { nickname: args.nickname, is_active: args.is_active }, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminUsers }), }); } // ─── Ачивки (редактор; выдача игрокам — на будущее) ────────────────────────── interface AchievementInput { name?: string; description?: string; condition?: string; } export function useAdminAchievements() { return useQuery({ queryKey: qk.adminAchievements, queryFn: async () => unwrap(await api.GET("/api/admin/achievements")), }); } export function useCreateAchievement() { const qc = useQueryClient(); return useMutation({ mutationFn: async (body: AchievementInput & { name: string }) => unwrap(await api.POST("/api/admin/achievements", { body })), onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminAchievements }), }); } export function useUpdateAchievement() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { slug: string; body: AchievementInput }) => unwrap( await api.PATCH("/api/admin/achievements/{slug}", { params: { path: { slug: args.slug } }, body: args.body, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminAchievements }), }); } export function useDeleteAchievement() { const qc = useQueryClient(); return useMutation({ mutationFn: async (slug: string) => unwrap( await api.DELETE("/api/admin/achievements/{slug}", { params: { path: { slug } }, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminAchievements }), }); } // Иконка ачивки — multipart отдельным fetch (как аватар), с CSRF из cookie. export function useUploadAchievementIcon() { const qc = useQueryClient(); return useMutation({ mutationFn: async ({ slug, file }: { slug: string; file: File }) => { const form = new FormData(); form.append("file", file, file.name); const csrf = readCsrfToken(); const base = import.meta.env.VITE_API_BASE_URL || ""; const r = await fetch(`${base}/api/admin/achievements/${slug}/icon`, { method: "PUT", 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.adminAchievements }), }); }