feat:create AI single article

This commit is contained in:
Anang Yusman 2024-12-29 17:09:10 +08:00
parent 85d95f37f6
commit da871a8671
10 changed files with 1286 additions and 180 deletions

View File

@ -320,7 +320,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
}}
events={displayedEvents} // Use apiEvents here
events={displayedEvents}
editable={true}
rerenderDelay={10}
eventDurationEditable={false}

View File

@ -0,0 +1,15 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageAI from "@/components/form/content/image-ai-form";
const ImageAICreatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageAI />
</div>
</div>
);
};
export default ImageAICreatePage;

View File

@ -63,10 +63,12 @@ const ReactTableImagePage = () => {
Unggah Foto
</Button>
</Link>
<Link href={"/contributor/content/image/createAi"}>
<Button color="primary" className="text-white ml-3">
<UploadIcon />
Unggah Foto Dengan AI
</Button>
</Link>
</div>
</div>
</CardTitle>

View File

@ -0,0 +1,816 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import { createTask } from "@/config/api";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
uploadThumbnail,
} from "@/service/content/content";
import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { loading } from "@/lib/swal";
import {
generateDataArticle,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/content/ai";
import { title } from "process";
import style from "styled-jsx/style";
import { getCookiesDecrypt } from "@/lib/utils";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
mainKeyword: z.string().min(1, { message: "Judul diperlukan" }),
seo: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
export default function FormImageAI() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [selectedTitle, setSelectedTitle] = useState("");
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedContent, setSelectedContent] = useState("single-article");
const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [articleIds, setArticleIds] = useState<any>([]);
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "1";
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
});
const doGenerateMainKeyword = async () => {
console.log(selectedMainKeyword);
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error during generation process:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateTitle = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const titleData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for title with data:", titleData);
const titleRes = await getGenerateTitle(titleData);
setTitle(titleRes?.data?.data || "");
console.log("Generated title:", titleRes?.data?.data);
} catch (error) {
console.error("Error generating title:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const doGenerateKeyword = async () => {
if (selectedMainKeyword?.length > 1) {
try {
setIsLoading(true);
const keywordsData = {
keyword: selectedMainKeyword,
style: selectedWritingStyle,
website: "0",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
clientId: "",
};
console.log("Sending request for keywords with data:", keywordsData);
const keywordsRes = await getGenerateKeywords(keywordsData);
setSelectedSEO(keywordsRes?.data?.data || []);
console.log("Generated keywords:", keywordsRes?.data?.data);
} catch (error) {
console.error("Error generating keywords:", error);
} finally {
setIsLoading(false);
}
} else {
console.error("Please provide a valid main keyword.");
}
};
const handleGenerateArtikel = async () => {
loading();
const request = {
advConfig: selectedAdvConfig,
style: selectedWritingStyle,
website: "None",
connectToWeb: true,
lang: selectedLanguage,
pointOfView: "None",
title: selectedTitle,
imageSource: "Web",
mainKeyword: selectedMainKeyword,
additionalKeywords: selectedSEO,
targetCountry: null,
articleSize: selectedSize,
projectId: 2,
createdBy: roleId,
clientId: "ngDLPPiorplznw2jTqVe3YFCz5xqKfUJ",
};
const res = await generateDataArticle(request);
close();
if (res.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: any) => {
if (prevIds.length < 5) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[4] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", articleIds);
};
const handleArticleIdClick = (articleId: string) => {
setEditingArticleId(articleId);
setSelectedArticleId(articleId);
};
const handleEditClick = () => {
if (editingArticleId) {
console.log("Editing article:", editingArticleId);
// Handle the edit action here
}
};
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
useEffect(() => {
async function initState() {
getCategories();
// setVideoActive(fileTypeId == '2');
// getRoles();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category.data.data.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
const save = async (data: ImageSchema) => {
const requestData = {
...data,
title: data.title,
description: data.description,
htmlDescription: data.description,
fileTypeId,
categoryId: selectedCategory,
subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: "6",
creatorName: data.creatorName,
tags: "siap",
isYoutube: false,
isInternationalMedia: false,
};
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
if (response.error) {
MySwal.fire("Error", response.message, "error");
return;
}
const imageId = response.data.data.id;
if (imageId && thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail);
const responseThumbnail = await uploadThumbnail(imageId, formMedia);
if (responseThumbnail.error) {
MySwal.fire("Error", responseThumbnail.message, "error");
return;
}
}
MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
});
};
const onSubmit = (data: ImageSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file);
console.log("Selected Thumbnail:", file);
}
if (file) {
setPreview(URL.createObjectURL(file));
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3 w-4/12">
<Label>Select Content</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="single-article">
single Article
</SelectItem>
<SelectItem value="bulk-article">Bulk Article</SelectItem>
<SelectItem value="speech-text">Speech to Text</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => {
console.log("Selected Category ID:", id);
setSelectedCategory(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label>
<Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="id">Indonesia</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label>
<Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="friendly">Friendly</SelectItem>
<SelectItem value="profesional">Profesional</SelectItem>
<SelectItem value="informational">
Informational
</SelectItem>
<SelectItem value="neutral">Neutral</SelectItem>
<SelectItem value="witty">Witty</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label>
<Select onValueChange={setSelectedSize}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="news">
News (300 - 900 words)
</SelectItem>
<SelectItem value="info">
Info (900 - 2000 words)
</SelectItem>
<SelectItem value="detail">
Detail (2000 - 5000 words)
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateMainKeyword}
disabled={isLoading}
>
{isLoading ? "Processing..." : "Proses"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={selectedMainKeyword}
onChange={(e) => setSelectedMainKeyword(e.target.value)}
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label>
<Button
variant="outline"
color="primary"
onClick={doGenerateTitle}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<div>
<Input
size="md"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Generated Title"
/>
</div>
</div>
<div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label>
<Button
variant={"outline"}
color="primary"
onClick={doGenerateKeyword}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</Button>
</div>
<p className="font-semibold">
Kata kunci untuk disertakan dalam teks
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci utama
untuk setiap bagian dan menggunakannya untuk membuat artikel.
Untuk menambahkan kata kunci baru, ketik ', + kata kunci'.
</p>
<div className="mt-3">
<Textarea
value={selectedSEO}
onChange={(e) => setSelectedSEO(e.target.value)}
placeholder="Enter Title"
/>
</div>
</div>
<div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label>
<div className="mt-3">
<Controller
control={control}
name="title"
render={({ field }) => (
<Textarea
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
</div>
</div>
<div>
<div className="my-5">
<Button
variant={"outline"}
color="primary"
onClick={handleGenerateArtikel}
>
Generate Article
</Button>
</div>
{isGeneratedArticle && (
<div className="mt-3 pb-0">
{articleIds.map((id: any, index: any) => (
<Button
key={index}
className={`btn m-1 ${
selectedArticleId === id
? "btn-warning"
: "btn-success"
}`}
onClick={() => handleArticleIdClick(id)}
variant={"outline"}
color="success"
>
{id}
</Button>
))}
</div>
)}
<div className="py-3">
<div className="flex flex-row justify-between items-center">
<Label>Deskripsi</Label>
{selectedArticleId && (
<a
href={`/admin/media/${
fileTypeId === "1"
? "image"
: fileTypeId === "2"
? "video"
: fileTypeId === "3"
? "text"
: "audio"
}/update-new/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
Edit
</Button>
</a>
)}
</div>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value || articleBody} // Show the selected article body in the editor
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
{/* {isGeneratedArticle && articleIds.length > 0 && (
<div className="my-4">
<h3>Generated Article IDs:</h3>
{articleIds.map((id: any, index: any) => (
<span
key={index}
className="badge badge-pill bg-primary text-white cursor-pointer mx-2"
onClick={() => handleArticleIdClick(id)}
>
{id}
</span>
))}
</div>
)} */}
{/* {editingArticleId && (
<div className="my-4">
<Button color="secondary" onClick={handleEditClick}>
Edit Article
</Button>
</div>
)} */}
</div>
<div className="py-3">
<Label>Attachment</Label>
<Input
id="fileInput"
type="file"
onChange={handleImageChange}
/>
<div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label>
<div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
{preview && (
<div className="mt-3 px-3">
<img
src={preview}
alt="Thumbnail Preview"
className="w-32 h-auto rounded"
/>
</div>
)}
</div>
</div>
{/* Submit Button */}
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[500px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Kreator</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3">
<Label>Tags</Label>
{/* <Controller
control={control}
name="tags"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
onKeyDown={handleKeyDown}
/>
)}
/> */}
<div className="text-sm text-red-500">
{tags.length === 0 && "Please add at least one tag."}
</div>
<div className="flex flex-wrap gap-2 border border-gray-300 mt-2 rounded-md p-2 items-center">
{tags.map((tag, index) => (
<div
key={index}
className="flex items-center gap-1 bg-blue-100 text-blue-800 rounded-full px-3 py-1 text-sm font-medium"
>
<span>{tag}</span>
<button
className="text-blue-600 hover:text-blue-800 focus:outline-none"
onClick={() => handleRemoveTag(index)}
>
×
</button>
</div>
))}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex gap-2 items-center">
<Checkbox id="all" />
<Label htmlFor="all">SEMUA</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="umum" />
<Label htmlFor="umum">UMUM</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="journalist" />
<Label htmlFor="journalist">JOURNALIS</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="polri" />
<Label htmlFor="polri">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox id="ksp" />
<Label htmlFor="ksp">KSP</Label>
</div>
</div>
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
Cancel
</Button>
</div>
</div>
</div>
</div>
</form>
);
}

View File

@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import ThemeSwitcher from "@/components/partials/header/theme-switcher";
import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi";
import { useParams, usePathname, useRouter } from "next/navigation";
@ -26,6 +26,21 @@ import {
import Image from "next/image";
import { Icon } from "../ui/icon";
import { getCookiesDecrypt } from "@/lib/utils";
import Cookies from "js-cookie";
import { getInfoProfile } from "@/service/auth";
type Detail = {
id: number;
userId: any;
firstName: string;
username: string;
fullname: string;
memberIdentity: any;
email: string;
address: string;
phoneNumber: any;
message: string;
};
const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false);
@ -39,12 +54,33 @@ const Navbar = () => {
const roleName = getCookiesDecrypt("urne");
const levelName = getCookiesDecrypt("ulnae");
const roleId = getCookiesDecrypt("urie");
const [detail, setDetail] = useState<Detail>();
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName);
});
router.push("/");
};
const handleLanguageChange = (lang: "id" | "en") => {
setLanguage(lang);
setIsOpen(false);
};
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
return (
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-20 py-4 gap-3">
@ -320,9 +356,13 @@ const Navbar = () => {
</span>
</div>
{roleId === "6" ? (
{roleId === "5" ||
roleId === "6" ||
roleId === "7" ||
roleId === "8" ? (
<DropdownMenu>
<DropdownMenuTrigger asChild className="cursor-pointer">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"https://netidhub.com/assets/img/user-avatar.png"}
@ -333,14 +373,17 @@ const Navbar = () => {
/>
<div>
<div className="text-sm font-medium capitalize lg:block hidden">
Mabes Polri - Approver
{detail?.fullname}
</div>
<p className="text-xs">(MABES POLRI)</p>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
<DropdownMenuGroup>
@ -351,7 +394,7 @@ const Navbar = () => {
href: "/profile",
},
{
name: "Dashboard",
name: "Kelola Konten",
icon: "heroicons:megaphone",
href: "/dashboard",
},
@ -371,10 +414,11 @@ const Navbar = () => {
<DropdownMenuSeparator />
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
<div>
<Link href={"/auth"}>
<Link href={"/"}>
<button
type="submit"
className="w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out
@ -384,10 +428,18 @@ const Navbar = () => {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : roleId === "3" ? (
) : roleId === "2" ||
roleId === "3" ||
roleId === "4" ||
roleId === "9" ||
roleId === "10" ||
roleId === "11" ||
roleId === "12" ||
roleId === "13" ? (
// Dropdown menu for roleId === 3
<DropdownMenu>
<DropdownMenuTrigger asChild className="cursor-pointer">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"https://netidhub.com/assets/img/user-avatar.png"}
@ -398,14 +450,17 @@ const Navbar = () => {
/>
<div>
<div className="text-sm font-medium capitalize lg:block hidden">
Mabes Polri - Reviewer
{detail?.fullname}
</div>
<p className="text-xs">(MABES POLRI)</p>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
<DropdownMenuGroup>
@ -436,10 +491,11 @@ const Navbar = () => {
<DropdownMenuSeparator />
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
<div>
<Link href={"/auth"}>
<Link href={"/"}>
<button
type="submit"
className="w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out

View File

@ -17,37 +17,79 @@ import Image from "next/image";
import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button";
import Cookies from "js-cookie";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useRouter } from "@/components/navigation";
import { getInfoProfile } from "@/service/auth";
type Detail = {
id: number;
userId: any;
firstName: string;
username: string;
fullname: string;
memberIdentity: any;
email: string;
address: string;
phoneNumber: any;
message: string;
};
const ProfileInfo = () => {
const username = Cookies.get("state");
const picture = Cookies.get("profile_picture");
const router = useRouter();
const [detail, setDetail] = useState<Detail>();
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName);
});
router.push("/");
};
useEffect(() => {
console.log("us", username);
}, [username]);
useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response.data?.data;
setDetail(details);
console.log("data", details);
}
initState();
}, []);
return (
<div className="md:block hidden">
<DropdownMenu>
<DropdownMenuTrigger asChild className=" cursor-pointer">
<div className=" flex items-center gap-3 text-default-800 ">
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"/images/avatar/icon-avatar-1.png"}
alt={username as string}
src={"https://netidhub.com/assets/img/user-avatar.png"}
alt={"Image"}
width={36}
height={36}
className="rounded-full"
/>
<div>
<div className="text-sm font-medium capitalize lg:block hidden ">
Mabes Polri - Approver
<div className="text-sm font-medium capitalize lg:block hidden">
{detail?.fullname}
</div>
<p className="text-xs">(MABES POLRI)</p>
<p className="text-xs">({detail?.fullname})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>
</span>
</div>
) : (
""
)}
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-0" align="end">
{/* <DropdownMenuLabel className="flex gap-2 items-center mb-1 p-3">
@ -186,6 +228,7 @@ const ProfileInfo = () => {
<button
type="submit"
className=" w-full flex items-center gap-2"
onClick={onLogout}
>
<Icon icon="heroicons:power" className="w-4 h-4" />
Log out

244
package-lock.json generated
View File

@ -132,14 +132,18 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^8.4.1",
"@next/bundle-analyzer": "^15.0.3",
"@types/d3-shape": "^3.1.6",
"@types/geojson": "^7946.0.15",
"@types/jquery": "^3.5.32",
"@types/leaflet": "^1.9.12",
"@types/node": "^20",
"@types/react": "^18.3.13",
"@types/react-geocode": "^0.2.4",
"@types/rtl-detect": "^1.0.3",
"d3-shape": "^3.2.0",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
@ -1063,6 +1067,126 @@
"glob": "10.3.10"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz",
"integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz",
"integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz",
"integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz",
"integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz",
"integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz",
"integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz",
"integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz",
"integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz",
@ -13849,126 +13973,6 @@
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz",
"integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz",
"integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz",
"integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz",
"integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz",
"integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz",
"integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz",
"integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz",
"integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}

111
service/content/ai.ts Normal file
View File

@ -0,0 +1,111 @@
import { getAPIInterceptor } from "@/config/api";
import { httpGet, httpPost } from "../http-config/nulisAIApi";
export async function generateDataArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-article", headers, data);
}
export async function getGenerateTitle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-title", headers, data);
}
export async function getGenerateKeywords(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-keywords", headers, data);
}
export async function getDetailArticle(id: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/article/findArticleById/${id}`, headers);
}
export async function getSeoScore(id: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpGet(`ai-writer/article/checkSEOScore/${id}`, headers);
}
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 getGenerateTopicKeywords(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/generate-topic-keywords", headers, data);
}
export async function saveBulkArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-bulk-article", headers, data);
}
export async function getGenerateRewriter(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/create-rewriter", headers, data);
}
export async function generateSpeechToText(data: any) {
const headers = {
"content-type": "multipart/form-data",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/speech-to-text", headers, data);
}
export async function generateDataBulkArticle(data: any) {
const headers = {
"content-type": "application/json",
Authorization:
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
};
return await httpPost("ai-writer/save-bulk-article", headers, data);
}
export async function listNulisAI(page: any, limit: any) {
const url = `media/nulis-ai/pagination?enablePage=1&page=${page}&size=${limit}`;
return getAPIInterceptor(url);
}
export async function detailMediaNulis(id: any) {
const url = `media/nulis-ai?id=${id}`;
return getAPIInterceptor({ url });
}

View File

@ -0,0 +1,12 @@
import axios from "axios";
const baseURL = "https://disestages.com/api";
const axiosNulisAIInstance = axios.create({
baseURL,
headers: {
"content-type": "application/json",
},
});
export default axiosNulisAIInstance;

View File

@ -0,0 +1,47 @@
import axiosNulisAIInstance from "./axiosNulisAIInstance";
export async function httpPost(pathUrl: any, headers: any, data: any) {
const response = await axiosNulisAIInstance
.post(pathUrl, data, { headers })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function httpGet(pathUrl: any, headers: any) {
const response = await axiosNulisAIInstance
.get(pathUrl, { headers })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}