From ad064d0dd6785b3cb194619a9df3f5af6694db41 Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Mon, 2 Feb 2026 13:40:41 +0800 Subject: [PATCH] fix:landingpage --- app/page.tsx | 18 +- components/details/details-content.tsx | 23 +- .../form/article/create-article-form.tsx | 26 +- components/form/article/edit-article-form.tsx | 92 ++++-- components/landing-page/development.tsx | 136 ++++++++ components/landing-page/footer.tsx | 99 +++--- components/landing-page/header.tsx | 297 ++++++++++++------ components/landing-page/health.tsx | 186 +++++++++++ components/landing-page/latest-news.tsx | 126 ++++++++ components/landing-page/navbar.tsx | 240 +++++++++----- components/landing-page/opinion-news.tsx | 126 ++++++++ components/landing-page/youtube-selection.tsx | 151 +++++++++ public/isukini-logo.jpg | Bin 0 -> 193747 bytes public/yt-logo.png | Bin 0 -> 19227 bytes service/article.ts | 17 +- 15 files changed, 1239 insertions(+), 298 deletions(-) create mode 100644 components/landing-page/development.tsx create mode 100644 components/landing-page/health.tsx create mode 100644 components/landing-page/latest-news.tsx create mode 100644 components/landing-page/opinion-news.tsx create mode 100644 components/landing-page/youtube-selection.tsx create mode 100644 public/isukini-logo.jpg create mode 100644 public/yt-logo.png diff --git a/app/page.tsx b/app/page.tsx index d4c5607..a431252 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,20 +1,26 @@ +import Development from "@/components/landing-page/development"; import Footer from "@/components/landing-page/footer"; import Header from "@/components/landing-page/header"; +import NewsTerkini from "@/components/landing-page/health"; +import News from "@/components/landing-page/latest-news"; import Navbar from "@/components/landing-page/navbar"; -import News from "@/components/landing-page/news"; -import Opini from "@/components/landing-page/opini"; -import PopularNews from "@/components/landing-page/popular-news"; + +import OpinionNews from "@/components/landing-page/opinion-news"; +import YouTubeSection from "@/components/landing-page/youtube-selection"; export default function Home() { return ( -
+
+ {/* Background fixed tidak ikut scroll */}
- - + + + +
); diff --git a/components/details/details-content.tsx b/components/details/details-content.tsx index 3440aed..49e3617 100644 --- a/components/details/details-content.tsx +++ b/components/details/details-content.tsx @@ -80,7 +80,7 @@ export default function DetailContent() { const [diseId, setDiseId] = useState(0); const [thumbnailImg, setThumbnailImg] = useState([]); const [selectedMainImage, setSelectedMainImage] = useState( - null + null, ); const [selectedIndex, setSelectedIndex] = useState(0); @@ -371,15 +371,18 @@ export default function DetailContent() { - - {new Date( - articleDetail?.publishedAt ?? articleDetail?.createdAt - ).toLocaleDateString("id-ID", { + {new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt) + .toLocaleString("id-ID", { day: "numeric", month: "long", year: "numeric", - })} - + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Asia/Jakarta", + }) + .replace("pukul ", "")}{" "} + WIB {articleDetail?.categories?.[0]?.title} @@ -496,7 +499,7 @@ export default function DetailContent() {
@@ -846,7 +849,7 @@ export default function DetailContent() { day: "2-digit", month: "long", year: "numeric", - } + }, )}

@@ -876,7 +879,7 @@ export default function DetailContent() { day: "2-digit", month: "long", year: "numeric", - } + }, )}

