feat:save generate article, edit generated article
This commit is contained in:
parent
24418ddb9b
commit
c5e4aac78e
|
|
@ -1,14 +1,27 @@
|
||||||
"use client";
|
"use client";
|
||||||
import EditGeneratedArticle from "@/components/form/article/edit-generated-article";
|
import EditGeneratedArticleChecker from "@/components/form/article/edit-generated-article-checker-form";
|
||||||
|
import EditGeneratedArticleContent from "@/components/form/article/edit-generated-article-content-form";
|
||||||
import { Card } from "@nextui-org/react";
|
import { Card } from "@nextui-org/react";
|
||||||
|
import { Tab, Tabs } from "@nextui-org/react";
|
||||||
|
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
export default function EditGeneratedArticlePage() {
|
export default function EditGeneratedArticlePage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = String(params.id);
|
const id = String(params.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="rounded-md border bg-transparent">
|
<Card className="rounded-md border bg-transparent p-6 overflow-auto">
|
||||||
<EditGeneratedArticle id={id} />
|
<div className="flex flex-col gap-2">
|
||||||
|
<Tabs aria-label="Options">
|
||||||
|
<Tab key="content" title="Content">
|
||||||
|
<EditGeneratedArticleContent id={id} />
|
||||||
|
</Tab>
|
||||||
|
<Tab key="checker" title="Checker">
|
||||||
|
<EditGeneratedArticleChecker id={id} />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,25 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { AddIcon } from "@/components/icons";
|
import { AddIcon } from "@/components/icons";
|
||||||
import ArticleTable from "@/components/table/article-table";
|
import ArticleTable from "@/components/table/article-table";
|
||||||
|
import generatedArticleIds from "@/store/generated-article-store";
|
||||||
import { Button, Card } from "@nextui-org/react";
|
import { Button, Card } from "@nextui-org/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function BasicPage() {
|
export default function BasicPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const setGeneratedArticleIdStore = generatedArticleIds(
|
||||||
|
(state) => state.setArticleIds
|
||||||
|
);
|
||||||
|
const goGenerate = () => {
|
||||||
|
setGeneratedArticleIdStore({
|
||||||
|
singleArticle: [],
|
||||||
|
bulkArticle: [],
|
||||||
|
rewriteArticle: [],
|
||||||
|
});
|
||||||
|
router.push("/admin/article/generate");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
|
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
|
||||||
<div className="px-2 md:px-4 w-full">
|
<div className="px-2 md:px-4 w-full">
|
||||||
|
|
@ -15,12 +30,15 @@ export default function BasicPage() {
|
||||||
New Article
|
New Article
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/admin/article/generate">
|
<Button
|
||||||
<Button size="md" color="primary" className="w-min">
|
size="md"
|
||||||
<AddIcon />
|
color="primary"
|
||||||
Generate Article
|
className="w-min"
|
||||||
</Button>
|
onPress={goGenerate}
|
||||||
</Link>
|
>
|
||||||
|
<AddIcon />
|
||||||
|
Generate Article
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
|
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
|
||||||
<ArticleTable />
|
<ArticleTable />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Input } from "@nextui-org/input";
|
||||||
|
import JoditEditor from "jodit-react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
getDetailArticle,
|
||||||
|
getSeoScore,
|
||||||
|
regenerateArticle,
|
||||||
|
updateManualArticle,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
import { Button } from "@nextui-org/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 "@nextui-org/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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Input } from "@nextui-org/input";
|
||||||
|
import JoditEditor from "jodit-react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
getDetailArticle,
|
||||||
|
updateManualArticle,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
import { Button } from "@nextui-org/button";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { error, loading } from "@/config/swal";
|
||||||
|
|
||||||
|
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 EditGeneratedArticleContent(props: { id: string }) {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const router = useRouter();
|
||||||
|
const { id } = props;
|
||||||
|
const editor = useRef(null);
|
||||||
|
|
||||||
|
const formOptions = { resolver: zodResolver(formSchema) };
|
||||||
|
type UserSettingSchema = z.infer<typeof formSchema>;
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
} = useForm<UserSettingSchema>(formOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getArticleDetail();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export default function EditGeneratedArticle(props: { id: string }) {
|
|
||||||
const { id } = props;
|
|
||||||
return <div>{id}</div>;
|
|
||||||
}
|
|
||||||
|
|
@ -32,14 +32,6 @@ import SpeechToTextOperator from "./speech-to-text-form";
|
||||||
import GenerateRewriteArticle from "./generate-rewrite-form";
|
import GenerateRewriteArticle from "./generate-rewrite-form";
|
||||||
import SpeechToText from "./speech-to-text-form";
|
import SpeechToText from "./speech-to-text-form";
|
||||||
|
|
||||||
const articleSchema = z.object({
|
|
||||||
title: z.string().min(1, { message: "Required" }),
|
|
||||||
article: z.string().min(1, { message: "Required" }),
|
|
||||||
slug: z.string().min(1, { message: "Required" }),
|
|
||||||
tags: z.string().min(0, { message: "Required" }).optional(),
|
|
||||||
description: z.string().min(0, { message: "Required" }).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const dummyCategory = [
|
const dummyCategory = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -73,7 +65,7 @@ export default function GenerateArticleForm() {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
const [article, setArticle] = React.useState<Selection>(new Set([]));
|
const [article, setArticle] = useState("");
|
||||||
const [slug, setSlug] = useState<string>("");
|
const [slug, setSlug] = useState<string>("");
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
const [newTags, setNewTags] = useState<string>("");
|
const [newTags, setNewTags] = useState<string>("");
|
||||||
|
|
@ -107,15 +99,11 @@ export default function GenerateArticleForm() {
|
||||||
const [selectedGeneratedArticleId, setSelectedGeneratedArticleId] =
|
const [selectedGeneratedArticleId, setSelectedGeneratedArticleId] =
|
||||||
useState<number>();
|
useState<number>();
|
||||||
|
|
||||||
const formOptions = { resolver: zodResolver(articleSchema) };
|
useEffect(() => {
|
||||||
type MicroIssueSchema = z.infer<typeof articleSchema>;
|
setCollectSingleArticleId(generatedArticleIdStore.singleArticle);
|
||||||
const {
|
setCollectBulkArticleId(generatedArticleIdStore.bulkArticle);
|
||||||
register,
|
setCollectRewriteArticleId(generatedArticleIdStore.rewriteArticle);
|
||||||
control,
|
});
|
||||||
handleSubmit,
|
|
||||||
setValue,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm<MicroIssueSchema>(formOptions);
|
|
||||||
|
|
||||||
const TypeId = [
|
const TypeId = [
|
||||||
{
|
{
|
||||||
|
|
@ -160,17 +148,17 @@ export default function GenerateArticleForm() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function save(data: any) {
|
async function save() {
|
||||||
const formData = {
|
const formData = {
|
||||||
title: title,
|
title: title,
|
||||||
typeId: parseInt(String(Array.from(article)[0])),
|
typeId: parseInt(String(Array.from(article)[0])),
|
||||||
slug: slug,
|
slug: slug,
|
||||||
|
categoryId: 12,
|
||||||
tags: tags.join(","),
|
tags: tags.join(","),
|
||||||
description: content,
|
description: content,
|
||||||
htmlDescription: content,
|
htmlDescription: content,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Form Data:", formData);
|
|
||||||
const response = await createArticle(formData);
|
const response = await createArticle(formData);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
|
|
@ -194,7 +182,7 @@ export default function GenerateArticleForm() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit(data: any) {
|
async function onSubmit() {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Simpan Data",
|
title: "Simpan Data",
|
||||||
text: "",
|
text: "",
|
||||||
|
|
@ -205,22 +193,31 @@ export default function GenerateArticleForm() {
|
||||||
confirmButtonText: "Simpan",
|
confirmButtonText: "Simpan",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
save(data);
|
save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleArticleId = (id: number) => {
|
const singleArticleId = (id: number) => {
|
||||||
let temp = [...collectSingleArticleId, id];
|
const temp = [...collectSingleArticleId, id];
|
||||||
|
const tempStore = generatedArticleIdStore;
|
||||||
|
tempStore.singleArticle = temp;
|
||||||
setCollectSingleArticleId(temp);
|
setCollectSingleArticleId(temp);
|
||||||
|
setGeneratedArticleIdStore(tempStore);
|
||||||
};
|
};
|
||||||
const bulkArticleId = (id: number[]) => {
|
const bulkArticleId = (id: number[]) => {
|
||||||
let temp: number[] = [...collectBulkArticleId, ...id];
|
const temp: number[] = [...collectBulkArticleId, ...id];
|
||||||
|
const tempStore = generatedArticleIdStore;
|
||||||
|
tempStore.bulkArticle = temp;
|
||||||
setCollectBulkArticleId(temp);
|
setCollectBulkArticleId(temp);
|
||||||
|
setGeneratedArticleIdStore(tempStore);
|
||||||
};
|
};
|
||||||
const rewriteArticleId = (id: number) => {
|
const rewriteArticleId = (id: number) => {
|
||||||
let temp = [...collectRewriteArticleId, id];
|
const temp = [...collectRewriteArticleId, id];
|
||||||
|
const tempStore = generatedArticleIdStore;
|
||||||
|
tempStore.rewriteArticle = temp;
|
||||||
setCollectRewriteArticleId(temp);
|
setCollectRewriteArticleId(temp);
|
||||||
|
setGeneratedArticleIdStore(tempStore);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -242,10 +239,20 @@ export default function GenerateArticleForm() {
|
||||||
checkArticleStatus(data);
|
checkArticleStatus(data);
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
setContent(data);
|
setContent(data);
|
||||||
|
} else {
|
||||||
|
setContent("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goEdit = () => {
|
||||||
|
const temp = generatedArticleIdStore;
|
||||||
|
temp.singleArticle = collectSingleArticleId;
|
||||||
|
temp.bulkArticle = collectBulkArticleId;
|
||||||
|
setGeneratedArticleIdStore(temp);
|
||||||
|
router.push(`/admin/article/generate/edit/${selectedGeneratedArticleId}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-5 my-5 overflow-y-auto">
|
<div className="mx-5 my-5 overflow-y-auto">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -253,7 +260,6 @@ export default function GenerateArticleForm() {
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
type="title"
|
type="title"
|
||||||
{...register("title")}
|
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
label="Judul"
|
label="Judul"
|
||||||
|
|
@ -262,19 +268,18 @@ export default function GenerateArticleForm() {
|
||||||
labelPlacement="outside"
|
labelPlacement="outside"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
<div className="text-sm text-red-500">
|
||||||
{title.length === 0 && errors.title && errors.title.message}
|
{title.length === 0 && <p className="text-danger">Required</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
label="Jenis Artikel"
|
label="Jenis Artikel"
|
||||||
{...register("article")}
|
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
labelPlacement="outside"
|
labelPlacement="outside"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
selectedKeys={article}
|
selectedKeys={[article]}
|
||||||
className="max-w-xs"
|
className="max-w-xs"
|
||||||
onSelectionChange={setArticle}
|
onChange={(e) => setArticle(e.target.value)}
|
||||||
>
|
>
|
||||||
{TypeId.map((data) => (
|
{TypeId.map((data) => (
|
||||||
<SelectItem key={data.key} value={data.key}>
|
<SelectItem key={data.key} value={data.key}>
|
||||||
|
|
@ -282,10 +287,7 @@ export default function GenerateArticleForm() {
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="text-sm text-red-500">
|
{article === "" && <p className="text-danger">Required</p>}
|
||||||
{errors.article?.message}
|
|
||||||
</div>
|
|
||||||
{/* <p>{article}</p> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm mb-1">Category</p>
|
<p className="text-sm mb-1">Category</p>
|
||||||
|
|
@ -315,7 +317,6 @@ export default function GenerateArticleForm() {
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("slug")}
|
|
||||||
value={slug}
|
value={slug}
|
||||||
onChange={(e) => setSlug(e.target.value)}
|
onChange={(e) => setSlug(e.target.value)}
|
||||||
label="Slug"
|
label="Slug"
|
||||||
|
|
@ -323,14 +324,11 @@ export default function GenerateArticleForm() {
|
||||||
placeholder="Enter Text"
|
placeholder="Enter Text"
|
||||||
labelPlacement="outside"
|
labelPlacement="outside"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
{slug.length === 0 && <div className=" text-danger">Required</div>}
|
||||||
{slug.length === 0 && errors.slug && errors.slug.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label="Tags (Optional)"
|
label="Tags (Optional)"
|
||||||
{...register("tags")}
|
|
||||||
labelPlacement="outside"
|
labelPlacement="outside"
|
||||||
type="text"
|
type="text"
|
||||||
value={newTags}
|
value={newTags}
|
||||||
|
|
@ -338,9 +336,8 @@ export default function GenerateArticleForm() {
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Tambahkan tag baru dan tekan Enter"
|
placeholder="Tambahkan tag baru dan tekan Enter"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
{tags.length === 0 && <div className=" text-red-500">Required</div>}
|
||||||
{tags.length === 0 && errors.tags && errors.tags.message}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 border border-inherit mt-2 rounded-md p-1 items-center h-11">
|
<div className="flex gap-2 border border-inherit mt-2 rounded-md p-1 items-center h-11">
|
||||||
{tags.map((tag, index) => (
|
{tags.map((tag, index) => (
|
||||||
<Chip
|
<Chip
|
||||||
|
|
@ -399,17 +396,18 @@ export default function GenerateArticleForm() {
|
||||||
(selectedGeneratedArticleId && content !== "" ? (
|
(selectedGeneratedArticleId && content !== "" ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="pb-2">Description</p>
|
<p className="pb-2">Description</p>
|
||||||
|
<a
|
||||||
|
className="text-primary cursor-pointer"
|
||||||
|
onClick={() => goEdit()}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
<JoditEditor
|
<JoditEditor
|
||||||
ref={editor}
|
ref={editor}
|
||||||
value={content}
|
value={content}
|
||||||
onChange={(newContent) => setContent(newContent)}
|
onChange={(newContent) => setContent(newContent)}
|
||||||
className="dark:text-black"
|
className="dark:text-black"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
|
||||||
{content?.length === 0 &&
|
|
||||||
errors?.description &&
|
|
||||||
errors?.description?.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : selectedGeneratedArticleId && content == "" ? (
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
|
|
@ -449,11 +447,6 @@ export default function GenerateArticleForm() {
|
||||||
onChange={(newContent) => setContent(newContent)}
|
onChange={(newContent) => setContent(newContent)}
|
||||||
className="dark:text-black"
|
className="dark:text-black"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
|
||||||
{content?.length === 0 &&
|
|
||||||
errors?.description &&
|
|
||||||
errors?.description?.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : selectedGeneratedArticleId && content == "" ? (
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
|
|
@ -496,11 +489,6 @@ export default function GenerateArticleForm() {
|
||||||
onChange={(newContent) => setContent(newContent)}
|
onChange={(newContent) => setContent(newContent)}
|
||||||
className="dark:text-black"
|
className="dark:text-black"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm text-red-500">
|
|
||||||
{content?.length === 0 &&
|
|
||||||
errors?.description &&
|
|
||||||
errors?.description?.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : selectedGeneratedArticleId && content == "" ? (
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
|
|
@ -594,7 +582,13 @@ export default function GenerateArticleForm() {
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button type="button" color="primary" variant="solid">
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
isDisabled={content === ""}
|
||||||
|
onPress={onSubmit}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -188,3 +188,30 @@ export async function getListArticleDraft(data: ContentBankRequest) {
|
||||||
};
|
};
|
||||||
return await httpPost("ai-writer/article/datatable", headers, data);
|
return await httpPost("ai-writer/article/datatable", headers, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateManualArticle(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/update-article", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSeoScore(id: string) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpGet(`ai-writer/article/checkSEOScore/${id}`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function regenerateArticle(id: number | string) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpGet(`ai-writer/re-create-article/${id}`, headers);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
interface targetStore {
|
interface targetStore {
|
||||||
articleIds: { singleArticle: number[]; bulkArticle: number[] };
|
articleIds: {
|
||||||
|
singleArticle: number[];
|
||||||
|
bulkArticle: number[];
|
||||||
|
rewriteArticle: number[];
|
||||||
|
};
|
||||||
setArticleIds: (newTarget: {
|
setArticleIds: (newTarget: {
|
||||||
singleArticle: number[];
|
singleArticle: number[];
|
||||||
bulkArticle: number[];
|
bulkArticle: number[];
|
||||||
|
rewriteArticle: number[];
|
||||||
}) => void;
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +18,7 @@ const getInitialTarget = () => {
|
||||||
const stored = localStorage.getItem("generated-article");
|
const stored = localStorage.getItem("generated-article");
|
||||||
const initial = stored
|
const initial = stored
|
||||||
? JSON.parse(stored)
|
? JSON.parse(stored)
|
||||||
: { singleArticle: [], bulkArticle: [] };
|
: { singleArticle: [], bulkArticle: [], rewriteArticle: [] };
|
||||||
return initial;
|
return initial;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -23,6 +28,7 @@ const generatedArticleIds = create<targetStore>((set) => ({
|
||||||
setArticleIds: (newTarget: {
|
setArticleIds: (newTarget: {
|
||||||
singleArticle: number[];
|
singleArticle: number[];
|
||||||
bulkArticle: number[];
|
bulkArticle: number[];
|
||||||
|
rewriteArticle: number[];
|
||||||
}) => {
|
}) => {
|
||||||
localStorage.setItem("generated-article", JSON.stringify(newTarget));
|
localStorage.setItem("generated-article", JSON.stringify(newTarget));
|
||||||
set({ articleIds: newTarget });
|
set({ articleIds: newTarget });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue