feat: add comments

This commit is contained in:
gosangam
2026-06-13 01:30:18 +05:30
parent 6a0559e608
commit cc5633c37a
6 changed files with 900 additions and 12 deletions
+92
View File
@@ -85,6 +85,32 @@ export type Post = {
viewer_has_liked?: boolean;
/** How many users are tagged on this post (drives the mentions icon). */
mentions_count?: number;
/** The post's pinned comment, if any — hydrated so the card footer can show
* it without a separate fetch. Null/absent when nothing is pinned. */
pinned_comment?: Comment | null;
};
/** A comment or reply on a post. Threading is one level deep: `parent_id` is
* null for a top-level comment and otherwise the thread root. `reply_to_handle`
* is who a reply was addressed to (for the "Replying to @handle" prefix). */
export type Comment = {
id: string;
post_id: string;
user_id: string;
parent_id?: string | null;
body: string;
likes_count: number;
/** Non-null when this comment is the post's pinned comment. */
pinned_at?: string | null;
created_at: string;
updated_at: string;
author_name: string;
author_handle: string;
author_avatar_url?: string | null;
reply_to_handle?: string | null;
viewer_has_liked: boolean;
/** Number of replies under a top-level comment (0 on replies). */
reply_count: number;
};
/** Result of a like / unlike toggle: the new viewer state + effective total. */
@@ -282,6 +308,68 @@ export const posts = {
},
};
// ─── Comments ────────────────────────────────────────────────────────────
export type CommentPageParams = { limit?: number; offset?: number };
function commentPageQuery({ limit = 20, offset = 0 }: CommentPageParams) {
return new URLSearchParams({
limit: String(limit),
offset: String(offset),
}).toString();
}
export const comments = {
/** A post's top-level comments — pinned first, then newest. Public. */
list(postId: string, params: CommentPageParams = {}) {
return call<Comment[]>(
`/posts/${postId}/comments?${commentPageQuery(params)}`,
);
},
/** A thread's replies, oldest first. Public. */
replies(commentId: string, params: CommentPageParams = {}) {
return call<Comment[]>(
`/comments/${commentId}/replies?${commentPageQuery(params)}`,
);
},
/** Post a comment, or a reply when `parentId` is set. */
create(postId: string, body: string, parentId?: string) {
return call<Comment>(`/posts/${postId}/comments`, {
method: "POST",
body: JSON.stringify({ body, parent_id: parentId ?? null }),
});
},
/** Like / unlike a comment; returns the new like count. */
like(commentId: string) {
return call<{ likes_count: number }>(`/comments/${commentId}/like`, {
method: "POST",
});
},
unlike(commentId: string) {
return call<{ likes_count: number }>(`/comments/${commentId}/like`, {
method: "DELETE",
});
},
/** Pin / unpin a comment (post author only; top-level only). */
pin(commentId: string) {
return call<void>(`/comments/${commentId}/pin`, { method: "POST" });
},
unpin(commentId: string) {
return call<void>(`/comments/${commentId}/pin`, { method: "DELETE" });
},
/** Delete a comment (author or post owner). Removes the whole thread. */
delete(commentId: string) {
return call<void>(`/comments/${commentId}`, { method: "DELETE" });
},
/** Report a comment. */
report(commentId: string, category: PostReportCategory, reason: string) {
return call<void>(`/comments/${commentId}/report`, {
method: "POST",
body: JSON.stringify({ category, reason }),
});
},
};
// ─── Bookmarks ───────────────────────────────────────────────────────────
export const bookmarks = {
@@ -486,6 +574,10 @@ export type AppNotification = {
* row represents (renders "X and N-1 others liked your post"). */
post_id?: string | null;
actor_count?: number;
/** Handle of the post's author (when the row references a post), so comment
* / reply notifications can deep-link to the post even though the actor
* isn't its owner. */
post_author_handle?: string | null;
};
export const notifications = {