
import Link from ‘next/link’;
type SearchParams = Promise<{ page?: string; }>;
const POSTS_PER_PAGE = 6;
async function getNews(page: number) {
try {
const res = await fetch(http://localhost/cv/wp-json/wp/v2/posts?page=${page}&per_page=${POSTS_PER_PAGE},
{
cache: ‘no-store’,
}
);
if (!res.ok) {
throw new Error('Failed to fetch news');
}
const data = await res.json();
/* TOTAL POSTS FROM WORDPRESS HEADER */
const totalPosts = Number(
res.headers.get('X-WP-Total')
);
const totalPages = Number(
res.headers.get('X-WP-TotalPages')
);
return {
posts: data,
totalPosts,
totalPages,
};
} catch (error) {
console.error(‘Error fetching news:’, error);
return {
posts: [],
totalPosts: 0,
totalPages: 0,
};
}
}
export default async function NewsPage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const params = await searchParams;
const currentPage = Number(params.page) || 1;
const {
posts,
totalPosts,
totalPages,
} = await getNews(currentPage);
return (
{/* BACKGROUND */}
<div className="absolute bottom-0 right-0 h-[500px] w-[500px] rounded-full bg-purple-500/10 blur-3xl" />
<div className="absolute left-1/2 top-1/3 h-[400px] w-[400px] -translate-x-1/2 rounded-full bg-blue-500/5 blur-3xl" />
</div>
<div className="mx-auto max-w-7xl px-6 py-12 lg:px-10">
{/* HERO */}
<section className="relative overflow-hidden rounded-[40px] border border-white/10 bg-white/[0.03] p-8 backdrop-blur-2xl lg:p-12">
<div className="absolute right-0 top-0 h-80 w-80 rounded-full bg-cyan-500/10 blur-3xl" />
<div className="relative z-10 flex flex-col gap-10 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-4xl">
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-cyan-400/20 bg-cyan-500/10 px-4 py-2 text-sm text-cyan-300">
<div className="h-2 w-2 rounded-full bg-cyan-400" />
AI Research & Publications
</div>
<h1 className="text-5xl font-black tracking-tight text-white md:text-6xl">
News & Insights
</h1>
<p className="mt-6 max-w-3xl text-lg leading-relaxed text-zinc-400">
Explore modern AI research, software engineering,
machine learning innovation, and digital technology
publications directly from your CMS.
</p>
</div>
<Link
href="/dashboard"
className="inline-flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-4 text-sm text-zinc-300 backdrop-blur-xl transition hover:border-cyan-400/20 hover:bg-cyan-500/10 hover:text-cyan-300"
>
<span>←</span>
Back Dashboard
</Link>
</div>
</section>
{/* TOP BAR */}
<section className="mt-10 flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
{/* SEARCH */}
<div className="relative w-full max-w-xl">
<input
type="text"
placeholder="Search articles, AI research, innovation..."
className="w-full rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-5 pr-14 text-white outline-none backdrop-blur-xl placeholder:text-zinc-500 focus:border-cyan-400/20"
/>
<div className="absolute right-5 top-1/2 -translate-y-1/2 text-zinc-500">
⌕
</div>
</div>
{/* STATS */}
<div className="flex items-center gap-4">
<div className="rounded-2xl border border-white/10 bg-white/[0.03] px-5 py-4 backdrop-blur-xl">
<p className="text-xs uppercase tracking-widest text-zinc-500">
Total Articles
</p>
<h3 className="mt-1 text-2xl font-bold text-white">
{totalPosts}
</h3>
</div>
<div className="rounded-2xl border border-cyan-400/20 bg-cyan-500/10 px-5 py-4">
<p className="text-xs uppercase tracking-widest text-cyan-300">
Current Page
</p>
<h3 className="mt-1 text-2xl font-bold text-white">
{currentPage}
</h3>
</div>
</div>
</section>
{/* EMPTY */}
{posts.length === 0 ? (
<div className="mt-12 rounded-[32px] border border-red-500/20 bg-red-500/10 p-14 text-center">
<div className="mb-5 text-6xl">📰</div>
<h2 className="text-3xl font-bold">
No Articles Found
</h2>
<p className="mt-4 text-zinc-400">
Make sure WordPress and XAMPP are running correctly.
</p>
</div>
) : (
<>
{/* GRID */}
<section className="mt-12 grid grid-cols-1 gap-8 md:grid-cols-2 xl:grid-cols-3">
{posts.map((item: any) => {
/* IMAGE FROM CONTENT */
const content =
item.content?.rendered || '';
const imageMatch = content.match(
/<img[^>]+src="([^">]+)"/
);
const image = imageMatch
? imageMatch[1]
: null;
return (
<article
key={item.id}
className="group overflow-hidden rounded-[32px] border border-white/10 bg-white/[0.03] backdrop-blur-xl transition duration-500 hover:-translate-y-2 hover:border-cyan-400/20 hover:bg-white/[0.05]"
>
{/* IMAGE */}
<div className="relative h-64 overflow-hidden bg-zinc-900">
{image ? (
<img
src={image}
alt={item.title.rendered}
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-cyan-500/10 to-purple-500/10 text-zinc-500">
No Image
</div>
)}
{/* OVERLAY */}
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/20 to-transparent" />
{/* CATEGORY */}
<div className="absolute bottom-4 left-4 rounded-full border border-white/10 bg-black/40 px-4 py-2 text-xs text-white backdrop-blur-xl">
AI Publication
</div>
</div>
{/* CONTENT */}
<div className="p-7">
<div className="mb-4 flex items-center justify-between">
<p className="text-sm text-zinc-500">
{new Date(
item.date
).toLocaleDateString()}
</p>
<div className="flex gap-1">
<div className="h-2 w-2 rounded-full bg-cyan-400" />
<div className="h-2 w-2 rounded-full bg-white/20" />
<div className="h-2 w-2 rounded-full bg-white/20" />
</div>
</div>
<h2
className="mb-4 line-clamp-2 text-2xl font-bold leading-snug text-white transition group-hover:text-cyan-300"
dangerouslySetInnerHTML={{
__html: item.title.rendered,
}}
/>
<div
className="mb-8 line-clamp-3 text-sm leading-7 text-zinc-400"
dangerouslySetInnerHTML={{
__html: item.excerpt.rendered,
}}
/>
<Link
href={`/dashboard/news/${item.slug}`}
className="inline-flex items-center gap-3 rounded-2xl border border-cyan-400/20 bg-cyan-500/10 px-5 py-3 text-sm font-medium text-cyan-300 transition hover:bg-cyan-500/20"
>
Read Article
<span>→</span>
</Link>
</div>
</article>
);
})}
</section>
{/* PAGINATION */}
<section className="mt-16 flex flex-wrap items-center justify-center gap-3">
{/* PREVIOUS */}
{currentPage > 1 && (
<Link
href={`/dashboard/news?page=${currentPage - 1}`}
className="rounded-2xl border border-white/10 bg-white/[0.03] px-5 py-3 text-sm text-zinc-300 transition hover:border-cyan-400/20 hover:bg-cyan-500/10 hover:text-cyan-300"
>
← Previous
</Link>
)}
{/* PAGE NUMBERS */}
{Array.from(
{ length: totalPages },
(_, index) => index + 1
).map((page) => (
<Link
key={page}
href={`/dashboard/news?page=${page}`}
className={`flex h-12 w-12 items-center justify-center rounded-2xl border text-sm font-medium transition ${
currentPage === page
? 'border-cyan-400/20 bg-cyan-400 text-black'
: 'border-white/10 bg-white/[0.03] text-zinc-300 hover:border-cyan-400/20 hover:bg-cyan-500/10 hover:text-cyan-300'
}`}
>
{page}
</Link>
))}
{/* NEXT */}
{currentPage < totalPages && (
<Link
href={`/dashboard/news?page=${currentPage + 1}`}
className="rounded-2xl border border-white/10 bg-white/[0.03] px-5 py-3 text-sm text-zinc-300 transition hover:border-cyan-400/20 hover:bg-cyan-500/10 hover:text-cyan-300"
>
Next →
</Link>
)}
</section>
</>
)}
</div>
</main>
);
}