This commit is contained in:
Anang Yusman 2025-10-07 00:25:25 +08:00
parent 97653728d3
commit 7e1f6df655
9 changed files with 548 additions and 610 deletions

29
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,29 @@
stages:
- build
- deploy
build-dev:
stage: build
when: on_success
only:
- main
image: docker:stable
services:
- name: docker:dind
command: ["--insecure-registry=103.82.242.92:8900"]
script:
- docker logout
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
- docker build -t 103.82.242.92:8900/medols/web-fokus-aja:dev .
- docker push 103.82.242.92:8900/medols/web-fokus-aja:dev
auto-deploy:
stage: deploy
when: on_success
only:
- main
image: curlimages/curl:latest
services:
- docker:dind
script:
- curl --user admin:$JENKINS_PWD http://38.47.180.165:8080/job/auto-deploy-fokus-aja/build?token=autodeploymedols

36
Dockerfile Normal file
View File

@ -0,0 +1,36 @@
# Menggunakan image Node.js yang lebih ringan
FROM node:23.5.0-alpine
# Mengatur port
ENV PORT 3000
# Install pnpm secara global
RUN npm install -g pnpm
# Membuat direktori aplikasi dan mengatur sebagai working directory
WORKDIR /usr/src/app
# Menyalin file penting terlebih dahulu untuk caching
COPY package.json ./
# Menyalin direktori ckeditor5 jika diperlukan
COPY vendor/ckeditor5 ./vendor/ckeditor5
# Menyalin env
COPY .env .env
# Install dependencies
RUN pnpm install
# RUN pnpm install --frozen-lockfile
# Menyalin source code aplikasi
COPY . .
# Build aplikasi
RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm next build
# Expose port untuk server
EXPOSE 3000
# Perintah untuk menjalankan aplikasi
CMD ["pnpm", "run", "start"]

View File

