Skip to content

Commit

Permalink
feat: implement infinite scrolling for posts with loading state and p…
Browse files Browse the repository at this point in the history
…agination
  • Loading branch information
ifalfahri committed Jan 13, 2025
1 parent 97e4710 commit 49e6002
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 18 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"prisma": "^6.2.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.14.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
76 changes: 59 additions & 17 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,60 @@

import { KeluhAdd } from "@/components/keluh-add";
import { KeluhCard } from "@/components/keluh-card";
import { SkeletonCard } from "@/components/skeleton-card";
import { Button } from "@/components/ui/button";
import { InteractiveGridPattern } from "@/components/ui/interactive-grid-pattern";
import { Navbar } from "@/components/ui/navbar";
import { getPosts } from "@/lib/storage";
import { cn } from "@/lib/utils";
import { MessageSquarePlus } from "lucide-react";
import Image from "next/image";
import { useEffect, useState } from "react";
import { useInView } from "react-intersection-observer";
import { KeluhPost } from "./types";
import { Navbar } from "@/components/ui/navbar";
import Image from "next/image";
import { SkeletonCard } from "@/components/skeleton-card";

export default function Home() {
const [posts, setPosts] = useState<KeluhPost[]>([]);
const [isNewPostOpen, setIsNewPostOpen] = useState(false);
const [isWibuMode, setIsWibuMode] = useState(false);
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(0);

const { ref, inView } = useInView();

const loadPosts = async (reset = false) => {
if (!hasMore && !reset) return;

const loadPosts = async () => {
const posts = await getPosts();
setPosts(posts);
setLoading(false);
try {
const newPosts = await getPosts(reset ? 0 : posts.length, 10);

if (reset) {
setPosts(newPosts);
setPage(1);
} else {
setPosts((prev) => [...prev, ...newPosts]);
setPage((prev) => prev + 1);
}

setHasMore(newPosts.length === 10);
} catch (error) {
console.error("Error loading posts:", error);
} finally {
setLoading(false);
}
};

useEffect(() => {
loadPosts();
}, []);

useEffect(() => {
if (inView && !loading) {
loadPosts();
}
}, [inView]);

return (
<main className="relative min-h-screen bg-gray-50 dark:bg-zinc-900">
<InteractiveGridPattern
Expand All @@ -42,13 +69,18 @@ export default function Home() {
/>
<Navbar isWibuMode={isWibuMode} setIsWibuMode={setIsWibuMode} />
<div className="container mx-auto px-4 py-8 pt-24">
<div className="flex items-center space-x-2 justify-center mb-6">

</div>
<div className="flex items-center space-x-2 justify-center mb-6"></div>
<div className="flex flex-col items-center mb-12">
{isWibuMode ? (
{isWibuMode ? (
<div className="z-10 mb-7 -mt-2">
<Image src="/keluhkesah.png" alt="Keluh Kesah Logo" width={400} height={400} className="w-80 sm:w-96" unoptimized />
<Image
src="/keluhkesah.png"
alt="Keluh Kesah Logo"
width={400}
height={400}
className="w-80 sm:w-96"
unoptimized
/>
</div>
) : (
<div>
Expand All @@ -70,24 +102,34 @@ export default function Home() {
</Button>
</div>

{loading ? (
{loading && posts.length === 0 ? (
<div className="relative z-10 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-7xl mx-auto">
{Array.from({ length: 6 }).map((_, index) => (
<SkeletonCard key={index} />
<SkeletonCard key={`skeleton-${index}`} />
))}
</div>
) : (
<div className="relative z-10 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-7xl mx-auto">
{posts.map((post) => (
<KeluhCard key={post.id} post={post} onUpdate={loadPosts} />
<KeluhCard
key={`post-${post.id}`}
post={post}
onUpdate={() => loadPosts(true)}
/>
))}

{hasMore && (
<div ref={ref} className="col-span-full flex justify-center p-4">
<div className="loader" />
</div>
)}
</div>
)}

<KeluhAdd
open={isNewPostOpen}
onOpenChange={setIsNewPostOpen}
onPostCreated={loadPosts}
onPostCreated={() => loadPosts(true)}
/>
</div>
</main>
Expand Down
4 changes: 3 additions & 1 deletion src/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ interface NewComment {
text: string;
}

export async function getPosts() {
export async function getPosts(skip = 0, take = 12) {
return await prisma.post.findMany({
include: { comments: true },
orderBy: { timestamp: 'desc' },
skip,
take,
});
}

Expand Down

0 comments on commit 49e6002

Please sign in to comment.