384 lines
12 KiB
TypeScript
384 lines
12 KiB
TypeScript
import { Controller, useForm } from "react-hook-form";
|
|
import * as z from "zod";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Input } from "@heroui/input";
|
|
import JoditEditor from "jodit-react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import {
|
|
getDetailArticle,
|
|
getSeoScore,
|
|
regenerateArticle,
|
|
updateManualArticle,
|
|
} from "@/service/generate-article";
|
|
import { Button } from "@heroui/button";
|
|
import Swal from "sweetalert2";
|
|
import withReactContent from "sweetalert2-react-content";
|
|
import { useRouter } from "next/navigation";
|
|
import { close, error, loading } from "@/config/swal";
|
|
import { Accordion, AccordionItem, CircularProgress } from "@heroui/react";
|
|
|
|
const formSchema = z.object({
|
|
mainKeyword: z.string().min(2, {
|
|
message: "Keyword must be at least 2 characters.",
|
|
}),
|
|
title: z.string().min(2, {
|
|
message: "Title must be at least 2 characters.",
|
|
}),
|
|
additionalKeyword: z.string().min(2, {
|
|
message: "Additional Keyword must be at least 2 characters.",
|
|
}),
|
|
metaTitle: z.string().min(2, {
|
|
message: "Meta Title must be at least 2 characters.",
|
|
}),
|
|
metaDescription: z.string().min(2, {
|
|
message: "Meta Description must be at least 2 characters.",
|
|
}),
|
|
articleBody: z.string().min(2, {
|
|
message: "Article Body must be at least 2 characters.",
|
|
}),
|
|
});
|
|
|
|
export default function EditGeneratedArticleChecker(props: { id: string }) {
|
|
const MySwal = withReactContent(Swal);
|
|
const router = useRouter();
|
|
const { id } = props;
|
|
const editor = useRef(null);
|
|
const [totalScoreSEO, setTotalScoreSEO] = useState();
|
|
const [errorSEO, setErrorSEO] = useState<string[]>([]);
|
|
const [warningSEO, setWarningSEO] = useState<string[]>([]);
|
|
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
|
|
|
|
const formOptions = { resolver: zodResolver(formSchema) };
|
|
type UserSettingSchema = z.infer<typeof formSchema>;
|
|
const {
|
|
control,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
setValue,
|
|
} = useForm<UserSettingSchema>(formOptions);
|
|
|
|
useEffect(() => {
|
|
getArticleDetail();
|
|
fetchSeoScore();
|
|
}, [id]);
|
|
|
|
const fetchSeoScore = async () => {
|
|
const res = await getSeoScore(id);
|
|
if (res.error) {
|
|
error(res.message);
|
|
return false;
|
|
}
|
|
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
|
|
const errorList: string[] = [
|
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error,
|
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.error,
|
|
];
|
|
setErrorSEO(errorList);
|
|
const warningList: string[] = [
|
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning,
|
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.warning,
|
|
];
|
|
setWarningSEO(warningList);
|
|
const optimizedList: string[] = [
|
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.optimized,
|
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized,
|
|
];
|
|
setOptimizedSEO(optimizedList);
|
|
};
|
|
|
|
const getArticleDetail = async () => {
|
|
const res = await getDetailArticle(Number(id));
|
|
// const data = res?.data?.data?.articleBody;
|
|
const data = res?.data?.data;
|
|
setValue("title", data?.title);
|
|
setValue("mainKeyword", data?.mainKeyword);
|
|
setValue("additionalKeyword", data?.additionalKeywords);
|
|
setValue("metaTitle", data?.metaTitle);
|
|
setValue("metaDescription", data?.metaDescription);
|
|
setValue("articleBody", data?.articleBody);
|
|
};
|
|
|
|
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
|
const request = {
|
|
id: Number(id),
|
|
title: values.title,
|
|
articleBody: values.articleBody,
|
|
metaDescription: values.metaDescription,
|
|
metaTitle: values.metaTitle,
|
|
createdBy: "123123",
|
|
};
|
|
console.log("request", request);
|
|
loading();
|
|
const res = await updateManualArticle(request);
|
|
if (res.error) {
|
|
error(res.message);
|
|
return false;
|
|
}
|
|
close();
|
|
successSubmit("/admin/article/generate");
|
|
};
|
|
|
|
function successSubmit(redirect: string) {
|
|
MySwal.fire({
|
|
title: "Success",
|
|
icon: "success",
|
|
confirmButtonColor: "#3085d6",
|
|
confirmButtonText: "OK",
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
router.push(redirect);
|
|
}
|
|
});
|
|
}
|
|
|
|
const doRegenerate = async () => {
|
|
loading();
|
|
const res1 = await regenerateArticle(id);
|
|
if (res1.error) {
|
|
error(res1.message);
|
|
return false;
|
|
}
|
|
close();
|
|
window.location.reload();
|
|
|
|
getArticleDetail();
|
|
fetchSeoScore();
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<div className="flex flex-row items-center gap-4">
|
|
<CircularProgress
|
|
aria-label="Loading..."
|
|
value={Number(totalScoreSEO) * 100}
|
|
color="warning"
|
|
className="w-full"
|
|
classNames={{
|
|
svg: "w-36 h-36 drop-shadow-md",
|
|
value: "text-3xl font-semibold",
|
|
}}
|
|
showValueLabel={true}
|
|
/>
|
|
<div className="border border-danger py-2 px-4 rounded-lg">
|
|
{errorSEO?.length} Errors
|
|
</div>
|
|
<div className="border border-warning py-2 px-4 rounded-lg">
|
|
{warningSEO?.length} Warnings
|
|
</div>
|
|
<div className="border border-success py-2 px-4 rounded-lg">
|
|
{optimizedSEO?.length} Optimized
|
|
</div>
|
|
</div>
|
|
<Accordion>
|
|
<AccordionItem
|
|
key="1"
|
|
aria-label="Errors"
|
|
className="border border-danger px-2 rounded-lg"
|
|
title={`${errorSEO?.length} Errors`}
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
{errorSEO?.map((item: string) => (
|
|
<p
|
|
key={item}
|
|
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
|
|
>
|
|
{item}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
<Accordion>
|
|
<AccordionItem
|
|
key="2"
|
|
aria-label="Accordion 2"
|
|
className="border border-warning px-2 rounded-lg"
|
|
title={`${warningSEO?.length} Warnings`}
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
{warningSEO?.map((item: string) => (
|
|
<p
|
|
key={item}
|
|
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
|
|
>
|
|
{item}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
|
|
<Accordion>
|
|
<AccordionItem
|
|
key="3"
|
|
aria-label="Accordion 3"
|
|
className="border border-success px-2 rounded-lg"
|
|
title={`${optimizedSEO?.length} Optimized`}
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
{optimizedSEO?.map((item: string) => (
|
|
<p
|
|
key={item}
|
|
className="w-full border rounded-md h-[40px] text-left flex flex-col justify-center px-3"
|
|
>
|
|
{item}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
<div className="border-y-2 py-4 my-4">
|
|
<Button
|
|
className="bg-[#4841A5] hover:bg-[#4f4a97] dark:hover:bg-[#302c61] text-white"
|
|
onClick={doRegenerate}
|
|
>
|
|
Regenerate
|
|
</Button>
|
|
</div>
|
|
|
|
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
|
<Controller
|
|
control={control}
|
|
name="title"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Input
|
|
type="text"
|
|
id="title"
|
|
placeholder="Title"
|
|
label="Title"
|
|
value={value}
|
|
onChange={onChange}
|
|
labelPlacement="outside"
|
|
className="w-full"
|
|
variant="bordered"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.title && (
|
|
<p className="text-red-400 text-sm">{errors.title?.message}</p>
|
|
)}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<div className="flex flex-col">
|
|
<Controller
|
|
control={control}
|
|
name="mainKeyword"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Input
|
|
type="text"
|
|
id="mainKeyword"
|
|
placeholder="Keyword"
|
|
label="Keyword"
|
|
value={value}
|
|
onChange={onChange}
|
|
labelPlacement="outside"
|
|
className="w-full"
|
|
variant="bordered"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.mainKeyword?.message && (
|
|
<p className="text-red-400 text-sm">
|
|
{errors.mainKeyword?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<Controller
|
|
control={control}
|
|
name="additionalKeyword"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Input
|
|
type="text"
|
|
id="additionalKeyword"
|
|
placeholder="Additional Keyword"
|
|
label="Additional Keyword"
|
|
value={value}
|
|
onChange={onChange}
|
|
labelPlacement="outside"
|
|
className="w-full"
|
|
variant="bordered"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.additionalKeyword?.message && (
|
|
<p className="text-red-400 text-sm">
|
|
{errors.additionalKeyword?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<Controller
|
|
control={control}
|
|
name="metaTitle"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Input
|
|
type="text"
|
|
id="metaTitle"
|
|
placeholder="Meta Title"
|
|
label="Meta Title"
|
|
value={value}
|
|
onChange={onChange}
|
|
labelPlacement="outside"
|
|
className="w-full"
|
|
variant="bordered"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.metaTitle?.message && (
|
|
<p className="text-red-400 text-sm">
|
|
{errors.metaTitle?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<Controller
|
|
control={control}
|
|
name="metaDescription"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Input
|
|
type="text"
|
|
id="metaDescription"
|
|
placeholder="Meta Description"
|
|
label="Meta Description"
|
|
value={value}
|
|
onChange={onChange}
|
|
labelPlacement="outside"
|
|
className="w-full"
|
|
variant="bordered"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.metaDescription?.message && (
|
|
<p className="text-red-400 text-sm">
|
|
{errors.metaDescription?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<Controller
|
|
control={control}
|
|
name="articleBody"
|
|
render={({ field: { onChange, value } }) => (
|
|
<JoditEditor
|
|
ref={editor}
|
|
value={value}
|
|
onChange={onChange}
|
|
className="dark:text-black"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.articleBody?.message && (
|
|
<p className="text-red-400 text-sm">
|
|
{errors.articleBody?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<Button color="primary" type="submit">
|
|
Save
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|