fix:landingpage

This commit is contained in:
Anang Yusman 2026-02-02 13:40:41 +08:00
parent 7460a50e55
commit ad064d0dd6
15 changed files with 1239 additions and 298 deletions

View File

@ -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 (
<div className="flex min-h-screen flex-col font-[family-name:var(--font-geist-sans)] bg-white">
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
{/* Background fixed tidak ikut scroll */}
<Navbar />
<div className="flex-1">
<Header />
</div>
<News />
<Opini />
<PopularNews />
<Development />
<OpinionNews />
<NewsTerkini />
<YouTubeSection />
<Footer />
</div>
);

View File

@ -80,7 +80,7 @@ export default function DetailContent() {
const [diseId, setDiseId] = useState(0);
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null
null,
);
const [selectedIndex, setSelectedIndex] = useState(0);
@ -371,15 +371,18 @@ export default function DetailContent() {
</span>
<span></span>
<span>
<span>
{new Date(
articleDetail?.publishedAt ?? articleDetail?.createdAt
).toLocaleDateString("id-ID", {
{new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt)
.toLocaleString("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Asia/Jakarta",
})
.replace("pukul ", "")}{" "}
WIB
</span>
<span></span>
<span>{articleDetail?.categories?.[0]?.title}</span>
@ -496,7 +499,7 @@ export default function DetailContent() {
<div
dangerouslySetInnerHTML={{
__html: decodeHtmlString(
articleDetail?.htmlDescription || ""
articleDetail?.htmlDescription || "",
),
}}
/>
@ -846,7 +849,7 @@ export default function DetailContent() {
day: "2-digit",
month: "long",
year: "numeric",
}
},
)}
</p>
</div>
@ -876,7 +879,7 @@ export default function DetailContent() {
day: "2-digit",
month: "long",
year: "numeric",
}
},
)}
</p>
</div>

View File

@ -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<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null
null,
);
const [thumbnailValidation, setThumbnailValidation] = useState("");
const [filesValidation, setFileValidation] = useState("");
const [diseData, setDiseData] = useState<DiseData>();
const [selectedWritingType, setSelectedWritingType] = useState("single");
const [status, setStatus] = useState<"publish" | "draft" | "scheduled">(
"publish"
"publish",
);
const [isScheduled, setIsScheduled] = useState(false);
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
@ -230,7 +230,7 @@ export default function CreateArticleForm() {
}
const saveArticleToDise = async (
values: z.infer<typeof createArticleSchema>
values: z.infer<typeof createArticleSchema>,
) => {
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[]],
);
}
}}

View File

@ -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<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
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<any>(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<Date | undefined>();
@ -196,28 +197,50 @@ 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
try {
// 1⃣ Ambil ARTICLE
const articleRes = await getArticleById(id);
const articleData = articleRes.data?.data;
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", "\\")
.replace(/\\n/g, "\n")
.trim()
: "";
setValue("description", cleanDescription);
setValue("tags", data?.tags ? data.tags.split(",") : []);
setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files);
setupInitCategory(data?.categories);
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) => {
const temp: CategoryType[] = [];
@ -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 } }) => (
<div className="w-full">
<label htmlFor="title" className="block text-sm font-medium mb-1">
<label htmlFor="title" className="block text-xl font-medium mb-2">
Judul
</label>
<Input
type="text"
id="title"
@ -677,7 +701,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
value={value ?? ""}
readOnly={isDetail}
onChange={onChange}
className="w-full border rounded-lg"
className="h-16 px-4 text-2xl leading-tight"
/>
</div>
)}
@ -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[]],
);
}
}}

View File

@ -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<Article[]>([]);
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 (
<section className="max-w-7xl mx-auto px-4">
<div className="bg-white ">
<div className="mb-4">
<h2 className="text-xl font-black text-[#000]">JAGA NEGERI</h2>
<div className="w-10 h-1 bg-orange-600 mt-1 rounded"></div>
</div>
<div className="border-b border-gray-300 mb-5"></div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{articles.slice(0, 6).map((item) => (
<Link
href={`/details/${item.slug}`}
key={item.id}
className="flex gap-4 pb-4 border-b border-gray-200"
>
<div className="relative w-28 h-28 rounded-md overflow-hidden flex-shrink-0">
<Image
src={item.thumbnailUrl || "/placeholder.jpg"}
alt={item.title}
fill
className="object-cover"
/>
</div>
<div className="flex flex-col">
<p className="text-[11px] font-bold text-black">
<span className="border-b-2 border-orange-600 pb-[1px]">
BERITA OPINI
</span>
</p>
<h3 className="font-semibold text-[15px] leading-tight mt-1 line-clamp-2">
{item.title}
</h3>
<p className="text-[11px] text-gray-600 mt-2">
By{" "}
<span className="font-semibold">
{item.customCreatorName}
</span>
</p>
<p className="text-[11px] text-gray-600">
{new Date(item.publishedAt).toLocaleDateString("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
})}
</p>
</div>
</Link>
))}
</div>
<div className="relative h-[140px] w-full overflow-hidden rounded-none my-5">
<Image
src="/image-kolom.png"
alt="Berita Utama"
fill
className="object-fill"
/>
</div>
</div>
</section>
);
}

