web-humas-fe/components/main/detail/comment.tsx

509 lines
16 KiB
TypeScript

import { Button } from "@heroui/button";
import { Input, Textarea } from "@heroui/input";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
deleteArticleComment,
editArticleComment,
getArticleComment,
otpRequest,
otpValidation,
postArticleComment,
} from "@/service/master-user";
import { error } from "@/config/swal";
import { UserProfileIcon } from "@/components/icons/globals";
import { convertDateFormat } from "@/utils/global";
import Cookies from "js-cookie";
import OTPInput from "react-otp-input";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { SendIcon, TimesIcon } from "@/components/icons";
import { saveActivity } from "@/service/activity-log";
import { usePathname } from "next/navigation";
const userId = Cookies.get("uie");
const token = Cookies.get("access_token");
const commentSchema = z.object({
name: z.string().min(1, {
message: "Judul harus diisi",
}),
email: z
.string()
.email({
message: "Email tidak valid",
})
.min(1, {
message: "Harus diisi",
}),
comment: z.string().min(1, {
message: "Deskripsi harus diisi",
}),
});
export default function Comment(props: { id: string | null }) {
const { id } = props;
const MySwal = withReactContent(Swal);
const pathname = usePathname();
const [needOtp, setNeedOtp] = useState(false);
const [otpValue, setOtpValue] = useState("");
const [commentList, setCommentList] = useState<any>([]);
const [openCommentId, setOpenCommentId] = useState(0);
const [editCommentId, setEditCommentId] = useState(0);
const [replyValue, setReplyValue] = useState("");
const [editValue, setEditValue] = useState("");
const formOptions = {
resolver: zodResolver(commentSchema),
};
type UserSettingSchema = z.infer<typeof commentSchema>;
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<UserSettingSchema>(formOptions);
useEffect(() => {
fetchData();
}, [id]);
const fetchData = async () => {
const res = await getArticleComment(String(id));
setCommentList(res?.data?.data);
};
const onSubmit = async (values: z.infer<typeof commentSchema>) => {
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 = {
articleId: Number(id),
isPublic: true,
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://kontenhumas.com/" + pathname,
articleId: Number(id),
};
const resActivity = await saveActivity(req);
reset();
fetchData();
setNeedOtp(false);
}
};
const handleDelete = async (id: number) => {
MySwal.fire({
title: "Delete Comment",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
const doDelete = async (id: number) => {
const res = await deleteArticleComment(id);
if (res?.error) {
error(res?.message);
return false;
}
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
}
});
fetchData();
};
const sendComment = async (idComment: number) => {
const data = {
articleId: Number(id),
isPublic: true,
message: replyValue,
parentId: idComment,
};
const res = await postArticleComment(data);
if (res?.error) {
error(res?.message);
return false;
}
const req: any = {
activityTypeId: 5,
url: "https://kontenhumas.com/" + pathname,
articleId: Number(id),
userId: Number(userId),
};
const resActivity = await saveActivity(req, token);
fetchData();
};
// const sendActivity = async () => {};
const editComment = async (idComment: number, parentId: number) => {
const data = {
articleId: Number(id),
isPublic: true,
id: idComment,
message: editValue,
parentId: parentId,
};
const res = await editArticleComment(data, idComment);
if (res?.error) {
error(res?.message);
return false;
}
setEditCommentId(0);
fetchData();
};
const childComment = (parentId: number) => {
const filteredComment = commentList.filter(
(a: any) => a.parentId === parentId
);
return filteredComment.length > 0 ? (
<div className="flex flex-col gap-2 ml-4 w-full">
{filteredComment.map((list: any) => (
<div className="flex flex-row gap-2 " key={list?.id}>
<UserProfileIcon size={44} />
<div className="flex flex-col w-full pr-4">
<div className="flex justify-between gap-1 w-full">
<p className="text-sm font-semibold">{list?.commentFromName}</p>
<p className="text-xs">{convertDateFormat(list?.updatedAt)}</p>
</div>
{editCommentId === list?.id ? (
<div className="flex flex-row gap-2 items-center">
<Input
type="text"
id="editComment"
placeholder=""
label=""
value={editValue}
onValueChange={setEditValue}
endContent={
<div className="flex flex-row gap-2">
<a
className="cursor-pointer"
onClick={() => editComment(list?.id, list?.parentId)}
>
<SendIcon />
</a>
</div>
}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
<a
className="cursor-pointer text-warning"
onClick={() => setEditCommentId(0)}
>
<TimesIcon />
</a>
</div>
) : (
<div className="flex justify-between gap-1 text-right">
<p className="text-sm">{list?.message}</p>
<div className="flex flex-row gap-2 justify-end">
{userId === "16" && (
<a
className="text-primary cursor-pointer text-xs"
onClick={() => {
setEditValue(list?.message);
setEditCommentId(list?.id);
}}
>
Edit
</a>
)}
{(userId === String(list?.commentFromId) ||
userId === "16") && (
<a
className="text-danger cursor-pointer text-xs"
onClick={() => handleDelete(list?.id)}
>
Hapus
</a>
)}
</div>
</div>
)}
</div>
</div>
))}
</div>
) : (
""
);
};
return (
<div className="px-0 lg:px-3 flex flex-col gap-3">
<form
className="py-3 px-4 flex flex-col gap-3 bg-gray-50 text-black dark:bg-stone-900 dark:text-white rounded-lg shadow-md"
onSubmit={handleSubmit(onSubmit)}
>
{!needOtp ? (
<>
<b>Tinggalkan balasan</b>
<p className="text-xs">
Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib
ditandai <span className="text-red-600">*</span>
</p>
<div className="flex flex-col gap-1">
<p className="text-sm">Komentar</p>
<Controller
control={control}
name="comment"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="comment"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.comment && (
<p className="text-red-400 text-sm mb-3">
{errors.comment?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">
Nama <span className="text-red-600">*</span>
</p>
<Controller
control={control}
name="name"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="name"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.name && (
<p className="text-red-400 text-sm mb-3">
{errors.name?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">
Email <span className="text-red-600">*</span>
</p>
<Controller
control={control}
name="email"
render={({ field: { onChange, value } }) => (
<Input
type="email"
id="email"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.email && (
<p className="text-red-400 text-sm mb-3">
{errors.email?.message}
</p>
)}
</div>
</>
) : (
<div className="flex flex-col gap-1">
<p className="text-xs">
Kode verifikasi sudah dikirmkan. Silahkan cek Email Anda!
</p>
<p>OTP</p>
<OTPInput
value={otpValue}
onChange={setOtpValue}
numInputs={6}
renderSeparator={<span>-</span>}
renderInput={(props) => (
<input
{...props}
className="!w-[30px] h-[30px] bg-stone-900 dark:bg-white text-white dark:text-black rounded-sm"
/>
)}
/>
</div>
)}
<Button className="bg-[#DD8306] text-white" radius="sm" type="submit">
Kirim
</Button>
</form>
<div className="flex flex-col gap-2 text-black">
{commentList?.map(
(list: any) =>
list?.parentId === 0 && (
<div
key={list?.id}
className="flex flex-col gap-3 w-full bg-white text-black dark:bg-stone-900 dark:text-white shadow-md rounded-md border-b-2 py-3 px-4 "
>
<div className="flex justify-between items-center">
<div className="flex flex-row gap-2">
<UserProfileIcon size={44} />
<div className="flex flex-col gap-1">
<p className="text-sm font-semibold">
{list?.commentFromName}
</p>
<p className="text-sm">{list?.message}</p>
</div>
</div>
<div className="flex flex-col gap-1 text-right">
<p className="text-xs">
{convertDateFormat(list?.updatedAt)}
</p>
<div className="flex flex-row gap-2 justify-end">
{userId === "16" && (
<a
className="text-primary cursor-pointer text-xs"
onClick={() => setOpenCommentId(list?.id)}
>
Balas
</a>
)}
{(userId === String(list?.commentFromId) ||
userId === "16") && (
<a
className="text-danger cursor-pointer text-xs"
onClick={() => handleDelete(list?.id)}
>
Hapus
</a>
)}
</div>
</div>
</div>
{childComment(list?.id)}
{openCommentId === list?.id && (
<div className="flex flex-row gap-2 items-center">
<Input
type="text"
id="comment"
placeholder=""
label=""
value={replyValue}
onValueChange={setReplyValue}
endContent={
<div className="flex flex-row gap-2">
<a
className="cursor-pointer"
onClick={() => sendComment(list?.id)}
>
<SendIcon />
</a>
</div>
}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
<a
className="cursor-pointer text-warning"
onClick={() => setOpenCommentId(0)}
>
<TimesIcon />
</a>
</div>
)}
</div>
)
)}
</div>
</div>
);
}