diff --git a/components/form/article/create-article-form.tsx b/components/form/article/create-article-form.tsx index d920ed7..b47c841 100644 --- a/components/form/article/create-article-form.tsx +++ b/components/form/article/create-article-form.tsx @@ -56,7 +56,7 @@ const CustomEditor = dynamic( () => { return import("@/components/editor/custom-editor"); }, - { ssr: false } + { ssr: false }, ); interface FileWithPreview extends File { @@ -118,14 +118,14 @@ export default function CreateArticleForm() { const [tag, setTag] = useState(""); const [thumbnailImg, setThumbnailImg] = useState([]); const [selectedMainImage, setSelectedMainImage] = useState( - null + null, ); const [thumbnailValidation, setThumbnailValidation] = useState(""); const [filesValidation, setFileValidation] = useState(""); const [diseData, setDiseData] = useState(); const [selectedWritingType, setSelectedWritingType] = useState("single"); const [status, setStatus] = useState<"publish" | "draft" | "scheduled">( - "publish" + "publish", ); const [isScheduled, setIsScheduled] = useState(false); const [startDateValue, setStartDateValue] = useState(); @@ -230,7 +230,7 @@ export default function CreateArticleForm() { } const saveArticleToDise = async ( - values: z.infer + values: z.infer, ) => { if (useAi) { const request = { @@ -351,12 +351,12 @@ export default function CreateArticleForm() { // format: 2025-10-08 14:30:00 const formattedDateTime = `${combinedDate.getFullYear()}-${String( - combinedDate.getMonth() + 1 + combinedDate.getMonth() + 1, ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( 2, - "0" + "0", )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( - combinedDate.getMinutes() + combinedDate.getMinutes(), ).padStart(2, "0")}:00`; const request = { @@ -493,7 +493,7 @@ export default function CreateArticleForm() { } } const uniqueArray = temp.filter( - (item, index) => temp.indexOf(item) === index + (item, index) => temp.indexOf(item) === index, ); setValue("tags", uniqueArray as [string, ...string[]]); @@ -514,7 +514,7 @@ export default function CreateArticleForm() { id="title" type="text" placeholder="Masukkan judul artikel" - className="w-full border rounded-lg dark:border-gray-400" + className="h-16 px-4 text-2xl leading-tight" {...field} /> )} @@ -578,7 +578,7 @@ export default function CreateArticleForm() { // }); setValue( "description", - data?.articleBody ? data?.articleBody : "" + data?.articleBody ? data?.articleBody : "", ); }} /> @@ -588,7 +588,7 @@ export default function CreateArticleForm() { setDiseData(data); setValue( "description", - data?.articleBody ? data?.articleBody : "" + data?.articleBody ? data?.articleBody : "", ); }} /> @@ -781,7 +781,7 @@ export default function CreateArticleForm() { type="button" onClick={() => { const filteredTags = value.filter( - (tag: string) => tag !== item + (tag: string) => tag !== item, ); if (filteredTags.length === 0) { setError("tags", { @@ -792,7 +792,7 @@ export default function CreateArticleForm() { clearErrors("tags"); setValue( "tags", - filteredTags as [string, ...string[]] + filteredTags as [string, ...string[]], ); } }} diff --git a/components/form/article/edit-article-form.tsx b/components/form/article/edit-article-form.tsx index b28e391..b12da46 100644 --- a/components/form/article/edit-article-form.tsx +++ b/components/form/article/edit-article-form.tsx @@ -23,6 +23,7 @@ import { deleteArticleFiles, getArticleByCategory, getArticleById, + getArticleFiles, submitApproval, unPublishArticle, updateArticle, @@ -64,13 +65,13 @@ const ViewEditor = dynamic( () => { return import("@/components/editor/view-editor"); }, - { ssr: false } + { ssr: false }, ); const CustomEditor = dynamic( () => { return import("@/components/editor/custom-editor"); }, - { ssr: false } + { ssr: false }, ); interface FileWithPreview extends File { @@ -141,7 +142,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) { const [diseId, setDiseId] = useState(0); const [thumbnailImg, setThumbnailImg] = useState([]); const [selectedMainImage, setSelectedMainImage] = useState( - null + null, ); const [thumbnailValidation, setThumbnailValidation] = useState(""); // const { isOpen, onOpen, onOpenChange } = useDisclosure(); @@ -154,7 +155,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) { // const [startDateValue, setStartDateValue] = useState(null); // const [timeValue, setTimeValue] = useState("00:00"); const [status, setStatus] = useState<"publish" | "draft" | "scheduled">( - "publish" + "publish", ); const [isScheduled, setIsScheduled] = useState(false); const [startDateValue, setStartDateValue] = useState(); @@ -196,27 +197,49 @@ export default function EditArticleForm(props: { isDetail: boolean }) { async function initState() { loading(); - const res = await getArticleById(id); - const data = res.data?.data; - setDetailData(data); - setValue("title", data?.title); - setValue("customCreatorName", data?.customCreatorName); - setValue("slug", data?.slug); - setValue("source", data?.source); - const cleanDescription = data?.htmlDescription - ? data.htmlDescription - .replace(/\\"/g, '"') - .replace(/\\n/g, "\n", "\\") - .trim() - : ""; - setValue("description", cleanDescription); - setValue("tags", data?.tags ? data.tags.split(",") : []); - setThumbnail(data?.thumbnailUrl); - setDiseId(data?.aiArticleId); - setDetailFiles(data?.files); + try { + // 1️⃣ Ambil ARTICLE + const articleRes = await getArticleById(id); + const articleData = articleRes.data?.data; - setupInitCategory(data?.categories); - close(); + if (!articleData) return; + + // ===== ARTICLE DATA ===== + setDetailData(articleData); + setValue("title", articleData.title); + setValue("customCreatorName", articleData.customCreatorName); + setValue("slug", articleData.slug); + setValue("source", articleData.source); + + const cleanDescription = articleData.htmlDescription + ? articleData.htmlDescription + .replace(/\\"/g, '"') + .replace(/\\n/g, "\n") + .trim() + : ""; + + setValue("description", cleanDescription); + setValue("tags", articleData.tags ? articleData.tags.split(",") : []); + + setThumbnail(articleData.thumbnailUrl); + setDiseId(articleData.aiArticleId); + setupInitCategory(articleData.categories); + + // 2️⃣ Ambil SEMUA article files + const filesRes = await getArticleFiles(); + const allFiles = filesRes.data?.data ?? []; + + // 3️⃣ FILTER berdasarkan ARTICLE ID yang sedang dibuka + const filteredFiles = allFiles.filter( + (file: any) => file.articleId === articleData.id, + ); + + setDetailFiles(filteredFiles); + } catch (error) { + console.error("Init state error:", error); + } finally { + close(); + } } const setupInitCategory = (data: any) => { @@ -329,12 +352,12 @@ export default function EditArticleForm(props: { isDetail: boolean }) { combinedDate.setHours(hours, minutes, 0, 0); const formattedDateTime = `${combinedDate.getFullYear()}-${String( - combinedDate.getMonth() + 1 + combinedDate.getMonth() + 1, ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( 2, - "0" + "0", )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( - combinedDate.getMinutes() + combinedDate.getMinutes(), ).padStart(2, "0")}:00`; const response = await updateArticle(String(id), { @@ -425,12 +448,12 @@ export default function EditArticleForm(props: { isDetail: boolean }) { // format: 2025-10-08 14:30:00 const formattedDateTime = `${combinedDate.getFullYear()}-${String( - combinedDate.getMonth() + 1 + combinedDate.getMonth() + 1, ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( 2, - "0" + "0", )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( - combinedDate.getMinutes() + combinedDate.getMinutes(), ).padStart(2, "0")}:00`; const request = { @@ -667,9 +690,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) { name="title" render={({ field: { onChange, value } }) => (
-
)} @@ -1046,7 +1070,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) { className="w-3 h-3 cursor-pointer" onClick={() => { const filteredTags = value.filter( - (tag: string) => tag !== item + (tag: string) => tag !== item, ); if (filteredTags.length === 0) { setError("tags", { @@ -1057,7 +1081,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) { clearErrors("tags"); setValue( "tags", - filteredTags as [string, ...string[]] + filteredTags as [string, ...string[]], ); } }} diff --git a/components/landing-page/development.tsx b/components/landing-page/development.tsx new file mode 100644 index 0000000..675e13a --- /dev/null +++ b/components/landing-page/development.tsx @@ -0,0 +1,136 @@ +"use client"; +import { useEffect, useState } from "react"; +import Image from "next/image"; +import { getListArticle } from "@/service/article"; +import Link from "next/link"; + +type Article = { + id: number; + title: string; + description: string; + categoryName: string; + slug: string; + createdAt: string; + publishedAt: string; + createdByName: string; + customCreatorName: string; + thumbnailUrl: string; + categories: { title: string }[]; + files: { fileUrl: string; file_alt: string }[]; +}; + +export default function Development() { + const [articles, setArticles] = useState([]); + const [page, setPage] = useState(1); + const [totalPage, setTotalPage] = useState(1); + + useEffect(() => { + initState(); + }, [page]); + + async function initState() { + const req = { + limit: "10", + page, + search: "", + categorySlug: "", + sort: "desc", + isPublish: true, + sortBy: "created_at", + }; + + try { + const res = await getListArticle(req); + setArticles(res?.data?.data || []); + setTotalPage(res?.data?.meta?.totalPage || 1); + } catch (err) { + console.error("Error fetching articles:", err); + } + } + + // Format tanggal ke gaya lokal + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("id-ID", { + day: "2-digit", + month: "long", + year: "numeric", + }); + }; + + // Mapping struktur seperti dummy sebelumnya + const leftMain = articles[0]; + const leftList = articles.slice(1, 4); + const centerMain = articles[4]; + const centerList = articles.slice(5, 8); + const rightMain = articles[8]; + const rightList = articles.slice(9, 12); + + return ( +
+
+
+

JAGA NEGERI

+ +
+
+ +
+ +
+ {articles.slice(0, 6).map((item) => ( + +
+ {item.title} +
+ +
+

+ + BERITA OPINI + +

+ +

+ {item.title} +

+ +

+ By{" "} + + {item.customCreatorName} + +

+ +

+ {new Date(item.publishedAt).toLocaleDateString("id-ID", { + day: "numeric", + month: "long", + year: "numeric", + })} +

+
+ + ))} +
+
+ Berita Utama +
+
+
+ ); +} diff --git a/components/landing-page/footer.tsx b/components/landing-page/footer.tsx index 5f94520..199dc79 100644 --- a/components/landing-page/footer.tsx +++ b/components/landing-page/footer.tsx @@ -1,69 +1,66 @@ // components/Footer.tsx -import { Facebook, Twitter, Instagram, Youtube } from "lucide-react"; import Image from "next/image"; +import { Facebook, Twitter, Instagram, Youtube } from "lucide-react"; export default function Footer() { return ( -