View File

@ -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 (
<footer className="bg-black text-white py-10 w-full">
<div className=" mx-auto flex flex-col items-center justify-between px-14">
<div className="flex justify-center ">
<footer className="bg-[#ECEFF5] pt-20 pb-10 w-full">
<div className="max-w-screen-xl mx-auto px-6 grid grid-cols-1 md:grid-cols-2 ">
{/* Logo */}
<div className="flex justify-center md:justify-end">
<Image
src="/isukini.jpg"
alt="Kritik Tajam Logo"
width={1080}
height={1000}
className="object-contain ml-0 md:ml-10"
src="/isukini-logo.jpg"
alt="Logo"
width={230}
height={230}
className="object-contain"
/>
</div>
<div className="w-full md:w-5/12">
<p className="text-[#666666] mb-2 text-center text-sm font-serif">
IsuKini.com adalah sebuah media online yang memiliki fokus utama
pada pemberitaan dan pencerahan terkait berbagai isu sosial yang
relevan. Dalam era informasi yang begitu cepat dan dinamis,
IsuKini.com hadir sebagai sumber berita yang mendalam dan mendalam
dalam menyoroti permasalahan sosial yang memengaruhi masyarakat.
</p>
<p className="text-[#666666] my-6 text-sm font-serif text-center">
Contact us:{" "}
<span className="text-[#795548]">contact@isukini.com</span>
</p>
</div>
{/* Subscribe Box */}
<div className="flex justify-center md:justify-end">
<div className=" p-8 w-full md:w-[420px]">
<h2 className="text-2xl font-semibold text-gray-800 leading-snug">
Subscribe us to get <br />
the latest news!
</h2>
{/* Follow Us */}
<div>
<div className="flex space-x-4">
<a
href="#"
className="bg-blue-700 w-10 h-10 flex items-center justify-center"
>
<Facebook size={20} />
</a>
<a
href="#"
className="bg-sky-500 w-10 h-10 flex items-center justify-center"
>
<Twitter size={20} />
</a>
<a
href="#"
className="bg-gradient-to-br from-yellow-500 via-pink-500 to-purple-600 w-10 h-10 flex items-center justify-center"
>
<Instagram size={20} />
</a>
<a
href="#"
className="bg-red-600 w-10 h-10 flex items-center justify-center"
>
<Youtube size={20} />
</a>
<label className="block mt-6 mb-1 text-sm text-gray-600">
Email address:
</label>
<input
type="email"
placeholder="Your email address"
className="w-full border border-gray-300 rounded-md px-4 py-3 outline-none"
/>
<button className="mt-4 bg-orange-600 hover:bg-orange-500 text-white px-6 py-3 rounded-md font-medium">
SIGN UP
</button>
</div>
</div>
</div>
<div className="text-center text-[#666666] text-sm mt-10 border-gray-800 pt-4">
@2023 - isukini.com. All Right Reserved.{" "}
<div className="flex flex-wrap justify-center gap-8 mt-16 text-gray-600 text-sm">
<a href="#">About Us</a>
<a href="#">Contact</a>
<a href="#">Kode Etik Jurnalistik</a>
<a href="#">Kebijakan Privasi</a>
<a href="#">Disclaimer</a>
<a href="#">Pedoman Media Siber</a>
</div>
<div className="flex justify-center gap-8 mt-8 text-gray-700">
<Facebook className="w-5 h-5 cursor-pointer" />
<Twitter className="w-5 h-5 cursor-pointer" />
<Instagram className="w-5 h-5 cursor-pointer" />
<Youtube className="w-5 h-5 cursor-pointer" />
</div>
<p className="text-start text-gray-500 text-sm mt-8 pl-5">
© 2025 Mikul News - All Rights Reserved.
</p>
</footer>
);
}

View File

