From 4375bb12a47e8029d7c649e850bccf00e1258169 Mon Sep 17 00:00:00 2001
From: mobiniqow
Date: Sat, 26 Sep 2026 18:10:31 +0330
Subject: [PATCH] Show user photos in admin panel, add per-photo reporting
- Add the missing `photos` field to FullProfile so the user-detail page
can render a photo gallery (the backend already returned this data,
the type just never declared it)
- Show applicants' first photo (or a "no photo" badge) in the pending-
users list instead of a generic avatar
- Add a report action alongside delete on the photo moderation page, so
a photo can be flagged without being removed
Co-Authored-By: Claude Sonnet 5
---
src/app/(dashboard)/photos/page.tsx | 78 ++++++++++++++++++++++---
src/app/(dashboard)/users/[id]/page.tsx | 27 +++++++++
src/app/(dashboard)/users/page.tsx | 20 ++++++-
src/lib/api.ts | 21 ++++++-
4 files changed, 133 insertions(+), 13 deletions(-)
diff --git a/src/app/(dashboard)/photos/page.tsx b/src/app/(dashboard)/photos/page.tsx
index 6d36fc5..005f0c7 100644
--- a/src/app/(dashboard)/photos/page.tsx
+++ b/src/app/(dashboard)/photos/page.tsx
@@ -1,16 +1,17 @@
"use client";
import { useCallback, useEffect, useState } from "react";
-import { ChevronLeft, ChevronRight, ImageOff, Trash2 } from "lucide-react";
+import { ChevronLeft, ChevronRight, ImageOff, Trash2, Flag } from "lucide-react";
import { Card } from "@/components/ui/Card";
import { Button } from "@/components/ui/Button";
import { EmptyState } from "@/components/ui/EmptyState";
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
import { ErrorBanner } from "@/components/ui/ErrorBanner";
import { ConfirmDialog } from "@/components/ui/ConfirmDialog";
+import { Modal } from "@/components/ui/Modal";
import { useToast } from "@/components/ui/Toast";
import { formatApiError } from "@/lib/hooks";
-import { AdminPhotoView, listAllPhotos, deletePhotoAdmin, photoUrl } from "@/lib/api";
+import { AdminPhotoView, listAllPhotos, deletePhotoAdmin, reportPhoto, photoUrl } from "@/lib/api";
const PAGE_SIZE = 24;
@@ -22,6 +23,9 @@ export default function PhotosModerationPage() {
const [error, setError] = useState(null);
const [pendingDelete, setPendingDelete] = useState(null);
const [deleting, setDeleting] = useState(false);
+ const [pendingReport, setPendingReport] = useState(null);
+ const [reportReason, setReportReason] = useState("");
+ const [reporting, setReporting] = useState(false);
const load = useCallback(async (p: number) => {
setError(null);
@@ -54,6 +58,21 @@ export default function PhotosModerationPage() {
}
}
+ async function handleReport() {
+ if (!pendingReport || reportReason.trim().length < 3) return;
+ setReporting(true);
+ try {
+ await reportPhoto(pendingReport.id, reportReason.trim());
+ toast.showSuccess("گزارش ثبت شد");
+ setPendingReport(null);
+ setReportReason("");
+ } catch (err) {
+ toast.showError(formatApiError(err));
+ } finally {
+ setReporting(false);
+ }
+ }
+
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
return (
@@ -102,13 +121,22 @@ export default function PhotosModerationPage() {
{p.owner_phone}
-
+
+
+
+
))}
@@ -149,6 +177,38 @@ export default function PhotosModerationPage() {
onConfirm={handleDelete}
onCancel={() => setPendingDelete(null)}
/>
+
+ setPendingReport(null)}
+ title="گزارش عکس"
+ >
+
+
+ یک یادداشت نظارتی روی صاحب این عکس ثبت میشه (مستقل از حذف کردن).
+
+
+
);
}
diff --git a/src/app/(dashboard)/users/[id]/page.tsx b/src/app/(dashboard)/users/[id]/page.tsx
index f2b9515..56ae798 100644
--- a/src/app/(dashboard)/users/[id]/page.tsx
+++ b/src/app/(dashboard)/users/[id]/page.tsx
@@ -16,6 +16,8 @@ import {
Check,
Flag,
Activity,
+ Images,
+ User as UserIcon,
} from "lucide-react";
import { Card } from "@/components/ui/Card";
import { Avatar } from "@/components/ui/Avatar";
@@ -39,6 +41,7 @@ import {
rejectUser,
listUserReports,
getRecentRequests,
+ photoUrl,
} from "@/lib/api";
const statusMeta: Record = {
@@ -188,6 +191,30 @@ export default function UserReviewPage() {
)}
+ } title={`عکسها (${profile.photos.length})`}>
+ {profile.photos.length === 0 ? (
+
+
+ این کاربر هنوز عکسی آپلود نکرده.
+
+ ) : (
+
+ )}
+
+
} title="قد و وزن">
diff --git a/src/app/(dashboard)/users/page.tsx b/src/app/(dashboard)/users/page.tsx
index 96d64bb..59071e5 100644
--- a/src/app/(dashboard)/users/page.tsx
+++ b/src/app/(dashboard)/users/page.tsx
@@ -11,10 +11,10 @@ import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
import { ErrorBanner } from "@/components/ui/ErrorBanner";
import { formatApiError } from "@/lib/hooks";
import { calcAge } from "@/lib/utils";
-import { UserProfile, listPendingUsers } from "@/lib/api";
+import { PendingUserView, listPendingUsers, photoUrl } from "@/lib/api";
export default function PendingUsersPage() {
- const [users, setUsers] = useState(null);
+ const [users, setUsers] = useState(null);
const [error, setError] = useState(null);
const load = useCallback(async () => {
@@ -70,7 +70,16 @@ export default function PendingUsersPage() {
i !== users.length - 1 ? "border-b border-(--color-border-subtle)" : ""
}`}
>
-
+ {u.photos.length > 0 ? (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ ) : (
+
+ )}
{u.first_name || "بدون نام"}
@@ -80,6 +89,11 @@ export default function PendingUsersPage() {
{u.phone}
+ {u.photos.length === 0 && (
+
+ بدون عکس
+
+ )}
{u.gender && (
{u.gender === "male" ? "مرد" : "زن"}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index bf2d9c9..559be3e 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -82,11 +82,19 @@ export interface ProfileAnswer {
question?: ProfileQuestion;
}
+export interface UserPhoto {
+ id: number;
+ user_id: number;
+ url: string;
+ sort_order: number;
+}
+
export interface FullProfile {
user: UserProfile;
onboarding_status: OnboardingStatus;
selections: SelectionSummary[];
profile_answers: ProfileAnswer[];
+ photos: UserPhoto[];
}
export interface TokenPair {
@@ -302,8 +310,12 @@ export function deleteQuestion(id: number) {
// ---------------- ادمین: بررسی و تایید کاربران ----------------
+export interface PendingUserView extends UserProfile {
+ photos: string[];
+}
+
export function listPendingUsers() {
- return authRequest("/admin/users/pending");
+ return authRequest("/admin/users/pending");
}
export function getUserDetail(id: number) {
@@ -443,6 +455,13 @@ export function deletePhotoAdmin(id: number) {
return authRequest(`/admin/photos/${id}`, { method: "DELETE" });
}
+export function reportPhoto(id: number, reason: string) {
+ return authRequest(`/admin/photos/${id}/report`, {
+ method: "POST",
+ body: JSON.stringify({ reason }),
+ });
+}
+
// ---------------- ادمین: بکآپ دیتابیس ----------------
export interface BackupInfo {