Add user moderation, matches, and photo review pages

- New "همه کاربران" page: searchable/paginated full user list with
  block/unblock/report actions and report-count badges.
- User detail page gained block/unblock/report buttons, a report history
  section, and a recent-activity panel (via the backend's per-user
  monitoring filter).
- New "رفقا" page listing every match across the app.
- New "نظارت بر عکس‌ها" page: grid of every uploaded photo with uploader
  info and a delete action, for content review without going profile-by-profile.
This commit is contained in:
2026-09-20 05:30:33 +03:30
parent 3480eac2b0
commit 35830189ea
7 changed files with 783 additions and 3 deletions
+107 -2
View File
@@ -3,6 +3,15 @@ import { tokenStore } from "./token-store";
export const API_BASE =
process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080/api/v1";
// عکس‌ها از مسیر نسبی (/uploads/...) برمی‌گردن، نه URL کامل؛ باید به origin
// سرور (بدون /api/v1) وصل بشن.
export const API_ORIGIN = API_BASE.replace(/\/api\/v1\/?$/, "");
export function photoUrl(path: string) {
if (!path) return "";
if (path.startsWith("http")) return path;
return `${API_ORIGIN}${path}`;
}
interface ApiEnvelope<T> {
success: boolean;
message?: string;
@@ -24,6 +33,8 @@ export class ApiError extends Error {
export type ApprovalStatus = "pending" | "approved" | "rejected";
export type UserState = "inactive" | "active" | "blocked";
export interface UserProfile {
id: number;
phone: string;
@@ -37,6 +48,9 @@ export interface UserProfile {
approval_status: ApprovalStatus;
rejection_reason?: string;
is_admin: boolean;
state: UserState;
blocked_reason?: string;
blocked_at?: string;
}
export interface StepView {
@@ -334,8 +348,99 @@ export function getMonitoringSummary() {
return authRequest<MonitoringSummary>("/admin/monitoring/summary");
}
export function getRecentRequests(limit = 100) {
return authRequest<RequestLogEntry[]>(`/admin/monitoring/requests?limit=${limit}`);
export function getRecentRequests(limit = 100, userId?: number) {
const userParam = userId ? `&user_id=${userId}` : "";
return authRequest<RequestLogEntry[]>(`/admin/monitoring/requests?limit=${limit}${userParam}`);
}
// ---------------- ادمین: همه‌ی کاربران، بلاک/آنبلاک، گزارش ----------------
export interface AdminUserListItem extends UserProfile {
report_count: number;
}
interface PaginatedResult<T> {
items: T[];
total: number;
page: number;
page_size: number;
}
export function listAllUsers(search: string, page = 1, pageSize = 25) {
const searchParam = search ? `&search=${encodeURIComponent(search)}` : "";
return authRequest<PaginatedResult<AdminUserListItem>>(
`/admin/users?page=${page}&page_size=${pageSize}${searchParam}`
);
}
export function blockUser(id: number, reason: string) {
return authRequest<UserProfile>(`/admin/users/${id}/block`, {
method: "POST",
body: JSON.stringify({ reason }),
});
}
export function unblockUser(id: number) {
return authRequest<UserProfile>(`/admin/users/${id}/unblock`, { method: "POST" });
}
export interface Report {
id: number;
user_id: number;
reason: string;
created_at: string;
}
export function reportUser(id: number, reason: string) {
return authRequest<Report>(`/admin/users/${id}/report`, {
method: "POST",
body: JSON.stringify({ reason }),
});
}
export function listUserReports(id: number) {
return authRequest<Report[]>(`/admin/users/${id}/reports`);
}
// ---------------- ادمین: مچ‌ها (رفقا) ----------------
export interface AdminMatchView {
id: number;
created_at: string;
user_one_id: number;
user_one_name: string;
user_one_phone: string;
user_two_id: number;
user_two_name: string;
user_two_phone: string;
}
export function listAllMatches(page = 1, pageSize = 25) {
return authRequest<PaginatedResult<AdminMatchView>>(
`/admin/matches?page=${page}&page_size=${pageSize}`
);
}
// ---------------- ادمین: نظارت بر عکس‌ها ----------------
export interface AdminPhotoView {
id: number;
user_id: number;
url: string;
sort_order: number;
created_at: string;
owner_name: string;
owner_phone: string;
}
export function listAllPhotos(page = 1, pageSize = 50) {
return authRequest<PaginatedResult<AdminPhotoView>>(
`/admin/photos?page=${page}&page_size=${pageSize}`
);
}
export function deletePhotoAdmin(id: number) {
return authRequest<null>(`/admin/photos/${id}`, { method: "DELETE" });
}
// ---------------- ادمین: بک‌آپ دیتابیس ----------------