Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useSearchParams } from "next/navigation";
|
|
import { useEffect, useState, Suspense } from "react";
|
|
import Link from "next/link";
|
|
|
|
interface Post {
|
|
id: number;
|
|
title: string;
|
|
content?: string;
|
|
createdAt: string;
|
|
author?: {
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
// Disable static generation
|
|
export const dynamic = "force-dynamic";
|
|
|
|
function PostsList() {
|
|
const searchParams = useSearchParams();
|
|
const page = parseInt(searchParams.get("page") || "1");
|
|
|
|
const [posts, setPosts] = useState<Post[]>([]);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchPosts() {
|
|
setIsLoading(true);
|
|
try {
|
|
const res = await fetch(`/api/posts?page=${page}`);
|
|
if (!res.ok) {
|
|
throw new Error("Failed to fetch posts");
|
|
}
|
|
const data = await res.json();
|
|
setPosts(data.posts);
|
|
setTotalPages(data.totalPages);
|
|
} catch (error) {
|
|
console.error("Error fetching posts:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchPosts();
|
|
}, [page]);
|
|
|
|
return (
|
|
<>
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center space-x-2 min-h-[200px]">
|
|
<div className="w-6 h-6 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
<p className="text-gray-600">Loading...</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{posts.length === 0 ? (
|
|
<p className="text-gray-600">No posts available.</p>
|
|
) : (
|
|
<ul className="space-y-6 w-full max-w-4xl mx-auto">
|
|
{posts.map((post) => (
|
|
<li
|
|
key={post.id}
|
|
className="border p-6 rounded-lg shadow-md bg-white"
|
|
>
|
|
<Link
|
|
href={`/posts/${post.id}`}
|
|
className="text-2xl font-semibold text-blue-600 hover:underline"
|
|
>
|
|
{post.title}
|
|
</Link>
|
|
<p className="text-sm text-gray-500">
|
|
by {post.author?.name || "Anonymous"}
|
|
</p>
|
|
<p className="text-xs text-gray-400">
|
|
{new Date(post.createdAt).toLocaleDateString("en-US", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
})}
|
|
</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
|
|
{/* Pagination Controls */}
|
|
<div className="flex justify-center space-x-4 mt-8">
|
|
{page > 1 && (
|
|
<Link href={`/posts?page=${page - 1}`}>
|
|
<button className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300">
|
|
Previous
|
|
</button>
|
|
</Link>
|
|
)}
|
|
{page < totalPages && (
|
|
<Link href={`/posts?page=${page + 1}`}>
|
|
<button className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300">
|
|
Next
|
|
</button>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function PostsPage() {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-start p-8">
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="w-10 h-10 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
<p className="ml-3 text-gray-600">Loading page...</p>
|
|
</div>
|
|
}
|
|
>
|
|
<PostsList />
|
|
</Suspense>
|
|
</div>
|
|
);
|
|
}
|