import { useQuery, useMutation, useQueryClient, keepPreviousData, } from "@tanstack/react-query"; import { useAuthToken } from "~/hooks/use-root-data"; import { getApiBaseUrl } from "~/utils/api-config"; /* ------------------------------------------------------------------ * * Admin panel data hooks (superuser only). The admin API is a new, * fast-moving surface, so these call it directly with fetch (same * pattern as use-seller-hooks' useMyStores) rather than the generated * client. Responses are camelCased by the backend renderer. * ------------------------------------------------------------------ */ export interface AdminOverview { generatedAt: string; users: { total: number; active: number; sellers: number; new7d: number; new30d: number; }; sellers: { total: number; pending: number; approved: number; rejected: number; verified: number; }; products: { total: number; active: number }; orders: { total: number; pending: number; confirmed: number; shipped: number; delivered: number; cancelled: number; paidCount: number; revenueTotal: number; ordersToday: number; revenueToday: number; }; money: { walletHeld: number; sellerPayable: number; payoutsPendingCount: number; payoutsPendingAmount: number; }; ordersSeries: Array<{ date: string; orders: number; revenue: number }>; } async function adminGet(path: string, token: string): Promise { const res = await fetch(`${getApiBaseUrl()}/api/adminpanel/v1/${path}`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { throw new Error(`Admin request failed (${res.status})`); } return res.json(); } async function adminPatch( path: string, body: Record, token: string ): Promise { const res = await fetch(`${getApiBaseUrl()}/api/adminpanel/v1/${path}`, { method: "PATCH", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!res.ok) { throw new Error(`Admin update failed (${res.status})`); } return res.json(); } export function useAdminOverview() { const token = useAuthToken(); return useQuery({ queryKey: ["admin", "overview"], enabled: !!token, refetchInterval: 60_000, queryFn: () => adminGet("overview/", token as string), }); } /* ------------------------------ boards ------------------------------ */ export interface Paginated { count: number; next: string | null; previous: string | null; results: T[]; } export type AdminListParams = Record< string, string | number | boolean | undefined >; function toQuery(params: AdminListParams): string { const usp = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== "" && v !== null) usp.set(k, String(v)); }); const s = usp.toString(); return s ? `?${s}` : ""; } function useAdminList(key: string, params: AdminListParams) { const token = useAuthToken(); return useQuery({ queryKey: ["admin", key, params], enabled: !!token, placeholderData: keepPreviousData, queryFn: () => adminGet>(`${key}/${toQuery(params)}`, token as string), }); } export interface AdminOrderRow { id: string; createdAt: string; status: string; paymentStatus: string; totalPrice: number; userPhone: string | null; userName: string | null; sellerName: string | null; sellerUsername: string | null; } export interface AdminSellerRow { id: string; username: string; storeName: string; storeLogo: string | null; status: string; isVerified: boolean; platformFee: number; productCount: number; followerCount: number; reviewAverage: number; createdAt: string; userPhone: string | null; } export interface AdminUserRow { id: string; phoneNumber: string; username: string | null; email: string | null; isActive: boolean; isSeller: boolean; isStaff: boolean; isSuperuser: boolean; otpVerified: boolean; createdAt: string; } export interface AdminProductRow { id: string; title: string; basePrice: number; isActive: boolean; createdAt: string; sellerName: string | null; sellerUsername: string | null; categoryName: string | null; primaryImage: string | null; } export const useAdminOrders = (params: AdminListParams) => useAdminList("orders", params); export const useAdminSellers = (params: AdminListParams) => useAdminList("sellers", params); export const useAdminUsers = (params: AdminListParams) => useAdminList("users", params); export const useAdminProducts = (params: AdminListParams) => useAdminList("products", params); /* ----------------------------- actions ------------------------------ */ function useAdminUpdate(resource: "sellers" | "users" | "products") { const token = useAuthToken(); const qc = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Record }) => adminPatch(`${resource}/${id}/`, data, token as string), onSuccess: () => { qc.invalidateQueries({ queryKey: ["admin", resource] }); qc.invalidateQueries({ queryKey: ["admin", "overview"] }); }, }); } export const useAdminSellerUpdate = () => useAdminUpdate("sellers"); export const useAdminUserUpdate = () => useAdminUpdate("users"); export const useAdminProductUpdate = () => useAdminUpdate("products");