@ -1,9 +1,9 @@
"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import { getListArticle } from "@/service/article";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
type Article = {
id: number;
@ -13,27 +13,20 @@ type Article = {
createdAt: string;
slug: string;
createdByName: string;
publishedAt: string;
customCreatorName: string;
thumbnailUrl: string;
categories: {
title: string;
}[];
files: {
fileUrl: string;
file_alt: string;
}[];
categories: { title: string }[];
files: { fileUrl: string; file_alt: string }[];
};
export default function Header() {
const [article, setArticle] = useState<Article | null>(null);
const [showData, setShowData] = useState("5");
const [articles, setArticles] = useState<Article[]>([]);
useEffect(() => {
initState();
}, []);
async function initState() {
const fetchArticles = async () => {
const req = {
limit: showData,
limit: "10",
page: 1,
search: "",
categorySlug: "",
@ -42,96 +35,198 @@ export default function Header() {
sortBy: "created_at",
};
try {
const res = await getListArticle(req);
setArticle(res?.data?.data?.[0] || null);
} catch (error) {
console.error("Gagal memuat artikel:", error);
}
}
setArticles(res?.data?.data || []);
};
fetchArticles();
}, []);
const flashArticles = articles.slice(0, 8);
const mainArticle = articles[8] || articles[0];
const recentPosts = articles.slice(1, 5);
return (
<header className="bg-white w-full pt-3 flex justify-center">
<div className="relative max-w-screen-xl h-auto md:h-[483px] flex flex-col md:flex-row overflow-hidden shadow-md mx-3 md:mx-3 lg:mx-5">
<Link className="flex" href={`/details/${article?.slug}`}>
<div className="w-full md:w-1/2 h-[250px] md:h-full">
<section className="max-w-7xl mx-auto bg-white px-4">
{/* FLASH STRIP */}
<div className="flex items-center justify-between mt-6 mb-3">
<div className="flex items-center gap-3">
<h4 className="text-orange-600 font-semibold">Flash</h4>
<span className="text-red-500"></span>
</div>
<div className="text-xs text-gray-500 hidden md:block">LOAD MORE </div>
</div>
<div className="overflow-x-auto no-scrollbar py-2">
<div className="flex gap-4">
{flashArticles.map((item) => (
<Link
href={`/details/${item.slug}`}
key={`flash-${item.id}`}
className="min-w-[200px] md:min-w-[220px] bg-gray-800 rounded-lg overflow-hidden relative shadow"
>
<div className="relative w-[200px] md:w-[220px] h-[140px]">
<Image
src={article?.thumbnailUrl || "/dummy.jpg"}
alt={article?.title || "No Title"}
width={730}
height={483}
className="object-cover w-full h-full"
src={
item.thumbnailUrl ||
item.files?.[0]?.fileUrl ||
"/placeholder.jpg"
}
alt={item.title}
fill
className="object-cover"
/>
</div>
<div className="w-full md:w-1/2 bg-[#714C3D] text-white p-6 md:p-10 flex flex-col justify-center">
<span className="bg-black text-white text-xs px-2 py-1 mb-4 inline-block w-fit">
{article?.categoryName || "Berita Terkini"}
{/* dark overlay with text */}
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent text-white">
<p className="text-xs line-clamp-2">{item.title}</p>
<div className="flex items-center justify-between mt-2 text-[11px] text-gray-300">
<span className="text-yellow-300 bg-black/30 px-1 rounded-sm">
{item.categoryName ||
item.categories?.[0]?.title ||
"Berita"}
</span>
<h1 className="text-xl md:text-3xl font-semibold leading-snug md:leading-tight mb-4 md:mb-6">
{article?.title || "Memuat..."}
</h1>
<div className="flex flex-wrap gap-4 text-sm opacity-90">
<div className="flex items-center gap-1">
<span></span>
</div>
</div>
{/* play icon */}
<div className="absolute top-3 right-3 w-8 h-8 bg-white/80 rounded-full flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
className="w-4 h-4 text-black"
viewBox="0 0 24 24"
>
<g fill="none">
<path d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M12 8v4l3 3l1-1l-2.5-2.5V8zM12 2a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2m0 18a8 8 0 1 1 8-8a8.009 8.009 0 0 1-8 8"
/>
</g>
>
<path d="M8 5v14l11-7z" />
</svg>
</div>
</Link>
))}
</div>
</div>
{/* Main Layout */}
<div className="grid grid-cols-1 md:grid-cols-[2.2fr_1fr] gap-6">
{/* LEFT SIDE MAIN ARTICLE */}
{mainArticle ? (
<div className="relative h-[500px] w-full rounded-xl overflow-hidden shadow-md">
<Link href={`/details/${mainArticle.slug}`}>
<Image
src={
mainArticle.thumbnailUrl ||
mainArticle.files?.[0]?.fileUrl ||
"/placeholder.jpg"
}
alt={mainArticle.files?.[0]?.file_alt || mainArticle.title}
fill
className="object-cover"
/>
{/* White Card Overlay */}
<div className="absolute bottom-6 left-6 bg-white bg-opacity-95 backdrop-blur-sm p-6 shadow-lg max-w-lg">
<span className="text-[11px] bg-orange-700 text-white px-2 py-1 rounded-sm">
{mainArticle.categoryName ||
mainArticle.categories?.[0]?.title ||
"Berita"}
</span>
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mt-2 leading-snug">
{mainArticle.title}
</h2>
<div className="flex items-center gap-2 text-gray-600 text-xs mt-3">
<span>
{article
? new Date(article.createdAt).toLocaleDateString("id-ID", {
day: "numeric",
By{" "}
{mainArticle.customCreatorName ||
mainArticle.createdByName ||
"Admin"}
</span>
<span></span>
<span>
{new Date(mainArticle.publishedAt).toLocaleDateString(
"id-ID",
{
day: "2-digit",
month: "long",
year: "numeric",
})
: ""}
},
)}
</span>
</div>
<div className="flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 32 32"
>
<path
fill="currentColor"
d="M28 4h-2V2h-2v2H8V2H6v2H4a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h24a2.006 2.006 0 0 0 2-2V6a2.006 2.006 0 0 0-2-2m0 24H4V10h24Z"
/>
</svg>
<span>{article?.createdByName || ""}</span>
</div>
<div className="flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
<span>0</span>
</div>
</div>
</div>
</Link>
</div>
</header>
) : (
<p className="text-gray-500">Loading...</p>
)}
{/* RIGHT SIDE RECENT POSTS */}
<div>
<h3 className="text-lg font-semibold mb-3">Recent Posts</h3>
<div className="space-y-4">
{recentPosts.map((item) => (
<Link
key={item.id}
href={`/details/${item.slug}`}
className="flex gap-3"
>
<div className="relative w-35 h-25 rounded-md overflow-hidden">
<Image
src={
item.thumbnailUrl ||
item.files?.[0]?.fileUrl ||
"/placeholder.jpg"
}
alt={item.title}
fill
className="object-cover"
/>
</div>
<div className="flex flex-col">
<p className="text-sm font-semibold line-clamp-2">
{item.title}
</p>
<span className="text-xs text-gray-500 mt-1">
{new Date(item.publishedAt).toLocaleDateString("id-ID", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</span>
</div>
</Link>
))}
</div>
</div>
</div>
{/* LOAD MORE */}
<div className="flex justify-center my-6">
<button className="text-gray-600 text-sm flex items-center gap-2 border-b pb-1">
<span>LOAD MORE</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
<path
d="M12 5v14M5 12h14"
stroke="#9CA3AF"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
</button>
</div>
{/* KOLOM PPS BOTTOM BANNER */}
<div className="relative h-[140px] w-full overflow-hidden rounded-none my-5 border rounded-md">
<Image
src="/image-kolom.png"
alt="Kolom PPS Bottom Banner"
fill
className="object-contain"
/>
</div>
</section>
);
}

View File

@ -0,0 +1,186 @@
"use client";
import { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { getListArticle } from "@/service/article";
type Article = {
id: number;
title: string;
description: string;
categoryName: string;
createdAt: string;
publishedAt: string;
slug: string;
createdByName: string;
customCreatorName?: string;
thumbnailUrl?: string;
categories?: { title: string }[];
};
export default function NewsTerkini() {
const [articles, setArticles] = useState<Article[]>([]);
const [popular, setPopular] = useState<Article[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadData();
}, []);
async function loadData() {
setLoading(true);
try {
const res = await getListArticle({
limit: "20",
page: 1,
search: "",
isPublish: true,
sort: "desc",
sortBy: "created_at",
});
const data = res?.data?.data || [];
setArticles(data.slice(0, 5));
setPopular(data.slice(0, 5));
} catch (err) {
console.log(err);
}
setLoading(false);
}
const formatDate = (d: string) =>
new Date(d).toLocaleDateString("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
});
if (loading)
return (
<p className="text-center py-10 text-gray-500">
Memuat berita terbaru...
</p>
);
return (
<section className="max-w-7xl mx-auto px-4 grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-6">
<div>
<h2 className="text-lg font-bold text-gray-900">BERITA TERKINI</h2>
<div className="w-14 h-1 bg-orange-600 mt-1 mb-4"></div>
<div className="space-y-6">
{articles.map((item) => (
<Link
key={item.id}
href={`/details/${item.slug}`}
className="block border-b pb-6"
>
<div className="flex gap-4">
<div className="flex-1">
{/* CATEGORY */}
<p className="text-[11px] text-orange-700 font-semibold mb-1">
{item.categoryName || "Kategori"}
</p>
{/* JUDUL */}
<h3 className="font-bold text-base leading-snug line-clamp-2">
{item.title}
</h3>
{/* DESKRIPSI */}
<p className="text-sm text-gray-600 line-clamp-2 mt-1">
{item.description}
</p>
{/* AUTHOR + DATE */}
<p className="text-xs text-gray-400 mt-2">
By {item.customCreatorName || item.createdByName} {" "}
{formatDate(item.publishedAt)}
</p>
</div>
<div className="relative w-40 h-28 rounded overflow-hidden flex-shrink-0">
<Image
src={item.thumbnailUrl || "/placeholder.jpg"}
alt={item.title}
fill
className="object-cover"
/>
</div>
</div>
</Link>
))}
</div>
{/* LOAD MORE */}
<div className="text-center mt-4 text-orange-600 text-sm cursor-pointer">
LOAD MORE
</div>
</div>
<div className="lg:col-span-1">
<h2 className="text-lg font-bold text-gray-900">TERBANYAK DIBAGIKAN</h2>
<div className="w-14 h-1 bg-orange-600 mt-1 mb-4"></div>
<div className="space-y-4">
{popular.map((item, index) => (
<Link
key={item.id}
href={`/details/${item.slug}`}
className="flex gap-3 border-b pb-4"
>
{/* NOMOR */}
<div className="text-orange-600 font-extrabold text-3xl leading-none">
{(index + 1).toString().padStart(2, "0")}
</div>
<div className="flex-1">
<p className="text-[10px] text-gray-500">
{item.categories?.[0]?.title || "Kategori"}
</p>
<h4 className="font-semibold text-sm leading-snug line-clamp-2">
{item.title}
</h4>
<p className="text-[10px] text-gray-400 mt-1">
{formatDate(item.createdAt)}
</p>
</div>
{/* THUMBNAIL KECIL */}
<div className="relative w-16 h-14 rounded overflow-hidden">
<Image
src={item.thumbnailUrl || "/placeholder.jpg"}
alt={item.title}
fill
className="object-cover"
/>
</div>
</Link>
))}
</div>
<div className="mt-6">
<div className="relative h-[180px] border rounded-lg overflow-hidden mb-6">
<Image
src="/image-kolom.png"
alt="Kolom PPS"
fill
className="object-contain bg-white"
/>
</div>
<div className="relative h-[180px] border rounded-lg overflow-hidden">
<Image
src="/image-kolom.png"
alt="Kolom PPS"
fill
className="object-contain bg-white"
/>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,126 @@
"use client";
import { getListArticle } from "@/service/article";
import { ChevronDown } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
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 News() {
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [articles, setArticles] = useState<Article[]>([]);
const [showData, setShowData] = useState("6");
const [search] = useState("");
const [selectedCategories] = useState<any>("");
const [startDateValue] = useState({
startDate: null,
endDate: null,
});
useEffect(() => {
initState();
}, [page, showData, startDateValue, selectedCategories]);
async function initState() {
const req = {
limit: showData,
page,
search,
categorySlug: Array.from(selectedCategories).join(","),
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);
}
}
return (
<section className="max-w-screen-xl mx-auto px-4 py-10">
<div className="">
{/* TITLE */}
<div className="mb-4">
<h2 className="text-xl font-black text-[#000]">BERITA POPULER</h2>
<div className="w-10 h-1 bg-orange-600 mt-1 rounded"></div>
</div>
{/* GRID 4 KOLOM */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{articles.slice(0, 4).map((item) => (
<Link href={`/details/${item.slug}`} key={item.id}>
<div>
{/* GAMBAR */}
<div className="relative w-full h-56 rounded-lg overflow-hidden">
<Image
src={item.thumbnailUrl || "/placeholder.jpg"}
alt={item.title}
fill
className="object-cover"
/>
{/* BADGE CATEGORY DI DALAM GAMBAR */}
<div className="absolute bottom-2 left-2">
<span className="px-3 py-1 text-[10px] font-semibold bg-orange-600 text-white rounded">
{item.categoryName || "Kategori"}
</span>
</div>
</div>
{/* JUDUL */}
<h3 className="mt-2 text-base font-bold leading-snug line-clamp-2">
{item.title}
</h3>
{/* AUTHOR + DATE */}
<div className="text-[11px] mt-2 flex items-center gap-1 text-gray-700">
<span className="font-semibold">
By {item.customCreatorName || item.createdByName || "Admin"}
</span>
<span className="text-yellow-500">-</span>
<span>
{new Date(item.publishedAt).toLocaleDateString("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
</div>
</div>
</Link>
))}
</div>
<div className="relative h-[160px] w-full overflow-hidden rounded-xl mt-6">
<Image
src="/image-kolom.png"
alt="Kolom PPS"
fill
className="object-contain bg-white"
/>
</div>
</div>
</section>
);
}

View File

@ -1,96 +1,180 @@
"use client";
import { Facebook, Instagram, Search, Twitter, Youtube } from "lucide-react";
import { Search } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { Button } from "../ui/button";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils"; // helper opsional, bisa hapus kalau tidak ada
export default function Navbar() {
const pathname = usePathname();
const navItems = [
{ label: "HOME", href: "/" },
{ label: "BERITA TERKINI", href: "/category/latest-news" },
{ label: "BERITA POPULER", href: "/category/popular-news" },
{ label: "JAGA NEGERI", href: "/category/protect" },
{ label: "BERITA OPINI", href: "/category/opinion-news" },
];
const isActive = (href: any) => {
return pathname === href || pathname.startsWith(href + "/");
};
return (
<div className="w-full bg-white">
{/* Banner */}
<div className="relative w-full h-[113px]">
<div className="w-full bg-white py-4 border-b">
<div className="max-w-screen-xl mx-auto flex flex-col justify-between px-4">
{/* Left: Logo */}
<div className="flex flex-row justify-between mb-3">
<div className="flex items-center">
<Image
src="/bg-isu.jpg"
alt="Kritik Tajam Background"
width={1920}
height={113}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 flex items-center justify-center">
<Image
src="/isukini.jpg"
src="/isukini-logo.jpg"
alt="Kritik Tajam Logo"
width={100}
height={93}
className="object-contain"
width={140}
height={100}
/>
</div>
</div>
{/* Navbar */}
<div className="flex max-w-screen-xl mx-auto items-center justify-between py-2 flex-wrap gap-y-2">
{/* Menu */}
<div className="flex flex-wrap gap-x-4 md:gap-x-6 font-semibold text-sm mx-3 md:mx-3 lg:mx-3 xl:mx-0">
{navItems.map((item) => {
const isActive =
item.href === "/"
? pathname === item.href
: pathname.startsWith(item.href);
return (
<Link
key={item.href}
href={item.href}
className={cn(
"pb-1 transition-colors",
isActive
? "text-white bg-[#795548] px-2 rounded"
: "text-black hover:text-[#795548]"
)}
<div className="flex items-center gap-6">
{/* Social Icons */}
<div className="hidden md:flex items-center gap-5 text-black text-xl">
<Link href="#">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
viewBox="0 0 24 24"
>
{item.label}
<path
fill="currentColor"
d="M14 13.5h2.5l1-4H14v-2c0-1.03 0-2 2-2h1.5V2.14c-.326-.043-1.557-.14-2.857-.14C11.928 2 10 3.657 10 6.7v2.8H7v4h3V22h4z"
/>
</svg>
</Link>
<Link href="#">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7.91 20.889c8.302 0 12.845-6.885 12.845-12.845c0-.193 0-.387-.009-.58A9.2 9.2 0 0 0 23 5.121a9.2 9.2 0 0 1-2.597.713a4.54 4.54 0 0 0 1.99-2.5a9 9 0 0 1-2.87 1.091A4.5 4.5 0 0 0 16.23 3a4.52 4.52 0 0 0-4.516 4.516c0 .352.044.696.114 1.03a12.82 12.82 0 0 1-9.305-4.718a4.526 4.526 0 0 0 1.4 6.03a4.6 4.6 0 0 1-2.043-.563v.061a4.524 4.524 0 0 0 3.62 4.428a4.4 4.4 0 0 1-1.189.159q-.435 0-.845-.08a4.51 4.51 0 0 0 4.217 3.135a9.05 9.05 0 0 1-5.608 1.936A9 9 0 0 1 1 18.873a12.84 12.84 0 0 0 6.91 2.016"
/>
</svg>
</Link>
<Link href="#">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7.8 2h8.4C19.4 2 22 4.6 22 7.8v8.4a5.8 5.8 0 0 1-5.8 5.8H7.8C4.6 22 2 19.4 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2m-.2 2A3.6 3.6 0 0 0 4 7.6v8.8C4 18.39 5.61 20 7.6 20h8.8a3.6 3.6 0 0 0 3.6-3.6V7.6C20 5.61 18.39 4 16.4 4zm9.65 1.5a1.25 1.25 0 0 1 1.25 1.25A1.25 1.25 0 0 1 17.25 8A1.25 1.25 0 0 1 16 6.75a1.25 1.25 0 0 1 1.25-1.25M12 7a5 5 0 0 1 5 5a5 5 0 0 1-5 5a5 5 0 0 1-5-5a5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3a3 3 0 0 0 3 3a3 3 0 0 0 3-3a3 3 0 0 0-3-3"
/>
</svg>
</Link>
<Link href="#">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12.244 4c.534.003 1.87.016 3.29.073l.504.022c1.429.067 2.857.183 3.566.38c.945.266 1.687 1.04 1.938 2.022c.4 1.56.45 4.602.456 5.339l.001.152v.174c-.007.737-.057 3.78-.457 5.339c-.254.985-.997 1.76-1.938 2.022c-.709.197-2.137.313-3.566.38l-.504.023c-1.42.056-2.756.07-3.29.072l-.235.001h-.255c-1.13-.007-5.856-.058-7.36-.476c-.944-.266-1.687-1.04-1.938-2.022c-.4-1.56-.45-4.602-.456-5.339v-.326c.006-.737.056-3.78.456-5.339c.254-.985.997-1.76 1.939-2.021c1.503-.419 6.23-.47 7.36-.476zM9.999 8.5v7l6-3.5z"
/>
</svg>
</Link>
);
})}
</div>
{/* Ikon kanan */}
<div className="flex items-center space-x-4 text-black mt-2 md:mt-0 mr-3 md:mr-3 lg:mr-3 xl:mr-0">
<Link href="#">
<Facebook size={18} />
</Link>
<Link href="#">
<Twitter size={18} />
</Link>
<Link href="#">
<Instagram size={18} />
</Link>
<Link href="#">
<Youtube size={18} />
</Link>
<Link href="#">
<Search size={18} />
</div>
</div>
{/* Middle Menu */}
<div className="flex flex-row justify-between">
<nav className="hidden md:flex items-center gap-10 text-sm font-semibold">
<Link
href="/"
className={
isActive("/")
? "text-orange-500 underline"
: "text-black hover:text-orange-500"
}
>
Beranda
</Link>
<Link
href="/category/latest-news"
className={
isActive("/category/latest-news")
? "text-orange-500 underline"
: "text-black hover:text-orange-500"
}
>
Berita Terkini
</Link>
<Link
href="/category/popular-news"
className={
isActive("/category/popular-news")
? "text-orange-500 underline"
: "text-black hover:text-orange-500"
}
>
Berita Populer
</Link>
<Link
href="/category/protect"
className={
isActive("/category/protect")
? "text-orange-500 underline"
: "text-black hover:text-orange-500"
}
>
Jaga Negeri
</Link>
<Link
href="/category/opinion-news"
className={
isActive("/category/opinion-news")
? "text-orange-500 underline"
: "text-black hover:text-orange-500"
}
>
Berita Opini
</Link>
</nav>
<div className="flex items-center gap-2">
<button
// onClick={() => document.documentElement.classList.toggle("dark")}
className="w-10 h-5 rounded-full bg-gray-300 dark:bg-gray-700 relative transition-all"
>
<div className="w-5 h-5 bg-white dark:bg-black rounded-full shadow absolute top-0 left-0 dark:left-5 transition-all"></div>
</button>
{/* BURGER BUTTON (mobile menu) */}
<button className="md:hidden p-2 rounded-lg border">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
<button className="p-2 border rounded-full">
<Search size={15} />
</button>
<Link href={"/auth"}>
<Button className="bg-black text-white rounded-md px-5 py-2 hover:bg-yellow-500">
Login
</Button>
<button className="bg-orange-600 text-white px-5 py-2 rounded-full text-sm font-semibold">
LOGIN
</button>
</Link>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,126 @@
"use client";
import { getListArticle } from "@/service/article";
import { ChevronDown } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
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 OpinionNews() {
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [articles, setArticles] = useState<Article[]>([]);
const [showData, setShowData] = useState("6");
const [search] = useState("");
const [selectedCategories] = useState<any>("");
const [startDateValue] = useState({
startDate: null,
endDate: null,
});
useEffect(() => {
initState();
}, [page, showData, startDateValue, selectedCategories]);
async function initState() {
const req = {
limit: showData,
page,
search,
categorySlug: Array.from(selectedCategories).join(","),
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);
}
}
return (
<section className="max-w-screen-xl mx-auto px-4 py-10">
<div className="">
{/* TITLE */}
<div className="mb-4">
<h2 className="text-xl font-black text-[#000]">BERITA OPINI</h2>
<div className="w-10 h-1 bg-orange-600 mt-1 rounded"></div>
</div>
{/* GRID 4 KOLOM */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{articles.slice(0, 4).map((item) => (
<Link href={`/details/${item.slug}`} key={item.id}>
<div>
{/* GAMBAR */}
<div className="relative w-full h-56 rounded-lg overflow-hidden">
<Image
src={item.thumbnailUrl || "/placeholder.jpg"}
alt={item.title}
fill
className="object-cover"
/>
{/* BADGE CATEGORY DI DALAM GAMBAR */}
<div className="absolute bottom-2 left-2">
<span className="px-3 py-1 text-[10px] font-semibold bg-orange-600 text-white rounded">
{item.categoryName || "Kategori"}
</span>
</div>
</div>
{/* JUDUL */}
<h3 className="mt-2 text-base font-bold leading-snug line-clamp-2">
{item.title}
</h3>
{/* AUTHOR + DATE */}
<div className="text-[11px] mt-2 flex items-center gap-1 text-gray-700">
<span className="font-semibold">
By {item.customCreatorName || item.createdByName || "Admin"}
</span>
<span className="text-yellow-500">-</span>
<span>
{new Date(item.publishedAt).toLocaleDateString("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
</div>
</div>
</Link>
))}
</div>
<div className="relative h-[160px] w-full overflow-hidden rounded-xl mt-6">
<Image
src="/image-kolom.png"
alt="Kolom PPS"
fill
className="object-contain bg-white"
/>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,151 @@
import { Eye, Heart, MessageCircle } from "lucide-react";
import Image from "next/image";
interface VideoItem {
id: number;
title: string;
thumbnail: string;
duration: string;
publishedAt: string;
views: string;
likes: string;
comments: string;
}
const sampleVideos: VideoItem[] = [
{
id: 1,
title: "Cuplikan Kegiatan Presiden di IKN",
thumbnail: "/yt/thumb1.jpg",
duration: "12:30",
publishedAt: "2 jam yang lalu",
views: "12K",
likes: "230",
comments: "45",
},
{
id: 2,
title: "Pembangunan MRT Fase Berikutnya Resmi Dimulai",
thumbnail: "/yt/thumb2.jpg",
duration: "08:12",
publishedAt: "5 jam yang lalu",
views: "9.4K",
likes: "180",
comments: "30",
},
{
id: 3,
title: "Wilayah Indonesia Siap Hadapi Cuaca Ekstrem",
thumbnail: "/yt/thumb3.jpg",
duration: "05:50",
publishedAt: "1 hari lalu",
views: "21K",
likes: "540",
comments: "121",
},
{
id: 4,
title: "Peningkatan Ekonomi Regional Terus Dijaga",
thumbnail: "/yt/thumb4.jpg",
duration: "10:44",
publishedAt: "2 hari lalu",
views: "18K",
likes: "420",
comments: "88",
},
{
id: 5,
title: "Laporan Khusus Perkembangan Industri Kreatif",
thumbnail: "/yt/thumb5.jpg",
duration: "14:02",
publishedAt: "3 hari lalu",
views: "30K",
likes: "830",
comments: "200",
},
{
id: 6,
title: "Paparan Menteri Perhubungan Soal Transportasi",
thumbnail: "/yt/thumb6.jpg",
duration: "07:21",
publishedAt: "4 hari lalu",
views: "9K",
likes: "114",
comments: "26",
},
];
export default function YouTubeSection() {
return (
<section className="max-w-7xl mx-auto px-4 py-8">
{/* Header Channel */}
<div className="flex items-center gap-4 mb-6">
<Image
src="/yt-logo.png"
alt="Logo Channel"
width={70}
height={70}
className="rounded-full"
/>
<div>
<h2 className="text-xl font-semibold">YouTube Channel Resmi</h2>
<p className="text-sm text-gray-600">
Lebih dari 3.5 juta pengikut 12k video
</p>
</div>
<a
href="#"
className="ml-auto bg-red-600 text-white px-4 py-2 rounded-lg font-semibold"
>
Subscribe
</a>
</div>
{/* Grid Video */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{sampleVideos.map((v) => (
<div key={v.id} className="cursor-pointer">
<div className="relative w-full h-44">
<Image
src={v.thumbnail}
alt={v.title}
fill
className="rounded-lg object-cover"
/>
<span className="absolute bottom-1 right-1 bg-black/80 text-white text-xs px-2 py-1 rounded">
{v.duration}
</span>
</div>
<h3 className="font-semibold mt-2 leading-tight line-clamp-2">
{v.title}
</h3>
<p className="text-sm text-gray-600">{v.publishedAt}</p>
<div className="flex items-center gap-4 text-sm text-gray-700 mt-1">
<span className="flex items-center gap-1">
<Eye size={16} /> {v.views}
</span>
<span className="flex items-center gap-1">
<Heart size={16} /> {v.likes}
</span>
<span className="flex items-center gap-1">
<MessageCircle size={16} /> {v.comments}
</span>
</div>
</div>
))}
</div>
{/* Pagination */}
<div className="flex justify-center mt-8">
<button className="px-5 py-2 rounded-lg border hover:bg-gray-100">
Lihat Selanjutnya
</button>
</div>
</section>
);
}

BIN
public/isukini-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
public/yt-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -30,7 +30,7 @@ export async function getListArticle(props: PaginationRequest) {
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
sort || "desc"
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`,
null
null,
);
}
@ -59,7 +59,7 @@ export async function getArticlePagination(props: PaginationRequest) {
sortBy || "created_at"
}&sort=${sort || "asc"}&category=${categorySlug || ""}&isBanner=${
isBanner || ""
}`
}`,
);
}
@ -75,7 +75,7 @@ export async function getTopArticles(props: PaginationRequest) {
}&title=${search}&startDate=${startDate || ""}&endDate=${
endDate || ""
}&category=${category || ""}&sortBy=view_count&sort=desc`,
headers
headers,
);
}
@ -106,6 +106,13 @@ export async function getArticleById(id: any) {
return await httpGet(`/articles/${id}`, headers);
}
export async function getArticleFiles() {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/article-files`, headers);
}
export async function getArticleBySlug(slug: any) {
const headers = {
"content-type": "application/json",
@ -125,7 +132,7 @@ export async function getArticleByCategory() {
}
export async function getCategoryPagination(data: any) {
return await httpGet(
`/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}`
`/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}`,
);
}
@ -152,7 +159,7 @@ export async function deleteArticleFiles(id: number) {
export async function getUserLevelDataStat(startDate: string, endDate: string) {
return await httpGet(
`/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}`
`/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}`,
);
}
export async function getStatisticMonthly(year: string) {