Fix admin review update detection and redirect cleanup

This commit is contained in:
2026-05-11 01:53:42 +07:00
parent e13725b9a0
commit c3aa2e78d2
2 changed files with 51 additions and 12 deletions

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useParams, useRouter, useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useRef, useState } from "react";
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "https://be.inatrading.co.id"; const API_BASE = process.env.NEXT_PUBLIC_API_URL || "https://be.inatrading.co.id";
@ -61,6 +61,26 @@ function extractIdChange(payload: unknown) {
| undefined; | undefined;
} }
function isProductUpdateFromCompare(
change:
| {
oldValue?: string;
newValue?: string;
isUpdate?: boolean;
}
| undefined
) {
if (!change) return false;
const hasDifferentIds =
typeof change.oldValue === "string" &&
typeof change.newValue === "string" &&
change.oldValue !== "" &&
change.newValue !== "" &&
change.oldValue !== change.newValue;
return change.isUpdate === true || hasDifferentIds;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function ProductColumn({ product, label, accent }: { product: any; label: string; accent?: boolean }) { function ProductColumn({ product, label, accent }: { product: any; label: string; accent?: boolean }) {
if (!product) return ( if (!product) return (
@ -208,6 +228,7 @@ function AdminReviewDetailPageInner() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [oldProduct, setOldProduct] = useState<any>(null); // original (compare) const [oldProduct, setOldProduct] = useState<any>(null); // original (compare)
const [isComparison, setIsComparison] = useState(false); const [isComparison, setIsComparison] = useState(false);
const [isUpdateProduct, setIsUpdateProduct] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState(""); const [loadError, setLoadError] = useState("");
@ -216,6 +237,7 @@ function AdminReviewDetailPageInner() {
const [actionSuccess, setActionSuccess] = useState(""); const [actionSuccess, setActionSuccess] = useState("");
const [showRejectModal, setShowRejectModal] = useState(false); const [showRejectModal, setShowRejectModal] = useState(false);
const [rejectReason, setRejectReason] = useState(""); const [rejectReason, setRejectReason] = useState("");
const redirectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => { useEffect(() => {
if (!params.productId) return; if (!params.productId) return;
@ -223,6 +245,7 @@ function AdminReviewDetailPageInner() {
setLoadError(""); setLoadError("");
setOldProduct(null); setOldProduct(null);
setIsComparison(false); setIsComparison(false);
setIsUpdateProduct(false);
const reviewFetch = fetch(`/api/admin/review/${params.productId}`, { const reviewFetch = fetch(`/api/admin/review/${params.productId}`, {
headers: { "x-auth-token": getToken() }, headers: { "x-auth-token": getToken() },
@ -254,7 +277,10 @@ function AdminReviewDetailPageInner() {
setProduct(updated); setProduct(updated);
const shouldCompare = idChange?.isUpdate === true && Boolean(idChange.oldValue) && Boolean(idChange.newValue); const isUpdate = isProductUpdateFromCompare(idChange);
setIsUpdateProduct(isUpdate);
const shouldCompare = isUpdate && Boolean(idChange?.oldValue) && Boolean(idChange?.newValue);
setIsComparison(shouldCompare); setIsComparison(shouldCompare);
if (shouldCompare) { if (shouldCompare) {
@ -272,6 +298,14 @@ function AdminReviewDetailPageInner() {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [params.productId]); }, [params.productId]);
useEffect(() => {
return () => {
if (redirectTimeoutRef.current) {
clearTimeout(redirectTimeoutRef.current);
}
};
}, []);
async function submitReview(action: "accept" | "reject", reason?: string) { async function submitReview(action: "accept" | "reject", reason?: string) {
setActing(true); setActing(true);
setActionError(""); setActionError("");
@ -279,7 +313,7 @@ function AdminReviewDetailPageInner() {
const res = await fetch(`/api/admin/review/${params.productId}`, { const res = await fetch(`/api/admin/review/${params.productId}`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", "x-auth-token": getToken() }, headers: { "Content-Type": "application/json", "x-auth-token": getToken() },
body: JSON.stringify({ action, isNew: product?.isNew ?? true, reason }), body: JSON.stringify({ action, isUpdate: isUpdateProduct, reason }),
}); });
const data = await res.json(); const data = await res.json();
if (!res.ok) { if (!res.ok) {
@ -287,7 +321,12 @@ function AdminReviewDetailPageInner() {
return; return;
} }
setActionSuccess(action === "accept" ? "Update produk berhasil disetujui!" : "Update produk berhasil ditolak!"); setActionSuccess(action === "accept" ? "Update produk berhasil disetujui!" : "Update produk berhasil ditolak!");
setTimeout(() => router.push("/admin/review"), 1800); if (redirectTimeoutRef.current) {
clearTimeout(redirectTimeoutRef.current);
}
redirectTimeoutRef.current = setTimeout(() => {
router.push("/admin/review");
}, 1800);
} catch { } catch {
setActionError("Gagal terhubung ke server"); setActionError("Gagal terhubung ke server");
} finally { } finally {

View File

@ -17,7 +17,7 @@ export async function GET(
} }
// action: "accept" | "reject" // action: "accept" | "reject"
// isNew: boolean — determines which backend endpoint to call // isUpdate: boolean — true for product update review, false for new product review
// body for reject: { reason: string } // body for reject: { reason: string }
export async function POST( export async function POST(
req: NextRequest, req: NextRequest,
@ -25,23 +25,23 @@ export async function POST(
) { ) {
const token = req.headers.get("x-auth-token") || ""; const token = req.headers.get("x-auth-token") || "";
const { productId } = await context.params; const { productId } = await context.params;
const { action, isNew, reason } = await req.json(); const { action, isUpdate, reason } = await req.json();
let url: string; let url: string;
let method = "POST"; let method = "POST";
let body: Record<string, string> | undefined; let body: Record<string, string> | undefined;
if (action === "accept") { if (action === "accept") {
// isNew=true: POST /product/accept/{id} // new product review: POST /product/accept/{id}
// isNew=false: PUT /product/accept/{id} // product update review: PUT /product/accept/{id}
url = `${API_URL}/api/v1.0/product/accept/${productId}`; url = `${API_URL}/api/v1.0/product/accept/${productId}`;
method = isNew ? "POST" : "PUT"; method = isUpdate ? "PUT" : "POST";
body = { state: "PUBLISHED" }; body = { state: "PUBLISHED" };
} else { } else {
// isNew=true: POST /product/reject/{id} // new product review: POST /product/reject/{id}
// isNew=false: PUT /product/reject/{id} // product update review: PUT /product/reject/{id}
url = `${API_URL}/api/v1.0/product/reject/${productId}`; url = `${API_URL}/api/v1.0/product/reject/${productId}`;
method = isNew ? "POST" : "PUT"; method = isUpdate ? "PUT" : "POST";
body = reason ? { reason, state: "REJECTED" } : { state: "REJECTED" }; body = reason ? { reason, state: "REJECTED" } : { state: "REJECTED" };
} }