This commit is contained in:
Anang Yusman 2025-09-28 23:18:21 +08:00
parent af3649fcb0
commit 75b8564a41
16 changed files with 607 additions and 406 deletions

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

View File

@ -0,0 +1,17 @@
import BreakingNews from "@/components/landing-page/breaking-news";
import Footer from "@/components/landing-page/footer";
import HeaderLatest from "@/components/landing-page/header-bumn";
import Navbar from "@/components/landing-page/navbar";
import BumnNews from "@/components/landing-page/news";
export default function Home() {
return (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<Navbar />
<BumnNews />
<Footer />
</div>
);
}

19
app/detail/[id]/page.tsx Normal file
View File

@ -0,0 +1,19 @@
import DetailContent from "@/components/details/details-content";
import Footer from "@/components/landing-page/footer";
import Navbar from "@/components/landing-page/navbar";
import Image from "next/image";
export default function Home() {
return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-[#F2F4F3] max-w-7xl mx-auto">
<Navbar />
<div className="flex-1">
<DetailContent />
</div>
<Footer />
</div>
</div>
);
}

View File

@ -56,6 +56,8 @@ export default function DetailContent() {
null null
); );
const [selectedIndex, setSelectedIndex] = useState(0);
const [tabArticles, setTabArticles] = useState<Article[]>([]); const [tabArticles, setTabArticles] = useState<Article[]>([]);
const [activeTab, setActiveTab] = useState<TabKey>("trending"); const [activeTab, setActiveTab] = useState<TabKey>("trending");
@ -154,16 +156,17 @@ export default function DetailContent() {
useEffect(() => { useEffect(() => {
initState(); initState();
}, [page, showData, startDateValue, selectedCategories]); }, [page, showData]);
async function initState() { async function initState() {
// loading(); // loading();
const req = { const req = {
limit: showData, limit: showData,
page, page: 1,
search, search: "",
categorySlug: Array.from(selectedCategories).join(","), categorySlug: "",
sort: "desc", sort: "desc",
isPublish: true,
sortBy: "created_at", sortBy: "created_at",
}; };
@ -188,22 +191,20 @@ export default function DetailContent() {
setThumbnail(data?.thumbnailUrl); setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId); setDiseId(data?.aiArticleId);
setDetailFiles(data?.files); setDetailFiles(data?.files);
setArticleDetail(data); // <-- Add this setArticleDetail(data);
close(); close();
} }
if (!articleDetail?.files || articleDetail.files.length === 0) {
return (
<div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p>
</div>
);
}
return ( return (
<> <>
<div className="flex items-center bg-[#F2F4F3] w-full overflow-hidden mb-4 py-6 px-8">
<Image
src={"/mikul.png"}
alt="Background"
width={272}
height={90}
className="w-full md:w-[272px] h-[90px] object-cover border"
priority
/>
</div>
<div className="bg-white grid grid-cols-1 md:grid-cols-3 gap-6 px-8 py-8"> <div className="bg-white grid grid-cols-1 md:grid-cols-3 gap-6 px-8 py-8">
<div className="md:col-span-2"> <div className="md:col-span-2">
<p className="text-sm text-gray-500 mb-2">Home {">"}Detail</p> <p className="text-sm text-gray-500 mb-2">Home {">"}Detail</p>
@ -211,7 +212,7 @@ export default function DetailContent() {
{articleDetail?.title} {articleDetail?.title}
</h1> </h1>
<div className="flex items-center space-x-2 text-sm text-gray-500 mb-4"> <div className="flex items-center space-x-2 text-sm text-gray-500 mb-4">
<div className="text-[#31942E]"> <div className="text-orange-400">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
@ -231,7 +232,7 @@ export default function DetailContent() {
</svg> </svg>
</div> </div>
<span className="text-[#31942E] font-medium"> <span className="text-orange-400 font-medium">
{articleDetail?.createdByName} {articleDetail?.createdByName}
</span> </span>
<span></span> <span></span>
@ -252,19 +253,41 @@ export default function DetailContent() {
</div> </div>
<div className="w-full h-auto mb-6"> <div className="w-full h-auto mb-6">
{articleDetail?.files?.[0]?.fileUrl ? ( {/* Gambar utama */}
<div className="w-full">
<Image <Image
src={articleDetail.files[0].fileUrl} src={articleDetail.files[selectedIndex].fileUrl}
alt="Berita" alt={articleDetail.files[selectedIndex].fileAlt || "Berita"}
width={800} width={800}
height={400} height={400}
className="rounded-lg w-full object-cover" className="rounded-lg w-full object-cover"
/> />
) : ( </div>
<div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p> {/* Thumbnail */}
</div> <div className="flex gap-2 mt-3 overflow-x-auto">
)} {articleDetail.files.map((file: any, index: number) => (
<button
key={file.id || index}
onClick={() => setSelectedIndex(index)}
className={`border-2 rounded-lg overflow-hidden ${
selectedIndex === index
? "border-red-500"
: "border-transparent"
}`}
>
<Image
src={file.fileUrl}
alt={file.fileAlt || "Thumbnail"}
width={100}
height={80}
className="object-cover"
/>
</button>
))}
</div>
{/* Slug */}
<p className="text-sm text-gray-500 mt-2 text-end"> <p className="text-sm text-gray-500 mt-2 text-end">
{articleDetail?.slug} {articleDetail?.slug}
</p> </p>
@ -336,13 +359,13 @@ export default function DetailContent() {
</Link> </Link>
</div> </div>
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<p className="text-gray-700 leading-relaxed text-justify"> <div className="text-gray-700 leading-relaxed text-justify">
<span className="text-black font-bold text-md"> <div
Mikulnews.com - dangerouslySetInnerHTML={{
</span> __html: articleDetail?.htmlDescription || "",
}}
{articleDetail?.description} />
</p> </div>
<Author /> <Author />
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row gap-2 items-center">
<span className="font-semibold text-sm text-gray-700"> <span className="font-semibold text-sm text-gray-700">
@ -360,34 +383,12 @@ export default function DetailContent() {
</div> </div>
</div> </div>
</div> </div>
<div className="relative mb-2 h-[120px] overflow-hidden flex items-center border my-8">
<Image
src={"/image-kolom.png"}
alt="Berita Utama"
fill
className="object-contain"
/>
</div>
<div className="mt-10">
<div className="flex items-center space-x-4 p-4 border rounded-lg mb-6">
<Image
src={"/author.png"}
alt="Author"
width={60}
height={60}
className="rounded-full"
/>
<div>
<p className="text-green-600 font-bold text-lg">
christine natalia
</p>
</div>
</div>
<div className="mt-10">
<h2 className="text-2xl font-bold mb-2">Tinggalkan Balasan</h2> <h2 className="text-2xl font-bold mb-2">Tinggalkan Balasan</h2>
<p className="text-gray-600 mb-4 text-sm"> <p className="text-gray-600 mb-4 text-sm">
Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib
ditandai <span className="text-green-600">*</span> ditandai <span className="text-orange-600">*</span>
</p> </p>
<form className="space-y-6 mt-6"> <form className="space-y-6 mt-6">
@ -396,7 +397,7 @@ export default function DetailContent() {
htmlFor="komentar" htmlFor="komentar"
className="block text-sm font-medium mb-1" className="block text-sm font-medium mb-1"
> >
Komentar <span className="text-green-600">*</span> Komentar <span className="text-orange-600">*</span>
</label> </label>
<textarea <textarea
id="komentar" id="komentar"
@ -410,7 +411,7 @@ export default function DetailContent() {
htmlFor="nama" htmlFor="nama"
className="block text-sm font-medium mb-1" className="block text-sm font-medium mb-1"
> >
Nama <span className="text-green-600">*</span> Nama <span className="text-orange-600">*</span>
</label> </label>
<input <input
type="text" type="text"
@ -426,7 +427,7 @@ export default function DetailContent() {
htmlFor="email" htmlFor="email"
className="block text-sm font-medium mb-1" className="block text-sm font-medium mb-1"
> >
Email <span className="text-green-600">*</span> Email <span className="text-orange-600">*</span>
</label> </label>
<input <input
type="email" type="email"
@ -465,7 +466,7 @@ export default function DetailContent() {
<button <button
type="submit" type="submit"
className="bg-green-600 hover:bg-green-700 text-white font-semibold px-6 py-2 rounded-md transition mt-2" className="bg-orange-600 hover:bg-orange-700 text-white font-semibold px-6 py-2 rounded-md transition mt-2"
> >
KIRIM KOMENTAR KIRIM KOMENTAR
</button> </button>
@ -584,82 +585,64 @@ export default function DetailContent() {
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="relative"> {/* Artikel utama (featured) */}
<Image {articles.length > 0 && (
src={"/gaza.png"} <div className="relative">
alt="Recommended Article" <Image
width={400} src={articles[0]?.thumbnailUrl || "/default.jpg"}
height={200} alt={articles[0]?.title || "Recommended Article"}
className="rounded-lg w-full h-auto object-cover" width={400}
/> height={200}
<div className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-60 text-white p-3 rounded-b-lg"> className="rounded-lg w-full h-auto object-cover"
<p className="text-sm font-semibold leading-tight"> />
Bom Bunuh Diri Guncang Gereja di Damaskus, 20 Orang Tewas <div className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-60 text-white p-3 rounded-b-lg">
dan Puluhan Terluka <p className="text-sm font-semibold leading-tight">
</p> {articles[0]?.title}
<p className="text-xs text-gray-300 mt-1"> </p>
📅 23 JUNI 2025 <p className="text-xs text-gray-300 mt-1">
</p> 📅{" "}
{new Date(articles[0]?.createdAt).toLocaleDateString(
"id-ID",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</p>
</div>
</div> </div>
</div> )}
{/* List artikel lain */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex space-x-3"> {articles.slice(1, 4).map((item) => (
<Image <div key={item.id} className="flex space-x-3">
src={"/perang.png"} <Image
alt="OPM Serang Gereja" src={item.thumbnailUrl || "/default.jpg"}
width={80} alt={item.title}
height={60} width={80}
className="rounded object-cover w-[80px] h-[60px]" height={60}
/> className="rounded object-cover w-[80px] h-[60px]"
<div> />
<p className="text-sm font-semibold leading-snug"> <div>
OPM Mulai Kehilangan Simpati dari Masyarakat Papua Usai <p className="text-sm font-semibold leading-snug">
Serang Gereja {item.title}
</p> </p>
<p className="text-xs text-gray-500 mt-1"> <p className="text-xs text-gray-500 mt-1">
📅 15 JUNI 2025 📅{" "}
</p> {new Date(item.createdAt).toLocaleDateString(
"id-ID",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</p>
</div>
</div> </div>
</div> ))}
<div className="flex space-x-3">
<Image
src={"/jateng.png"}
alt="Denda Merokok"
width={80}
height={60}
className="rounded object-cover w-[80px] h-[60px]"
/>
<div>
<p className="text-sm font-semibold leading-snug">
Jakarta Terapkan Denda Rp 250.000 bagi Warga yang
Merokok Sembarangan
</p>
<p className="text-xs text-gray-500 mt-1">
📅 13 JUNI 2025
</p>
</div>
</div>
<div className="flex space-x-3">
<Image
src={"/investasi.jpg"}
alt="Pengguna Internet"
width={80}
height={60}
className="rounded object-cover w-[80px] h-[60px]"
/>
<div>
<p className="text-sm font-semibold leading-snug">
Warga Indonesia Jadi Pengguna Internet via Ponsel
Terbanyak di Dunia
</p>
<p className="text-xs text-gray-500 mt-1">
📅 26 MEI 2025
</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -197,8 +197,8 @@ const TinyMCEEditor: React.FC<TinyMCEEditorProps> = ({
height, height,
language, language,
placeholder, placeholder,
readonly: readOnly, // readonly: readOnly,
disabled, // disabled,
branding: false, branding: false,
elementpath: false, elementpath: false,
resize: false, resize: false,
@ -270,9 +270,9 @@ const TinyMCEEditor: React.FC<TinyMCEEditorProps> = ({
onInit={handleEditorInit} onInit={handleEditorInit}
initialValue={initialData} initialValue={initialData}
onEditorChange={handleEditorChange} onEditorChange={handleEditorChange}
disabled={disabled} disabled={disabled || readOnly}
apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY} apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY}
// init={editorConfig} init={editorConfig}
/> />
{/* Status bar */} {/* Status bar */}

View File

@ -524,13 +524,21 @@ export default function CreateArticleForm() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="single">Single Article</SelectItem> <SelectItem value="single">Single Article</SelectItem>
<SelectItem value="rewrite">Content Rewrite</SelectItem> {/* <SelectItem value="rewrite">Content Rewrite</SelectItem> */}
</SelectContent> </SelectContent>
</Select> </Select>
{selectedWritingType === "single" ? ( {selectedWritingType === "single" ? (
<GenerateSingleArticleForm <GenerateSingleArticleForm
content={(data) => { content={(data) => {
setDiseData(data); setDiseData(data);
// setValue("title", data?.title ?? "", {
// shouldValidate: true,
// shouldDirty: true,
// });
// setValue("slug", generateSlug(data?.title ?? ""), {
// shouldValidate: true,
// shouldDirty: true,
// });
setValue( setValue(
"description", "description",
data?.articleBody ? data?.articleBody : "" data?.articleBody ? data?.articleBody : ""

View File

@ -681,6 +681,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
</div> </div>
<Button <Button
type="button"
className=" border-none rounded-full" className=" border-none rounded-full"
variant="outline" variant="outline"
color="danger" color="danger"

View File

@ -1,11 +1,20 @@
"use client"; "use client";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { close, error, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
import { delay } from "@/utils/global"; import { delay } from "@/utils/global";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { getDetailArticle, getGenerateRewriter } from "@/service/generate-article"; import {
getDetailArticle,
getGenerateRewriter,
} from "@/service/generate-article";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import GetSeoScore from "./get-seo-score-form"; import GetSeoScore from "./get-seo-score-form";
@ -69,8 +78,11 @@ interface DiseData {
additionalKeywords: string; additionalKeywords: string;
} }
export default function GenerateContentRewriteForm(props: { content: (data: DiseData) => void }) { export default function GenerateContentRewriteForm(props: {
const [selectedWritingSyle, setSelectedWritingStyle] = useState("Informational"); content: (data: DiseData) => void;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
const [selectedArticleSize, setSelectedArticleSize] = useState("News"); const [selectedArticleSize, setSelectedArticleSize] = useState("News");
const [selectedLanguage, setSelectedLanguage] = useState("id"); const [selectedLanguage, setSelectedLanguage] = useState("id");
const [mainKeyword, setMainKeyword] = useState(""); const [mainKeyword, setMainKeyword] = useState("");
@ -166,7 +178,10 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
))} ))}
</SelectSection> </SelectSection>
</Select> */} </Select> */}
<Select value={selectedWritingSyle} onValueChange={(value) => setSelectedWritingStyle(value)}> <Select
value={selectedWritingSyle}
onValueChange={(value) => setSelectedWritingStyle(value)}
>
<SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400"> <SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400">
<SelectValue placeholder="Writing Style" /> <SelectValue placeholder="Writing Style" />
</SelectTrigger> </SelectTrigger>
@ -198,7 +213,10 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
))} ))}
</SelectSection> </SelectSection>
</Select> */} </Select> */}
<Select value={selectedArticleSize} onValueChange={(value) => setSelectedArticleSize(value)}> <Select
value={selectedArticleSize}
onValueChange={(value) => setSelectedArticleSize(value)}
>
<SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400"> <SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400">
<SelectValue placeholder="Writing Style" /> <SelectValue placeholder="Writing Style" />
</SelectTrigger> </SelectTrigger>
@ -229,7 +247,10 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
<SelectItem key="en">English</SelectItem> <SelectItem key="en">English</SelectItem>
</SelectSection> </SelectSection>
</Select> */} </Select> */}
<Select value={selectedLanguage} onValueChange={(value) => setSelectedLanguage(value)}> <Select
value={selectedLanguage}
onValueChange={(value) => setSelectedLanguage(value)}
>
<SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400"> <SelectTrigger className="w-full border rounded-lg text-black dark:border-gray-400">
<SelectValue placeholder="Writing Style" /> <SelectValue placeholder="Writing Style" />
</SelectTrigger> </SelectTrigger>
@ -239,6 +260,7 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="flex flex-col mt-3"> <div className="flex flex-col mt-3">
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row gap-2 items-center">
<p className="text-sm">Text</p> <p className="text-sm">Text</p>
@ -246,9 +268,16 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
<div className="w-[78vw] lg:w-full"> <div className="w-[78vw] lg:w-full">
<CustomEditor onChange={setMainKeyword} initialData={mainKeyword} /> <CustomEditor onChange={setMainKeyword} initialData={mainKeyword} />
</div> </div>
{mainKeyword == "" && <p className="text-red-400 text-sm">Required</p>} {mainKeyword == "" && (
<p className="text-red-400 text-sm">Required</p>
)}
{articleIds.length < 3 && ( {articleIds.length < 3 && (
<Button onClick={onSubmit} type="button" disabled={mainKeyword === "" || isLoading} className="my-5 w-full py-5 text-xs md:text-base"> <Button
onClick={onSubmit}
type="button"
disabled={mainKeyword === "" || isLoading}
className="my-5 w-full py-5 text-xs md:text-base"
>
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
@ -263,7 +292,14 @@ export default function GenerateContentRewriteForm(props: { content: (data: Dise
{articleIds.length > 0 && ( {articleIds.length > 0 && (
<div className="flex flex-row gap-1 mt-2"> <div className="flex flex-row gap-1 mt-2">
{articleIds?.map((id, index) => ( {articleIds?.map((id, index) => (
<Button key={id} onClick={() => setSelectedId(id)} disabled={isLoading && selectedId === id} variant={selectedId === id ? "default" : "outline"} className="flex items-center gap-2"> <Button
type="button"
key={id}
onClick={() => setSelectedId(id)}
disabled={isLoading && selectedId === id}
variant={selectedId === id ? "default" : "outline"}
className="flex items-center gap-2"
>
{isLoading && selectedId === id ? ( {isLoading && selectedId === id ? (
<> <>
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />

View File

@ -85,7 +85,7 @@ export default function GenerateSingleArticleForm(props: {
const [additionalKeyword, setAdditionalKeyword] = useState(""); const [additionalKeyword, setAdditionalKeyword] = useState("");
const [articleIds, setArticleIds] = useState<number[]>([]); const [articleIds, setArticleIds] = useState<number[]>([]);
const [selectedId, setSelectedId] = useState<number>(); const [selectedId, setSelectedId] = useState<number>();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(false);
const generateAll = async (keyword: string | undefined) => { const generateAll = async (keyword: string | undefined) => {
if (keyword) { if (keyword) {
@ -319,6 +319,7 @@ export default function GenerateSingleArticleForm(props: {
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row gap-2 items-center">
<p className="text-sm">Main Keyword</p> <p className="text-sm">Main Keyword</p>
<Button <Button
type="button"
variant="default" variant="default"
size="sm" size="sm"
onClick={() => generateAll(mainKeyword)} onClick={() => generateAll(mainKeyword)}
@ -350,6 +351,7 @@ export default function GenerateSingleArticleForm(props: {
<div className="flex flex-row gap-2 items-center mt-3"> <div className="flex flex-row gap-2 items-center mt-3">
<p className="text-sm">Title</p> <p className="text-sm">Title</p>
<Button <Button
type="button"
variant="default" variant="default"
size="sm" size="sm"
onClick={() => generateTitle(mainKeyword)} onClick={() => generateTitle(mainKeyword)}
@ -373,6 +375,7 @@ export default function GenerateSingleArticleForm(props: {
<div className="flex flex-row gap-2 items-center mt-2"> <div className="flex flex-row gap-2 items-center mt-2">
<p className="text-sm">Additional Keyword</p> <p className="text-sm">Additional Keyword</p>
<Button <Button
type="button"
className="text-sm" className="text-sm"
size="sm" size="sm"
onClick={() => generateKeywords(mainKeyword)} onClick={() => generateKeywords(mainKeyword)}
@ -417,6 +420,7 @@ export default function GenerateSingleArticleForm(props: {
<div className="flex flex-row gap-1 mt-2"> <div className="flex flex-row gap-1 mt-2">
{articleIds.map((id, index) => ( {articleIds.map((id, index) => (
<Button <Button
type="button"
key={id} key={id}
onClick={() => setSelectedId(id)} onClick={() => setSelectedId(id)}
disabled={isLoading && selectedId === id} disabled={isLoading && selectedId === id}

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { Calendar } from "lucide-react"; import { Calendar } from "lucide-react";
import { getListArticle } from "@/service/article"; import { getListArticle } from "@/service/article";
import Link from "next/link";
type Article = { type Article = {
id: number; id: number;
@ -83,58 +84,60 @@ export default function BreakingNews() {
<div className="w-9/12"> <div className="w-9/12">
{mainArticle && ( {mainArticle && (
<div className=""> <div className="">
<div className="grid md:grid-cols-2 gap-6 items-center"> <Link href={`/detail/${mainArticle?.id}`}>
{/* LEFT - Image */} <div className="grid md:grid-cols-2 gap-6 items-center">
<div className="relative w-full h-[220px] md:h-[300px] overflow-hidden"> {/* LEFT - Image */}
<Image <div className="relative w-full h-[220px] md:h-[300px] overflow-hidden">
src={ <Image
mainArticle.thumbnailUrl || src={
mainArticle.files?.[0]?.fileUrl || mainArticle.thumbnailUrl ||
"/placeholder.png" mainArticle.files?.[0]?.fileUrl ||
} "/placeholder.png"
alt={mainArticle.title} }
fill alt={mainArticle.title}
className="object-cover" fill
/> className="object-cover"
</div> />
{/* RIGHT - Content */}
<div className="flex flex-col justify-center">
<span className="bg-yellow-400 text-black px-2 py-1 text-xs font-bold inline-block mb-3 w-fit">
{mainArticle.categoryName?.toUpperCase()}
</span>
<h2 className="text-xl md:text-2xl font-bold mb-2 leading-snug hover:underline cursor-pointer">
{mainArticle.title}
</h2>
<div className="flex items-center gap-4 text-xs text-gray-500 mb-3">
<span className="font-semibold text-blue-600">
{mainArticle.createdByName}
</span>
<div className="flex items-center gap-1">
<Calendar size={14} />
{new Date(mainArticle.createdAt).toLocaleDateString(
"en-GB",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</div>
<span>0</span>
</div> </div>
<p className="text-sm text-gray-600 mb-4 line-clamp-3"> {/* RIGHT - Content */}
{mainArticle.description} <div className="flex flex-col justify-center">
</p> <span className="bg-yellow-400 text-black px-2 py-1 text-xs font-bold inline-block mb-3 w-fit">
{mainArticle.categoryName?.toUpperCase()}
</span>
<button className="border border-gray-400 px-4 py-2 text-xs font-semibold hover:bg-black hover:text-white transition w-fit"> <h2 className="text-xl md:text-2xl font-bold mb-2 leading-snug hover:underline cursor-pointer">
READ MORE {mainArticle.title}
</button> </h2>
<div className="flex items-center gap-4 text-xs text-gray-500 mb-3">
<span className="font-semibold text-blue-600">
{mainArticle.createdByName}
</span>
<div className="flex items-center gap-1">
<Calendar size={14} />
{new Date(mainArticle.createdAt).toLocaleDateString(
"en-GB",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</div>
<span>0</span>
</div>
<p className="text-sm text-gray-600 mb-4 line-clamp-3">
{mainArticle.description}
</p>
<button className="border border-gray-400 px-4 py-2 text-xs font-semibold hover:bg-black hover:text-white transition w-fit">
READ MORE
</button>
</div>
</div> </div>
</div> </Link>
</div> </div>
)} )}
<div className="relative mb-2 h-[120px] overflow-hidden flex items-center border my-8"> <div className="relative mb-2 h-[120px] overflow-hidden flex items-center border my-8">
@ -165,33 +168,38 @@ export default function BreakingNews() {
return ( return (
<div key={article.id} className="group"> <div key={article.id} className="group">
{/* Image */} <Link href={`/detail/${article?.id}`}>
<div className="relative w-full h-[180px] overflow-hidden mb-3"> {/* Image */}
<Image <div className="relative w-full h-[180px] overflow-hidden mb-3">
src={imageUrl} <Image
alt={article.title} src={imageUrl}
fill alt={article.title}
className="object-cover group-hover:scale-105 transition-transform duration-300" fill
/> className="object-cover group-hover:scale-105 transition-transform duration-300"
<span className="absolute bottom-2 left-2 bg-yellow-400 text-black px-2 py-1 text-[10px] font-bold"> />
{category.toUpperCase()} <span className="absolute bottom-2 left-2 bg-yellow-400 text-black px-2 py-1 text-[10px] font-bold">
</span> {category.toUpperCase()}
</div> </span>
</div>
{/* Title */} {/* Title */}
<h3 className="font-semibold text-sm md:text-base mb-2 leading-snug group-hover:underline cursor-pointer"> <h3 className="font-semibold text-sm md:text-base mb-2 leading-snug group-hover:underline cursor-pointer">
{article.title} {article.title}
</h3> </h3>
{/* Date */} {/* Date */}
<div className="flex items-center gap-2 text-xs text-gray-500"> <div className="flex items-center gap-2 text-xs text-gray-500">
<Calendar size={14} /> <Calendar size={14} />
{new Date(article.createdAt).toLocaleDateString("en-GB", { {new Date(article.createdAt).toLocaleDateString(
day: "2-digit", "en-GB",
month: "long", {
year: "numeric", day: "2-digit",
})} month: "long",
</div> year: "numeric",
}
)}
</div>
</Link>
</div> </div>
); );
})} })}
@ -219,48 +227,50 @@ export default function BreakingNews() {
"/placeholder.png"; "/placeholder.png";
return ( return (
<div <div key={article.id}>
key={article.id} <Link
className="grid grid-cols-1 md:grid-cols-3 gap-4" className="grid grid-cols-1 md:grid-cols-3 gap-4"
> href={`/detail/${article?.id}`}
{/* LEFT - Image */} >
<div className="relative w-full h-[180px] md:h-[140px] rounded-md overflow-hidden md:col-span-1"> {/* LEFT - Image */}
<Image <div className="relative w-full h-[180px] md:h-[140px] rounded-md overflow-hidden md:col-span-1">
src={imageUrl} <Image
alt={article.title} src={imageUrl}
fill alt={article.title}
className="object-cover" fill
/> className="object-cover"
</div> />
{/* RIGHT - Content */}
<div className="md:col-span-2 flex flex-col justify-center">
<h3 className="font-bold text-lg md:text-xl mb-2 leading-snug hover:underline cursor-pointer">
{article.title}
</h3>
<div className="flex items-center gap-4 text-xs text-gray-500 mb-2">
<span className="font-semibold text-blue-600">
{article.createdByName || "DODDODYDOD"}
</span>
<div className="flex items-center gap-1">
<Calendar size={14} />
{new Date(article.createdAt).toLocaleDateString(
"en-GB",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</div>
<span>0</span>
</div> </div>
<p className="text-sm text-gray-600 line-clamp-2"> {/* RIGHT - Content */}
{article.description} <div className="md:col-span-2 flex flex-col justify-center">
</p> <h3 className="font-bold text-lg md:text-xl mb-2 leading-snug hover:underline cursor-pointer">
</div> {article.title}
</h3>
<div className="flex items-center gap-4 text-xs text-gray-500 mb-2">
<span className="font-semibold text-blue-600">
{article.createdByName || "DODDODYDOD"}
</span>
<div className="flex items-center gap-1">
<Calendar size={14} />
{new Date(article.createdAt).toLocaleDateString(
"en-GB",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
</div>
<span>0</span>
</div>
<p className="text-sm text-gray-600 line-clamp-2">
{article.description}
</p>
</div>
</Link>
</div> </div>
); );
})} })}
@ -291,31 +301,36 @@ export default function BreakingNews() {
<div className="space-y-4"> <div className="space-y-4">
{sideArticles.map((a) => ( {sideArticles.map((a) => (
<div key={a.id} className="flex gap-3 items-center"> <div key={a.id} className="flex gap-3 items-center">
<div className="relative w-20 h-16 flex-shrink-0 rounded overflow-hidden"> <Link
<Image className="flex gap-3 items-center"
src={ href={`/detail/${a?.id}`}
a.thumbnailUrl || >
a.files?.[0]?.fileUrl || <div className="relative w-20 h-16 flex-shrink-0 rounded overflow-hidden">
"/placeholder.png" <Image
} src={
alt={a.title} a.thumbnailUrl ||
fill a.files?.[0]?.fileUrl ||
className="object-cover" "/placeholder.png"
/> }
</div> alt={a.title}
<div className="flex-1"> fill
<h4 className="text-sm font-semibold line-clamp-2 hover:underline"> className="object-cover"
{a.title} />
</h4>
<div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
<Calendar size={12} />
{new Date(a.createdAt).toLocaleDateString("en-GB", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</div> </div>
</div> <div className="flex-1">
<h4 className="text-sm font-semibold line-clamp-2 hover:underline">
{a.title}
</h4>
<div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
<Calendar size={12} />
{new Date(a.createdAt).toLocaleDateString("en-GB", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</div>
</div>
</Link>
</div> </div>
))} ))}
</div> </div>
@ -365,48 +380,55 @@ export default function BreakingNews() {
{/* Artikel Utama (Top 1) */} {/* Artikel Utama (Top 1) */}
{popularArticles.length > 0 && ( {popularArticles.length > 0 && (
<div className="mb-6"> <div className="mb-6">
<div className="relative w-full h-[220px] md:h-[280px] rounded overflow-hidden"> <Link href={`/detail/${popularArticles[0]?.id}`}>
<Image <div className="relative w-full h-[220px] md:h-[280px] rounded overflow-hidden">
src={ <Image
popularArticles[0].thumbnailUrl || src={
popularArticles[0].files?.[0]?.fileUrl || popularArticles[0].thumbnailUrl ||
"/placeholder.png" popularArticles[0].files?.[0]?.fileUrl ||
} "/placeholder.png"
alt={popularArticles[0].title} }
fill alt={popularArticles[0].title}
className="object-cover" fill
/> className="object-cover"
</div> />
<div className="flex items-start justify-between mt-3"> </div>
<h3 className="font-bold text-base md:text-lg leading-snug max-w-[85%]"> <div className="flex items-start justify-between mt-3">
{popularArticles[0].title} <h3 className="font-bold text-base md:text-lg leading-snug max-w-[85%]">
</h3> {popularArticles[0].title}
<span className="text-3xl font-bold text-gray-300">01</span> </h3>
</div> <span className="text-3xl font-bold text-gray-300">01</span>
<div className="flex items-center gap-2 text-xs text-gray-500 mt-1"> </div>
<span>154 Shares</span> <div className="flex items-center gap-2 text-xs text-gray-500 mt-1">
</div> <span>154 Shares</span>
</div>
</Link>
</div> </div>
)} )}
{/* Artikel 2 - 5 */} {/* Artikel 2 - 5 */}
<div className="space-y-4"> <div className="space-y-4">
{popularArticles.slice(1, 5).map((a, i) => ( {popularArticles.slice(1, 5).map((a, i) => (
<div key={a.id} className="flex items-center gap-3"> <div key={a.id}>
{/* Nomor Urut */} <Link
<div className="w-10 h-10 flex items-center justify-center rounded-full border text-sm font-bold text-gray-500"> className="flex items-center gap-3"
{String(i + 2).padStart(2, "0")} href={`/detail/${a?.id}`}
</div> >
{/* Nomor Urut */}
{/* Judul & Info */} <div className="w-10 h-10 flex items-center justify-center rounded-full border text-sm font-bold text-gray-500">
<div className="flex-1"> {String(i + 2).padStart(2, "0")}
<h4 className="text-sm font-semibold line-clamp-2 hover:underline">
{a.title}
</h4>
<div className="flex items-center gap-2 text-xs text-gray-400 mt-1">
<span>{154 - i} Shares</span>
</div> </div>
</div>
{/* Judul & Info */}
<div className="flex-1">
<h4 className="text-sm font-semibold line-clamp-2 hover:underline">
{a.title}
</h4>
<div className="flex items-center gap-2 text-xs text-gray-400 mt-1">
<span>{154 - i} Shares</span>
</div>
</div>
</Link>
</div> </div>
))} ))}
</div> </div>

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { Calendar } from "lucide-react"; import { Calendar } from "lucide-react";
import { getListArticle } from "@/service/article"; import { getListArticle } from "@/service/article";
import Link from "next/link";
type Article = { type Article = {
id: number; id: number;
@ -72,38 +73,40 @@ export default function Header() {
key={article.id} key={article.id}
className="relative h-[438px] md:h-[438px] overflow-hidden" className="relative h-[438px] md:h-[438px] overflow-hidden"
> >
{/* Background Image */} <Link href={`/detail/${article?.id}`}>
<Image {/* Background Image */}
src={imageUrl} <Image
alt={article.title} src={imageUrl}
fill alt={article.title}
className="object-cover" fill
/> className="object-cover"
/>
{/* Overlay Warna */} {/* Overlay Warna */}
<div <div
className={`absolute inset-0 ${ className={`absolute inset-0 ${
overlayColors[idx] || "bg-black/50" overlayColors[idx] || "bg-black/50"
}`} }`}
></div> ></div>
{/* Content */} {/* Content */}
<div className="absolute inset-0 flex flex-col items-center justify-center p-6 text-white text-center"> <div className="absolute inset-0 flex flex-col items-center justify-center p-6 text-white text-center">
<span className="bg-yellow-400 text-black px-2 py-1 text-xs font-bold inline-block mb-3"> <span className="bg-yellow-400 text-black px-2 py-1 text-xs font-bold inline-block mb-3">
{category.toUpperCase()} {category.toUpperCase()}
</span> </span>
<h3 className="text-lg md:text-2xl font-bold mb-3 leading-snug"> <h3 className="text-lg md:text-2xl font-bold mb-3 leading-snug">
{article.title} {article.title}
</h3> </h3>
<div className="flex items-center gap-2 text-xs md:text-sm text-gray-200"> <div className="flex items-center gap-2 text-xs md:text-sm text-gray-200">
<Calendar size={14} /> <Calendar size={14} />
{new Date(article.createdAt).toLocaleDateString("en-GB", { {new Date(article.createdAt).toLocaleDateString("en-GB", {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
})} })}
</div>
</div> </div>
</div> </Link>
</div> </div>
); );
})} })}

View File

@ -3,6 +3,8 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { getListArticle } from "@/service/article"; import { getListArticle } from "@/service/article";
import Link from "next/link";
import { usePathname } from "next/navigation";
type Article = { type Article = {
id: number; id: number;
@ -24,7 +26,19 @@ type Article = {
export default function BumnNews() { export default function BumnNews() {
const [articles, setArticles] = useState<Article[]>([]); const [articles, setArticles] = useState<Article[]>([]);
const [popular, setPopular] = useState<Article[]>([]); const [popular, setPopular] = useState<Article[]>([]);
const [categoryName, setCategoryName] = useState("Bumn News"); const pathname = usePathname(); // ⬅️ "/bumn-news" misalnya
const categoryName =
pathname
.split("/")
.filter(Boolean)
.pop()
?.replace(/-/g, " ") // ubah slug jadi teks rapi
?.replace(/\b\w/g, (c) => c.toUpperCase()) || "News";
useEffect(() => {
fetchArticles();
fetchPopular();
}, []);
useEffect(() => { useEffect(() => {
fetchArticles(); fetchArticles();
@ -84,30 +98,35 @@ export default function BumnNews() {
<h1 className="text-3xl font-bold">{categoryName}</h1> <h1 className="text-3xl font-bold">{categoryName}</h1>
</div> </div>
{articles.map((item) => ( {articles.map((item) => (
<div key={item.id} className="flex gap-4 border-b pb-4"> <div key={item.id}>
<Image <Link
src={item.thumbnailUrl || "/dummy.jpg"} className="flex gap-4 border-b pb-4"
alt={item.title} href={`/detail/${item?.id}`}
width={160} >
height={100} <Image
className="object-cover rounded-md w-40 h-28" src={item.thumbnailUrl || "/dummy.jpg"}
/> alt={item.title}
<div className="flex-1"> width={160}
<h3 className="text-base font-semibold hover:text-blue-600 cursor-pointer"> height={100}
{item.title} className="object-cover rounded-md w-40 h-28"
</h3> />
<p className="text-xs text-gray-500 mt-1"> <div className="flex-1">
By {item.createdByName} {" "} <h3 className="text-base font-semibold hover:text-blue-600 cursor-pointer">
{new Date(item.createdAt).toLocaleDateString("id-ID", { {item.title}
day: "numeric", </h3>
month: "long", <p className="text-xs text-gray-500 mt-1">
year: "numeric", By {item.createdByName} {" "}
})} {new Date(item.createdAt).toLocaleDateString("id-ID", {
</p> day: "numeric",
<p className="text-sm text-gray-600 mt-2 line-clamp-2"> month: "long",
{item.description} year: "numeric",
</p> })}
</div> </p>
<p className="text-sm text-gray-600 mt-2 line-clamp-2">
{item.description}
</p>
</div>
</Link>
</div> </div>
))} ))}
</div> </div>
@ -147,43 +166,47 @@ export default function BumnNews() {
<div className="space-y-5"> <div className="space-y-5">
{/* Item pertama tampil besar */} {/* Item pertama tampil besar */}
<div className="relative"> <div className="relative">
<Image <Link href={`/detail/${popular[0]?.id}`}>
src={popular[0]?.files?.[0]?.fileUrl || "/dummy.jpg"} <Image
alt={ src={popular[0]?.files?.[0]?.fileUrl || "/dummy.jpg"}
popular[0]?.files?.[0]?.file_alt || alt={
popular[0]?.title || popular[0]?.files?.[0]?.file_alt ||
"No Title" popular[0]?.title ||
} "No Title"
width={400} }
height={200} width={400}
className="w-full h-48 object-cover rounded-md" height={200}
/> className="w-full h-48 object-cover rounded-md"
<div className="mt-2"> />
<h5 className="text-sm font-semibold hover:text-blue-600 cursor-pointer"> <div className="mt-2">
{popular[0]?.title} <h5 className="text-sm font-semibold hover:text-blue-600 cursor-pointer">
</h5> {popular[0]?.title}
<span className="absolute top-2 right-2 text-4xl font-bold text-gray-300/80"> </h5>
01 <span className="absolute top-2 right-2 text-4xl font-bold text-gray-300/80">
</span> 01
</div> </span>
</div>
</Link>
</div> </div>
{/* Item sisanya */} {/* Item sisanya */}
<div className="space-y-4"> <div className="space-y-4">
{popular.slice(1).map((item, i) => ( {popular.slice(1).map((item, i) => (
<div <div key={item.id}>
key={item.id} <Link
className="flex gap-3 items-start border-b pb-2 last:border-b-0" className="flex gap-3 items-start border-b pb-2 last:border-b-0"
> href={`/detail/${item?.id}`}
<span className="text-lg font-bold text-gray-400"> >
0{i + 2} <span className="text-lg font-bold text-gray-400">
</span> 0{i + 2}
<div> </span>
<h5 className="text-sm font-medium hover:text-blue-600 cursor-pointer"> <div>
{item.title} <h5 className="text-sm font-medium hover:text-blue-600 cursor-pointer">
</h5> {item.title}
<p className="text-xs text-gray-400">0 Shares</p> </h5>
</div> <p className="text-xs text-gray-400">0 Shares</p>
</div>
</Link>
</div> </div>
))} ))}
</div> </div>