This commit is contained in:
Anang Yusman 2025-10-13 11:33:47 +08:00
parent a36f2297ea
commit 5827fdb1e4
2 changed files with 199 additions and 89 deletions

View File

@ -3,11 +3,19 @@ import Image from "next/image";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { getArticleById, getListArticle } from "@/service/article"; import { getArticleById, getListArticle } from "@/service/article";
import { close, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
import { useParams } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import Author from "../landing-page/author"; import Author from "../landing-page/author";
import { Link2, MailIcon } from "lucide-react"; import { Link2, MailIcon } from "lucide-react";
import { getAdvertise } from "@/service/advertisement"; import { getAdvertise } from "@/service/advertisement";
import {
getArticleComment,
otpRequest,
otpValidation,
postArticleComment,
} from "@/service/master-user";
import { saveActivity } from "@/service/activity-log";
import { useForm } from "react-hook-form";
type TabKey = "trending" | "comments" | "latest"; type TabKey = "trending" | "comments" | "latest";
@ -46,6 +54,7 @@ type Advertise = {
export default function DetailContent() { export default function DetailContent() {
const params = useParams(); const params = useParams();
const id = params?.id; const id = params?.id;
const pathname = usePathname();
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1); const [totalPage, setTotalPage] = useState(1);
const [articles, setArticles] = useState<Article[]>([]); const [articles, setArticles] = useState<Article[]>([]);
@ -72,7 +81,67 @@ export default function DetailContent() {
const [tabArticles, setTabArticles] = useState<Article[]>([]); const [tabArticles, setTabArticles] = useState<Article[]>([]);
const [activeTab, setActiveTab] = useState<TabKey>("trending"); const [activeTab, setActiveTab] = useState<TabKey>("trending");
const [needOtp, setNeedOtp] = useState(false);
const [otpValue, setOtpValue] = useState("");
const { register, handleSubmit, reset, watch } = useForm();
const [commentList, setCommentList] = useState<any>([]);
useEffect(() => {
fetchData();
}, [id]);
const fetchData = async () => {
try {
const res = await getArticleComment(String(id));
const data = res?.data?.data;
setCommentList(data);
console.log("komen", data);
} catch (err) {
console.error("❌ Gagal memuat komentar:", err);
setCommentList([]);
}
};
const onSubmit = async (values: any) => {
if (!needOtp) {
const res = await otpRequest(values.email, values?.name);
if (res?.error) {
error(res.message);
return false;
}
setNeedOtp(true);
} else {
const validation = await otpValidation(values.email, otpValue);
if (validation?.error) {
error("OTP Tidak Sesuai");
return false;
}
const data = {
commentFromName: values.name,
commentFromEmail: values.email,
articleId: Number(id),
isPublic: false,
message: values.comment,
parentId: 0,
};
const res = await postArticleComment(data);
if (res?.error) {
error(res?.message);
return false;
}
const req: any = {
activityTypeId: 5,
url: "https://dev.mikulnews/" + pathname,
articleId: Number(id),
};
const resActivity = await saveActivity(req);
reset();
fetchData();
setNeedOtp(false);
}
};
const tabs: { id: TabKey; label: string }[] = [ const tabs: { id: TabKey; label: string }[] = [
{ id: "trending", label: "Trending" }, { id: "trending", label: "Trending" },
{ id: "comments", label: "Comments" }, { id: "comments", label: "Comments" },
@ -473,85 +542,121 @@ export default function DetailContent() {
ditandai <span className="text-orange-600">*</span> ditandai <span className="text-orange-600">*</span>
</p> </p>
<form className="space-y-6 mt-6"> <div>
<div> <h3 className="text-lg font-semibold border-b pb-2">Komentar</h3>
<label {commentList?.length === 0 ? (
htmlFor="komentar" <p className="text-sm text-gray-500">Belum ada komentar.</p>
className="block text-sm font-medium mb-1" ) : (
> commentList?.map((comment: any) => (
Komentar <span className="text-orange-600">*</span> <div
</label> key={comment.id}
<textarea className="border rounded-lg p-3 bg-gray-50 shadow-sm"
id="komentar"
className="w-full border border-gray-300 rounded-md p-3 h-40 focus:outline-none focus:ring-2 focus:ring-green-600"
required
/>
</div>
<div>
<label
htmlFor="nama"
className="block text-sm font-medium mb-1"
>
Nama <span className="text-orange-600">*</span>
</label>
<input
type="text"
id="nama"
className="w-full border border-gray-300 rounded-md p-2"
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label
htmlFor="email"
className="block text-sm font-medium mb-1"
> >
Email <span className="text-orange-600">*</span> <p className="text-sm text-gray-800 whitespace-pre-line">
</label> {comment.message}
<input </p>
type="email" <p className="text-xs text-gray-500 mt-1">
id="email" {comment.commentFromName || "Anonim"} {" "}
className="w-full border border-gray-300 rounded-md p-2" {new Date(comment.createdAt).toLocaleString("id-ID", {
required dateStyle: "short",
/> timeStyle: "short",
</div> })}
<div> </p>
<label </div>
htmlFor="website" ))
className="block text-sm font-medium mb-1" )}
</div>
<form className="space-y-6 mt-6" onSubmit={handleSubmit(onSubmit)}>
{!needOtp ? (
<>
{/* Komentar */}
<div>
<label
htmlFor="komentar"
className="block text-sm font-medium mb-1"
>
Komentar <span className="text-green-600">*</span>
</label>
<textarea
id="komentar"
className="w-full border border-gray-300 rounded-md p-3 h-40 focus:outline-none focus:ring-2 focus:ring-green-600"
{...register("comment", { required: true })}
/>
</div>
{/* Nama */}
<div>
<label
htmlFor="nama"
className="block text-sm font-medium mb-1"
>
Nama <span className="text-green-600">*</span>
</label>
<input
type="text"
id="nama"
className="w-full border border-gray-300 rounded-md p-2"
{...register("name", { required: true })}
/>
</div>
{/* Email */}
<div>
<label
htmlFor="email"
className="block text-sm font-medium mb-1"
>
Email <span className="text-green-600">*</span>
</label>
<input
type="email"
id="email"
className="w-full border border-gray-300 rounded-md p-2"
{...register("email", { required: true })}
/>
</div>
<button
type="submit"
className="bg-green-600 hover:bg-green-700 text-white font-semibold px-6 py-2 rounded-md transition mt-2 w-full"
> >
Situs Web KIRIM KOMENTAR
</label> </button>
<input </>
type="url" ) : (
id="website" <>
className="w-full border border-gray-300 rounded-md p-2" <p className="text-sm text-gray-600">
/> Kode verifikasi sudah dikirimkan. Silakan cek Email Anda!
</div> </p>
</div> <div>
<label className="block text-sm font-medium mb-1 mt-4">
<div className="flex items-start space-x-2 mt-2"> OTP
<input type="checkbox" id="saveInfo" className="mt-1" /> </label>
<label htmlFor="saveInfo" className="text-sm text-gray-700"> <div className="flex gap-2 justify-center">
Simpan nama, email, dan situs web saya pada peramban ini untuk {[...Array(6)].map((_, i) => (
komentar saya berikutnya. <input
</label> key={i}
</div> type="text"
maxLength={1}
<p className="text-red-600 text-sm"> className="w-10 h-10 text-center border border-gray-300 rounded-md text-lg"
The reCAPTCHA verification period has expired. Please reload the value={otpValue[i] || ""}
page. onChange={(e) => {
</p> const newValue = otpValue.split("");
newValue[i] = e.target.value.replace(/[^0-9]/g, "");
<button setOtpValue(newValue.join(""));
type="submit" }}
className="bg-orange-600 hover:bg-orange-700 text-white font-semibold px-6 py-2 rounded-md transition mt-2" />
> ))}
KIRIM KOMENTAR </div>
</button> </div>
<button
type="submit"
className="bg-green-600 hover:bg-green-700 text-white font-semibold px-6 py-2 rounded-md transition mt-4 w-full"
>
Kirim
</button>
</>
)}
</form> </form>
</div> </div>
</div> </div>

View File

@ -94,16 +94,21 @@ export async function otpValidation(email: string, otpCode: string) {
return await httpPost(pathUrl, { email, otpCode }); return await httpPost(pathUrl, { email, otpCode });
} }
// export async function postArticleComment(data: any) {
// const headers = token
// ? {
// "content-type": "application/json",
// Authorization: `Bearer ${token}`,
// }
// : {
// "content-type": "application/json",
// };
// return await httpPost(`/article-comments`, headers, data);
// }
export async function postArticleComment(data: any) { export async function postArticleComment(data: any) {
const headers = token const pathUrl = `/article-comments`;
? { return await httpPostInterceptor(pathUrl, data);
"content-type": "application/json",
Authorization: `Bearer ${token}`,
}
: {
"content-type": "application/json",
};
return await httpPost(`/article-comments`, headers, data);
} }
export async function editArticleComment(data: any, id: number) { export async function editArticleComment(data: any, id: number) {
@ -112,7 +117,7 @@ export async function editArticleComment(data: any, id: number) {
} }
export async function getArticleComment(id: string) { export async function getArticleComment(id: string) {
const pathUrl = `/article-comments?isPublic=true&articleId=${id}`; const pathUrl = `/article-comments?isPublic=false&articleId=${id}`;
return await httpGet(pathUrl); return await httpGet(pathUrl);
} }