diff --git a/src/app/api/posts/[id]/comments/route.ts b/src/app/api/posts/[id]/comments/route.ts new file mode 100644 index 0000000..0ea4bd1 --- /dev/null +++ b/src/app/api/posts/[id]/comments/route.ts @@ -0,0 +1,36 @@ +import { LimitType } from "@lens-protocol/client"; +import { type NextRequest, NextResponse } from "next/server"; +import { lensItemToPost } from "~/components/post/Post"; +import { getLensClient } from "~/utils/getLensClient"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: NextRequest, { params }: { params: { id: string } }) { + const id = params.id; + const cursor = req.nextUrl.searchParams.get("cursor") ?? undefined; + + if (!id) { + return NextResponse.json({ error: "Missing publication id" }, { status: 400 }); + } + + try { + const { client } = await getLensClient(); + + const comments = await client.publication.fetchAll({ + where: { commentOn: { id } }, + limit: LimitType.TwentyFive, + cursor, + }); + + if (!comments.items) { + throw new Error("No comments found"); + } + + const commentsPosts = comments.items.map((comment) => lensItemToPost(comment)); + + return NextResponse.json({ comments: commentsPosts, nextCursor: comments.pageInfo.next }, { status: 200 }); + } catch (error) { + console.error("Failed to load comments: ", error.message); + return NextResponse.json({ error: `${error.message}` }, { status: 500 }); + } +} diff --git a/src/components/LoadingIcon.tsx b/src/components/LoadingIcon.tsx index 7734c71..3f0497a 100644 --- a/src/components/LoadingIcon.tsx +++ b/src/components/LoadingIcon.tsx @@ -1,5 +1,9 @@ import { LoaderCircleIcon } from "lucide-react"; -export const LoadingSpinner = ({ size = 22 }: { size?: number }) => { - return ; +export const LoadingSpinner = ({ size = 22, className }: { size?: number; className?: string }) => { + return ( +
+ +
+ ); }; diff --git a/src/components/post/PostComments.tsx b/src/components/post/PostComments.tsx index 3835ff4..330ca72 100644 --- a/src/components/post/PostComments.tsx +++ b/src/components/post/PostComments.tsx @@ -1,28 +1,70 @@ +import { PlusIcon } from "lucide-react"; +import React, { useState, useEffect, useCallback } from "react"; +import { LoadingSpinner } from "../LoadingIcon"; +import { Button } from "../ui/button"; import type { Post } from "./Post"; import { PostView } from "./PostView"; import PostWizard from "./PostWizard"; export const PostComments = ({ post, isExpanded }: { post: Post; isExpanded: boolean }) => { - const comments = post.comments.map((comment, index) => ( + const [comments, setComments] = useState(post.comments); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [cursor, setCursor] = useState(undefined); + + useEffect(() => { + isExpanded ? loadMoreComments() : setComments(post.comments); + }, [isExpanded]); + + const loadMoreComments = useCallback(async () => { + if (loading) return; + setLoading(true); + try { + const res = await fetch(`/api/posts/${post.id}/comments?${cursor ? `cursor=${cursor}` : ""}`, { + method: "GET", + }); + if (!res.ok) throw new Error(res.statusText); + const { comments: newComments, nextCursor } = await res.json(); + const diffComments = newComments.filter((comment) => !post.comments.find((c) => c.id === comment.id)); + setComments((prevComments) => [...prevComments, ...diffComments]); + setCursor(nextCursor); + } catch (err) { + setError(`Could not fetch comments: ${err.message}`); + } finally { + setLoading(false); + } + }, [cursor, loading, post.id]); + + const commentElements = comments.map((comment, index) => ( )); + if (error) throw new Error(error); + return (
- {isExpanded && ( -
- -
- )} -
    {comments}
+
+ {isExpanded && ( +
+ +
+ )} +
    {commentElements}
+ {loading && } + {isExpanded && cursor && !loading && ( + + )} +
); };