@ -6,17 +6,6 @@ import Image from "next/image";
export default function Home() { export default function Home() {
return ( return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]"> <div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="fixed top-0 left-0 w-full h-auto z-0">
<Image
src="/rumput.jpg"
alt="Background"
width={1450}
height={600}
className="w-full h-auto object-cover"
priority
/>
</div>
<div className="relative z-10 bg-[#F2F4F3] max-w-7xl mx-auto"> <div className="relative z-10 bg-[#F2F4F3] max-w-7xl mx-auto">
<Navbar /> <Navbar />
<div className="flex-1"> <div className="flex-1">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Fokus Aja",
description: "Generated by create next app", description: "Fokus Aja",
}; };
export default function RootLayout({ export default function RootLayout({

View File

@ -5,6 +5,7 @@ import Link from "next/link";
import { getArticleById, getListArticle } from "@/service/article"; import { getArticleById, getListArticle } from "@/service/article";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { CommentIcon } from "../icons/sidebar-icon";
import { Link2, MailIcon } from "lucide-react"; import { Link2, MailIcon } from "lucide-react";
type TabKey = "trending" | "comments" | "latest"; type TabKey = "trending" | "comments" | "latest";
@ -13,7 +14,6 @@ type Article = {
id: number; id: number;
title: string; title: string;
description: string; description: string;
htmlDescription: string;
categoryName: string; categoryName: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
@ -41,9 +41,9 @@ export default function DetailContent() {
const [articles, setArticles] = useState<Article[]>([]); const [articles, setArticles] = useState<Article[]>([]);
const [articleDetail, setArticleDetail] = useState<any>(null); const [articleDetail, setArticleDetail] = useState<any>(null);
const [showData, setShowData] = useState("5"); const [showData, setShowData] = useState("5");
const [search, setSearch] = useState("-"); const [search, setSearch] = useState("");
const [listCategory, setListCategory] = useState<CategoryType[]>([]); const [listCategory, setListCategory] = useState<CategoryType[]>([]);
const [selectedCategories, setSelectedCategories] = useState<any>("-"); const [selectedCategories, setSelectedCategories] = useState<any>("");
const [startDateValue, setStartDateValue] = useState({ const [startDateValue, setStartDateValue] = useState({
startDate: null, startDate: null,
endDate: null, endDate: null,
@ -69,46 +69,29 @@ export default function DetailContent() {
]; ];
useEffect(() => { useEffect(() => {
fetchTabArticles(); initState();
}, [activeTab]); }, [page, showData, startDateValue, selectedCategories]);
async function fetchTabArticles() { async function initState() {
const req: any = { // loading();
const req = {
limit: showData, limit: showData,
page, page,
search, search,
categorySlug: Array.from(selectedCategories).join(","), categorySlug: Array.from(selectedCategories).join(","),
sort: "desc", sort: "desc",
isPublish: true,
sortBy: "created_at", sortBy: "created_at",
}; };
// Sesuaikan sortBy berdasarkan tab
if (activeTab === "trending") {
req.sortBy = "view_count";
} else if (activeTab === "comments") {
req.sortBy = "comment_count";
} else {
req.sortBy = "created_at";
}
// Hanya kirimkan search jika valid
if (search && search !== "-" && search !== "") {
req.search = search;
}
if (selectedCategories && selectedCategories !== "-") {
req.categorySlug = selectedCategories;
}
try { try {
const res = await getListArticle(req); const res = await getListArticle(req);
setTabArticles(res?.data?.data || []); setArticles(res?.data?.data || []);
} catch (error) { setTotalPage(res?.data?.meta?.totalPage || 1);
console.error("Failed fetching tab articles:", error); } finally {
setTabArticles([]); // Optional fallback // close();
} }
} }
const content: Record< const content: Record<
TabKey, TabKey,
{ image: string; title: string; date: string }[] { image: string; title: string; date: string }[]
@ -154,30 +137,6 @@ export default function DetailContent() {
], ],
}; };
useEffect(() => {
initState();
}, [page, showData, startDateValue, selectedCategories]);
async function initState() {
// loading();
const req = {
limit: showData,
page,
search,
categorySlug: Array.from(selectedCategories).join(","),
sort: "desc",
sortBy: "created_at",
};
try {
const res = await getListArticle(req);
setArticles(res?.data?.data || []);
setTotalPage(res?.data?.meta?.totalPage || 1);
} finally {
// close();
}
}
useEffect(() => { useEffect(() => {
initStateData(); initStateData();
}, [listCategory]); }, [listCategory]);
@ -194,33 +153,16 @@ export default function DetailContent() {
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>
<h1 className="text-3xl md:text-4xl font-bold text-[#1a1a1a] leading-tight mb-4"> <h1 className="text-3xl md:text-4xl font-bold text-[#1a1a1a] leading-tight mb-4">
{articleDetail?.title} {articleDetail?.title}
</h1> </h1>
<div className="flex items-center space-x-2 text-sm text-gray-500 mb-4"> <div className="flex flex-row justify-between items-center space-x-2 text-sm text-black mb-4">
<div className="flex flex-row gap-3">
<div className="text-[#31942E]"> <div className="text-[#31942E]">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -244,7 +186,7 @@ export default function DetailContent() {
<span className="text-[#31942E] font-medium"> <span className="text-[#31942E] font-medium">
{articleDetail?.createdByName} {articleDetail?.createdByName}
</span> </span>
<span></span> <span>-</span>
<span> <span>
<span> <span>
{new Date(articleDetail?.createdAt).toLocaleDateString( {new Date(articleDetail?.createdAt).toLocaleDateString(
@ -257,16 +199,19 @@ export default function DetailContent() {
)} )}
</span> </span>
</span> </span>
<span></span> <span className="text-gray-500">in</span>
<span>{articleDetail?.categories?.[0]?.title}</span> <span>{articleDetail?.categories?.[0]?.title}</span>
</div> </div>
<div className="flex items-center">
<CommentIcon />0
</div>
</div>
<div className="w-full h-auto mb-6"> <div className="w-full h-auto mb-6">
{/* Gambar utama */}
<div className="w-full"> <div className="w-full">
<Image <Image
src={articleDetail.files[selectedIndex].fileUrl} src={articleDetail?.files[selectedIndex].fileUrl}
alt={articleDetail.files[selectedIndex].fileAlt || "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"
@ -275,7 +220,7 @@ export default function DetailContent() {
{/* Thumbnail */} {/* Thumbnail */}
<div className="flex gap-2 mt-3 overflow-x-auto"> <div className="flex gap-2 mt-3 overflow-x-auto">
{articleDetail.files.map((file: any, index: number) => ( {articleDetail?.files.map((file: any, index: number) => (
<button <button
key={file.id || index} key={file.id || index}
onClick={() => setSelectedIndex(index)} onClick={() => setSelectedIndex(index)}
@ -296,13 +241,15 @@ export default function DetailContent() {
))} ))}
</div> </div>
{/* Slug */} <div className=" flex flex-row w-fit rounded overflow-hidden mr-5 gap-3 mt-3">
<p className="text-sm text-gray-500 mt-2 text-end"> <div className="flex flex-col items-center gap-2">
{articleDetail?.slug} <p className="text-red-500 font-semibold">0</p>
</p> <p className="text-red-500 font-semibold">SHARES</p>
</div>
<div className="flex flex-col items-center gap-2">
<p className="text-black font-semibold">3</p>
<p className="text-black font-semibold">VIEWS</p>
</div> </div>
<div className="flex relative">
<div className=" flex flex-col w-fit rounded overflow-hidden mr-5">
<Link <Link
href="#" href="#"
aria-label="Facebook" aria-label="Facebook"
@ -337,46 +284,88 @@ export default function DetailContent() {
<Link <Link
href="#" href="#"
aria-label="Google" aria-label="WhatsApp"
className="bg-[#fce9e7] p-4 flex justify-center items-center text-white" className="bg-green-700 p-4 flex justify-center items-center text-white"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="20" width="20"
height="20" height="20"
fill="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path d="M7.796 14.333v-2.618h7.211c.066.382.12.763.12 1.265c0 4.364-2.923 7.462-7.33 7.462A7.63 7.63 0 0 1 .16 12.806a7.63 7.63 0 0 1 7.636-7.637c2.062 0 3.786.753 5.117 1.997L10.84 9.162c-.567-.546-1.56-1.178-3.044-1.178c-2.607 0-4.734 2.16-4.734 4.822s2.127 4.821 4.734 4.821c3.022 0 4.157-2.17 4.331-3.294zm13.27-2.6H23.2v2.134h-2.133V16h-2.134v-2.133H16.8v-2.134h2.133V9.6h2.134z" /> <g fill="none">
<g
// clip-path="url(#SVGXv8lpc2Y)"
>
<path
fill="currentColor"
// fill-rule="evenodd"
d="M17.415 14.382c-.298-.149-1.759-.867-2.031-.967s-.47-.148-.669.15c-.198.297-.767.966-.94 1.164c-.174.199-.347.223-.644.075c-.297-.15-1.255-.463-2.39-1.475c-.883-.788-1.48-1.761-1.653-2.059c-.173-.297-.019-.458.13-.606c.134-.133.297-.347.446-.52s.198-.298.297-.497c.1-.198.05-.371-.025-.52c-.074-.149-.668-1.612-.916-2.207c-.241-.579-.486-.5-.668-.51c-.174-.008-.372-.01-.57-.01s-.52.074-.792.372c-.273.297-1.04 1.016-1.04 2.479c0 1.462 1.064 2.875 1.213 3.074s2.095 3.2 5.076 4.487c.71.306 1.263.489 1.694.625c.712.227 1.36.195 1.872.118c.57-.085 1.758-.719 2.006-1.413s.247-1.289.173-1.413s-.272-.198-.57-.347m-5.422 7.403h-.004a9.87 9.87 0 0 1-5.032-1.378l-.36-.214l-3.742.982l.999-3.648l-.235-.374a9.86 9.86 0 0 1-1.511-5.26c.002-5.45 4.436-9.884 9.889-9.884a9.8 9.8 0 0 1 6.988 2.899a9.82 9.82 0 0 1 2.892 6.992c-.002 5.45-4.436 9.885-9.884 9.885m8.412-18.297A11.82 11.82 0 0 0 11.992 0C5.438 0 .102 5.335.1 11.892a11.86 11.86 0 0 0 1.587 5.945L0 24l6.304-1.654a11.9 11.9 0 0 0 5.684 1.448h.005c6.554 0 11.89-5.335 11.892-11.893a11.82 11.82 0 0 0-3.48-8.413"
// clip-rule="evenodd"
/>
</g>
<defs>
<clipPath id="SVGXv8lpc2Y">
<path fill="#fff" d="M0 0h24v24H0z" />
</clipPath>
</defs>
</g>
</svg> </svg>
</Link> </Link>
<Link <Link
href="#" href="#"
aria-label="Share" aria-label="Telegram"
className="bg-[#cccccc] p-4 flex justify-center items-center text-white" className="bg-blue-400 p-4 flex justify-center items-center text-white"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="20" width="20"
height="20" height="20"
fill="currentColor" viewBox="0 0 256 256"
viewBox="0 0 24 24"
> >
<path d="m21 12l-7-7v4C7 10 4 15 3 20c2.5-3.5 6-5.1 11-5.1V19z" /> <defs>
<linearGradient
id="SVGuySfwdaH"
x1="50%"
x2="50%"
y1="0%"
y2="100%"
>
<stop
offset="0%"
// stop-color="#2aabee"
/>
<stop
offset="100%"
// stop-color="#229ed9"
/>
</linearGradient>
</defs>
<path
fill="url(#SVGuySfwdaH)"
d="M128 0C94.06 0 61.48 13.494 37.5 37.49A128.04 128.04 0 0 0 0 128c0 33.934 13.5 66.514 37.5 90.51C61.48 242.506 94.06 256 128 256s66.52-13.494 90.5-37.49c24-23.996 37.5-56.576 37.5-90.51s-13.5-66.514-37.5-90.51C194.52 13.494 161.94 0 128 0"
/>
<path
fill="#fff"
d="M57.94 126.648q55.98-24.384 74.64-32.152c35.56-14.786 42.94-17.354 47.76-17.441c1.06-.017 3.42.245 4.96 1.49c1.28 1.05 1.64 2.47 1.82 3.467c.16.996.38 3.266.2 5.038c-1.92 20.24-10.26 69.356-14.5 92.026c-1.78 9.592-5.32 12.808-8.74 13.122c-7.44.684-13.08-4.912-20.28-9.63c-11.26-7.386-17.62-11.982-28.56-19.188c-12.64-8.328-4.44-12.906 2.76-20.386c1.88-1.958 34.64-31.748 35.26-34.45c.08-.338.16-1.598-.6-2.262c-.74-.666-1.84-.438-2.64-.258c-1.14.256-19.12 12.152-54 35.686c-5.1 3.508-9.72 5.218-13.88 5.128c-4.56-.098-13.36-2.584-19.9-4.708c-8-2.606-14.38-3.984-13.82-8.41c.28-2.304 3.46-4.662 9.52-7.072"
/>
</svg> </svg>
</Link> </Link>
</div> </div>
<p className="text-sm text-gray-500 mt-2 text-start">
{articleDetail?.slug}
</p>
</div>
<div className="flex relative">
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<div className="prose max-w-none text-justify"> <div className="text-gray-700 leading-relaxed text-justify">
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: articleDetail?.htmlDescription || "", __html: articleDetail?.htmlDescription || "",
}} }}
/> />
</div> </div>
{/* <Author /> */}
<div className="w-full bg-white py-6"> <div className="w-full bg-white py-6">
<p className="mx-10 text-2xl mb-4 ">AUTHOR</p> <p className="mx-10 text-2xl mb-4 ">AUTHOR</p>
<div className=" border border-black p-6 flex items-center gap-6 max-w-[1200px] mx-auto"> <div className=" border border-black p-6 flex items-center gap-6 max-w-[1200px] mx-auto">
@ -443,25 +432,10 @@ export default function DetailContent() {
/> />
</div> </div>
<div className="mt-10"> <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> */}
<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-red-500">*</span>
</p> </p>
<form className="space-y-6 mt-6"> <form className="space-y-6 mt-6">
@ -470,11 +444,11 @@ 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-red-500">*</span>
</label> </label>
<textarea <textarea
id="komentar" id="komentar"
className="w-full border border-gray-300 rounded-md p-3 h-40 focus:outline-none focus:ring-2 focus:ring-green-600" className="w-full border border-gray-300 rounded-md p-3 h-40 focus:outline-none focus:ring-2 focus:ring-red-600"
required required
/> />
</div> </div>
@ -484,7 +458,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-red-500">*</span>
</label> </label>
<input <input
type="text" type="text"
@ -500,7 +474,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-red-500">*</span>
</label> </label>
<input <input
type="email" type="email"
@ -539,7 +513,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-red-500 hover:bg-red-700 text-white font-semibold px-6 py-2 rounded-md transition mt-2"
> >
KIRIM KOMENTAR KIRIM KOMENTAR
</button> </button>
@ -549,193 +523,49 @@ export default function DetailContent() {
<div className="md:col-span-1 space-y-6"> <div className="md:col-span-1 space-y-6">
<div className="sticky top-0 space-y-6"> <div className="sticky top-0 space-y-6">
<div className="bg-white shadow p-4 rounded-lg"> <div className="space-y-6">
<Image {articles?.map((article) => (
src={"/kolom.png"} <div key={article.id}>
alt="Iklan" <div>
width={345} <Link
height={345} className="flex space-x-3 mb-2"
className="rounded" href={`/detail/${article.id}`}
/>
<button className="mt-4 w-full bg-black text-white py-2 rounded hover:opacity-90">
Learn More
</button>
</div>
<div className="bg-white shadow p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-2">Connect with us</h2>
<div className="flex space-x-2">
<div className="bg-[#0057ff] text-white px-3 py-2 rounded">
<p className="text-sm font-bold"></p>
<p className="text-xs">139 Followers</p>
</div>
<div className="bg-[#ff0000] text-white px-3 py-2 rounded">
<p className="text-sm font-bold">YouTube</p>
<p className="text-xs">205k Subscribers</p>
</div>
<div className="bg-[#f9a825] text-white px-3 py-2 rounded">
<p className="text-sm font-bold">RSS</p>
<p className="text-xs">23.9k Followers</p>
</div>
</div>
</div>
<div className="bg-white shadow p-4 rounded-lg">
<div className="flex space-x-4 border-b mb-4">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`pb-2 text-sm font-medium ${
activeTab === tab.id
? "border-b-2 border-green-600 text-green-600"
: "text-gray-600"
}`}
> >
{tab.label} <Image
</button> src={article.thumbnailUrl || "/default-thumb.png"}
))} alt={article.title}
</div> width={120}
height={80}
className="rounded object-cover w-[120px] h-[80px]"
/>
<div className="flex-1">
<p className="text-sm font-bold leading-snug hover:text-red-700">
{article.title}
</p>
<div className="space-y-4"> <div className="flex items-center text-xs text-gray-500 mt-1 space-x-2">
{tabArticles.map((item, idx) => ( <span>
<div key={idx} className="flex space-x-3"> 📅{" "}
<Image {new Date(article.createdAt).toLocaleDateString(
src={item.thumbnailUrl || "/default-thumb.png"} "id-ID",
alt={item.title} {
width={70}
height={70}
className="rounded w-[70px] h-[70px] object-cover"
/>
<div>
<p className="text-sm font-semibold leading-snug">
{item.title}
</p>
<p className="text-xs text-gray-500 mt-1">
{new Date(item.createdAt).toLocaleDateString("id-ID", {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
})} }
</p>
</div>
</div>
))}
</div>
{tabArticles.length === 0 ? (
<p className="text-sm text-gray-500">
Artikel tidak ditemukan.
</p>
) : (
tabArticles.map((item, idx) => (
<div key={idx} className="flex space-x-3">
<Image
src={item.thumbnailUrl || "/default-thumb.png"}
alt={item.title}
width={70}
height={70}
className="rounded w-[70px] h-[70px] object-cover"
/>
<div>
<p className="text-sm font-semibold leading-snug">
{item.title}
</p>
<p className="text-xs text-gray-500 mt-1">
{new Date(item.createdAt).toLocaleDateString("id-ID", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</p>
</div>
</div>
))
)} )}
</span>
<span>💬 0</span>
</div>
</div>
</Link>
</div> </div>
<div className="mt-6"> <p className="text-sm text-gray-700 line-clamp-2">
<h3 className="text-base font-semibold mb-2 text-gray-800 border-b pb-1 border-green-600 inline-block"> {article.description.slice(0, 120)}...
Recommended
</h3>
<div className="space-y-4">
<div className="relative">
<Image
src={"/gaza.png"}
alt="Recommended Article"
width={400}
height={200}
className="rounded-lg w-full h-auto object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-60 text-white p-3 rounded-b-lg">
<p className="text-sm font-semibold leading-tight">
Bom Bunuh Diri Guncang Gereja di Damaskus, 20 Orang Tewas
dan Puluhan Terluka
</p>
<p className="text-xs text-gray-300 mt-1">
📅 23 JUNI 2025
</p> </p>
</div> </div>
</div> ))}
<div className="space-y-3">
<div className="flex space-x-3">
<Image
src={"/perang.png"}
alt="OPM Serang Gereja"
width={80}
height={60}
className="rounded object-cover w-[80px] h-[60px]"
/>
<div>
<p className="text-sm font-semibold leading-snug">
OPM Mulai Kehilangan Simpati dari Masyarakat Papua Usai
Serang Gereja
</p>
<p className="text-xs text-gray-500 mt-1">
📅 15 JUNI 2025
</p>
</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>
</div> </div>

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { getListArticle } from "@/service/article"; import { getListArticle } from "@/service/article";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
type Article = { type Article = {
@ -64,6 +65,7 @@ export default function Header() {
key={index} key={index}
className="border border-gray-200 overflow-hidden shadow-sm bg-white h-[440px]" className="border border-gray-200 overflow-hidden shadow-sm bg-white h-[440px]"
> >
<Link href={`/detail/${article?.id}`}>
<Image <Image
src={article.thumbnailUrl} src={article.thumbnailUrl}
alt={article.title} alt={article.title}
@ -78,8 +80,16 @@ export default function Header() {
<h3 className="text-lg font-semibold text-gray-900 mb-4 leading-snug px-8"> <h3 className="text-lg font-semibold text-gray-900 mb-4 leading-snug px-8">
{article.title} {article.title}
</h3> </h3>
<p className="text-xs text-gray-400">{article.createdAt}</p> <p className="text-xs text-gray-400">
{" "}
{new Date(article.createdAt).toLocaleDateString("id-ID", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</p>
</div> </div>
</Link>
</div> </div>
))} ))}
</div> </div>

View File

@ -4,6 +4,7 @@ import { Card } from "../ui/card";
import Image from "next/image"; import Image from "next/image";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { getListArticle } from "@/service/article"; import { getListArticle } from "@/service/article";
import Link from "next/link";
type Article = { type Article = {
id: number; id: number;
@ -80,7 +81,11 @@ export default function Beranda() {
</h2> </h2>
<ul className="space-y-4"> <ul className="space-y-4">
{articles.map((news, i) => ( {articles.map((news, i) => (
<li key={i} className="flex gap-3 items-start"> <li key={i}>
<Link
className="flex gap-3 items-start"
href={`/detail/${news?.id}`}
>
<Image <Image
src={news?.thumbnailUrl as string} src={news?.thumbnailUrl as string}
width={100} width={100}
@ -92,6 +97,7 @@ export default function Beranda() {
<p className="text-sm leading-snug font-semibold px-2"> <p className="text-sm leading-snug font-semibold px-2">
{news.title} {news.title}
</p> </p>
</Link>
</li> </li>
))} ))}
</ul> </ul>
@ -100,9 +106,10 @@ export default function Beranda() {
<h2 className="text-lg font-semibold border-b ">Berita Opini</h2> <h2 className="text-lg font-semibold border-b ">Berita Opini</h2>
<ul className="space-y-3 "> <ul className="space-y-3 ">
{articles.map((news, i) => ( {articles.map((news, i) => (
<li <li key={i}>
key={i} <Link
className="flex items-start gap-2 text-sm font-medium border-b" className="flex items-start gap-2 text-sm font-medium border-b"
href={`/detail/${news?.id}`}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -122,6 +129,7 @@ export default function Beranda() {
</g> </g>
</svg> </svg>
{news.title} {news.title}
</Link>
</li> </li>
))} ))}
</ul> </ul>
@ -133,6 +141,7 @@ export default function Beranda() {
</h2> </h2>
<div className="space-y-4 "> <div className="space-y-4 ">
<div> <div>
<Link href={`/detail/${articles[0]?.id}`}>
<div className="relative"> <div className="relative">
<img <img
src={articles[0]?.thumbnailUrl} src={articles[0]?.thumbnailUrl}
@ -143,7 +152,9 @@ export default function Beranda() {
{articles[0]?.categories?.[0]?.title || "TANPA KATEGORI"} {articles[0]?.categories?.[0]?.title || "TANPA KATEGORI"}
</span> </span>
</div> </div>
<h3 className="text-base font-bold mt-2">{articles[0]?.title}</h3> <h3 className="text-base font-bold mt-2">
{articles[0]?.title}
</h3>
<p className="text-xs text-[#A0A0A0] mt-1 flex items-center gap-1"> <p className="text-xs text-[#A0A0A0] mt-1 flex items-center gap-1">
{" "} {" "}
<span className="text-black"> <span className="text-black">
@ -163,7 +174,14 @@ export default function Beranda() {
/> />
</g> </g>
</svg>{" "} </svg>{" "}
{articles[0]?.createdAt} {new Date(articles[0]?.createdAt).toLocaleDateString(
"id-ID",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4" className="h-4 w-4"
@ -183,16 +201,22 @@ export default function Beranda() {
<p className="text-sm font-serif text-[#A0A0A0] mt-3 line-clamp-3"> <p className="text-sm font-serif text-[#A0A0A0] mt-3 line-clamp-3">
{articles[0]?.description} {articles[0]?.description}
</p> </p>
</Link>
</div> </div>
{articles.slice(1).map((item, i) => ( {articles.slice(1).map((item, i) => (
<div key={i} className="flex items-start gap-3"> <div key={i}>
<Link
className="flex items-start gap-3"
href={`/detail/${item?.id}`}
>
<img <img
src={item.thumbnailUrl} src={item.thumbnailUrl}
alt={item.title} alt={item.title}
className="w-24 h-16 object-cover" className="w-24 h-16 object-cover"
/> />
<p className="text-sm font-semibold">{item.title}</p> <p className="text-sm font-semibold">{item.title}</p>
</Link>
</div> </div>
))} ))}
</div> </div>
@ -222,9 +246,10 @@ export default function Beranda() {
> >
{articles.length > 0 ? ( {articles.length > 0 ? (
articles.map((article) => ( articles.map((article) => (
<div <div key={article.id}>
key={article.id} <Link
className="flex-shrink-0 flex items-start gap-3 w-[188px]" className="flex-shrink-0 flex items-start gap-3 w-[188px]"
href={`/detail/${article?.id}`}
> >
<img <img
src={article.thumbnailUrl || "/placeholder.jpg"} src={article.thumbnailUrl || "/placeholder.jpg"}
@ -234,6 +259,7 @@ export default function Beranda() {
<p className="text-xs font-medium w-[150px] line-clamp-3"> <p className="text-xs font-medium w-[150px] line-clamp-3">
{article.title} {article.title}
</p> </p>
</Link>
</div> </div>
)) ))
) : ( ) : (
@ -252,6 +278,7 @@ export default function Beranda() {
{articles.length > 0 ? ( {articles.length > 0 ? (
articles.map((article) => ( articles.map((article) => (
<div key={article.id}> <div key={article.id}>
<Link href={`/detail/${article?.id}`}>
<img <img
src={article.thumbnailUrl || "/komjen-pol.jpg"} src={article.thumbnailUrl || "/komjen-pol.jpg"}
alt={article.title} alt={article.title}
@ -303,6 +330,7 @@ export default function Beranda() {
READ MORE READ MORE
</button> </button>
</div> </div>
</Link>
</div> </div>
)) ))
) : ( ) : (
@ -324,6 +352,10 @@ export default function Beranda() {
<div <div
key={i} key={i}
className="bg-white shadow-md rounded-none overflow-hidden border" className="bg-white shadow-md rounded-none overflow-hidden border"
>
<Link
className="bg-white shadow-md rounded-none overflow-hidden border"
href={`/detail/${news?.id}`}
> >
<div className="relative"> <div className="relative">
<img <img
@ -357,6 +389,7 @@ export default function Beranda() {
<p className="text-xs text-gray-400">{news.createdAt}</p> <p className="text-xs text-gray-400">{news.createdAt}</p>
</div> </div>
</div> </div>
</Link>
</div> </div>
))} ))}
</div> </div>
@ -366,7 +399,8 @@ export default function Beranda() {
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{articles.map((news, i) => ( {articles.map((news, i) => (
<div key={i} className="flex gap-4"> <div key={i}>
<Link className="flex gap-4" href={`/detail/${news?.id}`}>
<img <img
src={news.thumbnailUrl} src={news.thumbnailUrl}
alt={news.title} alt={news.title}
@ -375,6 +409,7 @@ export default function Beranda() {
<p className="text-sm font-semibold text-gray-900 hover:underline leading-snug"> <p className="text-sm font-semibold text-gray-900 hover:underline leading-snug">
{news.title} {news.title}
</p> </p>
</Link>
</div> </div>
))} ))}
</div> </div>
@ -396,7 +431,11 @@ export default function Beranda() {
</h2> </h2>
{articles.map((recentNews, i) => ( {articles.map((recentNews, i) => (
<div key={i} className="flex flex-col sm:flex-row gap-4 mt-4"> <div key={i}>
<Link
className="flex flex-col sm:flex-row gap-4 mt-4"
href={`/detail/${recentNews?.id}`}
>
<img <img
src={recentNews.thumbnailUrl || "/komjen-pol.jpg"} src={recentNews.thumbnailUrl || "/komjen-pol.jpg"}
alt={recentNews.title} alt={recentNews.title}
@ -417,6 +456,7 @@ export default function Beranda() {
{recentNews.description} {recentNews.description}
</p> </p>
</div> </div>
</Link>
</div> </div>
))} ))}
@ -438,9 +478,10 @@ export default function Beranda() {
<aside className="w-full md:w-full lg:w-full xl:w-[312px] space-y-6 shrink-0"> <aside className="w-full md:w-full lg:w-full xl:w-[312px] space-y-6 shrink-0">
{articles.map((news, i) => ( {articles.map((news, i) => (
<Card <div className="border" key={i}>
key={i} <Link
className="rounded-none overflow-hidden bg-white shadow-sm p-0 gap-1" className="rounded-none overflow-hidden bg-white shadow-sm p-0 gap-1"
href={`/detail/${news?.id}`}
> >
<div className="relative"> <div className="relative">
{news?.thumbnailUrl && ( {news?.thumbnailUrl && (
@ -457,11 +498,13 @@ export default function Beranda() {
)} )}
</div> </div>
<div className="p-4"> <div className="p-4">
<h3 className="text-base font-bold leading-snug">{news.title}</h3> <h3 className="text-base font-bold leading-snug">
{news.title}
</h3>
{news.createdByName && news.createdAt && ( {news.createdByName && news.createdAt && (
<p className="text-xs text-gray-500 mt-2 font-medium mb-4"> <p className="text-xs text-gray-500 mt-2 font-medium mb-4">
BY <span className="text-black">{news.createdByName}</span> ·{" "} BY <span className="text-black">{news.createdByName}</span>{" "}
{news.createdAt} · {news.createdAt}
</p> </p>
)} )}
{news.description && ( {news.description && (
@ -470,7 +513,8 @@ export default function Beranda() {
</p> </p>
)} )}
</div> </div>
</Card> </Link>
</div>
))} ))}
<div className="relative w-auto max-w-full h-[300px] overflow-hidden flex items-center mx-auto border my-6 rounded"> <div className="relative w-auto max-w-full h-[300px] overflow-hidden flex items-center mx-auto border my-6 rounded">
<Image <Image

BIN
public/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB