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:
+107
-2
@@ -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" });
|
||||
}
|
||||
|
||||
// ---------------- ادمین: بکآپ دیتابیس ----------------
|
||||
|
||||
Reference in New Issue
Block a user